All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/82] DC Patches August 17, 2026
@ 2026-08-18 20:14 IVAN.LIPSKI
  2026-08-18 20:14 ` [PATCH 01/82] drm/amd/display: Fall back to overlay cursor on dcn4x when top plane doesn't fill CRTC IVAN.LIPSKI
                   ` (82 more replies)
  0 siblings, 83 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:14 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

From: Ivan Lipski <ivan.lipski@amd.com>

This DC patchset brings improvements in multiple areas. In summary, we highlight:

     - Fix HPO test regressions.
     - Refactor DPP_SET_INPUT_TRANSFER_FUNC to drop pipe_ctx params.
     - Use fast update path for address-only plane flips.
     - Split OPTC_PIPE_CONTROL_LOCK into smaller helpers.
     - Fix DPREFCLK override when SMU isn't available.
     - Fix CalculateFlipSchedule calculation.
     - Add hook to disable alt-ch in PMO.
     - Update alt-ch size calculations.
     - Enable min dispclk ODM on DCN42.
     - Remove RMCM tetrahedral cube from dc_plane_state.
     - Fix mismatch number of OPP/DPP accounting.
     - Adjust vblank_nom policy for HW SDP throttling.
     - Add passthrough visual confirm.
     - Expand amdgpu_dm KUnit test coverage across cursor, MST, EDID, HDMI,
       IRQ, i2c, framebuffer, tiling/modifier and HPD paths.
     - Various fixes for Kunit tests related to HDMI FRL, IRQ and cursors.

Cc: Dan Wheeler <daniel.wheeler@amd.com>


Alex Hung (38):
  drm/amd/display: Test writeback connector
  drm/amd/display: Test GPU memory allocation
  drm/amd/display: Test EDID quirks and ACPI EDID read
  drm/amd/display: Test execute_synaptics_rc_command failures
  drm/amd/display: Test MST stream feature read failure
  drm/amd/display: Test dm_helpers_submit_i2c_over_aux
  drm/amd/display: Test GPU memory allocate and free helpers
  drm/amd/display: Test dm_helpers_dmub_set_config_sync
  drm/amd/display: Test dm_helpers_is_dp_sink_present
  drm/amd/display: Test dm_helpers_read_local_edid
  drm/amd/display: Test dp_handle_test_pattern_request patterns
  drm/amd/display: Test DMUB reg callbacks
  drm/amd/display: Test VBIOS bounding box
  drm/amd/display: Test dm_init_microcode
  drm/amd/display: Test dm_dmub_sw_init
  drm/amd/display: Test MST sideband message ack path
  drm/amd/display: Test dm_dp_mst_get_modes without a remote EDID
  drm/amd/display: Test dm_dp_mst_get_modes with a remote EDID
  drm/amd/display: Test dm_dp_mst_detect DPCD probe and unplug
  drm/amd/display: Test MST connector register and unregister
  drm/amd/display: Test dm_dp_mst_connector_destroy
  drm/amd/display: Test plane state duplicate and destroy
  drm/amd/display: Test modifier list de-duplication
  drm/amd/display: Test GFX6-8 tiling info from modifiers
  drm/amd/display: Test GFX6-8 tile mode and tile split lookups
  drm/amd/display: Test GFX6-8 modifier calculation
  drm/amd/display: Test GFX6-8 modifier list generation
  drm/amd/display: Test framebuffer prepare and cleanup
  drm/amd/display: Test cursor update and async plane update
  drm/amd/display: Refactor amdgpu_dm_irq_test
  drm/amd/display: Test pageflip completion in the high IRQ handlers
  drm/amd/display: Test writeback handling in dm_crtc_high_irq
  drm/amd/display: Test schedule_dc_vmin_vmax
  drm/amd/display: Test handle_hpd_irq_helper detect and debounce exits
  drm/amd/display: Test HPD RX, HPD init and DMUB callback branches
  drm/amd/display: Test dm_dmub_outbox1_low_irq drain and work guards
  drm/amd/display: Test amdgpu_dm_dce110_register_irq_handlers
  drm/amd/display: Test amdgpu_dm_dcn10_register_irq_handlers

Alvin Lee (3):
  drm/amd/display: Disable alt-ch until dependencies are ready
  drm/amd/display: Add hook to disable alt-ch in PMO
  drm/amd/display: Update alt-ch size calculations

Austin Zheng (1):
  drm/amd/display: Fix CalculateFlipSchedule Calculation

Bhawanpreet Lakha (24):
  drm/amd/display: Cover MST path in encoder atomic_check
  drm/amd/display: Cover amdgpu_dm_encoder_init
  drm/amd/display: Cover MST-start failure in detect_mst
  drm/amd/display: Cover amdgpu_dm_update_connector_after_detect
  drm/amd/display: Cover HDMI infoframe/freesync timing paths
  drm/amd/display: Add amdgpu_dm_connector_poll KUnit tests
  drm/amd/display: Cover hide_secondary_tile_from_userspace
  drm/amd/display: Cover dm_validate_stream_and_context
  drm/amd/display: Cover amdgpu_dm_create_validate_stream_for_sink
  drm/amd/display: Cover amdgpu_dm_connector_mode_valid
  drm/amd/display: Cover mode_valid EDID mgmt path
  drm/amd/display: Cover amdgpu_dm_fill_hdr_info_packet
  drm/amd/display: Cover amdgpu_dm_connector_atomic_check
  drm/amd/display: Cover atomic_check modeset triggers
  drm/amd/display: Cover funcs_force valid EDID path
  drm/amd/display: Cover amdgpu_dm_connector_get_modes
  drm/amd/display: Cover create_eml_sink valid EDID path
  drm/amd/display: Cover amdgpu_set_panel_orientation
  drm/amd/display: Cover amdgpu_dm_prune_primary_tile_modes
  drm/amd/display: Cover add_fs_modes mode generation
  drm/amd/display: Cover add_fs_modes illegal timing skip
  drm/amd/display: Cover hdmi_frl_status_polling_work
  drm/amd/display: Cover amdgpu_dm_i2c_xfer
  drm/amd/display: Cover amdgpu_dm_create_i2c

Dillon Varone (2):
  drm/amd/display: Fix mismatch number of OPP/DPP accounting
  drm/amd/display: Populate vblank_nom according to bounding box

Gabe Teeger (1):
  drm/amd/display: Enable min dispclk ODM on DCN42

Gaghik Khachatrian (1):
  drm/amd/display: Add passthrough visual confirm

Ilya Bakoulin (1):
  drm/amd/display: Clear HUBPREQ_DEBUG_DB on DCN6

Ivan Lipski (2):
  drm/amd/display: Refactor hdmi_frl_status_polling_work for Kunit
    testing
  drm/amd/display: Guard amdgpu_dm_irq_schedule_work against NULL irq_wq

James Lin (2):
  drm/amd/display: Fall back to overlay cursor on dcn4x when top plane
    doesn't fill CRTC
  drm/amd/display: Use fast update path for address-only plane flips

Meera Patel (1):
  drm/amd/display: Fix DPREFCLK override when SMU isn't present or ready
    for DCN315

Nicholas Kazlauskas (1):
  drm/amd/display: Adjust vblank_nom policy for HW SDP tranmission reqs

Rafal Ostrowski (1):
  drm/amd/display: Remove RMCM tetrahedral cube from dc_plane_state

Sridevi Arvindekar (1):
  drm/amd/display: Fixes for HPO test regressions

Taimur Hassan (1):
  drm/amd/display: Promote DC to 3.2.395

Tomasz Siemek (2):
  drm/amd/display: Refactor DPP_SET_INPUT_TRANSFER_FUNC to drop pipe_ctx
  drm/amd/display: Split OPTC_PIPE_CONTROL_LOCK into smaller HWSS blocks

 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c |   10 +-
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h |   10 +
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |   18 +-
 .../display/amdgpu_dm/amdgpu_dm_connector.h   |    7 +
 .../amd/display/amdgpu_dm/amdgpu_dm_cursor.c  |   28 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.c    |   47 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.h    |   15 +
 .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c |    6 +
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c |    3 +
 .../display/amdgpu_dm/amdgpu_dm_mst_types.c   |   12 +-
 .../display/amdgpu_dm/amdgpu_dm_mst_types.h   |    4 +
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.c   |   48 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.h   |   12 +
 .../display/amdgpu_dm/amdgpu_dm_services.c    |   35 +-
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c  |   72 +-
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h  |   16 +
 .../tests/amdgpu_dm_connector_test.c          | 3387 ++++++++++++++++-
 .../amdgpu_dm/tests/amdgpu_dm_cursor_test.c   |   32 +-
 .../amdgpu_dm/tests/amdgpu_dm_dmub_test.c     |  578 ++-
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  |  862 ++++-
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 1117 +++++-
 .../tests/amdgpu_dm_mst_types_test.c          |  741 ++++
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    |  703 +++-
 .../amdgpu_dm/tests/amdgpu_dm_services_test.c |  228 +-
 .../amdgpu_dm/tests/amdgpu_dm_wb_test.c       |  330 ++
 .../dc/clk_mgr/dcn315/dcn315_clk_mgr.c        |    4 +-
 drivers/gpu/drm/amd/display/dc/core/dc.c      |   16 +-
 .../drm/amd/display/dc/core/dc_hw_sequencer.c |  193 +-
 .../gpu/drm/amd/display/dc/core/dc_resource.c |    7 +
 drivers/gpu/drm/amd/display/dc/dc.h           |    3 +-
 drivers/gpu/drm/amd/display/dc/dc_hw_types.h  |   18 +-
 drivers/gpu/drm/amd/display/dc/dce/dce_aux.c  |   12 +-
 .../dml2_0/dml21/dml21_translation_helper.c   |   30 +-
 .../amd/display/dc/dml2_0/dml21/dml21_utils.c |   15 +-
 .../dml21/inc/bounding_boxes/dcn42b_soc_bb.h  |   10 +-
 .../src/dml2_core/dml2_core_dcn4_calcs.c      |   34 +-
 .../dml2_core_dcn5_funcs_initialize.c         |    2 +-
 .../dml2_core_dcn5_funcs_mode_support.c       |   23 +-
 .../dml2_core/dml2_core_dcn6_calcs_dchub.c    |   47 +-
 .../dml2_core_dcn6_funcs_initialize.c         |    2 +
 .../dml2_core_dcn6_funcs_mode_support.c       |   27 +-
 .../src/dml2_core/dml2_core_shared_types.h    |    2 +
 drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c  |    7 +-
 .../amd/display/dc/hubp/dcn60/dcn60_hubp.c    |    3 +
 .../drm/amd/display/dc/hwss/dce/dce_hwseq.c   |   46 +-
 .../drm/amd/display/dc/hwss/dce/dce_hwseq.h   |   12 +-
 .../amd/display/dc/hwss/dce110/dce110_hwseq.c |   16 +-
 .../amd/display/dc/hwss/dce60/dce60_hwseq.c   |    8 +-
 .../amd/display/dc/hwss/dce80/dce80_hwseq.c   |    3 +-
 .../amd/display/dc/hwss/dcn10/dcn10_hwseq.c   |   66 +-
 .../amd/display/dc/hwss/dcn10/dcn10_hwseq.h   |    8 +-
 .../amd/display/dc/hwss/dcn10/dcn10_init.c    |    4 +-
 .../amd/display/dc/hwss/dcn20/dcn20_hwseq.c   |  216 +-
 .../amd/display/dc/hwss/dcn20/dcn20_hwseq.h   |   21 +-
 .../amd/display/dc/hwss/dcn20/dcn20_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn201/dcn201_hwseq.c |   34 +-
 .../amd/display/dc/hwss/dcn201/dcn201_hwseq.h |    5 +-
 .../amd/display/dc/hwss/dcn201/dcn201_init.c  |    4 +-
 .../amd/display/dc/hwss/dcn21/dcn21_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn30/dcn30_hwseq.c   |   51 +-
 .../amd/display/dc/hwss/dcn30/dcn30_hwseq.h   |    8 +-
 .../amd/display/dc/hwss/dcn30/dcn30_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn301/dcn301_init.c  |    3 +-
 .../amd/display/dc/hwss/dcn31/dcn31_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn314/dcn314_init.c  |    3 +-
 .../amd/display/dc/hwss/dcn32/dcn32_hwseq.c   |   64 +-
 .../amd/display/dc/hwss/dcn32/dcn32_hwseq.h   |   10 +-
 .../amd/display/dc/hwss/dcn32/dcn32_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn35/dcn35_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn351/dcn351_init.c  |    3 +-
 .../amd/display/dc/hwss/dcn401/dcn401_hwseq.c |  174 +-
 .../amd/display/dc/hwss/dcn401/dcn401_hwseq.h |    9 +-
 .../amd/display/dc/hwss/dcn401/dcn401_init.c  |    3 +-
 .../amd/display/dc/hwss/dcn42/dcn42_hwseq.c   |   49 +-
 .../amd/display/dc/hwss/dcn42/dcn42_hwseq.h   |   12 +-
 .../amd/display/dc/hwss/dcn42/dcn42_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn60/dcn60_init.c    |    3 +-
 .../drm/amd/display/dc/hwss/hw_sequencer.h    |   55 +-
 .../display/dc/hwss/hw_sequencer_private.h    |   21 +-
 .../gpu/drm/amd/display/dc/inc/hw/hw_shared.h |   10 -
 .../display/dc/link/hwss/link_hwss_hpo_dp.c   |   23 +-
 .../dc/link/protocols/link_dp_capability.c    |    3 +-
 .../dc/resource/dcn42/dcn42_resource.c        |    4 +-
 .../dc/resource/dcn42b/dcn42b_resource.c      |    4 +-
 .../dc/resource/dcn60/dcn60_resource.c        |    2 +
 85 files changed, 8768 insertions(+), 986 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 86+ messages in thread

* [PATCH 01/82] drm/amd/display: Fall back to overlay cursor on dcn4x when top plane doesn't fill CRTC
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
@ 2026-08-18 20:14 ` IVAN.LIPSKI
  2026-08-19  7:27   ` Michel Dänzer
  2026-08-18 20:14 ` [PATCH 02/82] drm/amd/display: Fixes for HPO test regressions IVAN.LIPSKI
                   ` (81 subsequent siblings)
  82 siblings, 1 reply; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:14 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

From: James Lin <PingLei.Lin@amd.com>

[why]
amdgpu_dm_crtc_get_cursor_mode() returns DM_CURSOR_NATIVE_MODE early and
unconditionally for dcn4.x. That early return was added because these
ASICs no longer have the cursor-on-scaled-plane or cursor-on-yuv-plane
restrictions of older DCN, so native cursor is fine in those cases.
However, it also bypasses the "does the top plane fill the CRTC?" (hole)
evaluation further down. When the top/primary plane does not cover the
whole CRTC, the cursor must fall back to an overlay plane so it is
composited over the uncovered region; keeping the native cursor there
produces an incorrect result. As a consequence
igt@amdgpu/amd_cursor_overlay@non-full, which verifies exactly this
native->overlay fallback, fails on dcn42.

[how]
Change unconditional early return with a skip_fmt_scale_restrictions flag.
For dcn4.x keep skipping the YUV / active-color-pipeline / different-scale
overlay triggers (genuinely unnecessary on this hardware), but fall
through to the existing entire_crtc_covered check so a top plane that does
not fill the CRTC still selects DM_CURSOR_OVERLAY_MODE. Native mode is
still chosen when the plane covers the whole CRTC.

Update the amdgpu_dm_cursor KUnit tests accordingly: exercise the dcn4x
path with a full atomic state fixture (full coverage -> native) and add a
dcn4x hole case (top plane does not fill the CRTC -> overlay).

Reviewed-by: Tom Chung <chiahsuan.chung@amd.com>
Signed-off-by: James Lin <PingLei.Lin@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_cursor.c  | 28 ++++++++--------
 .../amdgpu_dm/tests/amdgpu_dm_cursor_test.c   | 32 +++++++++++++------
 2 files changed, 38 insertions(+), 22 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
index 9534848ed240c..c41fce07b8fa8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
@@ -87,12 +87,8 @@ STATIC_IFN_KUNIT int dm_check_cursor_fb(struct amdgpu_crtc *new_acrtc,
 	 * check tiling flags when the FB doesn't have a modifier.
 	 */
 	if (!(fb->flags & DRM_MODE_FB_MODIFIERS)) {
-#if defined(CONFIG_DRM_AMD_DC_DCN6_0) || defined(CONFIG_DRM_AMD_DC_DCN5_0)
 		if (adev->family == AMDGPU_FAMILY_GC_12_0_0
 		    || adev->family == AMDGPU_FAMILY_GC_13_0_1) {
-#else
-		if (adev->family == AMDGPU_FAMILY_GC_12_0_0) {
-#endif
 			linear = AMDGPU_TILING_GET(afb->tiling_flags, GFX12_SWIZZLE_MODE) == 0;
 		} else if (adev->family >= AMDGPU_FAMILY_AI) {
 			linear = AMDGPU_TILING_GET(afb->tiling_flags, SWIZZLE_MODE) == 0;
@@ -289,6 +285,7 @@ int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
 	int underlying_scale_w, underlying_scale_h;
 	int cursor_scale_w, cursor_scale_h;
 	int i;
+	bool skip_fmt_scale_restrictions = false;
 
 	/* Overlay cursor not supported on HW before DCN
 	 * DCN401/420 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions
@@ -300,15 +297,17 @@ int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
 	 */
 	if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) ||
 	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) ||
-#if defined(CONFIG_DRM_AMD_DC_DCN6_0)
 	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) ||
 	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(6, 0, 0) ||
-#else
-	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) ||
-#endif
 	    !dm_crtc_state->base.enable) {
-		*cursor_mode = DM_CURSOR_NATIVE_MODE;
-		return 0;
+		/*
+		 * Newer DCN has no cursor-on-scaled/yuv-plane restriction, so
+		 * skip those overlay triggers below. A plane that does not fill
+		 * the CRTC still needs overlay mode so the cursor renders over
+		 * the hole, so fall through to the coverage check instead of
+		 * unconditionally forcing native mode here.
+		 */
+		skip_fmt_scale_restrictions = true;
 	}
 
 	/* Init cursor_mode to be the same as current */
@@ -407,13 +406,15 @@ int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
 			continue;
 
 		/* Underlying plane is YUV format - use overlay cursor */
-		if (amdgpu_dm_plane_is_video_format(plane_state->fb->format->format)) {
+		if (!skip_fmt_scale_restrictions &&
+		    amdgpu_dm_plane_is_video_format(plane_state->fb->format->format)) {
 			*cursor_mode = DM_CURSOR_OVERLAY_MODE;
 			return 0;
 		}
 
 		/* Underlying plane has an active color pipeline - cursor would be transformed */
-		if (dm_plane_color_pipeline_active(state, plane, false)) {
+		if (!skip_fmt_scale_restrictions &&
+		    dm_plane_color_pipeline_active(state, plane, false)) {
 			*cursor_mode = DM_CURSOR_OVERLAY_MODE;
 			return 0;
 		}
@@ -424,7 +425,8 @@ int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
 				   &cursor_scale_w, &cursor_scale_h);
 
 		/* Underlying plane has different scale - use overlay cursor */
-		if (cursor_scale_w != underlying_scale_w &&
+		if (!skip_fmt_scale_restrictions &&
+		    cursor_scale_w != underlying_scale_w &&
 		    cursor_scale_h != underlying_scale_h) {
 			*cursor_mode = DM_CURSOR_OVERLAY_MODE;
 			return 0;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
index efd16575896ec..9026c6ce73363 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
@@ -521,25 +521,38 @@ static void dm_test_crtc_get_cursor_mode_disabled_crtc(struct kunit *test)
 }
 
 /**
- * dm_test_crtc_get_cursor_mode_new_hardware - Test new hardware always uses native mode
+ * dm_test_crtc_get_cursor_mode_new_hardware - Test dcn4x uses native cursor when the top plane fills the CRTC
  * @test: The KUnit test context
  */
 static void dm_test_crtc_get_cursor_mode_new_hardware(struct kunit *test)
 {
-	struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
-	struct dm_crtc_state *dm_crtc_state;
+	struct dm_cursor_mode_fixture fixture = dm_test_alloc_cursor_mode_fixture(test);
 	enum amdgpu_dm_cursor_mode cursor_mode = DM_CURSOR_OVERLAY_MODE;
-	int ret;
 
-	dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
-	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 2, 0);
+	fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 2, 0);
 
-	ret = amdgpu_dm_crtc_get_cursor_mode(adev, NULL, dm_crtc_state, &cursor_mode);
-	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, dm_test_get_cursor_mode(&fixture, &cursor_mode), 0);
 	KUNIT_EXPECT_EQ(test, cursor_mode, DM_CURSOR_NATIVE_MODE);
 }
 
+/**
+ * dm_test_crtc_get_cursor_mode_new_hardware_hole - Test dcn4x falls back to
+ * overlay cursor when the top plane does not fill the CRTC
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_get_cursor_mode_new_hardware_hole(struct kunit *test)
+{
+	struct dm_cursor_mode_fixture fixture = dm_test_alloc_cursor_mode_fixture(test);
+	enum amdgpu_dm_cursor_mode cursor_mode = DM_CURSOR_NATIVE_MODE;
+
+	fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 2, 0);
+	fixture.old_primary_state->crtc_w = 1280;
+	fixture.primary_state->crtc_w = 1280;
+
+	KUNIT_EXPECT_EQ(test, dm_test_get_cursor_mode(&fixture, &cursor_mode), 0);
+	KUNIT_EXPECT_EQ(test, cursor_mode, DM_CURSOR_OVERLAY_MODE);
+}
+
 /**
  * dm_test_crtc_get_cursor_mode_no_change - Test unchanged atomic state preserves cursor mode
  * @test: The KUnit test context
@@ -948,6 +961,7 @@ static struct kunit_case amdgpu_dm_cursor_tests[] = {
 	/* amdgpu_dm_crtc_get_cursor_mode */
 	KUNIT_CASE(dm_test_crtc_get_cursor_mode_disabled_crtc),
 	KUNIT_CASE(dm_test_crtc_get_cursor_mode_new_hardware),
+	KUNIT_CASE(dm_test_crtc_get_cursor_mode_new_hardware_hole),
 	KUNIT_CASE(dm_test_crtc_get_cursor_mode_no_change),
 	KUNIT_CASE(dm_test_crtc_get_cursor_mode_disabled_cursor),
 	KUNIT_CASE(dm_test_crtc_get_cursor_mode_yuv_plane),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 02/82] drm/amd/display: Fixes for HPO test regressions
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
  2026-08-18 20:14 ` [PATCH 01/82] drm/amd/display: Fall back to overlay cursor on dcn4x when top plane doesn't fill CRTC IVAN.LIPSKI
@ 2026-08-18 20:14 ` IVAN.LIPSKI
  2026-08-18 20:14 ` [PATCH 03/82] drm/amd/display: Refactor DPP_SET_INPUT_TRANSFER_FUNC to drop pipe_ctx IVAN.LIPSKI
                   ` (80 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:14 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, Sridevi Arvindekar,
	Wenjing Liu

From: Sridevi Arvindekar <sridevi.arvindekar@amd.com>

[Why&How]
Updates and fixes to HPO functionality, initialization, encoder
configuration and platform-specific behavior.

Reviewed-by: Wenjing Liu <wenjing.liu@amd.com>
Signed-off-by: Sridevi Arvindekar <sridevi.arvindekar@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../gpu/drm/amd/display/dc/core/dc_resource.c |  7 ++++++
 drivers/gpu/drm/amd/display/dc/dce/dce_aux.c  | 12 +++++++++-
 drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c  |  7 +++---
 .../amd/display/dc/hwss/dcn30/dcn30_hwseq.c   |  6 +++++
 .../amd/display/dc/hwss/dcn401/dcn401_hwseq.c |  5 +++-
 .../display/dc/link/hwss/link_hwss_hpo_dp.c   | 23 ++++++++++---------
 .../dc/link/protocols/link_dp_capability.c    |  3 ++-
 7 files changed, 46 insertions(+), 17 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
index e019db3dc8226..837af136464ba 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
@@ -3002,6 +3002,11 @@ static inline int find_acquired_dio_link_enc_for_link(
 
 static inline int find_fixed_dio_link_enc(const struct dc_link *link)
 {
+	/* virtual links own their link encoder directly and are never
+	 * registered into pool->link_encoders[]. */
+	if (link->connector_signal == SIGNAL_TYPE_VIRTUAL)
+		return -1;
+
 	/* the 8b10b dp phy can only use fixed link encoder */
 	return link->eng_id;
 }
@@ -3147,6 +3152,8 @@ static bool add_dio_link_enc_to_ctx(const struct dc *dc,
 
 	if (enc_index >= 0)
 		pipe_ctx->link_res.dio_link_enc = pool->link_encoders[enc_index];
+	else
+		pipe_ctx->link_res.dio_link_enc = stream->link->link_enc;
 
 	return pipe_ctx->link_res.dio_link_enc != NULL;
 }
diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c b/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c
index 6cb5e8152cf10..4abf51c8ab52c 100644
--- a/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c
+++ b/drivers/gpu/drm/amd/display/dc/dce/dce_aux.c
@@ -432,14 +432,24 @@ static bool acquire_aux_engine_without_ddc_pin(
 	struct dce_aux *engine,
 	struct ddc *ddc)
 {
-	(void)ddc;
 	if ((engine == NULL) || !is_engine_available(engine))
 		return false;
 
+	/* Open the DDC in AUX mode so the I3C PAD set_config hook runs and
+	 * enables the PAD RX (disabled by default); otherwise DPCD reads time
+	 * out even though the connector has no native I2C/DDC pin. */
+	if (ddc != NULL &&
+		dal_ddc_open(ddc, GPIO_MODE_HARDWARE,
+			GPIO_DDC_CONFIG_TYPE_MODE_AUX) != GPIO_RESULT_OK)
+		return false;
+
 	if (!acquire_engine(engine)) {
+		engine->ddc = ddc;
 		release_engine(engine);
 		return false;
 	}
+
+	engine->ddc = ddc;
 	return true;
 }
 
diff --git a/drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c b/drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c
index b75bfea635fd4..bbfdcfc5b48fe 100644
--- a/drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c
+++ b/drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c
@@ -469,10 +469,11 @@ static enum gpio_result dal_hw_ddc_set_config_i3cpad(
 	switch (config_data->config.ddc.type) {
 	/* For ASICs with i3cpad module there is no dual pad mode for i3cpads */
 	case GPIO_DDC_CONFIG_TYPE_MODE_I2C:
-		/* Enable the RX for the PAD (it is disabled by default). */
-		REG_UPDATE(dc_i3cpad_control1, DC_I3CPAD_RXSEL, 0);
-		return GPIO_RESULT_OK;
 	case GPIO_DDC_CONFIG_TYPE_MODE_AUX:
+		/* Enable the RX for the PAD (it is disabled by default). Required for
+		 * both I2C (EDID) and AUX (DPCD); without it the sink reply is never
+		 * received and the transaction times out. */
+		REG_UPDATE(dc_i3cpad_control1, DC_I3CPAD_RXSEL, 0);
 		return GPIO_RESULT_OK;
 
 	case GPIO_DDC_CONFIG_TYPE_POLL_FOR_CONNECT:
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 cb163902e12e7..8c29aa2cd2c92 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
@@ -966,6 +966,12 @@ enum dc_status dcn30_setup_hdmi_frl_link(
 		frl_phy_clock_source_id,
 		link->frl_link_settings.frl_link_rate);
 	link->phy_state.symclk_state = SYMCLK_ON_TX_ON;
+
+	/* Enable HPO link encoder */
+	link->hpo_frl_link_enc->funcs->setup_link_encoder(
+			link->hpo_frl_link_enc,
+			link->frl_link_settings.frl_num_lanes);
+
 	return status;
 }
 
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 42b2fbb8a4ae4..bdefe699f8ca2 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
@@ -1847,7 +1847,10 @@ void dcn401_unblank_stream(struct pipe_ctx *pipe_ctx,
 	}
 
 	if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) {
-		if (link->link_status.link_active && link->frl_link_settings.frl_link_rate != 0)
+		bool unblank_required =
+			link->link_status.link_active && link->frl_link_settings.frl_link_rate != 0;
+
+		if (unblank_required)
 			pipe_ctx->stream_res.hpo_frl_stream_enc->funcs->hdmi_frl_unblank(
 					pipe_ctx->stream_res.hpo_frl_stream_enc,
 					pipe_ctx->stream_res.tg->inst);
diff --git a/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_dp.c b/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_dp.c
index 04df75114dd5b..8110481234012 100644
--- a/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_dp.c
+++ b/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_dp.c
@@ -117,16 +117,18 @@ void enable_hpo_dp_link_output(struct dc_link *link,
 		return;
 	}
 
-	if (link->dc->res_pool->dccg->funcs->set_symclk32_le_root_clock_gating)
-		link->dc->res_pool->dccg->funcs->set_symclk32_le_root_clock_gating(
-				link->dc->res_pool->dccg,
-				link_res->hpo_dp_link_enc->inst,
-				true);
-	link_res->hpo_dp_link_enc->funcs->enable_link_phy(
-			link_res->hpo_dp_link_enc,
-			link_settings,
-			link->link_enc->transmitter,
-			link->link_enc->hpd_source);
+	{
+		if (link->dc->res_pool->dccg->funcs->set_symclk32_le_root_clock_gating)
+			link->dc->res_pool->dccg->funcs->set_symclk32_le_root_clock_gating(
+					link->dc->res_pool->dccg,
+					link_res->hpo_dp_link_enc->inst,
+					true);
+		link_res->hpo_dp_link_enc->funcs->enable_link_phy(
+				link_res->hpo_dp_link_enc,
+				link_settings,
+				link->link_enc->transmitter,
+				link->link_enc->hpd_source);
+	}
 }
 
 void disable_hpo_dp_link_output(struct dc_link *link,
@@ -231,4 +233,3 @@ const struct link_hwss *get_hpo_dp_link_hwss(void)
 {
 	return &hpo_dp_link_hwss;
 }
-
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 24e3bbec15ffb..ad1a499b0d9d2 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
@@ -997,7 +997,8 @@ bool link_decide_link_settings(struct dc_stream_state *stream,
 		 * TODO: add MST specific link training routine
 		 */
 		decide_mst_link_settings(link, link_setting);
-	} else if (stream->signal == SIGNAL_TYPE_VIRTUAL) {
+	} else if (dc_is_virtual_signal(stream->signal)) {
+		/* virtual signals skip link training; use a valid dummy setting. */
 		link_setting->lane_count = LANE_COUNT_FOUR;
 		link_setting->link_rate = LINK_RATE_HIGH3;
 	} else if (link->connector_signal == SIGNAL_TYPE_EDP) {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 03/82] drm/amd/display: Refactor DPP_SET_INPUT_TRANSFER_FUNC to drop pipe_ctx
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
  2026-08-18 20:14 ` [PATCH 01/82] drm/amd/display: Fall back to overlay cursor on dcn4x when top plane doesn't fill CRTC IVAN.LIPSKI
  2026-08-18 20:14 ` [PATCH 02/82] drm/amd/display: Fixes for HPO test regressions IVAN.LIPSKI
@ 2026-08-18 20:14 ` IVAN.LIPSKI
  2026-08-18 20:14 ` [PATCH 04/82] drm/amd/display: Use fast update path for address-only plane flips IVAN.LIPSKI
                   ` (79 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:14 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, Tomasz Siemek, Alvin Lee

From: Tomasz Siemek <Tomasz.Siemek@amd.com>

[Why]
Pipe_ctx shouldn't be passed as block sequence block parameter.

[How]
- Adjust arguments for:
	- set_input_transfer_func,
	- populate_mcm_luts,
	- set_mcm_location_post_blend,
	- set_mcm_luts,
	- program_rmcm_luts.
- Remove const qualifier from plane_state in set_mcm_luts since it is
  mutated.

Reviewed-by: Alvin Lee <alvin.lee2@amd.com>
Signed-off-by: Tomasz Siemek <Tomasz.Siemek@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../drm/amd/display/dc/core/dc_hw_sequencer.c | 68 +++++++++++++++----
 .../amd/display/dc/hwss/dce110/dce110_hwseq.c | 11 ++-
 .../amd/display/dc/hwss/dce60/dce60_hwseq.c   |  3 +-
 .../amd/display/dc/hwss/dcn10/dcn10_hwseq.c   | 35 +++++-----
 .../amd/display/dc/hwss/dcn10/dcn10_hwseq.h   |  3 +-
 .../amd/display/dc/hwss/dcn20/dcn20_hwseq.c   | 63 ++++++++---------
 .../amd/display/dc/hwss/dcn20/dcn20_hwseq.h   |  7 +-
 .../amd/display/dc/hwss/dcn30/dcn30_hwseq.c   | 45 ++++++------
 .../amd/display/dc/hwss/dcn30/dcn30_hwseq.h   |  8 +--
 .../amd/display/dc/hwss/dcn32/dcn32_hwseq.c   | 60 ++++++++--------
 .../amd/display/dc/hwss/dcn32/dcn32_hwseq.h   | 10 +--
 .../amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 29 ++++----
 .../amd/display/dc/hwss/dcn401/dcn401_hwseq.h |  6 +-
 .../amd/display/dc/hwss/dcn42/dcn42_hwseq.c   | 49 +++++++------
 .../amd/display/dc/hwss/dcn42/dcn42_hwseq.h   | 12 ++--
 .../drm/amd/display/dc/hwss/hw_sequencer.h    | 12 +++-
 .../display/dc/hwss/hw_sequencer_private.h    | 18 ++---
 17 files changed, 238 insertions(+), 201 deletions(-)

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 9e82980e809b0..d61c4d9488c59 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
@@ -1514,9 +1514,22 @@ void hwss_build_fast_sequence(struct dc *dc,
 					(*num_steps)++;
 				}
 				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;
+					struct pipe_ctx *primary_dpp_pipe =
+						resource_get_primary_dpp_pipe(current_mpc_pipe);
+
+					block_sequence[*num_steps].params.set_input_transfer_func_params =
+						(struct set_input_transfer_func_params) {
+						.dc = dc,
+						.dpp = current_mpc_pipe->plane_res.dpp,
+						.hubp = current_mpc_pipe->plane_res.hubp,
+						.primary_hubp = primary_dpp_pipe ?
+							primary_dpp_pipe->plane_res.hubp : current_mpc_pipe->plane_res.hubp,
+						.ipp = current_mpc_pipe->plane_res.ipp,
+						.mpc = dc->res_pool->mpc,
+						.mpcc_id = current_mpc_pipe->plane_res.mpcc_inst,
+						.stream = current_mpc_pipe->stream,
+						.plane_state = current_mpc_pipe->plane_state,
+					};
 					block_sequence[*num_steps].func = DPP_SET_INPUT_TRANSFER_FUNC;
 					(*num_steps)++;
 				}
@@ -1717,9 +1730,7 @@ void hwss_execute_sequence(struct dc *dc,
 					params->update_plane_addr_params.pipe_ctx);
 			break;
 		case DPP_SET_INPUT_TRANSFER_FUNC:
-			hws->funcs.set_input_transfer_func(params->set_input_transfer_func_params.dc,
-					params->set_input_transfer_func_params.pipe_ctx,
-					params->set_input_transfer_func_params.plane_state);
+			hws->funcs.set_input_transfer_func(&params->set_input_transfer_func_params);
 			break;
 		case DPP_PROGRAM_GAMUT_REMAP:
 			if (dc->hwss.program_gamut_remap)
@@ -2290,15 +2301,25 @@ void hwss_add_hubp_update_plane_addr(struct block_sequence_state *seq_state,
 /*
  * Helper function to add DPP set input transfer function to block sequence
  */
-void hwss_add_dpp_set_input_transfer_func(struct block_sequence_state *seq_state,
-		struct dc *dc,
-		struct pipe_ctx *pipe_ctx,
-		struct dc_plane_state *plane_state)
+void hwss_add_dpp_set_input_transfer_func(struct block_sequence_state *seq_state, struct dc *dc,
+	struct pipe_ctx *pipe_ctx)
 {
 	if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) {
-		seq_state->steps[*seq_state->num_steps].params.set_input_transfer_func_params.dc = dc;
-		seq_state->steps[*seq_state->num_steps].params.set_input_transfer_func_params.pipe_ctx = pipe_ctx;
-		seq_state->steps[*seq_state->num_steps].params.set_input_transfer_func_params.plane_state = plane_state;
+		struct pipe_ctx *primary_dpp_pipe = resource_get_primary_dpp_pipe(pipe_ctx);
+
+		seq_state->steps[*seq_state->num_steps].params.set_input_transfer_func_params =
+			(struct set_input_transfer_func_params) {
+			.dc = dc,
+			.dpp = pipe_ctx->plane_res.dpp,
+			.hubp = pipe_ctx->plane_res.hubp,
+			.primary_hubp = primary_dpp_pipe ?
+				primary_dpp_pipe->plane_res.hubp : pipe_ctx->plane_res.hubp,
+			.ipp = pipe_ctx->plane_res.ipp,
+			.mpc = dc->res_pool->mpc,
+			.mpcc_id = pipe_ctx->plane_res.mpcc_inst,
+			.plane_state = pipe_ctx->plane_state,
+			.stream = pipe_ctx->stream,
+		};
 		seq_state->steps[*seq_state->num_steps].func = DPP_SET_INPUT_TRANSFER_FUNC;
 		(*seq_state->num_steps)++;
 	}
@@ -4544,6 +4565,26 @@ void hwss_disable_audio_stream(struct dc *dc, union block_sequence_params *param
 			params->disable_audio_stream_params.pipe_ctx);
 }
 
+void hwss_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx)
+{
+	if (dc->hwseq->funcs.set_input_transfer_func) {
+		struct pipe_ctx *primary_dpp_pipe = resource_get_primary_dpp_pipe(pipe_ctx);
+
+		dc->hwseq->funcs.set_input_transfer_func(&(struct set_input_transfer_func_params) {
+			.dc = dc,
+			.dpp = pipe_ctx->plane_res.dpp,
+			.hubp = pipe_ctx->plane_res.hubp,
+			.primary_hubp = primary_dpp_pipe ?
+				primary_dpp_pipe->plane_res.hubp : pipe_ctx->plane_res.hubp,
+			.ipp = pipe_ctx->plane_res.ipp,
+			.mpc = dc->res_pool->mpc,
+			.mpcc_id = pipe_ctx->plane_res.mpcc_inst,
+			.plane_state = pipe_ctx->plane_state,
+			.stream = pipe_ctx->stream,
+		});
+	}
+}
+
 void hwss_prepare_bandwidth(struct dc *dc, union block_sequence_params *params)
 {
 	if (dc && dc->hwss.prepare_bandwidth)
@@ -6203,4 +6244,3 @@ void hwss_hubp_wait_for_dcc_meta_prop(struct dc *dc, struct pipe_ctx *top_pipe_t
 		dc->hwss.wait_for_dcc_meta_propagation(delay);
 	}
 }
-
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 ff0c6e64c2b4a..ef91c61033a33 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
@@ -283,11 +283,10 @@ static void dce110_prescale_params(struct ipp_prescale_params *prescale_params,
 }
 
 static bool
-dce110_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx,
-			       const struct dc_plane_state *plane_state)
+dce110_set_input_transfer_func(struct set_input_transfer_func_params *params)
 {
-	(void)dc;
-	struct input_pixel_processor *ipp = pipe_ctx->plane_res.ipp;
+	struct input_pixel_processor *ipp = params->ipp;
+	struct dc_plane_state *plane_state = params->plane_state;
 	const struct dc_transfer_func *tf = NULL;
 	struct ipp_prescale_params prescale_params = { 0 };
 	bool result = true;
@@ -3140,7 +3139,6 @@ static void dce110_program_front_end_for_pipe(
 	struct xfm_grph_csc_adjustment adjust;
 	struct out_csc_color_matrix tbl_entry;
 	unsigned int i;
-	struct dce_hwseq *hws = dc->hwseq;
 
 	memset(&tbl_entry, 0, sizeof(tbl_entry));
 
@@ -3199,7 +3197,7 @@ static void dce110_program_front_end_for_pipe(
 	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);
+		hwss_set_input_transfer_func(dc, pipe_ctx);
 
 	if (pipe_ctx->plane_state->update_bits.full_update)
 		hwss_set_output_transfer_func(dc, pipe_ctx);
@@ -3689,5 +3687,6 @@ void dce110_hw_sequencer_construct(struct dc *dc)
 	dc->hwss = dce110_funcs;
 	dc->hwseq->funcs = dce110_private_funcs;
 }
+
 #endif /* CONFIG_DRM_AMD_DC_DCE */
 
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 221996b348abf..1652e47716a0a 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
@@ -275,7 +275,6 @@ dce60_program_front_end_for_pipe(
 	struct xfm_grph_csc_adjustment adjust;
 	struct out_csc_color_matrix tbl_entry;
 	unsigned int i;
-	struct dce_hwseq *hws = dc->hwseq;
 
 	DC_LOGGER_INIT();
 	memset(&tbl_entry, 0, sizeof(tbl_entry));
@@ -335,7 +334,7 @@ dce60_program_front_end_for_pipe(
 	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);
+		hwss_set_input_transfer_func(dc, pipe_ctx);
 
 	if (pipe_ctx->plane_state->update_bits.full_update)
 		hwss_set_output_transfer_func(dc, pipe_ctx);
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 2739f7acce988..d76fc6ef38c4e 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
@@ -2083,39 +2083,38 @@ void dcn10_update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx)
 		pipe_ctx->plane_state->address.grph_stereo.left_addr = addr;
 }
 
-bool dcn10_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx,
-			const struct dc_plane_state *plane_state)
+bool dcn10_set_input_transfer_func(struct set_input_transfer_func_params *params)
 {
-	(void)dc;
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
+	struct dpp *dpp = params->dpp;
+	struct dc_plane_state *plane_state = params->plane_state;
 	const struct dc_transfer_func *tf = NULL;
 	bool result = true;
 
-	if (dpp_base == NULL)
+	if (dpp == NULL)
 		return false;
 
 	tf = &plane_state->in_transfer_func;
 
-	if (!dpp_base->ctx->dc->debug.always_use_regamma
+	if (!dpp->ctx->dc->debug.always_use_regamma
 		&& !plane_state->gamma_correction.is_identity
 			&& dce_use_lut(plane_state->format))
-		dpp_base->funcs->dpp_program_input_lut(dpp_base, &plane_state->gamma_correction);
+		dpp->funcs->dpp_program_input_lut(dpp, &plane_state->gamma_correction);
 
 	if (tf->type == TF_TYPE_PREDEFINED) {
 		switch (tf->tf) {
 		case TRANSFER_FUNCTION_SRGB:
-			dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_HW_sRGB);
+			dpp->funcs->dpp_set_degamma(dpp, IPP_DEGAMMA_MODE_HW_sRGB);
 			break;
 		case TRANSFER_FUNCTION_BT709:
-			dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_HW_xvYCC);
+			dpp->funcs->dpp_set_degamma(dpp, IPP_DEGAMMA_MODE_HW_xvYCC);
 			break;
 		case TRANSFER_FUNCTION_LINEAR:
-			dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_BYPASS);
+			dpp->funcs->dpp_set_degamma(dpp, IPP_DEGAMMA_MODE_BYPASS);
 			break;
 		case TRANSFER_FUNCTION_PQ:
-			dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_USER_PWL);
-			cm_helper_translate_curve_to_degamma_hw_format(tf, &dpp_base->degamma_params);
-			dpp_base->funcs->dpp_program_degamma_pwl(dpp_base, &dpp_base->degamma_params);
+			dpp->funcs->dpp_set_degamma(dpp, IPP_DEGAMMA_MODE_USER_PWL);
+			cm_helper_translate_curve_to_degamma_hw_format(tf, &dpp->degamma_params);
+			dpp->funcs->dpp_program_degamma_pwl(dpp, &dpp->degamma_params);
 			result = true;
 			break;
 		default:
@@ -2123,12 +2122,12 @@ bool dcn10_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx,
 			break;
 		}
 	} else if (tf->type == TF_TYPE_BYPASS) {
-		dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_BYPASS);
+		dpp->funcs->dpp_set_degamma(dpp, IPP_DEGAMMA_MODE_BYPASS);
 	} else {
 		cm_helper_translate_curve_to_degamma_hw_format(tf,
-					&dpp_base->degamma_params);
-		dpp_base->funcs->dpp_program_degamma_pwl(dpp_base,
-				&dpp_base->degamma_params);
+					&dpp->degamma_params);
+		dpp->funcs->dpp_program_degamma_pwl(dpp,
+				&dpp->degamma_params);
 		result = true;
 	}
 
@@ -3303,7 +3302,7 @@ void dcn10_program_pipe(
 	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);
+		hwss_set_input_transfer_func(dc, pipe_ctx);
 
 	/* dcn10_translate_regamma_to_hw_format takes 750us to finish
 	 * only do gamma programming for full update.
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h
index a93109c2199b1..1f36bd58beb24 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h
@@ -74,8 +74,7 @@ void dcn10_program_output_csc(struct dc *dc,
 		uint16_t *matrix,
 		int opp_id);
 bool dcn10_set_output_transfer_func(struct set_output_transfer_func_params *params);
-bool dcn10_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx,
-			const struct dc_plane_state *plane_state);
+bool dcn10_set_input_transfer_func(struct set_input_transfer_func_params *params);
 void dcn10_update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx);
 void dcn10_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx);
 void dcn10_reset_hw_ctx_wrap(
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 66e2a6eb41c09..23e191d472991 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
@@ -1065,9 +1065,8 @@ bool dcn20_set_output_transfer_func(struct set_output_transfer_func_params *otf_
 }
 
 bool dcn20_set_blend_lut(
-	struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state)
+	struct dpp *dpp, struct dc_plane_state *plane_state)
 {
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
 	bool result = true;
 	const struct pwl_params *blend_lut = NULL;
 
@@ -1076,18 +1075,17 @@ bool dcn20_set_blend_lut(
 	else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
 		cm_helper_translate_curve_to_hw_format(plane_state->ctx,
 				&plane_state->cm.blend_func,
-				&dpp_base->regamma_params, false);
-		blend_lut = &dpp_base->regamma_params;
+				&dpp->regamma_params, false);
+		blend_lut = &dpp->regamma_params;
 	}
-	result = dpp_base->funcs->dpp_program_blnd_lut(dpp_base, blend_lut);
+	result = dpp->funcs->dpp_program_blnd_lut(dpp, blend_lut);
 
 	return result;
 }
 
 bool dcn20_set_shaper_3dlut(
-	struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state)
+	struct dpp *dpp, struct dc_plane_state *plane_state)
 {
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
 	bool result = true;
 	const struct pwl_params *shaper_lut = NULL;
 
@@ -1096,35 +1094,34 @@ bool dcn20_set_shaper_3dlut(
 	else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
 		cm_helper_translate_curve_to_hw_format(plane_state->ctx,
 				&plane_state->cm.shaper_func,
-				&dpp_base->shaper_params, true);
-		shaper_lut = &dpp_base->shaper_params;
+				&dpp->shaper_params, true);
+		shaper_lut = &dpp->shaper_params;
 	}
 
-	result = dpp_base->funcs->dpp_program_shaper_lut(dpp_base, shaper_lut);
+	dpp->funcs->dpp_program_shaper_lut(dpp, shaper_lut);
 	if (plane_state->cm.lut3d_func.state.bits.initialized == 1)
-		result = dpp_base->funcs->dpp_program_3dlut(dpp_base,
+		result = dpp->funcs->dpp_program_3dlut(dpp,
 								&plane_state->cm.lut3d_func.lut_3d);
 	else
-		result = dpp_base->funcs->dpp_program_3dlut(dpp_base, NULL);
+		result = dpp->funcs->dpp_program_3dlut(dpp, NULL);
 
 	return result;
 }
 
-bool dcn20_set_input_transfer_func(struct dc *dc,
-				struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state)
+bool dcn20_set_input_transfer_func(struct set_input_transfer_func_params *params)
 {
-	struct dce_hwseq *hws = dc->hwseq;
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
+	struct dpp *dpp = params->dpp;
+	struct dc_plane_state *plane_state = params->plane_state;
+	struct dce_hwseq *hws = params->dc->hwseq;
 	const struct dc_transfer_func *tf = NULL;
 	bool result = true;
 	bool use_degamma_ram = false;
 
-	if (dpp_base == NULL || plane_state == NULL)
+	if (dpp == NULL || plane_state == NULL)
 		return false;
 
-	hws->funcs.set_shaper_3dlut(pipe_ctx, plane_state);
-	hws->funcs.set_blend_lut(pipe_ctx, plane_state);
+	hws->funcs.set_shaper_3dlut(dpp, plane_state);
+	hws->funcs.set_blend_lut(dpp, plane_state);
 
 	tf = &plane_state->in_transfer_func;
 
@@ -1133,13 +1130,13 @@ bool dcn20_set_input_transfer_func(struct dc *dc,
 
 	if (use_degamma_ram == true) {
 		if (tf->type == TF_TYPE_HWPWL)
-			dpp_base->funcs->dpp_program_degamma_pwl(dpp_base,
+			dpp->funcs->dpp_program_degamma_pwl(dpp,
 					&tf->pwl);
 		else if (tf->type == TF_TYPE_DISTRIBUTED_POINTS) {
 			cm_helper_translate_curve_to_degamma_hw_format(tf,
-					&dpp_base->degamma_params);
-			dpp_base->funcs->dpp_program_degamma_pwl(dpp_base,
-				&dpp_base->degamma_params);
+					&dpp->degamma_params);
+			dpp->funcs->dpp_program_degamma_pwl(dpp,
+				&dpp->degamma_params);
 		}
 		return true;
 	}
@@ -1149,21 +1146,21 @@ bool dcn20_set_input_transfer_func(struct dc *dc,
 	if (tf->type == TF_TYPE_PREDEFINED) {
 		switch (tf->tf) {
 		case TRANSFER_FUNCTION_SRGB:
-			dpp_base->funcs->dpp_set_degamma(dpp_base,
+			dpp->funcs->dpp_set_degamma(dpp,
 					IPP_DEGAMMA_MODE_HW_sRGB);
 			break;
 		case TRANSFER_FUNCTION_BT709:
-			dpp_base->funcs->dpp_set_degamma(dpp_base,
+			dpp->funcs->dpp_set_degamma(dpp,
 					IPP_DEGAMMA_MODE_HW_xvYCC);
 			break;
 		case TRANSFER_FUNCTION_LINEAR:
-			dpp_base->funcs->dpp_set_degamma(dpp_base,
+			dpp->funcs->dpp_set_degamma(dpp,
 					IPP_DEGAMMA_MODE_BYPASS);
 			break;
 		case TRANSFER_FUNCTION_PQ:
-			dpp_base->funcs->dpp_set_degamma(dpp_base, IPP_DEGAMMA_MODE_USER_PWL);
-			cm_helper_translate_curve_to_degamma_hw_format(tf, &dpp_base->degamma_params);
-			dpp_base->funcs->dpp_program_degamma_pwl(dpp_base, &dpp_base->degamma_params);
+			dpp->funcs->dpp_set_degamma(dpp, IPP_DEGAMMA_MODE_USER_PWL);
+			cm_helper_translate_curve_to_degamma_hw_format(tf, &dpp->degamma_params);
+			dpp->funcs->dpp_program_degamma_pwl(dpp, &dpp->degamma_params);
 			result = true;
 			break;
 		default:
@@ -1171,7 +1168,7 @@ bool dcn20_set_input_transfer_func(struct dc *dc,
 			break;
 		}
 	} else if (tf->type == TF_TYPE_BYPASS)
-		dpp_base->funcs->dpp_set_degamma(dpp_base,
+		dpp->funcs->dpp_set_degamma(dpp,
 				IPP_DEGAMMA_MODE_BYPASS);
 	else {
 		/*
@@ -1179,7 +1176,7 @@ bool dcn20_set_input_transfer_func(struct dc *dc,
 		 * fix is required for this use case
 		 */
 		BREAK_TO_DEBUGGER();
-		dpp_base->funcs->dpp_set_degamma(dpp_base,
+		dpp->funcs->dpp_set_degamma(dpp,
 				IPP_DEGAMMA_MODE_BYPASS);
 	}
 
@@ -1987,7 +1984,7 @@ static void dcn20_program_pipe(
 			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);
+		hwss_set_input_transfer_func(dc, pipe_ctx);
 
 	/* dcn10_translate_regamma_to_hw_format takes 750us to finish
 	 * only do gamma programming for powering on, internal memcmp to avoid
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h
index 749348d3c7938..9312c88e31ad9 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h
@@ -31,9 +31,9 @@
 void dcn20_log_color_state(struct dc *dc,
 			   struct dc_log_buffer_ctx *log_ctx);
 bool dcn20_set_blend_lut(
-	struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state);
+	struct dpp *dpp, struct dc_plane_state *plane_state);
 bool dcn20_set_shaper_3dlut(
-	struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state);
+	struct dpp *dpp, struct dc_plane_state *plane_state);
 void dcn20_program_front_end_for_ctx(
 		struct dc *dc,
 		struct dc_state *context);
@@ -42,8 +42,7 @@ void dcn20_post_unlock_program_front_end(
 		struct dc_state *context);
 void dcn20_update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx);
 void dcn20_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx);
-bool dcn20_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx,
-			const struct dc_plane_state *plane_state);
+bool dcn20_set_input_transfer_func(struct set_input_transfer_func_params *params);
 bool dcn20_set_output_transfer_func(struct set_output_transfer_func_params *params);
 void dcn20_program_output_csc(struct dc *dc,
 		struct pipe_ctx *pipe_ctx,
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 8c29aa2cd2c92..71608f07be938 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
@@ -234,9 +234,8 @@ void dcn30_log_color_state(struct dc *dc,
 }
 
 bool dcn30_set_blend_lut(
-	struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state)
+	struct dpp *dpp, struct dc_plane_state *plane_state)
 {
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
 	bool result = true;
 	const struct pwl_params *blend_lut = NULL;
 
@@ -245,14 +244,14 @@ bool dcn30_set_blend_lut(
 	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,
+				&dpp->regamma_params,
 				false);
 		if (!result)
 			return result;
 
-		blend_lut = &dpp_base->regamma_params;
+		blend_lut = &dpp->regamma_params;
 	}
-	result = dpp_base->funcs->dpp_program_blnd_lut(dpp_base, blend_lut);
+	result = dpp->funcs->dpp_program_blnd_lut(dpp, blend_lut);
 
 	return result;
 }
@@ -314,17 +313,16 @@ static bool dcn30_set_mpc_shaper_3dlut(struct dpp *dpp, struct mpc *mpc,
 	return result;
 }
 
-bool dcn30_set_input_transfer_func(struct dc *dc,
-				struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state)
+bool dcn30_set_input_transfer_func(struct set_input_transfer_func_params *params)
 {
-	struct dce_hwseq *hws = dc->hwseq;
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
+	struct dpp *dpp = params->dpp;
+	struct dce_hwseq *hws = params->dc->hwseq;
+	struct dc_plane_state *plane_state = params->plane_state;
 	enum dc_transfer_func_predefined tf;
 	bool result = true;
-	const struct pwl_params *params = NULL;
+	const struct pwl_params *pwl_params = NULL;
 
-	if (dpp_base == NULL || plane_state == NULL)
+	if (dpp == NULL || plane_state == NULL)
 		return false;
 
 	tf = TRANSFER_FUNCTION_UNITY;
@@ -332,25 +330,24 @@ bool dcn30_set_input_transfer_func(struct dc *dc,
 	if (plane_state->in_transfer_func.type == TF_TYPE_PREDEFINED)
 		tf = plane_state->in_transfer_func.tf;
 
-	dpp_base->funcs->dpp_set_pre_degam(dpp_base, tf);
+	dpp->funcs->dpp_set_pre_degam(dpp, tf);
 
 	if (plane_state->in_transfer_func.type == TF_TYPE_HWPWL)
-		params = &plane_state->in_transfer_func.pwl;
+		pwl_params = &plane_state->in_transfer_func.pwl;
 	else if (plane_state->in_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS &&
 		cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
 							&plane_state->in_transfer_func,
-							&dpp_base->degamma_params, false))
-		params = &dpp_base->degamma_params;
+							&dpp->degamma_params, false))
+		pwl_params = &dpp->degamma_params;
 
-	result = dpp_base->funcs->dpp_program_gamcor_lut(dpp_base, params);
+	result = dpp->funcs->dpp_program_gamcor_lut(dpp, pwl_params);
+
+	if (dpp->funcs->dpp_program_blnd_lut)
+		hws->funcs.set_blend_lut(dpp, plane_state);
+	if (dpp->funcs->dpp_program_shaper_lut &&
+			dpp->funcs->dpp_program_3dlut)
+		hws->funcs.set_shaper_3dlut(dpp, plane_state);
 
-	if (pipe_ctx->stream_res.opp && pipe_ctx->stream_res.opp->ctx) {
-		if (dpp_base->funcs->dpp_program_blnd_lut)
-			hws->funcs.set_blend_lut(pipe_ctx, plane_state);
-		if (dpp_base->funcs->dpp_program_shaper_lut &&
-				dpp_base->funcs->dpp_program_3dlut)
-			hws->funcs.set_shaper_3dlut(pipe_ctx, plane_state);
-	}
 
 	return result;
 }
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h
index 0399642076eb5..65014d42195ca 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h
@@ -56,12 +56,10 @@ bool dcn30_mmhubbub_warmup(
 void dcn30_log_color_state(struct dc *dc,
 			   struct dc_log_buffer_ctx *log_ctx);
 
-bool dcn30_set_blend_lut(struct pipe_ctx *pipe_ctx,
-		const struct dc_plane_state *plane_state);
+bool dcn30_set_blend_lut(struct dpp *dpp,
+		struct dc_plane_state *plane_state);
 
-bool dcn30_set_input_transfer_func(struct dc *dc,
-				struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state);
+bool dcn30_set_input_transfer_func(struct set_input_transfer_func_params *params);
 
 void dcn30_program_gamut_remap(struct program_gamut_remap_params *params);
 
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 e188796cf272d..ec38fc2ba65cc 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
@@ -477,26 +477,30 @@ bool dcn32_set_mpc_shaper_3dlut(struct dpp *dpp, struct mpc *mpc,
 	return result;
 }
 
-bool dcn32_set_mcm_luts(
-	struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state)
+bool dcn32_set_mcm_luts(struct dc *dc, struct dpp *dpp, struct hubp *hubp,
+	struct hubp *primary_hubp, struct mpc *mpc, int mpcc_id,
+	struct dc_stream_state *stream,
+	struct dc_plane_state *plane_state)
 {
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
-	int mpcc_id = pipe_ctx->plane_res.hubp->inst;
-	struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc;
 	bool rval, result;
 	const struct pwl_params *lut_params = NULL;
 
+	(void)dc;
+	(void)hubp;
+	(void)primary_hubp;
+	(void)stream;
+
 	// 1D LUT
 	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_degamma_hw_format(
 			&plane_state->cm.blend_func,
-			&dpp_base->regamma_params);
+			&dpp->regamma_params);
 		if (!result)
 			return result;
 
-		lut_params = &dpp_base->regamma_params;
+		lut_params = &dpp->regamma_params;
 	}
 	mpc->funcs->program_1dlut(mpc, lut_params, mpcc_id);
 	lut_params = NULL;
@@ -505,12 +509,12 @@ bool dcn32_set_mcm_luts(
 	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
+		// TODO: dpp replace
 		rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
 			&plane_state->cm.shaper_func,
-			&dpp_base->shaper_params,
+			&dpp->shaper_params,
 			true);
-		lut_params = rval ? &dpp_base->shaper_params : NULL;
+		lut_params = rval ? &dpp->shaper_params : NULL;
 	}
 
 	mpc->funcs->program_shaper(mpc, lut_params, mpcc_id);
@@ -524,19 +528,17 @@ bool dcn32_set_mcm_luts(
 	return result;
 }
 
-bool dcn32_set_input_transfer_func(struct dc *dc,
-				struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state)
+bool dcn32_set_input_transfer_func(struct set_input_transfer_func_params *params)
 {
-	struct dce_hwseq *hws = dc->hwseq;
-	struct mpc *mpc = dc->res_pool->mpc;
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
+	struct dce_hwseq *hws = params->dc->hwseq;
+	struct dpp *dpp = params->dpp;
+	struct dc_plane_state *plane_state = params->plane_state;
 
 	enum dc_transfer_func_predefined tf;
 	bool result = true;
-	const struct pwl_params *params = NULL;
+	const struct pwl_params *pwl_params = NULL;
 
-	if (mpc == NULL || plane_state == NULL)
+	if (params->mpc == NULL || plane_state == NULL)
 		return false;
 
 	tf = TRANSFER_FUNCTION_UNITY;
@@ -544,24 +546,24 @@ bool dcn32_set_input_transfer_func(struct dc *dc,
 	if (plane_state->in_transfer_func.type == TF_TYPE_PREDEFINED)
 		tf = plane_state->in_transfer_func.tf;
 
-	if (dpp_base->funcs->dpp_set_pregam_state)
-		dpp_base->funcs->dpp_set_pregam_state(dpp_base, tf, plane_state->scaling_linearity);
+	if (dpp->funcs->dpp_set_pregam_state)
+		dpp->funcs->dpp_set_pregam_state(dpp, tf, plane_state->scaling_linearity);
 	else
-		dpp_base->funcs->dpp_set_pre_degam(dpp_base, tf);
+		dpp->funcs->dpp_set_pre_degam(dpp, tf);
 
 	if (plane_state->in_transfer_func.type == TF_TYPE_HWPWL)
-		params = &plane_state->in_transfer_func.pwl;
+		pwl_params = &plane_state->in_transfer_func.pwl;
 	else if (plane_state->in_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS &&
 		cm3_helper_translate_curve_to_degamma_hw_format(&plane_state->in_transfer_func,
-								&dpp_base->degamma_params))
-		params = &dpp_base->degamma_params;
+								&dpp->degamma_params))
+		pwl_params = &dpp->degamma_params;
 
-	dpp_base->funcs->dpp_program_gamcor_lut(dpp_base, params);
+	dpp->funcs->dpp_program_gamcor_lut(dpp, pwl_params);
 
-	if (pipe_ctx->stream_res.opp &&
-			pipe_ctx->stream_res.opp->ctx &&
-			hws->funcs.set_mcm_luts)
-		result = hws->funcs.set_mcm_luts(pipe_ctx, plane_state);
+	if (hws->funcs.set_mcm_luts)
+		result = hws->funcs.set_mcm_luts(params->dc, dpp, params->hubp,
+			params->primary_hubp, params->mpc, params->mpcc_id,
+			params->stream, plane_state);
 
 	return result;
 }
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h
index 090d94d38343b..c85ff14596afb 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h
@@ -47,12 +47,12 @@ void dcn32_cab_for_ss_control(struct dc *dc, bool enable);
 
 void dcn32_commit_subvp_config(struct dc *dc, struct dc_state *context);
 
-bool dcn32_set_mcm_luts(struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state);
+bool dcn32_set_mcm_luts(struct dc *dc, struct dpp *dpp, struct hubp *hubp,
+				struct hubp *primary_hubp, struct mpc *mpc, int mpcc_id,
+				struct dc_stream_state *stream,
+				struct dc_plane_state *plane_state);
 
-bool dcn32_set_input_transfer_func(struct dc *dc,
-				struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state);
+bool dcn32_set_input_transfer_func(struct set_input_transfer_func_params *params);
 
 bool dcn32_set_mpc_shaper_3dlut(struct dpp *dpp_base, struct mpc *mpc,
 	int mpcc_id, const struct dc_stream_state *stream);
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 bdefe699f8ca2..0e107754c891e 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
@@ -421,18 +421,12 @@ void dcn401_trigger_3dlut_dma_load(struct pipe_ctx *pipe_ctx)
 	}
 }
 
-bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state)
+bool dcn401_set_mcm_luts(struct dc *dc, struct dpp *dpp, struct hubp *hubp,
+				struct hubp *primary_hubp, struct mpc *mpc, int mpcc_id,
+				struct dc_stream_state *stream,
+				struct dc_plane_state *plane_state)
 {
-	struct dc *dc = pipe_ctx->plane_res.hubp->ctx->dc;
-	const struct pipe_ctx *primary_dpp_pipe_ctx = resource_get_primary_dpp_pipe(pipe_ctx);
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
-	struct hubp *hubp = pipe_ctx->plane_res.hubp;
-	struct hubp *primary_hubp = primary_dpp_pipe_ctx ?
-			primary_dpp_pipe_ctx->plane_res.hubp : hubp;   /* fall back to current pipe */
 	const struct dc_plane_cm *cm = &plane_state->cm;
-	int mpcc_id = hubp->inst;
-	struct mpc *mpc = dc->res_pool->mpc;
 	union mcm_lut_params m_lut_params;
 	struct dc_3dlut_dma lut3d_dma;
 	bool lut_enable;
@@ -440,6 +434,9 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx,
 	bool rval;
 	bool result = true;
 
+	(void)dc;
+	(void)stream;
+
 	/* decide LUT bank based on current in use */
 	mpc->funcs->get_lut_mode(mpc, MCM_LUT_1DLUT, mpcc_id, &lut_enable, &lut_bank_a);
 	if (!lut_enable) {
@@ -466,9 +463,9 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx,
 		else if (cm->blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
 			rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
 					&cm->blend_func,
-					&dpp_base->regamma_params,
+					&dpp->regamma_params,
 					false);
-			m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL;
+			m_lut_params.pwl = rval ? &dpp->regamma_params : NULL;
 		}
 
 		if (!m_lut_params.pwl) {
@@ -493,9 +490,9 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx,
 			ASSERT(false);
 			rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
 					&cm->shaper_func,
-					&dpp_base->shaper_params,
+					&dpp->shaper_params,
 					true);
-			m_lut_params.pwl = rval ? &dpp_base->shaper_params : NULL;
+			m_lut_params.pwl = rval ? &dpp->shaper_params : NULL;
 		}
 		if (!m_lut_params.pwl) {
 			lut_enable = false;
@@ -2291,7 +2288,7 @@ void dcn401_program_pipe(
 			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);
+		hwss_set_input_transfer_func(dc, pipe_ctx);
 
 	/* dcn10_translate_regamma_to_hw_format takes 750us to finish
 	 * only do gamma programming for powering on, internal memcmp to avoid
@@ -2448,7 +2445,7 @@ void dcn401_program_pipe_sequence(
 			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);
+		hwss_add_dpp_set_input_transfer_func(seq_state, dc, pipe_ctx);
 	}
 
 	/* dcn10_translate_regamma_to_hw_format takes 750us to finish
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 2e3392c963d03..c08fd359b38b0 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
@@ -36,8 +36,10 @@ void dcn401_program_gamut_remap(struct program_gamut_remap_params *params);
 
 void dcn401_init_hw(struct dc *dc);
 
-bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state);
+bool dcn401_set_mcm_luts(struct dc *dc, struct dpp *dpp, struct hubp *hubp,
+				struct hubp *primary_hubp, struct mpc *mpc, int mpcc_id,
+				struct dc_stream_state *stream,
+				struct dc_plane_state *plane_state);
 bool dcn401_set_output_transfer_func(struct set_output_transfer_func_params *params);
 void dcn401_trigger_3dlut_dma_load(struct pipe_ctx *pipe_ctx);
 void dcn401_calculate_dccg_tmds_div_value(struct pipe_ctx *pipe_ctx,
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 13580d9f9303e..23d4786ea2464 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
@@ -413,19 +413,18 @@ static bool dc_is_rmcm_3dlut_supported(struct hubp *hubp, struct mpc *mpc)
 }
 
 bool dcn42_program_rmcm_luts(
+	struct dc *dc,
+	struct dpp *dpp,
 	struct hubp *hubp,
-	struct pipe_ctx *pipe_ctx,
 	const struct dc_plane_cm *cm,
 	struct mpc *mpc,
-	int mpcc_id)
+	int mpcc_id,
+	struct dc_stream_state *stream)
 {
-	struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
 	union mcm_lut_params m_lut_params = {0};
 
-	struct dc *dc = hubp->ctx->dc;
 	struct mpc_fl_3dlut_config mpc_fl_config;
 
-	struct dc_stream_state *stream = pipe_ctx->stream;
 	bool bypass_rmcm_shaper = false;
 	// true->false when it can be allocated at DI time
 	struct dc_rmcm_3dlut *rmcm_3dlut = dc_stream_get_3dlut_for_stream(dc, stream, false);
@@ -457,8 +456,8 @@ bool dcn42_program_rmcm_luts(
 			cm_helper_translate_curve_to_hw_format(
 					dc->ctx,
 					&cm->shaper_func,
-					&dpp_base->shaper_params, true);
-			m_lut_params.pwl = &dpp_base->shaper_params;
+					&dpp->shaper_params, true);
+			m_lut_params.pwl = &dpp->shaper_params;
 		}
 		if (m_lut_params.pwl) {
 			if (mpc->funcs->rmcm.populate_lut)
@@ -511,31 +510,29 @@ bool dcn42_program_rmcm_luts(
 	return true;
 }
 
-bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state)
+bool dcn42_set_mcm_luts(struct dc *dc, struct dpp *dpp, struct hubp *hubp,
+				struct hubp *primary_hubp, struct mpc *mpc, int mpcc_id,
+				struct dc_stream_state *stream,
+				struct dc_plane_state *plane_state)
 {
 	bool result;
+	const struct dc_plane_cm *cm = &plane_state->cm;
 
 	/* MCM */
-	result = dcn401_set_mcm_luts(pipe_ctx, plane_state);
+	result = dcn401_set_mcm_luts(dc, dpp, hubp, primary_hubp, mpc, mpcc_id,
+			stream, plane_state);
 
 	/* RMCM */
-	{
-		struct dc *dc = pipe_ctx->plane_res.hubp->ctx->dc;
-		struct hubp *hubp = pipe_ctx->plane_res.hubp;
-		const struct dc_plane_cm *cm = &plane_state->cm;
-		struct mpc *mpc = dc->res_pool->mpc;
-		int mpcc_id = hubp->inst;
-
-		if (cm->flags.bits.rmcm_enable && cm->flags.bits.lut3d_dma_enable) {
-			/* TODO - move RMCM to its own block */
-			dcn42_program_rmcm_luts(
-				hubp,
-				pipe_ctx,
-				cm,
-				mpc,
-				mpcc_id);
-		}
+	if (cm->flags.bits.rmcm_enable && cm->flags.bits.lut3d_dma_enable) {
+		/* TODO - move RMCM to its own block */
+		dcn42_program_rmcm_luts(
+			dc,
+			dpp,
+			hubp,
+			cm,
+			mpc,
+			mpcc_id,
+			stream);
 	}
 
 	return result;
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 c4cfeed45b190..61d1217a30df5 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
@@ -15,15 +15,19 @@ void dcn42_program_cm_hist(
 	struct pipe_ctx *pipe_ctx,
 	const struct dc_plane_state *plane_state);
 
-bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx,
-	const struct dc_plane_state *plane_state);
+bool dcn42_set_mcm_luts(struct dc *dc, struct dpp *dpp, struct hubp *hubp,
+	struct hubp *primary_hubp, struct mpc *mpc, int mpcc_id,
+	struct dc_stream_state *stream,
+	struct dc_plane_state *plane_state);
 
 bool dcn42_program_rmcm_luts(
+	struct dc *dc,
+	struct dpp *dpp,
 	struct hubp *hubp,
-	struct pipe_ctx *pipe_ctx,
 	const struct dc_plane_cm *cm,
 	struct mpc *mpc,
-	int mpcc_id);
+	int mpcc_id,
+	struct dc_stream_state *stream);
 void dcn42_hardware_release(struct dc *dc);
 
 void dcn42_prepare_bandwidth(
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h
index bf63281a8cd2c..169f3420ac6dd 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h
@@ -90,7 +90,13 @@ struct update_plane_addr_params {
 
 struct set_input_transfer_func_params {
 	struct dc *dc;
-	struct pipe_ctx *pipe_ctx;
+	struct dpp *dpp;
+	struct hubp *hubp;
+	struct hubp *primary_hubp;
+	struct mpc *mpc;
+	int mpcc_id;
+	struct dc_stream_state *stream;
+	struct input_pixel_processor *ipp;
 	struct dc_plane_state *plane_state;
 };
 
@@ -2119,6 +2125,8 @@ void hwss_disable_audio_stream(struct dc *dc, union block_sequence_params *param
 
 void hwss_hubp_wait_for_dcc_meta_prop(struct dc *dc, struct pipe_ctx *top_pipe_to_program);
 
+void hwss_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx);
+
 void hwss_add_optc_pipe_control_lock(struct block_sequence_state *seq_state,
 		struct dc *dc, struct pipe_ctx *pipe_ctx, bool lock);
 
@@ -2132,7 +2140,7 @@ void hwss_add_hubp_update_plane_addr(struct block_sequence_state *seq_state,
 		struct dc *dc, struct pipe_ctx *pipe_ctx);
 
 void hwss_add_dpp_set_input_transfer_func(struct block_sequence_state *seq_state,
-		struct dc *dc, struct pipe_ctx *pipe_ctx, struct dc_plane_state *plane_state);
+		struct dc *dc, struct pipe_ctx *pipe_ctx);
 
 void hwss_add_dpp_program_gamut_remap(struct block_sequence_state *seq_state,
 		struct pipe_ctx *pipe_ctx);
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 0f7881a214620..956513125be0b 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
@@ -90,9 +90,7 @@ struct hwseq_private_funcs {
 	void (*update_mpcc)(struct dc *dc, struct pipe_ctx *pipe_ctx);
 	void (*update_mpcc_sequence)(struct dc *dc, struct pipe_ctx *pipe_ctx,
 			struct block_sequence_state *seq_state);
-	bool (*set_input_transfer_func)(struct dc *dc,
-				struct pipe_ctx *pipe_ctx,
-				const struct dc_plane_state *plane_state);
+	bool (*set_input_transfer_func)(struct set_input_transfer_func_params *params);
 	bool (*set_output_transfer_func)(struct set_output_transfer_func_params *params);
 	void (*power_down)(struct dc *dc);
 	void (*enable_display_pipe_clock_gating)(struct dc_context *ctx,
@@ -187,12 +185,14 @@ struct hwseq_private_funcs {
 		struct block_sequence_state *seq_state);
 	bool (*wait_for_blank_complete)(struct output_pixel_processor *opp);
 	void (*dccg_init)(struct dce_hwseq *hws);
-	bool (*set_blend_lut)(struct pipe_ctx *pipe_ctx,
-			const struct dc_plane_state *plane_state);
-	bool (*set_shaper_3dlut)(struct pipe_ctx *pipe_ctx,
-			const struct dc_plane_state *plane_state);
-	bool (*set_mcm_luts)(struct pipe_ctx *pipe_ctx,
-			const struct dc_plane_state *plane_state);
+	bool (*set_blend_lut)(struct dpp *dpp,
+			struct dc_plane_state *plane_state);
+	bool (*set_shaper_3dlut)(struct dpp *dpp,
+			struct dc_plane_state *plane_state);
+	bool (*set_mcm_luts)(struct dc *dc, struct dpp *dpp, struct hubp *hubp,
+			struct hubp *primary_hubp, struct mpc *mpc, int mpcc_id,
+			struct dc_stream_state *stream,
+			struct dc_plane_state *plane_state);
 	void (*PLAT_58856_wa)(struct dc_state *context,
 			struct pipe_ctx *pipe_ctx);
 	void (*setup_hpo_hw_control)(const struct dce_hwseq *hws, bool enable);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 04/82] drm/amd/display: Use fast update path for address-only plane flips
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (2 preceding siblings ...)
  2026-08-18 20:14 ` [PATCH 03/82] drm/amd/display: Refactor DPP_SET_INPUT_TRANSFER_FUNC to drop pipe_ctx IVAN.LIPSKI
@ 2026-08-18 20:14 ` IVAN.LIPSKI
  2026-08-18 20:14 ` [PATCH 05/82] drm/amd/display: Split OPTC_PIPE_CONTROL_LOCK into smaller HWSS blocks IVAN.LIPSKI
                   ` (78 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:14 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, James Lin

From: James Lin <pinglei.lin@amd.com>

[Why]
amdgpu_dm_commit_planes() unconditionally attaches plane_info and
scaling_info to every dc_surface_update, even for pure page flips that
only change the surface address. In DC, full_update_required() treats a
non-NULL plane_info or scaling_info pointer as a reason to force a full
update, so fast_update_only() always returns false and every commit is
routed through commit_planes_for_stream() instead of the lightweight
commit_planes_for_stream_fast() path. The heavy path acquires the DMU
global lock and waits for its ACK on every commit (spinning ~27ms),
serializing the kernel against the firmware even though the flip is
address-only.

Tracing the two commit paths during a steady 60Hz flip on two outputs
shows the heavy path is taken on every frame, while the update_type that
DC computed for those same commits is already UPDATE_TYPE_FAST - i.e.
nothing actually changed and the full path is chosen solely because the
optional pointers are present:

  before:  commit_planes_for_stream      (FULL) = 550
           commit_planes_for_stream_fast (FAST) = 0
           (all commits carried update_type == UPDATE_TYPE_FAST)

[How]
Only attach plane_info and scaling_info to the surface update when the
update is not UPDATE_TYPE_FAST. fill_dc_plane_info_and_addr() is still
called so the flip address is populated for the fast path, and the
scaling info is still computed into the bundle; only the pointers that
would otherwise force a full update are withheld for fast updates.

This is safe because every consumer already NULL-checks these optional
pointers (copy_surface_update_to_plane(), get_plane_info_update_type()
via det_surface_update(), full_update_required() and
commit_planes_for_stream()), det_surface_update() clears
surface->update_bits on every check so no stale state is left behind, and
UPDATE_TYPE_FAST guarantees no plane_info/scaling_info field has changed.
It also mirrors the existing flip_immediate logic, which already relies
on acrtc_state->update_type == UPDATE_TYPE_FAST. Non-fast updates
(modeset, rotation, format, DCC, scaling, MPO, cursor, PSR/Replay) keep
carrying plane_info/scaling_info and are unaffected.

With the change the same workload takes the fast path on every flip:

  after:   commit_planes_for_stream      (FULL) = 0
           commit_planes_for_stream_fast (FAST) = 449

Reviewed-by: Sun peng (Leo) Li <sunpeng.li@amd.com>
Signed-off-by: James Lin <pinglei.lin@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 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 567bf04979ee5..e1fdd0cb65b34 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -3884,8 +3884,9 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
 		amdgpu_dm_plane_fill_dc_scaling_info(dm->adev, new_plane_state,
 				     &bundle->scaling_infos[planes_count]);
 
-		bundle->surface_updates[planes_count].scaling_info =
-			&bundle->scaling_infos[planes_count];
+		if (acrtc_state->update_type != UPDATE_TYPE_FAST)
+			bundle->surface_updates[planes_count].scaling_info =
+				&bundle->scaling_infos[planes_count];
 
 		plane_needs_flip = old_plane_state->fb && new_plane_state->fb;
 
@@ -3906,8 +3907,9 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
 				 new_plane_state->plane->index,
 				 bundle->plane_infos[planes_count].dcc.enable);
 
-		bundle->surface_updates[planes_count].plane_info =
-			&bundle->plane_infos[planes_count];
+		if (acrtc_state->update_type != UPDATE_TYPE_FAST)
+			bundle->surface_updates[planes_count].plane_info =
+				&bundle->plane_infos[planes_count];
 
 		if (acrtc_state->stream->link->psr_settings.psr_feature_enabled ||
 		    acrtc_state->stream->link->replay_settings.replay_feature_enabled) {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 05/82] drm/amd/display: Split OPTC_PIPE_CONTROL_LOCK into smaller HWSS blocks
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (3 preceding siblings ...)
  2026-08-18 20:14 ` [PATCH 04/82] drm/amd/display: Use fast update path for address-only plane flips IVAN.LIPSKI
@ 2026-08-18 20:14 ` IVAN.LIPSKI
  2026-08-18 20:14 ` [PATCH 06/82] drm/amd/display: Fix DPREFCLK override when SMU isn't present or ready for DCN315 IVAN.LIPSKI
                   ` (77 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:14 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, Tomasz Siemek, Alvin Lee

From: Tomasz Siemek <tomasz.siemek@amd.com>

[why]
Block level sequence operations should not use global state structs such
as pipe_ctx. OPTC_PIPE_CONTROL_LOCK is a BLS with complex logic
dispatching a lot of smaller hardware programming operations.
BLS should be a simple and atomic hardware operation without performing
mutations or having an access to global state. All operations on global
state should be moved to build step in HWSS.

[how]
- Split OPTC_PIPE_CONTROL_LOCK into substeps:
	- HUBP_WAIT_FLIP_PENDING,
	- TG_SET_GSL,
	- TG_SET_GSL_SOURCE_SELECT,
	- TG_LOCK,
	- TG_3DLUT_WA_UNLOCK.
- Add hwss_pipe_control_lock - for in place build + execution.
- Remove timeout from HUBP_WAIT_FOR_FLIP_PENDING.
- Move software logic to builder. which fills pipe_control_lock_params
  containing data for programming actual hwss blocks.
- Due to differences in what pipe control lock means bettween
  asics following builder variants are added:
	- dce_build_pipe_control_lock_sequence for asics pre DCN20,
	- dcn20_build_pipe_control_lock_sequence.
- Rework gsl group programming to use builder / executor pattern.

Reviewed-by: Alvin Lee <alvin.lee2@amd.com>
Signed-off-by: Tomasz Siemek <tomasz.siemek@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 drivers/gpu/drm/amd/display/dc/core/dc.c      |   8 +-
 .../drm/amd/display/dc/core/dc_hw_sequencer.c | 125 ++++++++++----
 .../drm/amd/display/dc/hwss/dce/dce_hwseq.c   |  46 ++++--
 .../drm/amd/display/dc/hwss/dce/dce_hwseq.h   |  12 +-
 .../amd/display/dc/hwss/dce110/dce110_hwseq.c |   5 +-
 .../amd/display/dc/hwss/dce60/dce60_hwseq.c   |   5 +-
 .../amd/display/dc/hwss/dce80/dce80_hwseq.c   |   3 +-
 .../amd/display/dc/hwss/dcn10/dcn10_hwseq.c   |  31 ++--
 .../amd/display/dc/hwss/dcn10/dcn10_hwseq.h   |   5 +-
 .../amd/display/dc/hwss/dcn10/dcn10_init.c    |   4 +-
 .../amd/display/dc/hwss/dcn20/dcn20_hwseq.c   | 153 +++++++++++-------
 .../amd/display/dc/hwss/dcn20/dcn20_hwseq.h   |  14 +-
 .../amd/display/dc/hwss/dcn20/dcn20_init.c    |   3 +-
 .../amd/display/dc/hwss/dcn201/dcn201_hwseq.c |  34 ++--
 .../amd/display/dc/hwss/dcn201/dcn201_hwseq.h |   5 +-
 .../amd/display/dc/hwss/dcn201/dcn201_init.c  |   4 +-
 .../amd/display/dc/hwss/dcn21/dcn21_init.c    |   3 +-
 .../amd/display/dc/hwss/dcn30/dcn30_init.c    |   3 +-
 .../amd/display/dc/hwss/dcn301/dcn301_init.c  |   3 +-
 .../amd/display/dc/hwss/dcn31/dcn31_init.c    |   3 +-
 .../amd/display/dc/hwss/dcn314/dcn314_init.c  |   3 +-
 .../amd/display/dc/hwss/dcn32/dcn32_hwseq.c   |   4 +-
 .../amd/display/dc/hwss/dcn32/dcn32_init.c    |   3 +-
 .../amd/display/dc/hwss/dcn35/dcn35_init.c    |   3 +-
 .../amd/display/dc/hwss/dcn351/dcn351_init.c  |   3 +-
 .../amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 140 ++++------------
 .../amd/display/dc/hwss/dcn401/dcn401_hwseq.h |   3 +-
 .../amd/display/dc/hwss/dcn401/dcn401_init.c  |   3 +-
 .../amd/display/dc/hwss/dcn42/dcn42_init.c    |   3 +-
 .../amd/display/dc/hwss/dcn60/dcn60_init.c    |   3 +-
 .../drm/amd/display/dc/hwss/hw_sequencer.h    |  43 +++--
 .../display/dc/hwss/hw_sequencer_private.h    |   3 +-
 32 files changed, 367 insertions(+), 316 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c
index 6f7b715d4717e..3c6936943960d 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc.c
@@ -1221,7 +1221,7 @@ static void apply_ctx_interdependent_lock(struct dc *dc,
 			if (stream == pipe_ctx->stream) {
 				if (resource_is_pipe_type(pipe_ctx, OPP_HEAD) &&
 					(pipe_ctx->plane_state || old_pipe_ctx->plane_state))
-					dc->hwss.pipe_control_lock(dc, pipe_ctx, lock);
+					hwss_pipe_control_lock(dc, pipe_ctx, lock);
 			}
 		}
 	}
@@ -4991,7 +4991,7 @@ static void commit_planes_for_stream(struct dc *dc,
 		 *  plane addr update event triggers to be synchronized.
 		 *  top_pipe_to_program is expected to never be NULL
 		 */
-		dc->hwss.pipe_control_lock(dc, top_pipe_to_program, true);
+		hwss_pipe_control_lock(dc, top_pipe_to_program, true);
 	}
 
 	dc_dmub_update_dirty_rect(dc, surface_count, stream, srf_updates, context);
@@ -5013,7 +5013,7 @@ static void commit_planes_for_stream(struct dc *dc,
 		if (should_lock_all_pipes && dc->hwss.interdependent_update_lock) {
 			dc->hwss.interdependent_update_lock(dc, context, false);
 		} else {
-			dc->hwss.pipe_control_lock(dc, top_pipe_to_program, false);
+			hwss_pipe_control_lock(dc, top_pipe_to_program, false);
 		}
 		dc->hwss.post_unlock_program_front_end(dc, context);
 
@@ -5200,7 +5200,7 @@ static void commit_planes_for_stream(struct dc *dc,
 	if (should_lock_all_pipes && dc->hwss.interdependent_update_lock) {
 		dc->hwss.interdependent_update_lock(dc, context, false);
 	} else {
-		dc->hwss.pipe_control_lock(dc, top_pipe_to_program, false);
+		hwss_pipe_control_lock(dc, top_pipe_to_program, false);
 	}
 
 	if ((update_type != UPDATE_TYPE_FAST) && stream->update_flags.bits.dsc_changed)
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 d61c4d9488c59..8b723c3ca933d 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
@@ -1051,7 +1051,7 @@ void hwss_build_post_unlock_full_sequence(struct dc *dc,
 
 			unsigned int polling_interval_us;
 				polling_interval_us = 1;
-			hwss_add_hubp_wait_flip_pending(&seq_state, pipe->plane_res.hubp, 100000, polling_interval_us);
+			hwss_add_hubp_wait_flip_pending(&seq_state, pipe->plane_res.hubp, polling_interval_us);
 		}
 	}
 
@@ -1212,13 +1212,9 @@ void hwss_build_fast_sequence(struct dc *dc,
 		block_sequence[*num_steps].func = DMUB_HW_CONTROL_LOCK_FAST;
 		(*num_steps)++;
 	}
-	if (dc->hwss.pipe_control_lock) {
-		block_sequence[*num_steps].params.pipe_control_lock_params.dc = dc;
-		block_sequence[*num_steps].params.pipe_control_lock_params.lock = true;
-		block_sequence[*num_steps].params.pipe_control_lock_params.pipe_ctx = pipe_ctx;
-		block_sequence[*num_steps].func = OPTC_PIPE_CONTROL_LOCK;
-		(*num_steps)++;
-	}
+	hwss_add_optc_pipe_control_lock(
+			&(struct block_sequence_state){ block_sequence, num_steps },
+			dc, pipe_ctx, true);
 
 	for (i = 0; i < dmub_cmd_count; i++) {
 		block_sequence[*num_steps].params.send_dmcub_cmd_params.ctx = dc->ctx;
@@ -1646,13 +1642,9 @@ void hwss_build_fast_sequence(struct dc *dc,
 		current_pipe = current_pipe->next_odm_pipe;
 	}
 
-	if (dc->hwss.pipe_control_lock) {
-		block_sequence[*num_steps].params.pipe_control_lock_params.dc = dc;
-		block_sequence[*num_steps].params.pipe_control_lock_params.lock = false;
-		block_sequence[*num_steps].params.pipe_control_lock_params.pipe_ctx = pipe_ctx;
-		block_sequence[*num_steps].func = OPTC_PIPE_CONTROL_LOCK;
-		(*num_steps)++;
-	}
+	hwss_add_optc_pipe_control_lock(
+			&(struct block_sequence_state){ block_sequence, num_steps },
+			dc, pipe_ctx, false);
 	if (dc->hwss.subvp_pipe_control_lock_fast) {
 		block_sequence[*num_steps].params.subvp_pipe_control_lock_fast_params.dc = dc;
 		block_sequence[*num_steps].params.subvp_pipe_control_lock_fast_params.lock = false;
@@ -1710,10 +1702,14 @@ void hwss_execute_sequence(struct dc *dc,
 		case DMUB_SUBVP_PIPE_CONTROL_LOCK_FAST:
 			dc->hwss.subvp_pipe_control_lock_fast(params);
 			break;
-		case OPTC_PIPE_CONTROL_LOCK:
-			dc->hwss.pipe_control_lock(params->pipe_control_lock_params.dc,
-					params->pipe_control_lock_params.pipe_ctx,
-					params->pipe_control_lock_params.lock);
+		case TG_LOCK:
+			dc->hwss.tg_lock(&params->tg_lock_params);
+			break;
+		case TG_3DLUT_WA_UNLOCK:
+			if (dc->hwseq->funcs.perform_3dlut_wa_unlock)
+				dc->hwseq->funcs.perform_3dlut_wa_unlock(
+						params->tg_3dlut_wa_unlock_params.tg,
+						params->tg_3dlut_wa_unlock_params.hubp);
 			break;
 		case HUBP_SET_FLIP_CONTROL_GSL:
 			params->set_flip_control_gsl_params.hubp->funcs->hubp_set_flip_control_surface_gsl(
@@ -1925,7 +1921,8 @@ void hwss_execute_sequence(struct dc *dc,
 			hwss_tg_set_gsl_source_select(params);
 			break;
 		case HUBP_WAIT_FLIP_PENDING:
-			hwss_hubp_wait_flip_pending(params);
+			hwss_hubp_wait_flip_pending(params->hubp_wait_flip_pending_params.hubp,
+				params->hubp_wait_flip_pending_params.polling_interval_us);
 			break;
 		case TG_WAIT_DOUBLE_BUFFER_PENDING:
 			hwss_tg_wait_double_buffer_pending(params);
@@ -2234,23 +2231,85 @@ void hwss_execute_sequence(struct dc *dc,
 	}
 }
 
-/*
- * Helper function to add OPTC pipe control lock to block sequence
- */
 void hwss_add_optc_pipe_control_lock(struct block_sequence_state *seq_state,
 		struct dc *dc,
 		struct pipe_ctx *pipe_ctx,
 		bool lock)
 {
-	if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) {
-		seq_state->steps[*seq_state->num_steps].params.pipe_control_lock_params.dc = dc;
-		seq_state->steps[*seq_state->num_steps].params.pipe_control_lock_params.pipe_ctx = pipe_ctx;
-		seq_state->steps[*seq_state->num_steps].params.pipe_control_lock_params.lock = lock;
-		seq_state->steps[*seq_state->num_steps].func = OPTC_PIPE_CONTROL_LOCK;
-		(*seq_state->num_steps)++;
+	struct pipe_control_lock_params params = { 0 };
+	unsigned int hubp_idx;
+	unsigned int polling_interval_us = 1;
+
+	if (!dc->hwss.build_pipe_control_lock_sequence ||
+			!dc->hwss.build_pipe_control_lock_sequence(dc, pipe_ctx, lock, &params))
+		return;
+
+	for (hubp_idx = 0; hubp_idx < MAX_PIPES; hubp_idx++)
+		if (params.hubps_to_wait_for_flip[hubp_idx])
+			hwss_add_hubp_wait_flip_pending(seq_state, params.hubps_to_wait_for_flip[hubp_idx],
+				polling_interval_us);
+
+	if (params.gsl_lock) {
+		hwss_add_tg_set_gsl(seq_state, params.gsl.tg, params.gsl.gsl);
+		hwss_add_tg_set_gsl_source_select(seq_state, params.gsl_source_select.tg,
+			params.gsl_source_select.group_idx, params.gsl_source_select.gsl_ready_signal);
+	}
+
+	if (params.tg_3dlut_wa_unlock) {
+		if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) {
+			seq_state->steps[*seq_state->num_steps].params.tg_3dlut_wa_unlock_params =
+					params.tg_3dlut_wa_unlock_params;
+			seq_state->steps[*seq_state->num_steps].func = TG_3DLUT_WA_UNLOCK;
+			(*seq_state->num_steps)++;
+		}
+	} else {
+		if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) {
+			seq_state->steps[*seq_state->num_steps].params.tg_lock_params = params.tg_lock;
+			seq_state->steps[*seq_state->num_steps].func = TG_LOCK;
+			(*seq_state->num_steps)++;
+		}
 	}
 }
 
+void hwss_pipe_control_lock(struct dc *dc,
+		struct pipe_ctx *pipe_ctx,
+		bool lock)
+{
+	struct pipe_control_lock_params params = { 0 };
+	unsigned int hubp_idx;
+	unsigned int polling_interval_us = 1;
+
+	if (!dc->hwss.build_pipe_control_lock_sequence ||
+			!dc->hwss.build_pipe_control_lock_sequence(dc, pipe_ctx, lock, &params))
+		return;
+
+	for (hubp_idx = 0; hubp_idx < MAX_PIPES; hubp_idx++)
+		if (params.hubps_to_wait_for_flip[hubp_idx])
+			hwss_hubp_wait_flip_pending(
+					params.hubps_to_wait_for_flip[hubp_idx],
+					polling_interval_us);
+
+	if (params.gsl_lock) {
+		if (params.gsl.tg->funcs->set_gsl)
+			params.gsl.tg->funcs->set_gsl(params.gsl.tg, &params.gsl.gsl);
+		if (params.gsl_source_select.tg->funcs->set_gsl_source_select)
+			params.gsl_source_select.tg->funcs->set_gsl_source_select(
+					params.gsl_source_select.tg,
+					params.gsl_source_select.group_idx,
+					params.gsl_source_select.gsl_ready_signal);
+	}
+
+	if (params.tg_3dlut_wa_unlock) {
+		dc->hwseq->funcs.perform_3dlut_wa_unlock(
+				params.tg_3dlut_wa_unlock_params.tg,
+				params.tg_3dlut_wa_unlock_params.hubp);
+		return;
+	}
+
+	if (dc->hwss.tg_lock)
+		dc->hwss.tg_lock(&params.tg_lock);
+}
+
 /*
  * Helper function to add HUBP set flip control GSL to block sequence
  */
@@ -2853,12 +2912,10 @@ void hwss_add_tg_enable_crtc(struct block_sequence_state *seq_state,
  */
 void hwss_add_hubp_wait_flip_pending(struct block_sequence_state *seq_state,
 		struct hubp *hubp,
-		unsigned int timeout_us,
 		unsigned int polling_interval_us)
 {
 	if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) {
 		seq_state->steps[*seq_state->num_steps].params.hubp_wait_flip_pending_params.hubp = hubp;
-		seq_state->steps[*seq_state->num_steps].params.hubp_wait_flip_pending_params.timeout_us = timeout_us;
 		seq_state->steps[*seq_state->num_steps].params.hubp_wait_flip_pending_params.polling_interval_us = polling_interval_us;
 		seq_state->steps[*seq_state->num_steps].func = HUBP_WAIT_FLIP_PENDING;
 		(*seq_state->num_steps)++;
@@ -3611,11 +3668,9 @@ void hwss_tg_set_gsl_source_select(union block_sequence_params *params)
 		tg->funcs->set_gsl_source_select(tg, group_idx, gsl_ready_signal);
 }
 
-void hwss_hubp_wait_flip_pending(union block_sequence_params *params)
+void hwss_hubp_wait_flip_pending(struct hubp *hubp, unsigned int polling_interval_us)
 {
-	struct hubp *hubp = params->hubp_wait_flip_pending_params.hubp;
-	unsigned int timeout_us = params->hubp_wait_flip_pending_params.timeout_us;
-	unsigned int polling_interval_us = params->hubp_wait_flip_pending_params.polling_interval_us;
+	const unsigned int timeout_us = 100000U;
 	unsigned int j = 0;
 
 	for (j = 0; j < timeout_us / polling_interval_us
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce/dce_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce/dce_hwseq.c
index 4202fadb2c0e9..0f44577572fbe 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dce/dce_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dce/dce_hwseq.c
@@ -44,20 +44,36 @@ void dce_enable_fe_clock(struct dce_hwseq *hws,
 			DCFE_CLOCK_ENABLE, enable);
 }
 
-void dce_pipe_control_lock(struct dc *dc,
+bool dce_build_pipe_control_lock_sequence(struct dc *dc,
 		struct pipe_ctx *pipe,
-		bool lock)
+		bool lock,
+		struct pipe_control_lock_params *params)
 {
-	uint32_t lock_val = lock ? 1 : 0;
+	if (!pipe || pipe->top_pipe)
+		return false;
+
+	params->lock = lock;
+	params->tg_lock.dc = dc;
+	params->tg_lock.tg = pipe->stream_res.tg;
+	params->tg_lock.lock = lock;
+	params->tg_lock.triplebuffer_flips = pipe->plane_state &&
+			pipe->plane_state->triplebuffer_flips;
+	return true;
+}
+
+void dce_tg_lock(struct tg_lock_params *params)
+{
+	uint32_t lock_val = params->lock ? 1 : 0;
 	uint32_t dcp_grph, scl, blnd, update_lock_mode, val;
-	struct dce_hwseq *hws = dc->hwseq;
+	struct dce_hwseq *hws = params->dc->hwseq;
+	unsigned int tg_inst = params->tg->inst;
 
 	/* Not lock pipe when blank */
-	if (lock && pipe->stream_res.tg->funcs->is_blanked &&
-	    pipe->stream_res.tg->funcs->is_blanked(pipe->stream_res.tg))
+	if (params->lock && params->tg->funcs->is_blanked &&
+			params->tg->funcs->is_blanked(params->tg))
 		return;
 
-	val = REG_GET_4(BLND_V_UPDATE_LOCK[pipe->stream_res.tg->inst],
+	val = REG_GET_4(BLND_V_UPDATE_LOCK[tg_inst],
 			BLND_DCP_GRPH_V_UPDATE_LOCK, &dcp_grph,
 			BLND_SCL_V_UPDATE_LOCK, &scl,
 			BLND_BLND_V_UPDATE_LOCK, &blnd,
@@ -68,28 +84,28 @@ void dce_pipe_control_lock(struct dc *dc,
 	blnd = lock_val;
 	update_lock_mode = lock_val;
 
-	REG_SET_2(BLND_V_UPDATE_LOCK[pipe->stream_res.tg->inst], val,
+	REG_SET_2(BLND_V_UPDATE_LOCK[tg_inst], val,
 			BLND_DCP_GRPH_V_UPDATE_LOCK, dcp_grph,
 			BLND_SCL_V_UPDATE_LOCK, scl);
 
 	if (hws->masks->BLND_BLND_V_UPDATE_LOCK != 0)
-		REG_SET_2(BLND_V_UPDATE_LOCK[pipe->stream_res.tg->inst], val,
+		REG_SET_2(BLND_V_UPDATE_LOCK[tg_inst], val,
 				BLND_BLND_V_UPDATE_LOCK, blnd,
 				BLND_V_UPDATE_LOCK_MODE, update_lock_mode);
 
 	if (hws->wa.blnd_crtc_trigger) {
-		if (!lock) {
-			uint32_t value = REG_READ(CRTC_H_BLANK_START_END[pipe->stream_res.tg->inst]);
-			REG_WRITE(CRTC_H_BLANK_START_END[pipe->stream_res.tg->inst], value);
+		if (!params->lock) {
+			uint32_t value = REG_READ(CRTC_H_BLANK_START_END[tg_inst]);
+
+			REG_WRITE(CRTC_H_BLANK_START_END[tg_inst], value);
 		}
 	}
 }
 
 #if defined(CONFIG_DRM_AMD_DC_SI)
-void dce60_pipe_control_lock(struct dc *dc,
-		struct pipe_ctx *pipe,
-		bool lock)
+void dce60_tg_lock(struct tg_lock_params *params)
 {
+	(void)params;
 	/* DCE6 has no BLND_V_UPDATE_LOCK register */
 }
 #endif
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce/dce_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dce/dce_hwseq.h
index 2cc4e7516a5d7..0b9b418626110 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dce/dce_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dce/dce_hwseq.h
@@ -26,6 +26,7 @@
 #define __DCE_HWSEQ_H__
 
 #include "dc_types.h"
+#include "hw_sequencer_private.h"
 
 #define HWSEQ_DCEF_REG_LIST_DCE8() \
 	.DCFE_CLOCK_CONTROL[0] = mmCRTC0_CRTC_DCFE_CLOCK_CONTROL, \
@@ -1299,17 +1300,16 @@ struct clock_source;
 void dce_enable_fe_clock(struct dce_hwseq *hwss,
 		unsigned int inst, bool enable);
 
-void dce_pipe_control_lock(struct dc *dc,
-		struct pipe_ctx *pipe,
-		bool lock);
+bool dce_build_pipe_control_lock_sequence(struct dc *dc,
+		struct pipe_ctx *pipe, bool lock,
+		struct pipe_control_lock_params *params);
+void dce_tg_lock(struct tg_lock_params *params);
 
 void dce_set_blender_mode(struct dce_hwseq *hws,
 	unsigned int blnd_inst, enum blnd_mode mode);
 
 #if defined(CONFIG_DRM_AMD_DC_SI)
-void dce60_pipe_control_lock(struct dc *dc,
-		struct pipe_ctx *pipe,
-		bool lock);
+void dce60_tg_lock(struct tg_lock_params *params);
 #endif
 
 void dce_clock_gating_power_up(struct dce_hwseq *hws,
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 ef91c61033a33..bb46d72490259 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
@@ -3640,9 +3640,10 @@ static const struct hw_sequencer_funcs dce110_funcs = {
 	.enable_audio_stream = dce110_enable_audio_stream,
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dce110_power_down_fe,
-	.pipe_control_lock = dce_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dce_build_pipe_control_lock_sequence,
+	.tg_lock = dce_tg_lock,
 	.interdependent_update_lock = NULL,
-	.cursor_lock = dce_pipe_control_lock,
+	.cursor_lock = hwss_pipe_control_lock,
 	.prepare_bandwidth = dce110_prepare_bandwidth,
 	.optimize_bandwidth = dce110_optimize_bandwidth,
 	.set_drr = set_drr,
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 1652e47716a0a..b73a10b0acbd7 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
@@ -423,8 +423,9 @@ void dce60_hw_sequencer_construct(struct dc *dc)
 
 	dc->hwseq->funcs.enable_display_power_gating = dce100_enable_display_power_gating;
 	dc->hwss.apply_ctx_for_surface = dce60_apply_ctx_for_surface;
-	dc->hwss.cursor_lock = dce60_pipe_control_lock;
-	dc->hwss.pipe_control_lock = dce60_pipe_control_lock;
+	dc->hwss.cursor_lock = hwss_pipe_control_lock;
+	dc->hwss.build_pipe_control_lock_sequence = dce_build_pipe_control_lock_sequence;
+	dc->hwss.tg_lock = dce60_tg_lock;
 	dc->hwss.prepare_bandwidth = dce100_prepare_bandwidth;
 	dc->hwss.optimize_bandwidth = dce100_optimize_bandwidth;
 	dc->hwss.clear_surface_dcc_and_tiling = dce100_reset_surface_dcc_and_tiling;
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce80/dce80_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce80/dce80_hwseq.c
index 76fd45550c5e7..f44f41b13007d 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dce80/dce80_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dce80/dce80_hwseq.c
@@ -47,7 +47,8 @@ void dce80_hw_sequencer_construct(struct dc *dc)
 	dce110_hw_sequencer_construct(dc);
 
 	dc->hwseq->funcs.enable_display_power_gating = dce100_enable_display_power_gating;
-	dc->hwss.pipe_control_lock = dce_pipe_control_lock;
+	dc->hwss.build_pipe_control_lock_sequence = dce_build_pipe_control_lock_sequence;
+	dc->hwss.tg_lock = dce_tg_lock;
 	dc->hwss.prepare_bandwidth = dce100_prepare_bandwidth;
 	dc->hwss.optimize_bandwidth = dce100_optimize_bandwidth;
 	dc->hwss.clear_surface_dcc_and_tiling = dce100_reset_surface_dcc_and_tiling;
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 d76fc6ef38c4e..b1679791f903f 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
@@ -246,9 +246,9 @@ void dcn10_lock_all_pipes(struct dc *dc,
 			continue;
 
 		if (lock)
-			dc->hwss.pipe_control_lock(dc, pipe_ctx, true);
+			hwss_pipe_control_lock(dc, pipe_ctx, true);
 		else
-			dc->hwss.pipe_control_lock(dc, pipe_ctx, false);
+			hwss_pipe_control_lock(dc, pipe_ctx, false);
 	}
 }
 
@@ -2202,29 +2202,20 @@ bool dcn10_set_output_transfer_func(struct set_output_transfer_func_params *para
 	return true;
 }
 
-void dcn10_pipe_control_lock(
-	struct dc *dc,
-	struct pipe_ctx *pipe,
-	bool lock)
+void dcn10_tg_lock(struct tg_lock_params *params)
 {
-	struct dce_hwseq *hws = dc->hwseq;
-
-	/* use TG master update lock to lock everything on the TG
-	 * therefore only top pipe need to lock
-	 */
-	if (!pipe || pipe->top_pipe)
-		return;
+	struct dce_hwseq *hws = params->dc->hwseq;
 
-	if (dc->debug.sanity_checks)
-		hws->funcs.verify_allow_pstate_change_high(dc);
+	if (params->dc->debug.sanity_checks)
+		hws->funcs.verify_allow_pstate_change_high(params->dc);
 
-	if (lock)
-		pipe->stream_res.tg->funcs->lock(pipe->stream_res.tg);
+	if (params->lock)
+		params->tg->funcs->lock(params->tg);
 	else
-		pipe->stream_res.tg->funcs->unlock(pipe->stream_res.tg);
+		params->tg->funcs->unlock(params->tg);
 
-	if (dc->debug.sanity_checks)
-		hws->funcs.verify_allow_pstate_change_high(dc);
+	if (params->dc->debug.sanity_checks)
+		hws->funcs.verify_allow_pstate_change_high(params->dc);
 }
 
 /**
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h
index 1f36bd58beb24..30c213f2e8d6c 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h
@@ -57,10 +57,7 @@ void dcn10_wait_for_pipe_update_if_needed(
 void dcn10_set_wait_for_update_needed_for_pipe(
 	struct dc *dc,
 	struct pipe_ctx *pipe_ctx);
-void dcn10_pipe_control_lock(
-	struct dc *dc,
-	struct pipe_ctx *pipe,
-	bool lock);
+void dcn10_tg_lock(struct tg_lock_params *params);
 void dcn10_cursor_lock(struct dc *dc, struct pipe_ctx *pipe, bool lock);
 void dcn10_blank_pixel_data(
 		struct dc *dc,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_init.c
index b5e82e1901247..47ab50a3fab1c 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_init.c
@@ -27,6 +27,7 @@
 #include "dce110/dce110_hwseq.h"
 #include "dcn10/dcn10_hwseq.h"
 #include "dcn20/dcn20_hwseq.h"
+#include "dc/hwss/dce/dce_hwseq.h"
 
 static const struct hw_sequencer_funcs dcn10_funcs = {
 	.program_gamut_remap = dcn10_program_gamut_remap,
@@ -54,7 +55,8 @@ static const struct hw_sequencer_funcs dcn10_funcs = {
 	.enable_audio_stream = dce110_enable_audio_stream,
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn10_disable_plane,
-	.pipe_control_lock = dcn10_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dce_build_pipe_control_lock_sequence,
+	.tg_lock = dcn10_tg_lock,
 	.cursor_lock = dcn10_cursor_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.prepare_bandwidth = dcn10_prepare_bandwidth,
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 23e191d472991..38d587af9dd82 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
@@ -217,15 +217,18 @@ static int find_free_gsl_group(const struct dc *dc)
  * gsl_0 <=> pipe_ctx->stream_res.gsl_group == 1
  * Using a magic value like -1 would require tracking all inits/resets
  */
-void dcn20_setup_gsl_group_as_lock(
+void dcn20_build_gsl_group_as_lock(
 		const struct dc *dc,
 		struct pipe_ctx *pipe_ctx,
-		bool enable)
+		bool enable,
+		struct tg_set_gsl_params *gsl_params,
+		struct tg_set_gsl_source_select_params *gsl_source_select_params)
 {
-	struct gsl_params gsl;
 	int group_idx;
 
-	memset(&gsl, 0, sizeof(struct gsl_params));
+	memset(&gsl_params->gsl, 0, sizeof(gsl_params->gsl));
+	gsl_params->tg = pipe_ctx->stream_res.tg;
+	gsl_source_select_params->tg = pipe_ctx->stream_res.tg;
 
 	if (enable) {
 		/* return if group already assigned since GSL was set up
@@ -241,22 +244,22 @@ void dcn20_setup_gsl_group_as_lock(
 		/* set gsl group reg field and mark resource used */
 		switch (group_idx) {
 		case 1:
-			gsl.gsl0_en = 1;
+			gsl_params->gsl.gsl0_en = 1;
 			dc->res_pool->gsl_groups.gsl_0 = 1;
 			break;
 		case 2:
-			gsl.gsl1_en = 1;
+			gsl_params->gsl.gsl1_en = 1;
 			dc->res_pool->gsl_groups.gsl_1 = 1;
 			break;
 		case 3:
-			gsl.gsl2_en = 1;
+			gsl_params->gsl.gsl2_en = 1;
 			dc->res_pool->gsl_groups.gsl_2 = 1;
 			break;
 		default:
 			BREAK_TO_DEBUGGER();
 			return; // invalid case
 		}
-		gsl.gsl_master_en = 1;
+		gsl_params->gsl.gsl_master_en = 1;
 	} else {
 		group_idx = pipe_ctx->stream_res.gsl_group;
 		if (group_idx == 0)
@@ -267,32 +270,46 @@ void dcn20_setup_gsl_group_as_lock(
 		/* unset gsl group reg field and mark resource free */
 		switch (group_idx) {
 		case 1:
-			gsl.gsl0_en = 0;
 			dc->res_pool->gsl_groups.gsl_0 = 0;
 			break;
 		case 2:
-			gsl.gsl1_en = 0;
 			dc->res_pool->gsl_groups.gsl_1 = 0;
 			break;
 		case 3:
-			gsl.gsl2_en = 0;
 			dc->res_pool->gsl_groups.gsl_2 = 0;
 			break;
 		default:
 			BREAK_TO_DEBUGGER();
 			return;
 		}
-		gsl.gsl_master_en = 0;
 	}
 
+	gsl_source_select_params->group_idx = group_idx;
+	gsl_source_select_params->gsl_ready_signal = enable ? 4 : 0;
+}
+
+void dcn20_setup_gsl_group_as_lock(
+		const struct dc *dc,
+		struct pipe_ctx *pipe_ctx,
+		bool enable)
+{
+	struct tg_set_gsl_params gsl_params = { 0 };
+	struct tg_set_gsl_source_select_params gsl_source_select_params = { 0 };
+
+	dcn20_build_gsl_group_as_lock(dc, pipe_ctx, enable,
+			&gsl_params, &gsl_source_select_params);
+	if (!gsl_source_select_params.group_idx)
+		return;
+
 	/* at this point we want to program whether it's to enable or disable */
 	if (pipe_ctx->stream_res.tg->funcs->set_gsl != NULL) {
 		pipe_ctx->stream_res.tg->funcs->set_gsl(
 			pipe_ctx->stream_res.tg,
-			&gsl);
+			&gsl_params.gsl);
 		if (pipe_ctx->stream_res.tg->funcs->set_gsl_source_select != NULL)
 			pipe_ctx->stream_res.tg->funcs->set_gsl_source_select(
-				pipe_ctx->stream_res.tg, group_idx, enable ? 4 : 0);
+				pipe_ctx->stream_res.tg, gsl_source_select_params.group_idx,
+				gsl_source_select_params.gsl_ready_signal);
 	} else
 		BREAK_TO_DEBUGGER();
 }
@@ -1386,19 +1403,30 @@ void dcn20_enable_plane(struct dc *dc, struct pipe_ctx *pipe_ctx,
 //	}
 }
 
-void dcn20_pipe_control_lock(
-	struct dc *dc,
-	struct pipe_ctx *pipe,
-	bool lock)
+bool dcn20_build_pipe_control_lock_sequence(
+		struct dc *dc,
+		struct pipe_ctx *pipe,
+		bool lock,
+		struct pipe_control_lock_params *params)
 {
 	struct pipe_ctx *temp_pipe;
 	bool flip_immediate = false;
+	unsigned int hubp_count = 0;
 
 	/* use TG master update lock to lock everything on the TG
 	 * therefore only top pipe need to lock
 	 */
 	if (!pipe || pipe->top_pipe)
-		return;
+		return false;
+
+	params->lock = lock;
+	params->tg_lock.dc = dc;
+	params->tg_lock.lock = lock;
+	params->tg_lock.tg = pipe->stream_res.tg;
+	params->tg_lock.use_dmub_inbox1 = pipe->stream &&
+			should_use_dmub_inbox1_lock(dc, pipe->stream->link);
+	params->tg_lock.triplebuffer_flips = pipe->plane_state &&
+			pipe->plane_state->triplebuffer_flips;
 
 	if (pipe->plane_state != NULL)
 		flip_immediate = pipe->plane_state->flip_immediate;
@@ -1413,22 +1441,10 @@ void dcn20_pipe_control_lock(
 	}
 
 	if (flip_immediate && lock) {
-		const unsigned int TIMEOUT_FOR_FLIP_PENDING_US = 100000U;
-		unsigned int polling_interval_us = 1;
-		unsigned int i;
-
 		temp_pipe = pipe;
 		while (temp_pipe) {
-			if (temp_pipe->plane_state && temp_pipe->plane_state->flip_immediate) {
-				for (i = 0; i < TIMEOUT_FOR_FLIP_PENDING_US / polling_interval_us; ++i) {
-					if (!temp_pipe->plane_res.hubp->funcs->hubp_is_flip_pending(temp_pipe->plane_res.hubp))
-						break;
-					udelay(polling_interval_us);
-				}
-
-				/* no reason it should take this long for immediate flips */
-				ASSERT(i != TIMEOUT_FOR_FLIP_PENDING_US);
-			}
+			if (temp_pipe->plane_state && temp_pipe->plane_state->flip_immediate)
+				params->hubps_to_wait_for_flip[hubp_count++] = temp_pipe->plane_res.hubp;
 			temp_pipe = temp_pipe->bottom_pipe;
 		}
 	}
@@ -1438,8 +1454,11 @@ void dcn20_pipe_control_lock(
 	 */
 	if (lock && (pipe->bottom_pipe != NULL || !flip_immediate))
 		if ((flip_immediate && pipe->stream_res.gsl_group == 0) ||
-		    (!flip_immediate && pipe->stream_res.gsl_group > 0))
-			dcn20_setup_gsl_group_as_lock(dc, pipe, flip_immediate);
+		    (!flip_immediate && pipe->stream_res.gsl_group > 0)) {
+			params->gsl_lock = true;
+			dcn20_build_gsl_group_as_lock(dc, pipe, flip_immediate,
+					&params->gsl, &params->gsl_source_select);
+		}
 
 	if (pipe->plane_state != NULL)
 		flip_immediate = pipe->plane_state->flip_immediate;
@@ -1452,37 +1471,53 @@ void dcn20_pipe_control_lock(
 	}
 
 	if (!lock && pipe->stream_res.gsl_group > 0 && pipe->plane_state &&
-		!flip_immediate)
-	    dcn20_setup_gsl_group_as_lock(dc, pipe, false);
+			!flip_immediate) {
+		params->gsl_lock = true;
+		dcn20_build_gsl_group_as_lock(dc, pipe, false,
+				&params->gsl, &params->gsl_source_select);
+	}
+
+	if (!lock && !params->tg_lock.use_dmub_inbox1 &&
+			!params->tg_lock.triplebuffer_flips &&
+			dc->hwseq->funcs.perform_3dlut_wa_unlock) {
+		const struct pipe_ctx *otg_master_pipe = resource_get_otg_master(pipe);
+		const struct pipe_ctx *primary_dpp_pipe = resource_is_pipe_type(pipe, DPP_PIPE) ?
+				resource_get_primary_dpp_pipe(pipe) : pipe;
+
+		if (otg_master_pipe && otg_master_pipe->stream_res.tg &&
+			 primary_dpp_pipe && primary_dpp_pipe->plane_state &&
+				primary_dpp_pipe->plane_state->cm.flags.bits.lut3d_enable &&
+				primary_dpp_pipe->plane_state->cm.flags.bits.lut3d_dma_enable) {
+			params->tg_3dlut_wa_unlock = true;
+			params->tg_3dlut_wa_unlock_params.tg = otg_master_pipe->stream_res.tg;
+			params->tg_3dlut_wa_unlock_params.hubp = primary_dpp_pipe->plane_res.hubp;
+		}
+	}
+
+	return true;
+}
 
-	if (pipe->stream && should_use_dmub_inbox1_lock(dc, pipe->stream->link)) {
+void dcn20_tg_lock(struct tg_lock_params *params)
+{
+	if (params->use_dmub_inbox1) {
 		union dmub_hw_lock_flags hw_locks = { 0 };
 		struct dmub_hw_lock_inst_flags inst_flags = { 0 };
 
 		hw_locks.bits.lock_pipe = 1;
-		inst_flags.otg_inst = (uint8_t)pipe->stream_res.tg->inst;
-
-		if (pipe->plane_state != NULL)
-			hw_locks.bits.triple_buffer_lock = pipe->plane_state->triplebuffer_flips;
-
-		dmub_hw_lock_mgr_cmd(dc->ctx->dmub_srv,
-					lock,
-					&hw_locks,
-					&inst_flags);
-	} else if (pipe->plane_state != NULL && pipe->plane_state->triplebuffer_flips) {
-		if (lock)
-			pipe->stream_res.tg->funcs->triplebuffer_lock(pipe->stream_res.tg);
+		inst_flags.otg_inst = (uint8_t)params->tg->inst;
+		hw_locks.bits.triple_buffer_lock = params->triplebuffer_flips;
+		dmub_hw_lock_mgr_cmd(params->dc->ctx->dmub_srv, params->lock,
+				&hw_locks, &inst_flags);
+	} else if (params->triplebuffer_flips) {
+		if (params->lock)
+			params->tg->funcs->triplebuffer_lock(params->tg);
 		else
-			pipe->stream_res.tg->funcs->triplebuffer_unlock(pipe->stream_res.tg);
+			params->tg->funcs->triplebuffer_unlock(params->tg);
 	} else {
-		if (lock)
-			pipe->stream_res.tg->funcs->lock(pipe->stream_res.tg);
-		else {
-			if (dc->hwseq->funcs.perform_3dlut_wa_unlock)
-				dc->hwseq->funcs.perform_3dlut_wa_unlock(pipe);
-			else
-				pipe->stream_res.tg->funcs->unlock(pipe->stream_res.tg);
-		}
+		if (params->lock)
+			params->tg->funcs->lock(params->tg);
+		else
+			params->tg->funcs->unlock(params->tg);
 	}
 }
 
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h
index 9312c88e31ad9..c7bf79fda54c7 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h
@@ -61,10 +61,16 @@ void dcn20_blank_pixel_data(
 		struct dc *dc,
 		struct pipe_ctx *pipe_ctx,
 		bool blank);
-void dcn20_pipe_control_lock(
-	struct dc *dc,
-	struct pipe_ctx *pipe,
-	bool lock);
+bool dcn20_build_pipe_control_lock_sequence(struct dc *dc,
+		struct pipe_ctx *pipe, bool lock,
+		struct pipe_control_lock_params *params);
+void dcn20_build_gsl_group_as_lock(
+		const struct dc *dc,
+		struct pipe_ctx *pipe_ctx,
+		bool enable,
+		struct tg_set_gsl_params *gsl_params,
+		struct tg_set_gsl_source_select_params *gsl_source_select_params);
+void dcn20_tg_lock(struct tg_lock_params *params);
 void dcn20_prepare_bandwidth(
 		struct dc *dc,
 		struct dc_state *context);
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_init.c
index 1797a91b0186e..1c6c4e19bdda3 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_init.c
@@ -56,7 +56,8 @@ static const struct hw_sequencer_funcs dcn20_funcs = {
 	.enable_audio_stream = dce110_enable_audio_stream,
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn20_disable_plane,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn20_prepare_bandwidth,
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 7b820bdae55b4..9f4bc2a498730 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
@@ -525,35 +525,27 @@ void dcn201_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx)
 	hubp->mpcc_id = mpcc_id;
 }
 
-void dcn201_pipe_control_lock(
-	struct dc *dc,
-	struct pipe_ctx *pipe,
-	bool lock)
+void dcn201_tg_lock(struct tg_lock_params *params)
 {
-	struct dce_hwseq *hws = dc->hwseq;
-	/* use TG master update lock to lock everything on the TG
-	 * therefore only top pipe need to lock
-	 */
-	if (pipe->top_pipe)
-		return;
+	struct dce_hwseq *hws = params->dc->hwseq;
 
-	if (dc->debug.sanity_checks)
-		hws->funcs.verify_allow_pstate_change_high(dc);
+	if (params->dc->debug.sanity_checks)
+		hws->funcs.verify_allow_pstate_change_high(params->dc);
 
-	if (pipe->plane_state != NULL && pipe->plane_state->triplebuffer_flips) {
-		if (lock)
-			pipe->stream_res.tg->funcs->triplebuffer_lock(pipe->stream_res.tg);
+	if (params->triplebuffer_flips) {
+		if (params->lock)
+			params->tg->funcs->triplebuffer_lock(params->tg);
 		else
-			pipe->stream_res.tg->funcs->triplebuffer_unlock(pipe->stream_res.tg);
+			params->tg->funcs->triplebuffer_unlock(params->tg);
 	} else {
-		if (lock)
-			pipe->stream_res.tg->funcs->lock(pipe->stream_res.tg);
+		if (params->lock)
+			params->tg->funcs->lock(params->tg);
 		else
-			pipe->stream_res.tg->funcs->unlock(pipe->stream_res.tg);
+			params->tg->funcs->unlock(params->tg);
 	}
 
-	if (dc->debug.sanity_checks)
-		hws->funcs.verify_allow_pstate_change_high(dc);
+	if (params->dc->debug.sanity_checks)
+		hws->funcs.verify_allow_pstate_change_high(params->dc);
 }
 
 void dcn201_set_cursor_attribute(struct pipe_ctx *pipe_ctx)
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.h
index 6a50a9894be6a..bab5d9e6912e4 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.h
@@ -36,10 +36,7 @@ void dcn201_update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx);
 void dcn201_plane_atomic_disconnect(struct dc *dc, struct dc_state *state, struct pipe_ctx *pipe_ctx);
 void dcn201_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx);
 void dcn201_set_cursor_attribute(struct pipe_ctx *pipe_ctx);
-void dcn201_pipe_control_lock(
-	struct dc *dc,
-	struct pipe_ctx *pipe,
-	bool lock);
+void dcn201_tg_lock(struct tg_lock_params *params);
 void dcn201_init_blank(
 		struct dc *dc,
 		struct timing_generator *tg);
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_init.c
index dec57fb4c05c9..95dda06b6b175 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_init.c
@@ -23,6 +23,7 @@
  *
  */
 
+#include "dce/dce_hwseq.h"
 #include "dce110/dce110_hwseq.h"
 #include "dcn10/dcn10_hwseq.h"
 #include "dcn20/dcn20_hwseq.h"
@@ -55,7 +56,8 @@ static const struct hw_sequencer_funcs dcn201_funcs = {
 	.enable_audio_stream = dce110_enable_audio_stream,
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn10_disable_plane,
-	.pipe_control_lock = dcn201_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dce_build_pipe_control_lock_sequence,
+	.tg_lock = dcn201_tg_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn20_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_init.c
index c7701a8b574aa..b8ed664e1babf 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_init.c
@@ -56,7 +56,8 @@ static const struct hw_sequencer_funcs dcn21_funcs = {
 	.enable_audio_stream = dce110_enable_audio_stream,
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn20_disable_plane,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn20_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_init.c
index d5aa58462855c..b655c01f013bc 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_init.c
@@ -57,7 +57,8 @@ static const struct hw_sequencer_funcs dcn30_funcs = {
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn20_disable_plane,
 	.disable_pixel_data = dcn20_disable_pixel_data,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn30_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn301/dcn301_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn301/dcn301_init.c
index a570333aeac18..dfacc59166cdd 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn301/dcn301_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn301/dcn301_init.c
@@ -58,7 +58,8 @@ static const struct hw_sequencer_funcs dcn301_funcs = {
 	.enable_audio_stream = dce110_enable_audio_stream,
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn20_disable_plane,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn20_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_init.c
index 7197414e5bd69..3c2e2162da117 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_init.c
@@ -60,7 +60,8 @@ static const struct hw_sequencer_funcs dcn31_funcs = {
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn20_disable_plane,
 	.disable_pixel_data = dcn20_disable_pixel_data,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn20_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn314/dcn314_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn314/dcn314_init.c
index 4966c044a8644..b50ab468b8f8f 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn314/dcn314_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn314/dcn314_init.c
@@ -62,7 +62,8 @@ static const struct hw_sequencer_funcs dcn314_funcs = {
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn20_disable_plane,
 	.disable_pixel_data = dcn20_disable_pixel_data,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn20_prepare_bandwidth,
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 ec38fc2ba65cc..79be5c385280c 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
@@ -1867,9 +1867,9 @@ void dcn32_interdependent_update_lock(struct dc *dc,
 			continue;
 
 		if (lock)
-			dc->hwss.pipe_control_lock(dc, pipe, true);
+			hwss_pipe_control_lock(dc, pipe, true);
 		else
-			dc->hwss.pipe_control_lock(dc, pipe, false);
+			hwss_pipe_control_lock(dc, pipe, false);
 	}
 }
 
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_init.c
index 364b4108f5d6f..2251289622167 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_init.c
@@ -60,7 +60,8 @@ static const struct hw_sequencer_funcs dcn32_funcs = {
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn20_disable_plane,
 	.disable_pixel_data = dcn20_disable_pixel_data,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn32_interdependent_update_lock,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn32_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_init.c
index fc18d2207711f..c9125cc25d157 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_init.c
@@ -64,7 +64,8 @@ static const struct hw_sequencer_funcs dcn35_funcs = {
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn35_disable_plane,
 	.disable_pixel_data = dcn20_disable_pixel_data,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn35_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn351/dcn351_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn351/dcn351_init.c
index 19ec5b4edfdc9..e6f7d57cbfed5 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn351/dcn351_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn351/dcn351_init.c
@@ -63,7 +63,8 @@ static const struct hw_sequencer_funcs dcn351_funcs = {
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn35_disable_plane,
 	.disable_pixel_data = dcn20_disable_pixel_data,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn10_lock_all_pipes,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn35_prepare_bandwidth,
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 0e107754c891e..8e0ccc9d58199 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
@@ -32,6 +32,7 @@
 #include "dcn10/dcn10_cm_common.h"
 #include "dcn10/dcn10_hubbub.h"
 #include "dcn20/dcn20_optc.h"
+#include "dcn20/dcn20_hwseq.h"
 #include "dcn30/dcn30_cm_common.h"
 #include "dcn32/dcn32_hwseq.h"
 #include "dcn401_hwseq.h"
@@ -1935,7 +1936,7 @@ void dcn401_interdependent_update_lock(struct dc *dc,
 					!tg->funcs->is_tg_enabled(tg) ||
 					dc_state_get_pipe_subvp_type(context, pipe) == SUBVP_PHANTOM)
 				continue;
-			dc->hwss.pipe_control_lock(dc, pipe, true);
+			hwss_pipe_control_lock(dc, pipe, true);
 		}
 	} else {
 		/* Need to free DET being used first and have pipe update, then unlock the remaining pipes*/
@@ -1951,7 +1952,7 @@ void dcn401_interdependent_update_lock(struct dc *dc,
 
 			if (dc->scratch.pipes_to_unlock_first[i]) {
 				struct pipe_ctx *old_pipe = &dc->current_state->res_ctx.pipe_ctx[i];
-				dc->hwss.pipe_control_lock(dc, pipe, false);
+				hwss_pipe_control_lock(dc, pipe, false);
 				/* Assumes pipe of the same index in current_state is also an OTG_MASTER pipe*/
 				dcn401_wait_for_det_buffer_update_under_otg_master(dc, dc->current_state, old_pipe);
 			}
@@ -1970,12 +1971,13 @@ void dcn401_interdependent_update_lock(struct dc *dc,
 				continue;
 			}
 
-			dc->hwss.pipe_control_lock(dc, pipe, false);
+			hwss_pipe_control_lock(dc, pipe, false);
 		}
 	}
 }
 
-void dcn401_perform_3dlut_wa_unlock(struct pipe_ctx *pipe_ctx)
+void dcn401_perform_3dlut_wa_unlock(struct timing_generator *tg,
+		struct hubp *primary_hubp)
 {
 	/* If 3DLUT FL is enabled and 3DLUT is in use, follow the workaround sequence for pipe unlock to make sure that
 	 * HUBP will properly fetch 3DLUT contents after unlock.
@@ -1983,45 +1985,24 @@ void dcn401_perform_3dlut_wa_unlock(struct pipe_ctx *pipe_ctx)
 	 * This is meant to work around a known HW issue where VREADY will cancel the pending 3DLUT_ENABLE signal regardless
 	 * of whether OTG lock is currently being held or not.
 	 */
-	if (!pipe_ctx)
+	if (!tg)
 		return;
 
-	const struct pipe_ctx *otg_master_pipe_ctx = resource_get_otg_master(pipe_ctx);
-	struct timing_generator *tg = otg_master_pipe_ctx ?
-			otg_master_pipe_ctx->stream_res.tg : NULL;
-	const struct pipe_ctx *primary_dpp_pipe_ctx = resource_is_pipe_type(pipe_ctx, DPP_PIPE) ?
-			resource_get_primary_dpp_pipe(pipe_ctx) : pipe_ctx;
-	struct hubp *primary_hubp = primary_dpp_pipe_ctx ?
-			primary_dpp_pipe_ctx->plane_res.hubp : NULL;
-
-	if (!otg_master_pipe_ctx || !tg) {
-		return;
-	}
-
-	if (primary_dpp_pipe_ctx &&
-			primary_dpp_pipe_ctx->plane_state &&
-			primary_dpp_pipe_ctx->plane_state->cm.flags.bits.lut3d_enable &&
-			primary_dpp_pipe_ctx->plane_state->cm.flags.bits.lut3d_dma_enable) {
-		if (tg->funcs->set_vupdate_keepout)
-			tg->funcs->set_vupdate_keepout(tg, true);
+	if (tg->funcs->set_vupdate_keepout)
+		tg->funcs->set_vupdate_keepout(tg, true);
 
-		if (primary_hubp && primary_hubp->funcs->hubp_enable_3dlut_fl) {
-			primary_hubp->funcs->hubp_enable_3dlut_fl(primary_hubp, true);
-		}
+	if (primary_hubp && primary_hubp->funcs->hubp_enable_3dlut_fl)
+		primary_hubp->funcs->hubp_enable_3dlut_fl(primary_hubp, true);
 
-		tg->funcs->unlock(tg);
-		if (tg->funcs->wait_update_lock_status)
-			tg->funcs->wait_update_lock_status(tg, false);
+	tg->funcs->unlock(tg);
+	if (tg->funcs->wait_update_lock_status)
+		tg->funcs->wait_update_lock_status(tg, false);
 
-		if (primary_hubp && primary_hubp->funcs->hubp_enable_3dlut_fl) {
-			primary_hubp->funcs->hubp_enable_3dlut_fl(primary_hubp, true);
-		}
+	if (primary_hubp && primary_hubp->funcs->hubp_enable_3dlut_fl)
+		primary_hubp->funcs->hubp_enable_3dlut_fl(primary_hubp, true);
 
-		if (tg->funcs->set_vupdate_keepout)
-			tg->funcs->set_vupdate_keepout(tg, false);
-	} else {
-		tg->funcs->unlock(tg);
-	}
+	if (tg->funcs->set_vupdate_keepout)
+		tg->funcs->set_vupdate_keepout(tg, false);
 }
 
 void dcn401_program_outstanding_updates(struct dc *dc,
@@ -3462,89 +3443,24 @@ void dcn401_update_writeback_sequence(
 	hwss_add_mcif_wb_config_buf(seq_state, mcif_wb, &wb_info->mcif_buf_params, wb_info->dwb_params.dest_height);
 }
 
-static int find_free_gsl_group(const struct dc *dc)
-{
-	if (dc->res_pool->gsl_groups.gsl_0 == 0)
-		return 1;
-	if (dc->res_pool->gsl_groups.gsl_1 == 0)
-		return 2;
-	if (dc->res_pool->gsl_groups.gsl_2 == 0)
-		return 3;
-
-	return 0;
-}
-
 void dcn401_setup_gsl_group_as_lock_sequence(
 		const struct dc *dc,
 		struct pipe_ctx *pipe_ctx,
 		bool enable,
 		struct block_sequence_state *seq_state)
 {
-	struct gsl_params gsl;
-	int group_idx;
-
-	memset(&gsl, 0, sizeof(struct gsl_params));
+	struct tg_set_gsl_params gsl_params = { 0 };
+	struct tg_set_gsl_source_select_params gsl_source_select_params = { 0 };
 
-	if (enable) {
-		/* return if group already assigned since GSL was set up
-		 * for vsync flip, we would unassign so it can't be "left over"
-		 */
-		if (pipe_ctx->stream_res.gsl_group > 0)
-			return;
-
-		group_idx = find_free_gsl_group(dc);
-		ASSERT(group_idx != 0);
-		pipe_ctx->stream_res.gsl_group = (uint8_t)group_idx;
-
-		/* set gsl group reg field and mark resource used */
-		switch (group_idx) {
-		case 1:
-			gsl.gsl0_en = 1;
-			dc->res_pool->gsl_groups.gsl_0 = 1;
-			break;
-		case 2:
-			gsl.gsl1_en = 1;
-			dc->res_pool->gsl_groups.gsl_1 = 1;
-			break;
-		case 3:
-			gsl.gsl2_en = 1;
-			dc->res_pool->gsl_groups.gsl_2 = 1;
-			break;
-		default:
-			BREAK_TO_DEBUGGER();
-			return; // invalid case
-		}
-		gsl.gsl_master_en = 1;
-	} else {
-		group_idx = pipe_ctx->stream_res.gsl_group;
-		if (group_idx == 0)
-			return; // if not in use, just return
-
-		pipe_ctx->stream_res.gsl_group = 0;
-
-		/* unset gsl group reg field and mark resource free */
-		switch (group_idx) {
-		case 1:
-			gsl.gsl0_en = 0;
-			dc->res_pool->gsl_groups.gsl_0 = 0;
-			break;
-		case 2:
-			gsl.gsl1_en = 0;
-			dc->res_pool->gsl_groups.gsl_1 = 0;
-			break;
-		case 3:
-			gsl.gsl2_en = 0;
-			dc->res_pool->gsl_groups.gsl_2 = 0;
-			break;
-		default:
-			BREAK_TO_DEBUGGER();
-			return;
-		}
-		gsl.gsl_master_en = 0;
-	}
+	dcn20_build_gsl_group_as_lock(dc, pipe_ctx, enable,
+			&gsl_params, &gsl_source_select_params);
+	if (!gsl_source_select_params.group_idx)
+		return;
 
-	hwss_add_tg_set_gsl(seq_state, pipe_ctx->stream_res.tg, gsl);
-	hwss_add_tg_set_gsl_source_select(seq_state, pipe_ctx->stream_res.tg, group_idx, enable ? 4 : 0);
+	hwss_add_tg_set_gsl(seq_state, gsl_params.tg, gsl_params.gsl);
+	hwss_add_tg_set_gsl_source_select(seq_state, gsl_source_select_params.tg,
+			gsl_source_select_params.group_idx,
+			gsl_source_select_params.gsl_ready_signal);
 }
 
 void dcn401_disable_plane_sequence(
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 c08fd359b38b0..756b2d98a9309 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
@@ -109,7 +109,8 @@ void dcn401_program_pipe_sequence(
 	struct pipe_ctx *pipe_ctx,
 	struct dc_state *context,
 	struct block_sequence_state *seq_state);
-void dcn401_perform_3dlut_wa_unlock(struct pipe_ctx *pipe_ctx);
+void dcn401_perform_3dlut_wa_unlock(struct timing_generator *tg,
+		struct hubp *primary_hubp);
 void dcn401_program_front_end_for_ctx(struct dc *dc, struct dc_state *context);
 void dcn401_post_unlock_program_front_end(struct dc *dc, struct dc_state *context);
 bool dcn401_update_bandwidth(struct dc *dc, struct dc_state *context);
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_init.c
index 7039f40ea880d..783415e4e3a10 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_init.c
@@ -40,7 +40,8 @@ static const struct hw_sequencer_funcs dcn401_funcs = {
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn20_disable_plane,
 	.disable_plane_sequence = dcn401_disable_plane_sequence,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn401_interdependent_update_lock,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn401_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_init.c
index ba12a97a220fe..aad1885cc9887 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_init.c
@@ -42,7 +42,8 @@ static const struct hw_sequencer_funcs dcn42_funcs = {
 	.enable_audio_stream = dce110_enable_audio_stream,
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn35_disable_plane,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn401_interdependent_update_lock,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn42_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_init.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_init.c
index 1c908964153b7..37e88d45409be 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_init.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_init.c
@@ -42,7 +42,8 @@ static const struct hw_sequencer_funcs dcn60_funcs = {
 	.disable_audio_stream = dce110_disable_audio_stream,
 	.disable_plane = dcn20_disable_plane,
 	.disable_plane_sequence = dcn401_disable_plane_sequence,
-	.pipe_control_lock = dcn20_pipe_control_lock,
+	.build_pipe_control_lock_sequence = dcn20_build_pipe_control_lock_sequence,
+	.tg_lock = dcn20_tg_lock,
 	.interdependent_update_lock = dcn401_interdependent_update_lock,
 	.cursor_lock = dcn10_cursor_lock,
 	.prepare_bandwidth = dcn401_prepare_bandwidth,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h
index 169f3420ac6dd..4f0d000eab948 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h
@@ -66,10 +66,17 @@ struct subvp_pipe_control_lock_fast_params {
 	bool subvp_immediate_flip;
 };
 
-struct pipe_control_lock_params {
+struct tg_lock_params {
 	struct dc *dc;
-	struct pipe_ctx *pipe_ctx;
+	struct timing_generator *tg;
 	bool lock;
+	bool use_dmub_inbox1;
+	bool triplebuffer_flips;
+};
+
+struct tg_3dlut_wa_unlock_params {
+	struct timing_generator *tg;
+	struct hubp *hubp;
 };
 
 struct set_flip_control_gsl_params {
@@ -342,6 +349,17 @@ struct tg_set_gsl_source_select_params {
 	uint32_t gsl_ready_signal;
 };
 
+struct pipe_control_lock_params {
+	bool lock;
+	struct hubp *hubps_to_wait_for_flip[MAX_PIPES];
+	bool gsl_lock;
+	struct tg_set_gsl_params gsl;
+	struct tg_set_gsl_source_select_params gsl_source_select;
+	struct tg_lock_params tg_lock;
+	bool tg_3dlut_wa_unlock;
+	struct tg_3dlut_wa_unlock_params tg_3dlut_wa_unlock_params;
+};
+
 struct setup_vupdate_interrupt_params {
 	struct dc *dc;
 	struct pipe_ctx *pipe_ctx;
@@ -487,7 +505,6 @@ struct tg_enable_crtc_params {
 
 struct hubp_wait_flip_pending_params {
 	struct hubp *hubp;
-	unsigned int timeout_us;
 	unsigned int polling_interval_us;
 };
 
@@ -1026,7 +1043,8 @@ struct link_set_dpms_on_params {
 union block_sequence_params {
 	struct update_plane_addr_params update_plane_addr_params;
 	struct subvp_pipe_control_lock_fast_params subvp_pipe_control_lock_fast_params;
-	struct pipe_control_lock_params pipe_control_lock_params;
+	struct tg_lock_params tg_lock_params;
+	struct tg_3dlut_wa_unlock_params tg_3dlut_wa_unlock_params;
 	struct set_flip_control_gsl_params set_flip_control_gsl_params;
 	struct program_triplebuffer_params program_triplebuffer_params;
 	struct set_input_transfer_func_params set_input_transfer_func_params;
@@ -1200,7 +1218,8 @@ union block_sequence_params {
 
 enum block_sequence_func {
 	DMUB_SUBVP_PIPE_CONTROL_LOCK_FAST = 0,
-	OPTC_PIPE_CONTROL_LOCK,
+	TG_LOCK,
+	TG_3DLUT_WA_UNLOCK,
 	HUBP_SET_FLIP_CONTROL_GSL,
 	HUBP_PROGRAM_TRIPLEBUFFER,
 	HUBP_UPDATE_PLANE_ADDR,
@@ -1432,8 +1451,10 @@ struct hw_sequencer_funcs {
 	void (*clear_surface_dcc_and_tiling)(struct pipe_ctx *pipe_ctx, struct dc_plane_state *plane_state, bool clear_tiling);
 
 	/* Pipe Lock Related */
-	void (*pipe_control_lock)(struct dc *dc,
-			struct pipe_ctx *pipe, bool lock);
+	bool (*build_pipe_control_lock_sequence)(struct dc *dc,
+			struct pipe_ctx *pipe, bool lock,
+			struct pipe_control_lock_params *params);
+	void (*tg_lock)(struct tg_lock_params *params);
 	void (*interdependent_update_lock)(struct dc *dc,
 			struct dc_state *context, bool lock);
 	void (*set_flip_control_gsl)(struct pipe_ctx *pipe_ctx,
@@ -1959,7 +1980,8 @@ void hwss_tg_set_gsl(union block_sequence_params *params);
 
 void hwss_tg_set_gsl_source_select(union block_sequence_params *params);
 
-void hwss_hubp_wait_flip_pending(union block_sequence_params *params);
+void hwss_hubp_wait_flip_pending(struct hubp *hubp,
+		unsigned int polling_interval_us);
 
 void hwss_tg_wait_double_buffer_pending(union block_sequence_params *params);
 
@@ -2130,6 +2152,9 @@ void hwss_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx);
 void hwss_add_optc_pipe_control_lock(struct block_sequence_state *seq_state,
 		struct dc *dc, struct pipe_ctx *pipe_ctx, bool lock);
 
+void hwss_pipe_control_lock(struct dc *dc,
+		struct pipe_ctx *pipe_ctx, bool lock);
+
 void hwss_add_hubp_set_flip_control_gsl(struct block_sequence_state *seq_state,
 		struct hubp *hubp, bool flip_immediate);
 
@@ -2243,7 +2268,7 @@ void hwss_add_tg_enable_crtc(struct block_sequence_state *seq_state,
 		struct timing_generator *tg);
 
 void hwss_add_hubp_wait_flip_pending(struct block_sequence_state *seq_state,
-		struct hubp *hubp, unsigned int timeout_us, unsigned int polling_interval_us);
+		struct hubp *hubp, unsigned int polling_interval_us);
 
 void hwss_add_tg_wait_double_buffer_pending(struct block_sequence_state *seq_state,
 		struct timing_generator *tg, unsigned int timeout_us, unsigned int polling_interval_us);
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 956513125be0b..9730f51cf3c04 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
@@ -217,7 +217,8 @@ struct hwseq_private_funcs {
 	void (*reset_back_end_for_pipe)(struct dc *dc,
 			struct pipe_ctx *pipe_ctx,
 			struct dc_state *context);
-	void (*perform_3dlut_wa_unlock)(struct pipe_ctx *pipe_ctx);
+	void (*perform_3dlut_wa_unlock)(struct timing_generator *tg,
+			struct hubp *primary_hubp);
 	void (*wait_for_pipe_update_if_needed)(struct dc *dc, struct pipe_ctx *pipe_ctx, bool is_surface_update_only);
 	void (*set_wait_for_update_needed_for_pipe)(struct dc *dc, struct pipe_ctx *pipe_ctx);
 	void (*dc_ip_request_cntl)(struct dc *dc, bool enable);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 06/82] drm/amd/display: Fix DPREFCLK override when SMU isn't present or ready for DCN315
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (4 preceding siblings ...)
  2026-08-18 20:14 ` [PATCH 05/82] drm/amd/display: Split OPTC_PIPE_CONTROL_LOCK into smaller HWSS blocks IVAN.LIPSKI
@ 2026-08-18 20:14 ` IVAN.LIPSKI
  2026-08-18 20:14 ` [PATCH 07/82] drm/amd/display: Test writeback connector IVAN.LIPSKI
                   ` (76 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:14 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, Meera Patel,
	Dmytro Laktyushkin

From: Meera Patel <meera.patel@amd.com>

[Why]
If SMU has not loaded properly or is not present in a test environment,
then the DPREFCLK is overridden with an invalid value.

[How]
Add an SMU present check and shift the override below the check.

Reviewed-by: Dmytro Laktyushkin <dmytro.laktyushkin@amd.com>
Signed-off-by: Meera Patel <meera.patel@amd.com>
Signed-off-by: Dmytro Laktyushkin <dmytro.laktyushkin@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../gpu/drm/amd/display/dc/clk_mgr/dcn315/dcn315_clk_mgr.c    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn315/dcn315_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn315/dcn315_clk_mgr.c
index 6ba7cff2ce05e..8ca367b723253 100644
--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn315/dcn315_clk_mgr.c
+++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn315/dcn315_clk_mgr.c
@@ -673,16 +673,16 @@ void dcn315_clk_mgr_construct(
 				  &clk_mgr->base.base, &log_info);
 
 	clk_mgr->base.base.dprefclk_khz = 600000;
-	clk_mgr->base.base.dprefclk_khz = dcn315_smu_get_dpref_clk(&clk_mgr->base);
 	clk_mgr->base.base.clks.ref_dtbclk_khz = clk_mgr->base.base.dprefclk_khz;
 	dcn10_clock_read_ss_info(&clk_mgr->base);
 	clk_mgr->base.base.clks.ref_dtbclk_khz = dcn10_adjust_dp_ref_freq_for_ss(&clk_mgr->base, clk_mgr->base.base.dprefclk_khz);
 
 	clk_mgr->base.base.bw_params = &dcn315_bw_params;
 
-	if (clk_mgr->base.base.ctx->dc->debug.pstate_enabled) {
+	if (clk_mgr->base.base.ctx->dc->debug.pstate_enabled && clk_mgr->base.smu_present) {
 		int i;
 
+		clk_mgr->base.base.dprefclk_khz = dcn315_smu_get_dpref_clk(&clk_mgr->base);
 		dcn315_get_dpm_table_from_smu(&clk_mgr->base, &smu_dpm_clks);
 		DC_LOG_SMU("NumDcfClkLevelsEnabled: %d\n"
 				   "NumDispClkLevelsEnabled: %d\n"
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 07/82] drm/amd/display: Test writeback connector
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (5 preceding siblings ...)
  2026-08-18 20:14 ` [PATCH 06/82] drm/amd/display: Fix DPREFCLK override when SMU isn't present or ready for DCN315 IVAN.LIPSKI
@ 2026-08-18 20:14 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 08/82] drm/amd/display: Test GPU memory allocation IVAN.LIPSKI
                   ` (75 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:14 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for the writeback connector. The tests cover the
encoder atomic check (missing job, missing framebuffer, matching mode,
width, height and combined size mismatches, and an unsupported pixel
format), mode enumeration and its 3840x2160 bound, connector
initialization, and buffer object preparation and cleanup including
the reserve, fence slot, pin and GART allocation failure paths.

[HOW]
Add struct amdgpu_dm_wb_kunit_ops, a KUnit-only indirection table for
the buffer object calls that would otherwise need a live TTM device.
Tests install their own table to force error returns and to count
calls, which makes the failure and cleanup ordering paths reachable in
the UML test environment. The default table points at the existing
AMDGPU, TTM and DMA reservation functions, so no test-only wrapper is
introduced.

Route the affected calls through wb_* wrappers, so the KUnit build
dispatches through the table while non-KUnit builds call the same
functions directly.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c  |  72 +++-
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h  |  16 +
 .../amdgpu_dm/tests/amdgpu_dm_wb_test.c       | 330 ++++++++++++++++++
 3 files changed, 405 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c
index a7594012a0b2e..6f91a992d43f8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c
@@ -41,6 +41,52 @@ static const u32 amdgpu_dm_wb_formats[] = {
 	DRM_FORMAT_XRGB2101010,
 };
 
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+static const struct amdgpu_dm_wb_kunit_ops amdgpu_dm_wb_default_ops = {
+	.reserve = amdgpu_bo_reserve,
+	.reserve_fences = dma_resv_reserve_fences,
+	.pin = amdgpu_bo_pin,
+	.alloc_gart = amdgpu_ttm_alloc_gart,
+	.unreserve = amdgpu_bo_unreserve,
+	.gpu_offset = amdgpu_bo_gpu_offset,
+	.ref = amdgpu_bo_ref,
+	.unpin = amdgpu_bo_unpin,
+	.unref = amdgpu_bo_unref,
+};
+
+static const struct amdgpu_dm_wb_kunit_ops *amdgpu_dm_wb_ops =
+	&amdgpu_dm_wb_default_ops;
+
+void amdgpu_dm_wb_kunit_set_ops(const struct amdgpu_dm_wb_kunit_ops *ops)
+{
+	amdgpu_dm_wb_ops = ops ? ops : &amdgpu_dm_wb_default_ops;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_wb_kunit_set_ops);
+
+#define wb_bo_reserve			amdgpu_dm_wb_ops->reserve
+#define wb_dma_resv_reserve_fences	amdgpu_dm_wb_ops->reserve_fences
+#define wb_bo_pin			amdgpu_dm_wb_ops->pin
+#define wb_ttm_alloc_gart		amdgpu_dm_wb_ops->alloc_gart
+#define wb_bo_unreserve			amdgpu_dm_wb_ops->unreserve
+#define wb_bo_gpu_offset		amdgpu_dm_wb_ops->gpu_offset
+#define wb_bo_ref			amdgpu_dm_wb_ops->ref
+#define wb_bo_unpin			amdgpu_dm_wb_ops->unpin
+#define wb_bo_unref			amdgpu_dm_wb_ops->unref
+
+#else
+
+#define wb_bo_reserve			amdgpu_bo_reserve
+#define wb_dma_resv_reserve_fences	dma_resv_reserve_fences
+#define wb_bo_pin			amdgpu_bo_pin
+#define wb_ttm_alloc_gart		amdgpu_ttm_alloc_gart
+#define wb_bo_unreserve			amdgpu_bo_unreserve
+#define wb_bo_gpu_offset		amdgpu_bo_gpu_offset
+#define wb_bo_ref			amdgpu_bo_ref
+#define wb_bo_unpin			amdgpu_bo_unpin
+#define wb_bo_unref			amdgpu_bo_unref
+
+#endif
+
 STATIC_IFN_KUNIT int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder,
 					struct drm_crtc_state *crtc_state,
 					struct drm_connector_state *conn_state)
@@ -105,13 +151,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb
 	rbo = gem_to_amdgpu_bo(obj);
 	adev = amdgpu_ttm_adev(rbo->tbo.bdev);
 
-	r = amdgpu_bo_reserve(rbo, true);
+	r = wb_bo_reserve(rbo, true);
 	if (r) {
 		drm_err(adev_to_drm(adev), "fail to reserve bo: %pe\n", ERR_PTR(r));
 		return r;
 	}
 
-	r = dma_resv_reserve_fences(rbo->tbo.base.resv, TTM_NUM_MOVE_FENCES);
+	r = wb_dma_resv_reserve_fences(rbo->tbo.base.resv, TTM_NUM_MOVE_FENCES);
 	if (r) {
 		drm_err(adev_to_drm(adev), "reserving fence slot failed: %pe\n", ERR_PTR(r));
 		goto error_unlock;
@@ -120,32 +166,32 @@ STATIC_IFN_KUNIT int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb
 	domain = amdgpu_display_supported_domains(adev, rbo->flags);
 
 	rbo->flags |= AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS;
-	r = amdgpu_bo_pin(rbo, domain);
+	r = wb_bo_pin(rbo, domain);
 	if (unlikely(r != 0)) {
 		if (r != -ERESTARTSYS)
 			DRM_ERROR("Failed to pin framebuffer: %pe\n", ERR_PTR(r));
 		goto error_unlock;
 	}
 
-	r = amdgpu_ttm_alloc_gart(&rbo->tbo);
+	r = wb_ttm_alloc_gart(&rbo->tbo);
 	if (unlikely(r != 0)) {
 		DRM_ERROR("%p bind failed: %pe\n", rbo, ERR_PTR(r));
 		goto error_unpin;
 	}
 
-	amdgpu_bo_unreserve(rbo);
+	wb_bo_unreserve(rbo);
 
-	afb->address = amdgpu_bo_gpu_offset(rbo);
+	afb->address = wb_bo_gpu_offset(rbo);
 
-	amdgpu_bo_ref(rbo);
+	wb_bo_ref(rbo);
 
 	return 0;
 
 error_unpin:
-	amdgpu_bo_unpin(rbo);
+	wb_bo_unpin(rbo);
 
 error_unlock:
-	amdgpu_bo_unreserve(rbo);
+	wb_bo_unreserve(rbo);
 	return r;
 }
 EXPORT_IF_KUNIT(amdgpu_dm_wb_prepare_job);
@@ -160,15 +206,15 @@ STATIC_IFN_KUNIT void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *c
 		return;
 
 	rbo = gem_to_amdgpu_bo(job->fb->obj[0]);
-	r = amdgpu_bo_reserve(rbo, false);
+	r = wb_bo_reserve(rbo, false);
 	if (unlikely(r)) {
 		DRM_ERROR("failed to reserve rbo before unpin: %pe\n", ERR_PTR(r));
 		return;
 	}
 
-	amdgpu_bo_unpin(rbo);
-	amdgpu_bo_unreserve(rbo);
-	amdgpu_bo_unref(&rbo);
+	wb_bo_unpin(rbo);
+	wb_bo_unreserve(rbo);
+	wb_bo_unref(&rbo);
 }
 EXPORT_IF_KUNIT(amdgpu_dm_wb_cleanup_job);
 
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h
index 5fd616bc43b5c..8d39a37e80f0a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h
@@ -31,6 +31,9 @@
 
 struct amdgpu_display_manager;
 struct amdgpu_dm_wb_connector;
+struct amdgpu_bo;
+struct dma_resv;
+struct ttm_buffer_object;
 
 int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm,
 				struct amdgpu_dm_wb_connector *dm_wbcon,
@@ -40,6 +43,18 @@ int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm,
 #include <drm/drm_connector.h>
 #include <drm/drm_crtc.h>
 
+struct amdgpu_dm_wb_kunit_ops {
+	int (*reserve)(struct amdgpu_bo *bo, bool interruptible);
+	int (*reserve_fences)(struct dma_resv *resv, unsigned int num_fences);
+	int (*pin)(struct amdgpu_bo *bo, u32 domain);
+	int (*alloc_gart)(struct ttm_buffer_object *tbo);
+	void (*unreserve)(struct amdgpu_bo *bo);
+	u64 (*gpu_offset)(struct amdgpu_bo *bo);
+	struct amdgpu_bo *(*ref)(struct amdgpu_bo *bo);
+	void (*unpin)(struct amdgpu_bo *bo);
+	void (*unref)(struct amdgpu_bo **bo);
+};
+
 int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder,
 				      struct drm_crtc_state *crtc_state,
 				      struct drm_connector_state *conn_state);
@@ -48,6 +63,7 @@ int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector,
 			     struct drm_writeback_job *job);
 void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector,
 			      struct drm_writeback_job *job);
+void amdgpu_dm_wb_kunit_set_ops(const struct amdgpu_dm_wb_kunit_ops *ops);
 #endif
 
 #endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c
index 3454cf3714148..03036f9063854 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c
@@ -23,6 +23,151 @@
 #include "amdgpu_dm_wb.h"
 #include "amdgpu_dm_kunit_test_helpers.h"
 
+struct dm_wb_test_bo {
+	struct amdgpu_bo bo;
+	struct amdgpu_device *adev;
+	int reserve_ret;
+	int reserve_fences_ret;
+	int pin_ret;
+	int alloc_gart_ret;
+	u64 gpu_offset;
+	unsigned int reserve_count;
+	unsigned int reserve_fences_count;
+	unsigned int pin_count;
+	unsigned int alloc_gart_count;
+	unsigned int unreserve_count;
+	unsigned int ref_count;
+	unsigned int unpin_count;
+	unsigned int unref_count;
+	unsigned int call_seq;
+	unsigned int reserve_seq;
+	unsigned int unreserve_seq;
+	unsigned int unpin_seq;
+	unsigned int unref_seq;
+};
+
+static struct dm_wb_test_bo *to_dm_wb_test_bo(struct amdgpu_bo *bo)
+{
+	return container_of(bo, struct dm_wb_test_bo, bo);
+}
+
+static int dm_wb_test_reserve(struct amdgpu_bo *bo, bool interruptible)
+{
+	struct dm_wb_test_bo *test_bo = to_dm_wb_test_bo(bo);
+
+	test_bo->reserve_count++;
+	test_bo->reserve_seq = ++test_bo->call_seq;
+	return test_bo->reserve_ret;
+}
+
+static int dm_wb_test_reserve_fences(struct dma_resv *resv,
+				     unsigned int num_fences)
+{
+	struct dm_wb_test_bo *test_bo;
+
+	test_bo = container_of(resv, struct dm_wb_test_bo, bo.tbo.base._resv);
+	test_bo->reserve_fences_count++;
+	return test_bo->reserve_fences_ret;
+}
+
+static int dm_wb_test_pin(struct amdgpu_bo *bo, u32 domain)
+{
+	struct dm_wb_test_bo *test_bo = to_dm_wb_test_bo(bo);
+
+	test_bo->pin_count++;
+	return test_bo->pin_ret;
+}
+
+static int dm_wb_test_alloc_gart(struct ttm_buffer_object *tbo)
+{
+	struct dm_wb_test_bo *test_bo = to_dm_wb_test_bo(ttm_to_amdgpu_bo(tbo));
+
+	test_bo->alloc_gart_count++;
+	return test_bo->alloc_gart_ret;
+}
+
+static void dm_wb_test_unreserve(struct amdgpu_bo *bo)
+{
+	struct dm_wb_test_bo *test_bo = to_dm_wb_test_bo(bo);
+
+	test_bo->unreserve_count++;
+	test_bo->unreserve_seq = ++test_bo->call_seq;
+}
+
+static u64 dm_wb_test_gpu_offset(struct amdgpu_bo *bo)
+{
+	return to_dm_wb_test_bo(bo)->gpu_offset;
+}
+
+static struct amdgpu_bo *dm_wb_test_ref(struct amdgpu_bo *bo)
+{
+	to_dm_wb_test_bo(bo)->ref_count++;
+	return bo;
+}
+
+static void dm_wb_test_unpin(struct amdgpu_bo *bo)
+{
+	struct dm_wb_test_bo *test_bo = to_dm_wb_test_bo(bo);
+
+	test_bo->unpin_count++;
+	test_bo->unpin_seq = ++test_bo->call_seq;
+}
+
+static void dm_wb_test_unref(struct amdgpu_bo **bo)
+{
+	struct dm_wb_test_bo *test_bo = to_dm_wb_test_bo(*bo);
+
+	test_bo->unref_count++;
+	test_bo->unref_seq = ++test_bo->call_seq;
+	*bo = NULL;
+}
+
+static const struct amdgpu_dm_wb_kunit_ops dm_wb_test_ops = {
+	.reserve = dm_wb_test_reserve,
+	.reserve_fences = dm_wb_test_reserve_fences,
+	.pin = dm_wb_test_pin,
+	.alloc_gart = dm_wb_test_alloc_gart,
+	.unreserve = dm_wb_test_unreserve,
+	.gpu_offset = dm_wb_test_gpu_offset,
+	.ref = dm_wb_test_ref,
+	.unpin = dm_wb_test_unpin,
+	.unref = dm_wb_test_unref,
+};
+
+static void dm_wb_test_reset_ops(void *unused)
+{
+	amdgpu_dm_wb_kunit_set_ops(NULL);
+}
+
+static struct drm_writeback_job *dm_wb_test_alloc_job(struct kunit *test,
+						      struct dm_wb_test_bo **test_bo)
+{
+	struct amdgpu_framebuffer *afb;
+	struct drm_writeback_job *job;
+
+	*test_bo = kunit_kzalloc(test, sizeof(**test_bo), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, *test_bo);
+	(*test_bo)->adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_NULL(test, (*test_bo)->adev);
+
+	/* Let the driver's real BO and device lookups resolve to the fake BO. */
+	(*test_bo)->bo.tbo.bdev = &(*test_bo)->adev->mman.bdev;
+	(*test_bo)->bo.tbo.base.resv = &(*test_bo)->bo.tbo.base._resv;
+
+	afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, afb);
+	afb->base.obj[0] = &(*test_bo)->bo.tbo.base;
+
+	job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, job);
+	job->fb = &afb->base;
+
+	amdgpu_dm_wb_kunit_set_ops(&dm_wb_test_ops);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_wb_test_reset_ops, NULL), 0);
+
+	return job;
+}
+
 
 /* Helper functions */
 
@@ -387,6 +532,134 @@ static void dm_test_wb_prepare_job_no_fb(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, ret, 0);
 }
 
+/**
+ * dm_test_wb_prepare_job_success - Verify successful BO preparation
+ * @test: KUnit test context
+ *
+ * The writeback BO should be reserved, pinned, mapped into GART, referenced,
+ * and assigned its GPU address.
+ */
+static void dm_test_wb_prepare_job_success(struct kunit *test)
+{
+	struct dm_wb_test_bo *test_bo;
+	struct drm_writeback_job *job;
+	struct amdgpu_framebuffer *afb;
+	int ret;
+
+	job = dm_wb_test_alloc_job(test, &test_bo);
+	afb = to_amdgpu_framebuffer(job->fb);
+	test_bo->gpu_offset = 0x12340000;
+
+	ret = amdgpu_dm_wb_prepare_job(NULL, job);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, test_bo->reserve_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->reserve_fences_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->pin_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->alloc_gart_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->unreserve_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->ref_count, 1);
+	KUNIT_EXPECT_EQ(test, afb->address, test_bo->gpu_offset);
+	KUNIT_EXPECT_TRUE(test, test_bo->bo.flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS);
+}
+
+/**
+ * dm_test_wb_prepare_job_reserve_failure - Verify reserve errors are returned
+ * @test: KUnit test context
+ *
+ * A BO reserve failure should stop preparation without attempting cleanup on
+ * a BO that was never reserved.
+ */
+static void dm_test_wb_prepare_job_reserve_failure(struct kunit *test)
+{
+	struct dm_wb_test_bo *test_bo;
+	struct drm_writeback_job *job;
+	int ret;
+
+	job = dm_wb_test_alloc_job(test, &test_bo);
+	test_bo->reserve_ret = -EBUSY;
+
+	ret = amdgpu_dm_wb_prepare_job(NULL, job);
+
+	KUNIT_EXPECT_EQ(test, ret, -EBUSY);
+	KUNIT_EXPECT_EQ(test, test_bo->reserve_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->reserve_fences_count, 0);
+	KUNIT_EXPECT_EQ(test, test_bo->unreserve_count, 0);
+}
+
+/**
+ * dm_test_wb_prepare_job_fence_failure - Verify fence reservation cleanup
+ * @test: KUnit test context
+ *
+ * Failure to reserve fence slots should release the BO reservation without
+ * attempting to pin the BO.
+ */
+static void dm_test_wb_prepare_job_fence_failure(struct kunit *test)
+{
+	struct dm_wb_test_bo *test_bo;
+	struct drm_writeback_job *job;
+	int ret;
+
+	job = dm_wb_test_alloc_job(test, &test_bo);
+	test_bo->reserve_fences_ret = -ENOMEM;
+
+	ret = amdgpu_dm_wb_prepare_job(NULL, job);
+
+	KUNIT_EXPECT_EQ(test, ret, -ENOMEM);
+	KUNIT_EXPECT_EQ(test, test_bo->reserve_fences_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->pin_count, 0);
+	KUNIT_EXPECT_EQ(test, test_bo->unreserve_count, 1);
+}
+
+/**
+ * dm_test_wb_prepare_job_pin_failure - Verify pin failure cleanup
+ * @test: KUnit test context
+ *
+ * A pin failure should release the BO reservation without trying to unpin a
+ * BO that was not successfully pinned.
+ */
+static void dm_test_wb_prepare_job_pin_failure(struct kunit *test)
+{
+	struct dm_wb_test_bo *test_bo;
+	struct drm_writeback_job *job;
+	int ret;
+
+	job = dm_wb_test_alloc_job(test, &test_bo);
+	test_bo->pin_ret = -EINVAL;
+
+	ret = amdgpu_dm_wb_prepare_job(NULL, job);
+
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+	KUNIT_EXPECT_EQ(test, test_bo->pin_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->alloc_gart_count, 0);
+	KUNIT_EXPECT_EQ(test, test_bo->unpin_count, 0);
+	KUNIT_EXPECT_EQ(test, test_bo->unreserve_count, 1);
+}
+
+/**
+ * dm_test_wb_prepare_job_gart_failure - Verify GART allocation cleanup
+ * @test: KUnit test context
+ *
+ * A GART allocation failure should unpin and unreserve the BO.
+ */
+static void dm_test_wb_prepare_job_gart_failure(struct kunit *test)
+{
+	struct dm_wb_test_bo *test_bo;
+	struct drm_writeback_job *job;
+	int ret;
+
+	job = dm_wb_test_alloc_job(test, &test_bo);
+	test_bo->alloc_gart_ret = -ENOMEM;
+
+	ret = amdgpu_dm_wb_prepare_job(NULL, job);
+
+	KUNIT_EXPECT_EQ(test, ret, -ENOMEM);
+	KUNIT_EXPECT_EQ(test, test_bo->alloc_gart_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->unpin_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->unreserve_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->ref_count, 0);
+}
+
 /**
  * dm_test_wb_cleanup_job_no_fb - Verify cleanup_job early return without a framebuffer
  * @test: KUnit test context
@@ -406,6 +679,56 @@ static void dm_test_wb_cleanup_job_no_fb(struct kunit *test)
 	amdgpu_dm_wb_cleanup_job(NULL, job);
 }
 
+/**
+ * dm_test_wb_cleanup_job_success - Verify successful BO cleanup
+ * @test: KUnit test context
+ *
+ * Cleanup should reserve, unpin, unreserve, and drop the writeback BO
+ * reference in order.
+ */
+static void dm_test_wb_cleanup_job_success(struct kunit *test)
+{
+	struct dm_wb_test_bo *test_bo;
+	struct drm_writeback_job *job;
+
+	job = dm_wb_test_alloc_job(test, &test_bo);
+
+	amdgpu_dm_wb_cleanup_job(NULL, job);
+
+	KUNIT_EXPECT_EQ(test, test_bo->reserve_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->unpin_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->unreserve_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->unref_count, 1);
+
+	/* The BO stays reserved across the unpin, and is released last. */
+	KUNIT_EXPECT_LT(test, test_bo->reserve_seq, test_bo->unpin_seq);
+	KUNIT_EXPECT_LT(test, test_bo->unpin_seq, test_bo->unreserve_seq);
+	KUNIT_EXPECT_LT(test, test_bo->unreserve_seq, test_bo->unref_seq);
+}
+
+/**
+ * dm_test_wb_cleanup_job_reserve_failure - Verify cleanup reserve failure
+ * @test: KUnit test context
+ *
+ * If cleanup cannot reserve the BO, it should leave the pin and reference
+ * untouched for a later cleanup attempt.
+ */
+static void dm_test_wb_cleanup_job_reserve_failure(struct kunit *test)
+{
+	struct dm_wb_test_bo *test_bo;
+	struct drm_writeback_job *job;
+
+	job = dm_wb_test_alloc_job(test, &test_bo);
+	test_bo->reserve_ret = -EBUSY;
+
+	amdgpu_dm_wb_cleanup_job(NULL, job);
+
+	KUNIT_EXPECT_EQ(test, test_bo->reserve_count, 1);
+	KUNIT_EXPECT_EQ(test, test_bo->unpin_count, 0);
+	KUNIT_EXPECT_EQ(test, test_bo->unreserve_count, 0);
+	KUNIT_EXPECT_EQ(test, test_bo->unref_count, 0);
+}
+
 static struct kunit_case dm_wb_test_cases[] = {
 	/* amdgpu_dm_wb_encoder_atomic_check */
 	KUNIT_CASE(dm_test_wb_atomic_check_no_job),
@@ -422,7 +745,14 @@ static struct kunit_case dm_wb_test_cases[] = {
 	KUNIT_CASE(dm_test_wb_connector_init_success),
 	/* amdgpu_dm_wb_prepare_job / amdgpu_dm_wb_cleanup_job */
 	KUNIT_CASE(dm_test_wb_prepare_job_no_fb),
+	KUNIT_CASE(dm_test_wb_prepare_job_success),
+	KUNIT_CASE(dm_test_wb_prepare_job_reserve_failure),
+	KUNIT_CASE(dm_test_wb_prepare_job_fence_failure),
+	KUNIT_CASE(dm_test_wb_prepare_job_pin_failure),
+	KUNIT_CASE(dm_test_wb_prepare_job_gart_failure),
 	KUNIT_CASE(dm_test_wb_cleanup_job_no_fb),
+	KUNIT_CASE(dm_test_wb_cleanup_job_success),
+	KUNIT_CASE(dm_test_wb_cleanup_job_reserve_failure),
 	{}
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 08/82] drm/amd/display: Test GPU memory allocation
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (6 preceding siblings ...)
  2026-08-18 20:14 ` [PATCH 07/82] drm/amd/display: Test writeback connector IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 09/82] drm/amd/display: Cover MST path in encoder atomic_check IVAN.LIPSKI
                   ` (74 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests covering GART and VRAM domain selection, size and
alignment forwarding, the buffer object creation failure path,
and the free path for a matching pointer, a non-matching pointer
and an empty allocation list.

[HOW]
Add struct amdgpu_dm_services_kunit_ops, a KUnit-only indirection table
for the buffer object calls that would otherwise need a live TTM
device. Tests install their own table to force an error return and to
count calls, which makes the failure and list handling paths reachable
in the UML test environment. The default table points at the existing
AMDGPU functions, so no test-only wrapper is introduced.

Route the affected calls through services_bo_* wrappers, so the KUnit
build dispatches through the table while non-KUnit builds call the same
functions directly.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h |  10 +
 .../display/amdgpu_dm/amdgpu_dm_services.c    |  35 ++-
 .../amdgpu_dm/tests/amdgpu_dm_services_test.c | 228 +++++++++++++++++-
 3 files changed, 268 insertions(+), 5 deletions(-)

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 3524931451c86..0217a26283a36 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1188,6 +1188,16 @@ bool is_content_protection_different(struct drm_crtc_state *new_crtc_state,
 				     struct drm_connector_state *old_conn_state,
 				     const struct drm_connector *connector,
 				     struct hdcp_workqueue *hdcp_w);
+
+struct amdgpu_dm_services_kunit_ops {
+	int (*bo_create_kernel)(struct amdgpu_device *adev, unsigned long size,
+				int align, u32 domain, struct amdgpu_bo **bo_ptr,
+				u64 *gpu_addr, void **cpu_addr);
+	void (*bo_free_kernel)(struct amdgpu_bo **bo, u64 *gpu_addr,
+			       void **cpu_addr);
+};
+
+void amdgpu_dm_services_kunit_set_ops(const struct amdgpu_dm_services_kunit_ops *ops);
 #endif
 
 #endif /* __AMDGPU_DM_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 6dbc8d1b4ecd3..23f1db81a1544 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
@@ -38,6 +38,31 @@
 #include "amdgpu_dm_trace.h"
 #include "dm_helpers.h"
 
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+static const struct amdgpu_dm_services_kunit_ops amdgpu_dm_services_default_ops = {
+	.bo_create_kernel = amdgpu_bo_create_kernel,
+	.bo_free_kernel = amdgpu_bo_free_kernel,
+};
+
+static const struct amdgpu_dm_services_kunit_ops *amdgpu_dm_services_ops =
+	&amdgpu_dm_services_default_ops;
+
+void amdgpu_dm_services_kunit_set_ops(const struct amdgpu_dm_services_kunit_ops *ops)
+{
+	amdgpu_dm_services_ops = ops ? ops : &amdgpu_dm_services_default_ops;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_services_kunit_set_ops);
+
+#define services_bo_create_kernel	amdgpu_dm_services_ops->bo_create_kernel
+#define services_bo_free_kernel		amdgpu_dm_services_ops->bo_free_kernel
+
+#else
+
+#define services_bo_create_kernel	amdgpu_bo_create_kernel
+#define services_bo_free_kernel		amdgpu_bo_free_kernel
+
+#endif
+
 	unsigned long long
 	dm_get_elapse_time_in_ns(struct dc_context *ctx,
 				 unsigned long long current_time_stamp,
@@ -113,9 +138,9 @@ dm_allocate_gpu_mem(
 	if (!da)
 		return NULL;
 
-	ret = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE,
-				      domain, &da->bo,
-				      &da->gpu_addr, &da->cpu_ptr);
+	ret = services_bo_create_kernel(adev, size, PAGE_SIZE,
+					domain, &da->bo,
+					&da->gpu_addr, &da->cpu_ptr);
 
 	*addr = da->gpu_addr;
 
@@ -129,6 +154,7 @@ dm_allocate_gpu_mem(
 
 	return da->cpu_ptr;
 }
+EXPORT_IF_KUNIT(dm_allocate_gpu_mem);
 
 void
 dm_free_gpu_mem(
@@ -141,7 +167,7 @@ dm_free_gpu_mem(
 	/* walk the da list in DM */
 	list_for_each_entry(da, &adev->dm.da_list, list) {
 		if (pvMem == da->cpu_ptr) {
-			amdgpu_bo_free_kernel(&da->bo, &da->gpu_addr, &da->cpu_ptr);
+			services_bo_free_kernel(&da->bo, &da->gpu_addr, &da->cpu_ptr);
 			list_del(&da->list);
 			kfree(da);
 			break;
@@ -149,3 +175,4 @@ dm_free_gpu_mem(
 	}
 
 }
+EXPORT_IF_KUNIT(dm_free_gpu_mem);
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
index e48bac7fb024c..4ff430e4b23b6 100644
--- 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
@@ -277,6 +277,212 @@ static void dm_test_query_brightness_caps_non_lcd1_uses_second_slot(struct kunit
 	KUNIT_EXPECT_EQ(test, caps.data_points[0].signal_level, 0);
 }
 
+/* Tests for dm_allocate_gpu_mem() and dm_free_gpu_mem() */
+
+#define DM_TEST_FAKE_GPU_ADDR 0xF00DBEEFULL
+
+struct dm_test_bo_ops_ctx {
+	void *cpu_ptr;
+	u64 gpu_addr;
+	int create_ret;
+	unsigned long create_size;
+	int create_align;
+	u32 create_domain;
+	unsigned int create_calls;
+	unsigned int free_calls;
+};
+
+static struct dm_test_bo_ops_ctx dm_test_bo_ctx;
+
+static int dm_test_bo_create_kernel(struct amdgpu_device *adev, unsigned long size,
+				    int align, u32 domain, struct amdgpu_bo **bo_ptr,
+				    u64 *gpu_addr, void **cpu_addr)
+{
+	dm_test_bo_ctx.create_calls++;
+	dm_test_bo_ctx.create_size = size;
+	dm_test_bo_ctx.create_align = align;
+	dm_test_bo_ctx.create_domain = domain;
+
+	if (dm_test_bo_ctx.create_ret)
+		return dm_test_bo_ctx.create_ret;
+
+	*gpu_addr = dm_test_bo_ctx.gpu_addr;
+	*cpu_addr = dm_test_bo_ctx.cpu_ptr;
+
+	return 0;
+}
+
+static void dm_test_bo_free_kernel(struct amdgpu_bo **bo, u64 *gpu_addr, void **cpu_addr)
+{
+	dm_test_bo_ctx.free_calls++;
+
+	*bo = NULL;
+	*gpu_addr = 0;
+	*cpu_addr = NULL;
+}
+
+static const struct amdgpu_dm_services_kunit_ops dm_test_bo_ops = {
+	.bo_create_kernel = dm_test_bo_create_kernel,
+	.bo_free_kernel = dm_test_bo_free_kernel,
+};
+
+/**
+ * dm_test_gpu_mem_init - Install the fake buffer object ops
+ * @test: The KUnit test context
+ *
+ * amdgpu_bo_create_kernel() and amdgpu_bo_free_kernel() need a live TTM
+ * device, so route them through a fake table for the duration of the test.
+ */
+static int dm_test_gpu_mem_init(struct kunit *test)
+{
+	void *cpu_ptr;
+
+	cpu_ptr = kunit_kzalloc(test, sizeof(*cpu_ptr), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, cpu_ptr);
+
+	dm_test_bo_ctx = (struct dm_test_bo_ops_ctx) {
+		.cpu_ptr = cpu_ptr,
+		.gpu_addr = DM_TEST_FAKE_GPU_ADDR,
+	};
+
+	amdgpu_dm_services_kunit_set_ops(&dm_test_bo_ops);
+
+	return 0;
+}
+
+static void dm_test_gpu_mem_exit(struct kunit *test)
+{
+	amdgpu_dm_services_kunit_set_ops(NULL);
+}
+
+static struct amdgpu_device *dm_test_alloc_adev(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+	INIT_LIST_HEAD(&adev->dm.da_list);
+
+	return adev;
+}
+
+/**
+ * dm_test_allocate_gpu_mem_gart - Test Allocate gpu mem gart
+ * @test: The KUnit test context
+ */
+static void dm_test_allocate_gpu_mem_gart(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev(test);
+	long long addr = 0;
+	void *mem;
+
+	mem = dm_allocate_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, 4096, &addr);
+
+	KUNIT_EXPECT_PTR_EQ(test, mem, dm_test_bo_ctx.cpu_ptr);
+	KUNIT_EXPECT_EQ(test, addr, (long long)DM_TEST_FAKE_GPU_ADDR);
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.create_calls, 1U);
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.create_size, 4096UL);
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.create_align, (int)PAGE_SIZE);
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.create_domain, (u32)AMDGPU_GEM_DOMAIN_GTT);
+	KUNIT_EXPECT_FALSE(test, list_empty(&adev->dm.da_list));
+
+	dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, mem);
+}
+
+/**
+ * dm_test_allocate_gpu_mem_frame_buffer - Test Allocate gpu mem frame buffer
+ * @test: The KUnit test context
+ *
+ * Any non-GART allocation type must land in the VRAM domain.
+ */
+static void dm_test_allocate_gpu_mem_frame_buffer(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev(test);
+	long long addr = 0;
+	void *mem;
+
+	mem = dm_allocate_gpu_mem(adev, DC_MEM_ALLOC_TYPE_FRAME_BUFFER, 8192, &addr);
+
+	KUNIT_EXPECT_PTR_EQ(test, mem, dm_test_bo_ctx.cpu_ptr);
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.create_domain, (u32)AMDGPU_GEM_DOMAIN_VRAM);
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.create_size, 8192UL);
+
+	dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_FRAME_BUFFER, mem);
+}
+
+/**
+ * dm_test_allocate_gpu_mem_create_fails - Test Allocate gpu mem create fails
+ * @test: The KUnit test context
+ */
+static void dm_test_allocate_gpu_mem_create_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev(test);
+	long long addr = 0;
+	void *mem;
+
+	dm_test_bo_ctx.create_ret = -ENOMEM;
+
+	mem = dm_allocate_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, 4096, &addr);
+
+	KUNIT_EXPECT_NULL(test, mem);
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.create_calls, 1U);
+	KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.da_list));
+}
+
+/**
+ * dm_test_free_gpu_mem_matching - Test Free gpu mem matching
+ * @test: The KUnit test context
+ */
+static void dm_test_free_gpu_mem_matching(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev(test);
+	long long addr = 0;
+	void *mem;
+
+	mem = dm_allocate_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, 4096, &addr);
+	KUNIT_ASSERT_NOT_NULL(test, mem);
+
+	dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, mem);
+
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.free_calls, 1U);
+	KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.da_list));
+}
+
+/**
+ * dm_test_free_gpu_mem_no_match - Test Free gpu mem no match
+ * @test: The KUnit test context
+ */
+static void dm_test_free_gpu_mem_no_match(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev(test);
+	long long addr = 0;
+	void *mem;
+
+	mem = dm_allocate_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, 4096, &addr);
+	KUNIT_ASSERT_NOT_NULL(test, mem);
+
+	dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, (char *)mem + 1);
+
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.free_calls, 0U);
+	KUNIT_EXPECT_FALSE(test, list_empty(&adev->dm.da_list));
+
+	dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, mem);
+}
+
+/**
+ * dm_test_free_gpu_mem_empty_list - Test Free gpu mem empty list
+ * @test: The KUnit test context
+ */
+static void dm_test_free_gpu_mem_empty_list(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev(test);
+
+	dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, dm_test_bo_ctx.cpu_ptr);
+
+	KUNIT_EXPECT_EQ(test, dm_test_bo_ctx.free_calls, 0U);
+	KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.da_list));
+}
+
 static struct kunit_case amdgpu_dm_services_test_cases[] = {
 	/* dm_get_elapse_time_in_ns */
 	KUNIT_CASE(dm_test_get_elapse_time_zero_delta),
@@ -307,7 +513,27 @@ static struct kunit_suite amdgpu_dm_services_test_suite = {
 	.test_cases = amdgpu_dm_services_test_cases,
 };
 
-kunit_test_suite(amdgpu_dm_services_test_suite);
+static struct kunit_case amdgpu_dm_services_gpu_mem_test_cases[] = {
+	/* dm_allocate_gpu_mem */
+	KUNIT_CASE(dm_test_allocate_gpu_mem_gart),
+	KUNIT_CASE(dm_test_allocate_gpu_mem_frame_buffer),
+	KUNIT_CASE(dm_test_allocate_gpu_mem_create_fails),
+	/* dm_free_gpu_mem */
+	KUNIT_CASE(dm_test_free_gpu_mem_matching),
+	KUNIT_CASE(dm_test_free_gpu_mem_no_match),
+	KUNIT_CASE(dm_test_free_gpu_mem_empty_list),
+	{}
+};
+
+static struct kunit_suite amdgpu_dm_services_gpu_mem_test_suite = {
+	.name = "amdgpu_dm_services_gpu_mem",
+	.init = dm_test_gpu_mem_init,
+	.exit = dm_test_gpu_mem_exit,
+	.test_cases = amdgpu_dm_services_gpu_mem_test_cases,
+};
+
+kunit_test_suites(&amdgpu_dm_services_test_suite,
+		  &amdgpu_dm_services_gpu_mem_test_suite);
 
 MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_services");
 MODULE_LICENSE("Dual MIT/GPL");
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 09/82] drm/amd/display: Cover MST path in encoder atomic_check
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (7 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 08/82] drm/amd/display: Test GPU memory allocation IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 10/82] drm/amd/display: Cover amdgpu_dm_encoder_init IVAN.LIPSKI
                   ` (73 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for the multi-stream transport validation path in
dm_encoder_helper_atomic_check(). A shared scaffold builds a fully faked
DRM atomic state (private objs, connectors, MST payload list) plus a fake
DC link so the PBN divider is non-zero, exercising the topology-state
lookup, color-depth/PBN compute, VCPI slot allocation, and both the
topology-error and negative-vcpi_slots failure branches.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 252 ++++++++++++++++++
 1 file changed, 252 insertions(+)

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
index a54fd9529dc9e..d209e26b684eb 100644
--- 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
@@ -29,6 +29,7 @@
 #include "amdgpu_dm_backlight.h"
 #include "include/grph_object_id.h"
 #include "amdgpu_dm_kunit_test_helpers.h"
+#include "inc/link_service.h"
 
 /* Tests for get_subconnector_type() */
 
@@ -4394,6 +4395,252 @@ static void dm_test_atomic_check_non_mst_returns_zero(struct kunit *test)
 					       &ctx->dm_state->base), 0);
 }
 
+/**
+ * dm_test_atomic_check_mst_no_change_returns_zero - Test MST no-change short-circuit
+ * @test: The KUnit test context
+ *
+ * An MST connector (mst_output_port set) whose CRTC state reports neither a
+ * connectors nor a mode change hits the ``return 0`` before any topology state
+ * is fetched.
+ */
+static void dm_test_atomic_check_mst_no_change_returns_zero(struct kunit *test)
+{
+	struct dm_test_atomic_check_ctx *ctx =
+		dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct drm_dp_mst_port *mst_port;
+	struct amdgpu_dm_connector *mst_root;
+
+	mst_port = kunit_kzalloc(test, sizeof(*mst_port), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, mst_port);
+	mst_root = kunit_kzalloc(test, sizeof(*mst_root), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, mst_root);
+
+	ctx->aconnector->mst_output_port = mst_port;
+	ctx->aconnector->mst_root = mst_root;
+	ctx->crtc_state->connectors_changed = false;
+	ctx->crtc_state->mode_changed = false;
+
+	KUNIT_EXPECT_EQ(test,
+		dm_encoder_helper_atomic_check(&ctx->aenc->base,
+					       ctx->crtc_state,
+					       &ctx->dm_state->base), 0);
+}
+
+
+static uint32_t dm_test_atomic_check_dp_link_bandwidth_kbps(
+	const struct dc_link *link,
+	const struct dc_link_settings *link_settings)
+{
+	return 4320000;
+}
+
+static const struct dc_link_settings *dm_test_atomic_check_dp_get_verified_link_cap(
+	const struct dc_link *link)
+{
+	return &link->verified_link_cap;
+}
+
+/* Fake fully-wired MST atomic state for the deep dm_encoder_helper_atomic_check() path. */
+struct dm_test_mst_scaffold {
+	struct drm_atomic_commit *state;
+	struct drm_dp_mst_topology_state *mst_state;
+	struct drm_dp_mst_atomic_payload *payload;
+	struct drm_dp_mst_port *mst_port;
+	struct amdgpu_dm_connector *mst_root;
+	struct __drm_private_objs_state *priv_objs;
+};
+
+/*
+ * dm_test_atomic_check_mst_scaffold - build a fake MST atomic state.
+ * @map_success: when true the topology manager maps to a valid topology state
+ *               and a fake DC link/link-service is wired so the PBN divider is
+ *               non-zero; when false the manager maps to an error pointer so
+ *               drm_atomic_get_mst_topology_state() reports failure.
+ */
+static void dm_test_atomic_check_mst_scaffold(struct kunit *test,
+		struct dm_test_atomic_check_ctx *ctx,
+		struct dm_test_mst_scaffold *s,
+		bool map_success)
+{
+	struct drm_modeset_acquire_ctx *acquire_ctx;
+	struct link_service *link_srv;
+	struct dc *dc;
+	struct dc_link *link;
+	struct drm_connector *port_conn;
+	struct drm_crtc *crtc;
+	struct drm_connector_state *port_conn_state;
+	struct __drm_connnectors_state *conns;
+
+	memset(s, 0, sizeof(*s));
+
+	s->mst_root = kunit_kzalloc(test, sizeof(*s->mst_root), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, s->mst_root);
+	s->mst_root->mst_mgr.dev = ctx->drm;
+
+	s->mst_port = kunit_kzalloc(test, sizeof(*s->mst_port), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, s->mst_port);
+
+	ctx->aconnector->mst_root = s->mst_root;
+	ctx->aconnector->mst_output_port = s->mst_port;
+
+	acquire_ctx = kunit_kzalloc(test, sizeof(*acquire_ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, acquire_ctx);
+
+	s->state = kunit_kzalloc(test, sizeof(*s->state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, s->state);
+	s->state->dev = ctx->drm;
+	s->state->acquire_ctx = acquire_ctx;
+
+	s->priv_objs = kunit_kzalloc(test, sizeof(*s->priv_objs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, s->priv_objs);
+	s->priv_objs[0].ptr = &s->mst_root->mst_mgr.base;
+	s->state->num_private_objs = 1;
+	s->state->private_objs = s->priv_objs;
+
+	ctx->crtc_state->state = s->state;
+	ctx->crtc_state->connectors_changed = true;
+
+	if (!map_success) {
+		s->priv_objs[0].new_state = ERR_PTR(-ENOMEM);
+		return;
+	}
+
+	/* Fake DC link so dm_mst_get_pbn_divider() yields a non-zero divider. */
+	link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+	link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link_srv);
+	KUNIT_ASSERT_NOT_NULL(test, dc);
+	KUNIT_ASSERT_NOT_NULL(test, link);
+	link_srv->dp_get_verified_link_cap =
+		dm_test_atomic_check_dp_get_verified_link_cap;
+	link_srv->dp_link_bandwidth_kbps =
+		dm_test_atomic_check_dp_link_bandwidth_kbps;
+	dc->link_srv = link_srv;
+	link->dc = dc;
+	s->mst_root->dc_link = link;
+
+	s->mst_state = kunit_kzalloc(test, sizeof(*s->mst_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, s->mst_state);
+	INIT_LIST_HEAD(&s->mst_state->payloads);
+	s->priv_objs[0].new_state = &s->mst_state->base;
+
+	/* Pre-seed a payload so find_time_slots() skips the port kref alloc path. */
+	s->payload = kunit_kzalloc(test, sizeof(*s->payload), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, s->payload);
+	s->payload->port = s->mst_port;
+	list_add(&s->payload->next, &s->mst_state->payloads);
+
+	/* Wire a connector+crtc so find_time_slots() can resolve the crtc mask. */
+	port_conn = kunit_kzalloc(test, sizeof(*port_conn), GFP_KERNEL);
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	port_conn_state = kunit_kzalloc(test, sizeof(*port_conn_state), GFP_KERNEL);
+	conns = kunit_kzalloc(test, sizeof(*conns), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, port_conn);
+	KUNIT_ASSERT_NOT_NULL(test, crtc);
+	KUNIT_ASSERT_NOT_NULL(test, port_conn_state);
+	KUNIT_ASSERT_NOT_NULL(test, conns);
+	port_conn->index = 0;
+	crtc->index = 0;
+	port_conn_state->crtc = crtc;
+	s->mst_port->connector = port_conn;
+	conns[0].new_state = port_conn_state;
+	s->state->num_connector = 1;
+	s->state->connectors = conns;
+}
+
+/**
+ * dm_test_atomic_check_mst_finds_vcpi_slots - Test the MST time-slot allocation path
+ * @test: The KUnit test context
+ *
+ * With a fully wired MST atomic state and connectors_changed set, the check
+ * computes the color depth/PBN, allocates VCPI time slots and returns 0.
+ */
+static void dm_test_atomic_check_mst_finds_vcpi_slots(struct kunit *test)
+{
+	struct dm_test_atomic_check_ctx *ctx =
+		dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dm_test_mst_scaffold s;
+
+	dm_test_atomic_check_mst_scaffold(test, ctx, &s, true);
+	s.state->duplicated = false;
+	ctx->dm_state->base.max_requested_bpc = 8;
+
+	KUNIT_EXPECT_EQ(test,
+		dm_encoder_helper_atomic_check(&ctx->aenc->base,
+					       ctx->crtc_state,
+					       &ctx->dm_state->base), 0);
+	KUNIT_EXPECT_GE(test, ctx->dm_state->vcpi_slots, 0);
+}
+
+/**
+ * dm_test_atomic_check_mst_duplicated_skips_pbn - Test the duplicated-state fast path
+ * @test: The KUnit test context
+ *
+ * When the atomic state is duplicated the color depth/PBN recompute block is
+ * skipped, but time slots are still allocated and the check returns 0.
+ */
+static void dm_test_atomic_check_mst_duplicated_skips_pbn(struct kunit *test)
+{
+	struct dm_test_atomic_check_ctx *ctx =
+		dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dm_test_mst_scaffold s;
+
+	dm_test_atomic_check_mst_scaffold(test, ctx, &s, true);
+	s.state->duplicated = true;
+	ctx->dm_state->pbn = 0;
+
+	KUNIT_EXPECT_EQ(test,
+		dm_encoder_helper_atomic_check(&ctx->aenc->base,
+					       ctx->crtc_state,
+					       &ctx->dm_state->base), 0);
+}
+
+/**
+ * dm_test_atomic_check_mst_topology_err_propagates - Test topology-state error propagation
+ * @test: The KUnit test context
+ *
+ * When drm_atomic_get_mst_topology_state() returns an error pointer, the check
+ * propagates the error code back to the caller.
+ */
+static void dm_test_atomic_check_mst_topology_err_propagates(struct kunit *test)
+{
+	struct dm_test_atomic_check_ctx *ctx =
+		dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dm_test_mst_scaffold s;
+
+	dm_test_atomic_check_mst_scaffold(test, ctx, &s, false);
+
+	KUNIT_EXPECT_EQ(test,
+		dm_encoder_helper_atomic_check(&ctx->aenc->base,
+					       ctx->crtc_state,
+					       &ctx->dm_state->base), -ENOMEM);
+}
+
+/**
+ * dm_test_atomic_check_mst_vcpi_error_propagates - Test VCPI allocation error propagation
+ * @test: The KUnit test context
+ *
+ * A pre-existing payload marked for deletion makes drm_dp_atomic_find_time_slots()
+ * fail; the negative vcpi_slots result is propagated back to the caller.
+ */
+static void dm_test_atomic_check_mst_vcpi_error_propagates(struct kunit *test)
+{
+	struct dm_test_atomic_check_ctx *ctx =
+		dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dm_test_mst_scaffold s;
+
+	dm_test_atomic_check_mst_scaffold(test, ctx, &s, true);
+	s.state->duplicated = true;
+	ctx->dm_state->pbn = 100;
+	s.payload->delete = true;
+
+	KUNIT_EXPECT_EQ(test,
+		dm_encoder_helper_atomic_check(&ctx->aenc->base,
+					       ctx->crtc_state,
+					       &ctx->dm_state->base), -EINVAL);
+}
+
 /* Tests for hdmi_cec_unset_edid() */
 
 /**
@@ -5611,6 +5858,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_atomic_check_edp_native_keeps_scaling),
 	KUNIT_CASE(dm_test_atomic_check_lvds_non_native_enables_scaling),
 	KUNIT_CASE(dm_test_atomic_check_non_mst_returns_zero),
+	KUNIT_CASE(dm_test_atomic_check_mst_no_change_returns_zero),
+	KUNIT_CASE(dm_test_atomic_check_mst_finds_vcpi_slots),
+	KUNIT_CASE(dm_test_atomic_check_mst_duplicated_skips_pbn),
+	KUNIT_CASE(dm_test_atomic_check_mst_topology_err_propagates),
+	KUNIT_CASE(dm_test_atomic_check_mst_vcpi_error_propagates),
 	/* hdmi_cec_unset_edid */
 	KUNIT_CASE(dm_test_hdmi_cec_unset_edid_no_notifier),
 	/* create_eml_sink */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 10/82] drm/amd/display: Cover amdgpu_dm_encoder_init
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (8 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 09/82] drm/amd/display: Cover MST path in encoder atomic_check IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 11/82] drm/amd/display: Cover MST-start failure in detect_mst IVAN.LIPSKI
                   ` (72 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add a KUnit test that initializes a TMDS encoder on a DRM device embedded
in an amdgpu_device and verifies the derived possible_crtcs mask, the
encoder id set from the link index, and the attached helper funcs.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |  2 +
 .../tests/amdgpu_dm_connector_test.c          | 38 +++++++++++++++++++
 2 files changed, 40 insertions(+)

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 2cc3734c992bd..ed16e01987726 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
@@ -171,6 +171,7 @@ const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = {
 	.disable = dm_encoder_helper_disable,
 	.atomic_check = dm_encoder_helper_atomic_check
 };
+EXPORT_IF_KUNIT(amdgpu_dm_encoder_helper_funcs);
 
 int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev)
 {
@@ -215,6 +216,7 @@ int amdgpu_dm_encoder_init(struct drm_device *dev,
 
 	return res;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_encoder_init);
 
 STATIC_IFN_KUNIT enum drm_mode_subconnector get_subconnector_type(struct dc_link *link)
 {
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
index d209e26b684eb..b51aba8c07c3c 100644
--- 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
@@ -4289,6 +4289,43 @@ static void dm_test_encoder_disable_noop(struct kunit *test)
  * connector and connector-state are stacked in their containers and wired
  * together through conn_state->connector.
  */
+/* Tests for amdgpu_dm_encoder_init() */
+
+/**
+ * dm_test_encoder_init_success - Test encoder init wires id, crtc mask and helpers
+ * @test: The KUnit test context
+ *
+ * On a DRM device embedded in an amdgpu_device, amdgpu_dm_encoder_init()
+ * registers a TMDS encoder, derives possible_crtcs from mode_info.num_crtc,
+ * records the link index as the encoder id and attaches the helper funcs.
+ */
+static void dm_test_encoder_init_success(struct kunit *test)
+{
+	struct device *dev;
+	struct drm_device *drm;
+	struct amdgpu_device *adev;
+	struct amdgpu_encoder *aencoder;
+
+	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);
+	adev->mode_info.num_crtc = 4;
+
+	/* Plain kzalloc: amdgpu_dm_encoder_destroy() kfree()s it on teardown. */
+	aencoder = kzalloc_obj(*aencoder);
+	KUNIT_ASSERT_NOT_NULL(test, aencoder);
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_encoder_init(drm, aencoder, 2), 0);
+	KUNIT_EXPECT_EQ(test, aencoder->encoder_id, 2);
+	KUNIT_EXPECT_EQ(test, (int)aencoder->base.possible_crtcs, 0xf);
+	KUNIT_EXPECT_PTR_EQ(test, (const void *)aencoder->base.helper_private,
+			    (const void *)&amdgpu_dm_encoder_helper_funcs);
+}
+
 struct dm_test_atomic_check_ctx {
 	struct drm_device *drm;
 	struct amdgpu_encoder *aenc;
@@ -5863,6 +5900,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_atomic_check_mst_duplicated_skips_pbn),
 	KUNIT_CASE(dm_test_atomic_check_mst_topology_err_propagates),
 	KUNIT_CASE(dm_test_atomic_check_mst_vcpi_error_propagates),
+	KUNIT_CASE(dm_test_encoder_init_success),
 	/* hdmi_cec_unset_edid */
 	KUNIT_CASE(dm_test_hdmi_cec_unset_edid_no_notifier),
 	/* create_eml_sink */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 11/82] drm/amd/display: Cover MST-start failure in detect_mst
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (9 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 10/82] drm/amd/display: Cover amdgpu_dm_encoder_init IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 12/82] drm/amd/display: Cover amdgpu_dm_update_connector_after_detect IVAN.LIPSKI
                   ` (71 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add a KUnit test for amdgpu_dm_detect_mst_link_for_all_connectors() where
an MST branch fails to start its topology manager. A powered-down aux
forces drm_dp_mst_topology_mgr_set_mst() to fail on its initial DPCD read,
exercising the error path that downgrades the link and stops the manager.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 49 +++++++++++++++++++
 1 file changed, 49 insertions(+)

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
index b51aba8c07c3c..14240e6485049 100644
--- 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
@@ -2906,6 +2906,54 @@ static void dm_test_detect_mst_branch_without_aux(struct kunit *test)
 		amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0);
 }
 
+/**
+ * dm_test_detect_mst_start_fail - Test the MST-start failure path
+ * @test: The KUnit test context
+ *
+ * An MST branch whose topology manager fails to start must downgrade the link
+ * to a single connection and attempt to stop the topology manager. The failure
+ * is forced by giving the manager a powered-down aux, so its initial DPCD read
+ * returns an error before any real transfer is attempted.
+ */
+static void dm_test_detect_mst_start_fail(struct kunit *test)
+{
+	struct drm_device *drm = dm_test_alloc_drm(test);
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_link *link;
+	struct drm_dp_aux *aux;
+
+	aconnector = dm_test_add_connector(test, drm,
+					   DRM_MODE_CONNECTOR_DisplayPort);
+	link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link);
+	aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, aux);
+
+	link->type = dc_connection_mst_branch;
+	aconnector->dc_link = link;
+
+	/*
+	 * A powered-down aux makes the initial DPCD read return -EBUSY before
+	 * any transfer, so drm_dp_mst_topology_mgr_set_mst() fails fast.
+	 */
+	mutex_init(&aux->hw_mutex);
+	aux->name = "kunit-mst-aux";
+	aux->powered_down = true;
+	mutex_init(&aconnector->mst_mgr.lock);
+	aconnector->mst_mgr.dev = drm;
+	aconnector->mst_mgr.aux = aux;
+
+	/*
+	 * link->priv is left NULL so dm_helpers_dp_mst_stop_top_mgr() returns
+	 * early without recursing back into the topology manager.
+	 */
+	KUNIT_EXPECT_EQ(test,
+		amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0);
+
+	/* The failure path downgrades the link to a single connection. */
+	KUNIT_EXPECT_EQ(test, link->type, dc_connection_single);
+}
+
 /* Tests for amdgpu_dm_find_first_crtc_matching_connector() */
 
 /*
@@ -5835,6 +5883,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_detect_mst_skips_writeback),
 	KUNIT_CASE(dm_test_detect_mst_non_mst_link),
 	KUNIT_CASE(dm_test_detect_mst_branch_without_aux),
+	KUNIT_CASE(dm_test_detect_mst_start_fail),
 	/* amdgpu_dm_find_first_crtc_matching_connector */
 	KUNIT_CASE(dm_test_find_first_crtc_match),
 	KUNIT_CASE(dm_test_find_first_crtc_no_match),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 12/82] drm/amd/display: Cover amdgpu_dm_update_connector_after_detect
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (10 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 11/82] drm/amd/display: Cover MST-start failure in detect_mst IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 13/82] drm/amd/display: Cover HDMI infoframe/freesync timing paths IVAN.LIPSKI
                   ` (70 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests covering amdgpu_dm_update_connector_after_detect(): the
MST-sink early return, connect with and without EDID, disconnect teardown,
and the four forced emulated-sink paths. Sinks the function releases to
zero use real allocations while retained sinks use kunit-managed memory to
keep dc_sink refcounting balanced.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 331 ++++++++++++++++++
 1 file changed, 331 insertions(+)

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
index 14240e6485049..61cd1ebe9fcd1 100644
--- 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
@@ -5516,6 +5516,329 @@ static void dm_test_update_after_detect_sink_unchanged(struct kunit *test)
 	KUNIT_EXPECT_NULL(test, aconnector->dc_sink);
 }
 
+/* Tests for amdgpu_dm_update_connector_after_detect() */
+
+/*
+ * A minimal but structurally valid 128-byte EDID base block (correct header
+ * and checksum) so drm_edid_alloc()/drm_edid_connector_update() accept it when
+ * exercising the "sink carries EDID" branch.
+ */
+static const u8 dm_test_uad_edid[128] = {
+	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x04, 0x21, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x80, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c,
+};
+
+/*
+ * Build an amdgpu_dm_connector registered against a real kunit drm_device that
+ * is embedded in an amdgpu_device, so drm_to_adev()/adev_to_drm() resolve for
+ * amdgpu_dm_update_connector_after_detect() and all of its helpers.
+ *
+ * bl_idx is forced to -1 and adev->dm.freesync_module is left NULL so the
+ * backlight, CEC and freesync helpers take their early-return paths and the
+ * test stays focused on the sink-adoption logic.
+ */
+struct dm_test_uad_ctx {
+	struct amdgpu_device *adev;
+	struct drm_device *drm;
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_link *link;
+};
+
+static struct dm_test_uad_ctx *
+dm_test_uad_ctx_alloc(struct kunit *test, int connector_type)
+{
+	struct dm_test_uad_ctx *ctx;
+	struct device *dev;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	dev = drm_kunit_helper_alloc_device(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+	ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+			sizeof(*ctx->adev),
+			offsetof(struct amdgpu_device, ddev),
+			DRIVER_MODESET);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm);
+	ctx->adev = drm_to_adev(ctx->drm);
+
+	ctx->aconnector = drmm_kzalloc(ctx->drm, sizeof(*ctx->aconnector),
+			GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector);
+	KUNIT_ASSERT_EQ(test,
+			drmm_connector_init(ctx->drm, &ctx->aconnector->base,
+			&dm_test_connector_funcs, connector_type,
+			NULL), 0);
+
+	ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->link);
+	ctx->aconnector->dc_link = ctx->link;
+
+	/* Keep the backlight/CEC/freesync helpers on their early-return paths. */
+	ctx->aconnector->bl_idx = -1;
+
+	return ctx;
+}
+
+/* A real (non-kunit) sink the function is expected to free via dc_sink_release. */
+static struct dc_sink *dm_test_uad_owned_sink(void)
+{
+	struct dc_sink *sink = kzalloc_obj(*sink, GFP_KERNEL);
+
+	if (sink)
+		kref_init(&sink->refcount);
+	return sink;
+}
+
+/* A kunit-managed sink that stays referenced (never released to zero). */
+static struct dc_sink *dm_test_uad_kept_sink(struct kunit *test)
+{
+	struct dc_sink *sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+
+	if (sink)
+		kref_init(&sink->refcount);
+	return sink;
+}
+
+/**
+ * dm_test_update_after_detect_mst_sink - Test an MST sink is left to drm_mst
+ * @test: The KUnit test context
+ *
+ * A local sink reporting SIGNAL_TYPE_DISPLAY_PORT_MST is handled by the
+ * drm_mst framework, so the function returns before adopting it.
+ */
+static void dm_test_update_after_detect_mst_sink(struct kunit *test)
+{
+	struct dm_test_uad_ctx *ctx =
+		dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_sink *sink = dm_test_uad_kept_sink(test);
+
+	KUNIT_ASSERT_NOT_NULL(test, sink);
+	sink->sink_signal = SIGNAL_TYPE_DISPLAY_PORT_MST;
+	ctx->link->local_sink = sink;
+
+	amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+	KUNIT_EXPECT_NULL(test, ctx->aconnector->dc_sink);
+}
+
+/**
+ * dm_test_update_after_detect_connect_edid - Test adopting a new sink with EDID
+ * @test: The KUnit test context
+ *
+ * A freshly detected DisplayPort sink carrying EDID is adopted: the connector
+ * takes the sink, allocates a drm_edid and a requested-timing structure.
+ */
+static void dm_test_update_after_detect_connect_edid(struct kunit *test)
+{
+	struct dm_test_uad_ctx *ctx =
+		dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_sink *sink = dm_test_uad_kept_sink(test);
+
+	KUNIT_ASSERT_NOT_NULL(test, sink);
+	sink->sink_signal = SIGNAL_TYPE_DISPLAY_PORT;
+	memcpy(sink->dc_edid.raw_edid, dm_test_uad_edid, sizeof(dm_test_uad_edid));
+	sink->dc_edid.length = sizeof(dm_test_uad_edid);
+	ctx->link->local_sink = sink;
+	ctx->link->aux_mode = true;
+
+	amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, sink);
+	KUNIT_EXPECT_NOT_NULL(test, ctx->aconnector->timing_requested);
+
+	kfree(ctx->aconnector->timing_requested);
+	ctx->aconnector->timing_requested = NULL;
+}
+
+/**
+ * dm_test_update_after_detect_replace_no_edid - Test replacing a sink with no
+ * EDID and HDMI compression auto
+ * @test: The KUnit test context
+ *
+ * When a new EDID-less sink replaces an existing one, the old sink is released
+ * and, with hdmi_comp_auto set, an HDMI sink signal is promoted to FRL.
+ */
+static void dm_test_update_after_detect_replace_no_edid(struct kunit *test)
+{
+	struct dm_test_uad_ctx *ctx =
+		dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_sink *old_sink = dm_test_uad_owned_sink();
+	struct dc_sink *new_sink = dm_test_uad_kept_sink(test);
+
+	KUNIT_ASSERT_NOT_NULL(test, old_sink);
+	KUNIT_ASSERT_NOT_NULL(test, new_sink);
+
+	ctx->aconnector->dc_sink = old_sink;
+	new_sink->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+	new_sink->dc_edid.length = 0;
+	ctx->link->local_sink = new_sink;
+	ctx->link->aux_mode = true;
+	ctx->aconnector->hdmi_comp_auto = true;
+
+	amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, new_sink);
+	KUNIT_EXPECT_EQ(test, (int)new_sink->sink_signal,
+			(int)SIGNAL_TYPE_HDMI_FRL);
+}
+
+/**
+ * dm_test_update_after_detect_disconnect - Test tearing down on unplug
+ * @test: The KUnit test context
+ *
+ * With no local sink but an existing dc_sink, the disconnect path releases the
+ * sink, clears modes/timing, downgrades content protection and notifies audio.
+ */
+static void dm_test_update_after_detect_disconnect(struct kunit *test)
+{
+	struct dm_test_uad_ctx *ctx =
+		dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_sink *old_sink = dm_test_uad_owned_sink();
+	struct dc_crtc_timing *timing = kzalloc_obj(*timing, GFP_KERNEL);
+
+	KUNIT_ASSERT_NOT_NULL(test, old_sink);
+	KUNIT_ASSERT_NOT_NULL(test, timing);
+
+	mutex_init(&ctx->adev->dm.audio_lock);
+	ctx->aconnector->dc_sink = old_sink;
+	ctx->aconnector->timing_requested = timing;
+	ctx->aconnector->num_modes = 3;
+	ctx->aconnector->audio_inst = 5;
+	ctx->link->local_sink = NULL;
+
+	amdgpu_dm_connector_funcs_reset(&ctx->aconnector->base);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector->base.state);
+	ctx->aconnector->base.state->content_protection =
+		DRM_MODE_CONTENT_PROTECTION_ENABLED;
+
+	amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+	KUNIT_EXPECT_NULL(test, ctx->aconnector->dc_sink);
+	KUNIT_EXPECT_NULL(test, ctx->aconnector->timing_requested);
+	KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 0);
+	KUNIT_EXPECT_EQ(test, ctx->aconnector->audio_inst, -1);
+	KUNIT_EXPECT_EQ(test,
+			(int)ctx->aconnector->base.state->content_protection,
+			(int)DRM_MODE_CONTENT_PROTECTION_DESIRED);
+}
+
+/**
+ * dm_test_update_after_detect_force_em_adopt - Test forced eml_sink adoption
+ * @test: The KUnit test context
+ *
+ * A forced connector with an emulated sink adopts a newly reported local sink
+ * under the mode_config lock.
+ */
+static void dm_test_update_after_detect_force_em_adopt(struct kunit *test)
+{
+	struct dm_test_uad_ctx *ctx =
+		dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_sink *em_sink = dm_test_uad_kept_sink(test);
+	struct dc_sink *sink = dm_test_uad_kept_sink(test);
+
+	KUNIT_ASSERT_NOT_NULL(test, em_sink);
+	KUNIT_ASSERT_NOT_NULL(test, sink);
+
+	ctx->aconnector->base.force = DRM_FORCE_ON;
+	ctx->aconnector->dc_em_sink = em_sink;
+	ctx->link->local_sink = sink;
+
+	amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, sink);
+}
+
+/**
+ * dm_test_update_after_detect_force_em_replace - Test forced eml_sink replace
+ * @test: The KUnit test context
+ *
+ * A forced connector that already has a dc_sink releases it before adopting the
+ * newly reported local sink.
+ */
+static void dm_test_update_after_detect_force_em_replace(struct kunit *test)
+{
+	struct dm_test_uad_ctx *ctx =
+		dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_sink *em_sink = dm_test_uad_kept_sink(test);
+	struct dc_sink *old_sink = dm_test_uad_owned_sink();
+	struct dc_sink *new_sink = dm_test_uad_kept_sink(test);
+
+	KUNIT_ASSERT_NOT_NULL(test, em_sink);
+	KUNIT_ASSERT_NOT_NULL(test, old_sink);
+	KUNIT_ASSERT_NOT_NULL(test, new_sink);
+
+	ctx->aconnector->base.force = DRM_FORCE_ON;
+	ctx->aconnector->dc_em_sink = em_sink;
+	ctx->aconnector->dc_sink = old_sink;
+	ctx->link->local_sink = new_sink;
+
+	amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, new_sink);
+}
+
+/**
+ * dm_test_update_after_detect_force_em_fake - Test forced fallback to eml_sink
+ * @test: The KUnit test context
+ *
+ * A forced connector with no local sink and no dc_sink falls back to using the
+ * emulated sink so a headless stream can still be faked.
+ */
+static void dm_test_update_after_detect_force_em_fake(struct kunit *test)
+{
+	struct dm_test_uad_ctx *ctx =
+		dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_sink *em_sink = dm_test_uad_kept_sink(test);
+
+	KUNIT_ASSERT_NOT_NULL(test, em_sink);
+
+	ctx->aconnector->base.force = DRM_FORCE_ON;
+	ctx->aconnector->dc_em_sink = em_sink;
+	ctx->link->local_sink = NULL;
+
+	amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, em_sink);
+}
+
+/**
+ * dm_test_update_after_detect_force_em_keep - Test forced no-sink keeps dc_sink
+ * @test: The KUnit test context
+ *
+ * A forced connector with no local sink but an existing dc_sink keeps that sink
+ * (the emulated-sink fallback is skipped).
+ */
+static void dm_test_update_after_detect_force_em_keep(struct kunit *test)
+{
+	struct dm_test_uad_ctx *ctx =
+		dm_test_uad_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_sink *em_sink = dm_test_uad_kept_sink(test);
+	struct dc_sink *dc_sink = dm_test_uad_kept_sink(test);
+
+	KUNIT_ASSERT_NOT_NULL(test, em_sink);
+	KUNIT_ASSERT_NOT_NULL(test, dc_sink);
+
+	ctx->aconnector->base.force = DRM_FORCE_ON;
+	ctx->aconnector->dc_em_sink = em_sink;
+	ctx->aconnector->dc_sink = dc_sink;
+	ctx->link->local_sink = NULL;
+
+	amdgpu_dm_update_connector_after_detect(ctx->aconnector);
+
+	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, dc_sink);
+}
+
 /* Tests for amdgpu_dm_update_stream_scaling_settings() */
 
 /**
@@ -6007,6 +6330,14 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	/* amdgpu_dm_update_connector_after_detect */
 	KUNIT_CASE(dm_test_update_after_detect_mst_noop),
 	KUNIT_CASE(dm_test_update_after_detect_sink_unchanged),
+	KUNIT_CASE(dm_test_update_after_detect_mst_sink),
+	KUNIT_CASE(dm_test_update_after_detect_connect_edid),
+	KUNIT_CASE(dm_test_update_after_detect_replace_no_edid),
+	KUNIT_CASE(dm_test_update_after_detect_disconnect),
+	KUNIT_CASE(dm_test_update_after_detect_force_em_adopt),
+	KUNIT_CASE(dm_test_update_after_detect_force_em_replace),
+	KUNIT_CASE(dm_test_update_after_detect_force_em_fake),
+	KUNIT_CASE(dm_test_update_after_detect_force_em_keep),
 	/* amdgpu_dm_update_stream_scaling_settings */
 	KUNIT_CASE(dm_test_update_scaling_null_mode),
 	KUNIT_CASE(dm_test_update_scaling_fullscreen_default),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 13/82] drm/amd/display: Cover HDMI infoframe/freesync timing paths
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (11 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 12/82] drm/amd/display: Cover amdgpu_dm_update_connector_after_detect IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 14/82] drm/amd/display: Clear HUBPREQ_DEBUG_DB on DCN6 IVAN.LIPSKI
                   ` (69 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for fill_stream_properties_from_drm_display_mode(): an
HDMI stream signal that runs the AVI/vendor infoframe block and
overwrites the VIC, and a mode matching the connector freesync base
mode that takes timing from the drm mode fields with a 10x pixel clock.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 78 +++++++++++++++++++
 1 file changed, 78 insertions(+)

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
index 61cd1ebe9fcd1..1a95694ea35cd 100644
--- 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
@@ -3775,6 +3775,82 @@ static void dm_test_fill_stream_non_hdmi_ep_keeps_depth(struct kunit *test)
 			(int)COLOR_DEPTH_101010);
 }
 
+/**
+ * dm_test_fill_stream_hdmi_infoframe - Test HDMI signal fills VIC from infoframe
+ * @test: The KUnit test context
+ *
+ * With an HDMI stream signal the AVI/vendor infoframe path runs and overwrites
+ * the VIC (seeded via an old stream) with the value derived from the mode.
+ */
+static void dm_test_fill_stream_hdmi_infoframe(struct kunit *test)
+{
+	struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
+	struct dc_crtc_timing *timing = &ctx->stream->timing;
+	struct dc_stream_state *old_stream;
+	struct drm_display_mode *cea;
+
+	old_stream = kunit_kzalloc(test, sizeof(*old_stream), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, old_stream);
+	old_stream->timing.vic = 99;
+
+	/* A real CEA-16 (1080p60) mode so the AVI infoframe resolves VIC 16. */
+	cea = drm_display_mode_from_cea_vic(ctx->drm, 16);
+	KUNIT_ASSERT_NOT_NULL(test, cea);
+	*ctx->mode = *cea;
+	drm_mode_destroy(ctx->drm, cea);
+
+	ctx->stream->signal = SIGNAL_TYPE_HDMI_TYPE_A;
+
+	fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
+		&ctx->aconnector->base, ctx->conn_state, old_stream, 8,
+		PIXEL_ENCODING_RGB, false);
+
+	/* The HDMI block overwrote the old stream's VIC of 99 with the AVI code. */
+	KUNIT_EXPECT_EQ(test, (int)timing->vic, 16);
+}
+
+/**
+ * dm_test_fill_stream_freesync_video - Test timing taken from the mode for FS video
+ * @test: The KUnit test context
+ *
+ * When the mode matches the connector's cached freesync base mode the timing is
+ * taken from the drm mode fields (not crtc_*), including a 10x pixel clock.
+ */
+static void dm_test_fill_stream_freesync_video(struct kunit *test)
+{
+	struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
+	struct dc_crtc_timing *timing = &ctx->stream->timing;
+
+	/* Cached high mode == the mode passed in => freesync video mode. */
+	ctx->aconnector->freesync_vid_base.clock = 148500;
+	ctx->aconnector->freesync_vid_base.hdisplay = 1920;
+	ctx->aconnector->freesync_vid_base.vdisplay = 1080;
+	ctx->aconnector->freesync_vid_base.hsync_start = 2008;
+	ctx->aconnector->freesync_vid_base.hsync_end = 2052;
+	ctx->aconnector->freesync_vid_base.htotal = 2200;
+	ctx->aconnector->freesync_vid_base.vsync_start = 1084;
+	ctx->aconnector->freesync_vid_base.vsync_end = 1089;
+	ctx->aconnector->freesync_vid_base.vtotal = 1125;
+
+	ctx->mode->clock = 148500;
+	ctx->mode->hdisplay = 1920;
+	ctx->mode->vdisplay = 1080;
+	ctx->mode->hsync_start = 2008;
+	ctx->mode->hsync_end = 2052;
+	ctx->mode->htotal = 2200;
+	ctx->mode->vsync_start = 1084;
+	ctx->mode->vsync_end = 1089;
+	ctx->mode->vtotal = 1125;
+
+	fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
+		&ctx->aconnector->base, ctx->conn_state, NULL, 8,
+		PIXEL_ENCODING_RGB, false);
+
+	KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920);
+	KUNIT_EXPECT_EQ(test, (int)timing->v_addressable, 1080);
+	KUNIT_EXPECT_EQ(test, (int)timing->pix_clk_100hz, 1485000);
+}
+
 /* Tests for create_stream_for_sink() */
 
 /*
@@ -6239,6 +6315,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_fill_stream_encoding_from_caller_ycbcr444),
 	KUNIT_CASE(dm_test_fill_stream_hdmi_ep_clamps_depth),
 	KUNIT_CASE(dm_test_fill_stream_non_hdmi_ep_keeps_depth),
+	KUNIT_CASE(dm_test_fill_stream_hdmi_infoframe),
+	KUNIT_CASE(dm_test_fill_stream_freesync_video),
 	/* create_stream_for_sink */
 	KUNIT_CASE(dm_test_create_stream_fake_sink_success),
 	KUNIT_CASE(dm_test_create_stream_sets_dm_context),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 14/82] drm/amd/display: Clear HUBPREQ_DEBUG_DB on DCN6
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (12 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 13/82] drm/amd/display: Cover HDMI infoframe/freesync timing paths IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 15/82] drm/amd/display: Disable alt-ch until dependencies are ready IVAN.LIPSKI
                   ` (68 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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, Ilya Bakoulin, Dillon Varone

From: Ilya Bakoulin <Ilya.Bakoulin@amd.com>

[Why/How]
Make sure the HUBPREQ_DEBUG_DB register is cleared to prevent underflow
in some instances.

Reviewed-by: Dillon Varone <Dillon.Varone@amd.com>
Signed-off-by: Ilya Bakoulin <Ilya.Bakoulin@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 drivers/gpu/drm/amd/display/dc/hubp/dcn60/dcn60_hubp.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn60/dcn60_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn60/dcn60_hubp.c
index d22fe26aae623..01b6b3ed5a998 100644
--- a/drivers/gpu/drm/amd/display/dc/hubp/dcn60/dcn60_hubp.c
+++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn60/dcn60_hubp.c
@@ -25,6 +25,9 @@ static void hubp60_program_deadline(
 {
 	struct dcn20_hubp *hubp2 = TO_DCN20_HUBP(hubp);
 
+	/* Make sure DLG test mode is disabled */
+	REG_WRITE(HUBPREQ_DEBUG_DB, 0);
+
 	/* DLG - Per hubp */
 	REG_SET_2(BLANK_OFFSET_0, 0,
 		REFCYC_H_BLANK_END, dlg_attr->refcyc_h_blank_end,
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 15/82] drm/amd/display: Disable alt-ch until dependencies are ready
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (13 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 14/82] drm/amd/display: Clear HUBPREQ_DEBUG_DB on DCN6 IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 16/82] drm/amd/display: Fix CalculateFlipSchedule Calculation IVAN.LIPSKI
                   ` (67 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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, Alvin Lee, Dillon Varone

From: Alvin Lee <Alvin.Lee2@amd.com>

[Description]
- Alt-Ch cannot be enabled until all dependncies are ready (SMU needs
  to support double p-state + IFWI needs to support G7 regions
  enabled).
- If PMFW enables UCLK DPM without double p-state support and alt-ch
  is enabled then hangs could occur

Reviewed-by: Dillon Varone <Dillon.Varone@amd.com>
Signed-off-by: Alvin Lee <Alvin.Lee2@amd.com>
Signed-off-by: Ivan Lipsi <ivan.lipski@amd.com>
---
 drivers/gpu/drm/amd/display/dc/resource/dcn60/dcn60_resource.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn60/dcn60_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn60/dcn60_resource.c
index 7171b237c4278..d7947105af223 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn60/dcn60_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn60/dcn60_resource.c
@@ -671,6 +671,8 @@ static const struct dc_debug_options debug_defaults_drv = {
 		}
 	},
 	.force_cositing = CHROMA_COSITING_NONE + 1,
+	.dml21_disable_pstate_method_mask = 0x20, // disable alt-ch unconditionally until dependencies are ready
+
 };
 
 static const struct dc_check_config config_defaults = {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 16/82] drm/amd/display: Fix CalculateFlipSchedule Calculation
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (14 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 15/82] drm/amd/display: Disable alt-ch until dependencies are ready IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 17/82] drm/amd/display: Test EDID quirks and ACPI EDID read IVAN.LIPSKI
                   ` (66 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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, Austin Zheng, Dillon Varone

From: Austin Zheng <Austin.Zheng@amd.com>

[Why/How]
Row bandwidth contribution to lb_flip_bw was refactored to only consider
bandwidth needed for a single row.
row_time_budget is for 2 row's worth of time but only half is needed when
calculating the row bandwidth used in the lb_flip_bw calculation.

Also add meta row bytes that was accidentally removed during previous
refactor.

Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Austin Zheng <Austin.Zheng@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c      | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c
index 5a1b710b9ec8f..9a205b0e15989 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c
@@ -6654,7 +6654,7 @@ static void CalculateFlipSchedule(
 			l->lb_flip_bw = math_max3(
 				l->hvm_scaled_vm_row_bytes / l->vm_and_row_time_budget,
 				l->hvm_scaled_vm_bytes / math_min2(l->vm_time_budget, 31.75 * LineTime - Tno_bw_flip),
-				l->dpte_row_bytes * HostVMInefficiencyFactor / math_min2(l->row_time_budget, 15.75 * LineTime));
+				(l->dpte_row_bytes * HostVMInefficiencyFactor + meta_row_bytes) / math_min2(l->row_time_budget / 2, 15.75 * LineTime)); //use bandwidth for a single row
 #ifdef __DML_VBA_DEBUG__
 			DML_LOG_VERBOSE("DML::%s: max_flip_time = %f\n", __func__, l->max_flip_time);
 			DML_LOG_VERBOSE("DML::%s: total vm bytes (hvm ineff scaled) = %f\n", __func__, l->hvm_scaled_vm_bytes);
@@ -6681,7 +6681,7 @@ static void CalculateFlipSchedule(
 					Tno_bw_flip + vm_bytes * HostVMInefficiencyFactor / l->lb_flip_bw,
 					LineTime / 4.0);
 			l->Tr0_flip = math_max3(Tr0_trips_flip,
-					l->dpte_row_bytes * HostVMInefficiencyFactor / l->lb_flip_bw,
+					(l->dpte_row_bytes * HostVMInefficiencyFactor + meta_row_bytes) / l->lb_flip_bw,
 					LineTime / 4.0);
 
 			*dst_y_per_vm_flip = math_ceil2(4.0 * l->Tvm_flip / LineTime, 1.0) / 4.0;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 17/82] drm/amd/display: Test EDID quirks and ACPI EDID read
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (15 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 16/82] drm/amd/display: Fix CalculateFlipSchedule Calculation IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 18/82] drm/amd/display: Test execute_synaptics_rc_command failures IVAN.LIPSKI
                   ` (65 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for apply_edid_quirks() covering the two quirk entries
that had no coverage: the Apple Studio Display tile quirk and the Lenovo
G34w-30 FreeSync minimum refresh rate override. Both tests build an EDID
carrying the quirked panel ID and check the resulting dc_edid_caps panel
patch.

Add a KUnit test for dm_helpers_read_acpi_edid() that lets an eDP
connector reach drm_edid_read_custom() instead of returning early. The
probe callback finds no ACPI companion, so the helper returns NULL,
which covers the read and the failure return.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  | 54 +++++++++++++++++++
 1 file changed, 54 insertions(+)

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
index 5de697ca0fcce..89ce10d84f035 100644
--- 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
@@ -223,6 +223,38 @@ static void dm_test_apply_edid_quirks_unknown_noop(struct kunit *test)
 	KUNIT_EXPECT_FALSE(test, link->wa_flags.skip_phy_ssc_reduction);
 }
 
+/**
+ * dm_test_apply_edid_quirks_disable_second_tile - Test Apple Studio Display tile quirk
+ * @test: The KUnit test context
+ */
+static void dm_test_apply_edid_quirks_disable_second_tile(struct kunit *test)
+{
+	struct dc_edid_caps edid_caps = {0};
+	struct dc_link *link = dm_test_quirk_link(test);
+	struct edid *edid = dm_test_edid_with_panel_id(test,
+			drm_edid_encode_panel_id('A', 'P', 'P', 0xAE3A));
+
+	apply_edid_quirks(link, edid, &edid_caps);
+
+	KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.disable_second_tile);
+}
+
+/**
+ * dm_test_apply_edid_quirks_force_freesync_min_60 - Test Lenovo G34w-30 FreeSync min quirk
+ * @test: The KUnit test context
+ */
+static void dm_test_apply_edid_quirks_force_freesync_min_60(struct kunit *test)
+{
+	struct dc_edid_caps edid_caps = {0};
+	struct dc_link *link = dm_test_quirk_link(test);
+	struct edid *edid = dm_test_edid_with_panel_id(test,
+			drm_edid_encode_panel_id('L', 'E', 'N', 0x66F1));
+
+	apply_edid_quirks(link, edid, &edid_caps);
+
+	KUNIT_EXPECT_EQ(test, edid_caps.panel_patch.force_freesync_min_hz, 60U);
+}
+
 /* Tests for dm_helpers_parse_edid_caps() */
 
 /*
@@ -595,6 +627,25 @@ static void dm_test_read_acpi_edid_force_off(struct kunit *test)
 	KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector));
 }
 
+/**
+ * dm_test_read_acpi_edid_panel_connector - Test the ACPI EDID read is attempted
+ * @test: The KUnit test context
+ *
+ * An eDP connector that is not forced off reaches drm_edid_read_custom(), whose
+ * probe callback finds no ACPI companion, so no EDID is returned.
+ */
+static void dm_test_read_acpi_edid_panel_connector(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector;
+	struct amdgpu_device *adev;
+
+	adev = dm_kunit_alloc_adev(test);
+	aconnector = dm_kunit_alloc_connector(test, adev, NULL);
+	aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP;
+
+	KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector));
+}
+
 struct dm_test_vbios_edid {
 	struct dc_bios bios;
 	enum bp_result result;
@@ -3742,6 +3793,8 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	KUNIT_CASE(dm_test_apply_edid_quirks_skip_phy_ssc),
 	KUNIT_CASE(dm_test_apply_edid_quirks_force_freesync_min),
 	KUNIT_CASE(dm_test_apply_edid_quirks_unknown_noop),
+	KUNIT_CASE(dm_test_apply_edid_quirks_disable_second_tile),
+	KUNIT_CASE(dm_test_apply_edid_quirks_force_freesync_min_60),
 	/* dm_helpers_parse_edid_caps */
 	KUNIT_CASE(dm_test_parse_edid_caps_null_edid),
 	KUNIT_CASE(dm_test_parse_edid_caps_null_caps),
@@ -3756,6 +3809,7 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	KUNIT_CASE(dm_test_read_acpi_edid_debug_mask_disabled),
 	KUNIT_CASE(dm_test_read_acpi_edid_non_panel_connector),
 	KUNIT_CASE(dm_test_read_acpi_edid_force_off),
+	KUNIT_CASE(dm_test_read_acpi_edid_panel_connector),
 	KUNIT_CASE(dm_test_read_vbios_edid_non_embedded),
 	KUNIT_CASE(dm_test_read_vbios_edid_missing_callback),
 	KUNIT_CASE(dm_test_read_vbios_edid_callback_error),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 18/82] drm/amd/display: Test execute_synaptics_rc_command failures
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (16 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 17/82] drm/amd/display: Test EDID quirks and ACPI EDID read IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 19/82] drm/amd/display: Test MST stream feature read failure IVAN.LIPSKI
                   ` (64 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for execute_synaptics_rc_command() covering the three
AUX write failures that were untested (data, offset and command), plus
the poll loop that waits for the command to go inactive.

The Synaptics AUX fake gains a busy read counter so it can report the
command as still active for the first reads.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  | 87 ++++++++++++++++++-
 1 file changed, 85 insertions(+), 2 deletions(-)

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
index 89ce10d84f035..bc8b607b6c83b 100644
--- 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
@@ -1396,6 +1396,8 @@ struct dm_test_synaptics_aux {
 	unsigned int downspread_reads;
 	unsigned int downspread_writes;
 	unsigned int dsc_enable_writes;
+	/* number of RC command reads that report the command still active */
+	unsigned int command_busy_reads;
 };
 
 static ssize_t dm_test_synaptics_aux_transfer(struct drm_dp_aux *aux,
@@ -1464,8 +1466,14 @@ static ssize_t dm_test_synaptics_aux_transfer(struct drm_dp_aux *aux,
 			fixture->downspread_reads++;
 			break;
 		case SYNAPTICS_RC_COMMAND:
-			if (msg->size)
-				buffer[0] = fixture->last_rc_command & 0x7f;
+			if (msg->size) {
+				if (fixture->command_busy_reads) {
+					fixture->command_busy_reads--;
+					buffer[0] = fixture->last_rc_command;
+				} else {
+					buffer[0] = fixture->last_rc_command & 0x7f;
+				}
+			}
 			fixture->rc_command_reads++;
 			break;
 		case SYNAPTICS_RC_RESULT:
@@ -1612,6 +1620,77 @@ static void dm_test_execute_synaptics_rc_command_write_fail(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 0U);
 }
 
+/**
+ * dm_test_execute_synaptics_rc_command_data_fail - Test RC data write failure
+ * @test: The KUnit test context
+ */
+static void dm_test_execute_synaptics_rc_command_data_fail(struct kunit *test)
+{
+	struct dm_test_synaptics_aux *fixture;
+	u8 data = 0;
+
+	fixture = dm_test_alloc_synaptics_aux(test);
+	fixture->fail_address = SYNAPTICS_RC_DATA;
+
+	KUNIT_EXPECT_FALSE(test, execute_synaptics_rc_command(&fixture->aux, true,
+							      0x01, sizeof(data), 0, &data));
+	KUNIT_EXPECT_EQ(test, fixture->last_rc_offset, 0U);
+}
+
+/**
+ * dm_test_execute_synaptics_rc_command_offset_fail - Test RC offset write failure
+ * @test: The KUnit test context
+ */
+static void dm_test_execute_synaptics_rc_command_offset_fail(struct kunit *test)
+{
+	struct dm_test_synaptics_aux *fixture;
+	u8 data = 0;
+
+	fixture = dm_test_alloc_synaptics_aux(test);
+	fixture->fail_address = SYNAPTICS_RC_OFFSET;
+
+	KUNIT_EXPECT_FALSE(test, execute_synaptics_rc_command(&fixture->aux, false,
+							      0x31, sizeof(data), 0, &data));
+	KUNIT_EXPECT_EQ(test, fixture->last_rc_length, 0U);
+}
+
+/**
+ * dm_test_execute_synaptics_rc_command_command_fail - Test RC command write failure
+ * @test: The KUnit test context
+ */
+static void dm_test_execute_synaptics_rc_command_command_fail(struct kunit *test)
+{
+	struct dm_test_synaptics_aux *fixture;
+	u8 data = 0;
+
+	fixture = dm_test_alloc_synaptics_aux(test);
+	fixture->fail_address = SYNAPTICS_RC_COMMAND;
+
+	KUNIT_EXPECT_FALSE(test, execute_synaptics_rc_command(&fixture->aux, false,
+							      0x31, sizeof(data), 0, &data));
+	KUNIT_EXPECT_EQ(test, fixture->rc_result_reads, 0U);
+}
+
+/**
+ * dm_test_execute_synaptics_rc_command_busy_poll - Test the RC command poll loop
+ * @test: The KUnit test context
+ *
+ * The command stays marked active for the first reads, so the helper sleeps and
+ * polls again before reading the result.
+ */
+static void dm_test_execute_synaptics_rc_command_busy_poll(struct kunit *test)
+{
+	struct dm_test_synaptics_aux *fixture;
+	u8 data = 0;
+
+	fixture = dm_test_alloc_synaptics_aux(test);
+	fixture->command_busy_reads = 2;
+
+	KUNIT_EXPECT_TRUE(test, execute_synaptics_rc_command(&fixture->aux, false,
+							     0x31, sizeof(data), 0, &data));
+	KUNIT_EXPECT_EQ(test, fixture->rc_command_reads, 3U);
+}
+
 /**
  * dm_test_apply_synaptics_fifo_reset_wa_full - Test full FIFO reset sequence
  * @test: The KUnit test context
@@ -3857,6 +3936,10 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	KUNIT_CASE(dm_test_execute_synaptics_rc_command_write_success),
 	KUNIT_CASE(dm_test_execute_synaptics_rc_command_read_success),
 	KUNIT_CASE(dm_test_execute_synaptics_rc_command_write_fail),
+	KUNIT_CASE(dm_test_execute_synaptics_rc_command_data_fail),
+	KUNIT_CASE(dm_test_execute_synaptics_rc_command_offset_fail),
+	KUNIT_CASE(dm_test_execute_synaptics_rc_command_command_fail),
+	KUNIT_CASE(dm_test_execute_synaptics_rc_command_busy_poll),
 	KUNIT_CASE(dm_test_apply_synaptics_fifo_reset_wa_full),
 	KUNIT_CASE(dm_test_apply_synaptics_fifo_reset_wa_first_fail),
 	KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_inactive),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 19/82] drm/amd/display: Test MST stream feature read failure
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (17 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 18/82] drm/amd/display: Test execute_synaptics_rc_command failures IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 20/82] drm/amd/display: Test dm_helpers_submit_i2c_over_aux IVAN.LIPSKI
                   ` (63 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add a KUnit test for dm_helpers_mst_enable_stream_features() that fails
the DP_DOWNSPREAD_CTRL read.

The helper must bail out without writing the register back, which covers
the early return on a failed DPCD read.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)

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
index bc8b607b6c83b..6b4f6993ace94 100644
--- 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
@@ -2661,6 +2661,38 @@ static void dm_test_mst_enable_stream_features_writes_downspread(struct kunit *t
 	KUNIT_EXPECT_EQ(test, fixture->dpcd_write_value, (u8)BIT(7));
 }
 
+/**
+ * dm_test_mst_enable_stream_features_read_fail - Test early return on DPCD read failure
+ * @test: The KUnit test context
+ */
+static void dm_test_mst_enable_stream_features_read_fail(struct kunit *test)
+{
+	struct dm_test_synaptics_aux *fixture;
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_stream_state *stream;
+	struct amdgpu_device *adev;
+	struct dc_link *link;
+
+	adev = dm_kunit_alloc_adev(test);
+	fixture = dm_test_alloc_synaptics_aux_with_dev(test, &adev->ddev);
+	aconnector = dm_kunit_alloc_connector(test, adev, NULL);
+	link = dm_kunit_alloc_link(test);
+	stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, stream);
+
+	fixture->fail_address = DP_DOWNSPREAD_CTRL;
+	dm_test_current_aux_recorder = fixture;
+	aconnector->dm_dp_aux.aux.drm_dev = &adev->ddev;
+	aconnector->dm_dp_aux.aux.transfer = dm_test_current_aux_transfer;
+	drm_dp_aux_init(&aconnector->dm_dp_aux.aux);
+	link->priv = aconnector;
+	stream->link = link;
+
+	dm_helpers_mst_enable_stream_features(stream);
+
+	KUNIT_EXPECT_EQ(test, fixture->downspread_writes, 0U);
+}
+
 /* Tests for dm_helpers_enable_periodic_detection() */
 
 struct dm_test_idle_work {
@@ -4013,6 +4045,7 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	/* dm_helpers_mst_enable_stream_features */
 	KUNIT_CASE(dm_test_mst_enable_stream_features_aux_disabled),
 	KUNIT_CASE(dm_test_mst_enable_stream_features_writes_downspread),
+	KUNIT_CASE(dm_test_mst_enable_stream_features_read_fail),
 	/* dm_helpers_enable_periodic_detection */
 	KUNIT_CASE(dm_test_enable_periodic_detection_no_workqueue),
 	KUNIT_CASE(dm_test_enable_periodic_detection_updates_enable),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 20/82] drm/amd/display: Test dm_helpers_submit_i2c_over_aux
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (18 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 19/82] drm/amd/display: Test MST stream feature read failure IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 21/82] drm/amd/display: Test GPU memory allocate and free helpers IVAN.LIPSKI
                   ` (62 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add a KUnit test for dm_helpers_submit_i2c_over_aux(), which is still an
unimplemented stub, to pin down that it reports failure.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c    |  1 +
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c     | 16 ++++++++++++++++
 2 files changed, 17 insertions(+)

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 b05455498cbf6..29f254cafae4e 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
@@ -1927,3 +1927,4 @@ bool dm_helpers_submit_i2c_over_aux(struct ddc_service *ddc, uint32_t address, u
 	//TODO: Implement this
 	return false;
 }
+EXPORT_IF_KUNIT(dm_helpers_submit_i2c_over_aux);
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
index 6b4f6993ace94..0314ebb5c8aea 100644
--- 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
@@ -3892,6 +3892,20 @@ static void dm_test_dp_handle_test_pattern_no_pipe(struct kunit *test)
 							       test_params));
 }
 
+/* Tests for dm_helpers_submit_i2c_over_aux() */
+
+/**
+ * dm_test_submit_i2c_over_aux_unimplemented - Test the unimplemented stub returns false
+ * @test: The KUnit test context
+ */
+static void dm_test_submit_i2c_over_aux_unimplemented(struct kunit *test)
+{
+	u8 buffer[1] = { 0 };
+
+	KUNIT_EXPECT_FALSE(test, dm_helpers_submit_i2c_over_aux(NULL, 0x50, 0, buffer,
+								sizeof(buffer), true));
+}
+
 static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	/* edid_extract_panel_id */
 	KUNIT_CASE(dm_test_edid_extract_panel_id_basic),
@@ -4080,6 +4094,8 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	KUNIT_CASE(dm_test_dp_write_dsc_enable_pcon_disable),
 	/* dm_helpers_dp_handle_test_pattern_request */
 	KUNIT_CASE(dm_test_dp_handle_test_pattern_no_pipe),
+	/* dm_helpers_submit_i2c_over_aux */
+	KUNIT_CASE(dm_test_submit_i2c_over_aux_unimplemented),
 	{}
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 21/82] drm/amd/display: Test GPU memory allocate and free helpers
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (19 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 20/82] drm/amd/display: Test dm_helpers_submit_i2c_over_aux IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 22/82] drm/amd/display: Test dm_helpers_dmub_set_config_sync IVAN.LIPSKI
                   ` (61 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add a KUnit test for dm_helpers_allocate_gpu_mem() and
dm_helpers_free_gpu_mem() that allocates a DMUB tracebuffer and frees it
again, checking the returned CPU pointer and GPU address.

[HOW]
amdgpu_bo_create_kernel() and amdgpu_bo_free_kernel() need a live TTM
device, so both are replaced by a fake through the existing
amdgpu_dm_services KUnit operations table.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c |  2 +
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  | 93 +++++++++++++++++++
 2 files changed, 95 insertions(+)

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 29f254cafae4e..0344ce5ce816b 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
@@ -1442,6 +1442,7 @@ void *dm_helpers_allocate_gpu_mem(
 
 	return dm_allocate_gpu_mem(adev, type, size, addr);
 }
+EXPORT_IF_KUNIT(dm_helpers_allocate_gpu_mem);
 
 void dm_helpers_free_gpu_mem(
 		struct dc_context *ctx,
@@ -1452,6 +1453,7 @@ void dm_helpers_free_gpu_mem(
 
 	dm_free_gpu_mem(adev, type, pvMem);
 }
+EXPORT_IF_KUNIT(dm_helpers_free_gpu_mem);
 
 bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enable)
 {
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
index 0314ebb5c8aea..11a5e8f307354 100644
--- 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
@@ -3906,6 +3906,97 @@ static void dm_test_submit_i2c_over_aux_unimplemented(struct kunit *test)
 								sizeof(buffer), true));
 }
 
+/* Tests for dm_helpers_allocate_gpu_mem() and dm_helpers_free_gpu_mem() */
+
+/*
+ * Fake buffer object allocator: amdgpu_bo_create_kernel() and
+ * amdgpu_bo_free_kernel() need a live TTM device, so dm_allocate_gpu_mem() is
+ * routed through this fake.
+ */
+
+#define DM_TEST_FAKE_GPU_ADDR	0x1234ABCD0000ULL
+
+struct dm_test_bo_ctx {
+	void *cpu_ptr;
+	unsigned int create_calls;
+	unsigned int free_calls;
+	unsigned long create_size;
+	u32 create_domain;
+};
+
+static struct dm_test_bo_ctx dm_test_bo;
+
+static int dm_test_bo_create_kernel(struct amdgpu_device *adev, unsigned long size,
+				    int align, u32 domain, struct amdgpu_bo **bo_ptr,
+				    u64 *gpu_addr, void **cpu_addr)
+{
+	dm_test_bo.create_calls++;
+	dm_test_bo.create_size = size;
+	dm_test_bo.create_domain = domain;
+
+	*gpu_addr = DM_TEST_FAKE_GPU_ADDR;
+	*cpu_addr = dm_test_bo.cpu_ptr;
+
+	return 0;
+}
+
+static void dm_test_bo_free_kernel(struct amdgpu_bo **bo, u64 *gpu_addr, void **cpu_addr)
+{
+	dm_test_bo.free_calls++;
+
+	*bo = NULL;
+	*gpu_addr = 0;
+	*cpu_addr = NULL;
+}
+
+static const struct amdgpu_dm_services_kunit_ops dm_test_services_ops = {
+	.bo_create_kernel = dm_test_bo_create_kernel,
+	.bo_free_kernel = dm_test_bo_free_kernel,
+};
+
+/**
+ * dm_test_gpu_mem_alloc_and_free - Test the GPU memory helper wrappers
+ * @test: The KUnit test context
+ *
+ * Both helpers only unwrap the device from the DC context and forward to
+ * dm_allocate_gpu_mem()/dm_free_gpu_mem().
+ */
+static void dm_test_gpu_mem_alloc_and_free(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct dc_context *ctx;
+	long long addr = 0;
+	void *cpu_ptr;
+	void *mem;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+	cpu_ptr = kunit_kzalloc(test, PAGE_SIZE, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, cpu_ptr);
+
+	ctx->driver_context = adev;
+	INIT_LIST_HEAD(&adev->dm.da_list);
+	dm_test_bo = (struct dm_test_bo_ctx) { .cpu_ptr = cpu_ptr };
+	amdgpu_dm_services_kunit_set_ops(&dm_test_services_ops);
+
+	mem = dm_helpers_allocate_gpu_mem(ctx, DC_MEM_ALLOC_TYPE_GART, 4096, &addr);
+
+	KUNIT_EXPECT_PTR_EQ(test, mem, cpu_ptr);
+	KUNIT_EXPECT_EQ(test, addr, (long long)DM_TEST_FAKE_GPU_ADDR);
+	KUNIT_EXPECT_EQ(test, dm_test_bo.create_calls, 1U);
+	KUNIT_EXPECT_EQ(test, dm_test_bo.create_size, 4096UL);
+	KUNIT_EXPECT_EQ(test, dm_test_bo.create_domain, (u32)AMDGPU_GEM_DOMAIN_GTT);
+
+	dm_helpers_free_gpu_mem(ctx, DC_MEM_ALLOC_TYPE_GART, mem);
+
+	KUNIT_EXPECT_EQ(test, dm_test_bo.free_calls, 1U);
+	KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.da_list));
+
+	amdgpu_dm_services_kunit_set_ops(NULL);
+}
+
 static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	/* edid_extract_panel_id */
 	KUNIT_CASE(dm_test_edid_extract_panel_id_basic),
@@ -4096,6 +4187,8 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	KUNIT_CASE(dm_test_dp_handle_test_pattern_no_pipe),
 	/* dm_helpers_submit_i2c_over_aux */
 	KUNIT_CASE(dm_test_submit_i2c_over_aux_unimplemented),
+	/* dm_helpers_allocate_gpu_mem / dm_helpers_free_gpu_mem */
+	KUNIT_CASE(dm_test_gpu_mem_alloc_and_free),
 	{}
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 22/82] drm/amd/display: Test dm_helpers_dmub_set_config_sync
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (20 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 21/82] drm/amd/display: Test GPU memory allocate and free helpers IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 23/82] drm/amd/display: Test dm_helpers_is_dp_sink_present IVAN.LIPSKI
                   ` (60 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add a KUnit test for dm_helpers_dmub_set_config_sync() that submits a
SET_CONFIG payload without a DC DMUB service.

The command cannot reach the firmware, so the helper still returns
success while reporting SET_CONFIG_UNKNOWN_ERROR.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c |  1 +
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  | 48 +++++++++++++++++++
 2 files changed, 49 insertions(+)

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 0344ce5ce816b..7c7783aa40745 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
@@ -1374,6 +1374,7 @@ int dm_helpers_dmub_set_config_sync(struct dc_context *ctx,
 	return amdgpu_dm_process_dmub_set_config_sync(ctx, link->link_index, payload,
 			operation_result);
 }
+EXPORT_IF_KUNIT(dm_helpers_dmub_set_config_sync);
 
 void dm_set_dcn_clocks(struct dc_context *ctx, struct dc_clocks *clks)
 {
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
index 11a5e8f307354..170e0ee425b23 100644
--- 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
@@ -19,6 +19,7 @@
 #include "dc_bios_types.h"
 #include "dm_helpers.h"
 #include "ddc_service_types.h"
+#include "dmub/dmub_srv.h"
 #include "dmub_cmd.h"
 #include "amdgpu_dm_helpers.h"
 #include "amdgpu_dm_kunit_test_helpers.h"
@@ -3997,6 +3998,51 @@ static void dm_test_gpu_mem_alloc_and_free(struct kunit *test)
 	amdgpu_dm_services_kunit_set_ops(NULL);
 }
 
+/* Tests for dm_helpers_dmub_set_config_sync() */
+
+/**
+ * dm_test_dmub_set_config_sync_unknown_error - Test SET_CONFIG without a DMUB service
+ * @test: The KUnit test context
+ *
+ * The helper only unwraps the link index before forwarding. With no DC DMUB
+ * service the command cannot reach the firmware and is reported as completed
+ * with SET_CONFIG_UNKNOWN_ERROR.
+ */
+static void dm_test_dmub_set_config_sync_unknown_error(struct kunit *test)
+{
+	struct set_config_cmd_payload payload = {0};
+	enum set_config_status result = SET_CONFIG_PENDING;
+	struct dmub_notification *notify;
+	struct amdgpu_device *adev;
+	struct dc_context *dc_ctx;
+	struct dc_context *ctx;
+	struct dc_link *link;
+	struct dc *dc;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+	dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc_ctx);
+	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc);
+	notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, notify);
+	link = dm_kunit_alloc_link(test);
+
+	dc->ctx = dc_ctx;
+	dc->links[0] = link;
+	ctx->dc = dc;
+	ctx->driver_context = adev;
+	adev->dm.dmub_notify = notify;
+	mutex_init(&adev->dm.dpia_aux_lock);
+	init_completion(&adev->dm.dmub_aux_transfer_done);
+
+	KUNIT_EXPECT_EQ(test, dm_helpers_dmub_set_config_sync(ctx, link, &payload, &result), 0);
+	KUNIT_EXPECT_EQ(test, (int)result, (int)SET_CONFIG_UNKNOWN_ERROR);
+}
+
 static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	/* edid_extract_panel_id */
 	KUNIT_CASE(dm_test_edid_extract_panel_id_basic),
@@ -4189,6 +4235,8 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	KUNIT_CASE(dm_test_submit_i2c_over_aux_unimplemented),
 	/* dm_helpers_allocate_gpu_mem / dm_helpers_free_gpu_mem */
 	KUNIT_CASE(dm_test_gpu_mem_alloc_and_free),
+	/* dm_helpers_dmub_set_config_sync */
+	KUNIT_CASE(dm_test_dmub_set_config_sync_unknown_error),
 	{}
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 23/82] drm/amd/display: Test dm_helpers_is_dp_sink_present
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (21 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 22/82] drm/amd/display: Test dm_helpers_dmub_set_config_sync IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 24/82] drm/amd/display: Test dm_helpers_read_local_edid IVAN.LIPSKI
                   ` (59 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add a KUnit test for dm_helpers_is_dp_sink_present() that walks past the
early NULL checks and reaches the link service query.

A fake detect_dp_sink_present callback reports a sink, which covers the
DDC lookup and the return of the link service result.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  | 46 +++++++++++++++++++
 1 file changed, 46 insertions(+)

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
index 170e0ee425b23..b0733961545dc 100644
--- 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
@@ -12,6 +12,7 @@
 
 #include "dc.h"
 #include "core_types.h"
+#include "link_service.h"
 #include "amdgpu.h"
 #include "amdgpu_mode.h"
 #include "amdgpu_dm.h"
@@ -4043,6 +4044,49 @@ static void dm_test_dmub_set_config_sync_unknown_error(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, (int)result, (int)SET_CONFIG_UNKNOWN_ERROR);
 }
 
+/* Tests for dm_helpers_is_dp_sink_present() */
+
+static bool dm_test_dp_sink_present_true(struct dc_link *link)
+{
+	return true;
+}
+
+/**
+ * dm_test_is_dp_sink_present_queries_link_service - Test sink presence query
+ * @test: The KUnit test context
+ *
+ * With a connector present the helper takes the AUX hardware mutex and
+ * forwards to the link service.
+ */
+static void dm_test_is_dp_sink_present_queries_link_service(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector;
+	struct link_service *link_srv;
+	struct amdgpu_device *adev;
+	struct dc_link *link;
+	struct dc *dc;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc);
+	link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link_srv);
+	link = dm_kunit_alloc_link(test);
+	aconnector = dm_kunit_alloc_connector(test, adev, link);
+
+	aconnector->dm_dp_aux.aux.drm_dev = &adev->ddev;
+	aconnector->dm_dp_aux.aux.transfer = dm_test_dpcd_ack_transfer;
+	drm_dp_aux_init(&aconnector->dm_dp_aux.aux);
+
+	link_srv->dp_is_sink_present = dm_test_dp_sink_present_true;
+	dc->link_srv = link_srv;
+	link->dc = dc;
+	link->priv = aconnector;
+
+	KUNIT_EXPECT_TRUE(test, dm_helpers_is_dp_sink_present(link));
+}
+
 static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	/* edid_extract_panel_id */
 	KUNIT_CASE(dm_test_edid_extract_panel_id_basic),
@@ -4237,6 +4281,8 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	KUNIT_CASE(dm_test_gpu_mem_alloc_and_free),
 	/* dm_helpers_dmub_set_config_sync */
 	KUNIT_CASE(dm_test_dmub_set_config_sync_unknown_error),
+	/* dm_helpers_is_dp_sink_present */
+	KUNIT_CASE(dm_test_is_dp_sink_present_queries_link_service),
 	{}
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 24/82] drm/amd/display: Test dm_helpers_read_local_edid
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (22 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 23/82] drm/amd/display: Test dm_helpers_is_dp_sink_present IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 25/82] drm/amd/display: Test dp_handle_test_pattern_request patterns IVAN.LIPSKI
                   ` (58 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_helpers_read_local_edid() covering the AUX read
that returns a valid EDID, the compliance test request that acknowledges
the EDID checksum, and the I2C path where no sink responds.

[HOW]
The helper publishes the EDID through drm_edid_connector_update(), which
needs a fully initialised connector, so the tests build a real one. A
fake AUX channel serves the EDID and answers the native DPCD reads, and
a fake I2C algorithm always reports no response.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c |   2 +
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  | 231 ++++++++++++++++++
 2 files changed, 233 insertions(+)

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 7c7783aa40745..ab15ceec4daa0 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
@@ -1350,6 +1350,8 @@ enum dc_edid_status dm_helpers_read_local_edid(
 
 	return edid_status;
 }
+EXPORT_IF_KUNIT(dm_helpers_read_local_edid);
+
 int dm_helper_dmub_aux_transfer_sync(
 		struct dc_context *ctx,
 		const struct dc_link *link,
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
index b0733961545dc..3531d52da1591 100644
--- 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
@@ -6,8 +6,10 @@
  */
 
 #include <kunit/test.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_kunit_helpers.h>
+#include <drm/drm_managed.h>
 #include <drm/display/drm_dp_mst_helper.h>
 
 #include "dc.h"
@@ -19,6 +21,7 @@
 #include "amdgpu_dm_mst_types.h"
 #include "dc_bios_types.h"
 #include "dm_helpers.h"
+#include "dpcd_defs.h"
 #include "ddc_service_types.h"
 #include "dmub/dmub_srv.h"
 #include "dmub_cmd.h"
@@ -4087,6 +4090,230 @@ static void dm_test_is_dp_sink_present_queries_link_service(struct kunit *test)
 	KUNIT_EXPECT_TRUE(test, dm_helpers_is_dp_sink_present(link));
 }
 
+/* Tests for dm_helpers_read_local_edid() */
+
+/*
+ * dm_helpers_read_local_edid() feeds the EDID it reads into
+ * drm_edid_connector_update(), which needs a fully initialised connector so the
+ * EDID property blob is published. dm_kunit_alloc_connector() only fills in the
+ * device pointer, so build a real one here.
+ */
+static const struct drm_connector_funcs dm_test_connector_funcs = {
+	.reset = drm_atomic_helper_connector_reset,
+};
+
+static struct amdgpu_dm_connector *dm_test_alloc_real_connector(struct kunit *test,
+								struct amdgpu_device *adev)
+{
+	struct amdgpu_dm_connector *aconnector;
+
+	KUNIT_ASSERT_EQ(test, drmm_mode_config_init(&adev->ddev), 0);
+
+	aconnector = drmm_kzalloc(&adev->ddev, sizeof(*aconnector), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+	KUNIT_ASSERT_EQ(test,
+			drmm_connector_init(&adev->ddev, &aconnector->base,
+					    &dm_test_connector_funcs,
+					    DRM_MODE_CONNECTOR_DisplayPort, NULL),
+			0);
+
+	return aconnector;
+}
+
+/*
+ * Fake AUX channel that serves an EDID over I2C-over-AUX and answers the
+ * native DPCD reads the compliance test-request path makes.
+ */
+struct dm_test_edid_aux {
+	u8 edid[EDID_LENGTH];
+	u8 offset;
+	u8 test_request;
+	u8 checksum_written;
+	u8 response_written;
+};
+
+static struct dm_test_edid_aux *dm_test_current_edid_aux;
+
+static ssize_t dm_test_edid_aux_transfer(struct drm_dp_aux *aux,
+					 struct drm_dp_aux_msg *msg)
+{
+	struct dm_test_edid_aux *fixture = dm_test_current_edid_aux;
+	u8 *buffer = msg->buffer;
+	size_t len;
+
+	switch (msg->request & ~DP_AUX_I2C_MOT) {
+	case DP_AUX_I2C_WRITE:
+		if (msg->size >= 1)
+			fixture->offset = buffer[0];
+		msg->reply = DP_AUX_I2C_REPLY_ACK;
+		return msg->size;
+	case DP_AUX_I2C_READ:
+		len = min_t(size_t, msg->size, EDID_LENGTH - fixture->offset);
+		memcpy(buffer, fixture->edid + fixture->offset, len);
+		fixture->offset += len;
+		msg->reply = DP_AUX_I2C_REPLY_ACK;
+		return len;
+	case DP_AUX_NATIVE_READ:
+		memset(buffer, 0, msg->size);
+		if (msg->address == DP_TEST_REQUEST && msg->size)
+			buffer[0] = fixture->test_request;
+		msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+		return msg->size;
+	case DP_AUX_NATIVE_WRITE:
+		if (msg->size) {
+			if (msg->address == DP_TEST_EDID_CHECKSUM)
+				fixture->checksum_written = buffer[0];
+			else if (msg->address == DP_TEST_RESPONSE)
+				fixture->response_written = buffer[0];
+		}
+		msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+		return msg->size;
+	}
+
+	msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+	return msg->size;
+}
+
+/* Fake I2C DDC adapter and prepare_ddc hook for the non-AUX EDID read path. */
+
+static int dm_test_ddc_no_sink_xfer(struct i2c_adapter *adapter,
+				    struct i2c_msg *msgs, int num)
+{
+	return -ENXIO;
+}
+
+static const struct i2c_algorithm dm_test_ddc_algorithm = {
+	.master_xfer = dm_test_ddc_no_sink_xfer,
+};
+
+static unsigned int dm_test_prepare_ddc_calls;
+
+static void dm_test_prepare_ddc(struct dc_link *link)
+{
+	dm_test_prepare_ddc_calls++;
+}
+
+struct dm_test_local_edid {
+	struct dm_test_edid_aux *aux;
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_context *ctx;
+	struct dc_link *link;
+	struct dc_sink *sink;
+	struct dc *dc;
+};
+
+static struct dm_test_local_edid dm_test_setup_local_edid(struct kunit *test)
+{
+	struct dm_test_local_edid fixture = {0};
+	struct amdgpu_device *adev;
+	struct dc_edid *dc_edid;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+
+	fixture.aconnector = dm_test_alloc_real_connector(test, adev);
+	fixture.aux = kunit_kzalloc(test, sizeof(*fixture.aux), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, fixture.aux);
+	fixture.ctx = kunit_kzalloc(test, sizeof(*fixture.ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, fixture.ctx);
+	fixture.sink = kunit_kzalloc(test, sizeof(*fixture.sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, fixture.sink);
+	fixture.dc = kunit_kzalloc(test, sizeof(*fixture.dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, fixture.dc);
+	fixture.link = dm_kunit_alloc_link(test);
+	dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc_edid);
+
+	dm_test_fill_base_edid(dc_edid, true);
+	memcpy(fixture.aux->edid, dc_edid->raw_edid, EDID_LENGTH);
+	dm_test_current_edid_aux = fixture.aux;
+
+	fixture.aconnector->dm_dp_aux.aux.drm_dev = &adev->ddev;
+	fixture.aconnector->dm_dp_aux.aux.transfer = dm_test_edid_aux_transfer;
+	drm_dp_aux_init(&fixture.aconnector->dm_dp_aux.aux);
+
+	fixture.link->priv = fixture.aconnector;
+	fixture.link->dc = fixture.dc;
+	fixture.link->aux_mode = true;
+
+	return fixture;
+}
+
+/**
+ * dm_test_read_local_edid_aux_mode - Test reading EDID over the AUX DDC channel
+ * @test: The KUnit test context
+ *
+ * A DP link reads the EDID through the connector's AUX channel, copies it into
+ * the sink and parses the caps.
+ */
+static void dm_test_read_local_edid_aux_mode(struct kunit *test)
+{
+	struct dm_test_local_edid fixture = dm_test_setup_local_edid(test);
+
+	KUNIT_EXPECT_EQ(test,
+			dm_helpers_read_local_edid(fixture.ctx, fixture.link, fixture.sink),
+			EDID_OK);
+	KUNIT_EXPECT_EQ(test, fixture.sink->dc_edid.length, (uint32_t)EDID_LENGTH);
+	KUNIT_EXPECT_EQ(test, fixture.sink->edid_caps.manufacturer_id, 0xAC10);
+	KUNIT_EXPECT_EQ(test, fixture.sink->edid_caps.product_id, 0x1234);
+}
+
+/**
+ * dm_test_read_local_edid_test_request - Test the compliance EDID-read request
+ * @test: The KUnit test context
+ *
+ * When the sink raises the EDID_READ test request, the helper writes back the
+ * EDID checksum and the test response.
+ */
+static void dm_test_read_local_edid_test_request(struct kunit *test)
+{
+	struct dm_test_local_edid fixture = dm_test_setup_local_edid(test);
+	struct dm_test_edid_aux *aux = fixture.aux;
+	union test_response expected_response = {0};
+
+	aux->test_request = DP_TEST_LINK_EDID_READ;
+	expected_response.bits.EDID_CHECKSUM_WRITE = 1;
+
+	KUNIT_EXPECT_EQ(test,
+			dm_helpers_read_local_edid(fixture.ctx, fixture.link, fixture.sink),
+			EDID_OK);
+	KUNIT_EXPECT_EQ(test, aux->checksum_written,
+			aux->edid[EDID_LENGTH - 1]);
+	KUNIT_EXPECT_EQ(test, aux->response_written, expected_response.raw);
+}
+
+/**
+ * dm_test_read_local_edid_i2c_no_response - Test the I2C DDC path with no sink
+ * @test: The KUnit test context
+ *
+ * A non-AUX link reads the EDID over the connector's I2C adapter. With no sink
+ * responding, every retry fails and the helper reports EDID_NO_RESPONSE.
+ */
+static void dm_test_read_local_edid_i2c_no_response(struct kunit *test)
+{
+	struct dm_test_local_edid fixture = dm_test_setup_local_edid(test);
+	struct i2c_adapter *ddc;
+
+	ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ddc);
+	ddc->algo = &dm_test_ddc_algorithm;
+	ddc->lock_ops = &dm_test_i2c_lock_ops;
+	rt_mutex_init(&ddc->bus_lock);
+	rt_mutex_init(&ddc->mux_lock);
+
+	fixture.link->aux_mode = false;
+	fixture.link->ddc_hw_inst = 1;
+	fixture.aconnector->i2c = (struct amdgpu_i2c_adapter *)ddc;
+	fixture.dc->hwss.prepare_ddc = dm_test_prepare_ddc;
+	dm_test_prepare_ddc_calls = 0;
+
+	KUNIT_EXPECT_EQ(test,
+			dm_helpers_read_local_edid(fixture.ctx, fixture.link, fixture.sink),
+			EDID_NO_RESPONSE);
+	KUNIT_EXPECT_EQ(test, dm_test_prepare_ddc_calls, 1U);
+}
+
 static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	/* edid_extract_panel_id */
 	KUNIT_CASE(dm_test_edid_extract_panel_id_basic),
@@ -4283,6 +4510,10 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	KUNIT_CASE(dm_test_dmub_set_config_sync_unknown_error),
 	/* dm_helpers_is_dp_sink_present */
 	KUNIT_CASE(dm_test_is_dp_sink_present_queries_link_service),
+	/* dm_helpers_read_local_edid */
+	KUNIT_CASE(dm_test_read_local_edid_aux_mode),
+	KUNIT_CASE(dm_test_read_local_edid_test_request),
+	KUNIT_CASE(dm_test_read_local_edid_i2c_no_response),
 	{}
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 25/82] drm/amd/display: Test dp_handle_test_pattern_request patterns
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (23 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 24/82] drm/amd/display: Test dm_helpers_read_local_edid IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 26/82] drm/amd/display: Test DMUB reg callbacks IVAN.LIPSKI
                   ` (57 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_helpers_dp_handle_test_pattern_request() covering
the link test pattern mapping, the colour space mapping, the bit depth
change that retimes the stream, and the case where no connector is
available to store the original timing.

[HOW]
Fake link service and clock manager callbacks record the requested
pattern instead of programming the PHY or the clocks.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  | 254 ++++++++++++++++++
 1 file changed, 254 insertions(+)

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
index 3531d52da1591..d4232648d8e62 100644
--- 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
@@ -14,6 +14,7 @@
 
 #include "dc.h"
 #include "core_types.h"
+#include "clk_mgr.h"
 #include "link_service.h"
 #include "amdgpu.h"
 #include "amdgpu_mode.h"
@@ -4283,6 +4284,255 @@ static void dm_test_read_local_edid_test_request(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, aux->response_written, expected_response.raw);
 }
 
+/*
+ * Fake link service and clock manager for the test-pattern request path, which
+ * would otherwise reach the DSC config update, the clock update and the PHY
+ * test pattern programming.
+ */
+struct dm_test_pattern_state {
+	unsigned int dsc_config_calls;
+	unsigned int set_pattern_calls;
+	unsigned int update_clocks_calls;
+	enum dp_test_pattern last_pattern;
+	enum dp_test_pattern_color_space last_color_space;
+};
+
+static struct dm_test_pattern_state dm_test_pattern;
+
+static bool dm_test_link_update_dsc_config(struct pipe_ctx *pipe_ctx)
+{
+	dm_test_pattern.dsc_config_calls++;
+	return true;
+}
+
+static bool dm_test_link_dp_set_test_pattern(struct dc_link *link,
+					     enum dp_test_pattern test_pattern,
+					     enum dp_test_pattern_color_space color_space,
+					     const struct link_training_settings *settings,
+					     const unsigned char *custom_pattern,
+					     unsigned int custom_pattern_size)
+{
+	dm_test_pattern.set_pattern_calls++;
+	dm_test_pattern.last_pattern = test_pattern;
+	dm_test_pattern.last_color_space = color_space;
+	return true;
+}
+
+static void dm_test_update_clocks(struct clk_mgr *clk_mgr,
+				  struct dc_state *context, bool safe_to_lower)
+{
+	dm_test_pattern.update_clocks_calls++;
+}
+
+static struct clk_mgr_funcs dm_test_clk_mgr_funcs = {
+	.update_clocks = dm_test_update_clocks,
+};
+
+struct dm_test_pattern_fixture {
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_context *ctx;
+	struct dc_link *link;
+	struct dc_stream_state *stream;
+};
+
+static struct dm_test_pattern_fixture dm_test_setup_pattern(struct kunit *test)
+{
+	struct dm_test_pattern_fixture fixture = {0};
+	struct clk_bw_params *bw_params;
+	struct amdgpu_device *adev;
+	struct link_service *link_srv;
+	struct clk_mgr *clk_mgr;
+	struct dc_state *state;
+	struct dc *dc;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+	fixture.ctx = kunit_kzalloc(test, sizeof(*fixture.ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, fixture.ctx);
+	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc);
+	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, state);
+	link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link_srv);
+	clk_mgr = kunit_kzalloc(test, sizeof(*clk_mgr), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, clk_mgr);
+	bw_params = kunit_kzalloc(test, sizeof(*bw_params), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, bw_params);
+	fixture.link = dm_kunit_alloc_link(test);
+	fixture.aconnector = dm_kunit_alloc_connector(test, adev, fixture.link);
+	fixture.stream = dm_kunit_alloc_stream(test, fixture.link);
+
+	link_srv->update_dsc_config = dm_test_link_update_dsc_config;
+	link_srv->dp_set_test_pattern = dm_test_link_dp_set_test_pattern;
+	clk_mgr->funcs = &dm_test_clk_mgr_funcs;
+	clk_mgr->bw_params = bw_params;
+
+	dc->link_srv = link_srv;
+	dc->clk_mgr = clk_mgr;
+	dc->current_state = state;
+	fixture.ctx->dc = dc;
+	fixture.link->dc = dc;
+	fixture.link->priv = fixture.aconnector;
+
+	/* the first pipe drives the link under test */
+	state->res_ctx.pipe_ctx[0].stream = fixture.stream;
+
+	dm_test_pattern = (struct dm_test_pattern_state) {0};
+
+	return fixture;
+}
+
+/**
+ * dm_test_dp_handle_test_pattern_patterns - Test the requested pattern mapping
+ * @test: The KUnit test context
+ */
+static void dm_test_dp_handle_test_pattern_patterns(struct kunit *test)
+{
+	static const struct {
+		u8 pattern;
+		u8 dyn_range;
+		enum dp_test_pattern expected;
+	} cases[] = {
+		{ LINK_TEST_PATTERN_COLOR_RAMP, 0, DP_TEST_PATTERN_COLOR_RAMP },
+		{ LINK_TEST_PATTERN_VERTICAL_BARS, 0, DP_TEST_PATTERN_VERTICAL_BARS },
+		{ LINK_TEST_PATTERN_COLOR_SQUARES, TEST_DYN_RANGE_VESA, DP_TEST_PATTERN_COLOR_SQUARES },
+		{ LINK_TEST_PATTERN_COLOR_SQUARES, TEST_DYN_RANGE_CEA,  DP_TEST_PATTERN_COLOR_SQUARES_CEA },
+		/* PATTERN is 2 bits wide, so NONE is the only unhandled value */
+		{ LINK_TEST_PATTERN_NONE, 0, DP_TEST_PATTERN_VIDEO_MODE },
+	};
+	struct dm_test_pattern_fixture fixture = dm_test_setup_pattern(test);
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(cases); i++) {
+		union link_test_pattern test_pattern = {0};
+		union test_misc test_params = {0};
+		bool ret;
+
+		dm_test_pattern = (struct dm_test_pattern_state) {0};
+		test_pattern.bits.PATTERN = cases[i].pattern;
+		test_params.bits.DYN_RANGE = cases[i].dyn_range;
+
+		ret = dm_helpers_dp_handle_test_pattern_request(fixture.ctx, fixture.link,
+								test_pattern, test_params);
+
+		KUNIT_EXPECT_FALSE(test, ret);
+		KUNIT_EXPECT_EQ_MSG(test, (int)dm_test_pattern.last_pattern,
+				    (int)cases[i].expected, "pattern %u", cases[i].pattern);
+		KUNIT_EXPECT_EQ(test, dm_test_pattern.set_pattern_calls, 1U);
+		KUNIT_EXPECT_EQ(test, dm_test_pattern.update_clocks_calls, 1U);
+	}
+}
+
+/**
+ * dm_test_dp_handle_test_pattern_color_spaces - Test the colour space mapping
+ * @test: The KUnit test context
+ */
+static void dm_test_dp_handle_test_pattern_color_spaces(struct kunit *test)
+{
+	static const struct {
+		u8 clr_format;
+		u8 ycbcr_coefs;
+		enum dp_test_pattern_color_space expected;
+	} cases[] = {
+		{ 0, 0, DP_TEST_PATTERN_COLOR_SPACE_RGB },
+		{ 1, 0, DP_TEST_PATTERN_COLOR_SPACE_YCBCR601 },
+		{ 2, 1, DP_TEST_PATTERN_COLOR_SPACE_YCBCR709 },
+	};
+	struct dm_test_pattern_fixture fixture = dm_test_setup_pattern(test);
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(cases); i++) {
+		union link_test_pattern test_pattern = {0};
+		union test_misc test_params = {0};
+
+		dm_test_pattern = (struct dm_test_pattern_state) {0};
+		test_params.bits.CLR_FORMAT = cases[i].clr_format;
+		test_params.bits.YCBCR_COEFS = cases[i].ycbcr_coefs;
+
+		dm_helpers_dp_handle_test_pattern_request(fixture.ctx, fixture.link,
+							  test_pattern, test_params);
+
+		KUNIT_EXPECT_EQ_MSG(test, (int)dm_test_pattern.last_color_space,
+				    (int)cases[i].expected, "format %u", cases[i].clr_format);
+	}
+}
+
+/**
+ * dm_test_dp_handle_test_pattern_timing_change - Test the timing update branch
+ * @test: The KUnit test context
+ *
+ * A requested colour depth or pixel encoding that differs from the current
+ * stream timing reprograms the timing and updates the DSC config.
+ */
+static void dm_test_dp_handle_test_pattern_timing_change(struct kunit *test)
+{
+	static const struct {
+		u8 bpc;
+		u8 clr_format;
+		enum dc_color_depth depth;
+		enum dc_pixel_encoding encoding;
+	} cases[] = {
+		{ 2, 1, COLOR_DEPTH_101010, PIXEL_ENCODING_YCBCR422 },
+		{ 3, 2, COLOR_DEPTH_121212, PIXEL_ENCODING_YCBCR444 },
+	};
+	struct dm_test_pattern_fixture fixture = dm_test_setup_pattern(test);
+	struct dc_crtc_timing requested = {0};
+	unsigned int i;
+
+	fixture.aconnector->timing_requested = &requested;
+
+	for (i = 0; i < ARRAY_SIZE(cases); i++) {
+		union link_test_pattern test_pattern = {0};
+		union test_misc test_params = {0};
+
+		dm_test_pattern = (struct dm_test_pattern_state) {0};
+		fixture.stream->timing.display_color_depth = COLOR_DEPTH_888;
+		fixture.stream->timing.pixel_encoding = PIXEL_ENCODING_RGB;
+		test_params.bits.BPC = cases[i].bpc;
+		test_params.bits.CLR_FORMAT = cases[i].clr_format;
+
+		dm_helpers_dp_handle_test_pattern_request(fixture.ctx, fixture.link,
+							  test_pattern, test_params);
+
+		KUNIT_EXPECT_EQ_MSG(test, (int)fixture.stream->timing.display_color_depth,
+				    (int)cases[i].depth, "bpc %u", cases[i].bpc);
+		KUNIT_EXPECT_EQ_MSG(test, (int)fixture.stream->timing.pixel_encoding,
+				    (int)cases[i].encoding, "format %u", cases[i].clr_format);
+		KUNIT_EXPECT_EQ(test, dm_test_pattern.dsc_config_calls, 1U);
+		KUNIT_EXPECT_TRUE(test, fixture.aconnector->timing_changed);
+		KUNIT_EXPECT_EQ(test, (int)requested.display_color_depth,
+				(int)cases[i].depth);
+	}
+}
+
+/**
+ * dm_test_dp_handle_test_pattern_no_timing_storage - Test the missing storage path
+ * @test: The KUnit test context
+ *
+ * Without a timing_requested buffer the helper still reprograms the timing but
+ * reports that it could not store it.
+ */
+static void dm_test_dp_handle_test_pattern_no_timing_storage(struct kunit *test)
+{
+	struct dm_test_pattern_fixture fixture = dm_test_setup_pattern(test);
+	union link_test_pattern test_pattern = {0};
+	union test_misc test_params = {0};
+
+	fixture.aconnector->timing_requested = NULL;
+	fixture.stream->timing.display_color_depth = COLOR_DEPTH_888;
+
+	/* 6 bpc differs from the current 8 bpc timing */
+	test_params.bits.BPC = 0;
+
+	dm_helpers_dp_handle_test_pattern_request(fixture.ctx, fixture.link,
+						  test_pattern, test_params);
+
+	KUNIT_EXPECT_EQ(test, (int)fixture.stream->timing.display_color_depth,
+			(int)COLOR_DEPTH_666);
+	KUNIT_EXPECT_TRUE(test, fixture.aconnector->timing_changed);
+}
+
 /**
  * dm_test_read_local_edid_i2c_no_response - Test the I2C DDC path with no sink
  * @test: The KUnit test context
@@ -4502,6 +4752,10 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
 	KUNIT_CASE(dm_test_dp_write_dsc_enable_pcon_disable),
 	/* dm_helpers_dp_handle_test_pattern_request */
 	KUNIT_CASE(dm_test_dp_handle_test_pattern_no_pipe),
+	KUNIT_CASE(dm_test_dp_handle_test_pattern_patterns),
+	KUNIT_CASE(dm_test_dp_handle_test_pattern_color_spaces),
+	KUNIT_CASE(dm_test_dp_handle_test_pattern_timing_change),
+	KUNIT_CASE(dm_test_dp_handle_test_pattern_no_timing_storage),
 	/* dm_helpers_submit_i2c_over_aux */
 	KUNIT_CASE(dm_test_submit_i2c_over_aux_unimplemented),
 	/* dm_helpers_allocate_gpu_mem / dm_helpers_free_gpu_mem */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 26/82] drm/amd/display: Test DMUB reg callbacks
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (24 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 25/82] drm/amd/display: Test dp_handle_test_pattern_request patterns IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 27/82] drm/amd/display: Test VBIOS bounding box IVAN.LIPSKI
                   ` (56 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_dmub_reg_read() and
amdgpu_dm_dmub_reg_write(), the register accessors the DMUB service is
created with.

[HOW]
Both accessors reach the hardware through cgs_read_register() and
cgs_write_register(), which dispatch through the ops table at the start
of struct cgs_device. Add a fake CGS device that records the offset and
value of each access, so the callbacks can be driven without real MMIO.

The test writes a value through the write callback and reads it back
through the read callback, confirming both forward to the DC context of
the device passed as their opaque user context.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.c    |  8 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.h    |  2 +
 .../amdgpu_dm/tests/amdgpu_dm_dmub_test.c     | 95 ++++++++++++++++++-
 3 files changed, 101 insertions(+), 4 deletions(-)

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 9ff25bc8ee5ae..29990cfe4265e 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
@@ -468,20 +468,22 @@ enum dmub_ips_disable_type dm_get_default_ips_mode(
 }
 EXPORT_IF_KUNIT(dm_get_default_ips_mode);
 
-static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address)
+STATIC_IFN_KUNIT 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);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_dmub_reg_read);
 
-static void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address,
-				     uint32_t value)
+STATIC_IFN_KUNIT 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);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_dmub_reg_write);
 
 int dm_dmub_sw_init(struct amdgpu_device *adev)
 {
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
index 31d5f8c265c0d..735e2b4299521 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
@@ -73,6 +73,8 @@ struct dmub_cmd_fused_request;
 void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev);
 void abort_fused_io(struct dc_context *ctx,
 		    const struct dmub_cmd_fused_request *request);
+uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address);
+void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address, uint32_t value);
 #endif
 
 #endif /* AMDGPU_DM_AMDGPU_DM_DMUB_H_ */
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
index ead43eeb38c77..4bba282abd69d 100644
--- 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
@@ -1347,6 +1347,86 @@ static void dm_test_abort_fused_io_no_dmub_srv(struct kunit *test)
 	abort_fused_io(ctx, req);
 }
 
+/*
+ * Fake CGS device: the DMUB register callbacks reach the hardware through
+ * cgs_read_register()/cgs_write_register(), which dispatch via the ops table
+ * at the start of struct cgs_device.
+ */
+
+#define DM_TEST_GPINT_STATUS_MASK	0xF0000000U
+
+struct dm_test_cgs_ctx {
+	struct cgs_device dev;
+	u32 last_write;
+	unsigned int write_calls;
+	unsigned int read_calls;
+	unsigned int last_write_offset;
+	unsigned int last_read_offset;
+};
+
+static struct dm_test_cgs_ctx dm_test_cgs;
+
+static uint32_t dm_test_cgs_read_register(struct cgs_device *cgs_device,
+					  unsigned int offset)
+{
+	dm_test_cgs.read_calls++;
+	dm_test_cgs.last_read_offset = offset;
+
+	/* The firmware acks a GPINT by clearing the status nibble. */
+	return dm_test_cgs.last_write & ~DM_TEST_GPINT_STATUS_MASK;
+}
+
+static void dm_test_cgs_write_register(struct cgs_device *cgs_device,
+				       unsigned int offset, uint32_t value)
+{
+	dm_test_cgs.write_calls++;
+	dm_test_cgs.last_write_offset = offset;
+	dm_test_cgs.last_write = value;
+}
+
+static const struct cgs_ops dm_test_cgs_ops = {
+	.read_register = dm_test_cgs_read_register,
+	.write_register = dm_test_cgs_write_register,
+};
+
+static int dm_test_dmub_hw_access_init(struct kunit *test)
+{
+	dm_test_cgs = (struct dm_test_cgs_ctx) { .dev.ops = &dm_test_cgs_ops };
+
+	return 0;
+}
+
+/* Tests for amdgpu_dm_dmub_reg_read() and amdgpu_dm_dmub_reg_write() */
+
+/**
+ * dm_test_dmub_reg_write_then_read - Test the DMUB register access callbacks
+ * @test: The KUnit test context
+ *
+ * The callbacks the DMUB service is created with must forward to the DC
+ * context of the device passed as their opaque user context.
+ */
+static void dm_test_dmub_reg_write_then_read(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test);
+	struct dc_perf_trace *perf_trace;
+
+	perf_trace = kunit_kzalloc(test, sizeof(*perf_trace), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, perf_trace);
+
+	adev->dm.dc->ctx->cgs_device = &dm_test_cgs.dev;
+	adev->dm.dc->ctx->perf_trace = perf_trace;
+
+	amdgpu_dm_dmub_reg_write(adev, 0x1234, 0x0BADF00D);
+
+	KUNIT_EXPECT_EQ(test, dm_test_cgs.write_calls, 1U);
+	KUNIT_EXPECT_EQ(test, dm_test_cgs.last_write_offset, 0x1234U);
+	KUNIT_EXPECT_EQ(test, dm_test_cgs.last_write, 0x0BADF00DU);
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dmub_reg_read(adev, 0x1234), 0x0BADF00DU);
+	KUNIT_EXPECT_EQ(test, dm_test_cgs.read_calls, 1U);
+	KUNIT_EXPECT_EQ(test, dm_test_cgs.last_read_offset, 0x1234U);
+}
+
 static struct kunit_case amdgpu_dm_dmub_tests[] = {
 	/* dm_register_dmub_notify_callback() */
 	KUNIT_CASE(dm_test_register_dmub_notify_callback_null_callback),
@@ -1412,7 +1492,20 @@ static struct kunit_suite amdgpu_dm_dmub_test_suite = {
 	.test_cases = amdgpu_dm_dmub_tests,
 };
 
-kunit_test_suite(amdgpu_dm_dmub_test_suite);
+static struct kunit_case amdgpu_dm_dmub_hw_access_tests[] = {
+	/* amdgpu_dm_dmub_reg_read() and amdgpu_dm_dmub_reg_write() */
+	KUNIT_CASE(dm_test_dmub_reg_write_then_read),
+	{}
+};
+
+static struct kunit_suite amdgpu_dm_dmub_hw_access_test_suite = {
+	.name = "amdgpu_dm_dmub_hw_access",
+	.init = dm_test_dmub_hw_access_init,
+	.test_cases = amdgpu_dm_dmub_hw_access_tests,
+};
+
+kunit_test_suites(&amdgpu_dm_dmub_test_suite,
+		  &amdgpu_dm_dmub_hw_access_test_suite);
 
 MODULE_AUTHOR("AMD");
 MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_dmub");
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 27/82] drm/amd/display: Test VBIOS bounding box
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (25 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 26/82] drm/amd/display: Test DMUB reg callbacks IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 28/82] drm/amd/display: Test dm_init_microcode IVAN.LIPSKI
                   ` (55 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_dmub_get_vbios_bounding_box(), covering the
successful handshake on every ASIC that has a bounding box, the GPU
memory allocation failure path, and both GPINT timeout paths. This also
covers dm_dmub_send_vbios_gpint_command().

[HOW]
Extend the fake CGS device with a fail_on_write hook so a chosen GPINT
write is never acked, which drives the timeout paths without waiting on
real firmware. Reuse the buffer object indirection added for
dm_allocate_gpu_mem() with a fake allocator, since the real path needs a
live TTM device.

The success test walks every ASIC with a bounding box and confirms a
non-zero GART buffer is allocated and that all four address words plus
the copy request are sent. The timeout tests confirm the buffer is
released and the allocation list left empty.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_dmub_test.c     | 187 ++++++++++++++++++
 1 file changed, 187 insertions(+)

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
index 4bba282abd69d..cda048e8297a0 100644
--- 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
@@ -1353,6 +1353,7 @@ static void dm_test_abort_fused_io_no_dmub_srv(struct kunit *test)
  * at the start of struct cgs_device.
  */
 
+#define DM_TEST_GPINT_REG		(0x34c0 + 0x01f8)
 #define DM_TEST_GPINT_STATUS_MASK	0xF0000000U
 
 struct dm_test_cgs_ctx {
@@ -1362,6 +1363,8 @@ struct dm_test_cgs_ctx {
 	unsigned int read_calls;
 	unsigned int last_write_offset;
 	unsigned int last_read_offset;
+	/* 1-based index of the write that must never be acked; 0 acks all */
+	unsigned int fail_on_write;
 };
 
 static struct dm_test_cgs_ctx dm_test_cgs;
@@ -1372,6 +1375,9 @@ static uint32_t dm_test_cgs_read_register(struct cgs_device *cgs_device,
 	dm_test_cgs.read_calls++;
 	dm_test_cgs.last_read_offset = offset;
 
+	if (dm_test_cgs.fail_on_write == dm_test_cgs.write_calls)
+		return ~dm_test_cgs.last_write;
+
 	/* The firmware acks a GPINT by clearing the status nibble. */
 	return dm_test_cgs.last_write & ~DM_TEST_GPINT_STATUS_MASK;
 }
@@ -1389,13 +1395,89 @@ static const struct cgs_ops dm_test_cgs_ops = {
 	.write_register = dm_test_cgs_write_register,
 };
 
+/*
+ * Fake buffer object allocator: amdgpu_bo_create_kernel() and
+ * amdgpu_bo_free_kernel() need a live TTM device, so dm_allocate_gpu_mem() is
+ * routed through this fake.
+ */
+
+#define DM_TEST_FAKE_GPU_ADDR	0x1234ABCD0000ULL
+
+struct dm_test_bo_ctx {
+	void *cpu_ptr;
+	int create_ret;
+	unsigned int create_calls;
+	unsigned int free_calls;
+	unsigned long create_size;
+	u32 create_domain;
+};
+
+static struct dm_test_bo_ctx dm_test_bo;
+
+static int dm_test_bo_create_kernel(struct amdgpu_device *adev, unsigned long size,
+				    int align, u32 domain, struct amdgpu_bo **bo_ptr,
+				    u64 *gpu_addr, void **cpu_addr)
+{
+	dm_test_bo.create_calls++;
+	dm_test_bo.create_size = size;
+	dm_test_bo.create_domain = domain;
+
+	if (dm_test_bo.create_ret)
+		return dm_test_bo.create_ret;
+
+	*gpu_addr = DM_TEST_FAKE_GPU_ADDR;
+	*cpu_addr = dm_test_bo.cpu_ptr;
+
+	return 0;
+}
+
+static void dm_test_bo_free_kernel(struct amdgpu_bo **bo, u64 *gpu_addr, void **cpu_addr)
+{
+	dm_test_bo.free_calls++;
+
+	*bo = NULL;
+	*gpu_addr = 0;
+	*cpu_addr = NULL;
+}
+
+static const struct amdgpu_dm_services_kunit_ops dm_test_services_ops = {
+	.bo_create_kernel = dm_test_bo_create_kernel,
+	.bo_free_kernel = dm_test_bo_free_kernel,
+};
+
 static int dm_test_dmub_hw_access_init(struct kunit *test)
 {
+	void *cpu_ptr;
+
+	cpu_ptr = kunit_kzalloc(test, PAGE_SIZE, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cpu_ptr);
+
+	dm_test_bo = (struct dm_test_bo_ctx) { .cpu_ptr = cpu_ptr };
 	dm_test_cgs = (struct dm_test_cgs_ctx) { .dev.ops = &dm_test_cgs_ops };
 
+	amdgpu_dm_services_kunit_set_ops(&dm_test_services_ops);
+
 	return 0;
 }
 
+static void dm_test_dmub_hw_access_exit(struct kunit *test)
+{
+	amdgpu_dm_services_kunit_set_ops(NULL);
+}
+
+static struct amdgpu_device *dm_test_alloc_adev_with_cgs(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.cgs_device = &dm_test_cgs.dev;
+	INIT_LIST_HEAD(&adev->dm.da_list);
+
+	return adev;
+}
+
 /* Tests for amdgpu_dm_dmub_reg_read() and amdgpu_dm_dmub_reg_write() */
 
 /**
@@ -1427,6 +1509,105 @@ static void dm_test_dmub_reg_write_then_read(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, dm_test_cgs.last_read_offset, 0x1234U);
 }
 
+/* Tests for dm_dmub_get_vbios_bounding_box() */
+
+/**
+ * dm_test_dmub_get_vbios_bounding_box_success - Test the full bounding box handshake
+ * @test: The KUnit test context
+ *
+ * Every ASIC with a bounding box allocates a non-zero GART buffer, then sends
+ * and gets an ack for the four address words and the copy request.
+ */
+static void dm_test_dmub_get_vbios_bounding_box_success(struct kunit *test)
+{
+	static const u32 ip_versions[] = {
+		IP_VERSION(4, 0, 1),
+		IP_VERSION(4, 2, 0),
+		IP_VERSION(4, 2, 1),
+		IP_VERSION(6, 0, 0),
+	};
+	struct amdgpu_device *adev = dm_test_alloc_adev_with_cgs(test);
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ip_versions); i++) {
+		void *bb;
+
+		adev->ip_versions[DCE_HWIP][0] = ip_versions[i];
+
+		bb = dm_dmub_get_vbios_bounding_box(adev);
+
+		KUNIT_EXPECT_PTR_EQ_MSG(test, bb, dm_test_bo.cpu_ptr,
+					"IP version 0x%08x", ip_versions[i]);
+		KUNIT_EXPECT_GT_MSG(test, dm_test_bo.create_size, 0UL,
+				    "IP version 0x%08x", ip_versions[i]);
+		KUNIT_EXPECT_EQ(test, dm_test_bo.create_domain,
+				(u32)AMDGPU_GEM_DOMAIN_GTT);
+		KUNIT_EXPECT_EQ(test, dm_test_cgs.last_write_offset,
+				(unsigned int)DM_TEST_GPINT_REG);
+		/* Four address words plus the copy request per ASIC. */
+		KUNIT_EXPECT_EQ(test, dm_test_cgs.write_calls, 5 * (i + 1));
+
+		dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, bb);
+	}
+}
+
+/**
+ * dm_test_dmub_get_vbios_bounding_box_alloc_fails - Test the allocation failure path
+ * @test: The KUnit test context
+ *
+ * When the GPU memory allocation fails, no GPINT command is sent.
+ */
+static void dm_test_dmub_get_vbios_bounding_box_alloc_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev_with_cgs(test);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(6, 0, 0);
+	dm_test_bo.create_ret = -ENOMEM;
+
+	KUNIT_EXPECT_NULL(test, dm_dmub_get_vbios_bounding_box(adev));
+	KUNIT_EXPECT_EQ(test, dm_test_cgs.write_calls, 0U);
+}
+
+/**
+ * dm_test_dmub_get_vbios_bounding_box_addr_timeout - Test an unacked address word
+ * @test: The KUnit test context
+ *
+ * When the firmware never acks the first address word, the handshake stops
+ * there and the buffer is released.
+ */
+static void dm_test_dmub_get_vbios_bounding_box_addr_timeout(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev_with_cgs(test);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(6, 0, 0);
+	dm_test_cgs.fail_on_write = 1;
+
+	KUNIT_EXPECT_NULL(test, dm_dmub_get_vbios_bounding_box(adev));
+	KUNIT_EXPECT_EQ(test, dm_test_cgs.write_calls, 1U);
+	KUNIT_EXPECT_EQ(test, dm_test_bo.free_calls, 1U);
+	KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.da_list));
+}
+
+/**
+ * dm_test_dmub_get_vbios_bounding_box_copy_timeout - Test an unacked copy request
+ * @test: The KUnit test context
+ *
+ * When all four address words are acked but the copy request is not, the
+ * buffer is released and no bounding box is returned.
+ */
+static void dm_test_dmub_get_vbios_bounding_box_copy_timeout(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev_with_cgs(test);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(6, 0, 0);
+	dm_test_cgs.fail_on_write = 5;
+
+	KUNIT_EXPECT_NULL(test, dm_dmub_get_vbios_bounding_box(adev));
+	KUNIT_EXPECT_EQ(test, dm_test_cgs.write_calls, 5U);
+	KUNIT_EXPECT_EQ(test, dm_test_bo.free_calls, 1U);
+	KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.da_list));
+}
+
 static struct kunit_case amdgpu_dm_dmub_tests[] = {
 	/* dm_register_dmub_notify_callback() */
 	KUNIT_CASE(dm_test_register_dmub_notify_callback_null_callback),
@@ -1495,12 +1676,18 @@ static struct kunit_suite amdgpu_dm_dmub_test_suite = {
 static struct kunit_case amdgpu_dm_dmub_hw_access_tests[] = {
 	/* amdgpu_dm_dmub_reg_read() and amdgpu_dm_dmub_reg_write() */
 	KUNIT_CASE(dm_test_dmub_reg_write_then_read),
+	/* dm_dmub_get_vbios_bounding_box() */
+	KUNIT_CASE(dm_test_dmub_get_vbios_bounding_box_success),
+	KUNIT_CASE(dm_test_dmub_get_vbios_bounding_box_alloc_fails),
+	KUNIT_CASE(dm_test_dmub_get_vbios_bounding_box_addr_timeout),
+	KUNIT_CASE(dm_test_dmub_get_vbios_bounding_box_copy_timeout),
 	{}
 };
 
 static struct kunit_suite amdgpu_dm_dmub_hw_access_test_suite = {
 	.name = "amdgpu_dm_dmub_hw_access",
 	.init = dm_test_dmub_hw_access_init,
+	.exit = dm_test_dmub_hw_access_exit,
 	.test_cases = amdgpu_dm_dmub_hw_access_tests,
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 28/82] drm/amd/display: Test dm_init_microcode
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (26 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 27/82] drm/amd/display: Test VBIOS bounding box IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 29/82] drm/amd/display: Test dm_dmub_sw_init IVAN.LIPSKI
                   ` (54 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_init_microcode(), covering the ASIC to firmware
name mapping for all 21 supported IP versions and the firmware request
failure path.

[HOW]
The real amdgpu_ucode_request() reaches the firmware loader, so add a
KUnit-only indirection table for it. The default table entry points at
the existing function, so no test-only wrapper is introduced, and the
prototype is mirrored verbatim so an upstream change fails the build at
the default initializer rather than diverging silently.

Tests install a fake that records the formatted firmware name, letting a
single table-driven case assert the name chosen for every ASIC,
including the Green Sardine revision check and the Sienna Cichlid versus
Navy Flounder split on the graphics IP version.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.c    |  26 +++-
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.h    |   9 ++
 .../amdgpu_dm/tests/amdgpu_dm_dmub_test.c     | 115 ++++++++++++++++++
 3 files changed, 148 insertions(+), 2 deletions(-)

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 29990cfe4265e..2b34d5c36b48d 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
@@ -65,6 +65,28 @@ MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB);
 MODULE_FIRMWARE(FIRMWARE_DCN_42B_DMUB);
 MODULE_FIRMWARE(FIRMWARE_DCN_60_DMUB);
 
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+static const struct amdgpu_dm_dmub_kunit_ops amdgpu_dm_dmub_default_ops = {
+	.ucode_request = amdgpu_ucode_request,
+};
+
+static const struct amdgpu_dm_dmub_kunit_ops *amdgpu_dm_dmub_ops =
+	&amdgpu_dm_dmub_default_ops;
+
+void amdgpu_dm_dmub_kunit_set_ops(const struct amdgpu_dm_dmub_kunit_ops *ops)
+{
+	amdgpu_dm_dmub_ops = ops ? ops : &amdgpu_dm_dmub_default_ops;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_dmub_kunit_set_ops);
+
+#define dmub_ucode_request	amdgpu_dm_dmub_ops->ucode_request
+
+#else
+
+#define dmub_ucode_request	amdgpu_ucode_request
+
+#endif
+
 /**
  * dm_dmub_aux_setconfig_callback - Callback for AUX or SET_CONFIG command.
  * @adev: amdgpu_device pointer
@@ -768,8 +790,8 @@ int dm_init_microcode(struct amdgpu_device *adev)
 		/* ASIC doesn't support DMUB. */
 		return 0;
 	}
-	r = amdgpu_ucode_request(adev, &adev->dm.dmub_fw, AMDGPU_UCODE_REQUIRED,
-				 "%s", fw_name_dmub);
+	r = dmub_ucode_request(adev, &adev->dm.dmub_fw, AMDGPU_UCODE_REQUIRED,
+			       "%s", fw_name_dmub);
 	return r;
 }
 EXPORT_IF_KUNIT(dm_init_microcode);
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
index 735e2b4299521..d178e1bf4dd6b 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
@@ -75,6 +75,15 @@ void abort_fused_io(struct dc_context *ctx,
 		    const struct dmub_cmd_fused_request *request);
 uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address);
 void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address, uint32_t value);
+
+/* Signatures are verbatim copies so an upstream change breaks the default ops. */
+struct amdgpu_dm_dmub_kunit_ops {
+	__printf(4, 5)
+	int (*ucode_request)(struct amdgpu_device *adev, const struct firmware **fw,
+			     enum amdgpu_ucode_required required, const char *fmt, ...);
+};
+
+void amdgpu_dm_dmub_kunit_set_ops(const struct amdgpu_dm_dmub_kunit_ops *ops);
 #endif
 
 #endif /* AMDGPU_DM_AMDGPU_DM_DMUB_H_ */
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
index cda048e8297a0..fa81413be05f5 100644
--- 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
@@ -14,6 +14,7 @@
 #include "dc/inc/hw/abm.h"
 #include "amdgpu_mode.h"
 #include "amdgpu_dm.h"
+#include "dal_asic_id.h"
 #include "dm_services.h"
 #include "dmub/dmub_srv.h"
 #include "amdgpu_dm_dmub.h"
@@ -1445,6 +1446,32 @@ static const struct amdgpu_dm_services_kunit_ops dm_test_services_ops = {
 	.bo_free_kernel = dm_test_bo_free_kernel,
 };
 
+/* Fake firmware loader, records the firmware name the ASIC switch selected. */
+
+static char dm_test_ucode_name[64];
+static unsigned int dm_test_ucode_calls;
+static int dm_test_ucode_ret;
+
+static __printf(4, 5) int dm_test_ucode_request(struct amdgpu_device *adev,
+						const struct firmware **fw,
+						enum amdgpu_ucode_required required,
+						const char *fmt, ...)
+{
+	va_list args;
+
+	dm_test_ucode_calls++;
+
+	va_start(args, fmt);
+	vsnprintf(dm_test_ucode_name, sizeof(dm_test_ucode_name), fmt, args);
+	va_end(args);
+
+	return dm_test_ucode_ret;
+}
+
+static const struct amdgpu_dm_dmub_kunit_ops dm_test_dmub_ops = {
+	.ucode_request = dm_test_ucode_request,
+};
+
 static int dm_test_dmub_hw_access_init(struct kunit *test)
 {
 	void *cpu_ptr;
@@ -1454,8 +1481,12 @@ static int dm_test_dmub_hw_access_init(struct kunit *test)
 
 	dm_test_bo = (struct dm_test_bo_ctx) { .cpu_ptr = cpu_ptr };
 	dm_test_cgs = (struct dm_test_cgs_ctx) { .dev.ops = &dm_test_cgs_ops };
+	dm_test_ucode_name[0] = '\0';
+	dm_test_ucode_calls = 0;
+	dm_test_ucode_ret = 0;
 
 	amdgpu_dm_services_kunit_set_ops(&dm_test_services_ops);
+	amdgpu_dm_dmub_kunit_set_ops(&dm_test_dmub_ops);
 
 	return 0;
 }
@@ -1463,6 +1494,7 @@ static int dm_test_dmub_hw_access_init(struct kunit *test)
 static void dm_test_dmub_hw_access_exit(struct kunit *test)
 {
 	amdgpu_dm_services_kunit_set_ops(NULL);
+	amdgpu_dm_dmub_kunit_set_ops(NULL);
 }
 
 static struct amdgpu_device *dm_test_alloc_adev_with_cgs(struct kunit *test)
@@ -1608,6 +1640,86 @@ static void dm_test_dmub_get_vbios_bounding_box_copy_timeout(struct kunit *test)
 	KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.da_list));
 }
 
+/* Tests for dm_init_microcode() */
+
+struct dm_test_ucode_case {
+	u32 dce_version;
+	u32 gc_version;
+	u32 external_rev_id;
+	const char *fw_name;
+};
+
+static const struct dm_test_ucode_case dm_test_ucode_cases[] = {
+	{ IP_VERSION(2, 1, 0), 0, 0, FIRMWARE_RENOIR_DMUB },
+	{ IP_VERSION(2, 1, 0), 0, GREEN_SARDINE_A0, FIRMWARE_GREEN_SARDINE_DMUB },
+	{ IP_VERSION(3, 0, 0), IP_VERSION(10, 3, 0), 0, FIRMWARE_SIENNA_CICHLID_DMUB },
+	{ IP_VERSION(3, 0, 0), IP_VERSION(10, 3, 2), 0, FIRMWARE_NAVY_FLOUNDER_DMUB },
+	{ IP_VERSION(3, 0, 1), 0, 0, FIRMWARE_VANGOGH_DMUB },
+	{ IP_VERSION(3, 0, 2), 0, 0, FIRMWARE_DIMGREY_CAVEFISH_DMUB },
+	{ IP_VERSION(3, 0, 3), 0, 0, FIRMWARE_BEIGE_GOBY_DMUB },
+	{ IP_VERSION(3, 1, 2), 0, 0, FIRMWARE_YELLOW_CARP_DMUB },
+	{ IP_VERSION(3, 1, 3), 0, 0, FIRMWARE_YELLOW_CARP_DMUB },
+	{ IP_VERSION(3, 1, 4), 0, 0, FIRMWARE_DCN_314_DMUB },
+	{ IP_VERSION(3, 1, 5), 0, 0, FIRMWARE_DCN_315_DMUB },
+	{ IP_VERSION(3, 1, 6), 0, 0, FIRMWARE_DCN316_DMUB },
+	{ IP_VERSION(3, 2, 0), 0, 0, FIRMWARE_DCN_V3_2_0_DMCUB },
+	{ IP_VERSION(3, 2, 1), 0, 0, FIRMWARE_DCN_V3_2_1_DMCUB },
+	{ IP_VERSION(3, 5, 0), 0, 0, FIRMWARE_DCN_35_DMUB },
+	{ IP_VERSION(3, 5, 1), 0, 0, FIRMWARE_DCN_351_DMUB },
+	{ IP_VERSION(3, 6, 0), 0, 0, FIRMWARE_DCN_36_DMUB },
+	{ IP_VERSION(4, 0, 1), 0, 0, FIRMWARE_DCN_401_DMUB },
+	{ IP_VERSION(4, 2, 0), 0, 0, FIRMWARE_DCN_42_DMUB },
+	{ IP_VERSION(4, 2, 1), 0, 0, FIRMWARE_DCN_42B_DMUB },
+	{ IP_VERSION(6, 0, 0), 0, 0, FIRMWARE_DCN_60_DMUB },
+};
+
+/**
+ * dm_test_init_microcode_fw_names - Test the ASIC to firmware name mapping
+ * @test: The KUnit test context
+ */
+static void dm_test_init_microcode_fw_names(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	unsigned int i;
+
+	adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+	for (i = 0; i < ARRAY_SIZE(dm_test_ucode_cases); i++) {
+		const struct dm_test_ucode_case *c = &dm_test_ucode_cases[i];
+
+		adev->ip_versions[DCE_HWIP][0] = c->dce_version;
+		adev->ip_versions[GC_HWIP][0] = c->gc_version;
+		adev->external_rev_id = c->external_rev_id;
+		dm_test_ucode_name[0] = '\0';
+
+		KUNIT_EXPECT_EQ_MSG(test, dm_init_microcode(adev), 0,
+				    "IP version 0x%08x", c->dce_version);
+		KUNIT_EXPECT_STREQ_MSG(test, dm_test_ucode_name, c->fw_name,
+				       "IP version 0x%08x", c->dce_version);
+	}
+
+	KUNIT_EXPECT_EQ(test, dm_test_ucode_calls,
+			(unsigned int)ARRAY_SIZE(dm_test_ucode_cases));
+}
+
+/**
+ * dm_test_init_microcode_request_fails - Test a firmware request failure
+ * @test: The KUnit test context
+ */
+static void dm_test_init_microcode_request_fails(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);
+	dm_test_ucode_ret = -ENOENT;
+
+	KUNIT_EXPECT_EQ(test, dm_init_microcode(adev), -ENOENT);
+}
+
 static struct kunit_case amdgpu_dm_dmub_tests[] = {
 	/* dm_register_dmub_notify_callback() */
 	KUNIT_CASE(dm_test_register_dmub_notify_callback_null_callback),
@@ -1681,6 +1793,9 @@ static struct kunit_case amdgpu_dm_dmub_hw_access_tests[] = {
 	KUNIT_CASE(dm_test_dmub_get_vbios_bounding_box_alloc_fails),
 	KUNIT_CASE(dm_test_dmub_get_vbios_bounding_box_addr_timeout),
 	KUNIT_CASE(dm_test_dmub_get_vbios_bounding_box_copy_timeout),
+	/* dm_init_microcode() */
+	KUNIT_CASE(dm_test_init_microcode_fw_names),
+	KUNIT_CASE(dm_test_init_microcode_request_fails),
 	{}
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 29/82] drm/amd/display: Test dm_dmub_sw_init
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (27 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 28/82] drm/amd/display: Test dm_init_microcode IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 30/82] drm/amd/display: Add hook to disable alt-ch in PMO IVAN.LIPSKI
                   ` (53 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_dmub_sw_init(), covering the ASIC to DMUB service
mapping for all 19 supported IP versions, the memory domain choice, a
complete successful init, the BSS data region variant and the PSP
firmware load registration.

[HOW]
The framebuffer allocation needs a live TTM device, so add
amdgpu_bo_create_kernel() to the indirection table introduced for the
firmware request. The ASIC mapping and memory domain tests force that
allocation to fail, which walks every ASIC through DMUB service creation
and region calculation without a fake framebuffer.

Grow the fake firmware so its instruction constant region is larger than
two PSP footers. The footer probing in
dmub_srv_get_fw_meta_info_from_raw_fw() walks back from the end of that
region twice, so a region smaller than the footers underflows the size
and reads out of bounds.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.c    |  13 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.h    |   4 +
 .../amdgpu_dm/tests/amdgpu_dm_dmub_test.c     | 185 +++++++++++++++++-
 3 files changed, 193 insertions(+), 9 deletions(-)

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 2b34d5c36b48d..f4b0ced7d391c 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
@@ -67,6 +67,7 @@ MODULE_FIRMWARE(FIRMWARE_DCN_60_DMUB);
 
 #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
 static const struct amdgpu_dm_dmub_kunit_ops amdgpu_dm_dmub_default_ops = {
+	.bo_create_kernel = amdgpu_bo_create_kernel,
 	.ucode_request = amdgpu_ucode_request,
 };
 
@@ -79,10 +80,12 @@ void amdgpu_dm_dmub_kunit_set_ops(const struct amdgpu_dm_dmub_kunit_ops *ops)
 }
 EXPORT_IF_KUNIT(amdgpu_dm_dmub_kunit_set_ops);
 
+#define dmub_bo_create_kernel	amdgpu_dm_dmub_ops->bo_create_kernel
 #define dmub_ucode_request	amdgpu_dm_dmub_ops->ucode_request
 
 #else
 
+#define dmub_bo_create_kernel	amdgpu_bo_create_kernel
 #define dmub_ucode_request	amdgpu_ucode_request
 
 #endif
@@ -683,11 +686,11 @@ int dm_dmub_sw_init(struct amdgpu_device *adev)
 	 * 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,
-				    mem_domain,
-				    &adev->dm.dmub_bo,
-				    &adev->dm.dmub_bo_gpu_addr,
-				    &adev->dm.dmub_bo_cpu_addr);
+	r = dmub_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE,
+				  mem_domain,
+				  &adev->dm.dmub_bo,
+				  &adev->dm.dmub_bo_gpu_addr,
+				  &adev->dm.dmub_bo_cpu_addr);
 	if (r)
 		return r;
 
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
index d178e1bf4dd6b..df3e776c1ad14 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
@@ -78,6 +78,10 @@ void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address, uint32_t value);
 
 /* Signatures are verbatim copies so an upstream change breaks the default ops. */
 struct amdgpu_dm_dmub_kunit_ops {
+	int (*bo_create_kernel)(struct amdgpu_device *adev,
+				unsigned long size, int align,
+				u32 domain, struct amdgpu_bo **bo_ptr,
+				u64 *gpu_addr, void **cpu_addr);
 	__printf(4, 5)
 	int (*ucode_request)(struct amdgpu_device *adev, const struct firmware **fw,
 			     enum amdgpu_ucode_required required, const char *fmt, ...);
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
index fa81413be05f5..1e499acbcb908 100644
--- 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
@@ -19,7 +19,13 @@
 #include "dmub/dmub_srv.h"
 #include "amdgpu_dm_dmub.h"
 
-#define DM_TEST_FW_SIZE	512
+/*
+ * The PSP footer probing in dmub_srv_get_fw_meta_info_from_raw_fw() walks back
+ * from the end of the instruction constant region twice, so the fake firmware
+ * needs an instruction constant region larger than two footers.
+ */
+#define DM_TEST_FW_INST_CONST_BYTES	(PSP_HEADER_BYTES_256 + 1024)
+#define DM_TEST_FW_SIZE			(DM_TEST_FW_INST_CONST_BYTES + 1024)
 
 /* Tests for dm_register_dmub_notify_callback() */
 
@@ -112,7 +118,7 @@ static const struct firmware *dm_test_alloc_dmub_fw(struct kunit *test)
 	hdr = (struct dmcub_firmware_header_v1_0 *)data;
 	hdr->header.ucode_array_offset_bytes = cpu_to_le32(0);
 	hdr->header.ucode_version = cpu_to_le32(DMUB_FW_VERSION(9, 9, 9));
-	hdr->inst_const_bytes = cpu_to_le32(PSP_HEADER_BYTES_256);
+	hdr->inst_const_bytes = cpu_to_le32(DM_TEST_FW_INST_CONST_BYTES);
 	hdr->bss_data_bytes = cpu_to_le32(0);
 
 	fw->size = DM_TEST_FW_SIZE;
@@ -1398,8 +1404,8 @@ static const struct cgs_ops dm_test_cgs_ops = {
 
 /*
  * Fake buffer object allocator: amdgpu_bo_create_kernel() and
- * amdgpu_bo_free_kernel() need a live TTM device, so dm_allocate_gpu_mem() is
- * routed through this fake.
+ * amdgpu_bo_free_kernel() need a live TTM device, so both the DMUB framebuffer
+ * allocation and dm_allocate_gpu_mem() are routed through this fake.
  */
 
 #define DM_TEST_FAKE_GPU_ADDR	0x1234ABCD0000ULL
@@ -1469,6 +1475,7 @@ static __printf(4, 5) int dm_test_ucode_request(struct amdgpu_device *adev,
 }
 
 static const struct amdgpu_dm_dmub_kunit_ops dm_test_dmub_ops = {
+	.bo_create_kernel = dm_test_bo_create_kernel,
 	.ucode_request = dm_test_ucode_request,
 };
 
@@ -1720,6 +1727,170 @@ static void dm_test_init_microcode_request_fails(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, dm_init_microcode(adev), -ENOENT);
 }
 
+/* Tests for dm_dmub_sw_init() */
+
+static struct amdgpu_device *dm_test_alloc_adev_for_sw_init(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev_with_cgs(test);
+
+	adev->dm.dmub_fw = dm_test_alloc_dmub_fw(test);
+	adev->bios = kunit_kzalloc(test, 4, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->bios);
+	adev->bios_size = 4;
+
+	return adev;
+}
+
+static void dm_test_free_sw_init(struct amdgpu_device *adev)
+{
+	kfree(adev->dm.dmub_srv);
+	kfree(adev->dm.dmub_fb_info);
+	adev->dm.dmub_srv = NULL;
+	adev->dm.dmub_fb_info = NULL;
+}
+
+/**
+ * dm_test_dmub_sw_init_asic_mapping - Test the ASIC to DMUB service mapping
+ * @test: The KUnit test context
+ *
+ * Every supported ASIC must get past DMUB service creation and region
+ * calculation. The framebuffer allocation is forced to fail so the tests stop
+ * before the memory layout is rebased onto a fake buffer.
+ */
+static void dm_test_dmub_sw_init_asic_mapping(struct kunit *test)
+{
+	static const u32 ip_versions[] = {
+		IP_VERSION(2, 1, 0), IP_VERSION(3, 0, 0), IP_VERSION(3, 0, 1),
+		IP_VERSION(3, 0, 2), IP_VERSION(3, 0, 3), IP_VERSION(3, 1, 2),
+		IP_VERSION(3, 1, 3), IP_VERSION(3, 1, 4), IP_VERSION(3, 1, 5),
+		IP_VERSION(3, 1, 6), IP_VERSION(3, 2, 0), IP_VERSION(3, 2, 1),
+		IP_VERSION(3, 5, 0), IP_VERSION(3, 5, 1), IP_VERSION(3, 6, 0),
+		IP_VERSION(4, 0, 1), IP_VERSION(4, 2, 0), IP_VERSION(4, 2, 1),
+		IP_VERSION(6, 0, 0),
+	};
+	struct amdgpu_device *adev = dm_test_alloc_adev_for_sw_init(test);
+	unsigned int i;
+
+	dm_test_bo.create_ret = -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(ip_versions); i++) {
+		adev->ip_versions[DCE_HWIP][0] = ip_versions[i];
+
+		KUNIT_EXPECT_EQ_MSG(test, dm_dmub_sw_init(adev), -ENOMEM,
+				    "IP version 0x%08x", ip_versions[i]);
+
+		dm_test_free_sw_init(adev);
+	}
+
+	KUNIT_EXPECT_EQ(test, dm_test_bo.create_calls, (unsigned int)ARRAY_SIZE(ip_versions));
+}
+
+/**
+ * dm_test_dmub_sw_init_gtt_only_asic - Test the GTT-only memory domain
+ * @test: The KUnit test context
+ *
+ * DCN32 and DCN321 keep the DMUB framebuffer in GTT; every other ASIC also
+ * allows VRAM.
+ */
+static void dm_test_dmub_sw_init_gtt_only_asic(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev_for_sw_init(test);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 2, 0);
+	dm_test_bo.create_ret = -ENOMEM;
+
+	KUNIT_EXPECT_EQ(test, dm_dmub_sw_init(adev), -ENOMEM);
+	KUNIT_EXPECT_EQ(test, dm_test_bo.create_domain, (u32)AMDGPU_GEM_DOMAIN_GTT);
+
+	dm_test_free_sw_init(adev);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0);
+
+	KUNIT_EXPECT_EQ(test, dm_dmub_sw_init(adev), -ENOMEM);
+	KUNIT_EXPECT_EQ(test, dm_test_bo.create_domain,
+			(u32)(AMDGPU_GEM_DOMAIN_GTT | AMDGPU_GEM_DOMAIN_VRAM));
+
+	dm_test_free_sw_init(adev);
+}
+
+/**
+ * dm_test_dmub_sw_init_success - Test a complete software init
+ * @test: The KUnit test context
+ *
+ * With a fake framebuffer allocator and a fake CGS device, software init
+ * should publish the framebuffer info, the bounding box and the instruction
+ * constant size.
+ */
+static void dm_test_dmub_sw_init_success(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev_for_sw_init(test);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(6, 0, 0);
+
+	KUNIT_EXPECT_EQ(test, dm_dmub_sw_init(adev), 0);
+	KUNIT_EXPECT_NOT_NULL(test, adev->dm.dmub_srv);
+	KUNIT_EXPECT_NOT_NULL(test, adev->dm.dmub_fb_info);
+	KUNIT_EXPECT_NOT_NULL(test, adev->dm.bb_from_dmub);
+	KUNIT_EXPECT_EQ(test, adev->dm.dmcub_fw_version, DMUB_FW_VERSION(9, 9, 9));
+	/* Meta info lookup fails on the fake firmware and trims a PSP footer. */
+	KUNIT_EXPECT_EQ(test, adev->dm.fw_inst_size,
+			(u32)(DM_TEST_FW_INST_CONST_BYTES - PSP_HEADER_BYTES_256 -
+			      PSP_FOOTER_BYTES_256));
+
+	dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, adev->dm.bb_from_dmub);
+	dm_test_free_sw_init(adev);
+}
+
+/**
+ * dm_test_dmub_sw_init_bss_data - Test software init with a BSS data region
+ * @test: The KUnit test context
+ *
+ * A non-zero BSS data size makes software init point the firmware meta info
+ * lookup at the legacy metadata region instead of the instruction constants.
+ */
+static void dm_test_dmub_sw_init_bss_data(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev_for_sw_init(test);
+	struct dmcub_firmware_header_v1_0 *hdr;
+
+	hdr = (struct dmcub_firmware_header_v1_0 *)adev->dm.dmub_fw->data;
+	hdr->bss_data_bytes = cpu_to_le32(512);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(6, 0, 0);
+
+	KUNIT_EXPECT_EQ(test, dm_dmub_sw_init(adev), 0);
+	KUNIT_EXPECT_NOT_NULL(test, adev->dm.dmub_fb_info);
+
+	dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, adev->dm.bb_from_dmub);
+	dm_test_free_sw_init(adev);
+}
+
+/**
+ * dm_test_dmub_sw_init_psp_load - Test the PSP firmware load registration
+ * @test: The KUnit test context
+ *
+ * When the firmware is loaded by the PSP, software init must register the
+ * DMCUB microcode with the AMDGPU firmware loader.
+ */
+static void dm_test_dmub_sw_init_psp_load(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_adev_for_sw_init(test);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(6, 0, 0);
+	adev->firmware.load_type = AMDGPU_FW_LOAD_PSP;
+
+	KUNIT_EXPECT_EQ(test, dm_dmub_sw_init(adev), 0);
+	KUNIT_EXPECT_EQ(test, adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].ucode_id,
+			AMDGPU_UCODE_ID_DMCUB);
+	KUNIT_EXPECT_PTR_EQ(test, adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].fw,
+			    adev->dm.dmub_fw);
+	KUNIT_EXPECT_EQ(test, adev->firmware.fw_size,
+			(u32)ALIGN(DM_TEST_FW_INST_CONST_BYTES, PAGE_SIZE));
+
+	dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, adev->dm.bb_from_dmub);
+	dm_test_free_sw_init(adev);
+}
+
 static struct kunit_case amdgpu_dm_dmub_tests[] = {
 	/* dm_register_dmub_notify_callback() */
 	KUNIT_CASE(dm_test_register_dmub_notify_callback_null_callback),
@@ -1796,6 +1967,12 @@ static struct kunit_case amdgpu_dm_dmub_hw_access_tests[] = {
 	/* dm_init_microcode() */
 	KUNIT_CASE(dm_test_init_microcode_fw_names),
 	KUNIT_CASE(dm_test_init_microcode_request_fails),
+	/* dm_dmub_sw_init() */
+	KUNIT_CASE(dm_test_dmub_sw_init_asic_mapping),
+	KUNIT_CASE(dm_test_dmub_sw_init_gtt_only_asic),
+	KUNIT_CASE(dm_test_dmub_sw_init_success),
+	KUNIT_CASE(dm_test_dmub_sw_init_bss_data),
+	KUNIT_CASE(dm_test_dmub_sw_init_psp_load),
 	{}
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 30/82] drm/amd/display: Add hook to disable alt-ch in PMO
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (28 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 29/82] drm/amd/display: Test dm_dmub_sw_init IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 31/82] drm/amd/display: Update alt-ch size calculations IVAN.LIPSKI
                   ` (52 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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, Alvin Lee, Dillon Varone

From: Alvin Lee <Alvin.Lee2@amd.com>

[Why&How]
DML should have a method of disabling alt-ch pstate method.
Add a debug mask bit to disable the alt-ch pstate method in PMO.

Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Alvin Lee <Alvin.Lee2@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c   | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c
index f4f848d738208..d456fb3d616d4 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c
@@ -31,6 +31,7 @@ static void dml21_populate_pmo_options(struct dml2_pmo_options *pmo_options,
 			disable_fams2;
 	pmo_options->disable_drr_var = ((in_dc->debug.dml21_disable_pstate_method_mask >> 4) & 1) ||
 			disable_fams2;
+	pmo_options->disable_alternate_memory_training = ((in_dc->debug.dml21_disable_pstate_method_mask >> 5) & 1);
 	pmo_options->disable_fams2 = disable_fams2;
 
 	pmo_options->disable_drr_var_when_var_active = in_dc->debug.disable_fams_gaming == INGAME_FAMS_DISABLE ||
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 31/82] drm/amd/display: Update alt-ch size calculations
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (29 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 30/82] drm/amd/display: Add hook to disable alt-ch in PMO IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 32/82] drm/amd/display: Enable min dispclk ODM on DCN42 IVAN.LIPSKI
                   ` (51 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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, Alvin Lee, Dillon Varone

From: Alvin Lee <Alvin.Lee2@amd.com>

[Description]
- Align calculation with addrlib (need to align to macro tiled
  widht/height)
- Align the copy size / addr to 256KB for SW_256KB modes

Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Alvin Lee <Alvin.Lee2@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/dc/dml2_0/dml21/dml21_utils.c | 15 +++---
 .../dml2_core/dml2_core_dcn6_calcs_dchub.c    | 47 +++++++++++++------
 .../dml2_core_dcn6_funcs_mode_support.c       |  3 ++
 .../src/dml2_core/dml2_core_shared_types.h    |  2 +
 4 files changed, 46 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c
index 1ade48b8b621a..125ca619c8abb 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c
@@ -375,11 +375,12 @@ void dml21_handle_phantom_streams_planes(const struct dc *dc, struct dc_state *c
 		dml2_map_dc_pipes(dml_ctx, context, NULL, &dml_ctx->v21.dml_to_dc_pipe_mapping, dc->current_state);
 }
 
-static uint32_t calc_svp_size_64kb(uint32_t total_size_bytes)
+static uint32_t calc_svp_size_256kb(uint64_t addr_bytes)
 {
-	return (total_size_bytes + 0xFFFF) >> 16;
+    return (uint32_t)(((addr_bytes + 0x3FFFFull) >> 18) << 2); // ceil to 256KB, then return bits [47:16]
 }
 
+
 static unsigned int dml21_build_fams2_stream_programming_v3(const struct dc *dc,
 		struct dc_state *context,
 		struct dml2_context *dml_ctx)
@@ -387,7 +388,7 @@ static unsigned int dml21_build_fams2_stream_programming_v3(const struct dc *dc,
 	int dml_stream_idx, dc_stream_idx, dc_plane_idx, svp_idx;
 	unsigned int dc_pipe_idx;
 	unsigned int num_fams2_streams = 0;
-	unsigned int svp_size_64kb[2] = {0};
+	unsigned int svp_size_256kb[2] = {0};
 	struct pipe_ctx *pipe;
 
 	for (dc_stream_idx = 0; dc_stream_idx < context->stream_count; dc_stream_idx++) {
@@ -477,12 +478,12 @@ static unsigned int dml21_build_fams2_stream_programming_v3(const struct dc *dc,
 					}
 					for (svp_idx = 0; svp_idx < 2; svp_idx++) {
 						alternate_static_state->pipe_copy_addr_47_16[svp_idx][dc_pipe_idx] =
-								dml_ctx->config.alt_ch_cfg.region_base_addr_47_16[svp_idx] + svp_size_64kb[svp_idx];
-						svp_size_64kb[svp_idx] += calc_svp_size_64kb(alternate_static_state->pipe_copy_max_size[svp_idx][dc_plane_idx]);
+								dml_ctx->config.alt_ch_cfg.region_base_addr_47_16[svp_idx] + svp_size_256kb[svp_idx];
+						svp_size_256kb[svp_idx] += calc_svp_size_256kb(alternate_static_state->pipe_copy_max_size[svp_idx][dc_plane_idx]);
 						if (alternate_static_state->config[dc_plane_idx].bits.is_multi_planar) {
 							alternate_static_state->pipe_copy_addr_47_16_c[svp_idx][dc_pipe_idx] =
-									dml_ctx->config.alt_ch_cfg.region_base_addr_47_16[svp_idx] + svp_size_64kb[svp_idx];
-							svp_size_64kb[svp_idx] += calc_svp_size_64kb(alternate_static_state->pipe_copy_max_size_c[svp_idx][dc_plane_idx]);
+									dml_ctx->config.alt_ch_cfg.region_base_addr_47_16[svp_idx] + svp_size_256kb[svp_idx];
+							svp_size_256kb[svp_idx] += calc_svp_size_256kb(alternate_static_state->pipe_copy_max_size_c[svp_idx][dc_plane_idx]);
 						}
 					}
 				}
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c
index cae6bee93fe30..83fada0608724 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c
@@ -203,14 +203,32 @@ static unsigned int calculate_copy_swaths(double pre_first_hdl,
 	return num_swaths;
 }
 
+/**
+  * *******************************************************************************************************************************************************
+  * calculate_tile_aligned_size: Applies tiled-surface padding / alignment rules to an unaligned copy region
+  *
+  * Given the unaligned copy region dimensions (copy_width in pixels, copy_height in lines) and the plane's macro tile
+  * (swizzle block) dimensions, this returns the number of bytes reserved for the region following addrlib sizing rules:
+  * - The pitch is padded up to the macro tile width and the height is padded up to the macro tile height.
+  *   size = alignedPitch * alignedHeight * bpp, which is inherently macro-tile (block) aligned
+  * - The final ceil2 to 256 is a harmless floor for degenerate tile sizes.
+  *
+  * *******************************************************************************************************************************************************
+  */
+static unsigned int calculate_tile_aligned_size(unsigned int copy_width, unsigned int copy_height, unsigned int tile_width, unsigned int tile_height, unsigned int byte_per_pixel)
+{
+	unsigned int mem_width = (unsigned int)math_ceil2(copy_width, tile_width);
+	unsigned int mem_height = (unsigned int)math_ceil2(copy_height, tile_height);
+
+	return (unsigned int)math_ceil2(mem_width * mem_height * byte_per_pixel, 256);
+}
+
  /**
   * *******************************************************************************************************************************************************
   * calculate_max_mem_size_per_plane_per_dpp: Calculate (loose) upper bound for total number of bytes reserved in memory for the copy given number of swaths
   *
   * - The copy width / height is the min of the vp_width/height and maximum number of pixels that can fit across an ODM slice
   *     - This is to account for recout positions that cross the ODM seam but are "mostly" within the same ODM slice
-  * - For mem width assume an extra block width and tile width is required (the memory reserved must take into account pitch which must be tiled aligned)
-  * - For mem height assumes two extra block heights are required
   *
   * *******************************************************************************************************************************************************
   */
@@ -222,12 +240,11 @@ static unsigned int calculate_max_mem_size_per_plane_per_dpp(const struct dml2_c
 	unsigned int copy_src_lines = copy_swaths * (chroma ? p->SwathHeightC[plane_idx] : p->SwathHeightY[plane_idx]);
 	unsigned int vp_width = chroma ? p->display_cfg->plane_descriptors[plane_idx].composition.viewport.plane1.width : p->display_cfg->plane_descriptors[plane_idx].composition.viewport.plane0.width;
 	unsigned int vp_height = chroma ? p->display_cfg->plane_descriptors[plane_idx].composition.viewport.plane1.height : p->display_cfg->plane_descriptors[plane_idx].composition.viewport.plane0.height;
-	unsigned int block256_width = chroma ? p->Read256BlockWidthC[plane_idx] : p->Read256BlockWidthY[plane_idx];
-	unsigned int block256_height = chroma ? p->Read256BlockHeightC[plane_idx] : p->Read256BlockHeightY[plane_idx];
 	unsigned int tile_width = chroma ? p->MacroTileWidthC[plane_idx] : p->MacroTileWidthY[plane_idx];
+	unsigned int tile_height = chroma ? p->MacroTileHeightC[plane_idx] : p->MacroTileHeightY[plane_idx];
 	unsigned int byte_per_pixel = chroma ? p->BytePerPixelC[plane_idx] : p->BytePerPixelY[plane_idx];
-	unsigned int mem_width;
-	unsigned int mem_height;
+	unsigned int copy_width;
+	unsigned int copy_height;
 	unsigned int odm_combine_factor;
 	double odm_slice_pixels;
 
@@ -241,12 +258,14 @@ static unsigned int calculate_max_mem_size_per_plane_per_dpp(const struct dml2_c
 		odm_combine_factor = 1;
 
 	odm_slice_pixels = (double)h_active / odm_combine_factor * h_ratio + (odm_combine_factor == 3 ? 2 : 0);
-	mem_width = (vertical_access ? copy_src_lines : (unsigned int)math_ceil(math_min2(odm_slice_pixels, vp_width))) + block256_width + tile_width;
-	mem_height = (vertical_access ? (unsigned int)math_ceil(math_min2(odm_slice_pixels, vp_height)) : copy_src_lines) + 2 * block256_height;
 
-	return (unsigned int)math_ceil2(mem_width * mem_height * byte_per_pixel, 256);
-}
+	/* Unaligned copy region dimensions (in pixels / lines) prior to tile alignment.
+	 * Also divide vp_widht/height by NoOfDPP for MPC combine scenarios (i.e., MPC split within an ODM slice). */
+	copy_width = vertical_access ? copy_src_lines : (unsigned int)math_ceil(math_min2(odm_slice_pixels, vp_width / p->NoOfDPP[plane_idx]));
+	copy_height = vertical_access ? (unsigned int)math_ceil(math_min2(odm_slice_pixels, vp_height / p->NoOfDPP[plane_idx])) : copy_src_lines;
 
+	return calculate_tile_aligned_size(copy_width, copy_height, tile_width, tile_height, byte_per_pixel);
+}
  /**
   * ****************************************************************************************************************************************
   * calculate_ub_copy_size_per_plane_per_dpp: Calculate tight upper bound for total number of bytes required for the copy given number of swaths.
@@ -422,9 +441,9 @@ static void calculate_swath_params(const struct dml2_core_calcs_calculate_altern
 	out->prefetch_hdl_delta = (double)swath_height / vratio_pre;
 }
 
-static unsigned int calc_svp_size_64kb_aligned(unsigned int total_size_bytes)
+static unsigned int calc_svp_size_256kb_aligned(unsigned int total_size_bytes)
 {
-	return ((total_size_bytes + 0xFFFF) >> 16) << 16; // Round up to nearest 64KB boundary
+	return ((total_size_bytes + 0x3FFFFu) >> 18) << 18; // Round up to nearest 256KB boundary
 }
 
 void dcn6_calculate_alternate_params(struct dml2_core_calcs_calculate_alternate_params *p)
@@ -471,7 +490,7 @@ void dcn6_calculate_alternate_params(struct dml2_core_calcs_calculate_alternate_
 				p->prefetch_hdl_delta[j] = swath_params.prefetch_hdl_delta;
 				for (k = 0; k < 2; k++) {
 					svp_max_bytes_per_dpp[k] = calculate_ub_copy_size_per_plane_per_dpp_per_svp(p, svp_dst_lines[k], j, false);
-					svp_max_bytes[k] += calc_svp_size_64kb_aligned(svp_max_bytes_per_dpp[k]) * p->NoOfDPP[j];
+					svp_max_bytes[k] += calc_svp_size_256kb_aligned(svp_max_bytes_per_dpp[k]) * p->NoOfDPP[j];
 				}
 				p->svp0_max_bytes_per_dpp[j] = svp_max_bytes_per_dpp[0];
 				p->svp1_max_bytes_per_dpp[j] = svp_max_bytes_per_dpp[1];
@@ -485,7 +504,7 @@ void dcn6_calculate_alternate_params(struct dml2_core_calcs_calculate_alternate_
 
 					for (k = 0; k < 2; k++) {
 						svp_max_bytes_per_dpp[k] = calculate_ub_copy_size_per_plane_per_dpp_per_svp(p, svp_dst_lines[k], j, true);
-						svp_max_bytes[k] += calc_svp_size_64kb_aligned(svp_max_bytes_per_dpp[k]) * p->NoOfDPP[j];
+						svp_max_bytes[k] += calc_svp_size_256kb_aligned(svp_max_bytes_per_dpp[k]) * p->NoOfDPP[j];
 					}
 					p->svp0_max_bytes_per_dpp_c[j] = svp_max_bytes_per_dpp[0];
 					p->svp1_max_bytes_per_dpp_c[j] = svp_max_bytes_per_dpp[1];
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_mode_support.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_mode_support.c
index cb3cff316b0e8..01bbf4aacbe3c 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_mode_support.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_mode_support.c
@@ -2246,6 +2246,8 @@ static void dcn6_ms_calculate_alternate_params(const struct dml2_core_calculate_
 	p->Read256BlockHeightC = inputs->Read256BlockHeightC;
 	p->MacroTileWidthY = inputs->MacroTileWidthY;
 	p->MacroTileWidthC = inputs->MacroTileWidthC;
+	p->MacroTileHeightY = inputs->MacroTileHeightY;
+	p->MacroTileHeightC = inputs->MacroTileHeightC;
 	p->VInitPrefillY = inputs->PrefillY;
 	p->VInitPrefillC = inputs->PrefillC;
 	p->VRatioPrefetchY = inputs->VRatioPreY;
@@ -2316,6 +2318,7 @@ static void dcn6_ms_calculate_alternate_svp_lines(const struct dml2_core_calcula
 	struct dml2_core_internal_mode_support *outputs = states;
 
 	DML_LOG_FUNC_ENTER();
+
 	p->display_cfg = ctx->display_cfg;
 	p->SwathHeightY = inputs->SwathHeightY;
 	p->SwathHeightC = inputs->SwathHeightC;
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 55118fd484ce8..36250f3d83aaf 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
@@ -2300,6 +2300,8 @@ struct dml2_core_calcs_calculate_alternate_params {
 	unsigned int *Read256BlockHeightC;
 	unsigned int *MacroTileWidthY;
 	unsigned int *MacroTileWidthC;
+	unsigned int *MacroTileHeightY;
+	unsigned int *MacroTileHeightC;
 	unsigned int *VInitPrefillY;
 	unsigned int *VInitPrefillC;
 	double *VRatioPrefetchY;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 32/82] drm/amd/display: Enable min dispclk ODM on DCN42
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (30 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 31/82] drm/amd/display: Update alt-ch size calculations IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 33/82] drm/amd/display: Add amdgpu_dm_connector_poll KUnit tests IVAN.LIPSKI
                   ` (50 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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,
	Dmytro Laktyushkin

From: Gabe Teeger <gabe.teeger@amd.com>

[Why]
Dynamic ODM lowers the required dispclk and saves power. DCN42
and the DCN42 variant should follow whatever the driver requests
through the config flag rather than defaulting it off in resource
construct.

[How]
Assign minimize_dispclk_using_odm directly from the config flag
so an enable request from the driver is honoured. No functional
change when the flag is not set.

Reviewed-by: Dmytro Laktyushkin <dmytro.laktyushkin@amd.com>
Signed-off-by: Gabe Teeger <gabe.teeger@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c    | 4 +---
 .../gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c  | 4 +---
 2 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c
index 6370d3903eb52..3c7cd3ecc6872 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c
@@ -2407,9 +2407,7 @@ static bool dcn42_resource_construct(
 	dc->dml2_options.dcn_pipe_count = pool->base.pipe_count;
 	 /*this will use real soc clock table*/
 	dc->dml2_options.use_native_soc_bb_construction = true;
-	dc->dml2_options.minimize_dispclk_using_odm = false;
-	if (dc->config.EnableMinDispClkODM)
-		dc->dml2_options.minimize_dispclk_using_odm = true;
+	dc->dml2_options.minimize_dispclk_using_odm = dc->config.EnableMinDispClkODM;
 	dc->dml2_options.enable_windowed_mpo_odm = dc->config.enable_windowed_mpo_odm;
 	dc->dml2_options.map_dc_pipes_with_callbacks = true;
 	dc->dml2_options.force_tdlut_enable = true;
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 41f0c67f57fff..8b1ee45d9c13f 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
@@ -2387,9 +2387,7 @@ static bool dcn42b_resource_construct(
 	dc->dml2_options.dcn_pipe_count = pool->base.pipe_count;
 	 /*this will use real soc clock table*/
 	dc->dml2_options.use_native_soc_bb_construction = true;
-	dc->dml2_options.minimize_dispclk_using_odm = false;
-	if (dc->config.EnableMinDispClkODM)
-		dc->dml2_options.minimize_dispclk_using_odm = true;
+	dc->dml2_options.minimize_dispclk_using_odm = dc->config.EnableMinDispClkODM;
 	dc->dml2_options.enable_windowed_mpo_odm = dc->config.enable_windowed_mpo_odm;
 	dc->dml2_options.map_dc_pipes_with_callbacks = true;
 	dc->dml2_options.force_tdlut_enable = true;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 33/82] drm/amd/display: Add amdgpu_dm_connector_poll KUnit tests
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (31 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 32/82] drm/amd/display: Enable min dispclk ODM on DCN42 IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 34/82] drm/amd/display: Cover hide_secondary_tile_from_userspace IVAN.LIPSKI
                   ` (49 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add tests covering the non-shortcut poll paths: connected reusing a cached
local_sink, connected via full dc_link_detect(), and the disconnect
teardown that releases and clears a stale sink. Fakes wire
link->dc->link_srv detect_connection_type/detect_link function pointers.
Raises amdgpu_dm_connector_poll coverage from 35% to 100%.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 165 ++++++++++++++++++
 1 file changed, 165 insertions(+)

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
index 1a95694ea35cd..1b1ed534ed441 100644
--- 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
@@ -4175,6 +4175,168 @@ static void dm_test_poll_dac_load_returns_cached(struct kunit *test)
 		(int)connector_status_connected);
 }
 
+/*
+ * Fake link_service detection callbacks used by the connector poll tests.
+ * dc_link_detect_connection_type() and dc_link_detect() proxy through
+ * link->dc->link_srv, so faking these pointers exercises the poll paths
+ * without touching real hardware.
+ */
+static bool dm_test_poll_detect_type_connected(struct dc_link *link,
+						enum dc_connection_type *type)
+{
+	*type = dc_connection_single;
+	return true;
+}
+
+static bool dm_test_poll_detect_type_none(struct dc_link *link,
+					   enum dc_connection_type *type)
+{
+	*type = dc_connection_none;
+	return false;
+}
+
+static bool dm_test_poll_detect_link_connected(struct dc_link *link,
+					       enum dc_detect_reason reason)
+{
+	return true;
+}
+
+struct dm_test_poll_ctx {
+	struct amdgpu_device *adev;
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_link *link;
+	struct dc *dc;
+	struct dc_context *dc_ctx;
+	struct link_service *link_srv;
+	struct drm_device *drm;
+};
+
+/*
+ * Build an amdgpu_dm_connector embedded in an amdgpu_device (so drm_to_adev()
+ * resolves) with a faked dc_link->dc->link_srv. The hpd_lock and dc_lock
+ * mutexes are initialised because the non-shortcut poll path takes them.
+ */
+static struct dm_test_poll_ctx *dm_test_poll_ctx_alloc(struct kunit *test)
+{
+	struct dm_test_poll_ctx *ctx;
+	struct device *dev;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	dev = drm_kunit_helper_alloc_device(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+	ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+						       sizeof(*ctx->adev),
+						       offsetof(struct amdgpu_device, ddev),
+						       DRIVER_MODESET);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm);
+	ctx->adev = drm_to_adev(ctx->drm);
+	mutex_init(&ctx->adev->dm.dc_lock);
+
+	ctx->aconnector = drmm_kzalloc(ctx->drm, sizeof(*ctx->aconnector), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector);
+	KUNIT_ASSERT_EQ(test,
+		drmm_connector_init(ctx->drm, &ctx->aconnector->base,
+				    &dm_test_connector_funcs,
+				    DRM_MODE_CONNECTOR_VGA, NULL), 0);
+	mutex_init(&ctx->aconnector->hpd_lock);
+
+	ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->dc);
+	ctx->link_srv = kunit_kzalloc(test, sizeof(*ctx->link_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->link_srv);
+	ctx->dc->link_srv = ctx->link_srv;
+
+	ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx);
+
+	ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->link);
+	ctx->link->dc = ctx->dc;
+	ctx->link->ctx = ctx->dc_ctx;
+	ctx->aconnector->dc_link = ctx->link;
+
+	return ctx;
+}
+
+/**
+ * dm_test_poll_connected_cached_sink - Test the connected path reusing a sink
+ * @test: The KUnit test context
+ *
+ * detect_connection_type reports a connection and a local_sink already exists,
+ * so the short-circuit skips full detection and the status stays connected.
+ */
+static void dm_test_poll_connected_cached_sink(struct kunit *test)
+{
+	struct dm_test_poll_ctx *ctx = dm_test_poll_ctx_alloc(test);
+	struct dc_sink *local_sink;
+
+	ctx->link_srv->detect_connection_type = dm_test_poll_detect_type_connected;
+	local_sink = kunit_kzalloc(test, sizeof(*local_sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, local_sink);
+	ctx->link->local_sink = local_sink;
+	ctx->link->type = dc_connection_single;
+	ctx->aconnector->base.status = connector_status_connected;
+
+	KUNIT_EXPECT_EQ(test,
+		(int)amdgpu_dm_connector_poll(ctx->aconnector, false),
+		(int)connector_status_connected);
+}
+
+/**
+ * dm_test_poll_connected_new_sink - Test the connected path via full detection
+ * @test: The KUnit test context
+ *
+ * With no cached local_sink, dc_link_detect() is consulted and reports a
+ * connection, so the status becomes connected.
+ */
+static void dm_test_poll_connected_new_sink(struct kunit *test)
+{
+	struct dm_test_poll_ctx *ctx = dm_test_poll_ctx_alloc(test);
+
+	ctx->link_srv->detect_connection_type = dm_test_poll_detect_type_connected;
+	ctx->link_srv->detect_link = dm_test_poll_detect_link_connected;
+	ctx->link->local_sink = NULL;
+	ctx->aconnector->base.status = connector_status_connected;
+
+	KUNIT_EXPECT_EQ(test,
+		(int)amdgpu_dm_connector_poll(ctx->aconnector, false),
+		(int)connector_status_connected);
+}
+
+/**
+ * dm_test_poll_disconnect_releases_sink - Test the disconnect teardown path
+ * @test: The KUnit test context
+ *
+ * detect_connection_type reports no connection while the cached status was
+ * connected, so the stale local_sink is released and cleared before the
+ * connector is re-evaluated.
+ */
+static void dm_test_poll_disconnect_releases_sink(struct kunit *test)
+{
+	struct dm_test_poll_ctx *ctx = dm_test_poll_ctx_alloc(test);
+	struct dc_sink_init_data sink_init = { 0 };
+	struct dc_sink *local_sink;
+
+	ctx->link_srv->detect_connection_type = dm_test_poll_detect_type_none;
+
+	sink_init.link = ctx->link;
+	sink_init.sink_signal = SIGNAL_TYPE_VIRTUAL;
+	local_sink = dc_sink_create(&sink_init);
+	KUNIT_ASSERT_NOT_NULL(test, local_sink);
+	ctx->link->local_sink = local_sink;
+
+	ctx->aconnector->base.status = connector_status_connected;
+	ctx->aconnector->dc_sink = NULL;
+
+	KUNIT_EXPECT_EQ(test,
+		(int)amdgpu_dm_connector_poll(ctx->aconnector, false),
+		(int)connector_status_disconnected);
+	KUNIT_EXPECT_NULL(test, ctx->link->local_sink);
+}
+
 /* Tests for amdgpu_dm_connector_late_register() and _unregister() */
 
 /*
@@ -6331,6 +6493,9 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_detect_no_sink),
 	/* amdgpu_dm_connector_poll */
 	KUNIT_CASE(dm_test_poll_dac_load_returns_cached),
+	KUNIT_CASE(dm_test_poll_connected_cached_sink),
+	KUNIT_CASE(dm_test_poll_connected_new_sink),
+	KUNIT_CASE(dm_test_poll_disconnect_releases_sink),
 	/* amdgpu_dm_connector_late_register */
 	KUNIT_CASE(dm_test_late_register_non_dp_succeeds),
 	/* amdgpu_dm_connector_unregister */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 34/82] drm/amd/display: Cover hide_secondary_tile_from_userspace
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (32 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 33/82] drm/amd/display: Add amdgpu_dm_connector_poll KUnit tests IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 35/82] drm/amd/display: Cover dm_validate_stream_and_context IVAN.LIPSKI
                   ` (48 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add a KUnit test that drives amdgpu_dm_connector_detect() through the
amdgpu_dm_hide_secondary_tile_from_userspace() path using a sink that
requests disable_second_tile and a synthesized DisplayID tiled EDID at a
non-origin tile location, so the secondary Apple Studio Display tile is
hidden and the connector reports disconnected. Raises the helper coverage
from 44% to 100%.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 62 +++++++++++++++++++
 1 file changed, 62 insertions(+)

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
index 1b1ed534ed441..55cf9c3708394 100644
--- 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
@@ -4122,6 +4122,67 @@ static void dm_test_detect_no_sink(struct kunit *test)
 		(int)connector_status_disconnected);
 }
 
+/*
+ * A 256-byte EDID whose DisplayID extension carries a tiled display
+ * topology block placing this connector at tile location (h=1, v=0), i.e.
+ * a secondary tile. drm_edid_connector_update() parses it to set has_tile
+ * and the non-origin tile location.
+ */
+static const u8 dm_test_detect_tile_edid[256] = {
+	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x10, 0xac, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x80, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc4, 0x70, 0x12, 0x18, 0x00,
+	0x00, 0x12, 0x00, 0x15, 0x00, 0x11, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x90,
+};
+
+/**
+ * dm_test_detect_hides_secondary_tile - Test the secondary tile is hidden
+ * @test: The KUnit test context
+ *
+ * A sink whose panel patch requests disable_second_tile combined with a tiled
+ * EDID at a non-origin tile location makes the detect path hide the secondary
+ * Apple Studio Display tile, reporting the connector as disconnected.
+ */
+static void dm_test_detect_hides_secondary_tile(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test);
+	struct dc_sink *sink;
+
+	sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, sink);
+	sink->edid_caps.panel_patch.disable_second_tile = true;
+
+	aconnector->base.force = DRM_FORCE_UNSPECIFIED;
+	aconnector->dc_sink = sink;
+	aconnector->drm_edid = drm_edid_alloc(dm_test_detect_tile_edid,
+					     sizeof(dm_test_detect_tile_edid));
+	KUNIT_ASSERT_NOT_NULL(test, aconnector->drm_edid);
+
+	KUNIT_EXPECT_EQ(test,
+		(int)amdgpu_dm_connector_detect(&aconnector->base, false),
+		(int)connector_status_disconnected);
+
+	drm_edid_free(aconnector->drm_edid);
+}
+
 /* Tests for amdgpu_dm_connector_poll() */
 
 /**
@@ -6491,6 +6552,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_detect_force_off),
 	KUNIT_CASE(dm_test_detect_sink_present),
 	KUNIT_CASE(dm_test_detect_no_sink),
+	KUNIT_CASE(dm_test_detect_hides_secondary_tile),
 	/* amdgpu_dm_connector_poll */
 	KUNIT_CASE(dm_test_poll_dac_load_returns_cached),
 	KUNIT_CASE(dm_test_poll_connected_cached_sink),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 35/82] drm/amd/display: Cover dm_validate_stream_and_context
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (33 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 34/82] drm/amd/display: Cover hide_secondary_tile_from_userspace IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 36/82] drm/amd/display: Cover amdgpu_dm_create_validate_stream_for_sink IVAN.LIPSKI
                   ` (47 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add dm_test_validate_stream_dc_ok_no_pipe which builds a minimal fake
dc so stream and plane validation both pass, then lets
dc_state_add_stream() bail out early via timing_generator_count == 0 to
avoid deep pipe allocation, raising coverage from 24% to 90%.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 84 +++++++++++++++++++
 1 file changed, 84 insertions(+)

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
index 55cf9c3708394..54d6d2acdd7dc 100644
--- 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
@@ -30,6 +30,8 @@
 #include "include/grph_object_id.h"
 #include "amdgpu_dm_kunit_test_helpers.h"
 #include "inc/link_service.h"
+#include "inc/resource.h"
+#include "inc/hw/timing_generator.h"
 
 /* Tests for get_subconnector_type() */
 
@@ -5202,6 +5204,87 @@ static void dm_test_validate_stream_null_stream(struct kunit *test)
 			(int)DC_ERROR_UNEXPECTED);
 }
 
+static bool dm_test_vs_validate_timing_true(struct timing_generator *tg,
+					     const struct dc_crtc_timing *timing)
+{
+	return true;
+}
+
+static enum dc_status dm_test_vs_validate_mode_timing_ok(
+		const struct dc_stream_state *stream,
+		struct dc_link *link,
+		const struct dc_crtc_timing *timing)
+{
+	return DC_OK;
+}
+
+/**
+ * dm_test_validate_stream_dc_ok_no_pipe - Exercise the full validation body
+ * @test: The KUnit test context
+ *
+ * Build a minimal fake dc so stream and plane validation both pass, then
+ * let dc_state_add_stream() bail out early (timing_generator_count == 0) so
+ * the deep pipe-allocation paths are avoided. This drives plane population,
+ * dc_validate_stream(), dc_validate_plane() and cleanup.
+ */
+static void dm_test_validate_stream_dc_ok_no_pipe(struct kunit *test)
+{
+	struct drm_device *drm = dm_test_alloc_drm(test);
+	struct dc *dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+	struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	struct dal_logger *logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL);
+	struct resource_pool *pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL);
+	struct resource_funcs *rfuncs = kunit_kzalloc(test, sizeof(*rfuncs), GFP_KERNEL);
+	struct resource_caps *rcaps = kunit_kzalloc(test, sizeof(*rcaps), GFP_KERNEL);
+	struct timing_generator *tg = kunit_kzalloc(test, sizeof(*tg), GFP_KERNEL);
+	struct timing_generator_funcs *tgfuncs =
+		kunit_kzalloc(test, sizeof(*tgfuncs), GFP_KERNEL);
+	struct link_service *link_srv =
+		kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+	struct dc_stream_state *stream =
+		kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+	struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+	KUNIT_ASSERT_NOT_NULL(test, dc);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+	KUNIT_ASSERT_NOT_NULL(test, logger);
+	KUNIT_ASSERT_NOT_NULL(test, pool);
+	KUNIT_ASSERT_NOT_NULL(test, rfuncs);
+	KUNIT_ASSERT_NOT_NULL(test, rcaps);
+	KUNIT_ASSERT_NOT_NULL(test, tg);
+	KUNIT_ASSERT_NOT_NULL(test, tgfuncs);
+	KUNIT_ASSERT_NOT_NULL(test, link_srv);
+	KUNIT_ASSERT_NOT_NULL(test, stream);
+	KUNIT_ASSERT_NOT_NULL(test, link);
+
+	logger->dev = drm;
+	ctx->logger = logger;
+
+	tgfuncs->validate_timing = dm_test_vs_validate_timing_true;
+	tg->funcs = tgfuncs;
+
+	pool->funcs = rfuncs;
+	pool->res_cap = rcaps;
+	pool->timing_generators[0] = tg;
+	pool->timing_generator_count = 0;
+
+	link_srv->validate_mode_timing = dm_test_vs_validate_mode_timing_ok;
+
+	dc->ctx = ctx;
+	dc->res_pool = pool;
+	dc->link_srv = link_srv;
+
+	link->ep_type = DISPLAY_ENDPOINT_UNKNOWN;
+	stream->link = link;
+	stream->src.width = 1920;
+	stream->src.height = 1080;
+
+	KUNIT_EXPECT_EQ(test,
+			(int)dm_validate_stream_and_context(dc, stream),
+			(int)DC_ERROR_UNEXPECTED);
+}
+
 /**
  * dm_test_to_encoder_no_encoder - Test connector with no encoder returns NULL
  * @test: The KUnit test context
@@ -6589,6 +6672,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_funcs_force_no_edid),
 	/* dm_validate_stream_and_context */
 	KUNIT_CASE(dm_test_validate_stream_null_stream),
+	KUNIT_CASE(dm_test_validate_stream_dc_ok_no_pipe),
 	/* amdgpu_dm_connector_to_encoder */
 	KUNIT_CASE(dm_test_to_encoder_no_encoder),
 	KUNIT_CASE(dm_test_to_encoder_returns_attached),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 36/82] drm/amd/display: Cover amdgpu_dm_create_validate_stream_for_sink
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (34 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 35/82] drm/amd/display: Cover dm_validate_stream_and_context IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 37/82] drm/amd/display: Cover amdgpu_dm_connector_mode_valid IVAN.LIPSKI
                   ` (46 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for amdgpu_dm_create_validate_stream_for_sink()
covering the writeback connector path, the colour-depth downgrade when
no valid bpc is found, the HDMI YCbCr and forced YCbCr420/422/444
output paths, and the timing/context prune-and-retry loops.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 416 ++++++++++++++++++
 1 file changed, 416 insertions(+)

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
index 54d6d2acdd7dc..b9b32194a657f 100644
--- 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
@@ -5854,6 +5854,414 @@ static void dm_test_create_validate_stream_null_dm_state(struct kunit *test)
 								    NULL, NULL, NULL));
 }
 
+/* Tests for amdgpu_dm_create_validate_stream_for_sink() */
+
+/*
+ * Build a connector embedded in an amdgpu_device (so drm_to_adev() resolves)
+ * carrying a dc_link but a deliberately low atomic-requested bpc. Every
+ * candidate colour depth then exceeds that cap, so bpc_mask ends up empty and
+ * the enumeration returns NULL before create_stream_for_sink() and
+ * dc_validate_stream() (and thus the unpopulated dc handle) are ever reached.
+ * This lets the encoding/bpc mask-building branches be exercised on their own.
+ */
+struct dm_test_cvs_ctx {
+	struct amdgpu_device *adev;
+	struct drm_device *drm;
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_link *link;
+	struct dm_connector_state *dm_state;
+	struct drm_display_mode *mode;
+};
+
+static struct dm_test_cvs_ctx *
+dm_test_cvs_ctx_alloc(struct kunit *test, int connector_type)
+{
+	struct dm_test_cvs_ctx *ctx;
+	struct device *dev;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	dev = drm_kunit_helper_alloc_device(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+	ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+			sizeof(*ctx->adev),
+			offsetof(struct amdgpu_device, ddev),
+			DRIVER_MODESET);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm);
+	ctx->adev = drm_to_adev(ctx->drm);
+
+	ctx->aconnector = drmm_kzalloc(ctx->drm, sizeof(*ctx->aconnector),
+			GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector);
+	KUNIT_ASSERT_EQ(test,
+			drmm_connector_init(ctx->drm, &ctx->aconnector->base,
+			&dm_test_connector_funcs, connector_type,
+			NULL), 0);
+
+	ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->link);
+	ctx->aconnector->dc_link = ctx->link;
+
+	ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state);
+	ctx->dm_state->base.max_requested_bpc = 4;
+
+	ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->mode);
+	ctx->mode->hdisplay = 1920;
+	ctx->mode->vdisplay = 1080;
+
+	return ctx;
+}
+
+/**
+ * dm_test_create_validate_stream_writeback - Test the writeback stream path
+ * @test: The KUnit test context
+ *
+ * A writeback connector has no sink EDID to enumerate, so the helper builds and
+ * returns a single RGB stream directly instead of running the validation loop.
+ */
+static void dm_test_create_validate_stream_writeback(struct kunit *test)
+{
+	struct amdgpu_dm_wb_connector *wbcon;
+	struct dm_connector_state *dm_state;
+	struct drm_display_mode *mode;
+	struct dc_stream_state *stream;
+	struct amdgpu_device *adev;
+	struct dc_context *dc_ctx;
+	struct drm_device *drm;
+	struct dc_link *link;
+	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);
+
+	wbcon = drmm_kzalloc(drm, sizeof(*wbcon), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, wbcon);
+	KUNIT_ASSERT_EQ(test,
+			drmm_connector_init(drm, &wbcon->base.base,
+			&dm_test_connector_funcs,
+			DRM_MODE_CONNECTOR_WRITEBACK, NULL), 0);
+
+	dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc_ctx);
+	link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link);
+	link->ctx = dc_ctx;
+	link->connector_signal = SIGNAL_TYPE_VIRTUAL;
+	wbcon->link = link;
+
+	dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dm_state);
+	dm_state->scaling = RMX_OFF;
+
+	mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, mode);
+	mode->hdisplay = 1920;
+	mode->vdisplay = 1080;
+	mode->clock = 148500;
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&wbcon->base.base,
+							   mode, dm_state, NULL);
+	KUNIT_ASSERT_NOT_NULL(test, stream);
+	dc_stream_release(stream);
+}
+
+/**
+ * dm_test_create_validate_stream_no_valid_bpc - Test the exhausted-mask path
+ * @test: The KUnit test context
+ *
+ * On a plain DisplayPort sink using the default RGB encoding but a bpc cap below
+ * every candidate depth, no (encoding, bpc) pair is attempted and the helper
+ * returns NULL without touching the dc handle.
+ */
+static void dm_test_create_validate_stream_no_valid_bpc(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/**
+ * dm_test_create_validate_stream_hdmi_ycbcr - Test the HDMI encoding mask
+ * @test: The KUnit test context
+ *
+ * A native HDMI sink advertising YCbCr 4:4:4 and 4:2:2 exercises HDMI endpoint
+ * detection and the YCbCr444/YCbCr422 mask branches; the low bpc cap still
+ * prunes every depth, so the helper returns NULL.
+ */
+static void dm_test_create_validate_stream_hdmi_ycbcr(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+	ctx->aconnector->base.display_info.color_formats =
+		BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) |
+		BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/**
+ * dm_test_create_validate_stream_force_ycbcr420 - Test the forced YCbCr420 mask
+ * @test: The KUnit test context
+ *
+ * force_yuv_pixel_format pins the encoding mask to YCbCr420 even on an RGB sink;
+ * the low bpc cap prunes every depth, so the helper returns NULL.
+ */
+static void dm_test_create_validate_stream_force_ycbcr420(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+	ctx->aconnector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR420;
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/**
+ * dm_test_create_validate_stream_force_ycbcr422 - Test the forced YCbCr422 mask
+ * @test: The KUnit test context
+ *
+ * With the sink advertising YCbCr 4:2:2, a force_yuv override pins the encoding
+ * mask to YCbCr422; the low bpc cap prunes every depth, so the helper returns
+ * NULL.
+ */
+static void dm_test_create_validate_stream_force_ycbcr422(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+	ctx->aconnector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR422;
+	ctx->aconnector->base.display_info.color_formats =
+		BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/**
+ * dm_test_create_validate_stream_force_ycbcr444 - Test the forced YCbCr444 mask
+ * @test: The KUnit test context
+ *
+ * On a native HDMI sink advertising YCbCr 4:4:4, a force_yuv override pins the
+ * encoding mask to YCbCr444; the low bpc cap prunes every depth, so the helper
+ * returns NULL.
+ */
+static void dm_test_create_validate_stream_force_ycbcr444(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_stream_state *stream;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+	ctx->aconnector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR444;
+	ctx->aconnector->base.display_info.color_formats =
+		BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444);
+
+	stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
+							   ctx->mode,
+							   ctx->dm_state, NULL);
+	KUNIT_EXPECT_NULL(test, stream);
+}
+
+/*
+ * Drive the enumeration loop far enough to reach create_stream_for_sink() and
+ * dc_validate_stream() by providing a valid bpc plus a minimal fake dc. The
+ * connector carries a dc_link with a dc_context so a fake VIRTUAL sink and its
+ * stream can be built; adev->dm.dc is wired with just enough resource_pool /
+ * timing_generator / link_service state for dc_validate_stream() and
+ * dm_validate_stream_and_context() to run without a real pipe allocator.
+ */
+struct dm_test_cvs_dc {
+	struct amdgpu_device *adev;
+	struct drm_device *drm;
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_link *link;
+	struct dm_connector_state *dm_state;
+	struct drm_display_mode *mode;
+	struct dc *dc;
+	struct timing_generator_funcs *tgfuncs;
+	struct link_service *link_srv;
+};
+
+static bool dm_test_cvs_validate_timing_fail(struct timing_generator *tg,
+					     const struct dc_crtc_timing *timing)
+{
+	return false;
+}
+
+static bool dm_test_cvs_validate_timing_ok(struct timing_generator *tg,
+					   const struct dc_crtc_timing *timing)
+{
+	return true;
+}
+
+static enum dc_status dm_test_cvs_validate_mode_timing_ok(
+		const struct dc_stream_state *stream,
+		struct dc_link *link,
+		const struct dc_crtc_timing *timing)
+{
+	return DC_OK;
+}
+
+static struct dm_test_cvs_dc *dm_test_cvs_dc_alloc(struct kunit *test)
+{
+	struct timing_generator_funcs *tgfuncs;
+	struct resource_funcs *rfuncs;
+	struct resource_caps *rcaps;
+	struct resource_pool *pool;
+	struct timing_generator *tg;
+	struct dc_context *dcc;
+	struct dal_logger *logger;
+	struct dm_test_cvs_dc *c;
+	struct device *dev;
+
+	c = kunit_kzalloc(test, sizeof(*c), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c);
+
+	dev = drm_kunit_helper_alloc_device(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+	c->drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+			sizeof(*c->adev),
+			offsetof(struct amdgpu_device, ddev),
+			DRIVER_MODESET);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, c->drm);
+	c->adev = drm_to_adev(c->drm);
+
+	c->aconnector = drmm_kzalloc(c->drm, sizeof(*c->aconnector), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->aconnector);
+	KUNIT_ASSERT_EQ(test,
+			drmm_connector_init(c->drm, &c->aconnector->base,
+			&dm_test_connector_funcs,
+			DRM_MODE_CONNECTOR_DisplayPort, NULL), 0);
+	c->aconnector->base.display_info.bpc = 8;
+
+	dcc = kunit_kzalloc(test, sizeof(*dcc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dcc);
+	logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, logger);
+	logger->dev = c->drm;
+	dcc->logger = logger;
+
+	c->link = kunit_kzalloc(test, sizeof(*c->link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->link);
+	c->link->ctx = dcc;
+	c->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+	c->link->ep_type = DISPLAY_ENDPOINT_UNKNOWN;
+	c->aconnector->dc_link = c->link;
+
+	pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, pool);
+	rfuncs = kunit_kzalloc(test, sizeof(*rfuncs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, rfuncs);
+	rcaps = kunit_kzalloc(test, sizeof(*rcaps), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, rcaps);
+	tg = kunit_kzalloc(test, sizeof(*tg), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, tg);
+	tgfuncs = kunit_kzalloc(test, sizeof(*tgfuncs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, tgfuncs);
+	c->tgfuncs = tgfuncs;
+	c->link_srv = kunit_kzalloc(test, sizeof(*c->link_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->link_srv);
+
+	tg->funcs = tgfuncs;
+	pool->funcs = rfuncs;
+	pool->res_cap = rcaps;
+	pool->timing_generators[0] = tg;
+	pool->timing_generator_count = 0;
+
+	c->dc = kunit_kzalloc(test, sizeof(*c->dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->dc);
+	c->dc->ctx = dcc;
+	c->dc->res_pool = pool;
+	c->dc->link_srv = c->link_srv;
+	c->adev->dm.dc = c->dc;
+
+	c->dm_state = kunit_kzalloc(test, sizeof(*c->dm_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->dm_state);
+	c->dm_state->base.max_requested_bpc = 8;
+	c->dm_state->scaling = RMX_OFF;
+
+	c->mode = kunit_kzalloc(test, sizeof(*c->mode), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, c->mode);
+	c->mode->hdisplay = 1920;
+	c->mode->vdisplay = 1080;
+	c->mode->clock = 148500;
+
+	return c;
+}
+
+/**
+ * dm_test_create_validate_stream_prune_timing - Test the dc_validate_stream prune
+ * @test: The KUnit test context
+ *
+ * A built stream fails dc_validate_stream() (timing_generator rejects the
+ * timing), so every candidate is pruned and released and the helper returns
+ * NULL once the enumeration is exhausted.
+ */
+static void dm_test_create_validate_stream_prune_timing(struct kunit *test)
+{
+	struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+
+	c->tgfuncs->validate_timing = dm_test_cvs_validate_timing_fail;
+
+	KUNIT_EXPECT_NULL(test,
+			  amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
+								    c->mode,
+								    c->dm_state,
+								    NULL));
+}
+
+/**
+ * dm_test_create_validate_stream_prune_context - Test the context-validation prune
+ * @test: The KUnit test context
+ *
+ * dc_validate_stream() succeeds but dm_validate_stream_and_context() fails (no
+ * pipe allocator), exercising the DC_OK sub-branches and MST check before the
+ * candidate is pruned; the exhausted enumeration returns NULL.
+ */
+static void dm_test_create_validate_stream_prune_context(struct kunit *test)
+{
+	struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+
+	c->tgfuncs->validate_timing = dm_test_cvs_validate_timing_ok;
+	c->link_srv->validate_mode_timing = dm_test_cvs_validate_mode_timing_ok;
+
+	KUNIT_EXPECT_NULL(test,
+			  amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
+								    c->mode,
+								    c->dm_state,
+								    NULL));
+}
+
 /**
  * dm_test_update_after_detect_mst_noop - Test MST connectors are left to drm_mst
  * @test: The KUnit test context
@@ -6716,6 +7124,14 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_s3_handle_hdmi_cec_resume),
 	/* amdgpu_dm_create_validate_stream_for_sink */
 	KUNIT_CASE(dm_test_create_validate_stream_null_dm_state),
+	KUNIT_CASE(dm_test_create_validate_stream_writeback),
+	KUNIT_CASE(dm_test_create_validate_stream_no_valid_bpc),
+	KUNIT_CASE(dm_test_create_validate_stream_hdmi_ycbcr),
+	KUNIT_CASE(dm_test_create_validate_stream_force_ycbcr420),
+	KUNIT_CASE(dm_test_create_validate_stream_force_ycbcr422),
+	KUNIT_CASE(dm_test_create_validate_stream_force_ycbcr444),
+	KUNIT_CASE(dm_test_create_validate_stream_prune_timing),
+	KUNIT_CASE(dm_test_create_validate_stream_prune_context),
 	/* amdgpu_dm_update_connector_after_detect */
 	KUNIT_CASE(dm_test_update_after_detect_mst_noop),
 	KUNIT_CASE(dm_test_update_after_detect_sink_unchanged),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 37/82] drm/amd/display: Cover amdgpu_dm_connector_mode_valid
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (35 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 36/82] drm/amd/display: Cover amdgpu_dm_create_validate_stream_for_sink IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 38/82] drm/amd/display: Test MST sideband message ack path IVAN.LIPSKI
                   ` (45 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for amdgpu_dm_connector_mode_valid() covering the
missing dc_sink path and a forced-on connector that fails to create a
validation stream.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 63 ++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)

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
index b9b32194a657f..23b83bbde73fd 100644
--- 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
@@ -4161,7 +4161,7 @@ static const u8 dm_test_detect_tile_edid[256] = {
  *
  * A sink whose panel patch requests disable_second_tile combined with a tiled
  * EDID at a non-origin tile location makes the detect path hide the secondary
- * Apple Studio Display tile, reporting the connector as disconnected.
+ * display tile, reporting the connector as disconnected.
  */
 static void dm_test_detect_hides_secondary_tile(struct kunit *test)
 {
@@ -6262,6 +6262,65 @@ static void dm_test_create_validate_stream_prune_context(struct kunit *test)
 								    NULL));
 }
 
+/* Further tests for amdgpu_dm_connector_mode_valid() */
+
+/**
+ * dm_test_mode_valid_no_dc_sink - Test the missing-sink rejection
+ * @test: The KUnit test context
+ *
+ * With no dc_sink and an unforced connector there is nothing to validate
+ * against, so mode_valid() logs and returns MODE_ERROR without reaching stream
+ * creation.
+ */
+static void dm_test_mode_valid_no_dc_sink(struct kunit *test)
+{
+	struct drm_device *drm = dm_test_alloc_drm(test);
+	struct amdgpu_dm_connector *aconnector =
+		dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA);
+	struct drm_display_mode *mode;
+
+	mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, mode);
+
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_connector_mode_valid(&aconnector->base, mode),
+			MODE_ERROR);
+}
+
+/**
+ * dm_test_mode_valid_force_on_no_stream - Test the forced no-stream rejection
+ * @test: The KUnit test context
+ *
+ * A forced-on connector with dc_em_sink already set skips the EDID refresh and
+ * the missing-sink bail, reaching stream creation; with no validatable depth no
+ * stream is produced, so mode_valid() returns MODE_ERROR.
+ */
+static void dm_test_mode_valid_force_on_no_stream(struct kunit *test)
+{
+	struct dm_test_cvs_ctx *ctx =
+		dm_test_cvs_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_sink *em_sink;
+
+	em_sink = kunit_kzalloc(test, sizeof(*em_sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, em_sink);
+
+	ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+	ctx->aconnector->dc_em_sink = em_sink;
+	ctx->aconnector->base.force = DRM_FORCE_ON;
+
+	/* drm_connector_cleanup() kfree()s connector->state, so it must not
+	 * point at KUnit-managed memory.
+	 */
+	amdgpu_dm_connector_funcs_reset(&ctx->aconnector->base);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector->base.state);
+	ctx->aconnector->base.state->max_requested_bpc = 4;
+
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_connector_mode_valid(&ctx->aconnector->base,
+						       ctx->mode),
+			MODE_ERROR);
+}
+
 /**
  * dm_test_update_after_detect_mst_noop - Test MST connectors are left to drm_mst
  * @test: The KUnit test context
@@ -7117,6 +7176,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	/* amdgpu_dm_connector_mode_valid */
 	KUNIT_CASE(dm_test_mode_valid_interlace_rejected),
 	KUNIT_CASE(dm_test_mode_valid_dblscan_rejected),
+	KUNIT_CASE(dm_test_mode_valid_no_dc_sink),
+	KUNIT_CASE(dm_test_mode_valid_force_on_no_stream),
 	/* amdgpu_dm_hdmi_cec_set_edid */
 	KUNIT_CASE(dm_test_hdmi_cec_set_edid_no_notifier),
 	/* amdgpu_dm_s3_handle_hdmi_cec */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 38/82] drm/amd/display: Test MST sideband message ack path
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (36 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 37/82] drm/amd/display: Cover amdgpu_dm_connector_mode_valid IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 39/82] drm/amd/display: Test dm_dp_mst_get_modes without a remote EDID IVAN.LIPSKI
                   ` (44 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_handle_mst_sideband_msg_ready_event() covering the
DOWN_REP ready bit that is acknowledged at DPCD and re-polled until the
iteration limit, and the case where the acknowledge write keeps failing
and the handler gives up after the third retry.

[HOW]
A live topology manager is needed for the handler to reach its ack path,
so the tests initialise one on a DRM device and arm the DOWN_REP ready
bit in the fake DPCD. The fake AUX channel gains a separate write result
so the ESI read still succeeds while the acknowledge write fails.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_mst_types_test.c          | 80 +++++++++++++++++++
 1 file changed, 80 insertions(+)

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 caedad4197bd9..6a5191f9baf61 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
@@ -38,6 +38,7 @@ static int dm_mst_test_aux_transfer_raw_result;
 static u8 dm_mst_test_aux_transfer_raw_reply;
 static enum aux_return_code_type dm_mst_test_aux_transfer_raw_operation_result;
 static ssize_t dm_mst_test_aux_transfer_override;
+static ssize_t dm_mst_test_aux_write_override;
 
 static int dm_mst_test_aux_transfer_raw(struct ddc_service *ddc,
 						struct aux_payload *payload,
@@ -109,6 +110,8 @@ static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux,
 		msg->reply = DP_AUX_NATIVE_REPLY_ACK;
 		return msg->size;
 	case DP_AUX_NATIVE_WRITE:
+		if (dm_mst_test_aux_write_override)
+			return dm_mst_test_aux_write_override;
 		msg->reply = DP_AUX_NATIVE_REPLY_ACK;
 		return msg->size;
 	default:
@@ -146,6 +149,7 @@ static struct amdgpu_dm_connector *dm_mst_test_alloc_sideband_connector(struct k
 
 	memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd));
 	dm_mst_test_aux_transfer_override = 0;
+	dm_mst_test_aux_write_override = 0;
 
 	return aconnector;
 }
@@ -1339,6 +1343,80 @@ static void dm_mst_test_detect_unregistered(struct kunit *test)
 			(int)connector_status_disconnected);
 }
 
+/*
+ * Sideband connector with a live topology manager and the DOWN_REP ready bit
+ * armed, so dm_handle_mst_sideband_msg_ready_event() reaches its ack path.
+ */
+static struct amdgpu_dm_connector *
+dm_mst_test_alloc_armed_sideband_connector(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector;
+	struct amdgpu_device *adev;
+	int ret;
+
+	adev = dm_kunit_alloc_adev(test);
+	ret = drmm_mode_config_init(&adev->ddev);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	aconnector = dm_mst_test_alloc_sideband_connector(test);
+
+	ret = drm_dp_mst_topology_mgr_init(&aconnector->mst_mgr, &adev->ddev,
+					   &aconnector->dm_dp_aux.aux, 16, 4, 0);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	aconnector->mst_mgr.mst_state = true;
+	dm_mst_test_dpcd[(DP_SINK_COUNT_ESI + 1) & 0xf] = DP_DOWN_REP_MSG_RDY;
+
+	return aconnector;
+}
+
+static void dm_mst_test_free_armed_sideband_connector(struct amdgpu_dm_connector *aconnector)
+{
+	aconnector->mst_mgr.mst_state = false;
+	drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr);
+}
+
+/**
+ * dm_mst_test_sideband_msg_ready_acks_down_rep - Test DOWN_REP ack handling
+ * @test: KUnit test context
+ *
+ * With an active topology manager and the DOWN_REP ready bit set, the sideband
+ * handler must acknowledge the event at DPCD and keep polling until the
+ * iteration limit is reached, since the fake sideband message never completes.
+ */
+static void dm_mst_test_sideband_msg_ready_acks_down_rep(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector;
+
+	aconnector = dm_mst_test_alloc_armed_sideband_connector(test);
+
+	dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_REP_MSG_RDY_EVENT);
+
+	KUNIT_EXPECT_EQ(test, aconnector->mst_mgr.sink_count, 0);
+
+	dm_mst_test_free_armed_sideband_connector(aconnector);
+}
+
+/**
+ * dm_mst_test_sideband_msg_ready_ack_write_fails - Test failed DPCD ack
+ * @test: KUnit test context
+ *
+ * When the DPCD acknowledge write keeps failing, the sideband handler must give
+ * up after the third retry instead of looping forever.
+ */
+static void dm_mst_test_sideband_msg_ready_ack_write_fails(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector;
+
+	aconnector = dm_mst_test_alloc_armed_sideband_connector(test);
+	dm_mst_test_aux_write_override = -EIO;
+
+	dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_REP_MSG_RDY_EVENT);
+
+	KUNIT_EXPECT_EQ(test, aconnector->mst_mgr.sink_count, 0);
+
+	dm_mst_test_free_armed_sideband_connector(aconnector);
+}
+
 #if !defined(CONFIG_DRM_AMD_DC_FP)
 /**
  * dm_mst_test_fp_guarded_public_stubs - Test FP-off public fallbacks
@@ -1399,6 +1477,8 @@ static struct kunit_case dm_mst_types_test_cases[] = {
 	KUNIT_CASE(dm_mst_test_sideband_msg_ready_no_ready_bits),
 	KUNIT_CASE(dm_mst_test_sideband_msg_ready_read_error),
 	KUNIT_CASE(dm_mst_test_sideband_msg_ready_without_mst_state),
+	KUNIT_CASE(dm_mst_test_sideband_msg_ready_acks_down_rep),
+	KUNIT_CASE(dm_mst_test_sideband_msg_ready_ack_write_fails),
 	KUNIT_CASE(dm_mst_test_down_rep_msg_ready_wrapper),
 	/* amdgpu_dm_initialize_dp_connector tests */
 	KUNIT_CASE(dm_mst_test_initialize_dp_connector_edp),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 39/82] drm/amd/display: Test dm_dp_mst_get_modes without a remote EDID
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (37 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 38/82] drm/amd/display: Test MST sideband message ack path IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 40/82] drm/amd/display: Test dm_dp_mst_get_modes with " IVAN.LIPSKI
                   ` (43 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_dp_mst_get_modes() covering the path taken when
the remote EDID cannot be read: the default remote sink is registered and
MST_REMOTE_EDID is cleared, DC refusing the sink leaves the connector
without one, and an already attached sink is kept instead of asking DC
for a second one.

[HOW]
A shared fixture builds an MST downstream connector on a real DRM device
with an empty topology manager, so drm_dp_mst_edid_read() takes its "port
is gone" path without needing a branch device. dc_link_add_remote_sink()
and dc_link_remove_remote_sink() are thin wrappers over link_service
callbacks, so fakes for those two are enough to run the connector paths
without a DC core.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_mst_types.c   |   3 +-
 .../display/amdgpu_dm/amdgpu_dm_mst_types.h   |   1 +
 .../tests/amdgpu_dm_mst_types_test.c          | 216 ++++++++++++++++++
 3 files changed, 219 insertions(+), 1 deletion(-)

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 045a7f88b7546..79e8952edf8c0 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
@@ -386,7 +386,7 @@ STATIC_IFN_KUNIT bool retrieve_branch_specific_data(struct amdgpu_dm_connector *
 }
 EXPORT_IF_KUNIT(retrieve_branch_specific_data);
 
-static int dm_dp_mst_get_modes(struct drm_connector *connector)
+STATIC_IFN_KUNIT int dm_dp_mst_get_modes(struct drm_connector *connector)
 {
 	struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
 	int ret = 0;
@@ -518,6 +518,7 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector)
 
 	return ret;
 }
+EXPORT_IF_KUNIT(dm_dp_mst_get_modes);
 
 STATIC_IFN_KUNIT struct drm_encoder *
 dm_mst_atomic_best_encoder(struct drm_connector *connector,
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 2ae9d4b88888f..406dba95c4aec 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
@@ -118,6 +118,7 @@ int dm_dp_mst_atomic_check(struct drm_connector *connector,
 				   struct drm_atomic_commit *state);
 int dm_dp_mst_detect(struct drm_connector *connector,
 			     struct drm_modeset_acquire_ctx *ctx, bool force);
+int dm_dp_mst_get_modes(struct drm_connector *connector);
 #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 6a5191f9baf61..ee4e1f3963736 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
@@ -8,11 +8,13 @@
 #include <kunit/test.h>
 
 #include <drm/drm_drv.h>
+#include <drm/drm_edid.h>
 #include <drm/drm_fixed.h>
 #include <drm/drm_kunit_helpers.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_connector.h>
 #include <drm/drm_mode_config.h>
+#include <drm/drm_modeset_lock.h>
 #include <drm/display/drm_dp.h>
 #include <drm/display/drm_dp_helper.h>
 #include <drm/display/drm_dp_mst_helper.h>
@@ -1343,6 +1345,216 @@ static void dm_mst_test_detect_unregistered(struct kunit *test)
 			(int)connector_status_disconnected);
 }
 
+/*
+ * Fake DC remote-sink backend. dc_link_add_remote_sink() and
+ * dc_link_remove_remote_sink() are thin wrappers over these link_service
+ * callbacks, so overriding them is enough to run the MST connector paths
+ * without a DC core.
+ */
+static struct dc_sink *dm_mst_test_next_remote_sink;
+static struct dc_sink *dm_mst_test_removed_sink;
+static unsigned int dm_mst_test_add_remote_sink_calls;
+static unsigned int dm_mst_test_remove_remote_sink_calls;
+
+/* Signature copied verbatim from struct link_service::add_remote_sink. */
+static struct dc_sink *dm_mst_test_add_remote_sink(
+		struct dc_link *link,
+		const uint8_t *edid,
+		unsigned int len,
+		struct dc_sink_init_data *init_data)
+{
+	struct dc_sink *sink = dm_mst_test_next_remote_sink;
+
+	dm_mst_test_add_remote_sink_calls++;
+
+	if (sink) {
+		sink->link = link;
+		sink->sink_signal = init_data->sink_signal;
+		link->sink_count++;
+	}
+
+	return sink;
+}
+
+/* Signature copied verbatim from struct link_service::remove_remote_sink. */
+static void dm_mst_test_remove_remote_sink(struct dc_link *link, struct dc_sink *sink)
+{
+	dm_mst_test_remove_remote_sink_calls++;
+	dm_mst_test_removed_sink = sink;
+
+	if (link->sink_count)
+		link->sink_count--;
+}
+
+/*
+ * dc_sink_release() frees the sink with kfree(), so these must not come from
+ * the KUnit managed allocator.
+ */
+static struct dc_sink *dm_mst_test_alloc_sink(struct kunit *test)
+{
+	struct dc_sink *sink = kzalloc_obj(*sink);
+
+	KUNIT_ASSERT_NOT_NULL(test, sink);
+	kref_init(&sink->refcount);
+
+	return sink;
+}
+
+struct dm_mst_test_child {
+	struct amdgpu_device *adev;
+	struct amdgpu_dm_connector *aconnector;
+	struct amdgpu_dm_connector *root;
+	struct drm_dp_mst_port *port;
+	struct dc_link *link;
+};
+
+/*
+ * Build an MST downstream connector hanging off a root connector. The topology
+ * manager is left empty (no mst_primary) so the DRM MST helpers take their
+ * "port is gone" paths instead of requiring a real branch device.
+ */
+static void dm_mst_test_init_child(struct kunit *test, struct dm_mst_test_child *child)
+{
+	struct link_service *link_srv;
+	struct amdgpu_device *adev;
+	struct dc *dc;
+	int ret;
+
+	adev = dm_kunit_alloc_adev(test);
+	ret = drmm_mode_config_init(&adev->ddev);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+	link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+	child->port = kunit_kzalloc(test, sizeof(*child->port), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc);
+	KUNIT_ASSERT_NOT_NULL(test, link_srv);
+	KUNIT_ASSERT_NOT_NULL(test, child->port);
+
+	link_srv->add_remote_sink = dm_mst_test_add_remote_sink;
+	link_srv->remove_remote_sink = dm_mst_test_remove_remote_sink;
+	dc->link_srv = link_srv;
+
+	child->adev = adev;
+	child->link = dm_kunit_alloc_link(test);
+	child->link->dc = dc;
+
+	child->root = dm_kunit_alloc_connector(test, adev, child->link);
+	child->root->mst_mgr.dev = &adev->ddev;
+	mutex_init(&child->root->mst_mgr.lock);
+	drm_modeset_lock_init(&child->root->mst_mgr.base.lock);
+
+	child->port->mgr = &child->root->mst_mgr;
+	child->port->aux.name = "dm_mst_test_port_aux";
+	child->port->aux.transfer = dm_mst_test_aux_transfer;
+	drm_dp_aux_init(&child->port->aux);
+	drm_dp_dpcd_set_probe(&child->port->aux, false);
+
+	child->aconnector = dm_kunit_alloc_connector(test, adev, child->link);
+	child->aconnector->mst_root = child->root;
+	child->aconnector->mst_output_port = child->port;
+
+	ret = drm_connector_init(&adev->ddev, &child->aconnector->base,
+				 &dm_mst_test_connector_funcs,
+				 DRM_MODE_CONNECTOR_DisplayPort);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	/* The MST (un)register helpers log through the connector's kernel device. */
+	child->aconnector->base.kdev = adev->ddev.dev;
+
+	memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd));
+	dm_mst_test_aux_transfer_override = 0;
+	dm_mst_test_aux_write_override = 0;
+	dm_mst_test_next_remote_sink = NULL;
+	dm_mst_test_removed_sink = NULL;
+	dm_mst_test_add_remote_sink_calls = 0;
+	dm_mst_test_remove_remote_sink_calls = 0;
+}
+
+static void dm_mst_test_fini_child(struct dm_mst_test_child *child)
+{
+	drm_edid_free(child->aconnector->drm_edid);
+	drm_edid_free(child->port->cached_edid);
+	drm_connector_cleanup(&child->aconnector->base);
+	drm_modeset_lock_fini(&child->root->mst_mgr.base.lock);
+	mutex_destroy(&child->root->mst_mgr.lock);
+}
+
+/* Tests for dm_dp_mst_get_modes */
+
+/**
+ * dm_mst_test_get_modes_no_edid_adds_default_sink - Test default remote sink
+ * @test: KUnit test context
+ *
+ * When the remote EDID cannot be read, dm_dp_mst_get_modes() must clear the
+ * MST_REMOTE_EDID status and register a default remote sink for the connector.
+ */
+static void dm_mst_test_get_modes_no_edid_adds_default_sink(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+	struct dc_sink *sink;
+
+	dm_mst_test_init_child(test, &child);
+	sink = dm_mst_test_alloc_sink(test);
+	dm_mst_test_next_remote_sink = sink;
+	child.aconnector->mst_status = MST_REMOTE_EDID;
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_get_modes(&child.aconnector->base), 0);
+
+	KUNIT_EXPECT_EQ(test, dm_mst_test_add_remote_sink_calls, 1U);
+	KUNIT_EXPECT_PTR_EQ(test, child.aconnector->dc_sink, sink);
+	KUNIT_EXPECT_PTR_EQ(test, sink->priv, (void *)child.aconnector);
+	KUNIT_EXPECT_EQ(test, child.aconnector->mst_status & MST_REMOTE_EDID, 0);
+
+	dc_sink_release(sink);
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_get_modes_no_edid_sink_alloc_fails - Test remote sink failure
+ * @test: KUnit test context
+ *
+ * If DC cannot add the default remote sink, dm_dp_mst_get_modes() must bail out
+ * with zero modes and leave the connector without a sink.
+ */
+static void dm_mst_test_get_modes_no_edid_sink_alloc_fails(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+
+	dm_mst_test_init_child(test, &child);
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_get_modes(&child.aconnector->base), 0);
+
+	KUNIT_EXPECT_EQ(test, dm_mst_test_add_remote_sink_calls, 1U);
+	KUNIT_EXPECT_NULL(test, child.aconnector->dc_sink);
+
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_get_modes_no_edid_keeps_existing_sink - Test cached sink reuse
+ * @test: KUnit test context
+ *
+ * With no readable EDID but an existing remote sink, dm_dp_mst_get_modes() must
+ * keep that sink and must not ask DC for another one.
+ */
+static void dm_mst_test_get_modes_no_edid_keeps_existing_sink(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+	struct dc_sink *sink;
+
+	dm_mst_test_init_child(test, &child);
+	sink = dm_mst_test_alloc_sink(test);
+	child.aconnector->dc_sink = sink;
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_get_modes(&child.aconnector->base), 0);
+
+	KUNIT_EXPECT_EQ(test, dm_mst_test_add_remote_sink_calls, 0U);
+	KUNIT_EXPECT_PTR_EQ(test, child.aconnector->dc_sink, sink);
+
+	dc_sink_release(sink);
+	dm_mst_test_fini_child(&child);
+}
+
 /*
  * Sideband connector with a live topology manager and the DOWN_REP ready bit
  * armed, so dm_handle_mst_sideband_msg_ready_event() reaches its ack path.
@@ -1491,6 +1703,10 @@ static struct kunit_case dm_mst_types_test_cases[] = {
 	KUNIT_CASE(dm_mst_test_atomic_check_no_old_crtc),
 	/* dm_dp_mst_detect tests */
 	KUNIT_CASE(dm_mst_test_detect_unregistered),
+	/* dm_dp_mst_get_modes tests */
+	KUNIT_CASE(dm_mst_test_get_modes_no_edid_adds_default_sink),
+	KUNIT_CASE(dm_mst_test_get_modes_no_edid_sink_alloc_fails),
+	KUNIT_CASE(dm_mst_test_get_modes_no_edid_keeps_existing_sink),
 	/* CONFIG_DRM_AMD_DC_FP disabled public paths */
 #if !defined(CONFIG_DRM_AMD_DC_FP)
 	KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 40/82] drm/amd/display: Test dm_dp_mst_get_modes with a remote EDID
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (38 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 39/82] drm/amd/display: Test dm_dp_mst_get_modes without a remote EDID IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 41/82] drm/amd/display: Test dm_dp_mst_detect DPCD probe and unplug IVAN.LIPSKI
                   ` (42 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_dp_mst_get_modes() covering the paths taken once
an EDID is available: a placeholder virtual sink is released and replaced
by a real remote sink, DC refusing that sink returns zero modes, the
content protection state is restored from the HDCP workqueue, and an EDID
read from a port still in the topology is cached with MST_REMOTE_EDID
flagged.

[HOW]
The tests reuse the MST downstream connector fixture and feed it a
minimal EDID base block. For the remote read the fixture topology gains a
branch device holding the port so drm_dp_mst_edid_read() can validate it
and return the cached EDID.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_mst_types_test.c          | 160 ++++++++++++++++++
 1 file changed, 160 insertions(+)

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 ee4e1f3963736..b74f0611dfe06 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
@@ -25,6 +25,7 @@
 #include "amdgpu.h"
 #include "amdgpu_mode.h"
 #include "amdgpu_dm.h"
+#include "amdgpu_dm_hdcp.h"
 #include "amdgpu_dm_mst_types.h"
 #include "amdgpu_dm_kunit_test_helpers.h"
 #include "inc/link_service.h"
@@ -1555,6 +1556,161 @@ static void dm_mst_test_get_modes_no_edid_keeps_existing_sink(struct kunit *test
 	dm_mst_test_fini_child(&child);
 }
 
+/* Minimal EDID base block: valid header, no extensions, correct checksum. */
+static const u8 dm_mst_test_edid[EDID_LENGTH] = {
+	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+	[EDID_LENGTH - 1] = 0x06,
+};
+
+/**
+ * dm_mst_test_get_modes_cached_edid_replaces_virtual_sink - Test EDID sink path
+ * @test: KUnit test context
+ *
+ * With a cached EDID and a placeholder virtual sink, dm_dp_mst_get_modes() must
+ * release the virtual sink, add a real remote sink built from the EDID, and
+ * update the connector's mode list.
+ */
+static void dm_mst_test_get_modes_cached_edid_replaces_virtual_sink(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+	struct dc_sink *virtual_sink;
+	struct dc_sink *sink;
+
+	dm_mst_test_init_child(test, &child);
+
+	child.aconnector->drm_edid = drm_edid_alloc(dm_mst_test_edid, sizeof(dm_mst_test_edid));
+	KUNIT_ASSERT_NOT_NULL(test, child.aconnector->drm_edid);
+
+	virtual_sink = dm_mst_test_alloc_sink(test);
+	virtual_sink->sink_signal = SIGNAL_TYPE_VIRTUAL;
+	child.aconnector->dc_sink = virtual_sink;
+
+	sink = dm_mst_test_alloc_sink(test);
+	dm_mst_test_next_remote_sink = sink;
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_get_modes(&child.aconnector->base), 0);
+
+	KUNIT_EXPECT_EQ(test, dm_mst_test_add_remote_sink_calls, 1U);
+	KUNIT_EXPECT_PTR_EQ(test, child.aconnector->dc_sink, sink);
+	KUNIT_EXPECT_EQ(test, sink->sink_signal, SIGNAL_TYPE_DISPLAY_PORT_MST);
+	KUNIT_EXPECT_PTR_EQ(test, sink->priv, (void *)child.aconnector);
+
+	dc_sink_release(sink);
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_get_modes_cached_edid_sink_alloc_fails - Test EDID sink failure
+ * @test: KUnit test context
+ *
+ * If DC cannot add the remote sink built from the cached EDID,
+ * dm_dp_mst_get_modes() must bail out with zero modes.
+ */
+static void dm_mst_test_get_modes_cached_edid_sink_alloc_fails(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+
+	dm_mst_test_init_child(test, &child);
+
+	child.aconnector->drm_edid = drm_edid_alloc(dm_mst_test_edid, sizeof(dm_mst_test_edid));
+	KUNIT_ASSERT_NOT_NULL(test, child.aconnector->drm_edid);
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_get_modes(&child.aconnector->base), 0);
+
+	KUNIT_EXPECT_EQ(test, dm_mst_test_add_remote_sink_calls, 1U);
+	KUNIT_EXPECT_NULL(test, child.aconnector->dc_sink);
+
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_get_modes_restores_hdcp_properties - Test HDCP property restore
+ * @test: KUnit test context
+ *
+ * A connector re-plugged at the same display index must have its content
+ * protection state restored from the HDCP workqueue when the remote sink is
+ * re-created.
+ */
+static void dm_mst_test_get_modes_restores_hdcp_properties(struct kunit *test)
+{
+	struct dm_connector_state *conn_state;
+	struct dm_mst_test_child child;
+	struct hdcp_workqueue *hdcp;
+	struct dc_sink *sink;
+	unsigned int index;
+
+	dm_mst_test_init_child(test, &child);
+
+	child.aconnector->drm_edid = drm_edid_alloc(dm_mst_test_edid, sizeof(dm_mst_test_edid));
+	KUNIT_ASSERT_NOT_NULL(test, child.aconnector->drm_edid);
+
+	/* drm_connector_cleanup() hands the state back to the DRM helper's kfree(). */
+	conn_state = kzalloc_obj(*conn_state);
+	hdcp = kunit_kzalloc(test, sizeof(*hdcp), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, conn_state);
+	KUNIT_ASSERT_NOT_NULL(test, hdcp);
+
+	index = child.aconnector->base.index;
+	hdcp->hdcp_content_type[index] = DRM_MODE_HDCP_CONTENT_TYPE1;
+	hdcp->content_protection[index] = DRM_MODE_CONTENT_PROTECTION_ENABLED;
+	child.adev->dm.hdcp_workqueue = hdcp;
+	child.aconnector->base.state = &conn_state->base;
+
+	sink = dm_mst_test_alloc_sink(test);
+	dm_mst_test_next_remote_sink = sink;
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_get_modes(&child.aconnector->base), 0);
+
+	KUNIT_EXPECT_PTR_EQ(test, child.aconnector->dc_sink, sink);
+	KUNIT_EXPECT_EQ(test, child.aconnector->base.state->hdcp_content_type,
+			(unsigned int)DRM_MODE_HDCP_CONTENT_TYPE1);
+	KUNIT_EXPECT_EQ(test, child.aconnector->base.state->content_protection,
+			(unsigned int)DRM_MODE_CONTENT_PROTECTION_ENABLED);
+
+	dc_sink_release(sink);
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_get_modes_reads_remote_edid - Test remote EDID caching
+ * @test: KUnit test context
+ *
+ * When the MST port is still in the topology, dm_dp_mst_get_modes() must read
+ * its EDID, cache it on the connector and flag MST_REMOTE_EDID.
+ */
+static void dm_mst_test_get_modes_reads_remote_edid(struct kunit *test)
+{
+	struct drm_dp_mst_branch *mstb;
+	struct dm_mst_test_child child;
+	struct dc_sink *sink;
+
+	dm_mst_test_init_child(test, &child);
+
+	/* Minimal topology so drm_dp_mst_edid_read() can validate the port. */
+	mstb = kunit_kzalloc(test, sizeof(*mstb), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, mstb);
+	mstb->mgr = &child.root->mst_mgr;
+	INIT_LIST_HEAD(&mstb->ports);
+	list_add(&child.port->next, &mstb->ports);
+	kref_init(&child.port->topology_kref);
+	child.root->mst_mgr.mst_primary = mstb;
+
+	child.port->cached_edid = drm_edid_alloc(dm_mst_test_edid, sizeof(dm_mst_test_edid));
+	KUNIT_ASSERT_NOT_NULL(test, child.port->cached_edid);
+
+	sink = dm_mst_test_alloc_sink(test);
+	dm_mst_test_next_remote_sink = sink;
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_get_modes(&child.aconnector->base), 0);
+
+	KUNIT_EXPECT_NOT_NULL(test, child.aconnector->drm_edid);
+	KUNIT_EXPECT_EQ(test, child.aconnector->mst_status & MST_REMOTE_EDID, (int)MST_REMOTE_EDID);
+	KUNIT_EXPECT_PTR_EQ(test, child.aconnector->dc_sink, sink);
+
+	dc_sink_release(sink);
+	dm_mst_test_fini_child(&child);
+}
+
 /*
  * Sideband connector with a live topology manager and the DOWN_REP ready bit
  * armed, so dm_handle_mst_sideband_msg_ready_event() reaches its ack path.
@@ -1707,6 +1863,10 @@ static struct kunit_case dm_mst_types_test_cases[] = {
 	KUNIT_CASE(dm_mst_test_get_modes_no_edid_adds_default_sink),
 	KUNIT_CASE(dm_mst_test_get_modes_no_edid_sink_alloc_fails),
 	KUNIT_CASE(dm_mst_test_get_modes_no_edid_keeps_existing_sink),
+	KUNIT_CASE(dm_mst_test_get_modes_cached_edid_replaces_virtual_sink),
+	KUNIT_CASE(dm_mst_test_get_modes_cached_edid_sink_alloc_fails),
+	KUNIT_CASE(dm_mst_test_get_modes_restores_hdcp_properties),
+	KUNIT_CASE(dm_mst_test_get_modes_reads_remote_edid),
 	/* CONFIG_DRM_AMD_DC_FP disabled public paths */
 #if !defined(CONFIG_DRM_AMD_DC_FP)
 	KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 41/82] drm/amd/display: Test dm_dp_mst_detect DPCD probe and unplug
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (39 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 40/82] drm/amd/display: Test dm_dp_mst_get_modes with " IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 42/82] drm/amd/display: Test MST connector register and unregister IVAN.LIPSKI
                   ` (41 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_dp_mst_detect() covering the DPCD revision probe
of a peer device, the fallback when both revision registers read back as
zero, the NAKed remote read that must leave the cached revision alone,
and the unplugged port whose disconnected status releases the remote sink
and resets the MST connector state.

[HOW]
The tests reuse the MST downstream connector fixture. Its empty topology
manager makes drm_dp_mst_detect_port() report a disconnected port, which
is what the unplug path needs, and the port AUX is served by the fake
DPCD store.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_mst_types_test.c          | 111 ++++++++++++++++++
 1 file changed, 111 insertions(+)

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 b74f0611dfe06..6c0d5d233dcf1 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
@@ -1711,6 +1711,113 @@ static void dm_mst_test_get_modes_reads_remote_edid(struct kunit *test)
 	dm_mst_test_fini_child(&child);
 }
 
+/* Tests for dm_dp_mst_detect */
+
+/**
+ * dm_mst_test_detect_reads_port_dpcd_rev - Test DPCD revision probing
+ * @test: KUnit test context
+ *
+ * For a peer device with an unknown DPCD revision, dm_dp_mst_detect() must probe
+ * DP_DP13_DPCD_REV over the port AUX and cache the returned revision.
+ */
+static void dm_mst_test_detect_reads_port_dpcd_rev(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+
+	dm_mst_test_init_child(test, &child);
+	child.port->pdt = DP_PEER_DEVICE_SST_SINK;
+	/* Both DP_DP13_DPCD_REV and DP_DPCD_REV alias to offset 0 in the fake. */
+	dm_mst_test_dpcd[0] = 0x13;
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_detect(&child.aconnector->base, NULL, false),
+			(int)connector_status_disconnected);
+
+	KUNIT_EXPECT_EQ(test, (int)child.port->dpcd_rev, 0x13);
+
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_detect_unknown_dpcd_rev - Test unreadable DPCD revision
+ * @test: KUnit test context
+ *
+ * When both DPCD revision registers read back as zero, dm_dp_mst_detect() must
+ * leave the cached revision cleared instead of reporting a bogus value.
+ */
+static void dm_mst_test_detect_unknown_dpcd_rev(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+
+	dm_mst_test_init_child(test, &child);
+	child.port->pdt = DP_PEER_DEVICE_SST_SINK;
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_detect(&child.aconnector->base, NULL, false),
+			(int)connector_status_disconnected);
+
+	KUNIT_EXPECT_EQ(test, (int)child.port->dpcd_rev, 0);
+
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_detect_dpcd_read_error - Test unreachable port DPCD
+ * @test: KUnit test context
+ *
+ * When the remote DPCD read is NAKed, dm_dp_mst_detect() must leave the cached
+ * revision untouched instead of storing a garbage value.
+ */
+static void dm_mst_test_detect_dpcd_read_error(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+
+	dm_mst_test_init_child(test, &child);
+	child.port->pdt = DP_PEER_DEVICE_SST_SINK;
+	dm_mst_test_aux_transfer_override = -EIO;
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_detect(&child.aconnector->base, NULL, false),
+			(int)connector_status_disconnected);
+
+	KUNIT_EXPECT_EQ(test, (int)child.port->dpcd_rev, 0);
+
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_detect_disconnect_releases_sink - Test unplug sink release
+ * @test: KUnit test context
+ *
+ * A port with no peer device must have its cached DPCD revision cleared, and the
+ * resulting disconnected status must release the remote sink and reset the MST
+ * connector state.
+ */
+static void dm_mst_test_detect_disconnect_releases_sink(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+	struct dc_sink *sink;
+
+	dm_mst_test_init_child(test, &child);
+	child.port->pdt = DP_PEER_DEVICE_NONE;
+	child.port->dpcd_rev = 0x14;
+
+	sink = dm_mst_test_alloc_sink(test);
+	child.aconnector->dc_sink = sink;
+	child.aconnector->dsc_aux = &child.port->aux;
+	child.aconnector->mst_local_bw = 1234;
+	child.link->sink_count = 1;
+
+	KUNIT_EXPECT_EQ(test, dm_dp_mst_detect(&child.aconnector->base, NULL, false),
+			(int)connector_status_disconnected);
+
+	KUNIT_EXPECT_EQ(test, (int)child.port->dpcd_rev, 0);
+	KUNIT_EXPECT_EQ(test, dm_mst_test_remove_remote_sink_calls, 1U);
+	KUNIT_EXPECT_PTR_EQ(test, dm_mst_test_removed_sink, sink);
+	KUNIT_EXPECT_NULL(test, child.aconnector->dc_sink);
+	KUNIT_EXPECT_NULL(test, child.aconnector->dsc_aux);
+	KUNIT_EXPECT_EQ(test, child.aconnector->mst_local_bw, 0U);
+
+	dm_mst_test_fini_child(&child);
+}
+
 /*
  * Sideband connector with a live topology manager and the DOWN_REP ready bit
  * armed, so dm_handle_mst_sideband_msg_ready_event() reaches its ack path.
@@ -1859,6 +1966,10 @@ static struct kunit_case dm_mst_types_test_cases[] = {
 	KUNIT_CASE(dm_mst_test_atomic_check_no_old_crtc),
 	/* dm_dp_mst_detect tests */
 	KUNIT_CASE(dm_mst_test_detect_unregistered),
+	KUNIT_CASE(dm_mst_test_detect_reads_port_dpcd_rev),
+	KUNIT_CASE(dm_mst_test_detect_unknown_dpcd_rev),
+	KUNIT_CASE(dm_mst_test_detect_dpcd_read_error),
+	KUNIT_CASE(dm_mst_test_detect_disconnect_releases_sink),
 	/* dm_dp_mst_get_modes tests */
 	KUNIT_CASE(dm_mst_test_get_modes_no_edid_adds_default_sink),
 	KUNIT_CASE(dm_mst_test_get_modes_no_edid_sink_alloc_fails),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 42/82] drm/amd/display: Test MST connector register and unregister
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (40 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 41/82] drm/amd/display: Test dm_dp_mst_detect DPCD probe and unplug IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 43/82] drm/amd/display: Test dm_dp_mst_connector_destroy IVAN.LIPSKI
                   ` (40 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_mst_connector_late_register() and
amdgpu_dm_mst_connector_early_unregister(), covering the remote AUX bus
registration, the unregister that only resets the MST status when no
remote sink is attached, and the unregister that removes the sink from
the DC link and clears the MST connector state.

[HOW]
The tests reuse the MST downstream connector fixture, which already
provides the kernel device the DRM MST helpers log through and the
modeset lock the unregister path takes.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_mst_types.c   |  6 +-
 .../display/amdgpu_dm/amdgpu_dm_mst_types.h   |  2 +
 .../tests/amdgpu_dm_mst_types_test.c          | 79 +++++++++++++++++++
 3 files changed, 85 insertions(+), 2 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 79e8952edf8c0..693fd782283bc 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
@@ -187,7 +187,7 @@ dm_dp_mst_connector_destroy(struct drm_connector *connector)
 	kfree(aconnector);
 }
 
-static int
+STATIC_IFN_KUNIT int
 amdgpu_dm_mst_connector_late_register(struct drm_connector *connector)
 {
 	struct amdgpu_dm_connector *amdgpu_dm_connector =
@@ -205,6 +205,7 @@ amdgpu_dm_mst_connector_late_register(struct drm_connector *connector)
 
 	return 0;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_mst_connector_late_register);
 
 
 STATIC_IFN_KUNIT void
@@ -218,7 +219,7 @@ amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector
 }
 EXPORT_IF_KUNIT(amdgpu_dm_mst_reset_mst_connector_setting);
 
-static void
+STATIC_IFN_KUNIT void
 amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector)
 {
 	struct amdgpu_dm_connector *aconnector =
@@ -251,6 +252,7 @@ amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector)
 	aconnector->mst_status = MST_STATUS_DEFAULT;
 	drm_modeset_unlock(&root->mst_mgr.base.lock);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_mst_connector_early_unregister);
 
 static const struct drm_connector_funcs dm_dp_mst_connector_funcs = {
 	.fill_modes = drm_helper_probe_single_connector_modes,
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 406dba95c4aec..63130226e67eb 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
@@ -119,6 +119,8 @@ int dm_dp_mst_atomic_check(struct drm_connector *connector,
 int dm_dp_mst_detect(struct drm_connector *connector,
 			     struct drm_modeset_acquire_ctx *ctx, bool force);
 int dm_dp_mst_get_modes(struct drm_connector *connector);
+int amdgpu_dm_mst_connector_late_register(struct drm_connector *connector);
+void amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector);
 #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 6c0d5d233dcf1..6d920bc783d54 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
@@ -1818,6 +1818,80 @@ static void dm_mst_test_detect_disconnect_releases_sink(struct kunit *test)
 	dm_mst_test_fini_child(&child);
 }
 
+/* Tests for amdgpu_dm_mst_connector_late_register */
+
+/**
+ * dm_mst_test_connector_late_register - Test MST connector late registration
+ * @test: KUnit test context
+ *
+ * amdgpu_dm_mst_connector_late_register() must register the port's remote AUX
+ * bus and report success.
+ */
+static void dm_mst_test_connector_late_register(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+
+	dm_mst_test_init_child(test, &child);
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_mst_connector_late_register(&child.aconnector->base), 0);
+
+	dm_mst_test_fini_child(&child);
+}
+
+/* Tests for amdgpu_dm_mst_connector_early_unregister */
+
+/**
+ * dm_mst_test_connector_early_unregister_no_sink - Test unregister without sink
+ * @test: KUnit test context
+ *
+ * With no remote sink attached, amdgpu_dm_mst_connector_early_unregister() must
+ * only reset the MST status.
+ */
+static void dm_mst_test_connector_early_unregister_no_sink(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+
+	dm_mst_test_init_child(test, &child);
+	child.aconnector->mst_status = MST_REMOTE_EDID;
+
+	amdgpu_dm_mst_connector_early_unregister(&child.aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, dm_mst_test_remove_remote_sink_calls, 0U);
+	KUNIT_EXPECT_EQ(test, (int)child.aconnector->mst_status, (int)MST_STATUS_DEFAULT);
+
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_connector_early_unregister_releases_sink - Test sink release
+ * @test: KUnit test context
+ *
+ * When the port leaves the topology, amdgpu_dm_mst_connector_early_unregister()
+ * must remove the remote sink from the link and reset the MST connector state.
+ */
+static void dm_mst_test_connector_early_unregister_releases_sink(struct kunit *test)
+{
+	struct dm_mst_test_child child;
+	struct dc_sink *sink;
+
+	dm_mst_test_init_child(test, &child);
+
+	sink = dm_mst_test_alloc_sink(test);
+	child.aconnector->dc_sink = sink;
+	child.aconnector->vc_full_pbn = 42;
+	child.link->sink_count = 1;
+
+	amdgpu_dm_mst_connector_early_unregister(&child.aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, dm_mst_test_remove_remote_sink_calls, 1U);
+	KUNIT_EXPECT_PTR_EQ(test, dm_mst_test_removed_sink, sink);
+	KUNIT_EXPECT_NULL(test, child.aconnector->dc_sink);
+	KUNIT_EXPECT_EQ(test, child.aconnector->vc_full_pbn, 0U);
+	KUNIT_EXPECT_EQ(test, (int)child.aconnector->mst_status, (int)MST_STATUS_DEFAULT);
+
+	dm_mst_test_fini_child(&child);
+}
+
 /*
  * Sideband connector with a live topology manager and the DOWN_REP ready bit
  * armed, so dm_handle_mst_sideband_msg_ready_event() reaches its ack path.
@@ -1978,6 +2052,11 @@ static struct kunit_case dm_mst_types_test_cases[] = {
 	KUNIT_CASE(dm_mst_test_get_modes_cached_edid_sink_alloc_fails),
 	KUNIT_CASE(dm_mst_test_get_modes_restores_hdcp_properties),
 	KUNIT_CASE(dm_mst_test_get_modes_reads_remote_edid),
+	/* amdgpu_dm_mst_connector_late_register tests */
+	KUNIT_CASE(dm_mst_test_connector_late_register),
+	/* amdgpu_dm_mst_connector_early_unregister tests */
+	KUNIT_CASE(dm_mst_test_connector_early_unregister_no_sink),
+	KUNIT_CASE(dm_mst_test_connector_early_unregister_releases_sink),
 	/* CONFIG_DRM_AMD_DC_FP disabled public paths */
 #if !defined(CONFIG_DRM_AMD_DC_FP)
 	KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 43/82] drm/amd/display: Test dm_dp_mst_connector_destroy
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (41 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 42/82] drm/amd/display: Test MST connector register and unregister IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 44/82] drm/amd/display: Test plane state duplicate and destroy IVAN.LIPSKI
                   ` (39 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_dp_mst_connector_destroy() covering the teardown
of a connector that never had a remote sink, and the teardown that first
removes the remote sink from the DC link.

[HOW]
The function frees both the connector and the MST port, so the test
allocates them outside the KUnit managed allocator. The parent branch
device takes an extra malloc reference so it survives the port teardown.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_mst_types.c   |  3 +-
 .../display/amdgpu_dm/amdgpu_dm_mst_types.h   |  1 +
 .../tests/amdgpu_dm_mst_types_test.c          | 95 +++++++++++++++++++
 3 files changed, 98 insertions(+), 1 deletion(-)

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 693fd782283bc..bce605fd6cdf1 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
@@ -168,7 +168,7 @@ STATIC_IFN_KUNIT ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux,
 }
 EXPORT_IF_KUNIT(dm_dp_aux_transfer);
 
-static void
+STATIC_IFN_KUNIT void
 dm_dp_mst_connector_destroy(struct drm_connector *connector)
 {
 	struct amdgpu_dm_connector *aconnector =
@@ -186,6 +186,7 @@ dm_dp_mst_connector_destroy(struct drm_connector *connector)
 	drm_dp_mst_put_port_malloc(aconnector->mst_output_port);
 	kfree(aconnector);
 }
+EXPORT_IF_KUNIT(dm_dp_mst_connector_destroy);
 
 STATIC_IFN_KUNIT int
 amdgpu_dm_mst_connector_late_register(struct drm_connector *connector)
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 63130226e67eb..50dca0ef98fea 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
@@ -121,6 +121,7 @@ int dm_dp_mst_detect(struct drm_connector *connector,
 int dm_dp_mst_get_modes(struct drm_connector *connector);
 int amdgpu_dm_mst_connector_late_register(struct drm_connector *connector);
 void amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector);
+void dm_dp_mst_connector_destroy(struct drm_connector *connector);
 #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 6d920bc783d54..064af0022fb62 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
@@ -1892,6 +1892,98 @@ static void dm_mst_test_connector_early_unregister_releases_sink(struct kunit *t
 	dm_mst_test_fini_child(&child);
 }
 
+/* Tests for dm_dp_mst_connector_destroy */
+
+/*
+ * dm_dp_mst_connector_destroy() frees both the connector and the MST port, so
+ * they are allocated outside the KUnit managed allocator. The parent branch
+ * device takes an extra malloc reference so it survives the port teardown.
+ */
+static struct amdgpu_dm_connector *
+dm_mst_test_alloc_destroyable_connector(struct kunit *test, struct dm_mst_test_child *child)
+{
+	struct amdgpu_dm_connector *aconnector;
+	struct drm_dp_mst_branch *mstb;
+	struct drm_dp_mst_port *port;
+	int ret;
+
+	aconnector = kzalloc_obj(*aconnector);
+	port = kzalloc_obj(*port);
+	mstb = kunit_kzalloc(test, sizeof(*mstb), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, aconnector);
+	KUNIT_ASSERT_NOT_NULL(test, port);
+	KUNIT_ASSERT_NOT_NULL(test, mstb);
+
+	mstb->mgr = &child->root->mst_mgr;
+	kref_init(&mstb->malloc_kref);
+	kref_get(&mstb->malloc_kref);
+
+	port->mgr = &child->root->mst_mgr;
+	port->parent = mstb;
+	kref_init(&port->malloc_kref);
+
+	aconnector->dc_link = child->link;
+	aconnector->mst_root = child->root;
+	aconnector->mst_output_port = port;
+
+	ret = drm_connector_init(&child->adev->ddev, &aconnector->base,
+				 &dm_mst_test_connector_funcs,
+				 DRM_MODE_CONNECTOR_DisplayPort);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	return aconnector;
+}
+
+/**
+ * dm_mst_test_connector_destroy_no_sink - Test destroy without a remote sink
+ * @test: KUnit test context
+ *
+ * dm_dp_mst_connector_destroy() must clean up the DRM connector and drop the MST
+ * port reference even when no remote sink was ever attached.
+ */
+static void dm_mst_test_connector_destroy_no_sink(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector;
+	struct dm_mst_test_child child;
+
+	dm_mst_test_init_child(test, &child);
+	aconnector = dm_mst_test_alloc_destroyable_connector(test, &child);
+
+	dm_dp_mst_connector_destroy(&aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, dm_mst_test_remove_remote_sink_calls, 0U);
+
+	dm_mst_test_fini_child(&child);
+}
+
+/**
+ * dm_mst_test_connector_destroy_releases_sink - Test destroy releases the sink
+ * @test: KUnit test context
+ *
+ * dm_dp_mst_connector_destroy() must remove the remote sink from the DC link
+ * before tearing down the connector.
+ */
+static void dm_mst_test_connector_destroy_releases_sink(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector;
+	struct dm_mst_test_child child;
+	struct dc_sink *sink;
+
+	dm_mst_test_init_child(test, &child);
+	aconnector = dm_mst_test_alloc_destroyable_connector(test, &child);
+
+	sink = dm_mst_test_alloc_sink(test);
+	aconnector->dc_sink = sink;
+	child.link->sink_count = 1;
+
+	dm_dp_mst_connector_destroy(&aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, dm_mst_test_remove_remote_sink_calls, 1U);
+	KUNIT_EXPECT_PTR_EQ(test, dm_mst_test_removed_sink, sink);
+
+	dm_mst_test_fini_child(&child);
+}
+
 /*
  * Sideband connector with a live topology manager and the DOWN_REP ready bit
  * armed, so dm_handle_mst_sideband_msg_ready_event() reaches its ack path.
@@ -2057,6 +2149,9 @@ static struct kunit_case dm_mst_types_test_cases[] = {
 	/* amdgpu_dm_mst_connector_early_unregister tests */
 	KUNIT_CASE(dm_mst_test_connector_early_unregister_no_sink),
 	KUNIT_CASE(dm_mst_test_connector_early_unregister_releases_sink),
+	/* dm_dp_mst_connector_destroy tests */
+	KUNIT_CASE(dm_mst_test_connector_destroy_no_sink),
+	KUNIT_CASE(dm_mst_test_connector_destroy_releases_sink),
 	/* CONFIG_DRM_AMD_DC_FP disabled public paths */
 #if !defined(CONFIG_DRM_AMD_DC_FP)
 	KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 44/82] drm/amd/display: Test plane state duplicate and destroy
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (42 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 43/82] drm/amd/display: Test dm_dp_mst_connector_destroy IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 45/82] drm/amd/display: Test modifier list de-duplication IVAN.LIPSKI
                   ` (38 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_plane_drm_plane_duplicate_state() and
amdgpu_dm_plane_drm_plane_destroy_state() covering the resource paths.
Duplicating a state shares every color blob and takes an extra reference
on the DC plane state, and destroying one drops a reference on each.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    | 113 ++++++++++++++++++
 1 file changed, 113 insertions(+)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
index ba97092c7bb85..17789b3845b77 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
@@ -10,11 +10,14 @@
 #include <drm/drm_blend.h>
 #include "link_enc_cfg.h"
 #include "amdgpu_dm_plane.h"
+#include "amdgpu_dm_kunit_test_helpers.h"
 #include "amdgpu_rlc.h"
 #include "gc/gc_11_0_0_offset.h"
 #include "gc/gc_11_0_0_sh_mask.h"
 #include <drm/amdgpu_drm.h>
+#include <drm/drm_mode_config.h>
 #include <drm/drm_plane.h>
+#include <drm/drm_property.h>
 
 struct dm_test_dcc_cap_ctx {
 	bool callback_ret;
@@ -3001,6 +3004,78 @@ static void dm_test_plane_duplicate_state_copies_fields(struct kunit *test)
 	kfree(dup_state);
 }
 
+/*
+ * Attach a blob to every color property of @state. Each blob starts with a
+ * single reference that the tested function is expected to drop or share.
+ */
+static void dm_test_attach_color_blobs(struct kunit *test, struct drm_device *dev,
+				       struct dm_plane_state *state)
+{
+	struct drm_property_blob **blobs[] = {
+		&state->degamma_lut, &state->ctm, &state->lut3d,
+		&state->shaper_lut, &state->blend_lut,
+	};
+	u32 blob_data = 0;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(blobs); i++) {
+		*blobs[i] = drm_property_create_blob(dev, sizeof(blob_data), &blob_data);
+		KUNIT_ASSERT_NOT_ERR_OR_NULL(test, *blobs[i]);
+	}
+}
+
+/**
+ * dm_test_plane_duplicate_state_copies_resources() - Verify blob and DC state sharing.
+ * @test: KUnit test context.
+ *
+ * Verify amdgpu_dm_plane_drm_plane_duplicate_state() shares the DC plane state
+ * and every color blob with the duplicate, taking an extra reference on each.
+ */
+static void dm_test_plane_duplicate_state_copies_resources(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+	struct dm_plane_state *old_state;
+	struct drm_plane_state *dup_base;
+	struct dm_plane_state *dup_state;
+	struct dc_plane_state *dc_plane_state;
+	struct drm_plane *plane;
+
+	KUNIT_ASSERT_EQ(test, drmm_mode_config_init(&adev->ddev), 0);
+
+	plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL);
+	old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+	dc_plane_state = kunit_kzalloc(test, sizeof(*dc_plane_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, plane);
+	KUNIT_ASSERT_NOT_NULL(test, old_state);
+	KUNIT_ASSERT_NOT_NULL(test, dc_plane_state);
+
+	kref_init(&dc_plane_state->refcount);
+	old_state->dc_state = dc_plane_state;
+	dm_test_attach_color_blobs(test, &adev->ddev, old_state);
+	plane->state = &old_state->base;
+
+	dup_base = amdgpu_dm_plane_drm_plane_duplicate_state(plane);
+	KUNIT_ASSERT_NOT_NULL(test, dup_base);
+
+	dup_state = to_dm_plane_state(dup_base);
+	KUNIT_EXPECT_PTR_EQ(test, dup_state->dc_state, dc_plane_state);
+	KUNIT_EXPECT_EQ(test, kref_read(&dc_plane_state->refcount), 2U);
+	KUNIT_EXPECT_PTR_EQ(test, dup_state->degamma_lut, old_state->degamma_lut);
+	KUNIT_EXPECT_PTR_EQ(test, dup_state->ctm, old_state->ctm);
+	KUNIT_EXPECT_PTR_EQ(test, dup_state->shaper_lut, old_state->shaper_lut);
+	KUNIT_EXPECT_PTR_EQ(test, dup_state->lut3d, old_state->lut3d);
+	KUNIT_EXPECT_PTR_EQ(test, dup_state->blend_lut, old_state->blend_lut);
+
+	/* Drops the duplicate's references; the originals are released below. */
+	amdgpu_dm_plane_drm_plane_destroy_state(plane, dup_base);
+
+	drm_property_blob_put(old_state->degamma_lut);
+	drm_property_blob_put(old_state->ctm);
+	drm_property_blob_put(old_state->lut3d);
+	drm_property_blob_put(old_state->shaper_lut);
+	drm_property_blob_put(old_state->blend_lut);
+}
+
 /**
  * dm_test_plane_destroy_state_minimal() - Verify destroy of a minimal state.
  * @test: KUnit test context.
@@ -3024,6 +3099,42 @@ static void dm_test_plane_destroy_state_minimal(struct kunit *test)
 	amdgpu_dm_plane_drm_plane_destroy_state(plane, &dm_plane_state->base);
 }
 
+/**
+ * dm_test_plane_destroy_state_releases_resources() - Verify blob and DC state release.
+ * @test: KUnit test context.
+ *
+ * Verify amdgpu_dm_plane_drm_plane_destroy_state() drops a reference on every
+ * attached color blob and on the DC plane state.
+ */
+static void dm_test_plane_destroy_state_releases_resources(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+	struct dm_plane_state *dm_plane_state;
+	struct dc_plane_state *dc_plane_state;
+	struct drm_plane *plane;
+
+	KUNIT_ASSERT_EQ(test, drmm_mode_config_init(&adev->ddev), 0);
+
+	plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL);
+	dc_plane_state = kunit_kzalloc(test, sizeof(*dc_plane_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, plane);
+	KUNIT_ASSERT_NOT_NULL(test, dc_plane_state);
+
+	/* destroy_state frees the state itself, so use a plain allocation. */
+	dm_plane_state = kzalloc(sizeof(*dm_plane_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dm_plane_state);
+
+	/* Hold an extra reference so the release does not free KUnit memory. */
+	kref_init(&dc_plane_state->refcount);
+	kref_get(&dc_plane_state->refcount);
+	dm_plane_state->dc_state = dc_plane_state;
+	dm_test_attach_color_blobs(test, &adev->ddev, dm_plane_state);
+
+	amdgpu_dm_plane_drm_plane_destroy_state(plane, &dm_plane_state->base);
+
+	KUNIT_EXPECT_EQ(test, kref_read(&dc_plane_state->refcount), 1U);
+}
+
 static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_is_video_format() */
 	KUNIT_CASE(dm_test_plane_is_video_format_known_video),
@@ -3102,8 +3213,10 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	KUNIT_CASE(dm_test_plane_reset_initializes_state),
 	/* amdgpu_dm_plane_drm_plane_duplicate_state() */
 	KUNIT_CASE(dm_test_plane_duplicate_state_copies_fields),
+	KUNIT_CASE(dm_test_plane_duplicate_state_copies_resources),
 	/* amdgpu_dm_plane_drm_plane_destroy_state() */
 	KUNIT_CASE(dm_test_plane_destroy_state_minimal),
+	KUNIT_CASE(dm_test_plane_destroy_state_releases_resources),
 	/* amdgpu_dm_plane_add_modifier() */
 	KUNIT_CASE(dm_test_add_modifier_appends_value),
 	KUNIT_CASE(dm_test_add_modifier_grows_capacity),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 45/82] drm/amd/display: Test modifier list de-duplication
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (43 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 44/82] drm/amd/display: Test plane state duplicate and destroy IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 46/82] drm/amd/display: Test GFX6-8 tiling info from modifiers IVAN.LIPSKI
                   ` (37 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_plane_add_modifier_dedup() covering a
modifier already present in the list, which is not appended a second
time, a new modifier, which is appended, and a NULL list left over from
an earlier allocation failure, which is a no-op.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    | 53 +++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
index 17789b3845b77..4017dc1d6a771 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
@@ -3135,6 +3135,56 @@ static void dm_test_plane_destroy_state_releases_resources(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, kref_read(&dc_plane_state->refcount), 1U);
 }
 
+/**
+ * dm_test_add_modifier_dedup_skips_duplicate() - Verify duplicates are not appended.
+ * @test: KUnit test context.
+ *
+ * Verify if a modifier already present in the list is not appended a second
+ * time.
+ */
+static void dm_test_add_modifier_dedup_skips_duplicate(struct kunit *test)
+{
+	u64 size = 2;
+	u64 cap = 4;
+	u64 *mods = kmalloc_array(cap, sizeof(*mods), GFP_KERNEL);
+
+	KUNIT_ASSERT_NOT_NULL(test, mods);
+	mods[0] = 0xAAULL;
+	mods[1] = 0xBBULL;
+
+	amdgpu_dm_plane_add_modifier_dedup(&mods, &size, &cap, 0xAAULL);
+	KUNIT_EXPECT_EQ(test, size, 2ULL);
+
+	amdgpu_dm_plane_add_modifier_dedup(&mods, &size, &cap, 0xBBULL);
+	KUNIT_EXPECT_EQ(test, size, 2ULL);
+
+	amdgpu_dm_plane_add_modifier_dedup(&mods, &size, &cap, 0xCCULL);
+	KUNIT_ASSERT_EQ(test, size, 3ULL);
+	KUNIT_EXPECT_EQ(test, mods[2], 0xCCULL);
+
+	kfree(mods);
+}
+
+/**
+ * dm_test_add_modifier_dedup_noop_when_mods_null() - Verify NULL list is a no-op.
+ * @test: KUnit test context.
+ *
+ * Verify if the de-duplicating append does nothing when the modifier list is
+ * NULL after an earlier allocation failure.
+ */
+static void dm_test_add_modifier_dedup_noop_when_mods_null(struct kunit *test)
+{
+	u64 size = 3;
+	u64 cap = 7;
+	u64 *mods = NULL;
+
+	amdgpu_dm_plane_add_modifier_dedup(&mods, &size, &cap, 0x55ULL);
+
+	KUNIT_EXPECT_PTR_EQ(test, mods, NULL);
+	KUNIT_EXPECT_EQ(test, size, 3ULL);
+	KUNIT_EXPECT_EQ(test, cap, 7ULL);
+}
+
 static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_is_video_format() */
 	KUNIT_CASE(dm_test_plane_is_video_format_known_video),
@@ -3221,6 +3271,9 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	KUNIT_CASE(dm_test_add_modifier_appends_value),
 	KUNIT_CASE(dm_test_add_modifier_grows_capacity),
 	KUNIT_CASE(dm_test_add_modifier_noop_when_mods_null),
+	/* amdgpu_dm_plane_add_modifier_dedup() */
+	KUNIT_CASE(dm_test_add_modifier_dedup_skips_duplicate),
+	KUNIT_CASE(dm_test_add_modifier_dedup_noop_when_mods_null),
 	/* amdgpu_dm_plane_fill_gfx9_tiling_info_from_device() */
 	KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_pre_10_3),
 	KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_10_3_plus),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 46/82] drm/amd/display: Test GFX6-8 tiling info from modifiers
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (44 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 45/82] drm/amd/display: Test modifier list de-duplication IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 47/82] drm/amd/display: Test GFX6-8 tile mode and tile split lookups IVAN.LIPSKI
                   ` (36 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier()
covering the linear, micro tiled and macro tiled modifiers, and the
rejection of a modifier that is not of the GFX6 tile version.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    | 97 +++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
index 4017dc1d6a771..aaec65d7431ce 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
@@ -3185,6 +3185,98 @@ static void dm_test_add_modifier_dedup_noop_when_mods_null(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, cap, 7ULL);
 }
 
+static int dm_test_gfx6_tiling(struct dc_tiling_info *tiling_info, u64 modifier)
+{
+	return amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier(tiling_info, modifier);
+}
+
+/**
+ * dm_test_fill_gfx6_tiling_info_linear() - Verify linear modifier decoding.
+ * @test: KUnit test context.
+ *
+ * Verify if a linear modifier maps to the generic linear array mode.
+ */
+static void dm_test_fill_gfx6_tiling_info_linear(struct kunit *test)
+{
+	struct dc_tiling_info tiling_info = {0};
+
+	KUNIT_EXPECT_EQ(test, dm_test_gfx6_tiling(&tiling_info, DRM_FORMAT_MOD_LINEAR), 0);
+	KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.array_mode, (int)DC_ARRAY_LINEAR_GENERAL);
+	KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion8);
+}
+
+/**
+ * dm_test_fill_gfx6_tiling_info_rejects() - Verify unsupported modifiers fail.
+ * @test: KUnit test context.
+ *
+ * Verify if non-AMD modifiers and AMD modifiers from a newer tile version are
+ * rejected.
+ */
+static void dm_test_fill_gfx6_tiling_info_rejects(struct kunit *test)
+{
+	struct dc_tiling_info tiling_info = {0};
+	u64 not_amd_mod = DRM_FORMAT_MOD_VENDOR_AMD;
+	u64 gfx9_mod = AMD_FMT_MOD | AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9);
+
+	KUNIT_EXPECT_EQ(test, dm_test_gfx6_tiling(&tiling_info, not_amd_mod), -EINVAL);
+	KUNIT_EXPECT_EQ(test, dm_test_gfx6_tiling(&tiling_info, gfx9_mod), -EINVAL);
+}
+
+/**
+ * dm_test_fill_gfx6_tiling_info_1d() - Verify micro tiled modifier decoding.
+ * @test: KUnit test context.
+ *
+ * Verify if a 1D tiled modifier stops before decoding the macro tiling fields.
+ */
+static void dm_test_fill_gfx6_tiling_info_1d(struct kunit *test)
+{
+	struct dc_tiling_info tiling_info = {0};
+	u64 modifier = AMD_FMT_MOD |
+		       AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX6) |
+		       AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1) |
+		       AMD_FMT_MOD_SET(MICROTILE, AMD_FMT_MOD_MICROTILE_DISPLAY) |
+		       AMD_FMT_MOD_SET(NUM_BANKS, 3);
+
+	KUNIT_EXPECT_EQ(test, dm_test_gfx6_tiling(&tiling_info, modifier), 0);
+	KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.array_mode,
+			(int)AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1);
+	KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion8);
+	KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 0U);
+}
+
+/**
+ * dm_test_fill_gfx6_tiling_info_2d() - Verify macro tiled modifier decoding.
+ * @test: KUnit test context.
+ *
+ * Verify if a 2D tiled modifier decodes the full set of macro tiling fields.
+ */
+static void dm_test_fill_gfx6_tiling_info_2d(struct kunit *test)
+{
+	struct dc_tiling_info tiling_info = {0};
+	u64 modifier = AMD_FMT_MOD |
+		       AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX6) |
+		       AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) |
+		       AMD_FMT_MOD_SET(MICROTILE, AMD_FMT_MOD_MICROTILE_DISPLAY) |
+		       AMD_FMT_MOD_SET(PIPE_CONFIG, 5) |
+		       AMD_FMT_MOD_SET(TILE_SPLIT, 4) |
+		       AMD_FMT_MOD_SET(BANK_WIDTH, 1) |
+		       AMD_FMT_MOD_SET(BANK_HEIGHT, 2) |
+		       AMD_FMT_MOD_SET(MACRO_TILE_ASPECT, 3) |
+		       AMD_FMT_MOD_SET(NUM_BANKS, 2);
+
+	KUNIT_EXPECT_EQ(test, dm_test_gfx6_tiling(&tiling_info, modifier), 0);
+	KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.array_mode,
+			(int)AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1);
+	KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.tile_mode,
+			(int)AMD_FMT_MOD_MICROTILE_DISPLAY);
+	KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 5U);
+	KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_split, 4U);
+	KUNIT_EXPECT_EQ(test, tiling_info.gfx8.bank_width, 1U);
+	KUNIT_EXPECT_EQ(test, tiling_info.gfx8.bank_height, 2U);
+	KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_aspect, 3U);
+	KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 2U);
+}
+
 static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_is_video_format() */
 	KUNIT_CASE(dm_test_plane_is_video_format_known_video),
@@ -3274,6 +3366,11 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_add_modifier_dedup() */
 	KUNIT_CASE(dm_test_add_modifier_dedup_skips_duplicate),
 	KUNIT_CASE(dm_test_add_modifier_dedup_noop_when_mods_null),
+	/* amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier() */
+	KUNIT_CASE(dm_test_fill_gfx6_tiling_info_linear),
+	KUNIT_CASE(dm_test_fill_gfx6_tiling_info_rejects),
+	KUNIT_CASE(dm_test_fill_gfx6_tiling_info_1d),
+	KUNIT_CASE(dm_test_fill_gfx6_tiling_info_2d),
 	/* amdgpu_dm_plane_fill_gfx9_tiling_info_from_device() */
 	KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_pre_10_3),
 	KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_10_3_plus),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 47/82] drm/amd/display: Test GFX6-8 tile mode and tile split lookups
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (45 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 46/82] drm/amd/display: Test GFX6-8 tiling info from modifiers IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 48/82] drm/amd/display: Test GFX6-8 modifier calculation IVAN.LIPSKI
                   ` (35 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_plane_get_gfx6_tile_idx(),
amdgpu_dm_plane_calc_gfx7_tile_split() and
amdgpu_dm_plane_get_gfx7_macro_tile_idx() covering the per-family tile
mode index selection, the tile split derived from the sample split
factor with its 256 byte and memory row size clamps, and the macro tile
index capped by the tile split.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.c   | 18 +++--
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.h   |  5 ++
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    | 81 +++++++++++++++++++
 3 files changed, 97 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
index 824ef3ce5de0a..d6b80c380db85 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
@@ -504,9 +504,9 @@ static void amdgpu_dm_plane_add_gfx10_1_modifiers(const struct amdgpu_device *ad
  * present at specific indices.
  * See SiLib::HwlSetupTileInfo() and CiLib::HwlSetupTileInfo() in addrlib.
  */
-static u32 amdgpu_dm_plane_get_gfx6_tile_idx(const struct amdgpu_device *adev,
-					 const u32 bpp,
-					 const enum array_mode_values arr)
+STATIC_IFN_KUNIT u32 amdgpu_dm_plane_get_gfx6_tile_idx(const struct amdgpu_device *adev,
+						       const u32 bpp,
+						       const enum array_mode_values arr)
 {
 	/* Assume that the microtile mode is DISPLAY. */
 
@@ -527,6 +527,7 @@ static u32 amdgpu_dm_plane_get_gfx6_tile_idx(const struct amdgpu_device *adev,
 		return 12;
 	}
 }
+EXPORT_IF_KUNIT(amdgpu_dm_plane_get_gfx6_tile_idx);
 
 /**
  * amdgpu_dm_plane_calc_gfx7_tile_split() - Calculate tile split on GFX7-8
@@ -542,9 +543,9 @@ static u32 amdgpu_dm_plane_get_gfx6_tile_idx(const struct amdgpu_device *adev,
  * can be calculated. The TILE_SPLIT field is only used for the depth micro tile mode.
  * See CiLib::HwlComputeMacroModeIndex() in addrlib.
  */
-static u32 amdgpu_dm_plane_calc_gfx7_tile_split(const struct amdgpu_device *adev,
-						const u32 bpp,
-						const u32 gb_tile_mode)
+STATIC_IFN_KUNIT u32 amdgpu_dm_plane_calc_gfx7_tile_split(const struct amdgpu_device *adev,
+							  const u32 bpp,
+							  const u32 gb_tile_mode)
 {
 	/* Assume 2D_TILED_THIN1 mode with non-DEPTH microtiles */
 	const u32 sample_split = (gb_tile_mode >> 25) & 0x3;
@@ -557,6 +558,7 @@ static u32 amdgpu_dm_plane_calc_gfx7_tile_split(const struct amdgpu_device *adev
 		     256,
 		     adev->gfx.config.mem_row_size_in_kb * 1024);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_plane_calc_gfx7_tile_split);
 
 /**
  * amdgpu_dm_plane_get_gfx7_macro_tile_idx() - Get macro tile mode index on GFX7-8
@@ -570,7 +572,8 @@ static u32 amdgpu_dm_plane_calc_gfx7_tile_split(const struct amdgpu_device *adev
  * present at specific indices.
  * See CiLib::HwlComputeMacroModeIndex() in addrlib.
  */
-static u32 amdgpu_dm_plane_get_gfx7_macro_tile_idx(const u32 bpp, const u32 tile_split_bytes)
+STATIC_IFN_KUNIT u32 amdgpu_dm_plane_get_gfx7_macro_tile_idx(const u32 bpp,
+							     const u32 tile_split_bytes)
 {
 	const u32 thickness = 1;
 	const u32 tile_size_pixels = 8 * 8;
@@ -582,6 +585,7 @@ static u32 amdgpu_dm_plane_get_gfx7_macro_tile_idx(const u32 bpp, const u32 tile
 
 	return macro_tile_idx;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_plane_get_gfx7_macro_tile_idx);
 
 /**
  * amdgpu_dm_plane_calc_gfx6_mod() - Calculate a DRM format modifier for GFX6-8
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
index 092ade738ce98..7c5d0a7dadb1d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
@@ -125,5 +125,10 @@ void amdgpu_dm_plane_add_modifier_dedup(uint64_t **mods, uint64_t *size,
 					uint64_t *cap, uint64_t mod);
 int amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier(struct dc_tiling_info *tiling_info,
 							uint64_t modifier);
+u32 amdgpu_dm_plane_get_gfx6_tile_idx(const struct amdgpu_device *adev, const u32 bpp,
+				      const enum array_mode_values arr);
+u32 amdgpu_dm_plane_calc_gfx7_tile_split(const struct amdgpu_device *adev, const u32 bpp,
+					 const u32 gb_tile_mode);
+u32 amdgpu_dm_plane_get_gfx7_macro_tile_idx(const u32 bpp, const u32 tile_split_bytes);
 #endif
 #endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
index aaec65d7431ce..420b053d2e3dc 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
@@ -3277,6 +3277,81 @@ static void dm_test_fill_gfx6_tiling_info_2d(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 2U);
 }
 
+/**
+ * dm_test_get_gfx6_tile_idx() - Verify GFX6-8 tile mode index selection.
+ * @test: KUnit test context.
+ *
+ * Verify if the micro tiled mode maps to a fixed index, GFX7+ uses a single
+ * macro tiled index, and GFX6 selects the index from the bits per pixel.
+ */
+static void dm_test_get_gfx6_tile_idx(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+
+	adev->family = AMDGPU_FAMILY_VI;
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_plane_get_gfx6_tile_idx(adev, 32, DC_ARRAY_1D_TILED_THIN1),
+			9U);
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_plane_get_gfx6_tile_idx(adev, 32, DC_ARRAY_2D_TILED_THIN1),
+			10U);
+
+	adev->family = AMDGPU_FAMILY_SI;
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_plane_get_gfx6_tile_idx(adev, 8, DC_ARRAY_2D_TILED_THIN1),
+			10U);
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_plane_get_gfx6_tile_idx(adev, 16, DC_ARRAY_2D_TILED_THIN1),
+			11U);
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_plane_get_gfx6_tile_idx(adev, 32, DC_ARRAY_2D_TILED_THIN1),
+			12U);
+}
+
+/**
+ * dm_test_calc_gfx7_tile_split() - Verify GFX7-8 tile split calculation.
+ * @test: KUnit test context.
+ *
+ * Verify if the tile split is derived from the sample split factor and clamped
+ * to the 256 byte minimum and the memory row size maximum.
+ */
+static void dm_test_calc_gfx7_tile_split(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+
+	adev->gfx.config.mem_row_size_in_kb = 4;
+
+	/* 8 bpp gives 64 bytes, clamped up to the 256 byte minimum. */
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_calc_gfx7_tile_split(adev, 8, 0u << 25), 256U);
+	/* 32 bpp with a sample split factor of 2. */
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_calc_gfx7_tile_split(adev, 32, 1u << 25), 512U);
+
+	/* 64 bpp with a sample split factor of 8, clamped to the memory row size. */
+	adev->gfx.config.mem_row_size_in_kb = 1;
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_calc_gfx7_tile_split(adev, 64, 3u << 25), 1024U);
+}
+
+/**
+ * dm_test_get_gfx7_macro_tile_idx() - Verify GFX7-8 macro tile index selection.
+ * @test: KUnit test context.
+ *
+ * Verify if the macro tile index is the log2 of the tile size in 64 byte units,
+ * clamped to the tile split.
+ */
+static void dm_test_get_gfx7_macro_tile_idx(struct kunit *test)
+{
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_gfx7_macro_tile_idx(8, 1024), 0U);
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_gfx7_macro_tile_idx(32, 1024), 2U);
+	/* 64 bpp needs 512 bytes but the tile split caps it at 256. */
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_gfx7_macro_tile_idx(64, 256), 2U);
+}
+
 static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_is_video_format() */
 	KUNIT_CASE(dm_test_plane_is_video_format_known_video),
@@ -3371,6 +3446,12 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	KUNIT_CASE(dm_test_fill_gfx6_tiling_info_rejects),
 	KUNIT_CASE(dm_test_fill_gfx6_tiling_info_1d),
 	KUNIT_CASE(dm_test_fill_gfx6_tiling_info_2d),
+	/* amdgpu_dm_plane_get_gfx6_tile_idx() */
+	KUNIT_CASE(dm_test_get_gfx6_tile_idx),
+	/* amdgpu_dm_plane_calc_gfx7_tile_split() */
+	KUNIT_CASE(dm_test_calc_gfx7_tile_split),
+	/* amdgpu_dm_plane_get_gfx7_macro_tile_idx() */
+	KUNIT_CASE(dm_test_get_gfx7_macro_tile_idx),
 	/* amdgpu_dm_plane_fill_gfx9_tiling_info_from_device() */
 	KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_pre_10_3),
 	KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_10_3_plus),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 48/82] drm/amd/display: Test GFX6-8 modifier calculation
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (46 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 47/82] drm/amd/display: Test GFX6-8 tile mode and tile split lookups IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 49/82] drm/amd/display: Test GFX6-8 modifier list generation IVAN.LIPSKI
                   ` (34 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_plane_calc_gfx6_mod() and
amdgpu_dm_plane_gfx6_format_mod_supported() covering the micro tiled
early return, the GFX7-8 macro tile mode table lookup, the GFX6 tile
mode register fields, and the rejection of foreign, DCC, newer tile
version, GFX9+ and non-displayable modifiers.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.c   |  14 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.h   |   4 +
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    | 146 ++++++++++++++++++
 3 files changed, 158 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
index d6b80c380db85..b736cd13baaeb 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
@@ -597,9 +597,9 @@ EXPORT_IF_KUNIT(amdgpu_dm_plane_get_gfx7_macro_tile_idx);
  * Select suitable micro and macro tile modes for the given bits per pixel,
  * and calculate the corresponding DRM format modifier.
  */
-static u64 amdgpu_dm_plane_calc_gfx6_mod(const struct amdgpu_device *adev,
-					 const u32 bpp,
-					 const enum array_mode_values arr)
+STATIC_IFN_KUNIT u64 amdgpu_dm_plane_calc_gfx6_mod(const struct amdgpu_device *adev,
+						   const u32 bpp,
+						   const enum array_mode_values arr)
 {
 	u32 array_mode, micro_tile_mode, tile_split_bytes;
 	u32 gb_macrotile_mode, macrotile_idx;
@@ -649,6 +649,7 @@ static u64 amdgpu_dm_plane_calc_gfx6_mod(const struct amdgpu_device *adev,
 		AMD_FMT_MOD_SET(MACRO_TILE_ASPECT, (gb_macrotile_mode >> 4) & 0x3) |
 		AMD_FMT_MOD_SET(NUM_BANKS, (gb_macrotile_mode >> 6) & 0x3);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_plane_calc_gfx6_mod);
 
 /**
  * amdgpu_dm_plane_gfx6_format_mod_supported() - Check if a modifier is supported on GFX6-8
@@ -660,9 +661,9 @@ static u64 amdgpu_dm_plane_calc_gfx6_mod(const struct amdgpu_device *adev,
  * On GFX6-8, not all DRM format modifier can be used with all image formats.
  * Check whether the specified modifier is supported with the given bits per pixel value.
  */
-static bool amdgpu_dm_plane_gfx6_format_mod_supported(const struct amdgpu_device *adev,
-						      const u32 bpp,
-						      const u64 modifier)
+STATIC_IFN_KUNIT bool amdgpu_dm_plane_gfx6_format_mod_supported(const struct amdgpu_device *adev,
+								const u32 bpp,
+								const u64 modifier)
 {
 	const u32 array_mode = AMD_FMT_MOD_GET(TILE, modifier);
 	const u32 micro_tile_mode = AMD_FMT_MOD_GET(MICROTILE, modifier);
@@ -701,6 +702,7 @@ static bool amdgpu_dm_plane_gfx6_format_mod_supported(const struct amdgpu_device
 	/* Verify that the modifier is the same that we'd expose for this bpp */
 	return amdgpu_dm_plane_calc_gfx6_mod(adev, bpp, array_mode) == modifier;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_plane_gfx6_format_mod_supported);
 
 /**
  * amdgpu_dm_plane_add_gfx6_modifiers() - Expose modifiers for GFX6-8
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
index 7c5d0a7dadb1d..b396b3ea78bae 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
@@ -130,5 +130,9 @@ u32 amdgpu_dm_plane_get_gfx6_tile_idx(const struct amdgpu_device *adev, const u3
 u32 amdgpu_dm_plane_calc_gfx7_tile_split(const struct amdgpu_device *adev, const u32 bpp,
 					 const u32 gb_tile_mode);
 u32 amdgpu_dm_plane_get_gfx7_macro_tile_idx(const u32 bpp, const u32 tile_split_bytes);
+u64 amdgpu_dm_plane_calc_gfx6_mod(const struct amdgpu_device *adev, const u32 bpp,
+				  const enum array_mode_values arr);
+bool amdgpu_dm_plane_gfx6_format_mod_supported(const struct amdgpu_device *adev, const u32 bpp,
+					       const u64 modifier);
 #endif
 #endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
index 420b053d2e3dc..312fe34eb7105 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
@@ -3277,6 +3277,44 @@ static void dm_test_fill_gfx6_tiling_info_2d(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 2U);
 }
 
+static u64 dm_test_gfx6_mod(u32 tile, u32 pipe_config, u32 tile_split, u32 bank_width,
+			    u32 bank_height, u32 aspect, u32 num_banks)
+{
+	return AMD_FMT_MOD |
+	       AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX6) |
+	       AMD_FMT_MOD_SET(TILE, tile) |
+	       AMD_FMT_MOD_SET(MICROTILE, AMD_FMT_MOD_MICROTILE_DISPLAY) |
+	       AMD_FMT_MOD_SET(PIPE_CONFIG, pipe_config) |
+	       AMD_FMT_MOD_SET(TILE_SPLIT, tile_split) |
+	       AMD_FMT_MOD_SET(BANK_WIDTH, bank_width) |
+	       AMD_FMT_MOD_SET(BANK_HEIGHT, bank_height) |
+	       AMD_FMT_MOD_SET(MACRO_TILE_ASPECT, aspect) |
+	       AMD_FMT_MOD_SET(NUM_BANKS, num_banks);
+}
+
+static struct amdgpu_device *dm_test_alloc_gfx7_device(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+
+	adev->family = AMDGPU_FAMILY_VI;
+	adev->gfx.config.mem_row_size_in_kb = 4;
+	/* 2D_TILED_THIN1 array mode (bits 5:2) and pipe config 2 (bits 10:6). */
+	adev->gfx.config.tile_mode_array[10] = (4u << 2) | (2u << 6);
+	/*
+	 * Bank width (bits 1:0), bank height (bits 3:2), macro tile aspect
+	 * (bits 5:4) and num banks (bits 7:6). Entries 1 and 2 are identical so
+	 * the 16 and 32 bpp modifiers collide and the dedup path is taken.
+	 */
+	adev->gfx.config.macrotile_mode_array[1] = 0xb9u;
+	adev->gfx.config.macrotile_mode_array[2] = 0xb9u;
+	adev->gfx.config.macrotile_mode_array[3] = 0x2au;
+
+	return adev;
+}
+
 /**
  * dm_test_get_gfx6_tile_idx() - Verify GFX6-8 tile mode index selection.
  * @test: KUnit test context.
@@ -3352,6 +3390,108 @@ static void dm_test_get_gfx7_macro_tile_idx(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_gfx7_macro_tile_idx(64, 256), 2U);
 }
 
+/**
+ * dm_test_calc_gfx6_mod_1d() - Verify the micro tiled GFX6-8 modifier.
+ * @test: KUnit test context.
+ *
+ * Verify if a micro tiled array mode returns early with only the base modifier
+ * fields set.
+ */
+static void dm_test_calc_gfx6_mod_1d(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_gfx7_device(test);
+	u64 expected = AMD_FMT_MOD |
+		       AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX6) |
+		       AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1) |
+		       AMD_FMT_MOD_SET(MICROTILE, AMD_FMT_MOD_MICROTILE_DISPLAY);
+
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_plane_calc_gfx6_mod(adev, 0, DC_ARRAY_1D_TILED_THIN1),
+			expected);
+}
+
+/**
+ * dm_test_calc_gfx6_mod_gfx7() - Verify the macro tiled GFX7-8 modifier.
+ * @test: KUnit test context.
+ *
+ * Verify if the modifier combines the tile mode pipe config and computed tile
+ * split with the bank fields taken from the macro tile mode table.
+ */
+static void dm_test_calc_gfx6_mod_gfx7(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_gfx7_device(test);
+
+	/* 32 bpp: 256 byte tile split (log2 of 4 units), macro tile mode 2. */
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_plane_calc_gfx6_mod(adev, 32, DC_ARRAY_2D_TILED_THIN1),
+			dm_test_gfx6_mod(AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1, 2, 2, 1, 2, 3, 2));
+
+	/* 64 bpp: 512 byte tile split (log2 of 8 units), macro tile mode 3. */
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_plane_calc_gfx6_mod(adev, 64, DC_ARRAY_2D_TILED_THIN1),
+			dm_test_gfx6_mod(AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1, 2, 3, 2, 2, 2, 0));
+}
+
+/**
+ * dm_test_calc_gfx6_mod_si() - Verify the macro tiled GFX6 modifier.
+ * @test: KUnit test context.
+ *
+ * Verify if GFX6 takes the tile split and bank fields directly from the tile
+ * mode register instead of the macro tile mode table.
+ */
+static void dm_test_calc_gfx6_mod_si(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+
+	adev->family = AMDGPU_FAMILY_SI;
+	/* 2D_TILED_THIN1, pipe config 3, 256 byte tile split, banks 1/2/3/1. */
+	adev->gfx.config.tile_mode_array[12] = (4u << 2) | (3u << 6) | (2u << 11) |
+					       (1u << 14) | (2u << 16) | (3u << 18) |
+					       (1u << 20);
+
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_plane_calc_gfx6_mod(adev, 32, DC_ARRAY_2D_TILED_THIN1),
+			dm_test_gfx6_mod(AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1, 3, 2, 1, 2, 3, 1));
+}
+
+/**
+ * dm_test_gfx6_format_mod_supported() - Verify GFX6-8 modifier support checks.
+ * @test: KUnit test context.
+ *
+ * Verify if only the canonical modifier for a given bits per pixel is accepted,
+ * and that non-AMD modifiers, newer tile versions, GFX9+ devices, DCC, and
+ * non-displayable tile modes are rejected.
+ */
+static void dm_test_gfx6_format_mod_supported(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_gfx7_device(test);
+	u64 modifier = amdgpu_dm_plane_calc_gfx6_mod(adev, 32, DC_ARRAY_2D_TILED_THIN1);
+	u64 gfx9_mod = AMD_FMT_MOD | AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9);
+	/* Neither 1D nor 2D THIN array mode. */
+	u64 bad_tile_mod = AMD_FMT_MOD | AMD_FMT_MOD_SET(TILE, 3);
+	/* Non-displayable micro tile mode. */
+	u64 bad_micro_mod = AMD_FMT_MOD |
+			    AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) |
+			    AMD_FMT_MOD_SET(MICROTILE, AMD_FMT_MOD_MICROTILE_THIN);
+	u64 not_amd_mod = DRM_FORMAT_MOD_VENDOR_AMD;
+	u64 dcc_mod = modifier | AMD_FMT_MOD_SET(DCC, 1);
+
+	KUNIT_EXPECT_TRUE(test, amdgpu_dm_plane_gfx6_format_mod_supported(adev, 32, modifier));
+
+	KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_gfx6_format_mod_supported(adev, 64, modifier));
+	KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_gfx6_format_mod_supported(adev, 32, not_amd_mod));
+	KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_gfx6_format_mod_supported(adev, 32, gfx9_mod));
+	KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_gfx6_format_mod_supported(adev, 32, dcc_mod));
+	KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_gfx6_format_mod_supported(adev, 32, bad_tile_mod));
+	KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_gfx6_format_mod_supported(adev, 32, bad_micro_mod));
+
+	adev->family = AMDGPU_FAMILY_AI;
+	KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_gfx6_format_mod_supported(adev, 32, modifier));
+}
+
 static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_is_video_format() */
 	KUNIT_CASE(dm_test_plane_is_video_format_known_video),
@@ -3452,6 +3592,12 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	KUNIT_CASE(dm_test_calc_gfx7_tile_split),
 	/* amdgpu_dm_plane_get_gfx7_macro_tile_idx() */
 	KUNIT_CASE(dm_test_get_gfx7_macro_tile_idx),
+	/* amdgpu_dm_plane_calc_gfx6_mod() */
+	KUNIT_CASE(dm_test_calc_gfx6_mod_1d),
+	KUNIT_CASE(dm_test_calc_gfx6_mod_gfx7),
+	KUNIT_CASE(dm_test_calc_gfx6_mod_si),
+	/* amdgpu_dm_plane_gfx6_format_mod_supported() */
+	KUNIT_CASE(dm_test_gfx6_format_mod_supported),
 	/* amdgpu_dm_plane_fill_gfx9_tiling_info_from_device() */
 	KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_pre_10_3),
 	KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_10_3_plus),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 49/82] drm/amd/display: Test GFX6-8 modifier list generation
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (47 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 48/82] drm/amd/display: Test GFX6-8 modifier calculation IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 50/82] drm/amd/display: Test framebuffer prepare and cleanup IVAN.LIPSKI
                   ` (33 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_plane_get_plane_modifiers() and
amdgpu_dm_plane_format_mod_supported() on a GFX6-8 device. The exposed
list holds one macro tiled modifier per distinct bits per pixel plus a
micro tiled and a linear one, and a listed modifier is validated through
the GFX6-8 specific check.

Add dm_test_mods_count() for the list length and use it in the existing
sentinel scans.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    | 83 +++++++++++++++++--
 1 file changed, 76 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
index 312fe34eb7105..a0e7d012ccbab 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
@@ -1191,29 +1191,39 @@ static void dm_test_fill_blending_global_alpha_dcn42(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, global_alpha_value, 0x800);
 }
 
+/* Modifier lists are terminated by DRM_FORMAT_MOD_INVALID, which is not counted. */
+static int dm_test_mods_count(const u64 *mods)
+{
+	int i = 0;
+
+	while (mods[i] != DRM_FORMAT_MOD_INVALID)
+		i++;
+
+	return i;
+}
+
 static void dm_test_expect_mods_terminated(struct kunit *test, struct amdgpu_device *adev)
 {
 	u64 *mods = NULL;
 	int ret;
-	int i;
+	int count;
 
 	ret = amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_PRIMARY, &mods);
 	KUNIT_ASSERT_EQ(test, ret, 0);
 	KUNIT_ASSERT_NOT_NULL(test, mods);
 
-	for (i = 0; mods[i] != DRM_FORMAT_MOD_INVALID; i++)
-		;
-
-	KUNIT_EXPECT_GT(test, i, 0);
-	KUNIT_EXPECT_EQ(test, mods[i - 1], DRM_FORMAT_MOD_LINEAR);
+	count = dm_test_mods_count(mods);
+	KUNIT_EXPECT_GT(test, count, 0);
+	KUNIT_EXPECT_EQ(test, mods[count - 1], DRM_FORMAT_MOD_LINEAR);
 	kfree(mods);
 }
 
 static bool dm_test_mods_contain(const u64 *mods, u64 expected)
 {
+	int count = dm_test_mods_count(mods);
 	int i;
 
-	for (i = 0; mods[i] != DRM_FORMAT_MOD_INVALID; i++) {
+	for (i = 0; i < count; i++) {
 		if (mods[i] == expected)
 			return true;
 	}
@@ -3492,6 +3502,63 @@ static void dm_test_gfx6_format_mod_supported(struct kunit *test)
 	KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_gfx6_format_mod_supported(adev, 32, modifier));
 }
 
+/**
+ * dm_test_get_plane_modifiers_gfx6() - Verify GFX6-8 modifier list generation.
+ * @test: KUnit test context.
+ *
+ * Verify if a GFX6-8 family device exposes one macro tiled modifier per
+ * distinct bits per pixel plus a micro tiled one, and that the duplicate 32 bpp
+ * modifier is de-duplicated.
+ */
+static void dm_test_get_plane_modifiers_gfx6(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_gfx7_device(test);
+	u64 *mods = dm_test_get_primary_mods(test, adev);
+	u64 mod_1d = AMD_FMT_MOD |
+		     AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX6) |
+		     AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1) |
+		     AMD_FMT_MOD_SET(MICROTILE, AMD_FMT_MOD_MICROTILE_DISPLAY);
+
+	/* 16 bpp, 64 bpp, micro tiled and linear; the 32 bpp duplicate is dropped. */
+	KUNIT_EXPECT_EQ(test, dm_test_mods_count(mods), 4);
+	KUNIT_EXPECT_EQ(test, mods[0],
+			dm_test_gfx6_mod(AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1, 2, 2, 1, 2, 3, 2));
+	KUNIT_EXPECT_EQ(test, mods[1],
+			dm_test_gfx6_mod(AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1, 2, 3, 2, 2, 2, 0));
+	KUNIT_EXPECT_EQ(test, mods[2], mod_1d);
+	KUNIT_EXPECT_EQ(test, mods[3], DRM_FORMAT_MOD_LINEAR);
+
+	kfree(mods);
+}
+
+/**
+ * dm_test_format_mod_supported_gfx6() - Verify dispatch to the GFX6-8 check.
+ * @test: KUnit test context.
+ *
+ * Verify if a listed GFX6 tile version modifier is validated through the
+ * GFX6-8 specific helper.
+ */
+static void dm_test_format_mod_supported_gfx6(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_test_alloc_gfx7_device(test);
+	struct drm_plane *plane;
+	u64 listed_mod = amdgpu_dm_plane_calc_gfx6_mod(adev, 32, DC_ARRAY_2D_TILED_THIN1);
+
+	plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, plane);
+
+	plane->dev = &adev->ddev;
+	plane->modifiers = &listed_mod;
+	plane->modifier_count = 1;
+
+	KUNIT_EXPECT_TRUE(test,
+			  amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB8888,
+							       listed_mod));
+	KUNIT_EXPECT_FALSE(test,
+			   amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB16161616,
+								listed_mod));
+}
+
 static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_is_video_format() */
 	KUNIT_CASE(dm_test_plane_is_video_format_known_video),
@@ -3511,6 +3578,7 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	KUNIT_CASE(dm_test_get_plane_formats_overlay_universal_cap),
 	/* amdgpu_dm_plane_get_plane_modifiers() */
 	KUNIT_CASE(dm_test_get_plane_modifiers),
+	KUNIT_CASE(dm_test_get_plane_modifiers_gfx6),
 	KUNIT_CASE(dm_test_get_plane_modifiers_gfx9),
 	KUNIT_CASE(dm_test_get_plane_modifiers_rv),
 	KUNIT_CASE(dm_test_get_plane_modifiers_rv_constant_encode),
@@ -3535,6 +3603,7 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_format_mod_supported() */
 	KUNIT_CASE(dm_test_format_mod_supported),
 	KUNIT_CASE(dm_test_format_mod_supported_d_swizzle_reject),
+	KUNIT_CASE(dm_test_format_mod_supported_gfx6),
 	/* amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers() */
 	KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_from_modifiers),
 	KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_block0),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 50/82] drm/amd/display: Test framebuffer prepare and cleanup
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (48 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 49/82] drm/amd/display: Test GFX6-8 modifier list generation IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 51/82] drm/amd/display: Test cursor update and async plane update IVAN.LIPSKI
                   ` (32 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_plane_helper_prepare_fb() and
amdgpu_dm_plane_helper_cleanup_fb() covering the early return taken when
the plane state carries no framebuffer, so no buffer object is pinned or
unpinned.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.c   | 10 +++--
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.h   |  2 +
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    | 40 +++++++++++++++++++
 3 files changed, 48 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
index b736cd13baaeb..096e1a5e37ad0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
@@ -1223,8 +1223,8 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev,
 }
 EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_plane_buffer_attributes);
 
-static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane,
-					     struct drm_plane_state *new_state)
+STATIC_IFN_KUNIT int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane,
+						       struct drm_plane_state *new_state)
 {
 	struct amdgpu_framebuffer *afb;
 	struct drm_gem_object *obj;
@@ -1321,9 +1321,10 @@ static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane,
 	amdgpu_bo_unreserve(rbo);
 	return r;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_plane_helper_prepare_fb);
 
-static void amdgpu_dm_plane_helper_cleanup_fb(struct drm_plane *plane,
-					      struct drm_plane_state *old_state)
+STATIC_IFN_KUNIT void amdgpu_dm_plane_helper_cleanup_fb(struct drm_plane *plane,
+							struct drm_plane_state *old_state)
 {
 	struct amdgpu_bo *rbo;
 	int r;
@@ -1342,6 +1343,7 @@ static void amdgpu_dm_plane_helper_cleanup_fb(struct drm_plane *plane,
 	amdgpu_bo_unreserve(rbo);
 	amdgpu_bo_unref(&rbo);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_plane_helper_cleanup_fb);
 
 STATIC_IFN_KUNIT void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev,
 								   struct drm_framebuffer *fb,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
index b396b3ea78bae..d8ebe48abc9fa 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
@@ -134,5 +134,7 @@ u64 amdgpu_dm_plane_calc_gfx6_mod(const struct amdgpu_device *adev, const u32 bp
 				  const enum array_mode_values arr);
 bool amdgpu_dm_plane_gfx6_format_mod_supported(const struct amdgpu_device *adev, const u32 bpp,
 					       const u64 modifier);
+int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, struct drm_plane_state *new_state);
+void amdgpu_dm_plane_helper_cleanup_fb(struct drm_plane *plane, struct drm_plane_state *old_state);
 #endif
 #endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
index a0e7d012ccbab..7b3f0759cc1f9 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
@@ -3559,6 +3559,42 @@ static void dm_test_format_mod_supported_gfx6(struct kunit *test)
 								listed_mod));
 }
 
+/**
+ * dm_test_helper_prepare_fb_no_fb() - Verify prepare_fb without a framebuffer.
+ * @test: KUnit test context.
+ *
+ * Verify if prepare_fb succeeds without touching any buffer object when the new
+ * plane state has no framebuffer bound.
+ */
+static void dm_test_helper_prepare_fb_no_fb(struct kunit *test)
+{
+	struct drm_plane_state state = {0};
+	struct drm_plane *plane;
+
+	plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, plane);
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_helper_prepare_fb(plane, &state), 0);
+}
+
+/**
+ * dm_test_helper_cleanup_fb_no_fb() - Verify cleanup_fb without a framebuffer.
+ * @test: KUnit test context.
+ *
+ * Verify if cleanup_fb returns without unpinning anything when the old plane
+ * state has no framebuffer bound.
+ */
+static void dm_test_helper_cleanup_fb_no_fb(struct kunit *test)
+{
+	struct drm_plane_state state = {0};
+	struct drm_plane *plane;
+
+	plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, plane);
+
+	amdgpu_dm_plane_helper_cleanup_fb(plane, &state);
+}
+
 static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_is_video_format() */
 	KUNIT_CASE(dm_test_plane_is_video_format_known_video),
@@ -3624,6 +3660,10 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	KUNIT_CASE(dm_test_helper_check_state_small_viewport_height),
 	KUNIT_CASE(dm_test_helper_check_state_bottom_clipped_height),
 	KUNIT_CASE(dm_test_helper_check_state_scaling_caps),
+	/* amdgpu_dm_plane_helper_prepare_fb() */
+	KUNIT_CASE(dm_test_helper_prepare_fb_no_fb),
+	/* amdgpu_dm_plane_helper_cleanup_fb() */
+	KUNIT_CASE(dm_test_helper_cleanup_fb_no_fb),
 	/* amdgpu_dm_plane_atomic_async_check() */
 	KUNIT_CASE(dm_test_atomic_async_check_rejects),
 	KUNIT_CASE(dm_test_atomic_async_check_overlay_cursor),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 51/82] drm/amd/display: Test cursor update and async plane update
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (49 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 50/82] drm/amd/display: Test framebuffer prepare and cleanup IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 52/82] drm/amd/display: Remove RMCM tetrahedral cube from dc_plane_state IVAN.LIPSKI
                   ` (31 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_plane_handle_cursor_update() and
amdgpu_dm_plane_atomic_async_update() covering the return taken when the
cursor plane has no framebuffer, and the copy of the new position and
size into the plane state ahead of the cursor update.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.c   |  6 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.h   |  1 +
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    | 90 +++++++++++++++++++
 3 files changed, 95 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
index 096e1a5e37ad0..0a5a7347238ec 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
@@ -1738,9 +1738,10 @@ void amdgpu_dm_plane_handle_cursor_update(struct drm_plane *plane,
 		mutex_unlock(&adev->dm.dc_lock);
 	}
 }
+EXPORT_IF_KUNIT(amdgpu_dm_plane_handle_cursor_update);
 
-static void amdgpu_dm_plane_atomic_async_update(struct drm_plane *plane,
-						struct drm_atomic_commit *state)
+STATIC_IFN_KUNIT void amdgpu_dm_plane_atomic_async_update(struct drm_plane *plane,
+							  struct drm_atomic_commit *state)
 {
 	struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
 									   plane);
@@ -1762,6 +1763,7 @@ static void amdgpu_dm_plane_atomic_async_update(struct drm_plane *plane,
 
 	amdgpu_dm_plane_handle_cursor_update(plane, old_state);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_plane_atomic_async_update);
 
 STATIC_IFN_KUNIT void amdgpu_dm_plane_panic_flush(struct drm_plane *plane)
 {
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
index d8ebe48abc9fa..18b500585bedd 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h
@@ -136,5 +136,6 @@ bool amdgpu_dm_plane_gfx6_format_mod_supported(const struct amdgpu_device *adev,
 					       const u64 modifier);
 int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, struct drm_plane_state *new_state);
 void amdgpu_dm_plane_helper_cleanup_fb(struct drm_plane *plane, struct drm_plane_state *old_state);
+void amdgpu_dm_plane_atomic_async_update(struct drm_plane *plane, struct drm_atomic_commit *state);
 #endif
 #endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
index 7b3f0759cc1f9..1b39f41c8414b 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c
@@ -3595,6 +3595,92 @@ static void dm_test_helper_cleanup_fb_no_fb(struct kunit *test)
 	amdgpu_dm_plane_helper_cleanup_fb(plane, &state);
 }
 
+/**
+ * dm_test_handle_cursor_update_no_fb() - Verify cursor update without framebuffers.
+ * @test: KUnit test context.
+ *
+ * Verify if the cursor update returns early, without touching the CRTC, when
+ * neither the new nor the old plane state has a framebuffer bound.
+ */
+static void dm_test_handle_cursor_update_no_fb(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct drm_plane_state old_state = {0};
+	struct drm_plane_state state = {0};
+	struct drm_plane *plane;
+
+	adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+	plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+	KUNIT_ASSERT_NOT_NULL(test, plane);
+
+	plane->dev = &adev->ddev;
+	plane->state = &state;
+
+	amdgpu_dm_plane_handle_cursor_update(plane, &old_state);
+}
+
+/**
+ * dm_test_atomic_async_update_copies_state() - Verify async cursor state copy.
+ * @test: KUnit test context.
+ *
+ * Verify if the async update swaps the framebuffer and copies the source and
+ * CRTC rectangles from the new state into the current plane state.
+ */
+static void dm_test_atomic_async_update_copies_state(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct drm_atomic_commit *state;
+	struct __drm_planes_state *planes;
+	struct drm_plane_state *cur_state;
+	struct drm_plane_state *new_state;
+	struct drm_plane_state *old_state;
+	struct amdgpu_framebuffer *afb;
+	struct drm_plane *plane;
+
+	adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+	planes = kunit_kzalloc(test, sizeof(*planes), GFP_KERNEL);
+	plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL);
+	cur_state = kunit_kzalloc(test, sizeof(*cur_state), GFP_KERNEL);
+	new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
+	old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+	afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, adev);
+	KUNIT_ASSERT_NOT_NULL(test, state);
+	KUNIT_ASSERT_NOT_NULL(test, planes);
+	KUNIT_ASSERT_NOT_NULL(test, plane);
+	KUNIT_ASSERT_NOT_NULL(test, cur_state);
+	KUNIT_ASSERT_NOT_NULL(test, new_state);
+	KUNIT_ASSERT_NOT_NULL(test, old_state);
+	KUNIT_ASSERT_NOT_NULL(test, afb);
+
+	plane->dev = &adev->ddev;
+	plane->index = 0;
+	plane->state = cur_state;
+	cur_state->fb = &afb->base;
+	new_state->src_x = 1 << 16;
+	new_state->src_y = 2 << 16;
+	new_state->src_w = 64 << 16;
+	new_state->src_h = 64 << 16;
+	new_state->crtc_x = 10;
+	new_state->crtc_y = 20;
+	new_state->crtc_w = 64;
+	new_state->crtc_h = 64;
+	state->planes = planes;
+	state->planes[0].new_state = new_state;
+	state->planes[0].old_state = old_state;
+
+	amdgpu_dm_plane_atomic_async_update(plane, state);
+
+	KUNIT_EXPECT_PTR_EQ(test, cur_state->fb, NULL);
+	KUNIT_EXPECT_PTR_EQ(test, new_state->fb, &afb->base);
+	KUNIT_EXPECT_EQ(test, cur_state->src_x, 1U << 16);
+	KUNIT_EXPECT_EQ(test, cur_state->src_h, 64U << 16);
+	KUNIT_EXPECT_EQ(test, cur_state->crtc_x, 10);
+	KUNIT_EXPECT_EQ(test, cur_state->crtc_h, 64U);
+}
+
 static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	/* amdgpu_dm_plane_is_video_format() */
 	KUNIT_CASE(dm_test_plane_is_video_format_known_video),
@@ -3664,6 +3750,10 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = {
 	KUNIT_CASE(dm_test_helper_prepare_fb_no_fb),
 	/* amdgpu_dm_plane_helper_cleanup_fb() */
 	KUNIT_CASE(dm_test_helper_cleanup_fb_no_fb),
+	/* amdgpu_dm_plane_handle_cursor_update() */
+	KUNIT_CASE(dm_test_handle_cursor_update_no_fb),
+	/* amdgpu_dm_plane_atomic_async_update() */
+	KUNIT_CASE(dm_test_atomic_async_update_copies_state),
 	/* amdgpu_dm_plane_atomic_async_check() */
 	KUNIT_CASE(dm_test_atomic_async_check_rejects),
 	KUNIT_CASE(dm_test_atomic_async_check_overlay_cursor),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 52/82] drm/amd/display: Remove RMCM tetrahedral cube from dc_plane_state
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (50 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 51/82] drm/amd/display: Test cursor update and async plane update IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 53/82] drm/amd/display: Cover mode_valid EDID mgmt path IVAN.LIPSKI
                   ` (30 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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,
	Sridevi Arvindekar

From: Rafal Ostrowski <rafal.ostrowski@amd.com>

[Why&How]
MCM and RMCM both took their cube from struct tetrahedral_params, which is
embedded by value in dc_3dlut and therefore in dc_plane_state and
dc.scratch. RMCM needs a 33x33x33 cube, but that cube is ~421KB, so
enabling it in the shared union - where tetrahedral_33 was left commented
out - would push sizeof(struct dc) past the kmalloc MAX_PAGE_ORDER limit
and break kunit and other allocations.
Drop the unused struct tetrahedral_33x33x33 definition and lut_dim.

Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Reviewed-by: Sridevi Arvindekar <sridevi.arvindekar@amd.com>
Signed-off-by: Rafal Ostrowski <rafal.ostrowski@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 drivers/gpu/drm/amd/display/dc/inc/hw/hw_shared.h | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/hw_shared.h b/drivers/gpu/drm/amd/display/dc/inc/hw/hw_shared.h
index 176d048a64e70..6109d22e10938 100644
--- a/drivers/gpu/drm/amd/display/dc/inc/hw/hw_shared.h
+++ b/drivers/gpu/drm/amd/display/dc/inc/hw/hw_shared.h
@@ -153,13 +153,6 @@ struct dc_rgb {
 	uint32_t blue;
 };
 
-struct tetrahedral_33x33x33 {
-	struct dc_rgb lut0[8985];
-	struct dc_rgb lut1[8984];
-	struct dc_rgb lut2[8984];
-	struct dc_rgb lut3[8984];
-};
-
 struct tetrahedral_17x17x17 {
 	struct dc_rgb lut0[1229];
 	struct dc_rgb lut1[1228];
@@ -182,14 +175,11 @@ enum lut_dimension {
 
 struct tetrahedral_params {
 	union {
-//TODO: Uncomment when in use.
-//		struct tetrahedral_33x33x33 tetrahedral_33;
 		struct tetrahedral_17x17x17 tetrahedral_17;
 		struct tetrahedral_9x9x9 tetrahedral_9;
 	};
 	bool use_tetrahedral_9;
 	bool use_12bits;
-	enum lut_dimension lut_dim;
 };
 
 /* arr_curve_points - regamma regions/segments specification
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 53/82] drm/amd/display: Cover mode_valid EDID mgmt path
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (51 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 52/82] drm/amd/display: Remove RMCM tetrahedral cube from dc_plane_state IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 54/82] drm/amd/display: Cover amdgpu_dm_fill_hdr_info_packet IVAN.LIPSKI
                   ` (29 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add a KUnit test driving amdgpu_dm_connector_mode_valid() through the
EDID-management branch for a forced-on connector.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)

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
index 23b83bbde73fd..fc16d3a35b653 100644
--- 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
@@ -6321,6 +6321,30 @@ static void dm_test_mode_valid_force_on_no_stream(struct kunit *test)
 			MODE_ERROR);
 }
 
+/**
+ * dm_test_mode_valid_edid_mgmt_forced - Test the forced-connector EDID refresh
+ * @test: The KUnit test context
+ *
+ * A forced connector with no emulated sink runs the one-time EDID mgmt refresh
+ * before validating; with no readable EDID no dc_sink appears and a non-on
+ * force still bails to MODE_ERROR.
+ */
+static void dm_test_mode_valid_edid_mgmt_forced(struct kunit *test)
+{
+	struct dm_test_edid_ctx *ctx =
+		dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct drm_display_mode *mode;
+
+	ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+
+	mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, mode);
+
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_connector_mode_valid(&ctx->aconnector->base, mode),
+			MODE_ERROR);
+}
+
 /**
  * dm_test_update_after_detect_mst_noop - Test MST connectors are left to drm_mst
  * @test: The KUnit test context
@@ -7178,6 +7202,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_mode_valid_dblscan_rejected),
 	KUNIT_CASE(dm_test_mode_valid_no_dc_sink),
 	KUNIT_CASE(dm_test_mode_valid_force_on_no_stream),
+	KUNIT_CASE(dm_test_mode_valid_edid_mgmt_forced),
 	/* amdgpu_dm_hdmi_cec_set_edid */
 	KUNIT_CASE(dm_test_hdmi_cec_set_edid_no_notifier),
 	/* amdgpu_dm_s3_handle_hdmi_cec */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 54/82] drm/amd/display: Cover amdgpu_dm_fill_hdr_info_packet
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (52 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 53/82] drm/amd/display: Cover mode_valid EDID mgmt path IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 55/82] drm/amd/display: Cover amdgpu_dm_connector_atomic_check IVAN.LIPSKI
                   ` (28 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for amdgpu_dm_fill_hdr_info_packet() covering the HDMI
and DisplayPort HDR paths, an unsupported connector signal, and invalid
HDR metadata.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 122 ++++++++++++++++++
 1 file changed, 122 insertions(+)

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
index fc16d3a35b653..5c701c6b0511d 100644
--- 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
@@ -1654,6 +1654,124 @@ static void dm_test_fill_hdr_zeroes_output(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, (int)out.hb3, 0);
 }
 
+/*
+ * Build a minimal connector state carrying a valid static HDR metadata blob so
+ * the packing path can be exercised without a full atomic state. connector_type
+ * selects the sink-specific header layout.
+ */
+struct dm_test_hdr_ctx {
+	struct drm_connector conn;
+	struct drm_connector_state state;
+	struct drm_property_blob blob;
+	struct hdr_output_metadata meta;
+};
+
+static struct dm_test_hdr_ctx *
+dm_test_hdr_ctx_alloc(struct kunit *test, int connector_type)
+{
+	struct dm_test_hdr_ctx *ctx;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	ctx->conn.connector_type = connector_type;
+	ctx->state.connector = &ctx->conn;
+	ctx->blob.data = &ctx->meta;
+	ctx->blob.length = sizeof(ctx->meta);
+	ctx->state.hdr_output_metadata = &ctx->blob;
+
+	return ctx;
+}
+
+/**
+ * dm_test_fill_hdr_hdmi - Test the HDMI infopacket header layout
+ * @test: The KUnit test context
+ *
+ * A valid metadata blob on an HDMI connector packs a type 0x87 infoframe and
+ * marks the packet valid.
+ */
+static void dm_test_fill_hdr_hdmi(struct kunit *test)
+{
+	struct dm_test_hdr_ctx *ctx =
+		dm_test_hdr_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_info_packet out;
+
+	memset(&out, 0xAA, sizeof(out));
+
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_fill_hdr_info_packet(&ctx->state, &out), 0);
+	KUNIT_EXPECT_TRUE(test, out.valid);
+	KUNIT_EXPECT_EQ(test, (int)out.hb0, 0x87);
+	KUNIT_EXPECT_EQ(test, (int)out.hb1, 0x01);
+	KUNIT_EXPECT_EQ(test, (int)out.hb2, 0x1A);
+}
+
+/**
+ * dm_test_fill_hdr_dp - Test the DisplayPort SDP header layout
+ * @test: The KUnit test context
+ *
+ * A valid metadata blob on a DisplayPort connector packs the SDP header with
+ * the version/length subpacket bytes and marks the packet valid.
+ */
+static void dm_test_fill_hdr_dp(struct kunit *test)
+{
+	struct dm_test_hdr_ctx *ctx =
+		dm_test_hdr_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_info_packet out;
+
+	memset(&out, 0xAA, sizeof(out));
+
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_fill_hdr_info_packet(&ctx->state, &out), 0);
+	KUNIT_EXPECT_TRUE(test, out.valid);
+	KUNIT_EXPECT_EQ(test, (int)out.hb0, 0x00);
+	KUNIT_EXPECT_EQ(test, (int)out.hb1, 0x87);
+	KUNIT_EXPECT_EQ(test, (int)out.hb2, 0x1D);
+	KUNIT_EXPECT_EQ(test, (int)out.hb3, 0x13 << 2);
+	KUNIT_EXPECT_EQ(test, (int)out.sb[0], 0x01);
+	KUNIT_EXPECT_EQ(test, (int)out.sb[1], 0x1A);
+}
+
+/**
+ * dm_test_fill_hdr_unsupported_connector - Test unsupported sinks are rejected
+ * @test: The KUnit test context
+ *
+ * A connector type with no defined HDR infopacket layout returns -EINVAL and
+ * leaves the packet invalid.
+ */
+static void dm_test_fill_hdr_unsupported_connector(struct kunit *test)
+{
+	struct dm_test_hdr_ctx *ctx =
+		dm_test_hdr_ctx_alloc(test, DRM_MODE_CONNECTOR_VGA);
+	struct dc_info_packet out = {};
+
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_fill_hdr_info_packet(&ctx->state, &out),
+			-EINVAL);
+	KUNIT_EXPECT_FALSE(test, out.valid);
+}
+
+/**
+ * dm_test_fill_hdr_bad_metadata - Test malformed metadata is propagated
+ * @test: The KUnit test context
+ *
+ * A metadata blob with no payload makes drm_hdmi_infoframe_set_hdr_metadata()
+ * fail, and that error is returned unchanged.
+ */
+static void dm_test_fill_hdr_bad_metadata(struct kunit *test)
+{
+	struct dm_test_hdr_ctx *ctx =
+		dm_test_hdr_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct dc_info_packet out = {};
+
+	ctx->blob.data = NULL;
+
+	KUNIT_EXPECT_EQ(test,
+			amdgpu_dm_fill_hdr_info_packet(&ctx->state, &out),
+			-EINVAL);
+	KUNIT_EXPECT_FALSE(test, out.valid);
+}
+
 /* Tests for amdgpu_dm_connector_atomic_set_property() */
 
 /*
@@ -7018,6 +7136,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	/* amdgpu_dm_fill_hdr_info_packet */
 	KUNIT_CASE(dm_test_fill_hdr_null_metadata),
 	KUNIT_CASE(dm_test_fill_hdr_zeroes_output),
+	KUNIT_CASE(dm_test_fill_hdr_hdmi),
+	KUNIT_CASE(dm_test_fill_hdr_dp),
+	KUNIT_CASE(dm_test_fill_hdr_unsupported_connector),
+	KUNIT_CASE(dm_test_fill_hdr_bad_metadata),
 	/* amdgpu_dm_connector_atomic_set_property */
 	KUNIT_CASE(dm_test_set_property_scaling_center),
 	KUNIT_CASE(dm_test_set_property_scaling_aspect),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 55/82] drm/amd/display: Cover amdgpu_dm_connector_atomic_check
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (53 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 54/82] drm/amd/display: Cover amdgpu_dm_fill_hdr_info_packet IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 56/82] drm/amd/display: Cover atomic_check modeset triggers IVAN.LIPSKI
                   ` (27 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for amdgpu_dm_connector_atomic_check() covering the
no-CRTC early return, the DP-MST connector path, and an unchanged state
that skips revalidation.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |   3 +-
 .../display/amdgpu_dm/amdgpu_dm_connector.h   |   3 +
 .../tests/amdgpu_dm_connector_test.c          | 109 ++++++++++++++++++
 3 files changed, 114 insertions(+), 1 deletion(-)

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 ed16e01987726..44c223b178c50 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
@@ -2526,7 +2526,7 @@ int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state,
 }
 EXPORT_IF_KUNIT(amdgpu_dm_fill_hdr_info_packet);
 
-static int
+STATIC_IFN_KUNIT int
 amdgpu_dm_connector_atomic_check(struct drm_connector *conn,
 				 struct drm_atomic_commit *state)
 {
@@ -2607,6 +2607,7 @@ amdgpu_dm_connector_atomic_check(struct drm_connector *conn,
 
 	return 0;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_check);
 
 static const struct drm_connector_helper_funcs
 amdgpu_dm_connector_helper_funcs = {
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 4e9eb3ff2c904..3571378654fd7 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
@@ -222,5 +222,8 @@ void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode,
 					     bool scale_enabled);
 void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector);
 void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector);
+struct drm_atomic_commit;
+int amdgpu_dm_connector_atomic_check(struct drm_connector *conn,
+				     struct drm_atomic_commit *state);
 #endif
 #endif /* __AMDGPU_DM_CONNECTOR_H__ */
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
index 5c701c6b0511d..47c6caba46889 100644
--- 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
@@ -6463,6 +6463,111 @@ static void dm_test_mode_valid_edid_mgmt_forced(struct kunit *test)
 			MODE_ERROR);
 }
 
+/* Tests for amdgpu_dm_connector_atomic_check() */
+
+/*
+ * Build a single-connector atomic commit for amdgpu_dm_connector_atomic_check().
+ * The connector lives at index 0 so the hand-rolled connectors[] slot resolves
+ * both the old and new connector state; leaving crtc NULL keeps the check off
+ * the crtc-state machinery that a real atomic path would require.
+ */
+struct dm_test_conn_ac_ctx {
+	struct amdgpu_dm_connector *aconn;
+	struct drm_atomic_commit *state;
+	struct drm_connector_state *old_state;
+	struct drm_connector_state *new_state;
+};
+
+static struct dm_test_conn_ac_ctx *
+dm_test_conn_ac_ctx_alloc(struct kunit *test, int connector_type)
+{
+	struct dm_test_conn_ac_ctx *ctx;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	ctx->aconn = kunit_kzalloc(test, sizeof(*ctx->aconn), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->aconn);
+	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->state = dm_test_alloc_atomic_state(test, 1);
+
+	ctx->aconn->base.connector_type = connector_type;
+	ctx->new_state->connector = &ctx->aconn->base;
+	ctx->new_state->state = ctx->state;
+	ctx->old_state->connector = &ctx->aconn->base;
+
+	ctx->state->connectors[0].ptr = &ctx->aconn->base;
+	ctx->state->connectors[0].old_state = ctx->old_state;
+	ctx->state->connectors[0].new_state = ctx->new_state;
+
+	return ctx;
+}
+
+/**
+ * dm_test_conn_atomic_check_no_crtc - Test the unbound-connector short-circuit
+ * @test: The KUnit test context
+ *
+ * A non-DisplayPort connector with no crtc has nothing to validate and returns
+ * 0 immediately.
+ */
+static void dm_test_conn_atomic_check_no_crtc(struct kunit *test)
+{
+	struct dm_test_conn_ac_ctx *ctx =
+		dm_test_conn_ac_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	int ret;
+
+	ret = amdgpu_dm_connector_atomic_check(&ctx->aconn->base, ctx->state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
+/**
+ * dm_test_conn_atomic_check_dp_mst - Test the DisplayPort MST root check
+ * @test: The KUnit test context
+ *
+ * A DisplayPort connector runs the MST root atomic check; with no crtc bound on
+ * either state it finds nothing to reserve and returns 0.
+ */
+static void dm_test_conn_atomic_check_dp_mst(struct kunit *test)
+{
+	struct dm_test_conn_ac_ctx *ctx =
+		dm_test_conn_ac_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	int ret;
+
+	ret = amdgpu_dm_connector_atomic_check(&ctx->aconn->base, ctx->state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
+/**
+ * dm_test_conn_atomic_check_no_change - Test the bound-but-unchanged path
+ * @test: The KUnit test context
+ *
+ * With a crtc bound but no privacy/colorspace/content-type/HDR differences the
+ * check skips every modeset trigger and returns 0 without touching the crtc
+ * state.
+ */
+static void dm_test_conn_atomic_check_no_change(struct kunit *test)
+{
+	struct dm_test_conn_ac_ctx *ctx =
+		dm_test_conn_ac_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct drm_crtc *crtc;
+	int ret;
+
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, crtc);
+
+	ctx->new_state->crtc = crtc;
+
+	ret = amdgpu_dm_connector_atomic_check(&ctx->aconn->base, ctx->state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
 /**
  * dm_test_update_after_detect_mst_noop - Test MST connectors are left to drm_mst
  * @test: The KUnit test context
@@ -7140,6 +7245,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_fill_hdr_dp),
 	KUNIT_CASE(dm_test_fill_hdr_unsupported_connector),
 	KUNIT_CASE(dm_test_fill_hdr_bad_metadata),
+	/* amdgpu_dm_connector_atomic_check */
+	KUNIT_CASE(dm_test_conn_atomic_check_no_crtc),
+	KUNIT_CASE(dm_test_conn_atomic_check_dp_mst),
+	KUNIT_CASE(dm_test_conn_atomic_check_no_change),
 	/* amdgpu_dm_connector_atomic_set_property */
 	KUNIT_CASE(dm_test_set_property_scaling_center),
 	KUNIT_CASE(dm_test_set_property_scaling_aspect),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 56/82] drm/amd/display: Cover atomic_check modeset triggers
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (54 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 55/82] drm/amd/display: Cover amdgpu_dm_connector_atomic_check IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 57/82] drm/amd/display: Cover funcs_force valid EDID path IVAN.LIPSKI
                   ` (26 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for the amdgpu_dm_connector_atomic_check() properties
that force a modeset: privacy-screen, colorspace and content-type
changes, plus the HDR-metadata exit and fill-error branches.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 147 ++++++++++++++++++
 1 file changed, 147 insertions(+)

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
index 47c6caba46889..f7ea6aa801bc8 100644
--- 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
@@ -6476,6 +6476,8 @@ struct dm_test_conn_ac_ctx {
 	struct drm_atomic_commit *state;
 	struct drm_connector_state *old_state;
 	struct drm_connector_state *new_state;
+	struct drm_crtc *crtc;
+	struct drm_crtc_state *crtc_state;
 };
 
 static struct dm_test_conn_ac_ctx *
@@ -6568,6 +6570,146 @@ static void dm_test_conn_atomic_check_no_change(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, ret, 0);
 }
 
+/*
+ * Bind a crtc to the commit with a pre-populated crtc slot so
+ * drm_atomic_get_crtc_state() returns @crtc_state via its new-state fast path
+ * rather than locking and duplicating through absent crtc funcs.
+ */
+static void dm_test_conn_ac_bind_crtc(struct kunit *test,
+				      struct dm_test_conn_ac_ctx *ctx)
+{
+	ctx->crtc = kunit_kzalloc(test, sizeof(*ctx->crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->crtc);
+	ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state);
+
+	ctx->state->crtcs = kunit_kcalloc(test, 1, sizeof(*ctx->state->crtcs),
+					  GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->state->crtcs);
+	ctx->state->crtcs[0].ptr = ctx->crtc;
+	ctx->state->crtcs[0].new_state = ctx->crtc_state;
+
+	/* Only needs to be non-NULL to satisfy the get_crtc_state() WARN. */
+	ctx->state->acquire_ctx = (void *)ctx;
+
+	ctx->new_state->crtc = ctx->crtc;
+}
+
+/**
+ * dm_test_conn_atomic_check_privacy_change - Test privacy toggle forces modeset
+ * @test: The KUnit test context
+ *
+ * A changed privacy-screen software state pulls in the crtc state and flags a
+ * modeset.
+ */
+static void dm_test_conn_atomic_check_privacy_change(struct kunit *test)
+{
+	struct dm_test_conn_ac_ctx *ctx =
+		dm_test_conn_ac_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	int ret;
+
+	dm_test_conn_ac_bind_crtc(test, ctx);
+	ctx->new_state->privacy_screen_sw_state = PRIVACY_SCREEN_ENABLED;
+
+	ret = amdgpu_dm_connector_atomic_check(&ctx->aconn->base, ctx->state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_TRUE(test, ctx->crtc_state->mode_changed);
+}
+
+/**
+ * dm_test_conn_atomic_check_colorspace_change - Test colorspace change forces modeset
+ * @test: The KUnit test context
+ *
+ * A changed output colorspace pulls in the crtc state and flags a modeset.
+ */
+static void dm_test_conn_atomic_check_colorspace_change(struct kunit *test)
+{
+	struct dm_test_conn_ac_ctx *ctx =
+		dm_test_conn_ac_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	int ret;
+
+	dm_test_conn_ac_bind_crtc(test, ctx);
+	ctx->new_state->colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB;
+
+	ret = amdgpu_dm_connector_atomic_check(&ctx->aconn->base, ctx->state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_TRUE(test, ctx->crtc_state->mode_changed);
+}
+
+/**
+ * dm_test_conn_atomic_check_content_type_change - Test content-type change forces modeset
+ * @test: The KUnit test context
+ *
+ * A changed content type pulls in the crtc state and flags a modeset.
+ */
+static void dm_test_conn_atomic_check_content_type_change(struct kunit *test)
+{
+	struct dm_test_conn_ac_ctx *ctx =
+		dm_test_conn_ac_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	int ret;
+
+	dm_test_conn_ac_bind_crtc(test, ctx);
+	ctx->new_state->content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS;
+
+	ret = amdgpu_dm_connector_atomic_check(&ctx->aconn->base, ctx->state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_TRUE(test, ctx->crtc_state->mode_changed);
+}
+
+/**
+ * dm_test_conn_atomic_check_hdr_exit - Test exiting HDR forces a modeset
+ * @test: The KUnit test context
+ *
+ * Clearing previously set HDR metadata makes the metadata unequal; the fill
+ * succeeds for the empty new state and the enter/exit rule flags a modeset.
+ */
+static void dm_test_conn_atomic_check_hdr_exit(struct kunit *test)
+{
+	struct dm_test_conn_ac_ctx *ctx =
+		dm_test_conn_ac_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct drm_property_blob *blob;
+	int ret;
+
+	blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, blob);
+
+	dm_test_conn_ac_bind_crtc(test, ctx);
+	ctx->old_state->hdr_output_metadata = blob;
+
+	ret = amdgpu_dm_connector_atomic_check(&ctx->aconn->base, ctx->state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_TRUE(test, ctx->crtc_state->mode_changed);
+}
+
+/**
+ * dm_test_conn_atomic_check_hdr_fill_error - Test infopacket fill errors propagate
+ * @test: The KUnit test context
+ *
+ * A new HDR metadata blob with no payload makes the infopacket fill fail, and
+ * that error is returned before the crtc state is touched.
+ */
+static void dm_test_conn_atomic_check_hdr_fill_error(struct kunit *test)
+{
+	struct dm_test_conn_ac_ctx *ctx =
+		dm_test_conn_ac_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA);
+	struct drm_property_blob *blob;
+	int ret;
+
+	blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, blob);
+
+	dm_test_conn_ac_bind_crtc(test, ctx);
+	ctx->new_state->hdr_output_metadata = blob;
+
+	ret = amdgpu_dm_connector_atomic_check(&ctx->aconn->base, ctx->state);
+
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
 /**
  * dm_test_update_after_detect_mst_noop - Test MST connectors are left to drm_mst
  * @test: The KUnit test context
@@ -7249,6 +7391,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_conn_atomic_check_no_crtc),
 	KUNIT_CASE(dm_test_conn_atomic_check_dp_mst),
 	KUNIT_CASE(dm_test_conn_atomic_check_no_change),
+	KUNIT_CASE(dm_test_conn_atomic_check_privacy_change),
+	KUNIT_CASE(dm_test_conn_atomic_check_colorspace_change),
+	KUNIT_CASE(dm_test_conn_atomic_check_content_type_change),
+	KUNIT_CASE(dm_test_conn_atomic_check_hdr_exit),
+	KUNIT_CASE(dm_test_conn_atomic_check_hdr_fill_error),
 	/* amdgpu_dm_connector_atomic_set_property */
 	KUNIT_CASE(dm_test_set_property_scaling_center),
 	KUNIT_CASE(dm_test_set_property_scaling_aspect),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 57/82] drm/amd/display: Cover funcs_force valid EDID path
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (55 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 56/82] drm/amd/display: Cover atomic_check modeset triggers IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 58/82] drm/amd/display: Cover amdgpu_dm_connector_get_modes IVAN.LIPSKI
                   ` (25 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add a KUnit test that drives amdgpu_dm_connector_funcs_force() through
the valid-EDID branch: a fake i2c adapter serves a valid EDID over the
DDC read protocol so the connector caches drm_edid and, with an emulated
sink and dc_link present, copies the raw EDID into the sink and parses
its caps.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 95 +++++++++++++++++++
 1 file changed, 95 insertions(+)

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
index f7ea6aa801bc8..3a5edf84fbc0a 100644
--- 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
@@ -7077,6 +7077,100 @@ static void dm_test_update_after_detect_force_em_keep(struct kunit *test)
 	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, dc_sink);
 }
 
+/* Tests for amdgpu_dm_connector_funcs_force() with a valid EDID */
+
+/*
+ * Fake i2c adapter that serves dm_test_uad_edid over the DDC read protocol
+ * drm_do_probe_ddc_edid() uses: a one-byte write to DDC_ADDR (0x50) sets the
+ * block offset and the following read returns bytes from that offset. This
+ * lets amdgpu_dm_connector_funcs_force() obtain a valid EDID without hardware.
+ */
+static int dm_test_force_edid_i2c_xfer(struct i2c_adapter *adap,
+				       struct i2c_msg *msgs, int num)
+{
+	u8 offset = 0;
+	int i;
+
+	for (i = 0; i < num; i++) {
+		if (msgs[i].flags & I2C_M_RD) {
+			size_t len = msgs[i].len;
+
+			if (offset >= sizeof(dm_test_uad_edid))
+				len = 0;
+			else if (len > sizeof(dm_test_uad_edid) - offset)
+				len = sizeof(dm_test_uad_edid) - offset;
+			memcpy(msgs[i].buf, dm_test_uad_edid + offset, len);
+		} else if (msgs[i].addr == 0x50 && msgs[i].len >= 1) {
+			offset = msgs[i].buf[0];
+		}
+	}
+
+	return num;
+}
+
+static u32 dm_test_force_edid_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm dm_test_force_edid_i2c_algo = {
+	.master_xfer = dm_test_force_edid_i2c_xfer,
+	.functionality = dm_test_force_edid_i2c_func,
+};
+
+static void dm_test_force_edid_lock_bus(struct i2c_adapter *adap, unsigned int flags) {}
+static int dm_test_force_edid_trylock_bus(struct i2c_adapter *adap, unsigned int flags)
+{
+	return 1;
+}
+static void dm_test_force_edid_unlock_bus(struct i2c_adapter *adap, unsigned int flags) {}
+
+static const struct i2c_lock_operations dm_test_force_edid_lock_ops = {
+	.lock_bus = dm_test_force_edid_lock_bus,
+	.trylock_bus = dm_test_force_edid_trylock_bus,
+	.unlock_bus = dm_test_force_edid_unlock_bus,
+};
+
+/**
+ * dm_test_funcs_force_reads_edid - Test force() caches EDID and updates em_sink
+ * @test: The KUnit test context
+ *
+ * A non-AUX link selects the connector's i2c adapter, which serves a valid
+ * EDID. force() must cache it in drm_edid and, because an emulated sink and a
+ * dc_link are present, copy the raw EDID into the sink and parse its caps.
+ */
+static void dm_test_funcs_force_reads_edid(struct kunit *test)
+{
+	struct dm_test_edid_ctx *ctx =
+		dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct amdgpu_i2c_adapter *i2c;
+	struct dc_sink *em_sink;
+
+	i2c = kunit_kzalloc(test, sizeof(*i2c), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, i2c);
+	i2c->base.algo = &dm_test_force_edid_i2c_algo;
+	i2c->base.lock_ops = &dm_test_force_edid_lock_ops;
+	ctx->aconnector->i2c = i2c;
+
+	em_sink = kunit_kzalloc(test, sizeof(*em_sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, em_sink);
+	ctx->aconnector->dc_em_sink = em_sink;
+
+	/* Non-AUX link picks the i2c adapter; force-on skips the DDC probe. */
+	ctx->link->aux_mode = false;
+	ctx->link->priv = ctx->aconnector;
+	ctx->aconnector->base.force = DRM_FORCE_ON;
+
+	amdgpu_dm_connector_funcs_force(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_NOT_NULL(test, ctx->aconnector->drm_edid);
+	KUNIT_EXPECT_MEMEQ(test, em_sink->dc_edid.raw_edid,
+			   dm_test_uad_edid, sizeof(dm_test_uad_edid));
+
+	drm_edid_free(ctx->aconnector->drm_edid);
+	ctx->aconnector->drm_edid = NULL;
+}
+
 /* Tests for amdgpu_dm_update_stream_scaling_settings() */
 
 /**
@@ -7539,6 +7633,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_handle_edid_mgmt_non_dp_leaves_caps),
 	/* amdgpu_dm_connector_funcs_force */
 	KUNIT_CASE(dm_test_funcs_force_no_edid),
+	KUNIT_CASE(dm_test_funcs_force_reads_edid),
 	/* dm_validate_stream_and_context */
 	KUNIT_CASE(dm_test_validate_stream_null_stream),
 	KUNIT_CASE(dm_test_validate_stream_dc_ok_no_pipe),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 58/82] drm/amd/display: Cover amdgpu_dm_connector_get_modes
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (56 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 57/82] drm/amd/display: Cover funcs_force valid EDID path IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 59/82] drm/amd/display: Cover create_eml_sink valid EDID path IVAN.LIPSKI
                   ` (24 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for amdgpu_dm_connector_get_modes() covering the
no-EDID default modes, the extra modes added for 128b/132b and analog
links, and the path that adds modes from a valid EDID.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |   5 +-
 .../display/amdgpu_dm/amdgpu_dm_connector.h   |   1 +
 .../tests/amdgpu_dm_connector_test.c          | 178 ++++++++++++++++++
 3 files changed, 182 insertions(+), 2 deletions(-)

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 44c223b178c50..45ac4e2b5029b 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
@@ -256,7 +256,7 @@ STATIC_IFN_KUNIT void update_subconnector_property(struct amdgpu_dm_connector *a
 }
 EXPORT_IF_KUNIT(update_subconnector_property);
 
-static int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
+STATIC_IFN_KUNIT int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
 
 STATIC_IFN_KUNIT void amdgpu_dm_fbc_init(struct drm_connector *connector)
 {
@@ -3038,7 +3038,7 @@ STATIC_IFN_KUNIT void amdgpu_dm_connector_add_freesync_modes(struct drm_connecto
 }
 EXPORT_IF_KUNIT(amdgpu_dm_connector_add_freesync_modes);
 
-static int amdgpu_dm_connector_get_modes(struct drm_connector *connector)
+STATIC_IFN_KUNIT int amdgpu_dm_connector_get_modes(struct drm_connector *connector)
 {
 	struct amdgpu_dm_connector *amdgpu_dm_connector =
 			to_amdgpu_dm_connector(connector);
@@ -3077,6 +3077,7 @@ static int amdgpu_dm_connector_get_modes(struct drm_connector *connector)
 
 	return amdgpu_dm_connector->num_modes;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_connector_get_modes);
 
 static const u32 supported_colorspaces =
 	BIT(DRM_MODE_COLORIMETRY_BT709_YCC) |
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 3571378654fd7..91b8434ba591d 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
@@ -168,6 +168,7 @@ void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder,
 					  struct drm_connector *connector);
 void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector,
 				       const struct drm_edid *drm_edid);
+int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
 uint add_fs_modes(struct amdgpu_dm_connector *aconnector);
 void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector,
 					    const struct drm_edid *drm_edid);
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
index 3a5edf84fbc0a..fc6b6013d1188 100644
--- 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
@@ -7171,6 +7171,179 @@ static void dm_test_funcs_force_reads_edid(struct kunit *test)
 	ctx->aconnector->drm_edid = NULL;
 }
 
+/* Tests for amdgpu_dm_connector_get_modes() */
+
+static enum dp_link_encoding dm_test_gm_enc_8b10b(const struct dc_link_settings *s)
+{
+	return DP_8b_10b_ENCODING;
+}
+
+static enum dp_link_encoding dm_test_gm_enc_128b(const struct dc_link_settings *s)
+{
+	return DP_128b_132b_ENCODING;
+}
+
+/*
+ * Build an amdgpu_dm_connector on an amdgpu_device-backed drm device (so
+ * drm_to_adev() resolves for amdgpu_dm_fbc_init()) with an attached encoder
+ * and a dc/dc_link whose link_srv reports a non-128b encoding by default. The
+ * fbc compressor is left NULL so amdgpu_dm_fbc_init() early-returns.
+ */
+struct dm_test_gm_ctx {
+	struct amdgpu_device *adev;
+	struct drm_device *drm;
+	struct amdgpu_dm_connector *aconnector;
+	struct amdgpu_encoder *aenc;
+	struct dc *dc;
+	struct link_service *link_srv;
+	struct dc_link *link;
+};
+
+static struct dm_test_gm_ctx *
+dm_test_gm_ctx_alloc(struct kunit *test, int connector_type)
+{
+	struct dm_test_gm_ctx *ctx;
+	struct device *dev;
+	int ret;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	dev = drm_kunit_helper_alloc_device(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+	ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+						       sizeof(*ctx->adev),
+						       offsetof(struct amdgpu_device, ddev),
+						       DRIVER_MODESET);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm);
+	ctx->adev = drm_to_adev(ctx->drm);
+
+	ctx->aconnector = drmm_kzalloc(ctx->drm, sizeof(*ctx->aconnector),
+				       GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector);
+	ret = drmm_connector_init(ctx->drm, &ctx->aconnector->base,
+				  &dm_test_connector_funcs, connector_type,
+				  NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	ctx->aenc = drmm_kzalloc(ctx->drm, sizeof(*ctx->aenc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->aenc);
+	ret = drmm_encoder_init(ctx->drm, &ctx->aenc->base, NULL,
+				DRM_MODE_ENCODER_TMDS, NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	ret = drm_connector_attach_encoder(&ctx->aconnector->base,
+					   &ctx->aenc->base);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->dc);
+	ctx->link_srv = kunit_kzalloc(test, sizeof(*ctx->link_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->link_srv);
+	ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->link);
+
+	ctx->link_srv->dp_get_encoding_format = dm_test_gm_enc_8b10b;
+	ctx->dc->link_srv = ctx->link_srv;
+	ctx->adev->dm.dc = ctx->dc;
+	ctx->link->dc = ctx->dc;
+	ctx->aconnector->dc_link = ctx->link;
+
+	return ctx;
+}
+
+/**
+ * dm_test_get_modes_noedid_default - Test synthesized modes without an EDID
+ * @test: The KUnit test context
+ *
+ * With no cached EDID and a non-128b link, get_modes() synthesizes the default
+ * 640x480 fallback mode(s) and reports a non-zero count.
+ */
+static void dm_test_get_modes_noedid_default(struct kunit *test)
+{
+	struct dm_test_gm_ctx *ctx =
+		dm_test_gm_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+
+	KUNIT_EXPECT_GT(test,
+			amdgpu_dm_connector_get_modes(&ctx->aconnector->base), 0);
+}
+
+/**
+ * dm_test_get_modes_noedid_128b_adds_more - Test 128b links add 1080p modes
+ * @test: The KUnit test context
+ *
+ * A 128b/132b link synthesizes the extra 1920x1080 fallback modes, so the mode
+ * count is strictly greater than for an 8b/10b link.
+ */
+static void dm_test_get_modes_noedid_128b_adds_more(struct kunit *test)
+{
+	struct dm_test_gm_ctx *ctx =
+		dm_test_gm_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	int n_8b, n_128b;
+
+	n_8b = amdgpu_dm_connector_get_modes(&ctx->aconnector->base);
+
+	ctx->link_srv->dp_get_encoding_format = dm_test_gm_enc_128b;
+	n_128b = amdgpu_dm_connector_get_modes(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_GT(test, n_128b, n_8b);
+}
+
+/**
+ * dm_test_get_modes_noedid_analog_adds_common - Test analog sinks add common modes
+ * @test: The KUnit test context
+ *
+ * An analog VGA sink detected by load detection adds the common fallback modes
+ * on top of the default 640x480 mode(s).
+ */
+static void dm_test_get_modes_noedid_analog_adds_common(struct kunit *test)
+{
+	struct dm_test_gm_ctx *ctx =
+		dm_test_gm_ctx_alloc(test, DRM_MODE_CONNECTOR_VGA);
+	struct dc_sink *sink;
+	int n_base, n_analog;
+
+	n_base = amdgpu_dm_connector_get_modes(&ctx->aconnector->base);
+
+	sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, sink);
+	sink->edid_caps.analog = true;
+	ctx->aconnector->dc_sink = sink;
+	ctx->link->link_id.id = CONNECTOR_ID_VGA;
+
+	n_analog = amdgpu_dm_connector_get_modes(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_GT(test, n_analog, n_base);
+}
+
+/**
+ * dm_test_get_modes_with_edid - Test the cached-EDID path adds common modes
+ * @test: The KUnit test context
+ *
+ * With a cached EDID on an eDP connector, get_modes() takes the DDC path and
+ * adds the common downscaled modes derived from the encoder native mode.
+ */
+static void dm_test_get_modes_with_edid(struct kunit *test)
+{
+	struct dm_test_gm_ctx *ctx =
+		dm_test_gm_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP);
+	const struct drm_edid *drm_edid;
+
+	drm_edid = drm_edid_alloc(dm_test_uad_edid, sizeof(dm_test_uad_edid));
+	KUNIT_ASSERT_NOT_NULL(test, drm_edid);
+	drm_edid_connector_update(&ctx->aconnector->base, drm_edid);
+	ctx->aconnector->drm_edid = drm_edid;
+
+	ctx->aenc->native_mode.hdisplay = 1920;
+	ctx->aenc->native_mode.vdisplay = 1200;
+
+	KUNIT_EXPECT_GT(test,
+			amdgpu_dm_connector_get_modes(&ctx->aconnector->base), 0);
+
+	drm_edid_free(drm_edid);
+	ctx->aconnector->drm_edid = NULL;
+}
+
 /* Tests for amdgpu_dm_update_stream_scaling_settings() */
 
 /**
@@ -7634,6 +7807,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	/* amdgpu_dm_connector_funcs_force */
 	KUNIT_CASE(dm_test_funcs_force_no_edid),
 	KUNIT_CASE(dm_test_funcs_force_reads_edid),
+	/* amdgpu_dm_connector_get_modes */
+	KUNIT_CASE(dm_test_get_modes_noedid_default),
+	KUNIT_CASE(dm_test_get_modes_noedid_128b_adds_more),
+	KUNIT_CASE(dm_test_get_modes_noedid_analog_adds_common),
+	KUNIT_CASE(dm_test_get_modes_with_edid),
 	/* dm_validate_stream_and_context */
 	KUNIT_CASE(dm_test_validate_stream_null_stream),
 	KUNIT_CASE(dm_test_validate_stream_dc_ok_no_pipe),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 59/82] drm/amd/display: Cover create_eml_sink valid EDID path
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (57 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 58/82] drm/amd/display: Cover amdgpu_dm_connector_get_modes IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 60/82] drm/amd/display: Cover amdgpu_set_panel_orientation IVAN.LIPSKI
                   ` (23 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests driving create_eml_sink() through the valid-EDID
branch: reading the EDID over DDC, and the forced-on paths using an
emulated sink and a cached local sink.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 135 ++++++++++++++++++
 1 file changed, 135 insertions(+)

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
index fc6b6013d1188..120d0e27f5eed 100644
--- 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
@@ -7171,6 +7171,138 @@ static void dm_test_funcs_force_reads_edid(struct kunit *test)
 	ctx->aconnector->drm_edid = NULL;
 }
 
+/* Tests for create_eml_sink() with a valid EDID */
+
+/*
+ * create_eml_sink() reads the EDID off the connector DDC and, on success,
+ * installs an emulated sink from link_srv->add_remote_sink(). A non-AUX link
+ * selects the connector i2c adapter (serving dm_test_uad_edid) and a fake
+ * add_remote_sink() returns dm_test_ces_remote_sink, so the full EDID path
+ * runs without real hardware or DC.
+ */
+static struct dc_sink *dm_test_ces_remote_sink;
+
+static struct dc_sink *
+dm_test_ces_add_remote_sink(struct dc_link *link, const uint8_t *edid,
+			    unsigned int len, struct dc_sink_init_data *init_data)
+{
+	return dm_test_ces_remote_sink;
+}
+
+static void dm_test_ces_setup(struct kunit *test, struct dm_test_edid_ctx *ctx,
+			      struct dc_sink *em_sink)
+{
+	struct amdgpu_i2c_adapter *i2c;
+	struct link_service *link_srv;
+	struct dc *dc;
+
+	i2c = kunit_kzalloc(test, sizeof(*i2c), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, i2c);
+	i2c->base.algo = &dm_test_force_edid_i2c_algo;
+	i2c->base.lock_ops = &dm_test_force_edid_lock_ops;
+	ctx->aconnector->i2c = i2c;
+
+	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc);
+	link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link_srv);
+	link_srv->add_remote_sink = dm_test_ces_add_remote_sink;
+	dc->link_srv = link_srv;
+
+	ctx->link->dc = dc;
+	ctx->link->aux_mode = false;
+	dm_test_ces_remote_sink = em_sink;
+}
+
+/**
+ * dm_test_create_eml_sink_reads_edid - Test the valid-EDID path builds a sink
+ * @test: The KUnit test context
+ *
+ * With a readable EDID and force unspecified, create_eml_sink() caches the EDID
+ * in drm_edid and installs the emulated sink while leaving dc_sink untouched.
+ */
+static void dm_test_create_eml_sink_reads_edid(struct kunit *test)
+{
+	struct dm_test_edid_ctx *ctx =
+	dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_sink *em_sink;
+
+	em_sink = kunit_kzalloc(test, sizeof(*em_sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, em_sink);
+	dm_test_ces_setup(test, ctx, em_sink);
+	ctx->aconnector->base.force = DRM_FORCE_UNSPECIFIED;
+
+	create_eml_sink(ctx->aconnector);
+
+	KUNIT_EXPECT_NOT_NULL(test, ctx->aconnector->drm_edid);
+	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_em_sink, em_sink);
+	KUNIT_EXPECT_NULL(test, ctx->aconnector->dc_sink);
+
+	drm_edid_free(ctx->aconnector->drm_edid);
+	ctx->aconnector->drm_edid = NULL;
+}
+
+/**
+ * dm_test_create_eml_sink_force_on_em - Test force-on adopts the emulated sink
+ * @test: The KUnit test context
+ *
+ * With DRM_FORCE_ON and no local sink, create_eml_sink() adopts the emulated
+ * sink as dc_sink and retains it.
+ */
+static void dm_test_create_eml_sink_force_on_em(struct kunit *test)
+{
+	struct dm_test_edid_ctx *ctx =
+	dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_sink *em_sink;
+
+	em_sink = kunit_kzalloc(test, sizeof(*em_sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, em_sink);
+	kref_init(&em_sink->refcount);
+	dm_test_ces_setup(test, ctx, em_sink);
+	ctx->link->local_sink = NULL;
+	ctx->aconnector->base.force = DRM_FORCE_ON;
+
+	create_eml_sink(ctx->aconnector);
+
+	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, em_sink);
+	KUNIT_EXPECT_EQ(test, kref_read(&em_sink->refcount), 2);
+
+	drm_edid_free(ctx->aconnector->drm_edid);
+	ctx->aconnector->drm_edid = NULL;
+}
+
+/**
+ * dm_test_create_eml_sink_force_on_local - Test force-on prefers the local sink
+ * @test: The KUnit test context
+ *
+ * With DRM_FORCE_ON and a local sink present, create_eml_sink() adopts the
+ * local sink as dc_sink instead of the emulated one and retains it.
+ */
+static void dm_test_create_eml_sink_force_on_local(struct kunit *test)
+{
+	struct dm_test_edid_ctx *ctx =
+	dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+	struct dc_sink *em_sink;
+	struct dc_sink *local_sink;
+
+	em_sink = kunit_kzalloc(test, sizeof(*em_sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, em_sink);
+	local_sink = kunit_kzalloc(test, sizeof(*local_sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, local_sink);
+	kref_init(&local_sink->refcount);
+	dm_test_ces_setup(test, ctx, em_sink);
+	ctx->link->local_sink = local_sink;
+	ctx->aconnector->base.force = DRM_FORCE_ON;
+
+	create_eml_sink(ctx->aconnector);
+
+	KUNIT_EXPECT_PTR_EQ(test, ctx->aconnector->dc_sink, local_sink);
+	KUNIT_EXPECT_EQ(test, kref_read(&local_sink->refcount), 2);
+
+	drm_edid_free(ctx->aconnector->drm_edid);
+	ctx->aconnector->drm_edid = NULL;
+}
+
 /* Tests for amdgpu_dm_connector_get_modes() */
 
 static enum dp_link_encoding dm_test_gm_enc_8b10b(const struct dc_link_settings *s)
@@ -7801,6 +7933,9 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_hdmi_cec_unset_edid_no_notifier),
 	/* create_eml_sink */
 	KUNIT_CASE(dm_test_create_eml_sink_no_edid),
+	KUNIT_CASE(dm_test_create_eml_sink_reads_edid),
+	KUNIT_CASE(dm_test_create_eml_sink_force_on_em),
+	KUNIT_CASE(dm_test_create_eml_sink_force_on_local),
 	/* handle_edid_mgmt */
 	KUNIT_CASE(dm_test_handle_edid_mgmt_dp_sets_link_caps),
 	KUNIT_CASE(dm_test_handle_edid_mgmt_non_dp_leaves_caps),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 60/82] drm/amd/display: Cover amdgpu_set_panel_orientation
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (58 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 59/82] drm/amd/display: Cover create_eml_sink valid EDID path IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 61/82] drm/amd/display: Refactor amdgpu_dm_irq_test IVAN.LIPSKI
                   ` (22 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for amdgpu_set_panel_orientation() covering the non-eDP
early return, the missing native mode case, and a panel that applies
the orientation quirk.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |  1 +
 .../tests/amdgpu_dm_connector_test.c          | 68 +++++++++++++++++++
 2 files changed, 69 insertions(+)

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 45ac4e2b5029b..ddefef1debf25 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
@@ -2844,6 +2844,7 @@ void amdgpu_set_panel_orientation(struct drm_connector *connector)
 						       native_mode->hdisplay,
 						       native_mode->vdisplay);
 }
+EXPORT_IF_KUNIT(amdgpu_set_panel_orientation);
 
 /*
  * The Apple Studio Display primary tile advertises both the full 5120x2880
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
index 120d0e27f5eed..e63202fc7ba9f 100644
--- 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
@@ -7476,6 +7476,70 @@ static void dm_test_get_modes_with_edid(struct kunit *test)
 	ctx->aconnector->drm_edid = NULL;
 }
 
+/* Tests for amdgpu_set_panel_orientation() */
+
+/**
+ * dm_test_panel_orientation_non_edp - Test non-eDP/LVDS connectors are skipped
+ * @test: The KUnit test context
+ *
+ * Only eDP and LVDS panels carry a fixed orientation, so a DisplayPort
+ * connector returns immediately with its panel_orientation left unknown.
+ */
+static void dm_test_panel_orientation_non_edp(struct kunit *test)
+{
+	struct dm_test_gm_ctx *ctx =
+	dm_test_gm_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort);
+
+	amdgpu_set_panel_orientation(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_EQ(test,
+			ctx->aconnector->base.display_info.panel_orientation,
+			DRM_MODE_PANEL_ORIENTATION_UNKNOWN);
+}
+
+/**
+ * dm_test_panel_orientation_no_native_mode - Test a missing native mode is skipped
+ * @test: The KUnit test context
+ *
+ * On an eDP connector with no cached EDID the encoder native mode stays 0x0, so
+ * the function returns before applying an orientation.
+ */
+static void dm_test_panel_orientation_no_native_mode(struct kunit *test)
+{
+	struct dm_test_gm_ctx *ctx =
+	dm_test_gm_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP);
+
+	amdgpu_set_panel_orientation(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, ctx->aenc->native_mode.hdisplay, 0);
+	KUNIT_EXPECT_EQ(test,
+			ctx->aconnector->base.display_info.panel_orientation,
+			DRM_MODE_PANEL_ORIENTATION_UNKNOWN);
+}
+
+/**
+ * dm_test_panel_orientation_applies_quirk - Test a native mode reaches the quirk
+ * @test: The KUnit test context
+ *
+ * With a valid encoder native mode on an eDP connector the panel dimensions are
+ * forwarded to the orientation quirk lookup; absent a matching quirk the
+ * orientation stays unknown.
+ */
+static void dm_test_panel_orientation_applies_quirk(struct kunit *test)
+{
+	struct dm_test_gm_ctx *ctx =
+	dm_test_gm_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP);
+
+	ctx->aenc->native_mode.hdisplay = 1920;
+	ctx->aenc->native_mode.vdisplay = 1200;
+
+	amdgpu_set_panel_orientation(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_EQ(test,
+			ctx->aconnector->base.display_info.panel_orientation,
+			DRM_MODE_PANEL_ORIENTATION_UNKNOWN);
+}
+
 /* Tests for amdgpu_dm_update_stream_scaling_settings() */
 
 /**
@@ -7947,6 +8011,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_get_modes_noedid_128b_adds_more),
 	KUNIT_CASE(dm_test_get_modes_noedid_analog_adds_common),
 	KUNIT_CASE(dm_test_get_modes_with_edid),
+	/* amdgpu_set_panel_orientation */
+	KUNIT_CASE(dm_test_panel_orientation_non_edp),
+	KUNIT_CASE(dm_test_panel_orientation_no_native_mode),
+	KUNIT_CASE(dm_test_panel_orientation_applies_quirk),
 	/* dm_validate_stream_and_context */
 	KUNIT_CASE(dm_test_validate_stream_null_stream),
 	KUNIT_CASE(dm_test_validate_stream_dc_ok_no_pipe),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 61/82] drm/amd/display: Refactor amdgpu_dm_irq_test
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (59 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 60/82] drm/amd/display: Cover amdgpu_set_panel_orientation IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 62/82] drm/amd/display: Test pageflip completion in the high IRQ handlers IVAN.LIPSKI
                   ` (21 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Several amdgpu_dm_irq tests were each open-coding the same setup. Share
it through three functions: dm_test_add_crtc(),
dm_test_setup_hpd_irq_helper() and dm_test_setup_irq_regs().

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 214 ++++++++----------
 1 file changed, 95 insertions(+), 119 deletions(-)

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
index 28c36217f6a21..fbe37bb0a39d6 100644
--- 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
@@ -340,6 +340,38 @@ static void dm_test_crtc_list_del(void *data)
 	list_del_init(&acrtc->base.head);
 }
 
+/*
+ * The DRM vblank core looks CRTCs up by index on mode_config.crtc_list and
+ * dereferences crtc->funcs, so a listed CRTC must carry a funcs table even
+ * when it implements no callbacks.
+ */
+static const struct drm_crtc_funcs dm_test_crtc_funcs = {
+};
+
+/*
+ * Add an OTG instance 0 CRTC to the device so amdgpu_dm_get_crtc_by_otg_inst()
+ * finds it. The CRTC is removed from the list again on test teardown.
+ */
+static struct amdgpu_crtc *dm_test_add_crtc(struct kunit *test,
+					    struct amdgpu_device *adev)
+{
+	struct amdgpu_crtc *acrtc;
+
+	acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+
+	INIT_LIST_HEAD(&acrtc->base.head);
+	acrtc->base.dev = &adev->ddev;
+	acrtc->base.funcs = &dm_test_crtc_funcs;
+	acrtc->otg_inst = 0;
+
+	list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+				dm_test_crtc_list_del, acrtc), 0);
+
+	return acrtc;
+}
+
 struct dm_test_hpd_rx_wq_ctx {
 	struct hpd_rx_irq_offload_work_queue *wq;
 	int count;
@@ -2481,16 +2513,14 @@ static void dm_test_handle_hpd_irq_disabled(struct kunit *test)
 	KUNIT_EXPECT_TRUE(test, aconn->fake_enable);
 }
 
-/**
- * dm_test_handle_hpd_irq_helper_debounce_schedule - Test HDMI debounce branch
- * @test: The KUnit test context
- *
- * An HDMI link reporting a disconnect (connection type none) with a non-zero
- * debounce delay and a cached local_sink must take the debounce branch: it
- * caches local_sink in hdmi_prev_sink and schedules the delayed debounce work
- * instead of detecting immediately.
+/*
+ * Build an aconnector wired for handle_hpd_irq_helper(): an HPD lock, an armed
+ * debounce work item, an atomic state and a dc/link pair reporting no
+ * connection. Caller sets the link_srv stubs and any HDMI/debounce fields the
+ * branch under test needs.
  */
-static void dm_test_handle_hpd_irq_helper_debounce_schedule(struct kunit *test)
+static struct amdgpu_dm_connector *dm_test_setup_hpd_irq_helper(struct kunit *test,
+								struct link_service **link_srv_out)
 {
 	struct amdgpu_dm_connector *aconn;
 	struct dm_connector_state *state;
@@ -2501,6 +2531,7 @@ static void dm_test_handle_hpd_irq_helper_debounce_schedule(struct kunit *test)
 	struct dc *dc;
 
 	adev = dm_kunit_alloc_adev(test);
+	mutex_init(&adev->dm.dc_lock);
 
 	aconn = dm_kunit_alloc_connector(test, adev, NULL);
 	mutex_init(&aconn->hpd_lock);
@@ -2528,6 +2559,29 @@ static void dm_test_handle_hpd_irq_helper_debounce_schedule(struct kunit *test)
 	link->ctx = ctx;
 	aconn->dc_link = link;
 
+	*link_srv_out = link_srv;
+
+	return aconn;
+}
+
+/**
+ * dm_test_handle_hpd_irq_helper_debounce_schedule - Test HDMI debounce branch
+ * @test: The KUnit test context
+ *
+ * An HDMI link reporting a disconnect (connection type none) with a non-zero
+ * debounce delay and a cached local_sink must take the debounce branch: it
+ * caches local_sink in hdmi_prev_sink and schedules the delayed debounce work
+ * instead of detecting immediately.
+ */
+static void dm_test_handle_hpd_irq_helper_debounce_schedule(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconn;
+	struct link_service *link_srv;
+	struct dc_link *link;
+
+	aconn = dm_test_setup_hpd_irq_helper(test, &link_srv);
+	link = aconn->dc_link;
+
 	/* HDMI signal + debounce delay + cached sink -> debounce branch. */
 	aconn->hdmi_hpd_debounce_delay_ms = 100;
 	link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;
@@ -2555,40 +2609,11 @@ static void dm_test_handle_hpd_irq_helper_debounce_schedule(struct kunit *test)
 static void dm_test_handle_hpd_irq_helper_debounce_release_prev(struct kunit *test)
 {
 	struct amdgpu_dm_connector *aconn;
-	struct dm_connector_state *state;
 	struct link_service *link_srv;
-	struct amdgpu_device *adev;
-	struct dc_context *ctx;
 	struct dc_link *link;
-	struct dc *dc;
-
-	adev = dm_kunit_alloc_adev(test);
 
-	aconn = dm_kunit_alloc_connector(test, adev, NULL);
-	mutex_init(&aconn->hpd_lock);
-	INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work,
-			  amdgpu_dm_hdmi_hpd_debounce_work);
-
-	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
-	aconn->base.state = &state->base;
-
-	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc);
-	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
-	link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv);
-	link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link);
-
-	link_srv->detect_connection_type = dm_test_detect_connection_none;
-	dc->ctx = ctx;
-	dc->link_srv = link_srv;
-	ctx->dc = dc;
-	link->dc = dc;
-	link->ctx = ctx;
-	aconn->dc_link = link;
+	aconn = dm_test_setup_hpd_irq_helper(test, &link_srv);
+	link = aconn->dc_link;
 
 	/* HDMI signal + debounce delay + cached sink -> debounce branch. */
 	aconn->hdmi_hpd_debounce_delay_ms = 100;
@@ -2623,42 +2648,10 @@ static void dm_test_handle_hpd_irq_helper_debounce_release_prev(struct kunit *te
 static void dm_test_handle_hpd_irq_helper_detect_false(struct kunit *test)
 {
 	struct amdgpu_dm_connector *aconn;
-	struct dm_connector_state *state;
 	struct link_service *link_srv;
-	struct amdgpu_device *adev;
-	struct dc_context *ctx;
-	struct dc_link *link;
-	struct dc *dc;
 
-	adev = dm_kunit_alloc_adev(test);
-	mutex_init(&adev->dm.dc_lock);
-
-	aconn = dm_kunit_alloc_connector(test, adev, NULL);
-	mutex_init(&aconn->hpd_lock);
-	INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work,
-			  amdgpu_dm_hdmi_hpd_debounce_work);
-
-	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
-	aconn->base.state = &state->base;
-
-	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc);
-	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
-	link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv);
-	link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link);
-
-	link_srv->detect_connection_type = dm_test_detect_connection_none;
+	aconn = dm_test_setup_hpd_irq_helper(test, &link_srv);
 	link_srv->detect_link = dm_test_detect_link_false;
-	dc->ctx = ctx;
-	dc->link_srv = link_srv;
-	ctx->dc = dc;
-	link->dc = dc;
-	link->ctx = ctx;
-	aconn->dc_link = link;
 	aconn->fake_enable = true;
 
 	/* No debounce delay and no force -> immediate-detect else branch. */
@@ -3315,16 +3308,8 @@ static void dm_test_pflip_high_irq_not_submitted(struct kunit *test)
 	struct amdgpu_device *adev;
 
 	adev = dm_kunit_alloc_adev(test);
-	acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
-
-	INIT_LIST_HEAD(&acrtc->base.head);
-	acrtc->base.dev = &adev->ddev;
-	acrtc->otg_inst = 0;
+	acrtc = dm_test_add_crtc(test, adev);
 	acrtc->pflip_status = AMDGPU_FLIP_NONE;
-	list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list);
-	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
-				dm_test_crtc_list_del, acrtc), 0);
 
 	params.adev = adev;
 	params.irq_src = (enum dc_irq_source)IRQ_TYPE_PFLIP;
@@ -3380,17 +3365,9 @@ static void dm_test_crtc_high_irq_vrr_pre_ai(struct kunit *test)
 	struct amdgpu_device *adev;
 
 	adev = dm_kunit_alloc_adev(test);
-	acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
-
-	INIT_LIST_HEAD(&acrtc->base.head);
-	acrtc->base.dev = &adev->ddev;
-	acrtc->otg_inst = 0;
+	acrtc = dm_test_add_crtc(test, adev);
 	/* VRR active so the !vrr_active vblank handler is skipped. */
 	acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE;
-	list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list);
-	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
-				dm_test_crtc_list_del, acrtc), 0);
 
 	/* Pre-AI family returns right after CRC handling. */
 	adev->family = AMDGPU_FAMILY_SI;
@@ -3417,18 +3394,10 @@ static void dm_test_crtc_high_irq_vrr_ai_no_stream(struct kunit *test)
 	struct amdgpu_device *adev;
 
 	adev = dm_kunit_alloc_adev(test);
-	acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
-
-	INIT_LIST_HEAD(&acrtc->base.head);
-	acrtc->base.dev = &adev->ddev;
-	acrtc->otg_inst = 0;
+	acrtc = dm_test_add_crtc(test, adev);
 	acrtc->pflip_status = AMDGPU_FLIP_NONE;
 	/* VRR active so the !vrr_active vblank handler is skipped. */
 	acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE;
-	list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list);
-	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
-				dm_test_crtc_list_del, acrtc), 0);
 
 	/* AI+ family runs the freesync section; no stream skips it. */
 	adev->family = AMDGPU_FAMILY_AI;
@@ -3625,14 +3594,14 @@ static void dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources(s
 	KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL);
 }
 
-/**
- * dm_test_dce110_register_irq_handlers_one_crtc - Test DCE110 with 1 CRTC
- * @test: The KUnit test context
- *
- * Exercises the VBLANK, VUPDATE, PFLIP and HPD for-loop bodies with a
- * fake IRQ service that maps source IDs to DC IRQ sources.
+/*
+ * Build an adev/dc pair with one CRTC, ready for either register_irq_handlers()
+ * entry point, using the given fake IRQ service to map source IDs. A service
+ * that only maps a subset lets a chosen register loop hit its invalid-source
+ * guard, and clearing an amdgpu_irq_src's funcs makes its add_id() fail.
  */
-static void dm_test_dce110_register_irq_handlers_one_crtc(struct kunit *test)
+static struct amdgpu_device *dm_test_setup_irq_regs(struct kunit *test,
+						   const struct irq_service_funcs *funcs)
 {
 	struct amdgpu_device *adev;
 	struct dc *dc;
@@ -3640,14 +3609,31 @@ static void dm_test_dce110_register_irq_handlers_one_crtc(struct kunit *test)
 	adev = dm_kunit_alloc_adev(test);
 	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources,
 							 adev), 0);
-	dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dce110);
+	dc = dm_test_alloc_dc_with_irq_service(test, funcs);
 	dc->ctx->dce_version = DCE_VERSION_11_0;
+	dc->caps.max_otg_num = 1;
 	adev->dm.dc = dc;
 	adev->mode_info.num_crtc = 1;
 	adev->mode_info.num_hpd = 1;
 	amdgpu_dm_set_irq_funcs(adev);
 	KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
 
+	return adev;
+}
+
+/**
+ * dm_test_dce110_register_irq_handlers_one_crtc - Test DCE110 with 1 CRTC
+ * @test: The KUnit test context
+ *
+ * Exercises the VBLANK, VUPDATE, PFLIP and HPD for-loop bodies with a
+ * fake IRQ service that maps source IDs to DC IRQ sources.
+ */
+static void dm_test_dce110_register_irq_handlers_one_crtc(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_dce110);
+
 	KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), 0);
 
 	/* Verify VBLANK params were populated */
@@ -3702,18 +3688,8 @@ static void dm_test_dcn10_register_irq_handlers_zero_crtc(struct kunit *test)
 static void dm_test_dcn10_register_irq_handlers_one_crtc(struct kunit *test)
 {
 	struct amdgpu_device *adev;
-	struct dc *dc;
 
-	adev = dm_kunit_alloc_adev(test);
-	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources,
-							 adev), 0);
-	dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dcn10);
-	adev->dm.dc = dc;
-	adev->mode_info.num_crtc = 1;
-	adev->mode_info.num_hpd = 1;
-	dc->caps.max_otg_num = 1;
-	amdgpu_dm_set_irq_funcs(adev);
-	KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_dcn10);
 
 	KUNIT_EXPECT_EQ(test, amdgpu_dm_dcn10_register_irq_handlers(adev), 0);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 62/82] drm/amd/display: Test pageflip completion in the high IRQ handlers
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (60 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 61/82] drm/amd/display: Refactor amdgpu_dm_irq_test IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 63/82] drm/amd/display: Test writeback handling in dm_crtc_high_irq IVAN.LIPSKI
                   ` (20 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for pageflip completion: the non-VRR path in
dm_pflip_high_irq(), and the DCN VUPDATE_NO_LOCK paths in
dm_vupdate_high_irq() that deliver the event once the OTG reports no
flip pending, or retire the flip when there are no active planes.

[HOW]
Initialise DRM vblank and hold a reference to balance the handler's
drm_crtc_vblank_put(). A non-zero DCE IP version selects the DCN path,
and a NULL adev->dm.dc makes dc_get_flip_pending_on_otg() report no
pending flip.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 118 ++++++++++++++++++
 1 file changed, 118 insertions(+)

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
index fbe37bb0a39d6..0bf46e856f5a2 100644
--- 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
@@ -9,6 +9,7 @@
 #include <drm/drm_kunit_helpers.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "dc.h"
 #include "inc/core_types.h"
@@ -3318,6 +3319,45 @@ static void dm_test_pflip_high_irq_not_submitted(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE);
 }
 
+/**
+ * dm_test_pflip_high_irq_completes_flip - Test pflip high IRQ completes a flip
+ * @test: The KUnit test context
+ *
+ * With a submitted flip and an armed event on a non-VRR CRTC, the handler must
+ * send the pageflip completion event, drop the vblank reference taken when the
+ * flip was queued and return the CRTC to AMDGPU_FLIP_NONE.
+ */
+static void dm_test_pflip_high_irq_completes_flip(struct kunit *test)
+{
+	struct drm_pending_vblank_event *event;
+	struct common_irq_params params = { 0 };
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0);
+
+	acrtc = dm_test_add_crtc(test, adev);
+	acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED;
+
+	/* drm_crtc_send_vblank_event() consumes (kfree()s) the event. */
+	event = kzalloc_obj(*event, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event);
+	acrtc->event = event;
+
+	/* Balance the handler's drm_crtc_vblank_put(). */
+	adev->ddev.vblank[0].enabled = true;
+	KUNIT_ASSERT_EQ(test, drm_crtc_vblank_get(&acrtc->base), 0);
+
+	params.adev = adev;
+	params.irq_src = (enum dc_irq_source)IRQ_TYPE_PFLIP;
+
+	dm_pflip_high_irq(&params);
+
+	KUNIT_EXPECT_NULL(test, acrtc->event);
+	KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE);
+}
+
 /**
  * dm_test_vupdate_high_irq_no_crtc - Test vupdate high IRQ with no CRTC
  * @test: The KUnit test context
@@ -3334,6 +3374,81 @@ static void dm_test_vupdate_high_irq_no_crtc(struct kunit *test)
 	dm_vupdate_high_irq(&params);
 }
 
+/**
+ * dm_test_vupdate_high_irq_dcn_completes_flip - Test DCN vupdate flip delivery
+ * @test: The KUnit test context
+ *
+ * A non-zero DCE IP version routes vupdate to the shared CRTC handler. With a
+ * submitted flip, an armed event and no DC (so no flip is reported pending),
+ * the handler must deliver the event and clear the flip state. A non-zero last
+ * vblank timestamp also exercises the refresh-rate tracepoint.
+ */
+static void dm_test_vupdate_high_irq_dcn_completes_flip(struct kunit *test)
+{
+	struct drm_pending_vblank_event *event;
+	struct common_irq_params params = { 0 };
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 0, 0);
+	/* AI+ family so the handler does not return before the flip block. */
+	adev->family = AMDGPU_FAMILY_AI;
+
+	acrtc = dm_test_add_crtc(test, adev);
+	acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED;
+
+	event = kzalloc_obj(*event, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event);
+	acrtc->event = event;
+
+	adev->ddev.vblank[0].time = ktime_get();
+	adev->ddev.vblank[0].enabled = true;
+	KUNIT_ASSERT_EQ(test, drm_crtc_vblank_get(&acrtc->base), 0);
+
+	params.adev = adev;
+	params.irq_src = (enum dc_irq_source)IRQ_TYPE_VUPDATE;
+
+	dm_vupdate_high_irq(&params);
+
+	KUNIT_EXPECT_NULL(test, acrtc->event);
+	KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE);
+}
+
+/**
+ * dm_test_vupdate_high_irq_dcn_no_active_planes - Test DCN flip with no planes
+ * @test: The KUnit test context
+ *
+ * With no active planes the DCN HUBP may be clock-gated and the flip-pending
+ * status is undefined, so a submitted flip with no armed event must simply be
+ * retired rather than waiting on the OTG.
+ */
+static void dm_test_vupdate_high_irq_dcn_no_active_planes(struct kunit *test)
+{
+	struct common_irq_params params = { 0 };
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0);
+
+	adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 0, 0);
+	adev->family = AMDGPU_FAMILY_AI;
+
+	acrtc = dm_test_add_crtc(test, adev);
+	acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED;
+	acrtc->dm_irq_params.active_planes = 0;
+
+	params.adev = adev;
+	params.irq_src = (enum dc_irq_source)IRQ_TYPE_VUPDATE;
+
+	dm_vupdate_high_irq(&params);
+
+	KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE);
+}
+
 /**
  * dm_test_crtc_high_irq_no_crtc - Test crtc high IRQ with no CRTC
  * @test: The KUnit test context
@@ -4032,7 +4147,10 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	/* pflip/vupdate/crtc high IRQ callbacks */
 	KUNIT_CASE(dm_test_pflip_high_irq_no_crtc),
 	KUNIT_CASE(dm_test_pflip_high_irq_not_submitted),
+	KUNIT_CASE(dm_test_pflip_high_irq_completes_flip),
 	KUNIT_CASE(dm_test_vupdate_high_irq_no_crtc),
+	KUNIT_CASE(dm_test_vupdate_high_irq_dcn_completes_flip),
+	KUNIT_CASE(dm_test_vupdate_high_irq_dcn_no_active_planes),
 	KUNIT_CASE(dm_test_crtc_high_irq_no_crtc),
 	KUNIT_CASE(dm_test_crtc_high_irq_vrr_pre_ai),
 	KUNIT_CASE(dm_test_crtc_high_irq_vrr_ai_no_stream),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 63/82] drm/amd/display: Test writeback handling in dm_crtc_high_irq
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (61 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 62/82] drm/amd/display: Test pageflip completion in the high IRQ handlers IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 64/82] drm/amd/display: Test schedule_dc_vmin_vmax IVAN.LIPSKI
                   ` (19 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for the two writeback vblanks in dm_crtc_high_irq(): the
first disables frame capture and marks wb_frame_done, the second signals
the out fence and clears the pending state.

[HOW]
dc_stream_fc_disable_writeback() dereferences dc->res_pool before its
NULL-stream check, so supply a dc with a fake IRQ service. The
completion test queues a bare drm_writeback_job and holds a vblank
reference for the completion's drm_crtc_vblank_put().

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 97 +++++++++++++++++++
 1 file changed, 97 insertions(+)

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
index 0bf46e856f5a2..4cb52093987f3 100644
--- 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
@@ -10,6 +10,7 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_vblank.h>
+#include <drm/drm_writeback.h>
 
 #include "dc.h"
 #include "inc/core_types.h"
@@ -3524,6 +3525,100 @@ static void dm_test_crtc_high_irq_vrr_ai_no_stream(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE);
 }
 
+/**
+ * dm_test_crtc_high_irq_writeback_disables - Test first writeback vblank
+ * @test: The KUnit test context
+ *
+ * The first vblank after a writeback is armed must disable frame capture so the
+ * hardware cannot overwrite the buffer, and defer signalling completion to the
+ * next vblank by marking wb_frame_done.
+ */
+static void dm_test_crtc_high_irq_writeback_disables(struct kunit *test)
+{
+	struct drm_writeback_connector *wb_conn;
+	struct common_irq_params params = { 0 };
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc;
+	struct dc *dc;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0);
+
+	/* dc_stream_fc_disable_writeback() dereferences dc->res_pool. */
+	dc = dm_test_alloc_dc_with_irq_service(test,
+					       &dm_test_irq_service_funcs_dcn10);
+	adev->dm.dc = dc;
+	/* Pre-AI family returns right after CRC handling. */
+	adev->family = AMDGPU_FAMILY_SI;
+
+	acrtc = dm_test_add_crtc(test, adev);
+
+	wb_conn = kunit_kzalloc(test, sizeof(*wb_conn), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb_conn);
+	acrtc->wb_conn = wb_conn;
+	acrtc->wb_pending = true;
+
+	params.adev = adev;
+	params.irq_src = (enum dc_irq_source)IRQ_TYPE_VBLANK;
+
+	dm_crtc_high_irq(&params);
+
+	KUNIT_EXPECT_TRUE(test, acrtc->wb_frame_done);
+	KUNIT_EXPECT_TRUE(test, acrtc->wb_pending);
+}
+
+/**
+ * dm_test_crtc_high_irq_writeback_completes - Test second writeback vblank
+ * @test: The KUnit test context
+ *
+ * Once wb_frame_done is set the DMA has had a full frame period to flush, so
+ * the next vblank must signal the writeback out fence and clear the pending
+ * state.
+ */
+static void dm_test_crtc_high_irq_writeback_completes(struct kunit *test)
+{
+	struct drm_writeback_connector *wb_conn;
+	struct common_irq_params params = { 0 };
+	struct drm_writeback_job *job;
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0);
+	adev->family = AMDGPU_FAMILY_SI;
+
+	acrtc = dm_test_add_crtc(test, adev);
+
+	wb_conn = kunit_kzalloc(test, sizeof(*wb_conn), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb_conn);
+	spin_lock_init(&wb_conn->job_lock);
+	INIT_LIST_HEAD(&wb_conn->job_queue);
+
+	/*
+	 * The job carries no framebuffer or fence, so the deferred DRM cleanup
+	 * work only kfree()s it; it needs nothing from this test.
+	 */
+	job = kzalloc_obj(*job, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, job);
+	list_add_tail(&job->list_entry, &wb_conn->job_queue);
+
+	acrtc->wb_conn = wb_conn;
+	acrtc->wb_pending = true;
+	acrtc->wb_frame_done = true;
+
+	/* Balance the drm_crtc_vblank_put() done on completion. */
+	adev->ddev.vblank[0].enabled = true;
+	KUNIT_ASSERT_EQ(test, drm_crtc_vblank_get(&acrtc->base), 0);
+
+	params.adev = adev;
+	params.irq_src = (enum dc_irq_source)IRQ_TYPE_VBLANK;
+
+	dm_crtc_high_irq(&params);
+
+	KUNIT_EXPECT_FALSE(test, acrtc->wb_pending);
+	KUNIT_EXPECT_FALSE(test, acrtc->wb_frame_done);
+}
+
 /* Tests for dm_handle_hpd_work() */
 
 /**
@@ -4154,6 +4249,8 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	KUNIT_CASE(dm_test_crtc_high_irq_no_crtc),
 	KUNIT_CASE(dm_test_crtc_high_irq_vrr_pre_ai),
 	KUNIT_CASE(dm_test_crtc_high_irq_vrr_ai_no_stream),
+	KUNIT_CASE(dm_test_crtc_high_irq_writeback_disables),
+	KUNIT_CASE(dm_test_crtc_high_irq_writeback_completes),
 	/* dm_handle_hpd_work */
 	KUNIT_CASE(dm_test_handle_hpd_work_out_of_range),
 	/* dm_dmub_outbox1_low_irq */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 64/82] drm/amd/display: Test schedule_dc_vmin_vmax
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (62 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 63/82] drm/amd/display: Test writeback handling in dm_crtc_high_irq IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 65/82] drm/amd/display: Test handle_hpd_irq_helper detect and debounce exits IVAN.LIPSKI
                   ` (18 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for schedule_dc_vmin_vmax(), which had no coverage. Both
callers are exercised: dm_crtc_high_irq() on an AI+ family and the DCE
path of dm_vupdate_high_irq() on a pre-DCE12 family.

[HOW]
Add a dm_test_setup_vmin_vmax_crtc() fixture building a CRTC with an
active VRR stream and a vmin/vmax work queue. Each test flushes the
queue and checks the worker applied the adjust and dropped its stream
reference.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 114 ++++++++++++++++++
 1 file changed, 114 insertions(+)

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
index 4cb52093987f3..b398be878c932 100644
--- 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
@@ -3450,6 +3450,83 @@ static void dm_test_vupdate_high_irq_dcn_no_active_planes(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE);
 }
 
+/*
+ * Build a CRTC with an active VRR stream on a device whose vmin/vmax updates
+ * are offloaded to adev->dm.vmin_vmax_wq, seeded with the given timing adjust.
+ * Caller sets adev->family to select the DCE or DCN path, then invokes the
+ * handler and flushes the queue.
+ */
+static struct amdgpu_crtc *dm_test_setup_vmin_vmax_crtc(struct kunit *test,
+							unsigned int v_total_min,
+							unsigned int v_total_max)
+{
+	struct dc_stream_state *stream;
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc;
+	struct dc_link *link;
+	struct dc *dc;
+
+	adev = dm_kunit_alloc_adev(test);
+	mutex_init(&adev->dm.dc_lock);
+	KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0);
+	KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+	dc = dm_kunit_alloc_dc_with_ctx(test);
+	dc->current_state = kunit_kzalloc(test, sizeof(*dc->current_state),
+					  GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc->current_state);
+	adev->dm.dc = dc;
+
+	link = dm_kunit_alloc_link(test);
+	stream = dm_kunit_alloc_stream(test, link);
+
+	acrtc = dm_test_add_crtc(test, adev);
+	/* VRR active so the !vrr_active vblank handler is skipped. */
+	acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE;
+	acrtc->dm_irq_params.stream = stream;
+	acrtc->dm_irq_params.vrr_params.adjust.v_total_min = v_total_min;
+	acrtc->dm_irq_params.vrr_params.adjust.v_total_max = v_total_max;
+
+	return acrtc;
+}
+
+/**
+ * dm_test_vupdate_high_irq_dce_vrr_btr - Test DCE vupdate BTR handling
+ * @test: The KUnit test context
+ *
+ * On DCE (zero DCE IP version) with VRR active and a pre-DCE12 family, vupdate
+ * runs the below-the-range section: it handles the vblank and offloads the
+ * vmin/vmax update, which the worker then applies to the stream.
+ */
+static void dm_test_vupdate_high_irq_dce_vrr_btr(struct kunit *test)
+{
+	struct common_irq_params params = { 0 };
+	struct dc_stream_state *stream;
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc;
+
+	acrtc = dm_test_setup_vmin_vmax_crtc(test, 2000, 2200);
+	adev = drm_to_adev(acrtc->base.dev);
+	stream = acrtc->dm_irq_params.stream;
+	/* Pre-AI family so the BTR block inside the DCE path runs. */
+	adev->family = AMDGPU_FAMILY_SI;
+
+	adev->ddev.vblank[0].time = ktime_get();
+
+	params.adev = adev;
+	params.irq_src = (enum dc_irq_source)IRQ_TYPE_VUPDATE;
+
+	dm_vupdate_high_irq(&params);
+
+	/* The offloaded worker applies the adjust and drops its stream ref. */
+	flush_workqueue(adev->dm.vmin_vmax_wq);
+	KUNIT_EXPECT_EQ(test, stream->adjust.v_total_min, 2000);
+	KUNIT_EXPECT_EQ(test, stream->adjust.v_total_max, 2200);
+	KUNIT_EXPECT_EQ(test, kref_read(&stream->refcount), 1);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
 /**
  * dm_test_crtc_high_irq_no_crtc - Test crtc high IRQ with no CRTC
  * @test: The KUnit test context
@@ -3619,6 +3696,41 @@ static void dm_test_crtc_high_irq_writeback_completes(struct kunit *test)
 	KUNIT_EXPECT_FALSE(test, acrtc->wb_frame_done);
 }
 
+/**
+ * dm_test_crtc_high_irq_schedules_vmin_vmax - Test the freesync BTR update
+ * @test: The KUnit test context
+ *
+ * On an AI+ family with an active VRR stream, the handler must run the freesync
+ * v-update and offload the vmin/vmax programming, which the worker then applies
+ * to the stream.
+ */
+static void dm_test_crtc_high_irq_schedules_vmin_vmax(struct kunit *test)
+{
+	struct common_irq_params params = { 0 };
+	struct dc_stream_state *stream;
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc;
+
+	acrtc = dm_test_setup_vmin_vmax_crtc(test, 1000, 1100);
+	adev = drm_to_adev(acrtc->base.dev);
+	stream = acrtc->dm_irq_params.stream;
+	adev->family = AMDGPU_FAMILY_AI;
+	acrtc->dm_irq_params.vrr_params.supported = true;
+
+	params.adev = adev;
+	params.irq_src = (enum dc_irq_source)IRQ_TYPE_VBLANK;
+
+	dm_crtc_high_irq(&params);
+
+	/* The offloaded worker applies the adjust and drops its stream ref. */
+	flush_workqueue(adev->dm.vmin_vmax_wq);
+	KUNIT_EXPECT_EQ(test, stream->adjust.v_total_min, 1000);
+	KUNIT_EXPECT_EQ(test, stream->adjust.v_total_max, 1100);
+	KUNIT_EXPECT_EQ(test, kref_read(&stream->refcount), 1);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
 /* Tests for dm_handle_hpd_work() */
 
 /**
@@ -4246,11 +4358,13 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	KUNIT_CASE(dm_test_vupdate_high_irq_no_crtc),
 	KUNIT_CASE(dm_test_vupdate_high_irq_dcn_completes_flip),
 	KUNIT_CASE(dm_test_vupdate_high_irq_dcn_no_active_planes),
+	KUNIT_CASE(dm_test_vupdate_high_irq_dce_vrr_btr),
 	KUNIT_CASE(dm_test_crtc_high_irq_no_crtc),
 	KUNIT_CASE(dm_test_crtc_high_irq_vrr_pre_ai),
 	KUNIT_CASE(dm_test_crtc_high_irq_vrr_ai_no_stream),
 	KUNIT_CASE(dm_test_crtc_high_irq_writeback_disables),
 	KUNIT_CASE(dm_test_crtc_high_irq_writeback_completes),
+	KUNIT_CASE(dm_test_crtc_high_irq_schedules_vmin_vmax),
 	/* dm_handle_hpd_work */
 	KUNIT_CASE(dm_test_handle_hpd_work_out_of_range),
 	/* dm_dmub_outbox1_low_irq */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 65/82] drm/amd/display: Test handle_hpd_irq_helper detect and debounce exits
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (63 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 64/82] drm/amd/display: Test schedule_dc_vmin_vmax IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 66/82] drm/amd/display: Test HPD RX, HPD init and DMUB callback branches IVAN.LIPSKI
                   ` (17 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for handle_hpd_irq_helper() covering a failing
dc_link_detect_connection_type() and the early return taken when a
debounce re-detect is already scheduled. Also arm the debounce work in
the stale-prev-sink test so mod_delayed_work() re-schedules.

[HOW]
Both build on dm_test_setup_hpd_irq_helper() and override only the
link_srv stubs they need. The pending-debounce test arms the work ten
seconds out and spies on dc_link_detect() to prove it was skipped.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 79 +++++++++++++++++++
 1 file changed, 79 insertions(+)

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
index b398be878c932..c335ac0e2a6f5 100644
--- 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
@@ -58,6 +58,17 @@ static bool dm_test_detect_link_false(struct dc_link *link,
 	return false;
 }
 
+/* Spy on dc_link_detect() to prove the debounce early-return skipped it. */
+static int dm_test_detect_link_count;
+
+static bool dm_test_detect_link_false_count(struct dc_link *link,
+					    enum dc_detect_reason reason)
+{
+	dm_test_detect_link_count++;
+
+	return false;
+}
+
 static bool dm_test_detect_connection_single(struct dc_link *link,
 					     enum dc_connection_type *type)
 {
@@ -66,6 +77,12 @@ static bool dm_test_detect_connection_single(struct dc_link *link,
 	return true;
 }
 
+static bool dm_test_detect_connection_fail(struct dc_link *link,
+					   enum dc_connection_type *type)
+{
+	return false;
+}
+
 /* Recording stubs for the dm_handle_hpd_rx_offload_work() DP-IRQ branches. */
 static int dm_test_automated_test_count;
 static int dm_test_handle_link_loss_count;
@@ -2607,6 +2624,9 @@ static void dm_test_handle_hpd_irq_helper_debounce_schedule(struct kunit *test)
  * When the debounce branch is taken and a stale hdmi_prev_sink is already
  * cached from a previous HPD, it must be released before caching the current
  * local_sink. This exercises the dc_sink_release() of the previous sink.
+ *
+ * An already-armed debounce work also makes mod_delayed_work() re-schedule
+ * rather than queue.
  */
 static void dm_test_handle_hpd_irq_helper_debounce_release_prev(struct kunit *test)
 {
@@ -2628,6 +2648,10 @@ static void dm_test_handle_hpd_irq_helper_debounce_release_prev(struct kunit *te
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn->hdmi_prev_sink);
 	KUNIT_ASSERT_PTR_NE(test, aconn->hdmi_prev_sink, link->local_sink);
 
+	KUNIT_ASSERT_TRUE(test,
+			  schedule_delayed_work(&aconn->hdmi_hpd_debounce_work,
+						10 * HZ));
+
 	handle_hpd_irq_helper(aconn, DETECT_REASON_HPD);
 
 	/* Stale sink replaced by the current local_sink. */
@@ -2662,6 +2686,59 @@ static void dm_test_handle_hpd_irq_helper_detect_false(struct kunit *test)
 	KUNIT_EXPECT_FALSE(test, aconn->fake_enable);
 }
 
+/**
+ * dm_test_handle_hpd_irq_helper_detect_type_fails - Test detect failure logging
+ * @test: The KUnit test context
+ *
+ * When dc_link_detect_connection_type() itself fails the helper logs an error
+ * and carries on with the connection type left as none, so it still falls
+ * through to the immediate-detect branch.
+ */
+static void dm_test_handle_hpd_irq_helper_detect_type_fails(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconn;
+	struct link_service *link_srv;
+
+	aconn = dm_test_setup_hpd_irq_helper(test, &link_srv);
+	link_srv->detect_connection_type = dm_test_detect_connection_fail;
+	link_srv->detect_link = dm_test_detect_link_false;
+	aconn->fake_enable = true;
+
+	handle_hpd_irq_helper(aconn, DETECT_REASON_HPD);
+
+	KUNIT_EXPECT_FALSE(test, aconn->fake_enable);
+}
+
+/**
+ * dm_test_handle_hpd_irq_helper_debounce_pending - Test pending-debounce exit
+ * @test: The KUnit test context
+ *
+ * A debounce re-detect that is already scheduled owns the connector state, so
+ * an HPD that would otherwise detect immediately must return early and leave
+ * dc_link_detect() untouched.
+ */
+static void dm_test_handle_hpd_irq_helper_debounce_pending(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconn;
+	struct link_service *link_srv;
+
+	dm_test_detect_link_count = 0;
+
+	aconn = dm_test_setup_hpd_irq_helper(test, &link_srv);
+	link_srv->detect_link = dm_test_detect_link_false_count;
+
+	/* Arm the debounce work far enough out that it cannot run here. */
+	KUNIT_ASSERT_TRUE(test,
+			  schedule_delayed_work(&aconn->hdmi_hpd_debounce_work,
+						10 * HZ));
+
+	handle_hpd_irq_helper(aconn, DETECT_REASON_HPD);
+
+	KUNIT_EXPECT_EQ(test, dm_test_detect_link_count, 0);
+
+	cancel_delayed_work_sync(&aconn->hdmi_hpd_debounce_work);
+}
+
 /* Tests for handle_hpd_rx_irq()/schedule_hpd_rx_offload_work() */
 
 /**
@@ -4331,6 +4408,8 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	KUNIT_CASE(dm_test_handle_hpd_irq_helper_debounce_schedule),
 	KUNIT_CASE(dm_test_handle_hpd_irq_helper_debounce_release_prev),
 	KUNIT_CASE(dm_test_handle_hpd_irq_helper_detect_false),
+	KUNIT_CASE(dm_test_handle_hpd_irq_helper_detect_type_fails),
+	KUNIT_CASE(dm_test_handle_hpd_irq_helper_debounce_pending),
 	/* handle_hpd_rx_irq/schedule_hpd_rx_offload_work */
 	KUNIT_CASE(dm_test_handle_hpd_rx_irq_disabled),
 	KUNIT_CASE(dm_test_handle_hpd_rx_irq_no_left_work),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 66/82] drm/amd/display: Test HPD RX, HPD init and DMUB callback branches
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (64 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 65/82] drm/amd/display: Test handle_hpd_irq_helper detect and debounce exits IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:15 ` [PATCH 67/82] drm/amd/display: Test dm_dmub_outbox1_low_irq drain and work guards IVAN.LIPSKI
                   ` (16 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for handle_hpd_rx_irq() re-detecting a downstream port
change on a non-MST-root connector, amdgpu_dm_hpd_init() skipping a
connector with no dc_link, and dmub_hpd_callback() deferring HPD while
suspended. Also add a writeback connector to the HPD registration test.

[HOW]
The downstream-change test reuses dm_test_setup_hpd_rx_irq() and clears
the MST root state, with dc_link_detect() stubbed to fail. The suspend
test leaves adev->dm.ddev NULL, so reaching the connector walk would
crash.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 109 ++++++++++++++++++
 1 file changed, 109 insertions(+)

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
index c335ac0e2a6f5..4f22230a5520e 100644
--- 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
@@ -197,6 +197,18 @@ static bool dm_test_handle_hpd_rx_link_loss(struct dc_link *link,
 	return false;
 }
 
+static bool dm_test_handle_hpd_rx_result_true(struct dc_link *link,
+					      union hpd_irq_data *hpd_irq_data,
+					      bool *link_loss,
+					      bool defer_handling,
+					      bool *has_left_work)
+{
+	*link_loss = false;
+	*has_left_work = false;
+
+	return true;
+}
+
 static bool dm_test_allow_hpd_rx_irq_true(const struct dc_link *link)
 {
 	return true;
@@ -2165,6 +2177,35 @@ static void dm_test_hpd_init_fini_irq_ref(struct kunit *test)
 	amdgpu_dm_hpd_fini(adev);
 }
 
+/**
+ * dm_test_hpd_init_skips_connector_without_link - Test HPD init skips linkless
+ * @test: The KUnit test context
+ *
+ * A connector that does not carry a dc_link yet must be skipped by the init
+ * loop. Only init is exercised: fini has no such guard.
+ */
+static void dm_test_hpd_init_skips_connector_without_link(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconn;
+	struct amdgpu_device *adev;
+
+	adev = dm_kunit_alloc_adev(test);
+	adev->mode_info.num_hpd = 0;
+
+	aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn);
+	KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base,
+						 &dm_test_connector_funcs,
+						 DRM_MODE_CONNECTOR_DisplayPort), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_connector_cleanup,
+							&aconn->base), 0);
+
+	amdgpu_dm_hpd_init(adev);
+
+	/* Skipped before the analog check, so polling was never requested. */
+	KUNIT_EXPECT_FALSE(test, adev->ddev.mode_config.poll_enabled);
+}
+
 /* Tests for dm_handle_hpd_rx_offload_work() */
 
 /**
@@ -2953,6 +2994,34 @@ static void dm_test_handle_hpd_rx_irq_link_loss(struct kunit *test)
 				->dm.hpd_rx_offload_wq->is_handling_link_loss);
 }
 
+/**
+ * dm_test_handle_hpd_rx_irq_downstream_change - Test HPDRX post-detect block
+ * @test: The KUnit test context
+ *
+ * A handled HPD RX IRQ on a connector that is not an MST root means a
+ * downstream port status change, so the handler must re-detect the link.
+ * Detection is stubbed to fail, leaving the connector untouched, and a
+ * non-MST-branch link also exercises the trailing CEC IRQ dispatch.
+ */
+static void dm_test_handle_hpd_rx_irq_downstream_change(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconn;
+	struct link_service *link_srv;
+
+	aconn = dm_test_setup_hpd_rx_irq(test, &link_srv);
+	link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_result_true;
+	link_srv->detect_connection_type = dm_test_detect_connection_single;
+	link_srv->detect_link = dm_test_detect_link_false;
+
+	aconn->mst_mgr.mst_state = false;
+	aconn->dc_link->type = dc_connection_single;
+	aconn->fake_enable = true;
+
+	handle_hpd_rx_irq(aconn);
+
+	KUNIT_EXPECT_TRUE(test, aconn->fake_enable);
+}
+
 /* Tests for dmub_hpd_callback()/dmub_hpd_sense_callback() */
 
 /**
@@ -3010,6 +3079,31 @@ static void dm_test_dmub_hpd_callback_empty_connectors(struct kunit *test)
 	dmub_hpd_sense_callback(adev, &notify);
 }
 
+/**
+ * dm_test_dmub_hpd_callback_suspend_skip - Test DMUB HPD skipped in suspend
+ * @test: The KUnit test context
+ *
+ * Plain HPD notifications are deferred while the device is suspended; they get
+ * re-probed on resume, so the callback must return before the connector walk.
+ */
+static void dm_test_dmub_hpd_callback_suspend_skip(struct kunit *test)
+{
+	struct dmub_notification notify = { 0 };
+	struct amdgpu_device *adev;
+	struct dc *dc;
+
+	adev = dm_kunit_alloc_adev(test);
+	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc);
+	adev->dm.dc = dc;
+	adev->in_suspend = true;
+	dc->link_count = 1;
+	notify.type = DMUB_NOTIFICATION_HPD;
+
+	/* adev->dm.ddev is left NULL: reaching the connector walk would crash. */
+	dmub_hpd_callback(adev, &notify);
+}
+
 /**
  * dm_test_dmub_hpd_callback_unknown_type_match - Test DMUB callback matched
  *						   connector, unknown type
@@ -3217,10 +3311,15 @@ static void dm_test_register_hpd_handlers_empty(struct kunit *test)
 /**
  * dm_test_register_hpd_handlers_valid - Test HPD/HPDRX registration succeeds
  * @test: The KUnit test context
+ *
+ * A writeback connector must be skipped by the per-connector loop, while a
+ * DisplayPort connector carrying valid HPD and HPD RX sources registers a
+ * low-context handler for each.
  */
 static void dm_test_register_hpd_handlers_valid(struct kunit *test)
 {
 	struct amdgpu_dm_connector *aconn;
+	struct drm_connector *wbconn;
 	struct amdgpu_device *adev;
 	struct dc_link *link;
 	struct dc *dc;
@@ -3231,6 +3330,13 @@ static void dm_test_register_hpd_handlers_valid(struct kunit *test)
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc);
 	adev->dm.dc = dc;
 
+	wbconn = kunit_kzalloc(test, sizeof(*wbconn), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wbconn);
+	KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wbconn, &dm_test_connector_funcs,
+						 DRM_MODE_CONNECTOR_WRITEBACK), 0);
+	KUNIT_ASSERT_EQ(test,
+			kunit_add_action_or_reset(test, dm_test_connector_cleanup, wbconn), 0);
+
 	aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn);
 	KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base,
@@ -4395,6 +4501,7 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	KUNIT_CASE(dm_test_hpd_init_fini_with_connectors),
 	KUNIT_CASE(dm_test_hpd_init_fini_analog_connector),
 	KUNIT_CASE(dm_test_hpd_init_fini_irq_ref),
+	KUNIT_CASE(dm_test_hpd_init_skips_connector_without_link),
 	/* dm_handle_hpd_rx_offload_work */
 	KUNIT_CASE(dm_test_hpd_rx_offload_work_no_connector),
 	KUNIT_CASE(dm_test_hpd_rx_offload_work_no_connection),
@@ -4416,11 +4523,13 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	KUNIT_CASE(dm_test_handle_hpd_rx_irq_automated_test),
 	KUNIT_CASE(dm_test_handle_hpd_rx_irq_msg_rdy),
 	KUNIT_CASE(dm_test_handle_hpd_rx_irq_link_loss),
+	KUNIT_CASE(dm_test_handle_hpd_rx_irq_downstream_change),
 	KUNIT_CASE(dm_test_schedule_hpd_rx_offload_work),
 	/* dmub_hpd_callback/dmub_hpd_sense_callback */
 	KUNIT_CASE(dm_test_dmub_hpd_callback_null_inputs),
 	KUNIT_CASE(dm_test_dmub_hpd_callback_invalid_index),
 	KUNIT_CASE(dm_test_dmub_hpd_callback_empty_connectors),
+	KUNIT_CASE(dm_test_dmub_hpd_callback_suspend_skip),
 	KUNIT_CASE(dm_test_dmub_hpd_callback_unknown_type_match),
 	KUNIT_CASE(dm_test_dmub_hpd_callback_hpd_type),
 	KUNIT_CASE(dm_test_dmub_hpd_callback_hpd_irq_type),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 67/82] drm/amd/display: Test dm_dmub_outbox1_low_irq drain and work guards
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (65 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 66/82] drm/amd/display: Test HPD RX, HPD init and DMUB callback branches IVAN.LIPSKI
@ 2026-08-18 20:15 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 68/82] drm/amd/display: Test amdgpu_dm_dce110_register_irq_handlers IVAN.LIPSKI
                   ` (15 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:15 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for dm_dmub_outbox1_low_irq() draining a trace ring up
to the DMUB_TRACE_MAX_READ cap, dm_handle_hpd_work() rejecting a work
item with no notification, and amdgpu_dm_irq_schedule_work() not
queueing once the IRQ workqueue is gone.

[HOW]
Back dmub->outbox0_rb with a fake ring whose write pointer sits one
entry past the cap, and keep the ring larger so the read pointer does
not wrap. dm_handle_hpd_work() returns before its own kfree() on the
NULL-notify path, so the test frees the work item.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 110 ++++++++++++++++++
 1 file changed, 110 insertions(+)

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
index 4f22230a5520e..933a33a360fbe 100644
--- 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
@@ -220,6 +220,18 @@ static uint32_t dm_test_dmub_get_outbox0_wptr(struct dmub_srv *dmub)
 	return 0;
 }
 
+/* One more than DMUB_TRACE_MAX_READ, so the drain loop reaches its cap. */
+#define DM_TEST_OUTBOX0_TRACE_ENTRIES	65
+#define DM_TEST_OUTBOX0_TRACE_BYTES \
+	(DM_TEST_OUTBOX0_TRACE_ENTRIES * sizeof(struct dmcub_trace_buf_entry))
+/* Keep the ring larger than the write pointer so rptr does not wrap. */
+#define DM_TEST_OUTBOX0_RB_SIZE		(2 * DM_TEST_OUTBOX0_TRACE_BYTES)
+
+static uint32_t dm_test_dmub_get_outbox0_wptr_full(struct dmub_srv *dmub)
+{
+	return DM_TEST_OUTBOX0_TRACE_BYTES;
+}
+
 static uint32_t dm_test_dmub_get_outbox1_wptr(struct dmub_srv *dmub)
 {
 	return 0;
@@ -1941,6 +1953,39 @@ static void dm_test_irq_schedule_work_requeue_fallback(struct kunit *test)
 	amdgpu_dm_irq_fini(adev);
 }
 
+/**
+ * dm_test_irq_schedule_work_no_workqueue - Test schedule work after wq teardown
+ * @test: The KUnit test context
+ *
+ * An interrupt that races DM teardown can reach the scheduler after the IRQ
+ * workqueue is gone, so a registered handler must simply not be queued.
+ */
+static void dm_test_irq_schedule_work_no_workqueue(struct kunit *test)
+{
+	struct dc_interrupt_params int_params = { 0 };
+	struct amdgpu_device *adev;
+	int count = 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_count, &count);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+	destroy_workqueue(adev->dm.irq_wq);
+	adev->dm.irq_wq = NULL;
+
+	amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1);
+	KUNIT_EXPECT_EQ(test, count, 0);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
 /* Tests for amdgpu_dm_set_hpd_irq_state() */
 
 /**
@@ -3938,6 +3983,28 @@ static void dm_test_handle_hpd_work_out_of_range(struct kunit *test)
 	dm_handle_hpd_work(&hpd_work->handle_hpd_work);
 }
 
+/**
+ * dm_test_handle_hpd_work_null_notify - Test HPD work with no notification
+ * @test: The KUnit test context
+ *
+ * A work item carrying no notification must be rejected before dispatch.
+ */
+static void dm_test_handle_hpd_work_null_notify(struct kunit *test)
+{
+	struct dmub_hpd_work *hpd_work;
+	struct amdgpu_device *adev;
+
+	adev = dm_kunit_alloc_adev(test);
+	hpd_work = kzalloc_obj(*hpd_work, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hpd_work);
+	hpd_work->adev = adev;
+	INIT_WORK(&hpd_work->handle_hpd_work, dm_handle_hpd_work);
+
+	/* The handler bails out before its own kfree(), so free it here. */
+	dm_handle_hpd_work(&hpd_work->handle_hpd_work);
+	kfree(hpd_work);
+}
+
 /* Tests for dm_dmub_outbox1_low_irq() */
 
 /**
@@ -3969,6 +4036,46 @@ static void dm_test_dmub_outbox1_low_irq_empty(struct kunit *test)
 	dm_dmub_outbox1_low_irq(&params);
 }
 
+/**
+ * dm_test_dmub_outbox1_low_irq_drains_trace - Test the trace drain loop
+ * @test: The KUnit test context
+ *
+ * A trace ring holding more entries than the handler reads in one pass must be
+ * drained up to the DMUB_TRACE_MAX_READ cap, leaving the read pointer parked
+ * after the last entry the handler consumed.
+ */
+static void dm_test_dmub_outbox1_low_irq_drains_trace(struct kunit *test)
+{
+	struct common_irq_params params = { 0 };
+	struct dc_dmub_srv *dc_dmub_srv;
+	struct amdgpu_device *adev;
+	struct dmub_srv *dmub;
+	struct dc *dc;
+	void *rb;
+
+	adev = dm_kunit_alloc_adev(test);
+	dc = dm_kunit_alloc_dc_with_ctx(test);
+	dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv);
+	dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub);
+	rb = kunit_kzalloc(test, DM_TEST_OUTBOX0_RB_SIZE, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rb);
+
+	dmub->outbox0_rb.base_address = rb;
+	dmub->outbox0_rb.capacity = DM_TEST_OUTBOX0_RB_SIZE;
+	dmub->hw_funcs.get_outbox0_wptr = dm_test_dmub_get_outbox0_wptr_full;
+	dc_dmub_srv->dmub = dmub;
+	dc->ctx->dmub_srv = dc_dmub_srv;
+	adev->dm.dc = dc;
+	params.adev = adev;
+	params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX;
+
+	dm_dmub_outbox1_low_irq(&params);
+
+	KUNIT_EXPECT_EQ(test, dmub->outbox0_rb.rptr, DM_TEST_OUTBOX0_TRACE_BYTES);
+}
+
 /*
  * dm_test_alloc_adev_outbox_notify - Build an adev wired for DMUB outbox
  * notification handling.
@@ -4487,6 +4594,7 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	KUNIT_CASE(dm_test_irq_schedule_work_empty),
 	KUNIT_CASE(dm_test_irq_schedule_work_queues_handler),
 	KUNIT_CASE(dm_test_irq_schedule_work_requeue_fallback),
+	KUNIT_CASE(dm_test_irq_schedule_work_no_workqueue),
 	/* amdgpu_dm_set_hpd_irq_state */
 	KUNIT_CASE(dm_test_set_hpd_irq_state_null_dc),
 	/* amdgpu_dm_set_dmub_outbox_irq_state */
@@ -4555,8 +4663,10 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	KUNIT_CASE(dm_test_crtc_high_irq_schedules_vmin_vmax),
 	/* dm_handle_hpd_work */
 	KUNIT_CASE(dm_test_handle_hpd_work_out_of_range),
+	KUNIT_CASE(dm_test_handle_hpd_work_null_notify),
 	/* dm_dmub_outbox1_low_irq */
 	KUNIT_CASE(dm_test_dmub_outbox1_low_irq_empty),
+	KUNIT_CASE(dm_test_dmub_outbox1_low_irq_drains_trace),
 	KUNIT_CASE(dm_test_dmub_outbox1_low_irq_no_handler),
 	KUNIT_CASE(dm_test_dmub_outbox1_low_irq_direct_callback),
 	KUNIT_CASE(dm_test_dmub_outbox1_low_irq_offload),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 68/82] drm/amd/display: Test amdgpu_dm_dce110_register_irq_handlers
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (66 preceding siblings ...)
  2026-08-18 20:15 ` [PATCH 67/82] drm/amd/display: Test dm_dmub_outbox1_low_irq drain and work guards IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 69/82] drm/amd/display: Test amdgpu_dm_dcn10_register_irq_handlers IVAN.LIPSKI
                   ` (14 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_dce110_register_irq_handlers() covering
the SOC15 DCE client used from Vega on, the amdgpu_irq_add_id()
rejections for the CRTC, VUPDATE and HPD sources, and the
invalid-source guards in the VBLANK, VUPDATE and PFLIP loops.

[HOW]
Add three fake IRQ services that map only a prefix of the source IDs so
each loop reaches its guard in turn. Clearing a single amdgpu_irq_src's
funcs makes only that source's add_id() fail.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 196 ++++++++++++++++++
 1 file changed, 196 insertions(+)

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
index 933a33a360fbe..a354fa25998f7 100644
--- 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
@@ -276,6 +276,55 @@ static const struct irq_service_funcs dm_test_irq_service_funcs_dce110 = {
 	.to_dal_irq_source = dm_test_to_dal_irq_source_dce110
 };
 
+/* Maps nothing, so the first (VBLANK) register loop sees an invalid source. */
+static enum dc_irq_source dm_test_to_dal_irq_source_unmapped(
+		struct irq_service *irq_service,
+		uint32_t src_id,
+		uint32_t ext_id)
+{
+	return DC_IRQ_SOURCE_INVALID;
+}
+
+static const struct irq_service_funcs dm_test_irq_service_funcs_unmapped = {
+	.to_dal_irq_source = dm_test_to_dal_irq_source_unmapped
+};
+
+/* Maps VBLANK only, so the VUPDATE register loop sees an invalid source. */
+static enum dc_irq_source dm_test_to_dal_irq_source_vblank_only(
+		struct irq_service *irq_service,
+		uint32_t src_id,
+		uint32_t ext_id)
+{
+	if (src_id == VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0)
+		return DC_IRQ_SOURCE_VBLANK1;
+
+	return DC_IRQ_SOURCE_INVALID;
+}
+
+static const struct irq_service_funcs dm_test_irq_service_funcs_vblank_only = {
+	.to_dal_irq_source = dm_test_to_dal_irq_source_vblank_only
+};
+
+/* Maps everything but PFLIP, so the PFLIP register loop sees an invalid source. */
+static enum dc_irq_source dm_test_to_dal_irq_source_no_pflip(
+		struct irq_service *irq_service,
+		uint32_t src_id,
+		uint32_t ext_id)
+{
+	switch (src_id) {
+	case VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0:
+		return DC_IRQ_SOURCE_VBLANK1;
+	case VISLANDS30_IV_SRCID_D1_V_UPDATE_INT:
+		return DC_IRQ_SOURCE_VUPDATE1;
+	default:
+		return DC_IRQ_SOURCE_INVALID;
+	}
+}
+
+static const struct irq_service_funcs dm_test_irq_service_funcs_no_pflip = {
+	.to_dal_irq_source = dm_test_to_dal_irq_source_no_pflip
+};
+
 static enum dc_irq_source dm_test_to_dal_irq_source_dcn10(
 		struct irq_service *irq_service,
 		uint32_t src_id,
@@ -4266,6 +4315,146 @@ static void dm_test_dce110_register_irq_handlers_one_crtc(struct kunit *test)
 	amdgpu_dm_irq_fini(adev);
 }
 
+/**
+ * dm_test_dce110_register_irq_handlers_soc15_client - Test DCE110 on AI+ ASICs
+ * @test: The KUnit test context
+ *
+ * From Vega on, the DCE interrupts are routed through the SOC15 DCE client
+ * rather than the legacy one, so the IRQ sources must be registered there.
+ */
+static void dm_test_dce110_register_irq_handlers_soc15_client(struct kunit *test)
+{
+	struct amdgpu_irq_src **soc15_sources;
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_dce110);
+	adev->family = AMDGPU_FAMILY_AI;
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), 0);
+
+	KUNIT_EXPECT_NULL(test, adev->irq.client[AMDGPU_IRQ_CLIENTID_LEGACY].sources);
+	soc15_sources = adev->irq.client[SOC15_IH_CLIENTID_DCE].sources;
+	KUNIT_ASSERT_NOT_NULL(test, soc15_sources);
+	KUNIT_EXPECT_PTR_EQ(test,
+			    soc15_sources[VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0],
+			    &adev->crtc_irq);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
+/**
+ * dm_test_dce110_register_irq_handlers_crtc_add_id_fails - Test add-id failure
+ * @test: The KUnit test context
+ *
+ * amdgpu_irq_add_id() rejects a source with no funcs table, so registering the
+ * VBLANK interrupt before amdgpu_dm_set_irq_funcs() has run must fail.
+ */
+static void dm_test_dce110_register_irq_handlers_crtc_add_id_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct dc *dc;
+
+	adev = dm_kunit_alloc_adev(test);
+	dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dce110);
+	dc->ctx->dce_version = DCE_VERSION_11_0;
+	adev->dm.dc = dc;
+	adev->mode_info.num_crtc = 1;
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL);
+}
+
+/**
+ * dm_test_dce110_register_irq_handlers_bad_vblank_source - Test VBLANK guard
+ * @test: The KUnit test context
+ *
+ * A source ID that DC does not map to a VBLANK IRQ source must be rejected
+ * rather than used to index the vblank parameter array.
+ */
+static void dm_test_dce110_register_irq_handlers_bad_vblank_source(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_unmapped);
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
+/**
+ * dm_test_dce110_register_irq_handlers_bad_vupdate_source - Test VUPDATE guard
+ * @test: The KUnit test context
+ *
+ * With VBLANK mapped but VUPDATE unmapped, registration must fail once the
+ * VUPDATE loop sees the invalid source.
+ */
+static void dm_test_dce110_register_irq_handlers_bad_vupdate_source(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_vblank_only);
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
+/**
+ * dm_test_dce110_register_irq_handlers_bad_pflip_source - Test PFLIP guard
+ * @test: The KUnit test context
+ *
+ * With VBLANK and VUPDATE mapped but PFLIP unmapped, registration must fail
+ * once the pageflip loop sees the invalid source.
+ */
+static void dm_test_dce110_register_irq_handlers_bad_pflip_source(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_no_pflip);
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
+/**
+ * dm_test_dce110_register_irq_handlers_vupdate_add_id_fails - Test add-id failure
+ * @test: The KUnit test context
+ *
+ * The VBLANK loop succeeds, then a VUPDATE source with no funcs table is
+ * rejected by amdgpu_irq_add_id().
+ */
+static void dm_test_dce110_register_irq_handlers_vupdate_add_id_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_dce110);
+	adev->vupdate_irq.funcs = NULL;
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
+/**
+ * dm_test_dce110_register_irq_handlers_hpd_add_id_fails - Test HPD add-id failure
+ * @test: The KUnit test context
+ *
+ * All three per-CRTC loops succeed, then the HPD source with no funcs table is
+ * rejected by amdgpu_irq_add_id().
+ */
+static void dm_test_dce110_register_irq_handlers_hpd_add_id_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_dce110);
+	adev->hpd_irq.funcs = NULL;
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
 /**
  * dm_test_dcn10_register_irq_handlers_zero_crtc - Test DCN10 zero-CRTC registration
  * @test: The KUnit test context
@@ -4673,6 +4862,13 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	/* IRQ handler registration helpers */
 	KUNIT_CASE(dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources),
 	KUNIT_CASE(dm_test_dce110_register_irq_handlers_one_crtc),
+	KUNIT_CASE(dm_test_dce110_register_irq_handlers_soc15_client),
+	KUNIT_CASE(dm_test_dce110_register_irq_handlers_crtc_add_id_fails),
+	KUNIT_CASE(dm_test_dce110_register_irq_handlers_bad_vblank_source),
+	KUNIT_CASE(dm_test_dce110_register_irq_handlers_bad_vupdate_source),
+	KUNIT_CASE(dm_test_dce110_register_irq_handlers_bad_pflip_source),
+	KUNIT_CASE(dm_test_dce110_register_irq_handlers_vupdate_add_id_fails),
+	KUNIT_CASE(dm_test_dce110_register_irq_handlers_hpd_add_id_fails),
 	KUNIT_CASE(dm_test_dcn10_register_irq_handlers_zero_crtc),
 	KUNIT_CASE(dm_test_dcn10_register_irq_handlers_one_crtc),
 	KUNIT_CASE(dm_test_register_outbox_irq_handlers_without_dmub),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 69/82] drm/amd/display: Test amdgpu_dm_dcn10_register_irq_handlers
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (67 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 68/82] drm/amd/display: Test amdgpu_dm_dce110_register_irq_handlers IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 70/82] drm/amd/display: Fix mismatch number of OPP/DPP accounting IVAN.LIPSKI
                   ` (13 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Alex Hung <alex.hung@amd.com>

[WHAT]
Add KUnit tests for amdgpu_dm_dcn10_register_irq_handlers() covering the
add_id() rejections for the VUPDATE and HPD sources and the
invalid-source guard in the VUPDATE_NO_LOCK loop, and for
amdgpu_dm_register_outbox_irq_handlers() rejecting a source with no
funcs table.

[HOW]
Reuse the shared registration fixture and the fake IRQ service that maps
no source IDs.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 78 +++++++++++++++++++
 1 file changed, 78 insertions(+)

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
index a354fa25998f7..37851af59ffe1 100644
--- 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
@@ -4508,6 +4508,61 @@ static void dm_test_dcn10_register_irq_handlers_one_crtc(struct kunit *test)
 	amdgpu_dm_irq_fini(adev);
 }
 
+/**
+ * dm_test_dcn10_register_irq_handlers_vupdate_add_id_fails - Test add-id failure
+ * @test: The KUnit test context
+ *
+ * A VUPDATE source with no funcs table must be rejected by amdgpu_irq_add_id().
+ */
+static void dm_test_dcn10_register_irq_handlers_vupdate_add_id_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_dcn10);
+	adev->vupdate_irq.funcs = NULL;
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dcn10_register_irq_handlers(adev), -EINVAL);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
+/**
+ * dm_test_dcn10_register_irq_handlers_bad_vupdate_source - Test VUPDATE guard
+ * @test: The KUnit test context
+ *
+ * A source ID that DC does not map to a VUPDATE IRQ source must be rejected
+ * rather than used to index the vupdate parameter array.
+ */
+static void dm_test_dcn10_register_irq_handlers_bad_vupdate_source(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_unmapped);
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dcn10_register_irq_handlers(adev), -EINVAL);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
+/**
+ * dm_test_dcn10_register_irq_handlers_hpd_add_id_fails - Test HPD add-id failure
+ * @test: The KUnit test context
+ *
+ * The VUPDATE loop succeeds, then the HPD source with no funcs table is
+ * rejected by amdgpu_irq_add_id().
+ */
+static void dm_test_dcn10_register_irq_handlers_hpd_add_id_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+
+	adev = dm_test_setup_irq_regs(test, &dm_test_irq_service_funcs_dcn10);
+	adev->hpd_irq.funcs = NULL;
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_dcn10_register_irq_handlers(adev), -EINVAL);
+
+	amdgpu_dm_irq_fini(adev);
+}
+
 /**
  * dm_test_register_outbox_irq_handlers_without_dmub - Test outbox registration without DMUB
  * @test: The KUnit test context
@@ -4532,6 +4587,25 @@ static void dm_test_register_outbox_irq_handlers_without_dmub(struct kunit *test
 		&adev->dmub_outbox_irq);
 }
 
+/**
+ * dm_test_register_outbox_irq_handlers_add_id_fails - Test outbox add-id failure
+ * @test: The KUnit test context
+ *
+ * Registering the outbox interrupt before amdgpu_dm_set_irq_funcs() has run
+ * leaves the source without a funcs table, so amdgpu_irq_add_id() rejects it.
+ */
+static void dm_test_register_outbox_irq_handlers_add_id_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct dc *dc;
+
+	adev = dm_kunit_alloc_adev(test);
+	dc = dm_kunit_alloc_dc_with_ctx(test);
+	adev->dm.dc = dc;
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_register_outbox_irq_handlers(adev), -EINVAL);
+}
+
 /**
  * dm_test_register_outbox_irq_handlers_with_dmub - Test outbox registration with DMUB
  * @test: The KUnit test context
@@ -4871,7 +4945,11 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
 	KUNIT_CASE(dm_test_dce110_register_irq_handlers_hpd_add_id_fails),
 	KUNIT_CASE(dm_test_dcn10_register_irq_handlers_zero_crtc),
 	KUNIT_CASE(dm_test_dcn10_register_irq_handlers_one_crtc),
+	KUNIT_CASE(dm_test_dcn10_register_irq_handlers_vupdate_add_id_fails),
+	KUNIT_CASE(dm_test_dcn10_register_irq_handlers_bad_vupdate_source),
+	KUNIT_CASE(dm_test_dcn10_register_irq_handlers_hpd_add_id_fails),
 	KUNIT_CASE(dm_test_register_outbox_irq_handlers_without_dmub),
+	KUNIT_CASE(dm_test_register_outbox_irq_handlers_add_id_fails),
 	KUNIT_CASE(dm_test_register_outbox_irq_handlers_with_dmub),
 	/* amdgpu_dm_irq_handler */
 	KUNIT_CASE(dm_test_irq_handler_dispatches_work),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 70/82] drm/amd/display: Fix mismatch number of OPP/DPP accounting
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (68 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 69/82] drm/amd/display: Test amdgpu_dm_dcn10_register_irq_handlers IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 71/82] drm/amd/display: Cover amdgpu_dm_prune_primary_tile_modes IVAN.LIPSKI
                   ` (12 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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, Dillon Varone, Austin Zheng

From: Dillon Varone <Dillon.Varone@amd.com>

[WHY&HOW]
For cases where number of OPP/DPP mismatch, like 4 planes, 1 stream on
a 3 OPP ASIC, mode support would previously fail citing lack of OPP's.
This is incorrect, as the configuration only needs 1 OPP but 4 DPP's.

Reviewed-by: Austin Zheng <austin.zheng@amd.com>
Signed-off-by: Dillon Varone <Dillon.Varone@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../dml21/inc/bounding_boxes/dcn42b_soc_bb.h  | 10 +++----
 .../src/dml2_core/dml2_core_dcn4_calcs.c      | 30 ++++++++++---------
 .../dml2_core_dcn5_funcs_initialize.c         |  2 +-
 .../dml2_core_dcn5_funcs_mode_support.c       | 23 ++++++++++++--
 .../dml2_core_dcn6_funcs_initialize.c         |  2 ++
 .../dml2_core_dcn6_funcs_mode_support.c       | 24 +++++++++++++--
 6 files changed, 67 insertions(+), 24 deletions(-)

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 33eb583e3365e..0f8666788bbb8 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
@@ -57,7 +57,6 @@ static const struct dml2_soc_qos_parameters dml_dcn42b_variant_a_soc_qos_params
 	},
 	.qos_params = {
 		.dcn32x = {
-			.loaded_round_trip_latency_fclk_cycles = 106,
 			.urgent_latency_us = {
 				.base_latency_us = 4,
 				.base_latency_pixel_vm_us = 4,
@@ -65,8 +64,9 @@ static const struct dml2_soc_qos_parameters dml_dcn42b_variant_a_soc_qos_params
 				.scaling_factor_fclk_us = 0,
 				.scaling_factor_mhz = 0,
 			},
-			.urgent_out_of_order_return_per_channel_pixel_and_vm_bytes = 4096,
+			.loaded_round_trip_latency_fclk_cycles = 106,
 			.urgent_out_of_order_return_per_channel_pixel_only_bytes = 4096,
+			.urgent_out_of_order_return_per_channel_pixel_and_vm_bytes = 4096,
 			.urgent_out_of_order_return_per_channel_vm_only_bytes = 4096,
 		},
 	},
@@ -167,7 +167,6 @@ static const struct dml2_soc_bb dml2_socbb_dcn42b = {
 		},
 		.qos_params = {
 			.dcn32x = {
-				.loaded_round_trip_latency_fclk_cycles = 106,
 				.urgent_latency_us = {
 					.base_latency_us = 4,
 					.base_latency_pixel_vm_us = 4,
@@ -175,8 +174,9 @@ static const struct dml2_soc_bb dml2_socbb_dcn42b = {
 					.scaling_factor_fclk_us = 0,
 					.scaling_factor_mhz = 0,
 				},
-				.urgent_out_of_order_return_per_channel_pixel_and_vm_bytes = 4096,
+				.loaded_round_trip_latency_fclk_cycles = 106,
 				.urgent_out_of_order_return_per_channel_pixel_only_bytes = 4096,
+				.urgent_out_of_order_return_per_channel_pixel_and_vm_bytes = 4096,
 				.urgent_out_of_order_return_per_channel_vm_only_bytes = 4096,
 			},
 		},
@@ -208,8 +208,8 @@ static const struct dml2_soc_bb dml2_socbb_dcn42b = {
 	.return_bus_width_bytes = 64,
 	.hostvm_min_page_size_kbytes = 4,
 	.gpuvm_min_page_size_kbytes = 4,
-	.gpuvm_max_page_table_levels = 1,
 	.hostvm_max_non_cached_page_table_levels = 2,
+	.gpuvm_max_page_table_levels = 1,
 	.phy_downspread_percent = 0.38,
 	.dcn_downspread_percent = 0.38,
 	.dispclk_dppclk_vco_speed_mhz = 3000,
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c
index 9a205b0e15989..608bbb7287cb0 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c
@@ -4116,9 +4116,7 @@ static bool ValidateODMMode(enum dml2_odm_mode ODMMode,
 	bool UseDSC,
 	unsigned int NumberOfDSCSlices,
 	unsigned int TotalNumberOfActiveDPP,
-	unsigned int TotalNumberOfActiveOPP,
 	unsigned int MaxNumDPP,
-	unsigned int MaxNumOPP,
 	double DISPCLKRequired,
 	unsigned int NumberOfDPPRequired,
 	unsigned int MaxHActiveForDSC,
@@ -4134,7 +4132,7 @@ static bool ValidateODMMode(enum dml2_odm_mode ODMMode,
 
 	if (DISPCLKRequired > MaxDispclk)
 		return false;
-	if ((TotalNumberOfActiveDPP + NumberOfDPPRequired) > MaxNumDPP || (TotalNumberOfActiveOPP + NumberOfDPPRequired) > MaxNumOPP)
+	if ((TotalNumberOfActiveDPP + NumberOfDPPRequired) > MaxNumDPP)
 		return false;
 	if (are_odm_segments_symmetrical) {
 		if (HActive % (NumberOfDPPRequired * pixels_per_clock_cycle))
@@ -4180,9 +4178,7 @@ static noinline_for_stack void CalculateODMMode(
 	double MaxDispclk,
 	bool DSCEnable,
 	unsigned int TotalNumberOfActiveDPP,
-	unsigned int TotalNumberOfActiveOPP,
 	unsigned int MaxNumDPP,
-	unsigned int MaxNumOPP,
 	double PixelClock,
 	unsigned int NumberOfDSCSlices,
 
@@ -4252,9 +4248,7 @@ static noinline_for_stack void CalculateODMMode(
 			UseDSC,
 			NumberOfDSCSlices,
 			TotalNumberOfActiveDPP,
-			TotalNumberOfActiveOPP,
 			MaxNumDPP,
-			MaxNumOPP,
 			DISPCLKRequired,
 			NumberOfDPPRequired,
 			MaxHActiveForDSC,
@@ -8453,7 +8447,6 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out
 	CalculateSwathAndDETConfiguration(&mode_lib->scratch, CalculateSwathAndDETConfiguration_params);
 
 	mode_lib->ms.TotalNumberOfActiveDPP = 0;
-	mode_lib->ms.TotalNumberOfActiveOPP = 0;
 	mode_lib->ms.support.TotalAvailablePipesSupport = true;
 
 	for (k = 0; k < mode_lib->ms.num_active_planes; ++k) {
@@ -8489,9 +8482,7 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out
 			mode_lib->ms.max_dispclk_freq_mhz,
 			false, // DSCEnable
 			mode_lib->ms.TotalNumberOfActiveDPP,
-			mode_lib->ms.TotalNumberOfActiveOPP,
 			mode_lib->ip.max_num_dpp,
-			mode_lib->ip.max_num_opp,
 			((double)display_cfg->stream_descriptors[display_cfg->plane_descriptors[k].stream_index].timing.pixel_clock_khz / 1000),
 			mode_lib->ms.support.NumberOfDSCSlices[k],
 
@@ -8510,9 +8501,7 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out
 			mode_lib->ms.max_dispclk_freq_mhz,
 			true, // DSCEnable
 			mode_lib->ms.TotalNumberOfActiveDPP,
-			mode_lib->ms.TotalNumberOfActiveOPP,
 			mode_lib->ip.max_num_dpp,
-			mode_lib->ip.max_num_opp,
 			((double)display_cfg->stream_descriptors[display_cfg->plane_descriptors[k].stream_index].timing.pixel_clock_khz / 1000),
 			mode_lib->ms.support.NumberOfDSCSlices[k],
 
@@ -8650,12 +8639,25 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out
 #endif
 	}
 
+	// TotalNumberOfActiveDPP is the sum of all planes
 	mode_lib->ms.TotalNumberOfActiveDPP = 0;
-	mode_lib->ms.TotalNumberOfActiveOPP = 0;
 	for (k = 0; k < mode_lib->ms.num_active_planes; ++k) {
 		mode_lib->ms.TotalNumberOfActiveDPP += mode_lib->ms.NoOfDPP[k];
-		mode_lib->ms.TotalNumberOfActiveOPP += mode_lib->ms.NoOfOPP[k];
 	}
+
+	// TotalNumberOfActiveOPP is the sum of the per stream max of all planes
+	mode_lib->ms.TotalNumberOfActiveOPP = 0;
+	for (k = 0; k < display_cfg->num_streams; ++k) {
+		unsigned int NoOfOppPerStream = 0;
+		for (m = 0; m < display_cfg->num_planes; ++m) {
+			if (display_cfg->plane_descriptors[m].stream_index == k) {
+				NoOfOppPerStream = NoOfOppPerStream < mode_lib->ms.NoOfOPP[m] ? mode_lib->ms.NoOfOPP[m] : NoOfOppPerStream;
+			}
+		}
+
+		mode_lib->ms.TotalNumberOfActiveOPP += NoOfOppPerStream;
+	}
+
 	if (mode_lib->ms.TotalNumberOfActiveDPP > (unsigned int)mode_lib->ip.max_num_dpp)
 		mode_lib->ms.support.TotalAvailablePipesSupport = false;
 	if (mode_lib->ms.TotalNumberOfActiveOPP > (unsigned int)mode_lib->ip.max_num_opp)
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_initialize.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_initialize.c
index 31681c7f655e9..5907cc155505a 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_initialize.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_initialize.c
@@ -33,7 +33,7 @@ static void patch_ip_caps_with_explicit_ip_params(struct dml2_ip_capabilities *i
 static void patch_ip_params_with_ip_caps(struct dml2_core_ip_params *ip_params, const struct dml2_ip_capabilities *ip_caps)
 {
 	ip_params->max_num_dpp = ip_caps->pipe_count;
-	ip_params->max_num_opp = ip_caps->pipe_count;
+	ip_params->max_num_opp = ip_caps->otg_count;
 	ip_params->max_num_otg = ip_caps->otg_count;
 	ip_params->num_dsc = ip_caps->num_dsc;
 	ip_params->max_num_dp2p0_streams = ip_caps->max_num_dp2p0_streams;
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_mode_support.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_mode_support.c
index 9d68c9dd0a1de..2e86a134e074e 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_mode_support.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_mode_support.c
@@ -795,22 +795,26 @@ static void dcn5_ms_calculate_num_of_dpp_required(
 		const struct dml2_display_cfg *display_cfg,
 		struct dml2_core_internal_display_mode_lib *mode_lib)
 {
-	unsigned int k;
+	unsigned int k, m;
 	const struct dml2_plane_parameters *plane;
 
 	for (k = 0; k < mode_lib->ms.num_active_planes; ++k) {
 		plane = &display_cfg->plane_descriptors[k];
 		mode_lib->ms.MPCCombine[k] = false;
 		mode_lib->ms.NoOfDPP[k] = 1;
+		mode_lib->ms.NoOfOPP[k] = 1;
 		if (mode_lib->ms.ODMMode[k] == dml2_odm_mode_combine_4to1) {
 			mode_lib->ms.MPCCombine[k] = false;
 			mode_lib->ms.NoOfDPP[k] = 4;
+			mode_lib->ms.NoOfOPP[k] = 4;
 		} else if (mode_lib->ms.ODMMode[k] == dml2_odm_mode_combine_3to1) {
 			mode_lib->ms.MPCCombine[k] = false;
 			mode_lib->ms.NoOfDPP[k] = 3;
+			mode_lib->ms.NoOfOPP[k] = 3;
 		} else if (mode_lib->ms.ODMMode[k] == dml2_odm_mode_combine_2to1) {
 			mode_lib->ms.MPCCombine[k] = false;
 			mode_lib->ms.NoOfDPP[k] = 2;
+			mode_lib->ms.NoOfOPP[k] = 2;
 		} else if (plane->overrides.mpcc_combine_factor == 2) {
 			mode_lib->ms.MPCCombine[k] = true;
 			mode_lib->ms.NoOfDPP[k] = 2;
@@ -832,12 +836,27 @@ static void dcn5_ms_calculate_num_of_dpp_required(
 		DML_LOG_VERBOSE("DML::%s: k=%d, NoOfDPP = %d\n", __func__, k,
 				mode_lib->ms.NoOfDPP[k]);
 	}
+
+
+	// TotalNumberOfActiveOPP is the sum of the per stream max NoOfOPP of all planes driving that stream
+	mode_lib->ms.TotalNumberOfActiveOPP = 0;
+	for (k = 0; k < display_cfg->num_streams; ++k) {
+		unsigned int NoOfOppPerStream = 0;
+
+		for (m = 0; m < mode_lib->ms.num_active_planes; ++m) {
+			if (display_cfg->plane_descriptors[m].stream_index == k)
+				NoOfOppPerStream = NoOfOppPerStream < mode_lib->ms.NoOfOPP[m] ? mode_lib->ms.NoOfOPP[m] : NoOfOppPerStream;
+		}
+
+		mode_lib->ms.TotalNumberOfActiveOPP += NoOfOppPerStream;
+	}
 }
 
 static bool dcn5_ms_check_total_available_pipes_support(
 		struct dml2_core_internal_display_mode_lib *mode_lib)
 {
-	return mode_lib->ms.TotalNumberOfActiveDPP <= (unsigned int) mode_lib->ip.max_num_dpp;
+	return mode_lib->ms.TotalNumberOfActiveDPP <= (unsigned int) mode_lib->ip.max_num_dpp
+			&& mode_lib->ms.TotalNumberOfActiveOPP <= (unsigned int) mode_lib->ip.max_num_opp;
 }
 
 static void dcn5_ms_calculate_total_num_of_single_dpp_surfaces(
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_initialize.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_initialize.c
index 23fd6edeb0cf2..5abce647da0a1 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_initialize.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_initialize.c
@@ -24,6 +24,7 @@ struct dml2_core_ip_params core_dcn6_ip_caps_base = {
 	.writeback_interface_buffer_size_kbytes = 90,
 	//Number of pipes after DCN Pipe harvesting
 	.max_num_dpp = 4,
+	.max_num_opp = 4,
 	.max_num_otg = 4,
 	.max_num_wb = 1,
 	.zero_size_buffer_entries = 512,
@@ -111,6 +112,7 @@ static void patch_ip_caps_with_explicit_ip_params(struct dml2_ip_capabilities *i
 static void patch_ip_params_with_ip_caps(struct dml2_core_ip_params *ip_params, const struct dml2_ip_capabilities *ip_caps)
 {
 	ip_params->max_num_dpp = ip_caps->pipe_count;
+	ip_params->max_num_opp = ip_caps->otg_count;
 	ip_params->max_num_otg = ip_caps->otg_count;
 	ip_params->TDLUT_33cube_count = ip_caps->TDLUT_33cube_count;
 	ip_params->num_dsc = ip_caps->num_dsc;
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_mode_support.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_mode_support.c
index 01bbf4aacbe3c..ca3b8b82ec04a 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_mode_support.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_funcs_mode_support.c
@@ -988,12 +988,16 @@ static void dcn6_ms_calculate_num_of_dpp_required(
 	for (k = 0; k < display_cfg->num_planes; k++) {
 		plane = &display_cfg->plane_descriptors[k];
 		outputs->NoOfDPP[k] = 1;
+		outputs->NoOfOPP[k] = 1;
 		if (inputs->ODMMode[k] == dml2_odm_mode_combine_4to1) {
 			outputs->NoOfDPP[k] = 4;
+			outputs->NoOfOPP[k] = 4;
 		} else if (inputs->ODMMode[k] == dml2_odm_mode_combine_3to1) {
 			outputs->NoOfDPP[k] = 3;
+			outputs->NoOfOPP[k] = 3;
 		} else if (inputs->ODMMode[k] == dml2_odm_mode_combine_2to1) {
 			outputs->NoOfDPP[k] = 2;
+			outputs->NoOfOPP[k] = 2;
 		} else if (plane->overrides.mpcc_combine_factor == 2) {
 			outputs->MPCCombine[k] = true;
 			outputs->NoOfDPP[k] = 2;
@@ -1027,12 +1031,28 @@ static bool dcn6_ms_check_total_available_pipes_support(
 	struct dml2_core_internal_mode_support *inputs = states;
 	struct dml2_core_internal_mode_support *outputs = states;
 	unsigned int totalNumOfActiveDPP = 0;
-	unsigned int k;
+	unsigned int k, m;
 
 	DML_LOG_FUNC_ENTER();
 	for (k = 0; k < display_cfg->num_planes; k++)
 		totalNumOfActiveDPP += inputs->NoOfDPP[k];
-	outputs->support.TotalAvailablePipesSupport = totalNumOfActiveDPP <= (unsigned int)ip->max_num_dpp;
+
+
+	// TotalNumberOfActiveOPP is the sum of the per stream max NoOfOPP of all planes driving that stream
+	outputs->TotalNumberOfActiveOPP = 0;
+	for (k = 0; k < display_cfg->num_streams; k++) {
+		unsigned int NoOfOppPerStream = 0;
+
+		for (m = 0; m < display_cfg->num_planes; m++) {
+			if (display_cfg->plane_descriptors[m].stream_index == k)
+				NoOfOppPerStream = NoOfOppPerStream < inputs->NoOfOPP[m] ? inputs->NoOfOPP[m] : NoOfOppPerStream;
+		}
+
+		outputs->TotalNumberOfActiveOPP += NoOfOppPerStream;
+	}
+
+	outputs->support.TotalAvailablePipesSupport = totalNumOfActiveDPP <= (unsigned int)ip->max_num_dpp
+			&& outputs->TotalNumberOfActiveOPP <= (unsigned int)ip->max_num_opp;
 
 	DML_LOG_DEBUG_BOOL(outputs->support.TotalAvailablePipesSupport);
 	DML_LOG_FUNC_EXIT();
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 71/82] drm/amd/display: Cover amdgpu_dm_prune_primary_tile_modes
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (69 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 70/82] drm/amd/display: Fix mismatch number of OPP/DPP accounting IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 72/82] drm/amd/display: Cover add_fs_modes mode generation IVAN.LIPSKI
                   ` (11 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for amdgpu_dm_prune_primary_tile_modes() covering the
no-sink, no panel-patch and non-tiled early returns, a secondary tile,
and the primary-tile path that prunes per-tile modes.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |   3 +-
 .../display/amdgpu_dm/amdgpu_dm_connector.h   |   1 +
 .../tests/amdgpu_dm_connector_test.c          | 172 ++++++++++++++++++
 3 files changed, 175 insertions(+), 1 deletion(-)

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 ddefef1debf25..a473218f4d75c 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
@@ -2853,7 +2853,7 @@ EXPORT_IF_KUNIT(amdgpu_set_panel_orientation);
  * per-tile timing from the primary connector so compositors only pick the full
  * 5K mode.
  */
-static void amdgpu_dm_prune_primary_tile_modes(struct drm_connector *connector)
+STATIC_IFN_KUNIT void amdgpu_dm_prune_primary_tile_modes(struct drm_connector *connector)
 {
 	struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
 	struct drm_display_mode *mode, *t;
@@ -2886,6 +2886,7 @@ static void amdgpu_dm_prune_primary_tile_modes(struct drm_connector *connector)
 		aconnector->num_modes--;
 	}
 }
+EXPORT_IF_KUNIT(amdgpu_dm_prune_primary_tile_modes);
 
 STATIC_IFN_KUNIT void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector,
 					      const struct drm_edid *drm_edid)
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 91b8434ba591d..a86d5c879b646 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
@@ -169,6 +169,7 @@ void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder,
 void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector,
 				       const struct drm_edid *drm_edid);
 int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
+void amdgpu_dm_prune_primary_tile_modes(struct drm_connector *connector);
 uint add_fs_modes(struct amdgpu_dm_connector *aconnector);
 void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector,
 					    const struct drm_edid *drm_edid);
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
index e63202fc7ba9f..e022f93c36cf5 100644
--- 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
@@ -7540,6 +7540,172 @@ static void dm_test_panel_orientation_applies_quirk(struct kunit *test)
 			DRM_MODE_PANEL_ORIENTATION_UNKNOWN);
 }
 
+/* Tests for amdgpu_dm_prune_primary_tile_modes() */
+
+struct dm_test_prune_ctx {
+	struct drm_device *drm;
+	struct amdgpu_dm_connector *aconnector;
+	struct dc_sink *sink;
+};
+
+/*
+ * Build a connector configured as the primary tile of an Apple Studio Display:
+ * a dc_sink requesting the second-tile patch, has_tile set, tile location (0,0)
+ * and a per-tile timing of tile_h_size x tile_v_size. Individual tests relax a
+ * single precondition to exercise the early-return guards.
+ */
+static struct dm_test_prune_ctx *
+dm_test_prune_ctx_alloc(struct kunit *test)
+{
+	struct dm_test_prune_ctx *ctx;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	ctx->drm = dm_test_alloc_drm(test);
+	ctx->aconnector = dm_test_add_connector(test, ctx->drm,
+						DRM_MODE_CONNECTOR_DisplayPort);
+
+	ctx->sink = kunit_kzalloc(test, sizeof(*ctx->sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->sink);
+	ctx->sink->edid_caps.panel_patch.disable_second_tile = true;
+	ctx->aconnector->dc_sink = ctx->sink;
+
+	ctx->aconnector->base.has_tile = true;
+	ctx->aconnector->base.tile_h_size = 2560;
+	ctx->aconnector->base.tile_v_size = 2880;
+
+	return ctx;
+}
+
+static struct drm_display_mode *
+dm_test_prune_add_mode(struct kunit *test, struct drm_connector *connector,
+		       int hdisplay, int vdisplay)
+{
+	struct drm_display_mode *mode = drm_mode_create(connector->dev);
+
+	KUNIT_ASSERT_NOT_NULL(test, mode);
+	mode->hdisplay = hdisplay;
+	mode->vdisplay = vdisplay;
+	list_add_tail(&mode->head, &connector->probed_modes);
+
+	return mode;
+}
+
+static int dm_test_prune_count(struct drm_connector *connector)
+{
+	struct drm_display_mode *mode;
+	int n = 0;
+
+	list_for_each_entry(mode, &connector->probed_modes, head)
+		n++;
+
+	return n;
+}
+
+/**
+ * dm_test_prune_no_sink - Test a connector without a sink is left untouched
+ * @test: The KUnit test context
+ */
+static void dm_test_prune_no_sink(struct kunit *test)
+{
+	struct dm_test_prune_ctx *ctx = dm_test_prune_ctx_alloc(test);
+
+	ctx->aconnector->dc_sink = NULL;
+	dm_test_prune_add_mode(test, &ctx->aconnector->base, 2560, 2880);
+	ctx->aconnector->num_modes = 1;
+
+	amdgpu_dm_prune_primary_tile_modes(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, dm_test_prune_count(&ctx->aconnector->base), 1);
+	KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 1);
+}
+
+/**
+ * dm_test_prune_no_patch - Test the patch flag being unset skips pruning
+ * @test: The KUnit test context
+ */
+static void dm_test_prune_no_patch(struct kunit *test)
+{
+	struct dm_test_prune_ctx *ctx = dm_test_prune_ctx_alloc(test);
+
+	ctx->sink->edid_caps.panel_patch.disable_second_tile = false;
+	dm_test_prune_add_mode(test, &ctx->aconnector->base, 2560, 2880);
+	ctx->aconnector->num_modes = 1;
+
+	amdgpu_dm_prune_primary_tile_modes(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, dm_test_prune_count(&ctx->aconnector->base), 1);
+	KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 1);
+}
+
+/**
+ * dm_test_prune_no_tile - Test a non-tiled connector skips pruning
+ * @test: The KUnit test context
+ */
+static void dm_test_prune_no_tile(struct kunit *test)
+{
+	struct dm_test_prune_ctx *ctx = dm_test_prune_ctx_alloc(test);
+
+	ctx->aconnector->base.has_tile = false;
+	dm_test_prune_add_mode(test, &ctx->aconnector->base, 2560, 2880);
+	ctx->aconnector->num_modes = 1;
+
+	amdgpu_dm_prune_primary_tile_modes(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, dm_test_prune_count(&ctx->aconnector->base), 1);
+	KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 1);
+}
+
+/**
+ * dm_test_prune_secondary_tile - Test a secondary tile is left untouched
+ * @test: The KUnit test context
+ *
+ * Only the primary tile (location 0,0) is pruned; a secondary tile keeps its
+ * per-tile timing.
+ */
+static void dm_test_prune_secondary_tile(struct kunit *test)
+{
+	struct dm_test_prune_ctx *ctx = dm_test_prune_ctx_alloc(test);
+
+	ctx->aconnector->base.tile_h_loc = 1;
+	dm_test_prune_add_mode(test, &ctx->aconnector->base, 2560, 2880);
+	ctx->aconnector->num_modes = 1;
+
+	amdgpu_dm_prune_primary_tile_modes(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, dm_test_prune_count(&ctx->aconnector->base), 1);
+	KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 1);
+}
+
+/**
+ * dm_test_prune_removes_per_tile - Test the per-tile timing is pruned
+ * @test: The KUnit test context
+ *
+ * On the primary tile the per-tile (tile_h_size x tile_v_size) timing is
+ * dropped while the full-resolution mode is kept and num_modes is decremented.
+ */
+static void dm_test_prune_removes_per_tile(struct kunit *test)
+{
+	struct dm_test_prune_ctx *ctx = dm_test_prune_ctx_alloc(test);
+	struct drm_display_mode *mode;
+	bool has_tile_mode = false;
+
+	dm_test_prune_add_mode(test, &ctx->aconnector->base, 2560, 2880);
+	dm_test_prune_add_mode(test, &ctx->aconnector->base, 5120, 2880);
+	ctx->aconnector->num_modes = 2;
+
+	amdgpu_dm_prune_primary_tile_modes(&ctx->aconnector->base);
+
+	KUNIT_EXPECT_EQ(test, dm_test_prune_count(&ctx->aconnector->base), 1);
+	KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 1);
+
+	list_for_each_entry(mode, &ctx->aconnector->base.probed_modes, head)
+		if (mode->hdisplay == 2560 && mode->vdisplay == 2880)
+			has_tile_mode = true;
+	KUNIT_EXPECT_FALSE(test, has_tile_mode);
+}
+
 /* Tests for amdgpu_dm_update_stream_scaling_settings() */
 
 /**
@@ -8015,6 +8181,12 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_panel_orientation_non_edp),
 	KUNIT_CASE(dm_test_panel_orientation_no_native_mode),
 	KUNIT_CASE(dm_test_panel_orientation_applies_quirk),
+	/* amdgpu_dm_prune_primary_tile_modes */
+	KUNIT_CASE(dm_test_prune_no_sink),
+	KUNIT_CASE(dm_test_prune_no_patch),
+	KUNIT_CASE(dm_test_prune_no_tile),
+	KUNIT_CASE(dm_test_prune_secondary_tile),
+	KUNIT_CASE(dm_test_prune_removes_per_tile),
 	/* dm_validate_stream_and_context */
 	KUNIT_CASE(dm_test_validate_stream_null_stream),
 	KUNIT_CASE(dm_test_validate_stream_dc_ok_no_pipe),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 72/82] drm/amd/display: Cover add_fs_modes mode generation
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (70 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 71/82] drm/amd/display: Cover amdgpu_dm_prune_primary_tile_modes IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 73/82] drm/amd/display: Cover add_fs_modes illegal timing skip IVAN.LIPSKI
                   ` (10 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for add_fs_modes() covering successful freesync-video
mode generation and refresh rates that fall outside the supported
range.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 64 +++++++++++++++++++
 1 file changed, 64 insertions(+)

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
index e022f93c36cf5..988f0885679aa 100644
--- 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
@@ -5602,6 +5602,68 @@ static void dm_test_add_fs_modes_no_preferred_mode(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
 }
 
+/*
+ * Build a DisplayPort connector whose highest-refresh mode is a fixed
+ * 1920x1080@60 timing. A non-zero freesync_vid_base.clock makes
+ * amdgpu_dm_get_highest_refresh_rate_mode() return that timing directly,
+ * giving the test full control of the reference mode.
+ */
+static struct amdgpu_dm_connector *dm_test_fs_setup(struct kunit *test)
+{
+	struct drm_device *drm = dm_test_alloc_drm(test);
+	struct amdgpu_dm_connector *aconnector =
+		dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_DisplayPort);
+	struct drm_display_mode *m = &aconnector->freesync_vid_base;
+
+	m->clock = 148500;
+	m->hdisplay = 1920;
+	m->htotal = 2200;
+	m->vdisplay = 1080;
+	m->vsync_start = 1084;
+	m->vsync_end = 1089;
+	m->vtotal = 1125;
+
+	return aconnector;
+}
+
+/**
+ * dm_test_add_fs_modes_generates - Test FreeSync video modes are added
+ * @test: The KUnit test context
+ *
+ * With min/max vfreq spanning the standard rates, add_fs_modes() derives one
+ * mode per legal rate at or below the reference refresh. A second call finds
+ * every generated mode already present, so is_duplicate_mode() rejects them
+ * all and no new modes are added.
+ */
+static void dm_test_add_fs_modes_generates(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector = dm_test_fs_setup(test);
+
+	aconnector->min_vfreq = 20;
+	aconnector->max_vfreq = 60;
+
+	KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 8);
+	KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
+}
+
+/**
+ * dm_test_add_fs_modes_out_of_range - Test no modes when rates fall outside range
+ * @test: The KUnit test context
+ *
+ * A vfreq window above the reference refresh leaves every standard rate either
+ * higher than the mode or outside [min_vfreq, max_vfreq], so add_fs_modes()
+ * skips them all and returns 0.
+ */
+static void dm_test_add_fs_modes_out_of_range(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector = dm_test_fs_setup(test);
+
+	aconnector->min_vfreq = 100;
+	aconnector->max_vfreq = 120;
+
+	KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
+}
+
 /**
  * dm_test_add_freesync_modes_null_edid_noop - Test NULL EDID adds no modes
  * @test: The KUnit test context
@@ -8206,6 +8268,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_ddc_get_modes_null_edid),
 	/* add_fs_modes */
 	KUNIT_CASE(dm_test_add_fs_modes_no_preferred_mode),
+	KUNIT_CASE(dm_test_add_fs_modes_generates),
+	KUNIT_CASE(dm_test_add_fs_modes_out_of_range),
 	/* amdgpu_dm_connector_add_freesync_modes */
 	KUNIT_CASE(dm_test_add_freesync_modes_null_edid_noop),
 	/* amdgpu_dm_i2c_func */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 73/82] drm/amd/display: Cover add_fs_modes illegal timing skip
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (71 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 72/82] drm/amd/display: Cover add_fs_modes mode generation IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 74/82] drm/amd/display: Refactor hdmi_frl_status_polling_work for Kunit testing IVAN.LIPSKI
                   ` (9 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add a KUnit test for add_fs_modes() confirming that a generated mode
with illegal timing is skipped rather than added.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

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
index 988f0885679aa..5e5c372e9e01e 100644
--- 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
@@ -5664,6 +5664,26 @@ static void dm_test_add_fs_modes_out_of_range(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
 }
 
+/**
+ * dm_test_add_fs_modes_skips_illegal - Test illegal derived timings are dropped
+ * @test: The KUnit test context
+ *
+ * A reference vtotal of 1133 gives a true refresh of ~59.58 that rounds up to
+ * 60, so the 60000 rate clears the refresh check yet yields a negative vtotal
+ * delta. The resulting timing is illegal, so add_fs_modes() skips it and
+ * returns 0.
+ */
+static void dm_test_add_fs_modes_skips_illegal(struct kunit *test)
+{
+	struct amdgpu_dm_connector *aconnector = dm_test_fs_setup(test);
+
+	aconnector->freesync_vid_base.vtotal = 1133;
+	aconnector->min_vfreq = 59;
+	aconnector->max_vfreq = 60;
+
+	KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
+}
+
 /**
  * dm_test_add_freesync_modes_null_edid_noop - Test NULL EDID adds no modes
  * @test: The KUnit test context
@@ -8270,6 +8290,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_add_fs_modes_no_preferred_mode),
 	KUNIT_CASE(dm_test_add_fs_modes_generates),
 	KUNIT_CASE(dm_test_add_fs_modes_out_of_range),
+	KUNIT_CASE(dm_test_add_fs_modes_skips_illegal),
 	/* amdgpu_dm_connector_add_freesync_modes */
 	KUNIT_CASE(dm_test_add_freesync_modes_null_edid_noop),
 	/* amdgpu_dm_i2c_func */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 74/82] drm/amd/display: Refactor hdmi_frl_status_polling_work for Kunit testing
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (72 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 73/82] drm/amd/display: Cover add_fs_modes illegal timing skip IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 75/82] drm/amd/display: Cover hdmi_frl_status_polling_work IVAN.LIPSKI
                   ` (8 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Ivan Lipski <ivan.lipski@amd.com>

[Why&How]
The KUnit connector test references hdmi_frl_status_polling_work(), but the
function was static with no visible prototype, causing a build failure
(undeclared / implicit declaration). Make the function non-static and add
a prototype to amdgpu_dm_connector.h so the test can reference it. The
INIT_DELAYED_WORK setup remains in amdgpu_dm_connector_init_helper(); no
runtime behavior change.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 2 +-
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

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 a473218f4d75c..13f9ed0220c37 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
@@ -3087,7 +3087,7 @@ static const u32 supported_colorspaces =
 	BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) |
 	BIT(DRM_MODE_COLORIMETRY_BT2020_YCC);
 
-static void hdmi_frl_status_polling_work(struct work_struct *work)
+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,
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 a86d5c879b646..6732e5e644cda 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
@@ -71,6 +71,8 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
 				     struct dc_link *link,
 				     int link_index);
 
+void hdmi_frl_status_polling_work(struct work_struct *work);
+
 enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector,
 						    const struct drm_display_mode *mode);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 75/82] drm/amd/display: Cover hdmi_frl_status_polling_work
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (73 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 74/82] drm/amd/display: Refactor hdmi_frl_status_polling_work for Kunit testing IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 76/82] drm/amd/display: Cover amdgpu_dm_i2c_xfer IVAN.LIPSKI
                   ` (7 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for hdmi_frl_status_polling_work() covering the
no-links, no-local-sink, non-HDMI and zero-FRL-rate skip cases, plus
the poll paths that leave the link unchanged or trigger a retrain.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |   1 +
 .../tests/amdgpu_dm_connector_test.c          | 207 ++++++++++++++++++
 2 files changed, 208 insertions(+)

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 13f9ed0220c37..837ab9cfd35f6 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
@@ -3121,6 +3121,7 @@ void hdmi_frl_status_polling_work(struct work_struct *work)
 			   &dm->hdmi_frl_status_polling_work,
 			   msecs_to_jiffies(dm->hdmi_frl_status_polling_delay_ms));
 }
+EXPORT_IF_KUNIT(hdmi_frl_status_polling_work);
 
 void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
 				     struct amdgpu_dm_connector *aconnector,
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
index 5e5c372e9e01e..24f55432e3262 100644
--- 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
@@ -7988,6 +7988,206 @@ static void dm_test_update_scaling_underscan(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, stream->dst.height, 1048);
 }
 
+/* Tests for hdmi_frl_status_polling_work() */
+
+static int dm_test_frl_poll_calls;
+static int dm_test_frl_detect_calls;
+
+static bool dm_test_frl_poll_true(struct dc_link *link)
+{
+	dm_test_frl_poll_calls++;
+	return true;
+}
+
+static bool dm_test_frl_poll_false(struct dc_link *link)
+{
+	dm_test_frl_poll_calls++;
+	return false;
+}
+
+static bool dm_test_frl_detect(struct dc_link *link, enum dc_detect_reason reason)
+{
+	dm_test_frl_detect_calls++;
+	return true;
+}
+
+struct dm_test_frl_ctx {
+	struct amdgpu_display_manager *dm;
+	struct dc *dc;
+	struct link_service *link_srv;
+};
+
+/*
+ * Build a display manager whose polling work re-arms on a real workqueue. The
+ * dc starts with an empty link array and link_srv hooks that count poll and
+ * detect calls, so a test can assert exactly how far a link progresses. A large
+ * re-arm delay keeps the requeued work dormant until the test cancels it.
+ */
+static struct dm_test_frl_ctx *dm_test_frl_ctx_alloc(struct kunit *test)
+{
+	struct dm_test_frl_ctx *ctx;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	ctx->dm = kunit_kzalloc(test, sizeof(*ctx->dm), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->dm);
+	ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->dc);
+	ctx->link_srv = kunit_kzalloc(test, sizeof(*ctx->link_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->link_srv);
+
+	ctx->link_srv->hdmi_frl_poll_status_flag = dm_test_frl_poll_true;
+	ctx->link_srv->detect_link = dm_test_frl_detect;
+	ctx->dc->link_srv = ctx->link_srv;
+	ctx->dm->dc = ctx->dc;
+	mutex_init(&ctx->dm->dc_lock);
+	INIT_DELAYED_WORK(&ctx->dm->hdmi_frl_status_polling_work, hdmi_frl_status_polling_work);
+	ctx->dm->hdmi_frl_status_polling_delay_ms = 100000;
+	ctx->dm->hdmi_frl_status_polling_wq = system_wq;
+
+	dm_test_frl_poll_calls = 0;
+	dm_test_frl_detect_calls = 0;
+
+	return ctx;
+}
+
+/* Allocate a dc_link, wire its dc back-pointer and register it at index 0. */
+static struct dc_link *dm_test_frl_add_link(struct kunit *test, struct dm_test_frl_ctx *ctx)
+{
+	struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+
+	KUNIT_ASSERT_NOT_NULL(test, link);
+	link->dc = ctx->dc;
+	ctx->dc->links[0] = link;
+
+	return link;
+}
+
+/* Register a link that satisfies every guard up to the poll status check. */
+static struct dc_link *dm_test_frl_add_hdmi_link(struct kunit *test, struct dm_test_frl_ctx *ctx)
+{
+	struct dc_link *link = dm_test_frl_add_link(test, ctx);
+
+	link->local_sink = kunit_kzalloc(test, sizeof(*link->local_sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link->local_sink);
+	link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+	link->frl_link_settings.frl_link_rate = HDMI_FRL_LINK_RATE_3GBPS;
+
+	return link;
+}
+
+/* Run the work once directly, then cancel and tear down the re-armed work. */
+static void dm_test_frl_run(struct dm_test_frl_ctx *ctx)
+{
+	hdmi_frl_status_polling_work(&ctx->dm->hdmi_frl_status_polling_work.work);
+	cancel_delayed_work_sync(&ctx->dm->hdmi_frl_status_polling_work);
+}
+
+/**
+ * dm_test_frl_no_links - Test an empty link array is a no-op
+ * @test: The KUnit test context
+ *
+ * With no links registered the loop body never runs, so neither the poll nor
+ * the detect hook is invoked.
+ */
+static void dm_test_frl_no_links(struct kunit *test)
+{
+	struct dm_test_frl_ctx *ctx = dm_test_frl_ctx_alloc(test);
+
+	dm_test_frl_run(ctx);
+
+	KUNIT_EXPECT_EQ(test, dm_test_frl_poll_calls, 0);
+	KUNIT_EXPECT_EQ(test, dm_test_frl_detect_calls, 0);
+}
+
+/**
+ * dm_test_frl_skips_no_local_sink - Test a link without a local sink is skipped
+ * @test: The KUnit test context
+ */
+static void dm_test_frl_skips_no_local_sink(struct kunit *test)
+{
+	struct dm_test_frl_ctx *ctx = dm_test_frl_ctx_alloc(test);
+
+	dm_test_frl_add_link(test, ctx);
+
+	dm_test_frl_run(ctx);
+
+	KUNIT_EXPECT_EQ(test, dm_test_frl_poll_calls, 0);
+}
+
+/**
+ * dm_test_frl_skips_non_hdmi - Test a non-HDMI link is skipped
+ * @test: The KUnit test context
+ */
+static void dm_test_frl_skips_non_hdmi(struct kunit *test)
+{
+	struct dm_test_frl_ctx *ctx = dm_test_frl_ctx_alloc(test);
+	struct dc_link *link = dm_test_frl_add_link(test, ctx);
+
+	link->local_sink = kunit_kzalloc(test, sizeof(*link->local_sink), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link->local_sink);
+	link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+
+	dm_test_frl_run(ctx);
+
+	KUNIT_EXPECT_EQ(test, dm_test_frl_poll_calls, 0);
+}
+
+/**
+ * dm_test_frl_skips_zero_rate - Test a link with no FRL rate is skipped
+ * @test: The KUnit test context
+ */
+static void dm_test_frl_skips_zero_rate(struct kunit *test)
+{
+	struct dm_test_frl_ctx *ctx = dm_test_frl_ctx_alloc(test);
+	struct dc_link *link = dm_test_frl_add_hdmi_link(test, ctx);
+
+	link->frl_link_settings.frl_link_rate = 0;
+
+	dm_test_frl_run(ctx);
+
+	KUNIT_EXPECT_EQ(test, dm_test_frl_poll_calls, 0);
+}
+
+/**
+ * dm_test_frl_poll_no_update - Test a clear poll status skips retraining
+ * @test: The KUnit test context
+ *
+ * The poll hook is reached but reports no change, so dc_link_detect() is not
+ * called.
+ */
+static void dm_test_frl_poll_no_update(struct kunit *test)
+{
+	struct dm_test_frl_ctx *ctx = dm_test_frl_ctx_alloc(test);
+
+	ctx->link_srv->hdmi_frl_poll_status_flag = dm_test_frl_poll_false;
+	dm_test_frl_add_hdmi_link(test, ctx);
+
+	dm_test_frl_run(ctx);
+
+	KUNIT_EXPECT_EQ(test, dm_test_frl_poll_calls, 1);
+	KUNIT_EXPECT_EQ(test, dm_test_frl_detect_calls, 0);
+}
+
+/**
+ * dm_test_frl_poll_retrains - Test a set poll status triggers a retrain
+ * @test: The KUnit test context
+ *
+ * A poll status change drives dc_link_detect() with DETECT_REASON_RETRAIN.
+ */
+static void dm_test_frl_poll_retrains(struct kunit *test)
+{
+	struct dm_test_frl_ctx *ctx = dm_test_frl_ctx_alloc(test);
+
+	dm_test_frl_add_hdmi_link(test, ctx);
+
+	dm_test_frl_run(ctx);
+
+	KUNIT_EXPECT_EQ(test, dm_test_frl_poll_calls, 1);
+	KUNIT_EXPECT_EQ(test, dm_test_frl_detect_calls, 1);
+}
+
 static struct kunit_case amdgpu_dm_connector_tests[] = {
 	/* get_subconnector_type */
 	KUNIT_CASE(dm_test_subconnector_type_none),
@@ -8348,6 +8548,13 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_update_scaling_rmx_aspect_letterbox),
 	KUNIT_CASE(dm_test_update_scaling_rmx_center),
 	KUNIT_CASE(dm_test_update_scaling_underscan),
+	/* hdmi_frl_status_polling_work */
+	KUNIT_CASE(dm_test_frl_no_links),
+	KUNIT_CASE(dm_test_frl_skips_no_local_sink),
+	KUNIT_CASE(dm_test_frl_skips_non_hdmi),
+	KUNIT_CASE(dm_test_frl_skips_zero_rate),
+	KUNIT_CASE(dm_test_frl_poll_no_update),
+	KUNIT_CASE(dm_test_frl_poll_retrains),
 	{}
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 76/82] drm/amd/display: Cover amdgpu_dm_i2c_xfer
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (74 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 75/82] drm/amd/display: Cover hdmi_frl_status_polling_work IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 77/82] drm/amd/display: Cover amdgpu_dm_create_i2c IVAN.LIPSKI
                   ` (6 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for amdgpu_dm_i2c_xfer() covering a failed hardware
command submission and the OEM i2c path with no device present.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../tests/amdgpu_dm_connector_test.c          | 97 +++++++++++++++++++
 1 file changed, 97 insertions(+)

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
index 24f55432e3262..39b7dda52fb0b 100644
--- 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
@@ -5746,6 +5746,101 @@ static void dm_test_i2c_xfer_no_ddc_pin(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_xfer(&i2c->base, NULL, 0), -EIO);
 }
 
+/* Fake i2c adapter chain used by the amdgpu_dm_i2c_xfer() tests. */
+struct dm_test_i2c_ctx {
+	struct amdgpu_i2c_adapter *i2c;
+	struct ddc_service *ddc;
+	struct dc_context *dc_ctx;
+	struct dc *dc;
+	struct dc_link *link;
+};
+
+/*
+ * Build an i2c adapter whose ddc_service has a ddc_pin (so the transfer is not
+ * rejected up front) and whose dc back-pointer resolves to a link at index 0.
+ * The link ddc has no ddc_pin, so the non-OEM dc_submit_i2c() path bails out
+ * and reports failure without touching real hardware.
+ */
+static struct dm_test_i2c_ctx *dm_test_i2c_setup(struct kunit *test)
+{
+	struct dm_test_i2c_ctx *ctx;
+	struct ddc_service *link_ddc;
+	struct ddc *pin;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+	ctx->i2c = kunit_kzalloc(test, sizeof(*ctx->i2c), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->i2c);
+	ctx->ddc = kunit_kzalloc(test, sizeof(*ctx->ddc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->ddc);
+	ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx);
+	ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->dc);
+	ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->link);
+	pin = kunit_kzalloc(test, sizeof(*pin), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, pin);
+	link_ddc = kunit_kzalloc(test, sizeof(*link_ddc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, link_ddc);
+
+	ctx->ddc->ddc_pin = pin;
+	ctx->ddc->ctx = ctx->dc_ctx;
+	ctx->ddc->link = ctx->link;
+	ctx->dc_ctx->dc = ctx->dc;
+	ctx->link->link_index = 0;
+	ctx->link->ddc = link_ddc;
+	ctx->dc->links[0] = ctx->link;
+	ctx->i2c->ddc_service = ctx->ddc;
+	i2c_set_adapdata(&ctx->i2c->base, ctx->i2c);
+
+	return ctx;
+}
+
+/**
+ * dm_test_i2c_xfer_hw_submit_fails - Test the non-OEM path reports a failed submit
+ * @test: The KUnit test context
+ *
+ * With a ddc_pin present the transfer builds the payload list and dispatches
+ * through dc_submit_i2c(); the target link has no ddc_pin, so the submit fails
+ * and the transfer returns -EIO instead of the message count.
+ */
+static void dm_test_i2c_xfer_hw_submit_fails(struct kunit *test)
+{
+	struct dm_test_i2c_ctx *ctx = dm_test_i2c_setup(test);
+	u8 buf[2];
+	struct i2c_msg msgs[] = {
+		{ .addr = 0x50, .flags = I2C_M_RD, .len = sizeof(buf), .buf = buf },
+	};
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_xfer(&ctx->i2c->base, msgs, 1), -EIO);
+}
+
+/**
+ * dm_test_i2c_xfer_oem_no_device - Test the OEM path with no OEM device fails
+ * @test: The KUnit test context
+ *
+ * When the adapter is flagged as OEM the transfer routes through
+ * dc_submit_i2c_oem(); with no oem_device on the resource pool the submit
+ * fails and the transfer returns -EIO.
+ */
+static void dm_test_i2c_xfer_oem_no_device(struct kunit *test)
+{
+	struct dm_test_i2c_ctx *ctx = dm_test_i2c_setup(test);
+	struct resource_pool *pool;
+	u8 buf[2] = { 0x12, 0x34 };
+	struct i2c_msg msgs[] = {
+		{ .addr = 0x50, .flags = 0, .len = sizeof(buf), .buf = buf },
+	};
+
+	pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, pool);
+	ctx->dc->res_pool = pool;
+	ctx->i2c->oem = true;
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_xfer(&ctx->i2c->base, msgs, 1), -EIO);
+}
+
 /**
  * dm_test_get_amd_vsdb_unsupported - Test a zero VSDB version reports no support
  * @test: The KUnit test context
@@ -8497,6 +8592,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_i2c_func_returns_flags),
 	/* amdgpu_dm_i2c_xfer */
 	KUNIT_CASE(dm_test_i2c_xfer_no_ddc_pin),
+	KUNIT_CASE(dm_test_i2c_xfer_hw_submit_fails),
+	KUNIT_CASE(dm_test_i2c_xfer_oem_no_device),
 	/* get_amd_vsdb */
 	KUNIT_CASE(dm_test_get_amd_vsdb_unsupported),
 	KUNIT_CASE(dm_test_get_amd_vsdb_supported),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 77/82] drm/amd/display: Cover amdgpu_dm_create_i2c
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (75 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 76/82] drm/amd/display: Cover amdgpu_dm_i2c_xfer IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 78/82] drm/amd/display: Add passthrough visual confirm IVAN.LIPSKI
                   ` (5 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>

Add KUnit tests for amdgpu_dm_create_i2c() covering the OEM i2c adapter
and the hardware i2c bus creation paths.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |  1 +
 .../tests/amdgpu_dm_connector_test.c          | 91 +++++++++++++++++++
 2 files changed, 92 insertions(+)

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 837ab9cfd35f6..8a35e4df13cbf 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
@@ -3344,6 +3344,7 @@ amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem)
 
 	return i2c;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_create_i2c);
 
 int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector)
 {
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
index 39b7dda52fb0b..76334fb840d64 100644
--- 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
@@ -5841,6 +5841,94 @@ static void dm_test_i2c_xfer_oem_no_device(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_xfer(&ctx->i2c->base, msgs, 1), -EIO);
 }
 
+/* Fake ddc_service chain for amdgpu_dm_create_i2c() tests. */
+struct dm_test_create_i2c_ctx {
+	struct ddc_service *ddc;
+	struct amdgpu_device *adev;
+	struct pci_dev *pdev;
+	struct dc_link *link;
+};
+
+/*
+ * Build a ddc_service whose dc_context driver_context is an amdgpu_device with
+ * a pci_dev (for the parent device), plus a link used by the hardware-bus name.
+ */
+static struct dm_test_create_i2c_ctx *dm_test_create_i2c_setup(struct kunit *test)
+{
+	struct dm_test_create_i2c_ctx *ctx;
+	struct dc_context *dc_ctx;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+	ctx->ddc = kunit_kzalloc(test, sizeof(*ctx->ddc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->ddc);
+	ctx->adev = kunit_kzalloc(test, sizeof(*ctx->adev), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->adev);
+	ctx->pdev = kunit_kzalloc(test, sizeof(*ctx->pdev), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->pdev);
+	ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->link);
+	dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dc_ctx);
+
+	ctx->adev->pdev = ctx->pdev;
+	dc_ctx->driver_context = ctx->adev;
+	ctx->ddc->ctx = dc_ctx;
+	ctx->ddc->link = ctx->link;
+
+	return ctx;
+}
+
+/**
+ * dm_test_create_i2c_oem - Test the OEM adapter name and wiring
+ * @test: The KUnit test context
+ *
+ * The OEM adapter is named for the OEM bus and is wired to the dm i2c
+ * algorithm, the backing ddc_service, and the pci parent device.
+ */
+static void dm_test_create_i2c_oem(struct kunit *test)
+{
+	struct dm_test_create_i2c_ctx *ctx = dm_test_create_i2c_setup(test);
+	struct amdgpu_i2c_adapter *i2c;
+
+	i2c = amdgpu_dm_create_i2c(ctx->ddc, true);
+	KUNIT_ASSERT_NOT_NULL(test, i2c);
+
+	KUNIT_EXPECT_TRUE(test, i2c->oem);
+	KUNIT_EXPECT_PTR_EQ(test, i2c->ddc_service, ctx->ddc);
+	KUNIT_EXPECT_PTR_EQ(test, i2c_get_adapdata(&i2c->base), i2c);
+	KUNIT_EXPECT_PTR_EQ(test, i2c->base.dev.parent, &ctx->adev->pdev->dev);
+	KUNIT_EXPECT_TRUE(test, i2c->base.algo->master_xfer == amdgpu_dm_i2c_xfer);
+	KUNIT_EXPECT_TRUE(test, i2c->base.algo->functionality == amdgpu_dm_i2c_func);
+	KUNIT_EXPECT_STREQ(test, i2c->base.name, "AMDGPU DM i2c OEM bus");
+
+	kfree(i2c);
+}
+
+/**
+ * dm_test_create_i2c_hw_bus - Test the hardware bus adapter name carries the index
+ * @test: The KUnit test context
+ *
+ * The non-OEM adapter is named for the hardware bus and embeds the link index,
+ * and the OEM flag is left clear.
+ */
+static void dm_test_create_i2c_hw_bus(struct kunit *test)
+{
+	struct dm_test_create_i2c_ctx *ctx = dm_test_create_i2c_setup(test);
+	struct amdgpu_i2c_adapter *i2c;
+
+	ctx->link->link_index = 7;
+
+	i2c = amdgpu_dm_create_i2c(ctx->ddc, false);
+	KUNIT_ASSERT_NOT_NULL(test, i2c);
+
+	KUNIT_EXPECT_FALSE(test, i2c->oem);
+	KUNIT_EXPECT_PTR_EQ(test, i2c->ddc_service, ctx->ddc);
+	KUNIT_EXPECT_STREQ(test, i2c->base.name, "AMDGPU DM i2c hw bus 7");
+
+	kfree(i2c);
+}
+
 /**
  * dm_test_get_amd_vsdb_unsupported - Test a zero VSDB version reports no support
  * @test: The KUnit test context
@@ -8594,6 +8682,9 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
 	KUNIT_CASE(dm_test_i2c_xfer_no_ddc_pin),
 	KUNIT_CASE(dm_test_i2c_xfer_hw_submit_fails),
 	KUNIT_CASE(dm_test_i2c_xfer_oem_no_device),
+	/* amdgpu_dm_create_i2c */
+	KUNIT_CASE(dm_test_create_i2c_oem),
+	KUNIT_CASE(dm_test_create_i2c_hw_bus),
 	/* get_amd_vsdb */
 	KUNIT_CASE(dm_test_get_amd_vsdb_unsupported),
 	KUNIT_CASE(dm_test_get_amd_vsdb_supported),
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 78/82] drm/amd/display: Add passthrough visual confirm
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (76 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 77/82] drm/amd/display: Cover amdgpu_dm_create_i2c IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 79/82] drm/amd/display: Populate vblank_nom according to bounding box IVAN.LIPSKI
                   ` (4 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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, Gaghik Khachatrian,
	Joshua Aberback

From: Gaghik Khachatrian <gaghik.khachatrian@amd.com>

- Added a new visual_confirm mode VISUAL_CONFIRM_DM_PASSTHROUGH so the confirm
  color is supplied by the DM instead of computed by DC.
- Relocated struct tg_color above dc_flip_addrs so it can be embedded by value.

Reviewed-by: Joshua Aberback <joshua.aberback@amd.com>
Signed-off-by: Gaghik Khachatrian <gaghik.khachatrian@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 drivers/gpu/drm/amd/display/dc/core/dc.c     |  8 +++++++-
 drivers/gpu/drm/amd/display/dc/dc.h          |  1 +
 drivers/gpu/drm/amd/display/dc/dc_hw_types.h | 18 +++++++++---------
 3 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c
index 3c6936943960d..1b9bcc3f0d2be 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc.c
@@ -1229,7 +1229,9 @@ static void apply_ctx_interdependent_lock(struct dc *dc,
 
 static void dc_update_visual_confirm_color(struct dc *dc, struct dc_state *context, struct pipe_ctx *pipe_ctx)
 {
-	if (dc->debug.visual_confirm & VISUAL_CONFIRM_EXPLICIT) {
+	/* EXPLICIT and DM passthrough both apply the per-plane color set on the plane state */
+	if ((dc->debug.visual_confirm & VISUAL_CONFIRM_EXPLICIT) ||
+		dc->debug.visual_confirm == VISUAL_CONFIRM_DM_PASSTHROUGH) {
 		memcpy(&pipe_ctx->visual_confirm_color, &pipe_ctx->plane_state->visual_confirm_color,
 		sizeof(pipe_ctx->visual_confirm_color));
 		return;
@@ -3352,6 +3354,10 @@ static void copy_surface_update_to_plane(
 			surface->time.index = 0;
 
 		surface->triplebuffer_flips = srf_update->flip_addr->triplebuffer_flips;
+
+		/* DM passthrough mode: stash the per-flip color on the plane for dc_update_visual_confirm_color */
+		if (surface->ctx->dc->debug.visual_confirm == VISUAL_CONFIRM_DM_PASSTHROUGH)
+			surface->visual_confirm_color = srf_update->flip_addr->visual_confirm_color;
 	}
 
 	if (srf_update->scaling_info) {
diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h
index 1355ce2b49709..d59c23971ef8e 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -653,6 +653,7 @@ enum visual_confirm {
 	VISUAL_CONFIRM_VABC = 21,
 	VISUAL_CONFIRM_DCC = 22,
 	VISUAL_CONFIRM_BOOSTED_REFRESH_RATE = 23,
+	VISUAL_CONFIRM_DM_PASSTHROUGH = 26,
 	VISUAL_CONFIRM_EXPLICIT = 0x80000000,
 };
 
diff --git a/drivers/gpu/drm/amd/display/dc/dc_hw_types.h b/drivers/gpu/drm/amd/display/dc/dc_hw_types.h
index aeac5a45e13b7..084c506a8a94a 100644
--- a/drivers/gpu/drm/amd/display/dc/dc_hw_types.h
+++ b/drivers/gpu/drm/amd/display/dc/dc_hw_types.h
@@ -269,6 +269,14 @@ enum dc_pixel_format {
 	PIXEL_FORMAT_UNKNOWN
 };
 
+#define MAX_TG_COLOR_VALUE 0x3FF
+struct tg_color {
+	/* Maximum 10 bits color value */
+	uint16_t color_r_cr;
+	uint16_t color_g_y;
+	uint16_t color_b_cb;
+};
+
 /*
  * This structure holds a surface address.  There could be multiple addresses
  * in cases such as Stereo 3D, Planar YUV, etc.  Other per-flip attributes such
@@ -283,6 +291,7 @@ struct dc_flip_addrs {
 	bool triplebuffer_flips;
 	unsigned int dirty_rect_count;
 	struct rect dirty_rects[DC_MAX_DIRTY_RECTS];
+	struct tg_color visual_confirm_color; /* DM passthrough visual confirm color, applied in VISUAL_CONFIRM_DM_PASSTHROUGH mode */
 };
 
 enum tile_split_values {
@@ -1187,15 +1196,6 @@ struct mcif_buf_params {
 	uint8_t				tmz_id;
 };
 
-
-#define MAX_TG_COLOR_VALUE 0x3FF
-struct tg_color {
-	/* Maximum 10 bits color value */
-	uint16_t color_r_cr;
-	uint16_t color_g_y;
-	uint16_t color_b_cb;
-};
-
 struct fva_adj {
 	unsigned int pixel_clock_100hz;
 	unsigned int max_pixel_clock_100hz;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 79/82] drm/amd/display: Populate vblank_nom according to bounding box
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (77 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 78/82] drm/amd/display: Add passthrough visual confirm IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 80/82] drm/amd/display: Adjust vblank_nom policy for HW SDP tranmission reqs IVAN.LIPSKI
                   ` (3 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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, Dillon Varone, Austin Zheng

From: Dillon Varone <Dillon.Varone@amd.com>

[Why&How]
Wrapper currently populates the vblank nom as the entire blank for a
given timing. This should be constrained to a sane maximum but still
controlled by the caller's (wrapper) policy, which for now will be the
existing bounding box value.

Reviewed-by: Austin Zheng <Austin.Zheng@amd.com>
Signed-off-by: Dillon Varone <Dillon.Varone@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../dc/dml2_0/dml21/dml21_translation_helper.c  | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c
index d456fb3d616d4..db78810f2b906 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c
@@ -95,6 +95,20 @@ static unsigned int calc_max_hardware_v_total(const struct dc_stream_state *stre
 	return max_hw_v_total;
 }
 
+static unsigned int calc_vblank_nom_lines(const struct dc_stream_state *stream, const unsigned int default_vblank_nom_us)
+{
+	unsigned int vblank_avail = stream->timing.v_total - stream->timing.v_addressable;
+	unsigned int vblank_nom = (unsigned int)div64_u64((uint64_t)default_vblank_nom_us * 1000ULL, // vblank_nom_ns
+			div64_u64((uint64_t)stream->timing.h_total * 10000000ULL, (uint64_t)stream->timing.pix_clk_100hz)); // line_time_ns
+
+	if (vblank_avail < vblank_nom ||
+			stream->adaptive_sync_infopacket.valid ||
+			(stream->link && stream->link->replay_settings.config.replay_supported))
+		vblank_nom = vblank_avail;
+
+	return vblank_nom;
+}
+
 static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cfg *timing,
 		struct dc_stream_state *stream,
 		struct pipe_ctx *otg_master_pipe,
@@ -200,7 +214,7 @@ static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cf
 		break;
 	}
 
-	timing->vblank_nom = timing->v_total - timing->v_active;
+	timing->vblank_nom = calc_vblank_nom_lines(stream, dml_ctx->v21.dml_init.ip_caps.vblank_nom_default_us);
 }
 
 static void populate_dml21_output_config_from_stream_state(struct dml2_link_output_cfg *output,
@@ -1143,4 +1157,3 @@ void dml21_init_min_clocks_for_dc_state(struct dml2_context *in_ctx, struct dc_s
 	min_clocks->stutter_efficiency.z8_stutter_period = 100000;
 	min_clocks->zstate_support = DCN_ZSTATE_SUPPORT_ALLOW;
 }
-
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 80/82] drm/amd/display: Adjust vblank_nom policy for HW SDP tranmission reqs
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (78 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 79/82] drm/amd/display: Populate vblank_nom according to bounding box IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 81/82] drm/amd/display: Guard amdgpu_dm_irq_schedule_work against NULL irq_wq IVAN.LIPSKI
                   ` (2 subsequent siblings)
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Nicholas Kazlauskas <nicholas.kazlauskas@amd.com>

[Why]
The requirement is that VSTARTUP has to come before vsync, line 0.
This means that the VSTARTUP has to be in the blank front porch.

The current implementation in DML2.1 does meet this requirement by
adjusting vblank_nom to the full vblank_avail but this has two
undesirable side effects:

1. When vblank is nominal this pushes VUPDATE on the line after VBLANK
   start. For Replay this gives only 1 line worth of time to program the
   SDP and blank the DPG. If we miss this window then we have to wait
   an entire frame before we can retry IPX entry. Residency is impacted.

2. The prefetch schedule is suboptimal. Instead of leveraging a very
   long vblank for power we wake early and block c-state earlier.

[How]
Use the same policy as prior ASIC - clamp the vblank_nom adjustment to
be the maximum of vblank_nom or the size of the back porch + 2 lines.
The back porch includes the vsync width in it in the calculation.

Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Nicholas Kazlauskas <nicholas.kazlauskas@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 .../dml2_0/dml21/dml21_translation_helper.c   | 20 +++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c
index db78810f2b906..79da467ddb5d6 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c
@@ -101,10 +101,22 @@ static unsigned int calc_vblank_nom_lines(const struct dc_stream_state *stream,
 	unsigned int vblank_nom = (unsigned int)div64_u64((uint64_t)default_vblank_nom_us * 1000ULL, // vblank_nom_ns
 			div64_u64((uint64_t)stream->timing.h_total * 10000000ULL, (uint64_t)stream->timing.pix_clk_100hz)); // line_time_ns
 
-	if (vblank_avail < vblank_nom ||
-			stream->adaptive_sync_infopacket.valid ||
-			(stream->link && stream->link->replay_settings.config.replay_supported))
-		vblank_nom = vblank_avail;
+	/*
+	 * HW requirement for SDP to be in FP ahead of vsync (line 0).
+	 * TODO: Move this out of the translation layer into DC core as per-ASIC policy.
+	 */
+	if (stream->adaptive_sync_infopacket.valid ||
+			(stream->link && stream->link->replay_settings.config.replay_supported)) {
+		const unsigned int v_active = stream->timing.v_border_top + stream->timing.v_addressable +
+					      stream->timing.v_border_bottom;
+		const unsigned int blank_lines = stream->timing.v_total - v_active;
+		const unsigned int bp_lines = blank_lines - stream->timing.v_front_porch;
+		const unsigned int min_vblank_nom = bp_lines + 2;
+
+		vblank_nom = max(vblank_nom, min_vblank_nom);
+	}
+
+	vblank_nom = min(vblank_nom, vblank_avail);
 
 	return vblank_nom;
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 81/82] drm/amd/display: Guard amdgpu_dm_irq_schedule_work against NULL irq_wq
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (79 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 80/82] drm/amd/display: Adjust vblank_nom policy for HW SDP tranmission reqs IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-18 20:16 ` [PATCH 82/82] drm/amd/display: Promote DC to 3.2.395 IVAN.LIPSKI
  2026-08-24 13:46 ` [PATCH 00/82] DC Patches August 17, 2026 Wheeler, Daniel
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Ivan Lipski <ivan.lipski@amd.com>

[Why&How]
An interrupt racing DM teardown can reach amdgpu_dm_irq_schedule_work()
after the IRQ workqueue is destroyed, passing a NULL wq to queue_work()
and crashing in __queue_work(). Bail out early when adev->dm.irq_wq is
NULL so a registered handler is simply not queued.

Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
---
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 3 +++
 1 file changed, 3 insertions(+)

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 9be63996b0627..560ff697ff950 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
@@ -618,6 +618,9 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev,
 
 	DM_IRQ_TABLE_LOCK(adev, irq_table_flags);
 
+	if (!adev->dm.irq_wq)
+		goto out_unlock;
+
 	if (list_empty(handler_list))
 		goto out_unlock;
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [PATCH 82/82] drm/amd/display: Promote DC to 3.2.395
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (80 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 81/82] drm/amd/display: Guard amdgpu_dm_irq_schedule_work against NULL irq_wq IVAN.LIPSKI
@ 2026-08-18 20:16 ` IVAN.LIPSKI
  2026-08-24 13:46 ` [PATCH 00/82] DC Patches August 17, 2026 Wheeler, Daniel
  82 siblings, 0 replies; 86+ messages in thread
From: IVAN.LIPSKI @ 2026-08-18 20:16 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

From: Taimur Hassan <Syed.Hassan@amd.com>

This version brings along the following updates:

 - Fix HPO test regressions.
 - Refactor DPP_SET_INPUT_TRANSFER_FUNC to drop pipe_ctx params.
 - Use fast update path for address-only plane flips.
 - Split OPTC_PIPE_CONTROL_LOCK into smaller helpers.
 - Fix DPREFCLK override when SMU isn't available.
 - Fix CalculateFlipSchedule calculation.
 - Add hook to disable alt-ch in PMO.
 - Update alt-ch size calculations.
 - Enable min dispclk ODM on DCN42.
 - Remove RMCM tetrahedral cube from dc_plane_state.
 - Fix mismatch number of OPP/DPP accounting.
 - Adjust vblank_nom policy for HW SDP throttling.
 - Add passthrough visual confirm.
 - Expand amdgpu_dm KUnit test coverage across cursor, MST, EDID, HDMI,
   IRQ, i2c, framebuffer, tiling/modifier and HPD paths.
 - Various fixes for Kunit tests related to HDMI FRL, IRQ and cursors.

Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Taimur Hassan <Syed.Hassan@amd.com>
Signed-off-by: Ivan Lipski <ivan.lipski@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 d59c23971ef8e..a5a9791ee1000 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -66,7 +66,7 @@ struct dcn_dsc_reg_state;
 struct dcn_optc_reg_state;
 struct dcn_dccg_reg_state;
 
-#define DC_VER "3.2.394"
+#define DC_VER "3.2.395"
 
 /**
  * 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] 86+ messages in thread

* Re: [PATCH 01/82] drm/amd/display: Fall back to overlay cursor on dcn4x when top plane doesn't fill CRTC
  2026-08-18 20:14 ` [PATCH 01/82] drm/amd/display: Fall back to overlay cursor on dcn4x when top plane doesn't fill CRTC IVAN.LIPSKI
@ 2026-08-19  7:27   ` Michel Dänzer
  2026-08-20 10:04     ` Timur Kristóf
  0 siblings, 1 reply; 86+ messages in thread
From: Michel Dänzer @ 2026-08-19  7:27 UTC (permalink / raw)
  To: IVAN.LIPSKI, Timur Kristóf
  Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
	Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Alex Hung, James Lin,
	Chenyu Chen, amd-gfx


[ Adding Timur ]

On 8/18/26 22:14, IVAN.LIPSKI@amd.com wrote:
> From: James Lin <PingLei.Lin@amd.com>
> 
> [why]
> amdgpu_dm_crtc_get_cursor_mode() returns DM_CURSOR_NATIVE_MODE early and
> unconditionally for dcn4.x. That early return was added because these
> ASICs no longer have the cursor-on-scaled-plane or cursor-on-yuv-plane
> restrictions of older DCN, so native cursor is fine in those cases.
> However, it also bypasses the "does the top plane fill the CRTC?" (hole)
> evaluation further down. When the top/primary plane does not cover the
> whole CRTC, the cursor must fall back to an overlay plane so it is
> composited over the uncovered region; keeping the native cursor there
> produces an incorrect result. As a consequence
> igt@amdgpu/amd_cursor_overlay@non-full, which verifies exactly this
> native->overlay fallback, fails on dcn42.
> 
> [how]
> Change unconditional early return with a skip_fmt_scale_restrictions flag.
> For dcn4.x keep skipping the YUV / active-color-pipeline / different-scale
> overlay triggers (genuinely unnecessary on this hardware), but fall
> through to the existing entire_crtc_covered check so a top plane that does
> not fill the CRTC still selects DM_CURSOR_OVERLAY_MODE. Native mode is
> still chosen when the plane covers the whole CRTC.
> 
> Update the amdgpu_dm_cursor KUnit tests accordingly: exercise the dcn4x
> path with a full atomic state fixture (full coverage -> native) and add a
> dcn4x hole case (top plane does not fill the CRTC -> overlay).
> 
> Reviewed-by: Tom Chung <chiahsuan.chung@amd.com>
> Signed-off-by: James Lin <PingLei.Lin@amd.com>
> Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
> ---
>  .../amd/display/amdgpu_dm/amdgpu_dm_cursor.c  | 28 ++++++++--------
>  .../amdgpu_dm/tests/amdgpu_dm_cursor_test.c   | 32 +++++++++++++------
>  2 files changed, 38 insertions(+), 22 deletions(-)
> 
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
> index 9534848ed240c..c41fce07b8fa8 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
> [...]
> @@ -300,15 +297,17 @@ int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
>  	 */
>  	if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) ||
>  	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) ||
> -#if defined(CONFIG_DRM_AMD_DC_DCN6_0)
>  	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) ||
>  	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(6, 0, 0) ||
> -#else
> -	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) ||
> -#endif
>  	    !dm_crtc_state->base.enable) {
> -		*cursor_mode = DM_CURSOR_NATIVE_MODE;
> -		return 0;
> +		/*
> +		 * Newer DCN has no cursor-on-scaled/yuv-plane restriction, so
> +		 * skip those overlay triggers below. A plane that does not fill
> +		 * the CRTC still needs overlay mode so the cursor renders over
> +		 * the hole, so fall through to the coverage check instead of
> +		 * unconditionally forcing native mode here.
> +		 */
> +		skip_fmt_scale_restrictions = true;
>  	}

Timur, looks like this might break your fix in cea54c52d82d ("drm/amd/display: Set native cursor mode for disabled CRTCs") again.

Maybe the CRTC disabled case should be handled separately from the DCE cases after all.


-- 
Earthling Michel Dänzer       \        GNOME / Xwayland / Mesa developer
https://redhat.com             \               Libre software enthusiast

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [PATCH 01/82] drm/amd/display: Fall back to overlay cursor on dcn4x when top plane doesn't fill CRTC
  2026-08-19  7:27   ` Michel Dänzer
@ 2026-08-20 10:04     ` Timur Kristóf
  0 siblings, 0 replies; 86+ messages in thread
From: Timur Kristóf @ 2026-08-20 10:04 UTC (permalink / raw)
  To: IVAN.LIPSKI, Michel Dänzer
  Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
	Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Alex Hung, James Lin,
	Chenyu Chen, amd-gfx

On 2026. augusztus 19., szerda 9:27:20 közép-európai nyári idő Michel Dänzer 
wrote:
> [ Adding Timur ]

Thank you for your consideration!

> 
> On 8/18/26 22:14, IVAN.LIPSKI@amd.com wrote:
> > From: James Lin <PingLei.Lin@amd.com>
> > 
> > [why]
> > amdgpu_dm_crtc_get_cursor_mode() returns DM_CURSOR_NATIVE_MODE early and
> > unconditionally for dcn4.x. That early return was added because these
> > ASICs no longer have the cursor-on-scaled-plane or cursor-on-yuv-plane
> > restrictions of older DCN, so native cursor is fine in those cases.
> > However, it also bypasses the "does the top plane fill the CRTC?" (hole)
> > evaluation further down. When the top/primary plane does not cover the
> > whole CRTC, the cursor must fall back to an overlay plane so it is
> > composited over the uncovered region; keeping the native cursor there
> > produces an incorrect result. As a consequence
> > igt@amdgpu/amd_cursor_overlay@non-full, which verifies exactly this
> > native->overlay fallback, fails on dcn42.
> > 
> > [how]
> > Change unconditional early return with a skip_fmt_scale_restrictions flag.
> > For dcn4.x keep skipping the YUV / active-color-pipeline / different-scale
> > overlay triggers (genuinely unnecessary on this hardware), but fall
> > through to the existing entire_crtc_covered check so a top plane that does
> > not fill the CRTC still selects DM_CURSOR_OVERLAY_MODE. Native mode is
> > still chosen when the plane covers the whole CRTC.
> > 
> > Update the amdgpu_dm_cursor KUnit tests accordingly: exercise the dcn4x
> > path with a full atomic state fixture (full coverage -> native) and add a
> > dcn4x hole case (top plane does not fill the CRTC -> overlay).
> > 
> > Reviewed-by: Tom Chung <chiahsuan.chung@amd.com>
> > Signed-off-by: James Lin <PingLei.Lin@amd.com>
> > Signed-off-by: Ivan Lipski <ivan.lipski@amd.com>
> > ---
> > 
> >  .../amd/display/amdgpu_dm/amdgpu_dm_cursor.c  | 28 ++++++++--------
> >  .../amdgpu_dm/tests/amdgpu_dm_cursor_test.c   | 32 +++++++++++++------
> >  2 files changed, 38 insertions(+), 22 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
> > b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c index
> > 9534848ed240c..c41fce07b8fa8 100644
> > --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
> > +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
> > [...]
> > @@ -300,15 +297,17 @@ int amdgpu_dm_crtc_get_cursor_mode(struct
> > amdgpu_device *adev,> 
> >  	 */
> >  	
> >  	if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) ||
> >  	
> >  	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) ||
> > 
> > -#if defined(CONFIG_DRM_AMD_DC_DCN6_0)
> > 
> >  	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) ||
> >  	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(6, 0, 0) ||
> > 
> > -#else
> > -	    amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) ||
> > -#endif
> > 
> >  	    !dm_crtc_state->base.enable) {
> > 
> > -		*cursor_mode = DM_CURSOR_NATIVE_MODE;
> > -		return 0;
> > +		/*
> > +		 * Newer DCN has no cursor-on-scaled/yuv-plane 
restriction, so
> > +		 * skip those overlay triggers below. A plane that does 
not fill
> > +		 * the CRTC still needs overlay mode so the cursor 
renders over
> > +		 * the hole, so fall through to the coverage check 
instead of
> > +		 * unconditionally forcing native mode here.
> > +		 */
> > +		skip_fmt_scale_restrictions = true;
> > 
> >  	}
> 
> Timur, looks like this might break your fix in cea54c52d82d
> ("drm/amd/display: Set native cursor mode for disabled CRTCs") again.
> 
> Maybe the CRTC disabled case should be handled separately from the DCE cases
> after all.

I think the cleanest solution may be to just move this check:
!dm_crtc_state->base.enable
into a separate if statement above this one.

What do you think, would that fix it?

Thanks,
Timur



^ permalink raw reply	[flat|nested] 86+ messages in thread

* RE: [PATCH 00/82] DC Patches August 17, 2026
  2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
                   ` (81 preceding siblings ...)
  2026-08-18 20:16 ` [PATCH 82/82] drm/amd/display: Promote DC to 3.2.395 IVAN.LIPSKI
@ 2026-08-24 13:46 ` Wheeler, Daniel
  82 siblings, 0 replies; 86+ messages in thread
From: Wheeler, Daniel @ 2026-08-24 13:46 UTC (permalink / raw)
  To: LIPSKI, IVAN, 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

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.

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 - a84de5c9feaa2dbffac1f062ad3b4c0deca1ccc7 -> drm/amd/display: validate plane degamma LUT size for private color prop) with new patches added on top of it.

Tested on Ubuntu 26.04, on Gnome Wayland.

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: IVAN.LIPSKI@amd.com <IVAN.LIPSKI@amd.com>
Sent: Tuesday, August 18, 2026 4:15 PM
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>
Subject: [PATCH 00/82] DC Patches August 17, 2026

From: Ivan Lipski <ivan.lipski@amd.com>

This DC patchset brings improvements in multiple areas. In summary, we highlight:

     - Fix HPO test regressions.
     - Refactor DPP_SET_INPUT_TRANSFER_FUNC to drop pipe_ctx params.
     - Use fast update path for address-only plane flips.
     - Split OPTC_PIPE_CONTROL_LOCK into smaller helpers.
     - Fix DPREFCLK override when SMU isn't available.
     - Fix CalculateFlipSchedule calculation.
     - Add hook to disable alt-ch in PMO.
     - Update alt-ch size calculations.
     - Enable min dispclk ODM on DCN42.
     - Remove RMCM tetrahedral cube from dc_plane_state.
     - Fix mismatch number of OPP/DPP accounting.
     - Adjust vblank_nom policy for HW SDP throttling.
     - Add passthrough visual confirm.
     - Expand amdgpu_dm KUnit test coverage across cursor, MST, EDID, HDMI,
       IRQ, i2c, framebuffer, tiling/modifier and HPD paths.
     - Various fixes for Kunit tests related to HDMI FRL, IRQ and cursors.

Cc: Dan Wheeler <daniel.wheeler@amd.com>


Alex Hung (38):
  drm/amd/display: Test writeback connector
  drm/amd/display: Test GPU memory allocation
  drm/amd/display: Test EDID quirks and ACPI EDID read
  drm/amd/display: Test execute_synaptics_rc_command failures
  drm/amd/display: Test MST stream feature read failure
  drm/amd/display: Test dm_helpers_submit_i2c_over_aux
  drm/amd/display: Test GPU memory allocate and free helpers
  drm/amd/display: Test dm_helpers_dmub_set_config_sync
  drm/amd/display: Test dm_helpers_is_dp_sink_present
  drm/amd/display: Test dm_helpers_read_local_edid
  drm/amd/display: Test dp_handle_test_pattern_request patterns
  drm/amd/display: Test DMUB reg callbacks
  drm/amd/display: Test VBIOS bounding box
  drm/amd/display: Test dm_init_microcode
  drm/amd/display: Test dm_dmub_sw_init
  drm/amd/display: Test MST sideband message ack path
  drm/amd/display: Test dm_dp_mst_get_modes without a remote EDID
  drm/amd/display: Test dm_dp_mst_get_modes with a remote EDID
  drm/amd/display: Test dm_dp_mst_detect DPCD probe and unplug
  drm/amd/display: Test MST connector register and unregister
  drm/amd/display: Test dm_dp_mst_connector_destroy
  drm/amd/display: Test plane state duplicate and destroy
  drm/amd/display: Test modifier list de-duplication
  drm/amd/display: Test GFX6-8 tiling info from modifiers
  drm/amd/display: Test GFX6-8 tile mode and tile split lookups
  drm/amd/display: Test GFX6-8 modifier calculation
  drm/amd/display: Test GFX6-8 modifier list generation
  drm/amd/display: Test framebuffer prepare and cleanup
  drm/amd/display: Test cursor update and async plane update
  drm/amd/display: Refactor amdgpu_dm_irq_test
  drm/amd/display: Test pageflip completion in the high IRQ handlers
  drm/amd/display: Test writeback handling in dm_crtc_high_irq
  drm/amd/display: Test schedule_dc_vmin_vmax
  drm/amd/display: Test handle_hpd_irq_helper detect and debounce exits
  drm/amd/display: Test HPD RX, HPD init and DMUB callback branches
  drm/amd/display: Test dm_dmub_outbox1_low_irq drain and work guards
  drm/amd/display: Test amdgpu_dm_dce110_register_irq_handlers
  drm/amd/display: Test amdgpu_dm_dcn10_register_irq_handlers

Alvin Lee (3):
  drm/amd/display: Disable alt-ch until dependencies are ready
  drm/amd/display: Add hook to disable alt-ch in PMO
  drm/amd/display: Update alt-ch size calculations

Austin Zheng (1):
  drm/amd/display: Fix CalculateFlipSchedule Calculation

Bhawanpreet Lakha (24):
  drm/amd/display: Cover MST path in encoder atomic_check
  drm/amd/display: Cover amdgpu_dm_encoder_init
  drm/amd/display: Cover MST-start failure in detect_mst
  drm/amd/display: Cover amdgpu_dm_update_connector_after_detect
  drm/amd/display: Cover HDMI infoframe/freesync timing paths
  drm/amd/display: Add amdgpu_dm_connector_poll KUnit tests
  drm/amd/display: Cover hide_secondary_tile_from_userspace
  drm/amd/display: Cover dm_validate_stream_and_context
  drm/amd/display: Cover amdgpu_dm_create_validate_stream_for_sink
  drm/amd/display: Cover amdgpu_dm_connector_mode_valid
  drm/amd/display: Cover mode_valid EDID mgmt path
  drm/amd/display: Cover amdgpu_dm_fill_hdr_info_packet
  drm/amd/display: Cover amdgpu_dm_connector_atomic_check
  drm/amd/display: Cover atomic_check modeset triggers
  drm/amd/display: Cover funcs_force valid EDID path
  drm/amd/display: Cover amdgpu_dm_connector_get_modes
  drm/amd/display: Cover create_eml_sink valid EDID path
  drm/amd/display: Cover amdgpu_set_panel_orientation
  drm/amd/display: Cover amdgpu_dm_prune_primary_tile_modes
  drm/amd/display: Cover add_fs_modes mode generation
  drm/amd/display: Cover add_fs_modes illegal timing skip
  drm/amd/display: Cover hdmi_frl_status_polling_work
  drm/amd/display: Cover amdgpu_dm_i2c_xfer
  drm/amd/display: Cover amdgpu_dm_create_i2c

Dillon Varone (2):
  drm/amd/display: Fix mismatch number of OPP/DPP accounting
  drm/amd/display: Populate vblank_nom according to bounding box

Gabe Teeger (1):
  drm/amd/display: Enable min dispclk ODM on DCN42

Gaghik Khachatrian (1):
  drm/amd/display: Add passthrough visual confirm

Ilya Bakoulin (1):
  drm/amd/display: Clear HUBPREQ_DEBUG_DB on DCN6

Ivan Lipski (2):
  drm/amd/display: Refactor hdmi_frl_status_polling_work for Kunit
    testing
  drm/amd/display: Guard amdgpu_dm_irq_schedule_work against NULL irq_wq

James Lin (2):
  drm/amd/display: Fall back to overlay cursor on dcn4x when top plane
    doesn't fill CRTC
  drm/amd/display: Use fast update path for address-only plane flips

Meera Patel (1):
  drm/amd/display: Fix DPREFCLK override when SMU isn't present or ready
    for DCN315

Nicholas Kazlauskas (1):
  drm/amd/display: Adjust vblank_nom policy for HW SDP tranmission reqs

Rafal Ostrowski (1):
  drm/amd/display: Remove RMCM tetrahedral cube from dc_plane_state

Sridevi Arvindekar (1):
  drm/amd/display: Fixes for HPO test regressions

Taimur Hassan (1):
  drm/amd/display: Promote DC to 3.2.395

Tomasz Siemek (2):
  drm/amd/display: Refactor DPP_SET_INPUT_TRANSFER_FUNC to drop pipe_ctx
  drm/amd/display: Split OPTC_PIPE_CONTROL_LOCK into smaller HWSS blocks

 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c |   10 +-
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h |   10 +
 .../display/amdgpu_dm/amdgpu_dm_connector.c   |   18 +-
 .../display/amdgpu_dm/amdgpu_dm_connector.h   |    7 +
 .../amd/display/amdgpu_dm/amdgpu_dm_cursor.c  |   28 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.c    |   47 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_dmub.h    |   15 +
 .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c |    6 +
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c |    3 +
 .../display/amdgpu_dm/amdgpu_dm_mst_types.c   |   12 +-
 .../display/amdgpu_dm/amdgpu_dm_mst_types.h   |    4 +
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.c   |   48 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_plane.h   |   12 +
 .../display/amdgpu_dm/amdgpu_dm_services.c    |   35 +-
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c  |   72 +-
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h  |   16 +
 .../tests/amdgpu_dm_connector_test.c          | 3387 ++++++++++++++++-
 .../amdgpu_dm/tests/amdgpu_dm_cursor_test.c   |   32 +-
 .../amdgpu_dm/tests/amdgpu_dm_dmub_test.c     |  578 ++-
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  |  862 ++++-
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 1117 +++++-
 .../tests/amdgpu_dm_mst_types_test.c          |  741 ++++
 .../amdgpu_dm/tests/amdgpu_dm_plane_test.c    |  703 +++-
 .../amdgpu_dm/tests/amdgpu_dm_services_test.c |  228 +-
 .../amdgpu_dm/tests/amdgpu_dm_wb_test.c       |  330 ++
 .../dc/clk_mgr/dcn315/dcn315_clk_mgr.c        |    4 +-
 drivers/gpu/drm/amd/display/dc/core/dc.c      |   16 +-
 .../drm/amd/display/dc/core/dc_hw_sequencer.c |  193 +-
 .../gpu/drm/amd/display/dc/core/dc_resource.c |    7 +
 drivers/gpu/drm/amd/display/dc/dc.h           |    3 +-
 drivers/gpu/drm/amd/display/dc/dc_hw_types.h  |   18 +-
 drivers/gpu/drm/amd/display/dc/dce/dce_aux.c  |   12 +-
 .../dml2_0/dml21/dml21_translation_helper.c   |   30 +-
 .../amd/display/dc/dml2_0/dml21/dml21_utils.c |   15 +-
 .../dml21/inc/bounding_boxes/dcn42b_soc_bb.h  |   10 +-
 .../src/dml2_core/dml2_core_dcn4_calcs.c      |   34 +-
 .../dml2_core_dcn5_funcs_initialize.c         |    2 +-
 .../dml2_core_dcn5_funcs_mode_support.c       |   23 +-
 .../dml2_core/dml2_core_dcn6_calcs_dchub.c    |   47 +-
 .../dml2_core_dcn6_funcs_initialize.c         |    2 +
 .../dml2_core_dcn6_funcs_mode_support.c       |   27 +-
 .../src/dml2_core/dml2_core_shared_types.h    |    2 +
 drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c  |    7 +-
 .../amd/display/dc/hubp/dcn60/dcn60_hubp.c    |    3 +
 .../drm/amd/display/dc/hwss/dce/dce_hwseq.c   |   46 +-
 .../drm/amd/display/dc/hwss/dce/dce_hwseq.h   |   12 +-
 .../amd/display/dc/hwss/dce110/dce110_hwseq.c |   16 +-
 .../amd/display/dc/hwss/dce60/dce60_hwseq.c   |    8 +-
 .../amd/display/dc/hwss/dce80/dce80_hwseq.c   |    3 +-
 .../amd/display/dc/hwss/dcn10/dcn10_hwseq.c   |   66 +-
 .../amd/display/dc/hwss/dcn10/dcn10_hwseq.h   |    8 +-
 .../amd/display/dc/hwss/dcn10/dcn10_init.c    |    4 +-
 .../amd/display/dc/hwss/dcn20/dcn20_hwseq.c   |  216 +-
 .../amd/display/dc/hwss/dcn20/dcn20_hwseq.h   |   21 +-
 .../amd/display/dc/hwss/dcn20/dcn20_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn201/dcn201_hwseq.c |   34 +-
 .../amd/display/dc/hwss/dcn201/dcn201_hwseq.h |    5 +-
 .../amd/display/dc/hwss/dcn201/dcn201_init.c  |    4 +-
 .../amd/display/dc/hwss/dcn21/dcn21_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn30/dcn30_hwseq.c   |   51 +-
 .../amd/display/dc/hwss/dcn30/dcn30_hwseq.h   |    8 +-
 .../amd/display/dc/hwss/dcn30/dcn30_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn301/dcn301_init.c  |    3 +-
 .../amd/display/dc/hwss/dcn31/dcn31_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn314/dcn314_init.c  |    3 +-
 .../amd/display/dc/hwss/dcn32/dcn32_hwseq.c   |   64 +-
 .../amd/display/dc/hwss/dcn32/dcn32_hwseq.h   |   10 +-
 .../amd/display/dc/hwss/dcn32/dcn32_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn35/dcn35_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn351/dcn351_init.c  |    3 +-
 .../amd/display/dc/hwss/dcn401/dcn401_hwseq.c |  174 +-
 .../amd/display/dc/hwss/dcn401/dcn401_hwseq.h |    9 +-
 .../amd/display/dc/hwss/dcn401/dcn401_init.c  |    3 +-
 .../amd/display/dc/hwss/dcn42/dcn42_hwseq.c   |   49 +-
 .../amd/display/dc/hwss/dcn42/dcn42_hwseq.h   |   12 +-
 .../amd/display/dc/hwss/dcn42/dcn42_init.c    |    3 +-
 .../amd/display/dc/hwss/dcn60/dcn60_init.c    |    3 +-
 .../drm/amd/display/dc/hwss/hw_sequencer.h    |   55 +-
 .../display/dc/hwss/hw_sequencer_private.h    |   21 +-
 .../gpu/drm/amd/display/dc/inc/hw/hw_shared.h |   10 -
 .../display/dc/link/hwss/link_hwss_hpo_dp.c   |   23 +-
 .../dc/link/protocols/link_dp_capability.c    |    3 +-
 .../dc/resource/dcn42/dcn42_resource.c        |    4 +-
 .../dc/resource/dcn42b/dcn42b_resource.c      |    4 +-
 .../dc/resource/dcn60/dcn60_resource.c        |    2 +
 85 files changed, 8768 insertions(+), 986 deletions(-)

--
2.43.0


^ permalink raw reply	[flat|nested] 86+ messages in thread

end of thread, other threads:[~2026-08-24 13:46 UTC | newest]

Thread overview: 86+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 20:14 [PATCH 00/82] DC Patches August 17, 2026 IVAN.LIPSKI
2026-08-18 20:14 ` [PATCH 01/82] drm/amd/display: Fall back to overlay cursor on dcn4x when top plane doesn't fill CRTC IVAN.LIPSKI
2026-08-19  7:27   ` Michel Dänzer
2026-08-20 10:04     ` Timur Kristóf
2026-08-18 20:14 ` [PATCH 02/82] drm/amd/display: Fixes for HPO test regressions IVAN.LIPSKI
2026-08-18 20:14 ` [PATCH 03/82] drm/amd/display: Refactor DPP_SET_INPUT_TRANSFER_FUNC to drop pipe_ctx IVAN.LIPSKI
2026-08-18 20:14 ` [PATCH 04/82] drm/amd/display: Use fast update path for address-only plane flips IVAN.LIPSKI
2026-08-18 20:14 ` [PATCH 05/82] drm/amd/display: Split OPTC_PIPE_CONTROL_LOCK into smaller HWSS blocks IVAN.LIPSKI
2026-08-18 20:14 ` [PATCH 06/82] drm/amd/display: Fix DPREFCLK override when SMU isn't present or ready for DCN315 IVAN.LIPSKI
2026-08-18 20:14 ` [PATCH 07/82] drm/amd/display: Test writeback connector IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 08/82] drm/amd/display: Test GPU memory allocation IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 09/82] drm/amd/display: Cover MST path in encoder atomic_check IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 10/82] drm/amd/display: Cover amdgpu_dm_encoder_init IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 11/82] drm/amd/display: Cover MST-start failure in detect_mst IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 12/82] drm/amd/display: Cover amdgpu_dm_update_connector_after_detect IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 13/82] drm/amd/display: Cover HDMI infoframe/freesync timing paths IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 14/82] drm/amd/display: Clear HUBPREQ_DEBUG_DB on DCN6 IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 15/82] drm/amd/display: Disable alt-ch until dependencies are ready IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 16/82] drm/amd/display: Fix CalculateFlipSchedule Calculation IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 17/82] drm/amd/display: Test EDID quirks and ACPI EDID read IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 18/82] drm/amd/display: Test execute_synaptics_rc_command failures IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 19/82] drm/amd/display: Test MST stream feature read failure IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 20/82] drm/amd/display: Test dm_helpers_submit_i2c_over_aux IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 21/82] drm/amd/display: Test GPU memory allocate and free helpers IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 22/82] drm/amd/display: Test dm_helpers_dmub_set_config_sync IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 23/82] drm/amd/display: Test dm_helpers_is_dp_sink_present IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 24/82] drm/amd/display: Test dm_helpers_read_local_edid IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 25/82] drm/amd/display: Test dp_handle_test_pattern_request patterns IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 26/82] drm/amd/display: Test DMUB reg callbacks IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 27/82] drm/amd/display: Test VBIOS bounding box IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 28/82] drm/amd/display: Test dm_init_microcode IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 29/82] drm/amd/display: Test dm_dmub_sw_init IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 30/82] drm/amd/display: Add hook to disable alt-ch in PMO IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 31/82] drm/amd/display: Update alt-ch size calculations IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 32/82] drm/amd/display: Enable min dispclk ODM on DCN42 IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 33/82] drm/amd/display: Add amdgpu_dm_connector_poll KUnit tests IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 34/82] drm/amd/display: Cover hide_secondary_tile_from_userspace IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 35/82] drm/amd/display: Cover dm_validate_stream_and_context IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 36/82] drm/amd/display: Cover amdgpu_dm_create_validate_stream_for_sink IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 37/82] drm/amd/display: Cover amdgpu_dm_connector_mode_valid IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 38/82] drm/amd/display: Test MST sideband message ack path IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 39/82] drm/amd/display: Test dm_dp_mst_get_modes without a remote EDID IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 40/82] drm/amd/display: Test dm_dp_mst_get_modes with " IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 41/82] drm/amd/display: Test dm_dp_mst_detect DPCD probe and unplug IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 42/82] drm/amd/display: Test MST connector register and unregister IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 43/82] drm/amd/display: Test dm_dp_mst_connector_destroy IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 44/82] drm/amd/display: Test plane state duplicate and destroy IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 45/82] drm/amd/display: Test modifier list de-duplication IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 46/82] drm/amd/display: Test GFX6-8 tiling info from modifiers IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 47/82] drm/amd/display: Test GFX6-8 tile mode and tile split lookups IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 48/82] drm/amd/display: Test GFX6-8 modifier calculation IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 49/82] drm/amd/display: Test GFX6-8 modifier list generation IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 50/82] drm/amd/display: Test framebuffer prepare and cleanup IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 51/82] drm/amd/display: Test cursor update and async plane update IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 52/82] drm/amd/display: Remove RMCM tetrahedral cube from dc_plane_state IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 53/82] drm/amd/display: Cover mode_valid EDID mgmt path IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 54/82] drm/amd/display: Cover amdgpu_dm_fill_hdr_info_packet IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 55/82] drm/amd/display: Cover amdgpu_dm_connector_atomic_check IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 56/82] drm/amd/display: Cover atomic_check modeset triggers IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 57/82] drm/amd/display: Cover funcs_force valid EDID path IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 58/82] drm/amd/display: Cover amdgpu_dm_connector_get_modes IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 59/82] drm/amd/display: Cover create_eml_sink valid EDID path IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 60/82] drm/amd/display: Cover amdgpu_set_panel_orientation IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 61/82] drm/amd/display: Refactor amdgpu_dm_irq_test IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 62/82] drm/amd/display: Test pageflip completion in the high IRQ handlers IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 63/82] drm/amd/display: Test writeback handling in dm_crtc_high_irq IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 64/82] drm/amd/display: Test schedule_dc_vmin_vmax IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 65/82] drm/amd/display: Test handle_hpd_irq_helper detect and debounce exits IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 66/82] drm/amd/display: Test HPD RX, HPD init and DMUB callback branches IVAN.LIPSKI
2026-08-18 20:15 ` [PATCH 67/82] drm/amd/display: Test dm_dmub_outbox1_low_irq drain and work guards IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 68/82] drm/amd/display: Test amdgpu_dm_dce110_register_irq_handlers IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 69/82] drm/amd/display: Test amdgpu_dm_dcn10_register_irq_handlers IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 70/82] drm/amd/display: Fix mismatch number of OPP/DPP accounting IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 71/82] drm/amd/display: Cover amdgpu_dm_prune_primary_tile_modes IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 72/82] drm/amd/display: Cover add_fs_modes mode generation IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 73/82] drm/amd/display: Cover add_fs_modes illegal timing skip IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 74/82] drm/amd/display: Refactor hdmi_frl_status_polling_work for Kunit testing IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 75/82] drm/amd/display: Cover hdmi_frl_status_polling_work IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 76/82] drm/amd/display: Cover amdgpu_dm_i2c_xfer IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 77/82] drm/amd/display: Cover amdgpu_dm_create_i2c IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 78/82] drm/amd/display: Add passthrough visual confirm IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 79/82] drm/amd/display: Populate vblank_nom according to bounding box IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 80/82] drm/amd/display: Adjust vblank_nom policy for HW SDP tranmission reqs IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 81/82] drm/amd/display: Guard amdgpu_dm_irq_schedule_work against NULL irq_wq IVAN.LIPSKI
2026-08-18 20:16 ` [PATCH 82/82] drm/amd/display: Promote DC to 3.2.395 IVAN.LIPSKI
2026-08-24 13:46 ` [PATCH 00/82] DC Patches August 17, 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.