* [PATCH 00/49] DC Patches July 20th, 2026
@ 2026-07-23 20:13 Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 01/49] drm/amd/display: share common DM KUnit helpers Fangzhi Zuo
` (48 more replies)
0 siblings, 49 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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
This DC patchset brings improvements in multiple areas. In summary, we have:
* Expand KUnit test
* dm refactor ongoing
* Fix apple 5k tiled monitor light up
* Fix frl dsc upstream mistake
* dcn42 fixes
Alex Deucher (1):
drm/amd/display: check if dml21_add_phantom_plane() is successful
Alex Hung (16):
drm/amd/display: share common DM KUnit helpers
drm/amd/display: move scaling helper to connector
drm/amd/display: move stutter quirk to quirks file
drm/amd/display: move watermarks table to pp_smu
drm/amd/display: move GPU mem helpers to services
drm/amd/display: add FreeSync/VRR module
drm/amd/display: add cursor module
drm/amd/display: add KUnit tests for audio component get_eld
drm/amd/display: add KUnit tests for audio commit path
drm/amd/display: Add KUnit test for native backlight registration
drm/amd/display: Add color transfer-function tests
drm/amd/display: Add atomic transfer-function tests
drm/amd/display: Add CRTC and plane degamma tests
drm/amd/display: Add legacy plane LUT tests
drm/amd/display: Add truncated colorop tests
drm/amd/display: Add colorop LUT programming tests
Alexander Chechik (2):
drm/amd/display: enforce UCLK pstate support in mode_support
drm/amd/display: plumb PMO per-plane pstate methods into mode_support
Bhawanpreet Lakha (13):
drm/amd/display: Add KUnit tests for link_lock and psp SRM helpers
drm/amd/display: Add KUnit tests for HDCP display helpers
drm/amd/display: Add KUnit tests for event_callback
drm/amd/display: Add KUnit tests for event_property_validate
drm/amd/display: Add KUnit tests for watchdog and cpirq events
drm/amd/display: Add KUnit tests for hdcp_destroy
drm/amd/display: Add KUnit tests for enable_assr
drm/amd/display: Add KUnit tests for update_config
drm/amd/display: Add KUnit tests for hdcp_create_workqueue
drm/amd/display: Add KUnit tests for srm_data_write and srm_data_read
drm/amd/display: Add KUnit tests for HDCP DDC link adapters
drm/amd/display: Add initialized-branch test for psp_set_srm
drm/amd/display: Add deeper event_property_update tests
Charlene Liu (2):
drm/amd/display: revert "convert dcn42 GPIO translation to lookup
tables"
drm/amd/display: change dcc_rate from 1 to 2 for log use only
Clay King (1):
drm/amd/display: adjust floating point format for gamut remap when
needed
Dillon Varone (1):
drm/amd/display: Port DCN4+ MCIF ARB programming to new format
Fangzhi Zuo (2):
drm/amd/display: Prune per-tile Timing from Apple Studio Display
Primary Tile
drm/amd/display: dispatch compressed FRL cap check inside
dml1_frl_cap_chk_inter
George Zhang (1):
drm/amd/display: Fix divide-by-zero in calculate_mcache_setting on
zero viewport
Huang, Leon (1):
drm/amd/display: Add get replay residency function
Justin Chen (1):
drm/amd/display: add DalForceMaxDisplayClock debug option to DML2
Matthew Stewart (2):
drm/amd/display: Fixes for dcn42b_soc_bb.h
drm/amd/display: Fix rounding errors in CalculatePrefetchSchedule
Nicholas Carbones (2):
drm/amd/display: Correct vblank_end calc for fams cmd packet
drm/amd/display: Reintroduce "convert dcn42 GPIO translation to lookup
tables"
Ray Wu (1):
drm/amd/display: Add AV mute wait frames to dce110_set_avmute
Relja Vojvodic (1):
drm/amd/display: Fix force FRL rate debug setting
Santhosh, Ashwin (1):
drm/amd/display: Promote DC to 3.2.391
Tomasz Siemek (1):
drm/amd/display: Use current mpc pipe in set output transfer func
.../gpu/drm/amd/display/amdgpu_dm/Makefile | 4 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 981 +-------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 37 +-
.../amd/display/amdgpu_dm/amdgpu_dm_audio.c | 1 +
.../amd/display/amdgpu_dm/amdgpu_dm_color.c | 36 +-
.../amd/display/amdgpu_dm/amdgpu_dm_color.h | 14 +
.../display/amdgpu_dm/amdgpu_dm_connector.c | 100 +
.../amd/display/amdgpu_dm/amdgpu_dm_cursor.c | 443 ++++
.../amd/display/amdgpu_dm/amdgpu_dm_cursor.h | 52 +
.../display/amdgpu_dm/amdgpu_dm_freesync.c | 362 +++
.../display/amdgpu_dm/amdgpu_dm_freesync.h | 65 +
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 99 +-
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 37 +
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 52 +
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 3 +
.../amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 32 +
.../display/amdgpu_dm/amdgpu_dm_services.c | 53 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 2 +
.../amdgpu_dm/tests/amdgpu_dm_audio_test.c | 651 ++++++
.../tests/amdgpu_dm_backlight_test.c | 61 +-
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 602 +++++
.../tests/amdgpu_dm_connector_test.c | 209 ++
.../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 30 +-
.../amdgpu_dm/tests/amdgpu_dm_cursor_test.c | 268 +++
.../amdgpu_dm/tests/amdgpu_dm_freesync_test.c | 453 ++++
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 2063 ++++++++++++++++-
.../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 32 +-
.../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c | 39 +-
.../tests/amdgpu_dm_kunit_test_helpers.h | 4 +
.../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 20 +
.../amdgpu_dm/tests/amdgpu_dm_psr_test.c | 15 +-
.../amdgpu_dm/tests/amdgpu_dm_quirks_test.c | 65 +
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 937 --------
.../drm/amd/display/dc/basics/conversion.c | 61 +-
.../drm/amd/display/dc/basics/conversion.h | 12 +-
.../drm/amd/display/dc/core/dc_hw_sequencer.c | 4 +-
.../drm/amd/display/dc/core/dc_link_exports.c | 7 +
.../gpu/drm/amd/display/dc/core/dc_stream.c | 4 +-
drivers/gpu/drm/amd/display/dc/dc.h | 11 +-
.../drm/amd/display/dc/dce/dce_transform.c | 4 +-
.../drm/amd/display/dc/dml/dml1_frl_cap_chk.c | 10 +-
.../dml2_0/dml21/dml21_translation_helper.c | 8 +-
.../amd/display/dc/dml2_0/dml21/dml21_utils.c | 15 +-
.../dc/dml2_0/dml21/dml21_wrapper_fpu.c | 5 +
.../dml21/inc/bounding_boxes/dcn42b_soc_bb.h | 4 +-
.../dml21/src/dml2_core/dml2_core_dcn4.c | 4 +
.../src/dml2_core/dml2_core_dcn4_calcs.c | 67 +-
.../src/dml2_core/dml2_core_shared_types.h | 1 +
.../dml21/src/dml2_pmo/dml2_pmo_dcn42.c | 32 +-
.../dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c | 2 +-
.../amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c | 8 +-
.../amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c | 8 +-
.../amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c | 8 +-
.../amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c | 3 +-
.../dc/gpio/dcn42/hw_translate_dcn42.c.orig | 206 ++
.../amd/display/dc/hwss/dce110/dce110_hwseq.c | 21 +-
.../display/dc/link/protocols/link_hdmi_frl.c | 2 +-
.../gpu/drm/amd/display/dc/mmhubbub/Makefile | 10 +
.../dc/mmhubbub/dcn32/dcn32_mmhubbub.c | 4 +-
.../dc/mmhubbub/dcn32/dcn32_mmhubbub.h | 5 +
.../dc/mmhubbub/dcn401/dcn401_mmhubbub.c | 135 ++
.../dc/mmhubbub/dcn401/dcn401_mmhubbub.h | 40 +
.../dc/mmhubbub/dcn42/dcn42_mmhubbub.h | 1 +
.../drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c | 44 +-
.../amd/display/dc/mpc/dcn401/dcn401_mpc.c | 69 +-
.../amd/display/dc/mpc/dcn401/dcn401_mpc.h | 6 +-
.../dc/resource/dcn30/dcn30_resource.c | 2 +
.../dc/resource/dcn301/dcn301_resource.c | 2 +
.../dc/resource/dcn302/dcn302_resource.c | 2 +
.../dc/resource/dcn303/dcn303_resource.c | 2 +
.../dc/resource/dcn31/dcn31_resource.c | 2 +
.../dc/resource/dcn314/dcn314_resource.c | 2 +
.../dc/resource/dcn315/dcn315_resource.c | 2 +
.../dc/resource/dcn316/dcn316_resource.c | 2 +
.../dc/resource/dcn32/dcn32_resource.c | 2 +
.../dc/resource/dcn321/dcn321_resource.c | 2 +
.../dc/resource/dcn35/dcn35_resource.c | 2 +
.../dc/resource/dcn351/dcn351_resource.c | 2 +
.../dc/resource/dcn36/dcn36_resource.c | 2 +
.../dc/resource/dcn401/dcn401_resource.c | 31 +-
.../dc/resource/dcn401/dcn401_resource.h | 4 +
.../dc/resource/dcn42/dcn42_resource.c | 30 +-
.../dc/resource/dcn42b/dcn42b_resource.c | 2 +
83 files changed, 6482 insertions(+), 2254 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c
create mode 100644 drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c.orig
create mode 100644 drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c
create mode 100644 drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h
--
2.53.0
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH 01/49] drm/amd/display: share common DM KUnit helpers
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 02/49] drm/amd/display: Port DCN4+ MCIF ARB programming to new format Fangzhi Zuo
` (47 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Move fixture setup that was duplicated across several amdgpu_dm
KUnit tests into amdgpu_dm_kunit_helpers.c:
- dm_kunit_alloc_dc_with_ctx() allocates a dc and its dc_context.
- dm_kunit_alloc_drm_with_connector_list() allocates a drm_device
with an initialized connector list.
Update the irq, psr, crtc and backlight tests to use these helpers.
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../tests/amdgpu_dm_backlight_test.c | 9 +----
.../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 30 +++-----------
.../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 32 ++++-----------
.../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c | 39 +++++++++++++++----
.../tests/amdgpu_dm_kunit_test_helpers.h | 4 ++
.../amdgpu_dm/tests/amdgpu_dm_psr_test.c | 15 ++-----
6 files changed, 52 insertions(+), 77 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index f55f93747df7..618fd5142da0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -56,14 +56,7 @@ static void setup_test_connector(struct kunit *test,
static void setup_test_dm_ddev(struct kunit *test, struct amdgpu_display_manager *dm)
{
- struct drm_device *ddev;
-
- ddev = kunit_kzalloc(test, sizeof(*ddev), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddev);
-
- INIT_LIST_HEAD(&ddev->mode_config.connector_list);
- spin_lock_init(&ddev->mode_config.connector_list_lock);
- dm->ddev = ddev;
+ dm->ddev = dm_kunit_alloc_drm_with_connector_list(test);
}
/* Tests for dm_find_stream_with_link() */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
index 0edaf969f16b..ac21bbec92ab 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
@@ -296,13 +296,9 @@ static void dm_test_crtc_is_headless_null_adev(struct kunit *test)
static void dm_test_crtc_is_headless_no_connectors(struct kunit *test)
{
struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
- struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
-
- INIT_LIST_HEAD(&dev->mode_config.connector_list);
- spin_lock_init(&dev->mode_config.connector_list_lock);
adev->dm.ddev = dev;
KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev));
@@ -315,15 +311,11 @@ static void dm_test_crtc_is_headless_no_connectors(struct kunit *test)
static void dm_test_crtc_is_headless_writeback_only(struct kunit *test)
{
struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
- struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test);
struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb);
-
- INIT_LIST_HEAD(&dev->mode_config.connector_list);
- spin_lock_init(&dev->mode_config.connector_list_lock);
adev->dm.ddev = dev;
dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK,
@@ -339,15 +331,11 @@ static void dm_test_crtc_is_headless_writeback_only(struct kunit *test)
static void dm_test_crtc_is_headless_disconnected_display(struct kunit *test)
{
struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
- struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test);
struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display);
-
- INIT_LIST_HEAD(&dev->mode_config.connector_list);
- spin_lock_init(&dev->mode_config.connector_list_lock);
adev->dm.ddev = dev;
dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA,
@@ -363,15 +351,11 @@ static void dm_test_crtc_is_headless_disconnected_display(struct kunit *test)
static void dm_test_crtc_is_headless_connected_display(struct kunit *test)
{
struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
- struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test);
struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display);
-
- INIT_LIST_HEAD(&dev->mode_config.connector_list);
- spin_lock_init(&dev->mode_config.connector_list_lock);
adev->dm.ddev = dev;
dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA,
@@ -387,17 +371,13 @@ static void dm_test_crtc_is_headless_connected_display(struct kunit *test)
static void dm_test_crtc_is_headless_mixed_connectors(struct kunit *test)
{
struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
- struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test);
struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL);
struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display);
-
- INIT_LIST_HEAD(&dev->mode_config.connector_list);
- spin_lock_init(&dev->mode_config.connector_list_lock);
adev->dm.ddev = dev;
dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK,
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 dc7ef0523b8f..28c36217f6a2 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
@@ -202,22 +202,6 @@ static void dm_test_dmub_notify_callback(struct amdgpu_device *adev,
dm_test_dmub_notify_count++;
}
-static struct dc *dm_test_alloc_dc_with_ctx(struct kunit *test)
-{
- struct dc_context *ctx;
- struct dc *dc;
-
- 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);
-
- dc->ctx = ctx;
- ctx->dc = dc;
-
- return dc;
-}
-
static enum dc_irq_source dm_test_to_dal_irq_source_dce110(
struct irq_service *irq_service,
uint32_t src_id,
@@ -299,7 +283,7 @@ static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test,
struct dc *dc;
int i;
- dc = dm_test_alloc_dc_with_ctx(test);
+ dc = dm_kunit_alloc_dc_with_ctx(test);
res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool);
irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL);
@@ -3283,7 +3267,7 @@ static void dm_test_register_hpd_handlers_dmub_outbox(struct kunit *test)
adev = dm_kunit_alloc_adev(test);
KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
- dc = dm_test_alloc_dc_with_ctx(test);
+ dc = dm_kunit_alloc_dc_with_ctx(test);
adev->dm.dc = dc;
/* Make dc_is_dmub_outbox_supported() return true. */
@@ -3495,7 +3479,7 @@ static void dm_test_dmub_outbox1_low_irq_empty(struct kunit *test)
struct dc *dc;
adev = dm_kunit_alloc_adev(test);
- dc = dm_test_alloc_dc_with_ctx(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);
@@ -3529,7 +3513,7 @@ static struct amdgpu_device *dm_test_alloc_adev_outbox_notify(struct kunit *test
struct dc *dc;
adev = dm_kunit_alloc_adev(test);
- dc = dm_test_alloc_dc_with_ctx(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);
@@ -3635,7 +3619,7 @@ static void dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources(s
struct dc *dc;
adev = dm_kunit_alloc_adev(test);
- dc = dm_test_alloc_dc_with_ctx(test);
+ dc = dm_kunit_alloc_dc_with_ctx(test);
adev->dm.dc = dc;
KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL);
@@ -3696,7 +3680,7 @@ static void dm_test_dcn10_register_irq_handlers_zero_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_ctx(test);
+ dc = dm_kunit_alloc_dc_with_ctx(test);
adev->dm.dc = dc;
adev->mode_info.num_hpd = 1;
amdgpu_dm_set_irq_funcs(adev);
@@ -3759,7 +3743,7 @@ static void dm_test_register_outbox_irq_handlers_without_dmub(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_ctx(test);
+ dc = dm_kunit_alloc_dc_with_ctx(test);
adev->dm.dc = dc;
amdgpu_dm_set_irq_funcs(adev);
@@ -3899,7 +3883,7 @@ static void dm_test_handle_vmin_vmax_update(struct kunit *test)
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
mutex_init(&adev->dm.dc_lock);
- dc = dm_test_alloc_dc_with_ctx(test);
+ 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);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c
index 58615cdbe854..75cd4fef3076 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c
@@ -45,23 +45,32 @@ struct dc_link *dm_kunit_alloc_link(struct kunit *test)
}
EXPORT_SYMBOL(dm_kunit_alloc_link);
-struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test)
+struct dc *dm_kunit_alloc_dc_with_ctx(struct kunit *test)
{
- struct dc_link *link;
struct dc_context *ctx;
struct dc *dc;
- link = dm_kunit_alloc_link(test);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dc);
ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ctx);
- dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, dc);
-
- link->ctx = ctx;
- ctx->dc = dc;
dc->ctx = ctx;
+ ctx->dc = dc;
+
+ return dc;
+}
+EXPORT_SYMBOL(dm_kunit_alloc_dc_with_ctx);
+
+struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test)
+{
+ struct dc_link *link;
+ struct dc *dc;
+
+ link = dm_kunit_alloc_link(test);
+ dc = dm_kunit_alloc_dc_with_ctx(test);
+ link->ctx = dc->ctx;
return link;
}
@@ -138,5 +147,19 @@ struct amdgpu_dm_connector *dm_kunit_alloc_connector(struct kunit *test,
}
EXPORT_SYMBOL(dm_kunit_alloc_connector);
+struct drm_device *dm_kunit_alloc_drm_with_connector_list(struct kunit *test)
+{
+ struct drm_device *dev;
+
+ dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ INIT_LIST_HEAD(&dev->mode_config.connector_list);
+ spin_lock_init(&dev->mode_config.connector_list_lock);
+
+ return dev;
+}
+EXPORT_SYMBOL(dm_kunit_alloc_drm_with_connector_list);
+
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("KUnit test helpers for amdgpu_dm tests");
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h
index 0f1c48fa2128..49b358681d60 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h
@@ -13,14 +13,18 @@
struct amdgpu_device;
struct amdgpu_display_manager;
struct amdgpu_dm_connector;
+struct dc;
struct dc_link;
struct dc_state;
struct dc_stream_state;
+struct drm_device;
struct amdgpu_device *dm_kunit_alloc_adev(struct kunit *test);
+struct dc *dm_kunit_alloc_dc_with_ctx(struct kunit *test);
struct dc_link *dm_kunit_alloc_link(struct kunit *test);
struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test);
struct amdgpu_display_manager *dm_kunit_alloc_dm(struct kunit *test);
+struct drm_device *dm_kunit_alloc_drm_with_connector_list(struct kunit *test);
struct dc_stream_state *dm_kunit_alloc_stream(struct kunit *test,
struct dc_link *link);
void dm_kunit_add_stream_to_state(struct kunit *test, struct dc_state *state,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c
index 09bd98e93047..0a484ea7c0e8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c
@@ -46,19 +46,10 @@ static struct core_power *create_test_power_module(struct kunit *test,
static struct dc_link *alloc_test_psrsu_link(struct kunit *test)
{
- struct dc_link *link = dm_kunit_alloc_link(test);
- struct dc_context *ctx;
- struct dc *dc;
-
- ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, ctx);
-
- dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, dc);
+ struct dc_link *link = dm_kunit_alloc_link_with_ctx(test);
+ struct dc_context *ctx = link->ctx;
+ struct dc *dc = ctx->dc;
- link->ctx = ctx;
- ctx->dc = dc;
- dc->ctx = ctx;
dc->caps.dmcub_support = true;
ctx->dce_version = DCN_VERSION_3_1;
link->dpcd_caps.edp_rev = DP_EDP_14;
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 02/49] drm/amd/display: Port DCN4+ MCIF ARB programming to new format
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 01/49] drm/amd/display: share common DM KUnit helpers Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 03/49] drm/amd/display: Fix force FRL rate debug setting Fangzhi Zuo
` (46 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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,
Sridevi Arvindekar
From: Dillon Varone <Dillon.Varone@amd.com>
[WHY&HOW]
DML2.1 now outputs MCIF arbiter programming, but using a new
structure. Change DCN401 and DCN42 to use this new format.
Reviewed-by: Sridevi Arvindekar <sridevi.arvindekar@amd.com>
Signed-off-by: Dillon Varone <Dillon.Varone@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../gpu/drm/amd/display/dc/mmhubbub/Makefile | 10 ++
.../dc/mmhubbub/dcn32/dcn32_mmhubbub.c | 4 +-
.../dc/mmhubbub/dcn32/dcn32_mmhubbub.h | 5 +
.../dc/mmhubbub/dcn401/dcn401_mmhubbub.c | 135 ++++++++++++++++++
.../dc/mmhubbub/dcn401/dcn401_mmhubbub.h | 40 ++++++
.../dc/mmhubbub/dcn42/dcn42_mmhubbub.h | 1 +
.../dc/resource/dcn401/dcn401_resource.c | 29 ++--
.../dc/resource/dcn401/dcn401_resource.h | 4 +
.../dc/resource/dcn42/dcn42_resource.c | 28 ++--
9 files changed, 227 insertions(+), 29 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c
create mode 100644 drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h
diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile b/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile
index afc376defa0b..dddf5a9eb0b8 100644
--- a/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile
+++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile
@@ -50,6 +50,16 @@ MMHUBBUB_DCN35 = dcn35_mmhubbub.o
AMD_DAL_MMHUBBUB_DCN35 = $(addprefix $(AMDDALPATH)/dc/mmhubbub/dcn35/,$(MMHUBBUB_DCN35))
AMD_DISPLAY_FILES += $(AMD_DAL_MMHUBBUB_DCN35)
+
+
+###############################################################################
+# DCN401
+###############################################################################
+MMHUBBUB_DCN401 = dcn401_mmhubbub.o
+
+AMD_DAL_MMHUBBUB_DCN401 = $(addprefix $(AMDDALPATH)/dc/mmhubbub/dcn401/,$(MMHUBBUB_DCN401))
+
+AMD_DISPLAY_FILES += $(AMD_DAL_MMHUBBUB_DCN401)
endif
###############################################################################
diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c
index 6b6f80a3bfd5..5ce787632fe9 100644
--- a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c
+++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c
@@ -73,7 +73,7 @@
* the bufmgr status can show the progress of write back, can be used for debug purpose
*/
-static void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb,
+void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb,
struct mcif_warmup_params *params)
{
struct dcn30_mmhubbub *mcif_wb30 = TO_DCN30_MMHUBBUB(mcif_wb);
@@ -100,7 +100,7 @@ static void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb,
REG_UPDATE(MMHUBBUB_WARMUP_CONTROL_STATUS, MMHUBBUB_WARMUP_EN, false);
}
-static void mmhubbub32_config_mcif_buf(struct mcif_wb *mcif_wb,
+void mmhubbub32_config_mcif_buf(struct mcif_wb *mcif_wb,
struct mcif_buf_params *params,
unsigned int dest_height)
{
diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h
index ef15b4f1f6b9..682f3bba6d50 100644
--- a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h
+++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h
@@ -200,6 +200,11 @@
SF(MMHUBBUB_WARMUP_CONTROL_STATUS, MMHUBBUB_WARMUP_SW_INT_ACK, mask_sh),\
SF(MMHUBBUB_WARMUP_CONTROL_STATUS, MMHUBBUB_WARMUP_INC_ADDR, mask_sh)
+void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb,
+ struct mcif_warmup_params *params);
+void mmhubbub32_config_mcif_buf(struct mcif_wb *mcif_wb,
+ struct mcif_buf_params *params,
+ unsigned int dest_height);
void dcn32_mmhubbub_construct(struct dcn30_mmhubbub *mcif_wb30,
struct dc_context *ctx,
diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c
new file mode 100644
index 000000000000..d2845acb74c8
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright 2026 Advanced Micro Devices, Inc.
+
+#include "dcn401_mmhubbub.h"
+#include "reg_helper.h"
+
+#define REG(reg) \
+ ((const struct dcn35_mmhubbub_registers *)(mcif_wb30->mcif_wb_regs)) \
+ ->reg
+
+#define CTX mcif_wb30->base.ctx
+
+#undef FN
+#define FN(reg_name, field_name) \
+ ((const struct dcn401_mmhubbub_shift *)(mcif_wb30->mcif_wb_shift)) \
+ ->field_name, \
+ ((const struct dcn401_mmhubbub_mask *)(mcif_wb30->mcif_wb_mask)) \
+ ->field_name
+
+static void mmhubbub401_config_mcif_arb(struct mcif_wb *mcif_wb,
+ struct mcif_arb_params *params)
+{
+ struct dcn30_mmhubbub *mcif_wb30 = TO_DCN30_MMHUBBUB(mcif_wb);
+
+ /* Programmed by the video driver based on the CRTC timing (for DWB) */
+ REG_UPDATE(MCIF_WB_ARBITRATION_CONTROL,
+ MCIF_WB_TIME_PER_PIXEL, params->dcn4x.inst_regs.time_per_pixel);
+
+ /*
+ * Programming DWB watermark.
+ * Watermark to generate urgent in MCIF_WB_CLI, value is determined by MCIF_WB_CLI_WATERMARK_MASK.
+ * Program in ns. A formula will be provided in the pseudo code to calculate the value.
+ */
+ /* Program urgent_watermarkA */
+ REG_UPDATE_2(MCIF_WB_WATERMARK,
+ MCIF_WB_CLI_WATERMARK_MASK, 0x0,
+ MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent);
+ /* Program urgent_watermarkB */
+ REG_UPDATE_2(MCIF_WB_WATERMARK,
+ MCIF_WB_CLI_WATERMARK_MASK, 0x1,
+ MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent);
+ /* Program urgent_watermarkC */
+ REG_UPDATE_2(MCIF_WB_WATERMARK,
+ MCIF_WB_CLI_WATERMARK_MASK, 0x2,
+ MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent);
+ /* Program urgent_watermarkD */
+ REG_UPDATE_2(MCIF_WB_WATERMARK,
+ MCIF_WB_CLI_WATERMARK_MASK, 0x3,
+ MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent);
+
+ /* Programming UCLK P-State watermark */
+ /* Program nbp_state_change_watermarkA */
+ REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK,
+ NB_PSTATE_CHANGE_WATERMARK_MASK, 0x0,
+ NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0,
+ NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate);
+ /* Program nbp_state_change_watermarkB */
+ REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK,
+ NB_PSTATE_CHANGE_WATERMARK_MASK, 0x1,
+ NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0,
+ NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate);
+ /* Program nbp_state_change_watermarkC */
+ REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK,
+ NB_PSTATE_CHANGE_WATERMARK_MASK, 0x2,
+ NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0,
+ NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate);
+ /* Program nbp_state_change_watermarkD */
+ REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK,
+ NB_PSTATE_CHANGE_WATERMARK_MASK, 0x3,
+ NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0,
+ NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate);
+
+ /* Programming FCLK P-State watermark */
+ /* Program nbp_state_change_watermarkA */
+ REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK,
+ NB_PSTATE_CHANGE_WATERMARK_MASK, 0x0,
+ NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1,
+ NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].temp_read_or_ppt);
+ /* Program nbp_state_change_watermarkB */
+ REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK,
+ NB_PSTATE_CHANGE_WATERMARK_MASK, 0x1,
+ NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1,
+ NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].temp_read_or_ppt);
+ /* Program nbp_state_change_watermarkC */
+ REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK,
+ NB_PSTATE_CHANGE_WATERMARK_MASK, 0x2,
+ NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1,
+ NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].fclk_pstate);
+ /* Program nbp_state_change_watermarkD */
+ REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK,
+ NB_PSTATE_CHANGE_WATERMARK_MASK, 0x3,
+ NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1,
+ NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].fclk_pstate);
+
+ /* Program max_scaled_time */
+ REG_UPDATE(MULTI_LEVEL_QOS_CTRL,
+ MAX_SCALED_TIME_TO_URGENT, params->dcn4x.inst_regs.max_scaled_time_ns);
+
+ /* Program slice_lines */
+ REG_UPDATE(MCIF_WB_BUFMGR_VCE_CONTROL,
+ MCIF_WB_BUFMGR_SLICE_SIZE, params->dcn4x.inst_regs.slice_lines);
+
+ /* Set arbitration unit for Luma/Chroma */
+ /* arb_unit=2 should be chosen for more efficiency */
+ /* Arbitration size, 0: 2048 bytes 1: 4096 bytes 2: 8192 Bytes */
+ REG_UPDATE(MCIF_WB_ARBITRATION_CONTROL,
+ MCIF_WB_CLIENT_ARBITRATION_SLICE, params->dcn4x.inst_regs.arbitration_slice);
+}
+
+static const struct mcif_wb_funcs dcn401_mmhubbub_funcs = {
+ .warmup_mcif = mmhubbub32_warmup_mcif,
+ .enable_mcif = mmhubbub2_enable_mcif,
+ .disable_mcif = mmhubbub2_disable_mcif,
+ .config_mcif_buf = mmhubbub32_config_mcif_buf,
+ .config_mcif_arb = mmhubbub401_config_mcif_arb,
+ .config_mcif_irq = mmhubbub2_config_mcif_irq,
+ .dump_frame = mcifwb2_dump_frame,
+};
+
+void dcn401_mmhubbub_construct(struct dcn30_mmhubbub *mcif_wb30,
+ struct dc_context *ctx,
+ const struct dcn35_mmhubbub_registers *mcif_wb_regs,
+ const struct dcn401_mmhubbub_shift *mcif_wb_shift,
+ const struct dcn401_mmhubbub_mask *mcif_wb_mask,
+ int inst)
+{
+ mcif_wb30->base.ctx = ctx;
+
+ mcif_wb30->base.inst = inst;
+ mcif_wb30->base.funcs = &dcn401_mmhubbub_funcs;
+ mcif_wb30->mcif_wb_regs = (const struct dcn30_mmhubbub_registers *)mcif_wb_regs;
+ mcif_wb30->mcif_wb_shift = (const struct dcn30_mmhubbub_shift *)mcif_wb_shift;
+ mcif_wb30->mcif_wb_mask = (const struct dcn30_mmhubbub_mask *)mcif_wb_mask;
+}
diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h
new file mode 100644
index 000000000000..fbc69a210fa7
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: MIT */
+/* Copyright 2026 Advanced Micro Devices, Inc. */
+
+
+#ifndef __DCN401_MMHUBBUB_H
+#define __DCN401_MMHUBBUB_H
+
+#include "mcif_wb.h"
+#include "dcn32/dcn32_mmhubbub.h"
+#include "dcn35/dcn35_mmhubbub.h"
+
+#define MCIF_WB_REG_VARIABLE_LIST_DCN4_01 \
+ MCIF_WB_REG_VARIABLE_LIST_DCN3_5;
+
+#define MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(mask_sh) \
+ MCIF_WB_COMMON_MASK_SH_LIST_DCN3_5(mask_sh), \
+ SF(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, NB_PSTATE_CHANGE_WATERMARK_TYPE, mask_sh)
+
+#define MCIF_WB_REG_FIELD_LIST_DCN4_01(type) \
+ struct { \
+ MCIF_WB_REG_FIELD_LIST_DCN3_5(type); \
+ type NB_PSTATE_CHANGE_WATERMARK_TYPE; \
+ }
+
+struct dcn401_mmhubbub_mask {
+ MCIF_WB_REG_FIELD_LIST_DCN4_01(uint32_t);
+};
+
+struct dcn401_mmhubbub_shift {
+ MCIF_WB_REG_FIELD_LIST_DCN4_01(uint8_t);
+};
+
+void dcn401_mmhubbub_construct(struct dcn30_mmhubbub *mcif_wb30,
+ struct dc_context *ctx,
+ const struct dcn35_mmhubbub_registers *mcif_wb_regs,
+ const struct dcn401_mmhubbub_shift *mcif_wb_shift,
+ const struct dcn401_mmhubbub_mask *mcif_wb_mask,
+ int inst);
+
+#endif // __DCN401_MMHUBBUB_H
diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h
index 0dd1f0d2eb88..0001903a04ec 100644
--- a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h
+++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h
@@ -8,6 +8,7 @@
#include "mcif_wb.h"
#include "dcn32/dcn32_mmhubbub.h"
#include "dcn35/dcn35_mmhubbub.h"
+#include "dcn401/dcn401_mmhubbub.h"
void dcn42_mmhubbub_set_fgcg(struct dcn30_mmhubbub *mcif_wb30, bool enabled);
#endif // __DCN42_MMHUBBUB_H
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c
index e3ee792fb2e8..f7ba0509e6bd 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c
@@ -16,6 +16,7 @@
#include "dcn30/dcn30_resource.h"
#include "dcn32/dcn32_resource.h"
#include "dcn321/dcn321_resource.h"
+#include "dcn35/dcn35_resource.h"
#include "dcn10/dcn10_ipp.h"
#include "dcn401/dcn401_hubbub.h"
@@ -58,7 +59,7 @@
#include "dcn31/dcn31_panel_cntl.h"
#include "dcn30/dcn30_dwb.h"
-#include "dcn32/dcn32_mmhubbub.h"
+#include "dcn401/dcn401_mmhubbub.h"
#include "dcn/dcn_4_1_0_offset.h"
#include "dcn/dcn_4_1_0_sh_mask.h"
@@ -435,17 +436,17 @@ static const struct dcn30_dwbc_mask dwbc401_mask = {
};
-#define mcif_wb_regs_dcn3_init(id)\
- MCIF_WB_COMMON_REG_LIST_DCN32_RI(id)
+#define mcif_wb_regs_dcn401_init(id) \
+ MCIF_WB_COMMON_REG_LIST_DCN4_01_RI(id)
-static struct dcn30_mmhubbub_registers mcif_wb30_regs[1];
+static struct dcn35_mmhubbub_registers mcif_wb401_regs[1];
-static const struct dcn30_mmhubbub_shift mcif_wb30_shift = {
- MCIF_WB_COMMON_MASK_SH_LIST_DCN32(__SHIFT)
+static const struct dcn401_mmhubbub_shift mcif_wb401_shift = {
+ MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(__SHIFT)
};
-static const struct dcn30_mmhubbub_mask mcif_wb30_mask = {
- MCIF_WB_COMMON_MASK_SH_LIST_DCN32(_MASK)
+static const struct dcn401_mmhubbub_mask mcif_wb401_mask = {
+ MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(_MASK)
};
#define dsc_regs_init(id)\
@@ -1704,13 +1705,13 @@ static bool dcn401_mmhubbub_create(struct dc_context *ctx, struct resource_pool
}
#undef REG_STRUCT
-#define REG_STRUCT mcif_wb30_regs
- mcif_wb_regs_dcn3_init(0);
+#define REG_STRUCT mcif_wb401_regs
+ mcif_wb_regs_dcn401_init(0);
- dcn32_mmhubbub_construct(mcif_wb30, ctx,
- &mcif_wb30_regs[i],
- &mcif_wb30_shift,
- &mcif_wb30_mask,
+ dcn401_mmhubbub_construct(mcif_wb30, ctx,
+ &mcif_wb401_regs[i],
+ &mcif_wb401_shift,
+ &mcif_wb401_mask,
i);
pool->mcif_wb[i] = &mcif_wb30->base;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h
index ce96fe1ddb7c..42a0986d6fe2 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h
@@ -7,6 +7,7 @@
#include "core_types.h"
#include "dcn32/dcn32_resource.h"
+#include "dcn35/dcn35_resource.h"
#include "dcn401/dcn401_hubp.h"
#define TO_DCN401_RES_POOL(pool)\
@@ -660,4 +661,7 @@ int dcn401_get_power_profile(const struct dc_state *context);
SR(SYMCLKC_CLOCK_ENABLE),\
SR(SYMCLKD_CLOCK_ENABLE)
+#define MCIF_WB_COMMON_REG_LIST_DCN4_01_RI(inst) \
+ MCIF_WB_COMMON_REG_LIST_DCN3_5_RI(inst)
+
#endif /* _DCN401_RESOURCE_H_ */
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 547a0b816539..67673a9e5155 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
@@ -400,16 +400,18 @@ static const struct dcn30_dwbc_shift dwbc401_shift = {
static const struct dcn30_dwbc_mask dwbc401_mask = {
DWBC_COMMON_MASK_SH_LIST_DCN30(_MASK)};
-#define mcif_wb_regs_dcn3_init(id) \
- MCIF_WB_COMMON_REG_LIST_DCN3_5_RI(id)
+#define mcif_wb_regs_dcn401_init(id) \
+ MCIF_WB_COMMON_REG_LIST_DCN4_01_RI(id)
-static struct dcn35_mmhubbub_registers mcif_wb35_regs[1];
+static struct dcn35_mmhubbub_registers mcif_wb401_regs[1];
-static const struct dcn35_mmhubbub_shift mcif_wb35_shift = {
- MCIF_WB_COMMON_MASK_SH_LIST_DCN3_5(__SHIFT)};
+static const struct dcn401_mmhubbub_shift mcif_wb401_shift = {
+ MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(__SHIFT)
+};
-static const struct dcn35_mmhubbub_mask mcif_wb35_mask = {
- MCIF_WB_COMMON_MASK_SH_LIST_DCN3_5(_MASK)};
+static const struct dcn401_mmhubbub_mask mcif_wb401_mask = {
+ MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(_MASK)
+};
#define dsc_regs_init(id) \
DSC_REG_LIST_DCN401_RI(id)
@@ -1768,13 +1770,13 @@ static bool dcn42_mmhubbub_create(struct dc_context *ctx, struct resource_pool *
}
#undef REG_STRUCT
-#define REG_STRUCT mcif_wb35_regs
- mcif_wb_regs_dcn3_init(0);
+#define REG_STRUCT mcif_wb401_regs
+ mcif_wb_regs_dcn401_init(0);
- dcn35_mmhubbub_construct(mcif_wb30, ctx,
- &mcif_wb35_regs[i],
- &mcif_wb35_shift,
- &mcif_wb35_mask,
+ dcn401_mmhubbub_construct(mcif_wb30, ctx,
+ &mcif_wb401_regs[i],
+ &mcif_wb401_shift,
+ &mcif_wb401_mask,
i);
dcn42_mmhubbub_init(mcif_wb30, ctx);
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 03/49] drm/amd/display: Fix force FRL rate debug setting
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 01/49] drm/amd/display: share common DM KUnit helpers Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 02/49] drm/amd/display: Port DCN4+ MCIF ARB programming to new format Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 04/49] drm/amd/display: Add KUnit tests for link_lock and psp SRM helpers Fangzhi Zuo
` (45 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Relja Vojvodic, Chris Park
From: Relja Vojvodic <Relja.Vojvodic@amd.com>
[Why & How]
- force FRL rate debug bit was not correctly forcing requested FRL rate
Reviewed-by: Chris Park <chris.park@amd.com>
Signed-off-by: Relja Vojvodic <Relja.Vojvodic@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
drivers/gpu/drm/amd/display/dc/core/dc_stream.c | 4 +++-
drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c
index 25f904eccb10..cdcf140bc1bb 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c
@@ -100,7 +100,9 @@ void update_stream_signal(struct dc_stream_state *stream, struct dc_sink *sink)
if (stream->link->frl_flags.force_frl_always ||
stream->link->frl_flags.force_frl_max
- || stream->link->frl_flags.force_frl_dsc)
+ || stream->link->frl_flags.force_frl_dsc
+ || (stream->link->frl_flags.force_frl_rate != 0 &&
+ stream->link->frl_flags.force_frl_rate != 0xF))
stream->signal = SIGNAL_TYPE_HDMI_FRL;
}
}
diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c
index c35074177a36..2afdfc113675 100644
--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c
@@ -1090,7 +1090,7 @@ void hdmi_frl_decide_link_settings(struct dc_stream_state *stream,
&stream->link->frl_verified_link_cap);
if (stream->link->frl_flags.force_frl_rate != 0 &&
- stream->link->frl_flags.force_frl_rate < stream->link->frl_verified_link_cap.frl_link_rate) {
+ stream->link->frl_flags.force_frl_rate <= stream->link->frl_verified_link_cap.frl_link_rate) {
switch (stream->link->frl_flags.force_frl_rate) {
case HDMI_FRL_LINK_RATE_3GBPS:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_3GBPS;
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 04/49] drm/amd/display: Add KUnit tests for link_lock and psp SRM helpers
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (2 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 03/49] drm/amd/display: Fix force FRL rate debug setting Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 05/49] drm/amd/display: Add KUnit tests for HDCP display helpers Fangzhi Zuo
` (44 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Cover link_lock() across all links (lock then unlock) and the
max_link == 0 no-op path, checking each per-link mutex ends in
the expected state.
Also cover the psp_get_srm() and psp_set_srm() guard paths when
the HDCP TA context is uninitialized: psp_get_srm() returns NULL
and psp_set_srm() returns -EINVAL, both leaving their output
parameters untouched. The post-guard paths invoke real PSP
firmware and are not unit-testable in UML.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 12 +-
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 4 +
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 127 ++++++++++++++++++
3 files changed, 140 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index 0dee603ecfaa..3b5e0e64adde 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -106,7 +106,8 @@ static bool lp_atomic_write_poll_read_aux(
return dm_atomic_write_poll_read_aux(link, write, poll, read, poll_timeout_us, poll_mask_msb);
}
-static uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size)
+STATIC_IFN_KUNIT
+uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size)
{
struct ta_hdcp_shared_memory *hdcp_cmd;
@@ -129,8 +130,10 @@ static uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint
return hdcp_cmd->out_msg.hdcp_get_srm.srm_buf;
}
+EXPORT_IF_KUNIT(psp_get_srm);
-static int psp_set_srm(struct psp_context *psp,
+STATIC_IFN_KUNIT
+int psp_set_srm(struct psp_context *psp,
u8 *srm, uint32_t srm_size, uint32_t *srm_version)
{
struct ta_hdcp_shared_memory *hdcp_cmd;
@@ -157,6 +160,7 @@ static int psp_set_srm(struct psp_context *psp,
*srm_version = hdcp_cmd->out_msg.hdcp_set_srm.srm_version;
return 0;
}
+EXPORT_IF_KUNIT(psp_set_srm);
STATIC_IFN_KUNIT
void process_output(struct hdcp_workqueue *hdcp_work)
@@ -245,7 +249,8 @@ void hdcp_get_link_display_adjustments(
}
EXPORT_IF_KUNIT(hdcp_get_link_display_adjustments);
-static void link_lock(struct hdcp_workqueue *work, bool lock)
+STATIC_IFN_KUNIT
+void link_lock(struct hdcp_workqueue *work, bool lock)
{
int i = 0;
@@ -256,6 +261,7 @@ static void link_lock(struct hdcp_workqueue *work, bool lock)
mutex_unlock(&work[i].mutex);
}
}
+EXPORT_IF_KUNIT(link_lock);
STATIC_IFN_KUNIT
void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
index a82a20b80518..fb66f48d7297 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
@@ -44,6 +44,7 @@ struct mod_hdcp_link;
struct mod_hdcp_display;
struct cp_psp;
struct amdgpu_device;
+struct psp_context;
struct hdcp_workqueue {
struct work_struct cpirq_work;
@@ -113,6 +114,9 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work,
unsigned int conn_index,
bool enable_encryption);
void event_property_update(struct work_struct *work);
+void link_lock(struct hdcp_workqueue *work, bool lock);
+uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size);
+int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version);
#endif
#endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index dbcff92672e6..43c7efcdd22c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -8,6 +8,7 @@
#include <kunit/test.h>
#include <linux/workqueue.h>
+#include "amdgpu.h"
#include "amdgpu_dm_hdcp.h"
static void dummy_work_fn(struct work_struct *work) {}
@@ -632,6 +633,126 @@ static void dm_test_hdcp_create_workqueue_zero_max_links_returns_null(struct kun
/* End of tests for hdcp_create_workqueue() */
+/* Tests for link_lock() */
+
+/**
+ * dm_test_link_lock_locks_and_unlocks_all_links - lock/unlock spans every link
+ * @test: KUnit test context
+ *
+ * link_lock() should acquire the mutex of every entry from 0 to max_link
+ * when locking, and release all of them when unlocking. A subsequent
+ * lock/unlock cycle must succeed, proving the mutexes were left released.
+ */
+static void dm_test_link_lock_locks_and_unlocks_all_links(struct kunit *test)
+{
+ const int num_links = 3;
+ struct hdcp_workqueue *work;
+ int i;
+
+ work = kunit_kcalloc(test, num_links, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+
+ /* max_link is read from the first element. */
+ work[0].max_link = num_links;
+ for (i = 0; i < num_links; i++)
+ mutex_init(&work[i].mutex);
+
+ link_lock(work, true);
+ for (i = 0; i < num_links; i++)
+ KUNIT_EXPECT_TRUE(test, mutex_is_locked(&work[i].mutex));
+
+ link_lock(work, false);
+ for (i = 0; i < num_links; i++)
+ KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work[i].mutex));
+
+ /* Mutexes must be re-acquirable after being released. */
+ link_lock(work, true);
+ for (i = 0; i < num_links; i++)
+ KUNIT_EXPECT_TRUE(test, mutex_is_locked(&work[i].mutex));
+ link_lock(work, false);
+}
+
+/**
+ * dm_test_link_lock_zero_links_is_noop - zero max_link touches no mutexes
+ * @test: KUnit test context
+ *
+ * When max_link is zero, link_lock() must not touch any mutex and simply
+ * return, leaving the (single) entry's mutex unlocked.
+ */
+static void dm_test_link_lock_zero_links_is_noop(struct kunit *test)
+{
+ struct hdcp_workqueue *work;
+
+ work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+
+ mutex_init(&work->mutex);
+ work->max_link = 0;
+
+ link_lock(work, true);
+
+ KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex));
+}
+
+/* End of tests for link_lock() */
+
+/* Tests for psp_get_srm() and psp_set_srm() */
+
+/**
+ * dm_test_psp_get_srm_uninitialized_returns_null - GET fails when TA not initialized
+ * @test: KUnit test context
+ *
+ * When the HDCP TA context is not initialized, psp_get_srm() must take the
+ * guard path and return NULL without touching the output parameters or
+ * invoking the (real) firmware path.
+ */
+static void dm_test_psp_get_srm_uninitialized_returns_null(struct kunit *test)
+{
+ struct psp_context *psp;
+ uint32_t srm_version = 0xdead;
+ uint32_t srm_size = 0xbeef;
+ uint8_t *srm;
+
+ psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, psp);
+
+ /* kzalloc leaves hdcp_context.context.initialized == false */
+ srm = psp_get_srm(psp, &srm_version, &srm_size);
+
+ KUNIT_EXPECT_PTR_EQ(test, srm, NULL);
+ /* Output parameters must be left untouched on the guard path. */
+ KUNIT_EXPECT_EQ(test, srm_version, 0xdead);
+ KUNIT_EXPECT_EQ(test, srm_size, 0xbeef);
+}
+
+/**
+ * dm_test_psp_set_srm_uninitialized_returns_einval - SET fails when TA not initialized
+ * @test: KUnit test context
+ *
+ * When the HDCP TA context is not initialized, psp_set_srm() must take the
+ * guard path and return -EINVAL without updating srm_version or invoking
+ * the (real) firmware path.
+ */
+static void dm_test_psp_set_srm_uninitialized_returns_einval(struct kunit *test)
+{
+ struct psp_context *psp;
+ uint32_t srm_version = 0xdead;
+ u8 srm_buf[4] = {0};
+ int ret;
+
+ psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, psp);
+
+ /* kzalloc leaves hdcp_context.context.initialized == false */
+ ret = psp_set_srm(psp, srm_buf, sizeof(srm_buf), &srm_version);
+
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+ /* srm_version must be left untouched on the guard path. */
+ KUNIT_EXPECT_EQ(test, srm_version, 0xdead);
+}
+
+/* End of tests for psp_get_srm() and psp_set_srm() */
+
static struct kunit_case dm_hdcp_test_cases[] = {
/* hdcp_get_content_protection_from_status() */
KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired),
@@ -663,6 +784,12 @@ static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate),
/* hdcp_create_workqueue() */
KUNIT_CASE(dm_test_hdcp_create_workqueue_zero_max_links_returns_null),
+ /* link_lock() */
+ KUNIT_CASE(dm_test_link_lock_locks_and_unlocks_all_links),
+ KUNIT_CASE(dm_test_link_lock_zero_links_is_noop),
+ /* psp_get_srm() / psp_set_srm() */
+ KUNIT_CASE(dm_test_psp_get_srm_uninitialized_returns_null),
+ KUNIT_CASE(dm_test_psp_set_srm_uninitialized_returns_einval),
{}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 05/49] drm/amd/display: Add KUnit tests for HDCP display helpers
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (3 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 04/49] drm/amd/display: Add KUnit tests for link_lock and psp SRM helpers Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 06/49] drm/amd/display: Add KUnit tests for event_callback Fangzhi Zuo
` (43 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Cover the HDCP display lifecycle helpers:
- hdcp_update_display() registers the connector and, on the
disable path, resets the per-link encryption status to
HDCP_OFF.
- hdcp_remove_display() reverts an ENABLED connector back to
DESIRED and clears the per-link entry, including the NULL
connector-state path.
- hdcp_reset_display() clears the encryption status and the
connector for every link.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 6 +-
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 2 +
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 207 ++++++++++++++++++
3 files changed, 214 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index 3b5e0e64adde..f786c0a85425 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -317,8 +317,10 @@ void hdcp_update_display(struct hdcp_workqueue *hdcp_work,
process_output(hdcp_w);
}
+EXPORT_IF_KUNIT(hdcp_update_display);
-static void hdcp_remove_display(struct hdcp_workqueue *hdcp_work,
+STATIC_IFN_KUNIT
+void hdcp_remove_display(struct hdcp_workqueue *hdcp_work,
unsigned int link_index,
struct amdgpu_dm_connector *aconnector)
{
@@ -348,6 +350,7 @@ static void hdcp_remove_display(struct hdcp_workqueue *hdcp_work,
}
process_output(hdcp_w);
}
+EXPORT_IF_KUNIT(hdcp_remove_display);
void hdcp_reset_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index)
{
@@ -371,6 +374,7 @@ void hdcp_reset_display(struct hdcp_workqueue *hdcp_work, unsigned int link_inde
process_output(hdcp_w);
}
+EXPORT_IF_KUNIT(hdcp_reset_display);
void hdcp_handle_cpirq(struct hdcp_workqueue *hdcp_work, unsigned int link_index)
{
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
index fb66f48d7297..69a50e081c56 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
@@ -115,6 +115,8 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work,
bool enable_encryption);
void event_property_update(struct work_struct *work);
void link_lock(struct hdcp_workqueue *work, bool lock);
+void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index,
+ struct amdgpu_dm_connector *aconnector);
uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size);
int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version);
#endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index 43c7efcdd22c..e6d03199187b 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -9,6 +9,7 @@
#include <linux/workqueue.h>
#include "amdgpu.h"
+#include "amdgpu_dm.h"
#include "amdgpu_dm_hdcp.h"
static void dummy_work_fn(struct work_struct *work) {}
@@ -753,6 +754,206 @@ static void dm_test_psp_set_srm_uninitialized_returns_einval(struct kunit *test)
/* End of tests for psp_get_srm() and psp_set_srm() */
+/*
+ * Tests for hdcp_update_display() / hdcp_remove_display() /
+ * hdcp_reset_display().
+ */
+
+/**
+ * alloc_test_workqueue_locked - workqueue with dworks and mutex initialised
+ * @test: KUnit test context for managed allocation
+ *
+ * Allocates a minimal hdcp_workqueue with its mutex and the three delayed
+ * works initialised, as required by the guard(mutex) and process_output()
+ * usage in the display update/remove/reset helpers.
+ */
+static struct hdcp_workqueue *alloc_test_workqueue_locked(struct kunit *test)
+{
+ struct hdcp_workqueue *work;
+
+ work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+
+ mutex_init(&work->mutex);
+ INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn);
+ INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn);
+ INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn);
+
+ return work;
+}
+
+/**
+ * alloc_test_connector - minimal amdgpu_dm_connector for display tests
+ * @test: KUnit test context for managed allocation
+ * @index: drm connector index to assign
+ *
+ * Allocates an amdgpu_dm_connector with an initialised connector refcount
+ * (so drm_connector_get() is valid) and a dc_link/dc pair. The connector's
+ * free_cb is left NULL so drm_connector_put() is a safe no-op that never
+ * releases the object.
+ */
+static struct amdgpu_dm_connector *alloc_test_connector(struct kunit *test,
+ unsigned int index)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct dc_link *dc_link;
+ struct dc *dc;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ dc_link = kunit_kzalloc(test, sizeof(*dc_link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dc_link);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dc);
+
+ kref_init(&aconnector->base.base.refcount);
+ aconnector->base.index = index;
+ dc_link->dc = dc;
+ aconnector->dc_link = dc_link;
+
+ return aconnector;
+}
+
+/**
+ * dm_test_hdcp_update_display_enable_registers_connector - enable stores the connector
+ * @test: KUnit test context
+ *
+ * hdcp_update_display() with enable_encryption=true should register the
+ * connector in the per-link aconnector array at its connector index. The
+ * hdcp has no active display, so mod_hdcp_update_display() is effectively a
+ * no-op and the call must not touch encryption_status.
+ */
+static void dm_test_hdcp_update_display_enable_registers_connector(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_locked(test);
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0);
+
+ work->srm_size = 0;
+
+ hdcp_update_display(work, 0, aconnector, DRM_MODE_HDCP_CONTENT_TYPE0, true);
+
+ KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], aconnector);
+ KUNIT_EXPECT_EQ(test, work->encryption_status[0],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF);
+
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+}
+
+/**
+ * dm_test_hdcp_update_display_disable_sets_status_off - disable clears status
+ * @test: KUnit test context
+ *
+ * hdcp_update_display() with enable_encryption=false should register the
+ * connector and reset the connector's encryption_status entry to HDCP_OFF
+ * via hdcp_update_display_encryption_control().
+ */
+static void dm_test_hdcp_update_display_disable_sets_status_off(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_locked(test);
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 2);
+
+ work->encryption_status[2] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON;
+
+ hdcp_update_display(work, 0, aconnector, DRM_MODE_HDCP_CONTENT_TYPE0, false);
+
+ KUNIT_EXPECT_PTR_EQ(test, work->aconnector[2], aconnector);
+ KUNIT_EXPECT_EQ(test, work->encryption_status[2],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF);
+
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+}
+
+/**
+ * dm_test_hdcp_remove_display_enabled_resets_cp - ENABLED CP reverts to DESIRED
+ * @test: KUnit test context
+ *
+ * hdcp_remove_display() must revert a connector whose content_protection is
+ * ENABLED back to DESIRED and clear the per-link aconnector entry.
+ */
+static void dm_test_hdcp_remove_display_enabled_resets_cp(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_locked(test);
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 1);
+ struct drm_connector_state *conn_state;
+
+ conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, conn_state);
+
+ conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED;
+ aconnector->base.state = conn_state;
+ work->aconnector[1] = aconnector;
+
+ hdcp_remove_display(work, 0, aconnector);
+
+ KUNIT_EXPECT_EQ(test, conn_state->content_protection,
+ DRM_MODE_CONTENT_PROTECTION_DESIRED);
+ KUNIT_EXPECT_PTR_EQ(test, work->aconnector[1], NULL);
+
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+}
+
+/**
+ * dm_test_hdcp_remove_display_null_state_clears_connector - NULL state skips CP change
+ * @test: KUnit test context
+ *
+ * When the connector has no drm_connector_state, hdcp_remove_display() must
+ * skip the content_protection update path and still clear the per-link
+ * aconnector entry.
+ */
+static void dm_test_hdcp_remove_display_null_state_clears_connector(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_locked(test);
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0);
+
+ aconnector->base.state = NULL;
+ work->aconnector[0] = aconnector;
+
+ hdcp_remove_display(work, 0, aconnector);
+
+ KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL);
+
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+}
+
+/**
+ * dm_test_hdcp_reset_display_clears_all_state - reset clears status and connectors
+ * @test: KUnit test context
+ *
+ * hdcp_reset_display() must reset every connector's encryption_status to
+ * HDCP_OFF and clear all per-link aconnector entries.
+ */
+static void dm_test_hdcp_reset_display_clears_all_state(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_locked(test);
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 4);
+
+ work->encryption_status[4] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON;
+ work->aconnector[4] = aconnector;
+
+ hdcp_reset_display(work, 0);
+
+ KUNIT_EXPECT_EQ(test, work->encryption_status[4],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF);
+ KUNIT_EXPECT_PTR_EQ(test, work->aconnector[4], NULL);
+
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+}
+
+/*
+ * End of tests for hdcp_update_display() / hdcp_remove_display() /
+ * hdcp_reset_display().
+ */
+
static struct kunit_case dm_hdcp_test_cases[] = {
/* hdcp_get_content_protection_from_status() */
KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired),
@@ -790,6 +991,12 @@ static struct kunit_case dm_hdcp_test_cases[] = {
/* psp_get_srm() / psp_set_srm() */
KUNIT_CASE(dm_test_psp_get_srm_uninitialized_returns_null),
KUNIT_CASE(dm_test_psp_set_srm_uninitialized_returns_einval),
+ /* hdcp_update_display() / hdcp_remove_display() / hdcp_reset_display() */
+ KUNIT_CASE(dm_test_hdcp_update_display_enable_registers_connector),
+ KUNIT_CASE(dm_test_hdcp_update_display_disable_sets_status_off),
+ KUNIT_CASE(dm_test_hdcp_remove_display_enabled_resets_cp),
+ KUNIT_CASE(dm_test_hdcp_remove_display_null_state_clears_connector),
+ KUNIT_CASE(dm_test_hdcp_reset_display_clears_all_state),
{}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 06/49] drm/amd/display: Add KUnit tests for event_callback
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (4 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 05/49] drm/amd/display: Add KUnit tests for HDCP display helpers Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 07/49] drm/amd/display: Add KUnit tests for event_property_validate Fangzhi Zuo
` (42 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Verify event_callback() cancels a pending callback_dwork and then
runs process_output(), which re-arms property_validate_dwork, and
that the work-queue mutex is released on return.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 4 +-
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 1 +
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 78 +++++++++++++++++++
3 files changed, 82 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index f786c0a85425..34f70e517dc6 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -384,7 +384,8 @@ void hdcp_handle_cpirq(struct hdcp_workqueue *hdcp_work, unsigned int link_index
}
EXPORT_IF_KUNIT(hdcp_handle_cpirq);
-static void event_callback(struct work_struct *work)
+STATIC_IFN_KUNIT
+void event_callback(struct work_struct *work)
{
struct hdcp_workqueue *hdcp_work;
@@ -400,6 +401,7 @@ static void event_callback(struct work_struct *work)
process_output(hdcp_work);
}
+EXPORT_IF_KUNIT(event_callback);
STATIC_IFN_KUNIT
void event_property_update(struct work_struct *work)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
index 69a50e081c56..4b8ba743f9d8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
@@ -114,6 +114,7 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work,
unsigned int conn_index,
bool enable_encryption);
void event_property_update(struct work_struct *work);
+void event_callback(struct work_struct *work);
void link_lock(struct hdcp_workqueue *work, bool lock);
void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index,
struct amdgpu_dm_connector *aconnector);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index e6d03199187b..e0386030b35e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -501,6 +501,81 @@ static void dm_test_event_property_update_skips_null_connector(struct kunit *tes
/* End of tests for event_property_update() */
+/* Tests for event_callback() */
+
+/**
+ * alloc_test_workqueue_for_callback - workqueue ready for event_callback()
+ * @test: KUnit test context for managed allocation
+ *
+ * Allocates a minimal hdcp_workqueue with its mutex and the three delayed
+ * works initialised, as required by the guard(mutex), cancel_delayed_work()
+ * and process_output() usage inside event_callback().
+ */
+static struct hdcp_workqueue *alloc_test_workqueue_for_callback(struct kunit *test)
+{
+ struct hdcp_workqueue *work;
+
+ work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+
+ mutex_init(&work->mutex);
+ INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn);
+ INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn);
+ INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn);
+
+ return work;
+}
+
+/**
+ * dm_test_event_callback_cancels_callback_dwork - callback work is cancelled
+ * @test: KUnit test context
+ *
+ * event_callback() must cancel a previously scheduled callback_dwork. With
+ * no active hdcp display, mod_hdcp_process_event() leaves output cleared so
+ * the callback is not requeued and callback_dwork ends up not pending.
+ */
+static void dm_test_event_callback_cancels_callback_dwork(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test);
+
+ /* Pre-schedule callback_dwork with a long delay so it won't fire. */
+ schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000));
+ KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork));
+
+ event_callback(&work->callback_dwork.work);
+
+ KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork));
+
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+}
+
+/**
+ * dm_test_event_callback_schedules_property_validate - process_output() runs
+ * @test: KUnit test context
+ *
+ * event_callback() finishes by calling process_output(), which always
+ * enqueues property_validate_dwork with delay=0. Verifying it is pending
+ * proves event_callback() reached process_output() and released the mutex.
+ */
+static void dm_test_event_callback_schedules_property_validate(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test);
+
+ event_callback(&work->callback_dwork.work);
+
+ KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work));
+ /* Mutex must be released after the guard scope exits. */
+ KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex));
+
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+}
+
+/* End of tests for event_callback() */
+
/* Tests for hdcp_handle_cpirq() */
/**
@@ -977,6 +1052,9 @@ static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_process_output_watchdog_stop_and_needed_requeues),
/* event_property_update() */
KUNIT_CASE(dm_test_event_property_update_skips_null_connector),
+ /* event_callback() */
+ KUNIT_CASE(dm_test_event_callback_cancels_callback_dwork),
+ KUNIT_CASE(dm_test_event_callback_schedules_property_validate),
/* hdcp_handle_cpirq() */
KUNIT_CASE(dm_test_hdcp_handle_cpirq_schedules_work),
KUNIT_CASE(dm_test_hdcp_handle_cpirq_selects_link_index),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 07/49] drm/amd/display: Add KUnit tests for event_property_validate
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (5 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 06/49] drm/amd/display: Add KUnit tests for event_callback Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 08/49] drm/amd/display: Add KUnit tests for watchdog and cpirq events Fangzhi Zuo
` (41 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Cover the per-connector scan in event_property_validate(): NULL,
disconnected and NULL-state connectors are skipped; a changed
encryption status updates the cached value and schedules
property_update_work; and an unchanged status leaves it
untouched.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 4 +-
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 1 +
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 178 ++++++++++++++++++
3 files changed, 182 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index 34f70e517dc6..622d9c275c3a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -465,7 +465,8 @@ void event_property_update(struct work_struct *work)
}
EXPORT_IF_KUNIT(event_property_update);
-static void event_property_validate(struct work_struct *work)
+STATIC_IFN_KUNIT
+void event_property_validate(struct work_struct *work)
{
struct hdcp_workqueue *hdcp_work =
container_of(to_delayed_work(work), struct hdcp_workqueue, property_validate_dwork);
@@ -514,6 +515,7 @@ static void event_property_validate(struct work_struct *work)
}
}
}
+EXPORT_IF_KUNIT(event_property_validate);
static void event_watchdog_timer(struct work_struct *work)
{
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
index 4b8ba743f9d8..57c534efbc71 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
@@ -114,6 +114,7 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work,
unsigned int conn_index,
bool enable_encryption);
void event_property_update(struct work_struct *work);
+void event_property_validate(struct work_struct *work);
void event_callback(struct work_struct *work);
void link_lock(struct hdcp_workqueue *work, bool lock);
void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index e0386030b35e..6f5cae0960e1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -576,6 +576,178 @@ static void dm_test_event_callback_schedules_property_validate(struct kunit *tes
/* End of tests for event_callback() */
+/* Tests for event_property_validate() */
+
+/**
+ * alloc_test_workqueue_for_property_validate - workqueue for validate tests
+ * @test: KUnit test context for managed allocation
+ *
+ * Allocates a minimal hdcp_workqueue with its mutex and property_update_work
+ * initialised, as required by the guard(mutex) and schedule_work() usage
+ * inside event_property_validate().
+ */
+static struct hdcp_workqueue *alloc_test_workqueue_for_property_validate(struct kunit *test)
+{
+ struct hdcp_workqueue *work;
+
+ work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+
+ mutex_init(&work->mutex);
+ INIT_WORK(&work->property_update_work, dummy_work_fn);
+
+ return work;
+}
+
+/**
+ * alloc_validate_connector - connector for event_property_validate() tests
+ * @test: KUnit test context for managed allocation
+ * @index: drm connector index to assign
+ * @status: drm connector detection status to assign
+ * @with_state: whether to attach a zeroed drm_connector_state
+ *
+ * Allocates an amdgpu_dm_connector sufficient for the traversal in
+ * event_property_validate(). mod_hdcp_query_display() has no active display
+ * so it returns early, leaving the pre-set HDCP_OFF query untouched.
+ */
+static struct amdgpu_dm_connector *alloc_validate_connector(struct kunit *test,
+ unsigned int index,
+ enum drm_connector_status status,
+ bool with_state)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->base.index = index;
+ aconnector->base.status = status;
+
+ if (with_state) {
+ struct drm_connector_state *conn_state;
+
+ conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, conn_state);
+ aconnector->base.state = conn_state;
+ }
+
+ return aconnector;
+}
+
+/**
+ * dm_test_event_property_validate_skips_null_connector - null entries ignored
+ * @test: KUnit test context
+ *
+ * With every aconnector entry NULL, event_property_validate() must not
+ * schedule property_update_work and must leave encryption_status untouched.
+ */
+static void dm_test_event_property_validate_skips_null_connector(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test);
+
+ work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON;
+
+ event_property_validate(&work->property_validate_dwork.work);
+
+ KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work));
+ KUNIT_EXPECT_EQ(test, work->encryption_status[0],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON);
+}
+
+/**
+ * dm_test_event_property_validate_skips_disconnected - disconnected is skipped
+ * @test: KUnit test context
+ *
+ * A connector whose status is not connector_status_connected must be
+ * skipped, leaving encryption_status unchanged and no work scheduled.
+ */
+static void dm_test_event_property_validate_skips_disconnected(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test);
+
+ work->aconnector[1] = alloc_validate_connector(test, 1,
+ connector_status_disconnected, true);
+ work->encryption_status[1] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON;
+
+ event_property_validate(&work->property_validate_dwork.work);
+
+ KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work));
+ KUNIT_EXPECT_EQ(test, work->encryption_status[1],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON);
+}
+
+/**
+ * dm_test_event_property_validate_skips_null_state - missing state is skipped
+ * @test: KUnit test context
+ *
+ * A connected connector without a drm_connector_state must be skipped
+ * before the query, leaving encryption_status unchanged.
+ */
+static void dm_test_event_property_validate_skips_null_state(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test);
+
+ work->aconnector[2] = alloc_validate_connector(test, 2,
+ connector_status_connected, false);
+ work->encryption_status[2] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON;
+
+ event_property_validate(&work->property_validate_dwork.work);
+
+ KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work));
+ KUNIT_EXPECT_EQ(test, work->encryption_status[2],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON);
+}
+
+/**
+ * dm_test_event_property_validate_updates_on_status_change - change triggers update
+ * @test: KUnit test context
+ *
+ * For a connected connector with state, the query returns HDCP_OFF (no
+ * active hdcp display). When the stored encryption_status differs, it must
+ * be updated to HDCP_OFF and property_update_work must be scheduled.
+ */
+static void dm_test_event_property_validate_updates_on_status_change(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test);
+
+ work->aconnector[3] = alloc_validate_connector(test, 3,
+ connector_status_connected, true);
+ work->encryption_status[3] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON;
+
+ event_property_validate(&work->property_validate_dwork.work);
+
+ KUNIT_EXPECT_EQ(test, work->encryption_status[3],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF);
+ KUNIT_EXPECT_TRUE(test, work_pending(&work->property_update_work));
+
+ cancel_work_sync(&work->property_update_work);
+}
+
+/**
+ * dm_test_event_property_validate_no_update_when_unchanged - no change, no work
+ * @test: KUnit test context
+ *
+ * For a connected connector with state whose stored encryption_status is
+ * already HDCP_OFF (matching the query result), event_property_validate()
+ * must not reschedule property_update_work.
+ */
+static void dm_test_event_property_validate_no_update_when_unchanged(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test);
+
+ work->aconnector[4] = alloc_validate_connector(test, 4,
+ connector_status_connected, true);
+ work->encryption_status[4] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF;
+
+ event_property_validate(&work->property_validate_dwork.work);
+
+ KUNIT_EXPECT_EQ(test, work->encryption_status[4],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF);
+ KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work));
+}
+
+/* End of tests for event_property_validate() */
+
/* Tests for hdcp_handle_cpirq() */
/**
@@ -1055,6 +1227,12 @@ static struct kunit_case dm_hdcp_test_cases[] = {
/* event_callback() */
KUNIT_CASE(dm_test_event_callback_cancels_callback_dwork),
KUNIT_CASE(dm_test_event_callback_schedules_property_validate),
+ /* event_property_validate() */
+ KUNIT_CASE(dm_test_event_property_validate_skips_null_connector),
+ KUNIT_CASE(dm_test_event_property_validate_skips_disconnected),
+ KUNIT_CASE(dm_test_event_property_validate_skips_null_state),
+ KUNIT_CASE(dm_test_event_property_validate_updates_on_status_change),
+ KUNIT_CASE(dm_test_event_property_validate_no_update_when_unchanged),
/* hdcp_handle_cpirq() */
KUNIT_CASE(dm_test_hdcp_handle_cpirq_schedules_work),
KUNIT_CASE(dm_test_hdcp_handle_cpirq_selects_link_index),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 08/49] drm/amd/display: Add KUnit tests for watchdog and cpirq events
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (6 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 07/49] drm/amd/display: Add KUnit tests for event_property_validate Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 09/49] drm/amd/display: Add KUnit tests for hdcp_destroy Fangzhi Zuo
` (40 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Verify event_watchdog_timer() cancels its pending watchdog_dwork
and runs process_output(), and that event_cpirq() runs
process_output() without arming the callback or watchdog timers.
Both release the work-queue mutex on return.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 8 +-
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 2 +
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 132 ++++++++++++++++++
3 files changed, 140 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index 622d9c275c3a..896668c92888 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -517,7 +517,8 @@ void event_property_validate(struct work_struct *work)
}
EXPORT_IF_KUNIT(event_property_validate);
-static void event_watchdog_timer(struct work_struct *work)
+STATIC_IFN_KUNIT
+void event_watchdog_timer(struct work_struct *work)
{
struct hdcp_workqueue *hdcp_work;
@@ -535,8 +536,10 @@ static void event_watchdog_timer(struct work_struct *work)
process_output(hdcp_work);
}
+EXPORT_IF_KUNIT(event_watchdog_timer);
-static void event_cpirq(struct work_struct *work)
+STATIC_IFN_KUNIT
+void event_cpirq(struct work_struct *work)
{
struct hdcp_workqueue *hdcp_work;
@@ -548,6 +551,7 @@ static void event_cpirq(struct work_struct *work)
process_output(hdcp_work);
}
+EXPORT_IF_KUNIT(event_cpirq);
void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *hdcp_work)
{
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
index 57c534efbc71..a2b08bec58b5 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
@@ -116,6 +116,8 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work,
void event_property_update(struct work_struct *work);
void event_property_validate(struct work_struct *work);
void event_callback(struct work_struct *work);
+void event_watchdog_timer(struct work_struct *work);
+void event_cpirq(struct work_struct *work);
void link_lock(struct hdcp_workqueue *work, bool lock);
void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index,
struct amdgpu_dm_connector *aconnector);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index 6f5cae0960e1..a4097a4aec26 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -748,6 +748,132 @@ static void dm_test_event_property_validate_no_update_when_unchanged(struct kuni
/* End of tests for event_property_validate() */
+/* Tests for event_watchdog_timer() */
+
+/**
+ * dm_test_event_watchdog_timer_cancels_watchdog_dwork - watchdog work is cancelled
+ * @test: KUnit test context
+ *
+ * event_watchdog_timer() must cancel a previously scheduled
+ * watchdog_timer_dwork. With no active hdcp display,
+ * mod_hdcp_process_event() leaves output cleared so the watchdog is not
+ * requeued and watchdog_timer_dwork ends up not pending.
+ */
+static void dm_test_event_watchdog_timer_cancels_watchdog_dwork(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test);
+
+ /* Pre-schedule watchdog_timer_dwork with a long delay so it won't fire. */
+ schedule_delayed_work(&work->watchdog_timer_dwork, msecs_to_jiffies(10000));
+ KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork));
+
+ event_watchdog_timer(&work->watchdog_timer_dwork.work);
+
+ KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork));
+
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+}
+
+/**
+ * dm_test_event_watchdog_timer_schedules_property_validate - process_output() runs
+ * @test: KUnit test context
+ *
+ * event_watchdog_timer() finishes by calling process_output(), which always
+ * enqueues property_validate_dwork with delay=0. Verifying it is pending
+ * proves the handler reached process_output() and released the mutex.
+ */
+static void dm_test_event_watchdog_timer_schedules_property_validate(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test);
+
+ event_watchdog_timer(&work->watchdog_timer_dwork.work);
+
+ KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work));
+ /* Mutex must be released after the guard scope exits. */
+ KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex));
+
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+}
+
+/* End of tests for event_watchdog_timer() */
+
+/* Tests for event_cpirq() */
+
+/**
+ * alloc_test_workqueue_for_cpirq - workqueue ready for event_cpirq()
+ * @test: KUnit test context for managed allocation
+ *
+ * Allocates a minimal hdcp_workqueue with its mutex, cpirq_work and the
+ * three delayed works initialised, as required by the guard(mutex) and
+ * process_output() usage inside event_cpirq().
+ */
+static struct hdcp_workqueue *alloc_test_workqueue_for_cpirq(struct kunit *test)
+{
+ struct hdcp_workqueue *work;
+
+ work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+
+ mutex_init(&work->mutex);
+ INIT_WORK(&work->cpirq_work, dummy_work_fn);
+ INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn);
+ INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn);
+ INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn);
+
+ return work;
+}
+
+/**
+ * dm_test_event_cpirq_schedules_property_validate - process_output() runs
+ * @test: KUnit test context
+ *
+ * event_cpirq() finishes by calling process_output(), which always
+ * enqueues property_validate_dwork with delay=0. Verifying it is pending
+ * proves the handler reached process_output() and released the mutex.
+ */
+static void dm_test_event_cpirq_schedules_property_validate(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_cpirq(test);
+
+ event_cpirq(&work->cpirq_work);
+
+ KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work));
+ /* Mutex must be released after the guard scope exits. */
+ KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex));
+
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+}
+
+/**
+ * dm_test_event_cpirq_leaves_callback_and_watchdog_idle - only validate is queued
+ * @test: KUnit test context
+ *
+ * With no active hdcp display, mod_hdcp_process_event() leaves output
+ * cleared, so event_cpirq() must not schedule callback_dwork or
+ * watchdog_timer_dwork; only property_validate_dwork is enqueued.
+ */
+static void dm_test_event_cpirq_leaves_callback_and_watchdog_idle(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_cpirq(test);
+
+ event_cpirq(&work->cpirq_work);
+
+ KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork));
+ KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork));
+
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+}
+
+/* End of tests for event_cpirq() */
+
/* Tests for hdcp_handle_cpirq() */
/**
@@ -1233,6 +1359,12 @@ static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_event_property_validate_skips_null_state),
KUNIT_CASE(dm_test_event_property_validate_updates_on_status_change),
KUNIT_CASE(dm_test_event_property_validate_no_update_when_unchanged),
+ /* event_watchdog_timer() */
+ KUNIT_CASE(dm_test_event_watchdog_timer_cancels_watchdog_dwork),
+ KUNIT_CASE(dm_test_event_watchdog_timer_schedules_property_validate),
+ /* event_cpirq() */
+ KUNIT_CASE(dm_test_event_cpirq_schedules_property_validate),
+ KUNIT_CASE(dm_test_event_cpirq_leaves_callback_and_watchdog_idle),
/* hdcp_handle_cpirq() */
KUNIT_CASE(dm_test_hdcp_handle_cpirq_schedules_work),
KUNIT_CASE(dm_test_hdcp_handle_cpirq_selects_link_index),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 09/49] drm/amd/display: Add KUnit tests for hdcp_destroy
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (7 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 08/49] drm/amd/display: Add KUnit tests for watchdog and cpirq events Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 10/49] drm/amd/display: Add AV mute wait frames to dce110_set_avmute Fangzhi Zuo
` (39 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Verify hdcp_destroy() cancels each link's delayed works, removes
the SRM sysfs file and frees the workqueue and SRM buffers. Cover
both a populated work-queue and the zero-link / NULL-SRM path.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 1 +
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 122 ++++++++++++++++++
2 files changed, 123 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index 896668c92888..e704b6b7fc0b 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -568,6 +568,7 @@ void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *hdcp_work)
kfree(hdcp_work->srm_temp);
kfree(hdcp_work);
}
+EXPORT_IF_KUNIT(hdcp_destroy);
static bool enable_assr(void *handle, struct dc_link *link)
{
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index a4097a4aec26..5eace7fa4100 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -7,6 +7,9 @@
#include <kunit/test.h>
#include <linux/workqueue.h>
+#include <linux/kobject.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
#include "amdgpu.h"
#include "amdgpu_dm.h"
@@ -1007,6 +1010,122 @@ static void dm_test_hdcp_create_workqueue_zero_max_links_returns_null(struct kun
/* End of tests for hdcp_create_workqueue() */
+/* Tests for hdcp_destroy() */
+
+static ssize_t test_srm_bin_read(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buffer,
+ loff_t pos, size_t count)
+{
+ return 0;
+}
+
+static ssize_t test_srm_bin_write(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buffer,
+ loff_t pos, size_t count)
+{
+ return count;
+}
+
+/**
+ * setup_destroy_sysfs - create a kobject with the SRM bin file attached
+ * @test: KUnit test context
+ * @work: workqueue whose attr will be registered
+ *
+ * hdcp_destroy() calls sysfs_remove_bin_file() on the first entry's attr, so
+ * a real kobject with the bin file created is required. Returns the kobject,
+ * which the caller must kobject_put() after hdcp_destroy() has run.
+ */
+static struct kobject *setup_destroy_sysfs(struct kunit *test,
+ struct hdcp_workqueue *work)
+{
+ struct kobject *kobj;
+ int ret;
+
+ kobj = kobject_create_and_add("amdgpu_dm_hdcp_test", NULL);
+ KUNIT_ASSERT_NOT_NULL(test, kobj);
+
+ sysfs_bin_attr_init(&work->attr);
+ work->attr.attr.name = "hdcp_srm";
+ work->attr.attr.mode = 0664;
+ work->attr.size = 16;
+ work->attr.read = test_srm_bin_read;
+ work->attr.write = test_srm_bin_write;
+
+ ret = sysfs_create_bin_file(kobj, &work->attr);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ return kobj;
+}
+
+/**
+ * dm_test_hdcp_destroy_frees_and_removes_sysfs - full teardown path
+ * @test: KUnit test context
+ *
+ * hdcp_destroy() must cancel every link's delayed works, remove the SRM
+ * sysfs bin file and free srm, srm_temp and the workqueue itself. The
+ * workqueue and SRM buffers use kzalloc() (not kunit-managed) because
+ * hdcp_destroy() frees them; KASAN/kmemleak validate there is no leak or
+ * use-after-free.
+ */
+static void dm_test_hdcp_destroy_frees_and_removes_sysfs(struct kunit *test)
+{
+ struct hdcp_workqueue *work;
+ struct kobject *kobj;
+
+ work = kzalloc_obj(*work);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+
+ work->max_link = 1;
+ INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn);
+ INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn);
+ INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn);
+
+ work->srm = kzalloc(16, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work->srm);
+ work->srm_temp = kzalloc(16, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work->srm_temp);
+
+ kobj = setup_destroy_sysfs(test, work);
+
+ /* Pre-schedule a delayed work to exercise the cancel path. */
+ schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000));
+ KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork));
+
+ hdcp_destroy(kobj, work);
+
+ /* work is freed by hdcp_destroy(); only the kobject remains. */
+ kobject_put(kobj);
+}
+
+/**
+ * dm_test_hdcp_destroy_zero_links_null_srm - teardown with no links or SRM
+ * @test: KUnit test context
+ *
+ * With max_link == 0 the cancel loop is skipped, and NULL srm/srm_temp make
+ * the kfree() calls no-ops. hdcp_destroy() must still remove the sysfs bin
+ * file and free the workqueue without crashing.
+ */
+static void dm_test_hdcp_destroy_zero_links_null_srm(struct kunit *test)
+{
+ struct hdcp_workqueue *work;
+ struct kobject *kobj;
+
+ work = kzalloc_obj(*work);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+
+ work->max_link = 0;
+ work->srm = NULL;
+ work->srm_temp = NULL;
+
+ kobj = setup_destroy_sysfs(test, work);
+
+ hdcp_destroy(kobj, work);
+
+ kobject_put(kobj);
+}
+
+/* End of tests for hdcp_destroy() */
+
/* Tests for link_lock() */
/**
@@ -1373,6 +1492,9 @@ static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate),
/* hdcp_create_workqueue() */
KUNIT_CASE(dm_test_hdcp_create_workqueue_zero_max_links_returns_null),
+ /* hdcp_destroy() */
+ KUNIT_CASE(dm_test_hdcp_destroy_frees_and_removes_sysfs),
+ KUNIT_CASE(dm_test_hdcp_destroy_zero_links_null_srm),
/* link_lock() */
KUNIT_CASE(dm_test_link_lock_locks_and_unlocks_all_links),
KUNIT_CASE(dm_test_link_lock_zero_links_is_noop),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 10/49] drm/amd/display: Add AV mute wait frames to dce110_set_avmute
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (8 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 09/49] drm/amd/display: Add KUnit tests for hdcp_destroy Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 11/49] drm/amd/display: revert "convert dcn42 GPIO translation to lookup tables" Fangzhi Zuo
` (38 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Ray Wu
From: Ray Wu <ray.wu@amd.com>
Port the three-frame wait logic from dcn30_set_avmute to
dce110_set_avmute so that older DCN versions (1.0, 2.0) also
wait for GCP packets to be sent out before proceeding.
This ensures HDMI sinks properly process the mute state,
preventing garbled display after link re-establishment.
Reviewed-by: Wayne Lin <wayne.lin@amd.com>
Signed-off-by: Ray Wu <ray.wu@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/dc/hwss/dce110/dce110_hwseq.c | 21 ++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
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 262982ca5ad9..f91ce2b4b1bf 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
@@ -1336,8 +1336,27 @@ void dce110_blank_stream(struct pipe_ctx *pipe_ctx)
void dce110_set_avmute(struct pipe_ctx *pipe_ctx, bool enable)
{
- if (pipe_ctx != NULL && pipe_ctx->stream_res.stream_enc != NULL)
+ if (pipe_ctx == NULL || pipe_ctx->stream_res.stream_enc == NULL)
+ return;
+
+ if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) {
pipe_ctx->stream_res.stream_enc->funcs->set_avmute(pipe_ctx->stream_res.stream_enc, enable);
+
+ /* Wait for three frames to make sure AV mute is sent out.
+ * Some HDMI sinks need additional GCP packets to properly
+ * process the mute state, especially after link re-establishment
+ * with HDMI 2.0 scrambling enabled.
+ */
+ if (enable && pipe_ctx->stream_res.tg->funcs->is_tg_enabled(pipe_ctx->stream_res.tg)) {
+ int i;
+
+ pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE);
+ for (i = 0; i < 3; i++) {
+ pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK);
+ pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE);
+ }
+ }
+ }
}
enum audio_dto_source translate_to_dto_source(enum controller_id crtc_id)
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 11/49] drm/amd/display: revert "convert dcn42 GPIO translation to lookup tables"
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (9 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 10/49] drm/amd/display: Add AV mute wait frames to dce110_set_avmute Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 12/49] drm/amd/display: move scaling helper to connector Fangzhi Zuo
` (37 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Charlene Liu, Yihan Zhu
From: Charlene Liu <Charlene.Liu@amd.com>
[why]
This reverts commit b536028ffbce3c3f026ba7c9b42b8f66cfb186e9.
Reviewed-by: Yihan Zhu <yihan.zhu@amd.com>
Signed-off-by: Charlene Liu <Charlene.Liu@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../dc/gpio/dcn42/hw_translate_dcn42.c | 193 +++++++++++-------
.../dc/gpio/dcn42/hw_translate_dcn42.c.orig | 153 ++++++++++++++
2 files changed, 276 insertions(+), 70 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c.orig
diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c
index 7b2c4cd42450..e7e1d9979876 100644
--- a/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c
+++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c
@@ -39,76 +39,62 @@
* end *********************/
-static const struct gpio_id_offset_entry gpio_offsets[] = {
- /* HPD */
- GPIO_ENTRY(HPD0_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_1),
- GPIO_ENTRY(HPD1_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_2),
- GPIO_ENTRY(HPD2_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_3),
- GPIO_ENTRY(HPD3_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_4),
- GPIO_ENTRY(HPD4_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_5),
-};
-
-
-/* DDC */
-static const struct gpio_ddc_offset_entry ddc_offset_map[] = {
- { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 },
- { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 },
- { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 },
- { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 },
- { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 },
- { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA },
-};
-
-
-static const struct gpio_pin_entry gpio_pins[] = {
- /* DDC */
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1,
- DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2,
- DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3,
- DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4,
- DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5,
- DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA,
- DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1,
- DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2,
- DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3,
- DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4,
- DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5,
- DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA,
- DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
-};
-
-
static bool offset_to_id(
uint32_t offset,
uint32_t mask,
enum gpio_id *id,
uint32_t *en)
{
- if (dal_hw_translate_gpio_ddc_offset_to_id(
- ddc_offset_map,
- ARRAY_SIZE(ddc_offset_map),
- offset, en))
+ (void)mask;
+ switch (offset) {
+ /* HPD */
+ case REG(HPD0_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_1;
return true;
-
- if (dal_hw_translate_gpio_offset_to_id(
- gpio_offsets,
- ARRAY_SIZE(gpio_offsets),
- offset, mask, id, en))
+ case REG(HPD1_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_2;
return true;
-
- ASSERT_CRITICAL(false);
- return false;
+ case REG(HPD2_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_3;
+ return true;
+ case REG(HPD3_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_4;
+ return true;
+ case REG(HPD4_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_5;
+ return true;
+ /* DDC */
+ /* we don't care about the GPIO_ID for DDC
+ * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK
+ * directly in the create method
+ */
+ case REG(DC_GPIO_DDC1_A):
+ *en = GPIO_DDC_LINE_DDC1;
+ return true;
+ case REG(DC_GPIO_DDC2_A):
+ *en = GPIO_DDC_LINE_DDC2;
+ return true;
+ case REG(DC_GPIO_DDC3_A):
+ *en = GPIO_DDC_LINE_DDC3;
+ return true;
+ case REG(DC_GPIO_DDC4_A):
+ *en = GPIO_DDC_LINE_DDC4;
+ return true;
+ case REG(DC_GPIO_DDC5_A):
+ *en = GPIO_DDC_LINE_DDC5;
+ return true;
+ case REG(DC_GPIO_DDCVGA_A):
+ *en = GPIO_DDC_LINE_DDC_VGA;
+ return true;
+ default:
+ ASSERT_CRITICAL(false);
+ return false;
+ }
}
@@ -117,14 +103,81 @@ static bool id_to_offset(
uint32_t en,
struct gpio_pin_info *info)
{
- if (dal_hw_translate_id_to_offset(
- gpio_pins,
- ARRAY_SIZE(gpio_pins),
- id, en, info))
- return true;
-
- ASSERT_CRITICAL(false);
- return false;
+ bool result = true;
+
+ switch (id) {
+ case GPIO_ID_DDC_DATA:
+ info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK;
+ switch (en) {
+ case GPIO_DDC_LINE_DDC1:
+ info->offset = REG(DC_GPIO_DDC1_A);
+ break;
+ case GPIO_DDC_LINE_DDC2:
+ info->offset = REG(DC_GPIO_DDC2_A);
+ break;
+ case GPIO_DDC_LINE_DDC3:
+ info->offset = REG(DC_GPIO_DDC3_A);
+ break;
+ case GPIO_DDC_LINE_DDC4:
+ info->offset = REG(DC_GPIO_DDC4_A);
+ break;
+ case GPIO_DDC_LINE_DDC5:
+ info->offset = REG(DC_GPIO_DDC5_A);
+ break;
+ case GPIO_DDC_LINE_DDC_VGA:
+ info->offset = REG(DC_GPIO_DDCVGA_A);
+ break;
+ case GPIO_DDC_LINE_I2C_PAD:
+ default:
+ ASSERT_CRITICAL(false);
+ result = false;
+ }
+ break;
+ case GPIO_ID_DDC_CLOCK:
+ info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK;
+ switch (en) {
+ case GPIO_DDC_LINE_DDC1:
+ info->offset = REG(DC_GPIO_DDC1_A);
+ break;
+ case GPIO_DDC_LINE_DDC2:
+ info->offset = REG(DC_GPIO_DDC2_A);
+ break;
+ case GPIO_DDC_LINE_DDC3:
+ info->offset = REG(DC_GPIO_DDC3_A);
+ break;
+ case GPIO_DDC_LINE_DDC4:
+ info->offset = REG(DC_GPIO_DDC4_A);
+ break;
+ case GPIO_DDC_LINE_DDC5:
+ info->offset = REG(DC_GPIO_DDC5_A);
+ break;
+ case GPIO_DDC_LINE_DDC_VGA:
+ info->offset = REG(DC_GPIO_DDCVGA_A);
+ break;
+ case GPIO_DDC_LINE_I2C_PAD:
+ default:
+ ASSERT_CRITICAL(false);
+ result = false;
+ }
+ break;
+ case GPIO_ID_SYNC:
+ case GPIO_ID_VIP_PAD:
+ default:
+ ASSERT_CRITICAL(false);
+ result = false;
+ }
+
+ if (result) {
+ info->offset_y = info->offset + 2;
+ info->offset_en = info->offset + 1;
+ info->offset_mask = info->offset - 1;
+
+ info->mask_y = info->mask;
+ info->mask_en = info->mask;
+ info->mask_mask = info->mask;
+ }
+
+ return result;
}
diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c.orig b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c.orig
new file mode 100644
index 000000000000..7b2c4cd42450
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c.orig
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright 2026 Advanced Micro Devices, Inc.
+
+#include "hw_translate_dcn42.h"
+
+#include "dm_services.h"
+#include "include/gpio_types.h"
+#include "../hw_translate.h"
+
+
+#include "dcn/dcn_4_2_0_offset.h"
+#include "dcn/dcn_4_2_0_sh_mask.h"
+#include "dpcs/dpcs_4_0_0_offset.h"
+#include "dpcs/dpcs_4_0_0_sh_mask.h"
+
+#define DCN_BASE__INST0_SEG2 0x000034C0
+
+/* begin *********************
+ * macros to expend register list macro defined in HW object header file */
+
+/* DCN */
+#define block HPD
+#define reg_num 0
+
+#undef BASE_INNER
+#define BASE_INNER(seg) DCN_BASE__INST0_SEG ## seg
+
+#define BASE(seg) BASE_INNER(seg)
+
+#undef REG
+#define REG(reg_name)\
+ (BASE(reg ## reg_name ## _BASE_IDX) + reg ## reg_name)
+#define SF_HPD(reg_name, field_name, post_fix)\
+ .field_name = reg_name ## __ ## field_name ## post_fix
+
+
+/* macros to expend register list macro defined in HW object header file
+ * end *********************/
+
+
+static const struct gpio_id_offset_entry gpio_offsets[] = {
+ /* HPD */
+ GPIO_ENTRY(HPD0_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_1),
+ GPIO_ENTRY(HPD1_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_2),
+ GPIO_ENTRY(HPD2_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_3),
+ GPIO_ENTRY(HPD3_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_4),
+ GPIO_ENTRY(HPD4_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_5),
+};
+
+
+/* DDC */
+static const struct gpio_ddc_offset_entry ddc_offset_map[] = {
+ { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 },
+ { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 },
+ { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 },
+ { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 },
+ { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 },
+ { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA },
+};
+
+
+static const struct gpio_pin_entry gpio_pins[] = {
+ /* DDC */
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1,
+ DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2,
+ DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3,
+ DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4,
+ DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5,
+ DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA,
+ DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1,
+ DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2,
+ DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3,
+ DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4,
+ DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5,
+ DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA,
+ DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+};
+
+
+static bool offset_to_id(
+ uint32_t offset,
+ uint32_t mask,
+ enum gpio_id *id,
+ uint32_t *en)
+{
+ if (dal_hw_translate_gpio_ddc_offset_to_id(
+ ddc_offset_map,
+ ARRAY_SIZE(ddc_offset_map),
+ offset, en))
+ return true;
+
+ if (dal_hw_translate_gpio_offset_to_id(
+ gpio_offsets,
+ ARRAY_SIZE(gpio_offsets),
+ offset, mask, id, en))
+ return true;
+
+ ASSERT_CRITICAL(false);
+ return false;
+}
+
+
+static bool id_to_offset(
+ enum gpio_id id,
+ uint32_t en,
+ struct gpio_pin_info *info)
+{
+ if (dal_hw_translate_id_to_offset(
+ gpio_pins,
+ ARRAY_SIZE(gpio_pins),
+ id, en, info))
+ return true;
+
+ ASSERT_CRITICAL(false);
+ return false;
+}
+
+
+/* function table */
+static const struct hw_translate_funcs funcs = {
+ .offset_to_id = offset_to_id,
+ .id_to_offset = id_to_offset,
+};
+
+
+/*
+ * dal_hw_translate_dcn42_init
+ *
+ * @brief
+ * Initialize Hw translate function pointers.
+ *
+ * @param
+ * struct hw_translate *tr - [out] struct of function pointers
+ *
+ */
+void dal_hw_translate_dcn42_init(struct hw_translate *tr)
+{
+ tr->funcs = &funcs;
+}
+
+
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 12/49] drm/amd/display: move scaling helper to connector
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (10 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 11/49] drm/amd/display: revert "convert dcn42 GPIO translation to lookup tables" Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 13/49] drm/amd/display: move stutter quirk to quirks file Fangzhi Zuo
` (36 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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]
amdgpu_dm_update_stream_scaling_settings() computes the stream src/dst
rectangles for a connector's scaling mode. It is already declared in
amdgpu_dm_connector.h and consumed by create_stream_for_sink(), so move
its definition out of the oversized amdgpu_dm.c into
amdgpu_dm_connector.c where it belongs.
Relocate its KUnit tests from the amdgpu_dm suite to the
amdgpu_dm_connector suite accordingly. No functional change.
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 57 -----
.../display/amdgpu_dm/amdgpu_dm_connector.c | 57 +++++
.../tests/amdgpu_dm_connector_test.c | 209 ++++++++++++++++++
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 208 -----------------
4 files changed, 266 insertions(+), 265 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 111cdd66fe93..e6452237ba70 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -3405,63 +3405,6 @@ static void fill_dc_dirty_rects(struct drm_plane *plane,
&flip_addrs->dirty_rect_count, true);
}
-void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev,
- const struct drm_display_mode *mode,
- const struct dm_connector_state *dm_state,
- struct dc_stream_state *stream)
-{
- enum amdgpu_rmx_type rmx_type;
-
- struct rect src = { 0 }; /* viewport in composition space*/
- struct rect dst = { 0 }; /* stream addressable area */
-
- /* no mode. nothing to be done */
- if (!mode)
- return;
-
- /* Full screen scaling by default */
- src.width = mode->hdisplay;
- src.height = mode->vdisplay;
- dst.width = stream->timing.h_addressable;
- dst.height = stream->timing.v_addressable;
-
- if (dm_state) {
- rmx_type = dm_state->scaling;
- if (rmx_type == RMX_ASPECT || rmx_type == RMX_OFF) {
- if (src.width * dst.height <
- src.height * dst.width) {
- /* height needs less upscaling/more downscaling */
- dst.width = src.width *
- dst.height / src.height;
- } else {
- /* width needs less upscaling/more downscaling */
- dst.height = src.height *
- dst.width / src.width;
- }
- } else if (rmx_type == RMX_CENTER) {
- dst = src;
- }
-
- dst.x = (stream->timing.h_addressable - dst.width) / 2;
- dst.y = (stream->timing.v_addressable - dst.height) / 2;
-
- if (dm_state->underscan_enable) {
- dst.x += dm_state->underscan_hborder / 2;
- dst.y += dm_state->underscan_vborder / 2;
- dst.width -= dm_state->underscan_hborder;
- dst.height -= dm_state->underscan_vborder;
- }
- }
-
- stream->src = src;
- stream->dst = dst;
-
- drm_dbg_kms(dev, "Destination Rectangle x:%d y:%d width:%d height:%d\n",
- dst.x, dst.y, dst.width, dst.height);
-
-}
-EXPORT_IF_KUNIT(amdgpu_dm_update_stream_scaling_settings);
-
static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_commit *state,
struct dc_state *dc_state,
struct dsc_mst_fairness_vars *vars)
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 931f653ebdec..135107c73971 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
@@ -1371,6 +1371,63 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector,
}
#endif
+void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev,
+ const struct drm_display_mode *mode,
+ const struct dm_connector_state *dm_state,
+ struct dc_stream_state *stream)
+{
+ enum amdgpu_rmx_type rmx_type;
+
+ struct rect src = { 0 }; /* viewport in composition space*/
+ struct rect dst = { 0 }; /* stream addressable area */
+
+ /* no mode. nothing to be done */
+ if (!mode)
+ return;
+
+ /* Full screen scaling by default */
+ src.width = mode->hdisplay;
+ src.height = mode->vdisplay;
+ dst.width = stream->timing.h_addressable;
+ dst.height = stream->timing.v_addressable;
+
+ if (dm_state) {
+ rmx_type = dm_state->scaling;
+ if (rmx_type == RMX_ASPECT || rmx_type == RMX_OFF) {
+ if (src.width * dst.height <
+ src.height * dst.width) {
+ /* height needs less upscaling/more downscaling */
+ dst.width = src.width *
+ dst.height / src.height;
+ } else {
+ /* width needs less upscaling/more downscaling */
+ dst.height = src.height *
+ dst.width / src.width;
+ }
+ } else if (rmx_type == RMX_CENTER) {
+ dst = src;
+ }
+
+ dst.x = (stream->timing.h_addressable - dst.width) / 2;
+ dst.y = (stream->timing.v_addressable - dst.height) / 2;
+
+ if (dm_state->underscan_enable) {
+ dst.x += dm_state->underscan_hborder / 2;
+ dst.y += dm_state->underscan_vborder / 2;
+ dst.width -= dm_state->underscan_hborder;
+ dst.height -= dm_state->underscan_vborder;
+ }
+ }
+
+ stream->src = src;
+ stream->dst = dst;
+
+ drm_dbg_kms(dev, "Destination Rectangle x:%d y:%d width:%d height:%d\n",
+ dst.x, dst.y, dst.width, dst.height);
+
+}
+EXPORT_IF_KUNIT(amdgpu_dm_update_stream_scaling_settings);
+
STATIC_IFN_KUNIT struct dc_stream_state *
create_stream_for_sink(struct drm_connector *connector,
const struct drm_display_mode *drm_mode,
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 75f6be829f24..0e6fbc4b70f4 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
@@ -27,6 +27,7 @@
#include "amdgpu_dm_connector.h"
#include "amdgpu_dm_backlight.h"
#include "include/grph_object_id.h"
+#include "amdgpu_dm_kunit_test_helpers.h"
/* Tests for get_subconnector_type() */
@@ -5060,6 +5061,206 @@ static void dm_test_update_after_detect_sink_unchanged(struct kunit *test)
KUNIT_EXPECT_NULL(test, aconnector->dc_sink);
}
+/* Tests for amdgpu_dm_update_stream_scaling_settings() */
+
+/**
+ * dm_test_update_scaling_null_mode - Test NULL mode leaves the stream rects untouched
+ * @test: The KUnit test context
+ */
+static void dm_test_update_scaling_null_mode(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
+
+ stream->timing.h_addressable = 1920;
+ stream->timing.v_addressable = 1080;
+
+ amdgpu_dm_update_stream_scaling_settings(&adev->ddev, NULL, NULL, stream);
+
+ /* NULL mode: early return before touching src/dst */
+ KUNIT_EXPECT_EQ(test, stream->src.width, 0);
+ KUNIT_EXPECT_EQ(test, stream->dst.width, 0);
+}
+
+/**
+ * dm_test_update_scaling_fullscreen_default - Test full-screen default with no dm_state
+ * @test: The KUnit test context
+ */
+static void dm_test_update_scaling_fullscreen_default(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
+ struct drm_display_mode mode = { 0 };
+
+ mode.hdisplay = 1920;
+ mode.vdisplay = 1080;
+ stream->timing.h_addressable = 2560;
+ stream->timing.v_addressable = 1440;
+
+ amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, NULL, stream);
+
+ /* src = mode, dst = timing addressable, no centering without dm_state */
+ KUNIT_EXPECT_EQ(test, stream->src.width, 1920);
+ KUNIT_EXPECT_EQ(test, stream->src.height, 1080);
+ KUNIT_EXPECT_EQ(test, stream->dst.width, 2560);
+ KUNIT_EXPECT_EQ(test, stream->dst.height, 1440);
+ KUNIT_EXPECT_EQ(test, stream->dst.x, 0);
+ KUNIT_EXPECT_EQ(test, stream->dst.y, 0);
+}
+
+/**
+ * dm_test_update_scaling_rmx_full - Test RMX_FULL keeps a full-size, centered dst
+ * @test: The KUnit test context
+ */
+static void dm_test_update_scaling_rmx_full(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
+ struct dm_connector_state *dm_state;
+ struct drm_display_mode mode = { 0 };
+
+ dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_state);
+
+ mode.hdisplay = 1280;
+ mode.vdisplay = 720;
+ stream->timing.h_addressable = 1920;
+ stream->timing.v_addressable = 1080;
+ dm_state->scaling = RMX_FULL;
+
+ amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
+
+ /* RMX_FULL: dst stays full addressable, offset 0 */
+ KUNIT_EXPECT_EQ(test, stream->dst.width, 1920);
+ KUNIT_EXPECT_EQ(test, stream->dst.height, 1080);
+ KUNIT_EXPECT_EQ(test, stream->dst.x, 0);
+ KUNIT_EXPECT_EQ(test, stream->dst.y, 0);
+}
+
+/**
+ * dm_test_update_scaling_rmx_aspect_pillarbox - Test RMX_ASPECT preserves aspect ratio
+ * @test: The KUnit test context
+ */
+static void dm_test_update_scaling_rmx_aspect_pillarbox(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
+ struct dm_connector_state *dm_state;
+ struct drm_display_mode mode = { 0 };
+
+ dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_state);
+
+ /* 4:3 source on a 16:9 panel -> pillarboxed */
+ mode.hdisplay = 1024;
+ mode.vdisplay = 768;
+ stream->timing.h_addressable = 1920;
+ stream->timing.v_addressable = 1080;
+ dm_state->scaling = RMX_ASPECT;
+
+ amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
+
+ /*
+ * src.width*dst.height (1024*1080) < src.height*dst.width (768*1920):
+ * width scaled to src.width*dst.height/src.height = 1440, height stays
+ * 1080, centered horizontally at (1920-1440)/2 = 240.
+ */
+ KUNIT_EXPECT_EQ(test, stream->dst.width, 1440);
+ KUNIT_EXPECT_EQ(test, stream->dst.height, 1080);
+ KUNIT_EXPECT_EQ(test, stream->dst.x, 240);
+ KUNIT_EXPECT_EQ(test, stream->dst.y, 0);
+}
+
+/**
+ * dm_test_update_scaling_rmx_aspect_letterbox - Test RMX_ASPECT letterboxes wide sources
+ * @test: The KUnit test context
+ */
+static void dm_test_update_scaling_rmx_aspect_letterbox(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
+ struct dm_connector_state *dm_state;
+ struct drm_display_mode mode = { 0 };
+
+ dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_state);
+
+ /* 16:9 source on a 4:3 panel -> letterboxed */
+ mode.hdisplay = 1920;
+ mode.vdisplay = 1080;
+ stream->timing.h_addressable = 1024;
+ stream->timing.v_addressable = 768;
+ dm_state->scaling = RMX_ASPECT;
+
+ amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
+
+ KUNIT_EXPECT_EQ(test, stream->dst.width, 1024);
+ KUNIT_EXPECT_EQ(test, stream->dst.height, 576);
+ KUNIT_EXPECT_EQ(test, stream->dst.x, 0);
+ KUNIT_EXPECT_EQ(test, stream->dst.y, 96);
+}
+
+/**
+ * dm_test_update_scaling_rmx_center - Test RMX_CENTER centers a 1:1 dst
+ * @test: The KUnit test context
+ */
+static void dm_test_update_scaling_rmx_center(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
+ struct dm_connector_state *dm_state;
+ struct drm_display_mode mode = { 0 };
+
+ dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_state);
+
+ mode.hdisplay = 1280;
+ mode.vdisplay = 720;
+ stream->timing.h_addressable = 1920;
+ stream->timing.v_addressable = 1080;
+ dm_state->scaling = RMX_CENTER;
+
+ amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
+
+ /* RMX_CENTER: dst = src, centered on the addressable area */
+ KUNIT_EXPECT_EQ(test, stream->dst.width, 1280);
+ KUNIT_EXPECT_EQ(test, stream->dst.height, 720);
+ KUNIT_EXPECT_EQ(test, stream->dst.x, 320);
+ KUNIT_EXPECT_EQ(test, stream->dst.y, 180);
+}
+
+/**
+ * dm_test_update_scaling_underscan - Test underscan borders shrink and offset dst
+ * @test: The KUnit test context
+ */
+static void dm_test_update_scaling_underscan(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
+ struct dm_connector_state *dm_state;
+ struct drm_display_mode mode = { 0 };
+
+ dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_state);
+
+ mode.hdisplay = 1920;
+ mode.vdisplay = 1080;
+ stream->timing.h_addressable = 1920;
+ stream->timing.v_addressable = 1080;
+ dm_state->scaling = RMX_FULL;
+ dm_state->underscan_enable = true;
+ dm_state->underscan_hborder = 64;
+ dm_state->underscan_vborder = 32;
+
+ amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
+
+ /* Full dst, then underscan: x/y += border/2, width/height -= border */
+ KUNIT_EXPECT_EQ(test, stream->dst.x, 32);
+ KUNIT_EXPECT_EQ(test, stream->dst.y, 16);
+ KUNIT_EXPECT_EQ(test, stream->dst.width, 1856);
+ KUNIT_EXPECT_EQ(test, stream->dst.height, 1048);
+}
+
static struct kunit_case amdgpu_dm_connector_tests[] = {
/* get_subconnector_type */
KUNIT_CASE(dm_test_subconnector_type_none),
@@ -5339,6 +5540,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),
+ /* amdgpu_dm_update_stream_scaling_settings */
+ KUNIT_CASE(dm_test_update_scaling_null_mode),
+ KUNIT_CASE(dm_test_update_scaling_fullscreen_default),
+ KUNIT_CASE(dm_test_update_scaling_rmx_full),
+ KUNIT_CASE(dm_test_update_scaling_rmx_aspect_pillarbox),
+ KUNIT_CASE(dm_test_update_scaling_rmx_aspect_letterbox),
+ KUNIT_CASE(dm_test_update_scaling_rmx_center),
+ KUNIT_CASE(dm_test_update_scaling_underscan),
{}
};
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index f967eced6dbb..88ac5e4f6c04 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -1225,206 +1225,6 @@ static void dm_test_master_stream_defaults_to_first(struct kunit *test)
stream0);
}
-/* Tests for amdgpu_dm_update_stream_scaling_settings() */
-
-/**
- * dm_test_update_scaling_null_mode - Test NULL mode leaves the stream rects untouched
- * @test: The KUnit test context
- */
-static void dm_test_update_scaling_null_mode(struct kunit *test)
-{
- struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
- struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
-
- stream->timing.h_addressable = 1920;
- stream->timing.v_addressable = 1080;
-
- amdgpu_dm_update_stream_scaling_settings(&adev->ddev, NULL, NULL, stream);
-
- /* NULL mode: early return before touching src/dst */
- KUNIT_EXPECT_EQ(test, stream->src.width, 0);
- KUNIT_EXPECT_EQ(test, stream->dst.width, 0);
-}
-
-/**
- * dm_test_update_scaling_fullscreen_default - Test full-screen default with no dm_state
- * @test: The KUnit test context
- */
-static void dm_test_update_scaling_fullscreen_default(struct kunit *test)
-{
- struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
- struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
- struct drm_display_mode mode = { 0 };
-
- mode.hdisplay = 1920;
- mode.vdisplay = 1080;
- stream->timing.h_addressable = 2560;
- stream->timing.v_addressable = 1440;
-
- amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, NULL, stream);
-
- /* src = mode, dst = timing addressable, no centering without dm_state */
- KUNIT_EXPECT_EQ(test, stream->src.width, 1920);
- KUNIT_EXPECT_EQ(test, stream->src.height, 1080);
- KUNIT_EXPECT_EQ(test, stream->dst.width, 2560);
- KUNIT_EXPECT_EQ(test, stream->dst.height, 1440);
- KUNIT_EXPECT_EQ(test, stream->dst.x, 0);
- KUNIT_EXPECT_EQ(test, stream->dst.y, 0);
-}
-
-/**
- * dm_test_update_scaling_rmx_full - Test RMX_FULL keeps a full-size, centered dst
- * @test: The KUnit test context
- */
-static void dm_test_update_scaling_rmx_full(struct kunit *test)
-{
- struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
- struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
- struct dm_connector_state *dm_state;
- struct drm_display_mode mode = { 0 };
-
- dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, dm_state);
-
- mode.hdisplay = 1280;
- mode.vdisplay = 720;
- stream->timing.h_addressable = 1920;
- stream->timing.v_addressable = 1080;
- dm_state->scaling = RMX_FULL;
-
- amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
-
- /* RMX_FULL: dst stays full addressable, offset 0 */
- KUNIT_EXPECT_EQ(test, stream->dst.width, 1920);
- KUNIT_EXPECT_EQ(test, stream->dst.height, 1080);
- KUNIT_EXPECT_EQ(test, stream->dst.x, 0);
- KUNIT_EXPECT_EQ(test, stream->dst.y, 0);
-}
-
-/**
- * dm_test_update_scaling_rmx_aspect_pillarbox - Test RMX_ASPECT preserves aspect ratio
- * @test: The KUnit test context
- */
-static void dm_test_update_scaling_rmx_aspect_pillarbox(struct kunit *test)
-{
- struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
- struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
- struct dm_connector_state *dm_state;
- struct drm_display_mode mode = { 0 };
-
- dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, dm_state);
-
- /* 4:3 source on a 16:9 panel -> pillarboxed */
- mode.hdisplay = 1024;
- mode.vdisplay = 768;
- stream->timing.h_addressable = 1920;
- stream->timing.v_addressable = 1080;
- dm_state->scaling = RMX_ASPECT;
-
- amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
-
- /*
- * src.width*dst.height (1024*1080) < src.height*dst.width (768*1920):
- * width scaled to src.width*dst.height/src.height = 1440, height stays
- * 1080, centered horizontally at (1920-1440)/2 = 240.
- */
- KUNIT_EXPECT_EQ(test, stream->dst.width, 1440);
- KUNIT_EXPECT_EQ(test, stream->dst.height, 1080);
- KUNIT_EXPECT_EQ(test, stream->dst.x, 240);
- KUNIT_EXPECT_EQ(test, stream->dst.y, 0);
-}
-
-/**
- * dm_test_update_scaling_rmx_aspect_letterbox - Test RMX_ASPECT letterboxes wide sources
- * @test: The KUnit test context
- */
-static void dm_test_update_scaling_rmx_aspect_letterbox(struct kunit *test)
-{
- struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
- struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
- struct dm_connector_state *dm_state;
- struct drm_display_mode mode = { 0 };
-
- dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, dm_state);
-
- /* 16:9 source on a 4:3 panel -> letterboxed */
- mode.hdisplay = 1920;
- mode.vdisplay = 1080;
- stream->timing.h_addressable = 1024;
- stream->timing.v_addressable = 768;
- dm_state->scaling = RMX_ASPECT;
-
- amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
-
- KUNIT_EXPECT_EQ(test, stream->dst.width, 1024);
- KUNIT_EXPECT_EQ(test, stream->dst.height, 576);
- KUNIT_EXPECT_EQ(test, stream->dst.x, 0);
- KUNIT_EXPECT_EQ(test, stream->dst.y, 96);
-}
-
-/**
- * dm_test_update_scaling_rmx_center - Test RMX_CENTER centers a 1:1 dst
- * @test: The KUnit test context
- */
-static void dm_test_update_scaling_rmx_center(struct kunit *test)
-{
- struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
- struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
- struct dm_connector_state *dm_state;
- struct drm_display_mode mode = { 0 };
-
- dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, dm_state);
-
- mode.hdisplay = 1280;
- mode.vdisplay = 720;
- stream->timing.h_addressable = 1920;
- stream->timing.v_addressable = 1080;
- dm_state->scaling = RMX_CENTER;
-
- amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
-
- /* RMX_CENTER: dst = src, centered on the addressable area */
- KUNIT_EXPECT_EQ(test, stream->dst.width, 1280);
- KUNIT_EXPECT_EQ(test, stream->dst.height, 720);
- KUNIT_EXPECT_EQ(test, stream->dst.x, 320);
- KUNIT_EXPECT_EQ(test, stream->dst.y, 180);
-}
-
-/**
- * dm_test_update_scaling_underscan - Test underscan borders shrink and offset dst
- * @test: The KUnit test context
- */
-static void dm_test_update_scaling_underscan(struct kunit *test)
-{
- struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
- struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL);
- struct dm_connector_state *dm_state;
- struct drm_display_mode mode = { 0 };
-
- dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, dm_state);
-
- mode.hdisplay = 1920;
- mode.vdisplay = 1080;
- stream->timing.h_addressable = 1920;
- stream->timing.v_addressable = 1080;
- dm_state->scaling = RMX_FULL;
- dm_state->underscan_enable = true;
- dm_state->underscan_hborder = 64;
- dm_state->underscan_vborder = 32;
-
- amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream);
-
- /* Full dst, then underscan: x/y += border/2, width/height -= border */
- KUNIT_EXPECT_EQ(test, stream->dst.x, 32);
- KUNIT_EXPECT_EQ(test, stream->dst.y, 16);
- KUNIT_EXPECT_EQ(test, stream->dst.width, 1856);
- KUNIT_EXPECT_EQ(test, stream->dst.height, 1048);
-}
-
/* Tests for is_content_protection_different() */
struct dm_test_cp_ctx {
@@ -2113,14 +1913,6 @@ static struct kunit_case amdgpu_dm_tests[] = {
/* set_master_stream */
KUNIT_CASE(dm_test_master_stream_highest_refresh),
KUNIT_CASE(dm_test_master_stream_defaults_to_first),
- /* amdgpu_dm_update_stream_scaling_settings */
- KUNIT_CASE(dm_test_update_scaling_null_mode),
- KUNIT_CASE(dm_test_update_scaling_fullscreen_default),
- KUNIT_CASE(dm_test_update_scaling_rmx_full),
- KUNIT_CASE(dm_test_update_scaling_rmx_aspect_pillarbox),
- KUNIT_CASE(dm_test_update_scaling_rmx_aspect_letterbox),
- KUNIT_CASE(dm_test_update_scaling_rmx_center),
- KUNIT_CASE(dm_test_update_scaling_underscan),
/* is_content_protection_different */
KUNIT_CASE(dm_test_cp_diff_hdcp_type_change),
KUNIT_CASE(dm_test_cp_diff_reenable_mode_changed),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 13/49] drm/amd/display: move stutter quirk to quirks file
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (11 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 12/49] drm/amd/display: move scaling helper to connector Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 14/49] drm/amd/display: move watermarks table to pp_smu Fangzhi Zuo
` (35 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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]
dm_should_disable_stutter() and its amdgpu_stutter_quirk PCI match
table are self-contained quirk logic. Move them out of amdgpu_dm.c
into amdgpu_dm_quirks.c alongside the existing DMI quirk handling.
The helper is called from amdgpu_dm_init(), so it becomes a plain
exported function instead of STATIC_IFN_KUNIT, and its declaration
moves from the KUnit-only block to a regular prototype in amdgpu_dm.h.
Relocate its KUnit tests from the amdgpu_dm suite to the
amdgpu_dm_quirks suite. No functional change.
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 33 ----------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 4 +-
.../amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 32 +++++++++
.../amdgpu_dm/tests/amdgpu_dm_quirks_test.c | 65 +++++++++++++++++++
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 64 ------------------
5 files changed, 99 insertions(+), 99 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 e6452237ba70..c6b9cd1ce489 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -410,39 +410,6 @@ static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_
}
-struct amdgpu_stutter_quirk {
- u16 chip_vendor;
- u16 chip_device;
- u16 subsys_vendor;
- u16 subsys_device;
- u8 revision;
-};
-
-static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = {
- /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */
- { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 },
- { 0, 0, 0, 0, 0 },
-};
-
-STATIC_IFN_KUNIT bool dm_should_disable_stutter(struct pci_dev *pdev)
-{
- const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list;
-
- while (p && p->chip_device != 0) {
- if (pdev->vendor == p->chip_vendor &&
- pdev->device == p->chip_device &&
- pdev->subsystem_vendor == p->subsys_vendor &&
- pdev->subsystem_device == p->subsys_device &&
- pdev->revision == p->revision) {
- return true;
- }
- ++p;
- }
- return false;
-}
-EXPORT_IF_KUNIT(dm_should_disable_stutter);
-
-
void*
dm_allocate_gpu_mem(
struct amdgpu_device *adev,
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 03189f44efd4..617328497203 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1134,6 +1134,8 @@ bool amdgpu_dm_is_headless(struct amdgpu_device *adev);
bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc);
void retrieve_dmi_info(struct amdgpu_display_manager *dm);
+struct pci_dev;
+bool dm_should_disable_stutter(struct pci_dev *pdev);
void amdgpu_dm_emulated_link_detect(struct dc_link *link);
void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev,
@@ -1178,8 +1180,6 @@ bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
struct dm_crtc_state *new_state);
void set_multisync_trigger_params(struct dc_stream_state *stream);
void set_master_stream(struct dc_stream_state *stream_set[], int stream_count);
-struct pci_dev;
-bool dm_should_disable_stutter(struct pci_dev *pdev);
void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state);
void get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state,
struct dm_connector_state *new_con_state);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c
index 0a7602ed70b2..259966703cef 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c
@@ -178,3 +178,35 @@ void retrieve_dmi_info(struct amdgpu_display_manager *dm)
}
}
EXPORT_IF_KUNIT(retrieve_dmi_info);
+
+struct amdgpu_stutter_quirk {
+ u16 chip_vendor;
+ u16 chip_device;
+ u16 subsys_vendor;
+ u16 subsys_device;
+ u8 revision;
+};
+
+static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = {
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */
+ { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 },
+ { 0, 0, 0, 0, 0 },
+};
+
+bool dm_should_disable_stutter(struct pci_dev *pdev)
+{
+ const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list;
+
+ while (p && p->chip_device != 0) {
+ if (pdev->vendor == p->chip_vendor &&
+ pdev->device == p->chip_device &&
+ pdev->subsystem_vendor == p->subsys_vendor &&
+ pdev->subsystem_device == p->subsys_device &&
+ pdev->revision == p->revision) {
+ return true;
+ }
+ ++p;
+ }
+ return false;
+}
+EXPORT_IF_KUNIT(dm_should_disable_stutter);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c
index a09f31ee0a2a..150c53a68bed 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c
@@ -6,6 +6,7 @@
*/
#include <kunit/test.h>
+#include <linux/pci.h>
#include "dc.h"
#include "amdgpu_mode.h"
@@ -83,11 +84,75 @@ static void dm_test_quirks_no_dmi_match_both_false(struct kunit *test)
KUNIT_EXPECT_FALSE(test, dm->edp0_on_dp1_quirk);
}
+/* Tests for dm_should_disable_stutter() */
+
+/**
+ * dm_test_should_disable_stutter_match - Test the quirk device matches
+ * @test: The KUnit test context
+ */
+static void dm_test_should_disable_stutter_match(struct kunit *test)
+{
+ struct pci_dev *pdev;
+
+ pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, pdev);
+
+ pdev->vendor = 0x1002;
+ pdev->device = 0x15dd;
+ pdev->subsystem_vendor = 0x1002;
+ pdev->subsystem_device = 0x15dd;
+ pdev->revision = 0xc8;
+
+ KUNIT_EXPECT_TRUE(test, dm_should_disable_stutter(pdev));
+}
+
+/**
+ * dm_test_should_disable_stutter_no_match - Test a non-quirk device does not match
+ * @test: The KUnit test context
+ */
+static void dm_test_should_disable_stutter_no_match(struct kunit *test)
+{
+ struct pci_dev *pdev;
+
+ pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, pdev);
+
+ pdev->vendor = 0x1002;
+ pdev->device = 0x1234;
+
+ KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev));
+}
+
+/**
+ * dm_test_should_disable_stutter_revision_differs - Test a partial match (revision) fails
+ * @test: The KUnit test context
+ */
+static void dm_test_should_disable_stutter_revision_differs(struct kunit *test)
+{
+ struct pci_dev *pdev;
+
+ pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, pdev);
+
+ /* Everything matches the quirk except the revision */
+ pdev->vendor = 0x1002;
+ pdev->device = 0x15dd;
+ pdev->subsystem_vendor = 0x1002;
+ pdev->subsystem_device = 0x15dd;
+ pdev->revision = 0x00;
+
+ KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev));
+}
+
static struct kunit_case amdgpu_dm_quirks_tests[] = {
/* retrieve_dmi_info */
KUNIT_CASE(dm_test_quirks_aux_hpd_discon_reset),
KUNIT_CASE(dm_test_quirks_edp0_on_dp1_reset),
KUNIT_CASE(dm_test_quirks_no_dmi_match_both_false),
+ /* dm_should_disable_stutter */
+ KUNIT_CASE(dm_test_should_disable_stutter_match),
+ KUNIT_CASE(dm_test_should_disable_stutter_no_match),
+ KUNIT_CASE(dm_test_should_disable_stutter_revision_differs),
{}
};
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index 88ac5e4f6c04..72d5a4012a3c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -1737,66 +1737,6 @@ static void dm_test_per_frame_master_sync_skips_null_stream(struct kunit *test)
stream);
}
-/* Tests for dm_should_disable_stutter() */
-
-/**
- * dm_test_should_disable_stutter_match - Test the quirk device matches
- * @test: The KUnit test context
- */
-static void dm_test_should_disable_stutter_match(struct kunit *test)
-{
- struct pci_dev *pdev;
-
- pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, pdev);
-
- pdev->vendor = 0x1002;
- pdev->device = 0x15dd;
- pdev->subsystem_vendor = 0x1002;
- pdev->subsystem_device = 0x15dd;
- pdev->revision = 0xc8;
-
- KUNIT_EXPECT_TRUE(test, dm_should_disable_stutter(pdev));
-}
-
-/**
- * dm_test_should_disable_stutter_no_match - Test a non-quirk device does not match
- * @test: The KUnit test context
- */
-static void dm_test_should_disable_stutter_no_match(struct kunit *test)
-{
- struct pci_dev *pdev;
-
- pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, pdev);
-
- pdev->vendor = 0x1002;
- pdev->device = 0x1234;
-
- KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev));
-}
-
-/**
- * dm_test_should_disable_stutter_revision_differs - Test a partial match (revision) fails
- * @test: The KUnit test context
- */
-static void dm_test_should_disable_stutter_revision_differs(struct kunit *test)
-{
- struct pci_dev *pdev;
-
- pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, pdev);
-
- /* Everything matches the quirk except the revision */
- pdev->vendor = 0x1002;
- pdev->device = 0x15dd;
- pdev->subsystem_vendor = 0x1002;
- pdev->subsystem_device = 0x15dd;
- pdev->revision = 0x00;
-
- KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev));
-}
-
/* Tests for amdgpu_dm_apply_delay_after_dpcd_poweroff() */
/**
@@ -1938,10 +1878,6 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_per_frame_master_sync_single_stream),
KUNIT_CASE(dm_test_per_frame_master_sync_two_streams),
KUNIT_CASE(dm_test_per_frame_master_sync_skips_null_stream),
- /* dm_should_disable_stutter */
- KUNIT_CASE(dm_test_should_disable_stutter_match),
- KUNIT_CASE(dm_test_should_disable_stutter_no_match),
- KUNIT_CASE(dm_test_should_disable_stutter_revision_differs),
/* amdgpu_dm_apply_delay_after_dpcd_poweroff */
KUNIT_CASE(dm_test_apply_delay_null_sink),
KUNIT_CASE(dm_test_apply_delay_zero_wait),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 14/49] drm/amd/display: move watermarks table to pp_smu
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (12 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 13/49] drm/amd/display: move stutter quirk to quirks file Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 15/49] drm/amd/display: move GPU mem helpers to services Fangzhi Zuo
` (34 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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]
amdgpu_dm_smu_write_watermarks_table() pushes fixed dcn watermark
clock settings to the SMU on Navi1x. It belongs with the other
dc-pplib bridge code, so move it out of amdgpu_dm.c into
amdgpu_dm_pp_smu.c.
It is called from dm_resume(), so it becomes a plain exported function
declared in amdgpu_dm_pp_smu.h (now included by amdgpu_dm.c) instead of
STATIC_IFN_KUNIT in amdgpu_dm.h. Relocate its KUnit test from the
amdgpu_dm suite to the amdgpu_dm_pp_smu suite. No functional change.
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 53 +------------------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 1 -
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 52 ++++++++++++++++++
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 3 ++
.../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 20 +++++++
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 16 ------
6 files changed, 76 insertions(+), 69 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 c6b9cd1ce489..d49f0cd3b113 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -70,6 +70,7 @@
#include "amdgpu_dm_audio.h"
#include "amdgpu_dm_dmub.h"
#include "amdgpu_dm_connector.h"
+#include "amdgpu_dm_pp_smu.h"
#include "ivsrcid/ivsrcid_vislands30.h"
@@ -1372,58 +1373,6 @@ static void s3_handle_mst(struct drm_device *dev, bool suspend)
drm_connector_list_iter_end(&iter);
}
-STATIC_IFN_KUNIT int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev)
-{
- int ret = 0;
-
- /* This interface is for dGPU Navi1x.Linux dc-pplib interface depends
- * on window driver dc implementation.
- * For Navi1x, clock settings of dcn watermarks are fixed. the settings
- * should be passed to smu during boot up and resume from s3.
- * boot up: dc calculate dcn watermark clock settings within dc_create,
- * dcn20_resource_construct
- * then call pplib functions below to pass the settings to smu:
- * smu_set_watermarks_for_clock_ranges
- * smu_set_watermarks_table
- * navi10_set_watermarks_table
- * smu_write_watermarks_table
- *
- * For Renoir, clock settings of dcn watermark are also fixed values.
- * dc has implemented different flow for window driver:
- * dc_hardware_init / dc_set_power_state
- * dcn10_init_hw
- * notify_wm_ranges
- * set_wm_ranges
- * -- Linux
- * smu_set_watermarks_for_clock_ranges
- * renoir_set_watermarks_table
- * smu_write_watermarks_table
- *
- * For Linux,
- * dc_hardware_init -> amdgpu_dm_init
- * dc_set_power_state --> dm_resume
- *
- * therefore, this function apply to navi10/12/14 but not Renoir
- * *
- */
- switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
- case IP_VERSION(2, 0, 2):
- case IP_VERSION(2, 0, 0):
- break;
- default:
- return 0;
- }
-
- ret = amdgpu_dpm_write_watermarks_table(adev);
- if (ret) {
- drm_err(adev_to_drm(adev), "Failed to update WMTABLE!\n");
- return ret;
- }
-
- return 0;
-}
-EXPORT_IF_KUNIT(amdgpu_dm_smu_write_watermarks_table);
-
static int dm_oem_i2c_hw_init(struct amdgpu_device *adev)
{
struct amdgpu_display_manager *dm = &adev->dm;
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 617328497203..70220997cde9 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1157,7 +1157,6 @@ int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc,
struct dm_atomic_state *dm_atomic_get_new_state(struct drm_atomic_commit *state);
void dm_atomic_destroy_state(struct drm_private_obj *obj,
struct drm_private_state *state);
-int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev);
bool dm_should_update_native_cursor(struct drm_atomic_commit *state,
struct drm_crtc *old_plane_crtc,
struct drm_crtc *new_plane_crtc,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c
index 8431e164a0db..3280bfadc6a8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c
@@ -792,3 +792,55 @@ void dm_pp_get_funcs(
}
}
EXPORT_IF_KUNIT(dm_pp_get_funcs);
+
+int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev)
+{
+ int ret = 0;
+
+ /* This interface is for dGPU Navi1x.Linux dc-pplib interface depends
+ * on window driver dc implementation.
+ * For Navi1x, clock settings of dcn watermarks are fixed. the settings
+ * should be passed to smu during boot up and resume from s3.
+ * boot up: dc calculate dcn watermark clock settings within dc_create,
+ * dcn20_resource_construct
+ * then call pplib functions below to pass the settings to smu:
+ * smu_set_watermarks_for_clock_ranges
+ * smu_set_watermarks_table
+ * navi10_set_watermarks_table
+ * smu_write_watermarks_table
+ *
+ * For Renoir, clock settings of dcn watermark are also fixed values.
+ * dc has implemented different flow for window driver:
+ * dc_hardware_init / dc_set_power_state
+ * dcn10_init_hw
+ * notify_wm_ranges
+ * set_wm_ranges
+ * -- Linux
+ * smu_set_watermarks_for_clock_ranges
+ * renoir_set_watermarks_table
+ * smu_write_watermarks_table
+ *
+ * For Linux,
+ * dc_hardware_init -> amdgpu_dm_init
+ * dc_set_power_state --> dm_resume
+ *
+ * therefore, this function apply to navi10/12/14 but not Renoir
+ * *
+ */
+ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
+ case IP_VERSION(2, 0, 2):
+ case IP_VERSION(2, 0, 0):
+ break;
+ default:
+ return 0;
+ }
+
+ ret = amdgpu_dpm_write_watermarks_table(adev);
+ if (ret) {
+ drm_err(adev_to_drm(adev), "Failed to update WMTABLE!\n");
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_smu_write_watermarks_table);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h
index f918eb71f0d1..1d153d0e2f03 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h
@@ -11,6 +11,9 @@
struct amd_pp_display_configuration;
struct pp_smu_wm_range_sets;
struct dm_pp_wm_sets_with_clock_ranges_soc15;
+struct amdgpu_device;
+
+int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev);
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
void build_pm_display_cfg(struct amd_pp_display_configuration *pm_display_cfg,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c
index 8d1d26bfcc16..fbd07af60230 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c
@@ -16,6 +16,7 @@
#include "amdgpu_mode.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_pp_smu.h"
+#include "amdgpu_dm_kunit_test_helpers.h"
/* ---- Stub DPM layer ---- */
@@ -2321,6 +2322,23 @@ static void dm_test_nv_get_uclk_dpm_states_fail(struct kunit *test)
(int)PP_SMU_RESULT_FAIL);
}
+/* Tests for amdgpu_dm_smu_write_watermarks_table() */
+
+/**
+ * dm_test_smu_write_watermarks_table_default - Test watermarks table skips non-Navi1x IP versions
+ * @test: The KUnit test context
+ */
+static void dm_test_smu_write_watermarks_table_default(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+
+ /*
+ * A zeroed adev reports DCE IP version 0, which is not one of the
+ * Navi1x versions handled by the switch, so the function returns early.
+ */
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_smu_write_watermarks_table(adev), 0);
+}
+
static struct kunit_case dm_pp_smu_test_cases[] = {
/* get_default_clock_levels */
KUNIT_CASE(dm_test_default_clock_levels_display),
@@ -2440,6 +2458,8 @@ static struct kunit_case dm_pp_smu_test_cases[] = {
KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_ok),
KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_unsupported),
KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_fail),
+ /* amdgpu_dm_smu_write_watermarks_table */
+ KUNIT_CASE(dm_test_smu_write_watermarks_table_default),
{}
};
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index 72d5a4012a3c..49a1d2ae5588 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -316,21 +316,6 @@ static void dm_test_atomic_destroy_state_no_context(struct kunit *test)
dm_atomic_destroy_state(NULL, &dm_state->base);
}
-/**
- * dm_test_smu_write_watermarks_table_default - Test watermarks table skips non-Navi1x IP versions
- * @test: The KUnit test context
- */
-static void dm_test_smu_write_watermarks_table_default(struct kunit *test)
-{
- struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
-
- /*
- * A zeroed adev reports DCE IP version 0, which is not one of the
- * Navi1x versions handled by the switch, so the function returns early.
- */
- KUNIT_EXPECT_EQ(test, amdgpu_dm_smu_write_watermarks_table(adev), 0);
-}
-
/* Tests for dm_plane_layer_index_cmp() */
/**
@@ -1801,7 +1786,6 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_should_update_native_cursor_disable_native),
KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay),
KUNIT_CASE(dm_test_atomic_destroy_state_no_context),
- KUNIT_CASE(dm_test_smu_write_watermarks_table_default),
/* dm_plane_layer_index_cmp */
KUNIT_CASE(dm_test_plane_layer_index_cmp_equal),
KUNIT_CASE(dm_test_plane_layer_index_cmp_descending),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 15/49] drm/amd/display: move GPU mem helpers to services
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (13 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 14/49] drm/amd/display: move watermarks table to pp_smu Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 16/49] drm/amd/display: add FreeSync/VRR module Fangzhi Zuo
` (33 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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]
dm_allocate_gpu_mem() and dm_free_gpu_mem() are generic DM service
helpers that wrap amdgpu_bo_create_kernel()/amdgpu_bo_free_kernel()
and track allocations on adev->dm.da_list. Move them out of
amdgpu_dm.c into amdgpu_dm_services.c alongside the other dm_* service
callbacks.
Both are already public and declared in amdgpu_dm.h; their callers in
amdgpu_dm_dmub.c and amdgpu_dm_helpers.c are unaffected. No functional
change.
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 53 -------------------
.../display/amdgpu_dm/amdgpu_dm_services.c | 53 +++++++++++++++++++
2 files changed, 53 insertions(+), 53 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 d49f0cd3b113..dfc16f5e415a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -411,59 +411,6 @@ static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_
}
-void*
-dm_allocate_gpu_mem(
- struct amdgpu_device *adev,
- enum dc_gpu_mem_alloc_type type,
- size_t size,
- long long *addr)
-{
- struct dal_allocation *da;
- u32 domain = (type == DC_MEM_ALLOC_TYPE_GART) ?
- AMDGPU_GEM_DOMAIN_GTT : AMDGPU_GEM_DOMAIN_VRAM;
- int ret;
-
- da = kzalloc_obj(struct dal_allocation);
- if (!da)
- return NULL;
-
- ret = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE,
- domain, &da->bo,
- &da->gpu_addr, &da->cpu_ptr);
-
- *addr = da->gpu_addr;
-
- if (ret) {
- kfree(da);
- return NULL;
- }
-
- /* add da to list in dm */
- list_add(&da->list, &adev->dm.da_list);
-
- return da->cpu_ptr;
-}
-
-void
-dm_free_gpu_mem(
- struct amdgpu_device *adev,
- enum dc_gpu_mem_alloc_type type,
- void *pvMem)
-{
- struct dal_allocation *da;
-
- /* 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);
- list_del(&da->list);
- kfree(da);
- break;
- }
- }
-
-}
-
static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm)
{
struct mod_power_init_params init_data[MAX_NUM_EDP];
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 dbdda6ed12d4..6dbc8d1b4ecd 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
@@ -96,3 +96,56 @@ bool dm_query_extended_brightness_caps(struct dc_context *ctx,
return true;
}
EXPORT_IF_KUNIT(dm_query_extended_brightness_caps);
+
+void*
+dm_allocate_gpu_mem(
+ struct amdgpu_device *adev,
+ enum dc_gpu_mem_alloc_type type,
+ size_t size,
+ long long *addr)
+{
+ struct dal_allocation *da;
+ u32 domain = (type == DC_MEM_ALLOC_TYPE_GART) ?
+ AMDGPU_GEM_DOMAIN_GTT : AMDGPU_GEM_DOMAIN_VRAM;
+ int ret;
+
+ da = kzalloc_obj(*da);
+ if (!da)
+ return NULL;
+
+ ret = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE,
+ domain, &da->bo,
+ &da->gpu_addr, &da->cpu_ptr);
+
+ *addr = da->gpu_addr;
+
+ if (ret) {
+ kfree(da);
+ return NULL;
+ }
+
+ /* add da to list in dm */
+ list_add(&da->list, &adev->dm.da_list);
+
+ return da->cpu_ptr;
+}
+
+void
+dm_free_gpu_mem(
+ struct amdgpu_device *adev,
+ enum dc_gpu_mem_alloc_type type,
+ void *pvMem)
+{
+ struct dal_allocation *da;
+
+ /* 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);
+ list_del(&da->list);
+ kfree(da);
+ break;
+ }
+ }
+
+}
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 16/49] drm/amd/display: add FreeSync/VRR module
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (14 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 15/49] drm/amd/display: move GPU mem helpers to services Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 17/49] drm/amd/display: add cursor module Fangzhi Zuo
` (32 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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]
Group the FreeSync/VRR helpers from amdgpu_dm.c into a dedicated
amdgpu_dm_freesync.c and amdgpu_dm_freesync.h. The moved functions are:
is_dc_timing_adjust_needed, is_timing_unchanged_for_freesync,
set_freesync_fixed_config, reset_freesync_config_for_crtc,
get_freesync_config_for_crtc, update_freesync_state_on_stream,
update_stream_irq_parameters, amdgpu_dm_handle_vrr_transition.
The FreeSync/VRR helpers exposed by amdgpu_dm_freesync.h are called
from amdgpu_dm.c, so give the seven that lacked a namespace prefix the
standard amdgpu_dm_ prefix:
Relocate the corresponding KUnit tests into amdgpu_dm_freesync_test.c.
No functional change.
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 349 +-------------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 8 -
.../display/amdgpu_dm/amdgpu_dm_freesync.c | 362 ++++++++++++++
.../display/amdgpu_dm/amdgpu_dm_freesync.h | 65 +++
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../amdgpu_dm/tests/amdgpu_dm_freesync_test.c | 453 ++++++++++++++++++
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 417 ----------------
8 files changed, 894 insertions(+), 764 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
index d83878e35b61..5cb8c143e72f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
@@ -45,7 +45,8 @@ AMDGPUDM = \
amdgpu_dm_backlight.o \
amdgpu_dm_audio.o \
amdgpu_dm_dmub.o \
- amdgpu_dm_connector.o
+ amdgpu_dm_connector.o \
+ amdgpu_dm_freesync.o
ifdef CONFIG_DRM_AMD_DC_FP
AMDGPUDM += dc_fpu.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index dfc16f5e415a..c36eb8a1d2d1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -71,6 +71,7 @@
#include "amdgpu_dm_dmub.h"
#include "amdgpu_dm_connector.h"
#include "amdgpu_dm_pp_smu.h"
+#include "amdgpu_dm_freesync.h"
#include "ivsrcid/ivsrcid_vislands30.h"
@@ -127,7 +128,6 @@ MODULE_FIRMWARE(FIRMWARE_NAVI12_DMCU);
/* basic init/fini API */
static int amdgpu_dm_init(struct amdgpu_device *adev);
static void amdgpu_dm_fini(struct amdgpu_device *adev);
-STATIC_IFN_KUNIT void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state);
/*
* initializes drm_device display related structures, based on the information
@@ -147,10 +147,6 @@ STATIC_IFN_KUNIT void dm_enable_per_frame_crtc_master_sync(struct dc_state *cont
static int amdgpu_dm_atomic_check(struct drm_device *dev,
struct drm_atomic_commit *state);
-STATIC_IFN_KUNIT bool
-is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
- struct drm_crtc_state *new_crtc_state);
-
static inline void amdgpu_dm_exit_ips_for_hw_access(struct dc *dc)
{
if (dc->ctx->dmub_srv && !dc->ctx->dmub_srv->idle_exit_counter)
@@ -248,20 +244,6 @@ STATIC_IFN_KUNIT int dm_soft_reset(struct amdgpu_ip_block *ip_block)
}
EXPORT_IF_KUNIT(dm_soft_reset);
-STATIC_IFN_KUNIT bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
- struct dm_crtc_state *new_state)
-{
- if (new_state->stream->adjust.timing_adjust_pending)
- return true;
- if (new_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED)
- return true;
- else if (amdgpu_dm_crtc_vrr_active(old_state) != amdgpu_dm_crtc_vrr_active(new_state))
- return true;
- else
- return false;
-}
-EXPORT_IF_KUNIT(is_dc_timing_adjust_needed);
-
/*
* DC will program planes with their z-order determined by their ordering
* in the dc_surface_updates array. This comparator is used to sort them
@@ -1542,7 +1524,7 @@ static void dm_destroy_cached_state(struct amdgpu_device *adev)
for_each_new_crtc_in_state(dm->cached_state, crtc, new_crtc_state, i) {
new_crtc_state->active_changed = true;
dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
- reset_freesync_config_for_crtc(dm_new_crtc_state);
+ amdgpu_dm_reset_freesync_config_for_crtc(dm_new_crtc_state);
}
/*
@@ -3614,213 +3596,6 @@ static void prepare_flip_isr(struct amdgpu_crtc *acrtc)
acrtc->crtc_id);
}
-static void update_freesync_state_on_stream(
- struct amdgpu_display_manager *dm,
- struct dm_crtc_state *new_crtc_state,
- struct dc_stream_state *new_stream,
- struct dc_plane_state *surface,
- u32 flip_timestamp_in_us)
-{
- struct mod_vrr_params vrr_params;
- struct dc_info_packet vrr_infopacket = {0};
- struct amdgpu_device *adev = dm->adev;
- struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc);
- unsigned long flags;
- bool pack_sdp_v1_3 = false;
- struct amdgpu_dm_connector *aconn;
- enum vrr_packet_type packet_type = PACKET_TYPE_VRR;
-
- if (!new_stream)
- return;
-
- /*
- * TODO: Determine why min/max totals and vrefresh can be 0 here.
- * For now it's sufficient to just guard against these conditions.
- */
-
- if (!new_stream->timing.h_total || !new_stream->timing.v_total)
- return;
-
- spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
- vrr_params = acrtc->dm_irq_params.vrr_params;
-
- if (surface) {
- mod_freesync_handle_preflip(
- dm->freesync_module,
- surface,
- new_stream,
- flip_timestamp_in_us,
- &vrr_params);
-
- if (adev->family < AMDGPU_FAMILY_AI &&
- amdgpu_dm_crtc_vrr_active(new_crtc_state)) {
- mod_freesync_handle_v_update(dm->freesync_module,
- new_stream, &vrr_params);
-
- /* Need to call this before the frame ends. */
- dc_stream_adjust_vmin_vmax(dm->dc,
- new_crtc_state->stream,
- &vrr_params.adjust);
- }
- }
-
- aconn = (struct amdgpu_dm_connector *)new_stream->dm_stream_context;
-
- if (aconn && (aconn->as_type == FREESYNC_TYPE_PCON_IN_WHITELIST || aconn->vsdb_info.replay_mode)) {
- pack_sdp_v1_3 = aconn->pack_sdp_v1_3;
-
- if (aconn->vsdb_info.amd_vsdb_version == 1)
- packet_type = PACKET_TYPE_FS_V1;
- else if (aconn->vsdb_info.amd_vsdb_version == 2)
- packet_type = PACKET_TYPE_FS_V2;
- else if (aconn->vsdb_info.amd_vsdb_version == 3)
- packet_type = PACKET_TYPE_FS_V3;
-
- mod_build_adaptive_sync_infopacket(new_stream, aconn->as_type, NULL,
- &new_stream->adaptive_sync_infopacket);
- }
-
- mod_freesync_build_vrr_infopacket(
- dm->freesync_module,
- new_stream,
- &vrr_params,
- packet_type,
- TRANSFER_FUNC_UNKNOWN,
- &vrr_infopacket,
- pack_sdp_v1_3);
-
- new_crtc_state->freesync_vrr_info_changed |=
- (memcmp(&new_crtc_state->vrr_infopacket,
- &vrr_infopacket,
- sizeof(vrr_infopacket)) != 0);
-
- acrtc->dm_irq_params.vrr_params = vrr_params;
- new_crtc_state->vrr_infopacket = vrr_infopacket;
-
- new_stream->vrr_infopacket = vrr_infopacket;
- new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params);
-
- if (new_crtc_state->freesync_vrr_info_changed)
- drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d",
- new_crtc_state->base.crtc->base.id,
- (int)new_crtc_state->base.vrr_enabled,
- (int)vrr_params.state);
-
- spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
-}
-
-static void update_stream_irq_parameters(
- struct amdgpu_display_manager *dm,
- struct dm_crtc_state *new_crtc_state)
-{
- struct dc_stream_state *new_stream = new_crtc_state->stream;
- struct mod_vrr_params vrr_params;
- struct mod_freesync_config config = new_crtc_state->freesync_config;
- struct amdgpu_device *adev = dm->adev;
- struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc);
- unsigned long flags;
-
- if (!new_stream)
- return;
-
- /*
- * TODO: Determine why min/max totals and vrefresh can be 0 here.
- * For now it's sufficient to just guard against these conditions.
- */
- if (!new_stream->timing.h_total || !new_stream->timing.v_total)
- return;
-
- spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
- vrr_params = acrtc->dm_irq_params.vrr_params;
-
- if (new_crtc_state->vrr_supported &&
- config.min_refresh_in_uhz &&
- config.max_refresh_in_uhz) {
- /*
- * if freesync compatible mode was set, config.state will be set
- * in atomic check
- */
- if (config.state == VRR_STATE_ACTIVE_FIXED && config.fixed_refresh_in_uhz &&
- (!drm_atomic_crtc_needs_modeset(&new_crtc_state->base) ||
- new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED)) {
- vrr_params.max_refresh_in_uhz = config.max_refresh_in_uhz;
- vrr_params.min_refresh_in_uhz = config.min_refresh_in_uhz;
- vrr_params.fixed_refresh_in_uhz = config.fixed_refresh_in_uhz;
- vrr_params.state = VRR_STATE_ACTIVE_FIXED;
- } else {
- config.state = new_crtc_state->base.vrr_enabled ?
- VRR_STATE_ACTIVE_VARIABLE :
- VRR_STATE_INACTIVE;
- }
- } else {
- config.state = VRR_STATE_UNSUPPORTED;
- }
-
- mod_freesync_build_vrr_params(dm->freesync_module,
- new_stream,
- &config, &vrr_params);
-
- new_crtc_state->freesync_config = config;
- /* Copy state for access from DM IRQ handler */
- acrtc->dm_irq_params.freesync_config = config;
- acrtc->dm_irq_params.active_planes = new_crtc_state->active_planes;
- acrtc->dm_irq_params.vrr_params = vrr_params;
- spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
-}
-
-static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm,
- struct dm_crtc_state *old_state,
- struct dm_crtc_state *new_state)
-{
- struct amdgpu_device *adev = dm->adev;
- bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state);
- bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state);
-
- /* Only DCE gates vupdate on VRR, keep it enabled for DCN */
- bool vrr_gates_vupdate = amdgpu_ip_version(adev, DCE_HWIP, 0) == 0;
-
- if (!old_vrr_active && new_vrr_active) {
- /* Transition VRR inactive -> active:
- * While VRR is active, we must not disable vblank irq, as a
- * reenable after disable would compute bogus vblank/pflip
- * timestamps if it likely happened inside display front-porch.
- *
- * We also need vupdate irq for the actual core vblank handling
- * at end of vblank.
- */
- if (vrr_gates_vupdate)
- WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0);
- WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0);
- drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n",
- __func__, new_state->base.crtc->base.id);
-
- scoped_guard(mutex, &dm->dc_lock) {
- dc_exit_ips_for_hw_access(dm->dc);
- amdgpu_dm_psr_set_event(dm, new_state->stream, true,
- psr_event_vrr_transition, true);
- amdgpu_dm_replay_set_event(dm, new_state->stream, true,
- replay_event_vrr, true);
- }
- } else if (old_vrr_active && !new_vrr_active) {
- /* Transition VRR active -> inactive:
- * Allow vblank irq disable again for fixed refresh rate.
- */
- if (vrr_gates_vupdate)
- WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0);
- drm_crtc_vblank_put(new_state->base.crtc);
- drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n",
- __func__, new_state->base.crtc->base.id);
-
- scoped_guard(mutex, &dm->dc_lock) {
- dc_exit_ips_for_hw_access(dm->dc);
- amdgpu_dm_psr_set_event(dm, new_state->stream, false,
- psr_event_vrr_transition, false);
- amdgpu_dm_replay_set_event(dm, new_state->stream, false,
- replay_event_vrr, false);
- }
- }
-}
-
static void amdgpu_dm_commit_cursors(struct drm_atomic_commit *state)
{
struct drm_plane *plane;
@@ -4177,7 +3952,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
}
if (plane == pcrtc->primary)
- update_freesync_state_on_stream(
+ amdgpu_dm_update_freesync_state_on_stream(
dm,
acrtc_state,
acrtc_state->stream,
@@ -4295,7 +4070,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
* re-adjust the min/max bounds now that DC doesn't handle this
* as part of commit.
*/
- if (is_dc_timing_adjust_needed(dm_old_crtc_state, acrtc_state)) {
+ if (amdgpu_dm_is_dc_timing_adjust_needed(dm_old_crtc_state, acrtc_state)) {
spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
dc_stream_adjust_vmin_vmax(
dm->dc, acrtc_state->stream,
@@ -5344,7 +5119,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state)
dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
/* For freesync config update on crtc state and params for irq */
- update_stream_irq_parameters(dm, dm_new_crtc_state);
+ amdgpu_dm_update_stream_irq_parameters(dm, dm_new_crtc_state);
#ifdef CONFIG_DEBUG_FS
spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
@@ -5544,108 +5319,6 @@ static int do_aquire_global_lock(struct drm_device *dev,
return ret < 0 ? ret : 0;
}
-STATIC_IFN_KUNIT void get_freesync_config_for_crtc(
- struct dm_crtc_state *new_crtc_state,
- struct dm_connector_state *new_con_state)
-{
- struct mod_freesync_config config = {0};
- struct amdgpu_dm_connector *aconnector;
- struct drm_display_mode *mode = &new_crtc_state->base.mode;
- int vrefresh = drm_mode_vrefresh(mode);
- bool fs_vid_mode = false;
-
- if (new_con_state->base.connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- return;
-
- aconnector = to_amdgpu_dm_connector(new_con_state->base.connector);
-
- new_crtc_state->vrr_supported = new_con_state->freesync_capable &&
- vrefresh >= aconnector->min_vfreq &&
- vrefresh <= aconnector->max_vfreq;
-
- if (new_crtc_state->vrr_supported) {
- new_crtc_state->stream->ignore_msa_timing_param = true;
- fs_vid_mode = new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED;
-
- config.min_refresh_in_uhz = aconnector->min_vfreq * 1000000;
- config.max_refresh_in_uhz = aconnector->max_vfreq * 1000000;
- config.vsif_supported = true;
- config.btr = true;
-
- if (fs_vid_mode) {
- config.state = VRR_STATE_ACTIVE_FIXED;
- config.fixed_refresh_in_uhz = new_crtc_state->freesync_config.fixed_refresh_in_uhz;
- goto out;
- } else if (new_crtc_state->base.vrr_enabled) {
- config.state = VRR_STATE_ACTIVE_VARIABLE;
- } else {
- config.state = VRR_STATE_INACTIVE;
- }
- } else {
- config.state = VRR_STATE_UNSUPPORTED;
- }
-out:
- new_crtc_state->freesync_config = config;
-}
-EXPORT_IF_KUNIT(get_freesync_config_for_crtc);
-
-STATIC_IFN_KUNIT void reset_freesync_config_for_crtc(
- struct dm_crtc_state *new_crtc_state)
-{
- new_crtc_state->vrr_supported = false;
-
- memset(&new_crtc_state->vrr_infopacket, 0,
- sizeof(new_crtc_state->vrr_infopacket));
-}
-EXPORT_IF_KUNIT(reset_freesync_config_for_crtc);
-
-STATIC_IFN_KUNIT bool
-is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
- struct drm_crtc_state *new_crtc_state)
-{
- const struct drm_display_mode *old_mode, *new_mode;
-
- if (!old_crtc_state || !new_crtc_state)
- return false;
-
- old_mode = &old_crtc_state->mode;
- new_mode = &new_crtc_state->mode;
-
- if (old_mode->clock == new_mode->clock &&
- old_mode->hdisplay == new_mode->hdisplay &&
- old_mode->vdisplay == new_mode->vdisplay &&
- old_mode->htotal == new_mode->htotal &&
- old_mode->vtotal != new_mode->vtotal &&
- old_mode->hsync_start == new_mode->hsync_start &&
- old_mode->vsync_start != new_mode->vsync_start &&
- old_mode->hsync_end == new_mode->hsync_end &&
- old_mode->vsync_end != new_mode->vsync_end &&
- old_mode->hskew == new_mode->hskew &&
- old_mode->vscan == new_mode->vscan &&
- (old_mode->vsync_end - old_mode->vsync_start) ==
- (new_mode->vsync_end - new_mode->vsync_start))
- return true;
-
- return false;
-}
-EXPORT_IF_KUNIT(is_timing_unchanged_for_freesync);
-
-STATIC_IFN_KUNIT void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state)
-{
- u64 num, den, res;
- struct drm_crtc_state *new_crtc_state = &dm_new_crtc_state->base;
-
- dm_new_crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED;
-
- num = (unsigned long long)new_crtc_state->mode.clock * 1000 * 1000000;
- den = (unsigned long long)new_crtc_state->mode.htotal *
- (unsigned long long)new_crtc_state->mode.vtotal;
-
- res = div_u64(num, den);
- dm_new_crtc_state->freesync_config.fixed_refresh_in_uhz = res;
-}
-EXPORT_IF_KUNIT(set_freesync_fixed_config);
-
static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
struct drm_atomic_commit *state,
struct drm_crtc *crtc,
@@ -5742,7 +5415,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
*/
if (amdgpu_freesync_vid_mode &&
dm_new_crtc_state->stream &&
- is_timing_unchanged_for_freesync(new_crtc_state, old_crtc_state))
+ amdgpu_dm_is_timing_unchanged_for_freesync(new_crtc_state, old_crtc_state))
goto skip_modeset;
if (dm_new_crtc_state->stream &&
@@ -5784,14 +5457,14 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
if (amdgpu_freesync_vid_mode && dm_new_crtc_state->stream &&
dc_is_stream_unchanged(new_stream, dm_old_crtc_state->stream) &&
dc_is_stream_scaling_unchanged(new_stream, dm_old_crtc_state->stream) &&
- is_timing_unchanged_for_freesync(new_crtc_state,
+ amdgpu_dm_is_timing_unchanged_for_freesync(new_crtc_state,
old_crtc_state)) {
new_crtc_state->mode_changed = false;
drm_dbg_driver(adev_to_drm(adev),
"Mode change not required for front porch change, setting mode_changed to %d",
new_crtc_state->mode_changed);
- set_freesync_fixed_config(dm_new_crtc_state);
+ amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
goto skip_modeset;
} else if (amdgpu_freesync_vid_mode && aconnector &&
@@ -5801,7 +5474,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
if (!drm_mode_equal(&new_crtc_state->mode, high_mode))
- set_freesync_fixed_config(dm_new_crtc_state);
+ amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
}
ret = dm_atomic_get_state(state, &dm_state);
@@ -5823,7 +5496,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
dc_stream_release(dm_old_crtc_state->stream);
dm_new_crtc_state->stream = NULL;
- reset_freesync_config_for_crtc(dm_new_crtc_state);
+ amdgpu_dm_reset_freesync_config_for_crtc(dm_new_crtc_state);
*lock_and_validation_needed = true;
@@ -5911,7 +5584,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
}
/* Update Freesync settings. */
- get_freesync_config_for_crtc(dm_new_crtc_state,
+ amdgpu_dm_get_freesync_config_for_crtc(dm_new_crtc_state,
dm_new_conn_state);
return ret;
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 70220997cde9..1ef92a28e3e9 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1172,16 +1172,8 @@ void dm_get_plane_scale(struct drm_plane_state *plane_state,
int *out_plane_scale_w, int *out_plane_scale_h);
bool is_scaling_state_different(const struct dm_connector_state *dm_state,
const struct dm_connector_state *old_dm_state);
-bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
- struct drm_crtc_state *new_crtc_state);
-void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state);
-bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
- struct dm_crtc_state *new_state);
void set_multisync_trigger_params(struct dc_stream_state *stream);
void set_master_stream(struct dc_stream_state *stream_set[], int stream_count);
-void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state);
-void get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state,
- struct dm_connector_state *new_con_state);
void dm_enable_per_frame_crtc_master_sync(struct dc_state *context);
struct hdcp_workqueue;
bool is_content_protection_different(struct drm_crtc_state *new_crtc_state,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c
new file mode 100644
index 000000000000..7e484adb4120
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: AMD
+ *
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_vblank.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_crtc.h"
+#include "amdgpu_dm_psr.h"
+#include "amdgpu_dm_replay.h"
+#include "amdgpu_dm_freesync.h"
+#include "dm_helpers.h"
+#include "modules/inc/mod_freesync.h"
+
+bool amdgpu_dm_is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
+ struct dm_crtc_state *new_state)
+{
+ if (new_state->stream->adjust.timing_adjust_pending)
+ return true;
+ if (new_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED)
+ return true;
+ else if (amdgpu_dm_crtc_vrr_active(old_state) != amdgpu_dm_crtc_vrr_active(new_state))
+ return true;
+ else
+ return false;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_is_dc_timing_adjust_needed);
+
+bool
+amdgpu_dm_is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
+ struct drm_crtc_state *new_crtc_state)
+{
+ const struct drm_display_mode *old_mode, *new_mode;
+
+ if (!old_crtc_state || !new_crtc_state)
+ return false;
+
+ old_mode = &old_crtc_state->mode;
+ new_mode = &new_crtc_state->mode;
+
+ if (old_mode->clock == new_mode->clock &&
+ old_mode->hdisplay == new_mode->hdisplay &&
+ old_mode->vdisplay == new_mode->vdisplay &&
+ old_mode->htotal == new_mode->htotal &&
+ old_mode->vtotal != new_mode->vtotal &&
+ old_mode->hsync_start == new_mode->hsync_start &&
+ old_mode->vsync_start != new_mode->vsync_start &&
+ old_mode->hsync_end == new_mode->hsync_end &&
+ old_mode->vsync_end != new_mode->vsync_end &&
+ old_mode->hskew == new_mode->hskew &&
+ old_mode->vscan == new_mode->vscan &&
+ (old_mode->vsync_end - old_mode->vsync_start) ==
+ (new_mode->vsync_end - new_mode->vsync_start))
+ return true;
+
+ return false;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_is_timing_unchanged_for_freesync);
+
+void amdgpu_dm_set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state)
+{
+ u64 num, den, res;
+ struct drm_crtc_state *new_crtc_state = &dm_new_crtc_state->base;
+
+ dm_new_crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED;
+
+ num = (unsigned long long)new_crtc_state->mode.clock * 1000 * 1000000;
+ den = (unsigned long long)new_crtc_state->mode.htotal *
+ (unsigned long long)new_crtc_state->mode.vtotal;
+
+ res = div_u64(num, den);
+ dm_new_crtc_state->freesync_config.fixed_refresh_in_uhz = res;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_set_freesync_fixed_config);
+
+void amdgpu_dm_reset_freesync_config_for_crtc(
+ struct dm_crtc_state *new_crtc_state)
+{
+ new_crtc_state->vrr_supported = false;
+
+ memset(&new_crtc_state->vrr_infopacket, 0,
+ sizeof(new_crtc_state->vrr_infopacket));
+}
+EXPORT_IF_KUNIT(amdgpu_dm_reset_freesync_config_for_crtc);
+
+void amdgpu_dm_get_freesync_config_for_crtc(
+ struct dm_crtc_state *new_crtc_state,
+ struct dm_connector_state *new_con_state)
+{
+ struct mod_freesync_config config = {0};
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *mode = &new_crtc_state->base.mode;
+ int vrefresh = drm_mode_vrefresh(mode);
+ bool fs_vid_mode = false;
+
+ if (new_con_state->base.connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ return;
+
+ aconnector = to_amdgpu_dm_connector(new_con_state->base.connector);
+
+ new_crtc_state->vrr_supported = new_con_state->freesync_capable &&
+ vrefresh >= aconnector->min_vfreq &&
+ vrefresh <= aconnector->max_vfreq;
+
+ if (new_crtc_state->vrr_supported) {
+ new_crtc_state->stream->ignore_msa_timing_param = true;
+ fs_vid_mode = new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED;
+
+ config.min_refresh_in_uhz = aconnector->min_vfreq * 1000000;
+ config.max_refresh_in_uhz = aconnector->max_vfreq * 1000000;
+ config.vsif_supported = true;
+ config.btr = true;
+
+ if (fs_vid_mode) {
+ config.state = VRR_STATE_ACTIVE_FIXED;
+ config.fixed_refresh_in_uhz = new_crtc_state->freesync_config.fixed_refresh_in_uhz;
+ goto out;
+ } else if (new_crtc_state->base.vrr_enabled) {
+ config.state = VRR_STATE_ACTIVE_VARIABLE;
+ } else {
+ config.state = VRR_STATE_INACTIVE;
+ }
+ } else {
+ config.state = VRR_STATE_UNSUPPORTED;
+ }
+out:
+ new_crtc_state->freesync_config = config;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_get_freesync_config_for_crtc);
+
+void amdgpu_dm_update_freesync_state_on_stream(
+ struct amdgpu_display_manager *dm,
+ struct dm_crtc_state *new_crtc_state,
+ struct dc_stream_state *new_stream,
+ struct dc_plane_state *surface,
+ u32 flip_timestamp_in_us)
+{
+ struct mod_vrr_params vrr_params;
+ struct dc_info_packet vrr_infopacket = {0};
+ struct amdgpu_device *adev = dm->adev;
+ struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc);
+ unsigned long flags;
+ bool pack_sdp_v1_3 = false;
+ struct amdgpu_dm_connector *aconn;
+ enum vrr_packet_type packet_type = PACKET_TYPE_VRR;
+
+ if (!new_stream)
+ return;
+
+ /*
+ * TODO: Determine why min/max totals and vrefresh can be 0 here.
+ * For now it's sufficient to just guard against these conditions.
+ */
+
+ if (!new_stream->timing.h_total || !new_stream->timing.v_total)
+ return;
+
+ spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
+ vrr_params = acrtc->dm_irq_params.vrr_params;
+
+ if (surface) {
+ mod_freesync_handle_preflip(
+ dm->freesync_module,
+ surface,
+ new_stream,
+ flip_timestamp_in_us,
+ &vrr_params);
+
+ if (adev->family < AMDGPU_FAMILY_AI &&
+ amdgpu_dm_crtc_vrr_active(new_crtc_state)) {
+ mod_freesync_handle_v_update(dm->freesync_module,
+ new_stream, &vrr_params);
+
+ /* Need to call this before the frame ends. */
+ dc_stream_adjust_vmin_vmax(dm->dc,
+ new_crtc_state->stream,
+ &vrr_params.adjust);
+ }
+ }
+
+ aconn = (struct amdgpu_dm_connector *)new_stream->dm_stream_context;
+
+ if (aconn && (aconn->as_type == FREESYNC_TYPE_PCON_IN_WHITELIST || aconn->vsdb_info.replay_mode)) {
+ pack_sdp_v1_3 = aconn->pack_sdp_v1_3;
+
+ if (aconn->vsdb_info.amd_vsdb_version == 1)
+ packet_type = PACKET_TYPE_FS_V1;
+ else if (aconn->vsdb_info.amd_vsdb_version == 2)
+ packet_type = PACKET_TYPE_FS_V2;
+ else if (aconn->vsdb_info.amd_vsdb_version == 3)
+ packet_type = PACKET_TYPE_FS_V3;
+
+ mod_build_adaptive_sync_infopacket(new_stream, aconn->as_type, NULL,
+ &new_stream->adaptive_sync_infopacket);
+ }
+
+ mod_freesync_build_vrr_infopacket(
+ dm->freesync_module,
+ new_stream,
+ &vrr_params,
+ packet_type,
+ TRANSFER_FUNC_UNKNOWN,
+ &vrr_infopacket,
+ pack_sdp_v1_3);
+
+ new_crtc_state->freesync_vrr_info_changed |=
+ (memcmp(&new_crtc_state->vrr_infopacket,
+ &vrr_infopacket,
+ sizeof(vrr_infopacket)) != 0);
+
+ acrtc->dm_irq_params.vrr_params = vrr_params;
+ new_crtc_state->vrr_infopacket = vrr_infopacket;
+
+ new_stream->vrr_infopacket = vrr_infopacket;
+ new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params);
+
+ if (new_crtc_state->freesync_vrr_info_changed)
+ drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d",
+ new_crtc_state->base.crtc->base.id,
+ (int)new_crtc_state->base.vrr_enabled,
+ (int)vrr_params.state);
+
+ spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
+}
+
+void amdgpu_dm_update_stream_irq_parameters(
+ struct amdgpu_display_manager *dm,
+ struct dm_crtc_state *new_crtc_state)
+{
+ struct dc_stream_state *new_stream = new_crtc_state->stream;
+ struct mod_vrr_params vrr_params;
+ struct mod_freesync_config config = new_crtc_state->freesync_config;
+ struct amdgpu_device *adev = dm->adev;
+ struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc);
+ unsigned long flags;
+
+ if (!new_stream)
+ return;
+
+ /*
+ * TODO: Determine why min/max totals and vrefresh can be 0 here.
+ * For now it's sufficient to just guard against these conditions.
+ */
+ if (!new_stream->timing.h_total || !new_stream->timing.v_total)
+ return;
+
+ spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
+ vrr_params = acrtc->dm_irq_params.vrr_params;
+
+ if (new_crtc_state->vrr_supported &&
+ config.min_refresh_in_uhz &&
+ config.max_refresh_in_uhz) {
+ /*
+ * if freesync compatible mode was set, config.state will be set
+ * in atomic check
+ */
+ if (config.state == VRR_STATE_ACTIVE_FIXED && config.fixed_refresh_in_uhz &&
+ (!drm_atomic_crtc_needs_modeset(&new_crtc_state->base) ||
+ new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED)) {
+ vrr_params.max_refresh_in_uhz = config.max_refresh_in_uhz;
+ vrr_params.min_refresh_in_uhz = config.min_refresh_in_uhz;
+ vrr_params.fixed_refresh_in_uhz = config.fixed_refresh_in_uhz;
+ vrr_params.state = VRR_STATE_ACTIVE_FIXED;
+ } else {
+ config.state = new_crtc_state->base.vrr_enabled ?
+ VRR_STATE_ACTIVE_VARIABLE :
+ VRR_STATE_INACTIVE;
+ }
+ } else {
+ config.state = VRR_STATE_UNSUPPORTED;
+ }
+
+ mod_freesync_build_vrr_params(dm->freesync_module,
+ new_stream,
+ &config, &vrr_params);
+
+ new_crtc_state->freesync_config = config;
+ /* Copy state for access from DM IRQ handler */
+ acrtc->dm_irq_params.freesync_config = config;
+ acrtc->dm_irq_params.active_planes = new_crtc_state->active_planes;
+ acrtc->dm_irq_params.vrr_params = vrr_params;
+ spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
+}
+
+void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm,
+ struct dm_crtc_state *old_state,
+ struct dm_crtc_state *new_state)
+{
+ struct amdgpu_device *adev = dm->adev;
+ bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state);
+ bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state);
+
+ /* Only DCE gates vupdate on VRR, keep it enabled for DCN */
+ bool vrr_gates_vupdate = amdgpu_ip_version(adev, DCE_HWIP, 0) == 0;
+
+ if (!old_vrr_active && new_vrr_active) {
+ /* Transition VRR inactive -> active:
+ * While VRR is active, we must not disable vblank irq, as a
+ * reenable after disable would compute bogus vblank/pflip
+ * timestamps if it likely happened inside display front-porch.
+ *
+ * We also need vupdate irq for the actual core vblank handling
+ * at end of vblank.
+ */
+ if (vrr_gates_vupdate)
+ WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0);
+ WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0);
+ drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n",
+ __func__, new_state->base.crtc->base.id);
+
+ scoped_guard(mutex, &dm->dc_lock) {
+ dc_exit_ips_for_hw_access(dm->dc);
+ amdgpu_dm_psr_set_event(dm, new_state->stream, true,
+ psr_event_vrr_transition, true);
+ amdgpu_dm_replay_set_event(dm, new_state->stream, true,
+ replay_event_vrr, true);
+ }
+ } else if (old_vrr_active && !new_vrr_active) {
+ /* Transition VRR active -> inactive:
+ * Allow vblank irq disable again for fixed refresh rate.
+ */
+ if (vrr_gates_vupdate)
+ WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0);
+ drm_crtc_vblank_put(new_state->base.crtc);
+ drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n",
+ __func__, new_state->base.crtc->base.id);
+
+ scoped_guard(mutex, &dm->dc_lock) {
+ dc_exit_ips_for_hw_access(dm->dc);
+ amdgpu_dm_psr_set_event(dm, new_state->stream, false,
+ psr_event_vrr_transition, false);
+ amdgpu_dm_replay_set_event(dm, new_state->stream, false,
+ replay_event_vrr, false);
+ }
+ }
+}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h
new file mode 100644
index 000000000000..24c410306284
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: AMD
+ *
+ */
+
+#ifndef __AMDGPU_DM_FREESYNC_H__
+#define __AMDGPU_DM_FREESYNC_H__
+
+#include <linux/types.h>
+
+struct amdgpu_display_manager;
+struct dm_crtc_state;
+struct dm_connector_state;
+struct dc_stream_state;
+struct dc_plane_state;
+struct drm_crtc_state;
+
+bool amdgpu_dm_is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
+ struct dm_crtc_state *new_state);
+
+bool amdgpu_dm_is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
+ struct drm_crtc_state *new_crtc_state);
+
+void amdgpu_dm_set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state);
+
+void amdgpu_dm_reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state);
+
+void amdgpu_dm_get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state,
+ struct dm_connector_state *new_con_state);
+
+void amdgpu_dm_update_freesync_state_on_stream(struct amdgpu_display_manager *dm,
+ struct dm_crtc_state *new_crtc_state,
+ struct dc_stream_state *new_stream,
+ struct dc_plane_state *surface,
+ u32 flip_timestamp_in_us);
+
+void amdgpu_dm_update_stream_irq_parameters(struct amdgpu_display_manager *dm,
+ struct dm_crtc_state *new_crtc_state);
+
+void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm,
+ struct dm_crtc_state *old_state,
+ struct dm_crtc_state *new_state);
+
+#endif /* __AMDGPU_DM_FREESYNC_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index 1592e8dae1a9..e58ffc4dcd84 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_plane_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_freesync_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c
new file mode 100644
index 000000000000..0ae636dcbbe4
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_freesync.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_modes.h>
+
+#include "dc.h"
+#include "inc/core_types.h"
+#include "amd_shared.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_freesync.h"
+#include "amdgpu_dm_kunit_test_helpers.h"
+
+/* Tests for amdgpu_dm_is_timing_unchanged_for_freesync() */
+
+/**
+ * dm_test_timing_unchanged_null_args - Test NULL crtc states return false
+ * @test: The KUnit test context
+ */
+static void dm_test_timing_unchanged_null_args(struct kunit *test)
+{
+ struct drm_crtc_state crtc_state = { 0 };
+
+ KUNIT_EXPECT_FALSE(test,
+ amdgpu_dm_is_timing_unchanged_for_freesync(NULL, &crtc_state));
+ KUNIT_EXPECT_FALSE(test,
+ amdgpu_dm_is_timing_unchanged_for_freesync(&crtc_state, NULL));
+}
+
+/**
+ * dm_test_timing_unchanged_identical_modes - Test identical modes are not "unchanged"
+ * @test: The KUnit test context
+ *
+ * The helper only returns true when vtotal/vsync shift (vrr) while the rest
+ * of the timing stays fixed, so identical modes must return false.
+ */
+static void dm_test_timing_unchanged_identical_modes(struct kunit *test)
+{
+ struct drm_crtc_state old_state = { 0 };
+ struct drm_crtc_state new_state = { 0 };
+
+ old_state.mode.clock = 148500;
+ old_state.mode.hdisplay = 1920;
+ old_state.mode.vdisplay = 1080;
+ old_state.mode.htotal = 2200;
+ old_state.mode.vtotal = 1125;
+ new_state.mode = old_state.mode;
+
+ KUNIT_EXPECT_FALSE(test,
+ amdgpu_dm_is_timing_unchanged_for_freesync(&old_state, &new_state));
+}
+
+/**
+ * dm_test_timing_unchanged_vrr_shift - Test vrr-style vtotal/vsync shift is detected
+ * @test: The KUnit test context
+ */
+static void dm_test_timing_unchanged_vrr_shift(struct kunit *test)
+{
+ struct drm_crtc_state old_state = { 0 };
+ struct drm_crtc_state new_state = { 0 };
+
+ old_state.mode.clock = 148500;
+ old_state.mode.hdisplay = 1920;
+ old_state.mode.vdisplay = 1080;
+ old_state.mode.htotal = 2200;
+ old_state.mode.vtotal = 1125;
+ old_state.mode.hsync_start = 2008;
+ old_state.mode.vsync_start = 1084;
+ old_state.mode.hsync_end = 2052;
+ old_state.mode.vsync_end = 1089;
+
+ /* Same horizontal timing, vertical totals/sync shifted by 125 lines */
+ new_state.mode = old_state.mode;
+ new_state.mode.vtotal = 1250;
+ new_state.mode.vsync_start = 1209;
+ new_state.mode.vsync_end = 1214;
+
+ KUNIT_EXPECT_TRUE(test,
+ amdgpu_dm_is_timing_unchanged_for_freesync(&old_state, &new_state));
+}
+
+/**
+ * dm_test_timing_unchanged_clock_changed - Test pixel clock change returns false
+ * @test: The KUnit test context
+ */
+static void dm_test_timing_unchanged_clock_changed(struct kunit *test)
+{
+ struct drm_crtc_state old_state = { 0 };
+ struct drm_crtc_state new_state = { 0 };
+
+ old_state.mode.clock = 148500;
+ old_state.mode.htotal = 2200;
+ old_state.mode.vtotal = 1125;
+ old_state.mode.vsync_start = 1084;
+ old_state.mode.vsync_end = 1089;
+
+ new_state.mode = old_state.mode;
+ new_state.mode.clock = 297000;
+ new_state.mode.vtotal = 1250;
+ new_state.mode.vsync_start = 1209;
+ new_state.mode.vsync_end = 1214;
+
+ KUNIT_EXPECT_FALSE(test,
+ amdgpu_dm_is_timing_unchanged_for_freesync(&old_state, &new_state));
+}
+
+/* Tests for amdgpu_dm_set_freesync_fixed_config() */
+
+/**
+ * dm_test_set_freesync_fixed_config_60hz - Test fixed refresh computed for 1080p60
+ * @test: The KUnit test context
+ */
+static void dm_test_set_freesync_fixed_config_60hz(struct kunit *test)
+{
+ struct dm_crtc_state dm_crtc_state = { 0 };
+
+ dm_crtc_state.base.mode.clock = 148500;
+ dm_crtc_state.base.mode.htotal = 2200;
+ dm_crtc_state.base.mode.vtotal = 1125;
+
+ amdgpu_dm_set_freesync_fixed_config(&dm_crtc_state);
+
+ KUNIT_EXPECT_EQ(test, (int)dm_crtc_state.freesync_config.state,
+ (int)VRR_STATE_ACTIVE_FIXED);
+ /* 148500 kHz / (2200 * 1125) = 60 Hz = 60000000 uHz */
+ KUNIT_EXPECT_EQ(test, dm_crtc_state.freesync_config.fixed_refresh_in_uhz,
+ 60000000U);
+}
+
+/* Tests for amdgpu_dm_is_dc_timing_adjust_needed() */
+
+/**
+ * dm_test_dc_timing_adjust_pending - Test a pending hw timing adjust forces true
+ * @test: The KUnit test context
+ */
+static void dm_test_dc_timing_adjust_pending(struct kunit *test)
+{
+ struct dm_crtc_state *old_state, *new_state;
+ struct dc_stream_state *stream;
+
+ old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
+ new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+
+ new_state->stream = stream;
+ stream->adjust.timing_adjust_pending = 1;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state));
+}
+
+/**
+ * dm_test_dc_timing_adjust_active_fixed - Test VRR active-fixed forces true
+ * @test: The KUnit test context
+ */
+static void dm_test_dc_timing_adjust_active_fixed(struct kunit *test)
+{
+ struct dm_crtc_state *old_state, *new_state;
+ struct dc_stream_state *stream;
+
+ old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
+ new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+
+ new_state->stream = stream;
+ new_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state));
+}
+
+/**
+ * dm_test_dc_timing_adjust_vrr_toggle - Test a change in vrr active state forces true
+ * @test: The KUnit test context
+ */
+static void dm_test_dc_timing_adjust_vrr_toggle(struct kunit *test)
+{
+ struct dm_crtc_state *old_state, *new_state;
+ struct dc_stream_state *stream;
+
+ old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
+ new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+
+ new_state->stream = stream;
+ old_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE;
+ new_state->freesync_config.state = VRR_STATE_INACTIVE;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state));
+}
+
+/**
+ * dm_test_dc_timing_adjust_not_needed - Test steady-state timing needs no adjust
+ * @test: The KUnit test context
+ */
+static void dm_test_dc_timing_adjust_not_needed(struct kunit *test)
+{
+ struct dm_crtc_state *old_state, *new_state;
+ struct dc_stream_state *stream;
+
+ old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
+ new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+
+ new_state->stream = stream;
+ old_state->freesync_config.state = VRR_STATE_INACTIVE;
+ new_state->freesync_config.state = VRR_STATE_INACTIVE;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state));
+}
+
+/* Tests for amdgpu_dm_get_freesync_config_for_crtc() */
+
+struct dm_test_freesync_ctx {
+ struct amdgpu_dm_connector *aconnector;
+ struct dm_crtc_state *crtc_state;
+ struct dm_connector_state *conn_state;
+ struct dc_stream_state *stream;
+};
+
+static struct dm_test_freesync_ctx *dm_test_freesync_ctx_alloc(struct kunit *test)
+{
+ struct dm_test_freesync_ctx *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector);
+ ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state);
+ ctx->conn_state = kunit_kzalloc(test, sizeof(*ctx->conn_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state);
+ ctx->stream = dm_kunit_alloc_stream(test, NULL);
+
+ ctx->conn_state->base.connector = &ctx->aconnector->base;
+ ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_DisplayPort;
+ ctx->crtc_state->stream = ctx->stream;
+
+ /* 1080p60 timing so drm_mode_vrefresh() == 60 */
+ ctx->crtc_state->base.mode.clock = 148500;
+ ctx->crtc_state->base.mode.htotal = 2200;
+ ctx->crtc_state->base.mode.vtotal = 1125;
+
+ return ctx;
+}
+
+/**
+ * dm_test_freesync_config_writeback - Test writeback connector is left untouched
+ * @test: The KUnit test context
+ */
+static void dm_test_freesync_config_writeback(struct kunit *test)
+{
+ struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
+
+ ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK;
+ ctx->conn_state->freesync_capable = true;
+ ctx->aconnector->min_vfreq = 48;
+ ctx->aconnector->max_vfreq = 120;
+ ctx->crtc_state->vrr_supported = true; /* sentinel: must stay set */
+
+ amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
+
+ /* Writeback: early return leaves vrr_supported sentinel untouched */
+ KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported);
+}
+
+/**
+ * dm_test_freesync_config_not_capable - Test a non-freesync sink reports UNSUPPORTED
+ * @test: The KUnit test context
+ */
+static void dm_test_freesync_config_not_capable(struct kunit *test)
+{
+ struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
+
+ ctx->conn_state->freesync_capable = false;
+ ctx->aconnector->min_vfreq = 48;
+ ctx->aconnector->max_vfreq = 120;
+
+ amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
+
+ KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported);
+ KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
+ (int)VRR_STATE_UNSUPPORTED);
+}
+
+/**
+ * dm_test_freesync_config_out_of_range - Test a refresh outside the range is UNSUPPORTED
+ * @test: The KUnit test context
+ */
+static void dm_test_freesync_config_out_of_range(struct kunit *test)
+{
+ struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
+
+ ctx->conn_state->freesync_capable = true;
+ ctx->aconnector->min_vfreq = 90; /* 60 < 90 -> out of range */
+ ctx->aconnector->max_vfreq = 120;
+
+ amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
+
+ KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported);
+ KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
+ (int)VRR_STATE_UNSUPPORTED);
+}
+
+/**
+ * dm_test_freesync_config_active_variable - Test vrr_enabled yields ACTIVE_VARIABLE
+ * @test: The KUnit test context
+ */
+static void dm_test_freesync_config_active_variable(struct kunit *test)
+{
+ struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
+
+ ctx->conn_state->freesync_capable = true;
+ ctx->aconnector->min_vfreq = 48;
+ ctx->aconnector->max_vfreq = 120;
+ ctx->crtc_state->base.vrr_enabled = true;
+
+ amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
+
+ KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported);
+ KUNIT_EXPECT_TRUE(test, ctx->stream->ignore_msa_timing_param);
+ KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
+ (int)VRR_STATE_ACTIVE_VARIABLE);
+ KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.min_refresh_in_uhz,
+ 48000000U);
+ KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.max_refresh_in_uhz,
+ 120000000U);
+ KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.vsif_supported);
+ KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.btr);
+}
+
+/**
+ * dm_test_freesync_config_inactive - Test supported-but-off yields INACTIVE
+ * @test: The KUnit test context
+ */
+static void dm_test_freesync_config_inactive(struct kunit *test)
+{
+ struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
+
+ ctx->conn_state->freesync_capable = true;
+ ctx->aconnector->min_vfreq = 48;
+ ctx->aconnector->max_vfreq = 120;
+ ctx->crtc_state->base.vrr_enabled = false;
+
+ amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
+
+ KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported);
+ KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
+ (int)VRR_STATE_INACTIVE);
+}
+
+/**
+ * dm_test_freesync_config_active_fixed - Test freesync-video mode yields ACTIVE_FIXED
+ * @test: The KUnit test context
+ */
+static void dm_test_freesync_config_active_fixed(struct kunit *test)
+{
+ struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
+
+ ctx->conn_state->freesync_capable = true;
+ ctx->aconnector->min_vfreq = 48;
+ ctx->aconnector->max_vfreq = 120;
+ /* Pre-set fixed state selects the freesync-video (fixed) path */
+ ctx->crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED;
+ ctx->crtc_state->freesync_config.fixed_refresh_in_uhz = 60000000;
+ ctx->crtc_state->base.vrr_enabled = true; /* ignored on the fixed path */
+
+ amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
+
+ KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported);
+ KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
+ (int)VRR_STATE_ACTIVE_FIXED);
+ KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.fixed_refresh_in_uhz,
+ 60000000U);
+}
+
+/* Tests for amdgpu_dm_reset_freesync_config_for_crtc() */
+
+/**
+ * dm_test_reset_freesync_config - Test reset clears vrr support and info packet
+ * @test: The KUnit test context
+ */
+static void dm_test_reset_freesync_config(struct kunit *test)
+{
+ struct dm_crtc_state *crtc_state;
+
+ crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+
+ crtc_state->vrr_supported = true;
+ crtc_state->vrr_infopacket.valid = true;
+
+ amdgpu_dm_reset_freesync_config_for_crtc(crtc_state);
+
+ KUNIT_EXPECT_FALSE(test, crtc_state->vrr_supported);
+ KUNIT_EXPECT_FALSE(test, crtc_state->vrr_infopacket.valid);
+}
+
+static struct kunit_case amdgpu_dm_freesync_tests[] = {
+ /* amdgpu_dm_is_timing_unchanged_for_freesync */
+ KUNIT_CASE(dm_test_timing_unchanged_null_args),
+ KUNIT_CASE(dm_test_timing_unchanged_identical_modes),
+ KUNIT_CASE(dm_test_timing_unchanged_vrr_shift),
+ KUNIT_CASE(dm_test_timing_unchanged_clock_changed),
+ /* amdgpu_dm_set_freesync_fixed_config */
+ KUNIT_CASE(dm_test_set_freesync_fixed_config_60hz),
+ /* amdgpu_dm_is_dc_timing_adjust_needed */
+ KUNIT_CASE(dm_test_dc_timing_adjust_pending),
+ KUNIT_CASE(dm_test_dc_timing_adjust_active_fixed),
+ KUNIT_CASE(dm_test_dc_timing_adjust_vrr_toggle),
+ KUNIT_CASE(dm_test_dc_timing_adjust_not_needed),
+ /* amdgpu_dm_get_freesync_config_for_crtc */
+ KUNIT_CASE(dm_test_freesync_config_writeback),
+ KUNIT_CASE(dm_test_freesync_config_not_capable),
+ KUNIT_CASE(dm_test_freesync_config_out_of_range),
+ KUNIT_CASE(dm_test_freesync_config_active_variable),
+ KUNIT_CASE(dm_test_freesync_config_inactive),
+ KUNIT_CASE(dm_test_freesync_config_active_fixed),
+ /* amdgpu_dm_reset_freesync_config_for_crtc */
+ KUNIT_CASE(dm_test_reset_freesync_config),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_freesync_test_suite = {
+ .name = "amdgpu_dm_freesync",
+ .test_cases = amdgpu_dm_freesync_tests,
+};
+
+kunit_test_suite(amdgpu_dm_freesync_test_suite);
+
+MODULE_AUTHOR("AMD");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_freesync");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index 49a1d2ae5588..428d24259198 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -860,214 +860,6 @@ static void dm_test_scaling_state_underscan_border_changed(struct kunit *test)
KUNIT_EXPECT_TRUE(test, is_scaling_state_different(a, b));
}
-/* Tests for is_timing_unchanged_for_freesync() */
-
-/**
- * dm_test_timing_unchanged_null_args - Test NULL crtc states return false
- * @test: The KUnit test context
- */
-static void dm_test_timing_unchanged_null_args(struct kunit *test)
-{
- struct drm_crtc_state crtc_state = { 0 };
-
- KUNIT_EXPECT_FALSE(test,
- is_timing_unchanged_for_freesync(NULL, &crtc_state));
- KUNIT_EXPECT_FALSE(test,
- is_timing_unchanged_for_freesync(&crtc_state, NULL));
-}
-
-/**
- * dm_test_timing_unchanged_identical_modes - Test identical modes are not "unchanged"
- * @test: The KUnit test context
- *
- * The helper only returns true when vtotal/vsync shift (vrr) while the rest
- * of the timing stays fixed, so identical modes must return false.
- */
-static void dm_test_timing_unchanged_identical_modes(struct kunit *test)
-{
- struct drm_crtc_state old_state = { 0 };
- struct drm_crtc_state new_state = { 0 };
-
- old_state.mode.clock = 148500;
- old_state.mode.hdisplay = 1920;
- old_state.mode.vdisplay = 1080;
- old_state.mode.htotal = 2200;
- old_state.mode.vtotal = 1125;
- new_state.mode = old_state.mode;
-
- KUNIT_EXPECT_FALSE(test,
- is_timing_unchanged_for_freesync(&old_state, &new_state));
-}
-
-/**
- * dm_test_timing_unchanged_vrr_shift - Test vrr-style vtotal/vsync shift is detected
- * @test: The KUnit test context
- */
-static void dm_test_timing_unchanged_vrr_shift(struct kunit *test)
-{
- struct drm_crtc_state old_state = { 0 };
- struct drm_crtc_state new_state = { 0 };
-
- old_state.mode.clock = 148500;
- old_state.mode.hdisplay = 1920;
- old_state.mode.vdisplay = 1080;
- old_state.mode.htotal = 2200;
- old_state.mode.vtotal = 1125;
- old_state.mode.hsync_start = 2008;
- old_state.mode.vsync_start = 1084;
- old_state.mode.hsync_end = 2052;
- old_state.mode.vsync_end = 1089;
-
- /* Same horizontal timing, vertical totals/sync shifted by 125 lines */
- new_state.mode = old_state.mode;
- new_state.mode.vtotal = 1250;
- new_state.mode.vsync_start = 1209;
- new_state.mode.vsync_end = 1214;
-
- KUNIT_EXPECT_TRUE(test,
- is_timing_unchanged_for_freesync(&old_state, &new_state));
-}
-
-/**
- * dm_test_timing_unchanged_clock_changed - Test pixel clock change returns false
- * @test: The KUnit test context
- */
-static void dm_test_timing_unchanged_clock_changed(struct kunit *test)
-{
- struct drm_crtc_state old_state = { 0 };
- struct drm_crtc_state new_state = { 0 };
-
- old_state.mode.clock = 148500;
- old_state.mode.htotal = 2200;
- old_state.mode.vtotal = 1125;
- old_state.mode.vsync_start = 1084;
- old_state.mode.vsync_end = 1089;
-
- new_state.mode = old_state.mode;
- new_state.mode.clock = 297000;
- new_state.mode.vtotal = 1250;
- new_state.mode.vsync_start = 1209;
- new_state.mode.vsync_end = 1214;
-
- KUNIT_EXPECT_FALSE(test,
- is_timing_unchanged_for_freesync(&old_state, &new_state));
-}
-
-/* Tests for set_freesync_fixed_config() */
-
-/**
- * dm_test_set_freesync_fixed_config_60hz - Test fixed refresh computed for 1080p60
- * @test: The KUnit test context
- */
-static void dm_test_set_freesync_fixed_config_60hz(struct kunit *test)
-{
- struct dm_crtc_state dm_crtc_state = { 0 };
-
- dm_crtc_state.base.mode.clock = 148500;
- dm_crtc_state.base.mode.htotal = 2200;
- dm_crtc_state.base.mode.vtotal = 1125;
-
- set_freesync_fixed_config(&dm_crtc_state);
-
- KUNIT_EXPECT_EQ(test, (int)dm_crtc_state.freesync_config.state,
- (int)VRR_STATE_ACTIVE_FIXED);
- /* 148500 kHz / (2200 * 1125) = 60 Hz = 60000000 uHz */
- KUNIT_EXPECT_EQ(test, dm_crtc_state.freesync_config.fixed_refresh_in_uhz,
- 60000000U);
-}
-
-/* Tests for is_dc_timing_adjust_needed() */
-
-/**
- * dm_test_dc_timing_adjust_pending - Test a pending hw timing adjust forces true
- * @test: The KUnit test context
- */
-static void dm_test_dc_timing_adjust_pending(struct kunit *test)
-{
- struct dm_crtc_state *old_state, *new_state;
- struct dc_stream_state *stream;
-
- old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
- new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
- stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
-
- new_state->stream = stream;
- stream->adjust.timing_adjust_pending = 1;
-
- KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state));
-}
-
-/**
- * dm_test_dc_timing_adjust_active_fixed - Test VRR active-fixed forces true
- * @test: The KUnit test context
- */
-static void dm_test_dc_timing_adjust_active_fixed(struct kunit *test)
-{
- struct dm_crtc_state *old_state, *new_state;
- struct dc_stream_state *stream;
-
- old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
- new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
- stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
-
- new_state->stream = stream;
- new_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED;
-
- KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state));
-}
-
-/**
- * dm_test_dc_timing_adjust_vrr_toggle - Test a change in vrr active state forces true
- * @test: The KUnit test context
- */
-static void dm_test_dc_timing_adjust_vrr_toggle(struct kunit *test)
-{
- struct dm_crtc_state *old_state, *new_state;
- struct dc_stream_state *stream;
-
- old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
- new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
- stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
-
- new_state->stream = stream;
- old_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE;
- new_state->freesync_config.state = VRR_STATE_INACTIVE;
-
- KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state));
-}
-
-/**
- * dm_test_dc_timing_adjust_not_needed - Test steady-state timing needs no adjust
- * @test: The KUnit test context
- */
-static void dm_test_dc_timing_adjust_not_needed(struct kunit *test)
-{
- struct dm_crtc_state *old_state, *new_state;
- struct dc_stream_state *stream;
-
- old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
- new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
- stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
-
- new_state->stream = stream;
- old_state->freesync_config.state = VRR_STATE_INACTIVE;
- new_state->freesync_config.state = VRR_STATE_INACTIVE;
-
- KUNIT_EXPECT_FALSE(test, is_dc_timing_adjust_needed(old_state, new_state));
-}
-
/* Tests for set_multisync_trigger_params() */
/**
@@ -1435,194 +1227,6 @@ static void dm_test_cp_diff_desired_to_undesired(struct kunit *test)
KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx));
}
-/* Tests for get_freesync_config_for_crtc() */
-
-struct dm_test_freesync_ctx {
- struct amdgpu_dm_connector *aconnector;
- struct dm_crtc_state *crtc_state;
- struct dm_connector_state *conn_state;
- struct dc_stream_state *stream;
-};
-
-static struct dm_test_freesync_ctx *dm_test_freesync_ctx_alloc(struct kunit *test)
-{
- struct dm_test_freesync_ctx *ctx;
-
- ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, ctx);
-
- ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector);
- ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state);
- ctx->conn_state = kunit_kzalloc(test, sizeof(*ctx->conn_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state);
- ctx->stream = dm_kunit_alloc_stream(test, NULL);
-
- ctx->conn_state->base.connector = &ctx->aconnector->base;
- ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_DisplayPort;
- ctx->crtc_state->stream = ctx->stream;
-
- /* 1080p60 timing so drm_mode_vrefresh() == 60 */
- ctx->crtc_state->base.mode.clock = 148500;
- ctx->crtc_state->base.mode.htotal = 2200;
- ctx->crtc_state->base.mode.vtotal = 1125;
-
- return ctx;
-}
-
-/**
- * dm_test_freesync_config_writeback - Test writeback connector is left untouched
- * @test: The KUnit test context
- */
-static void dm_test_freesync_config_writeback(struct kunit *test)
-{
- struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
-
- ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK;
- ctx->conn_state->freesync_capable = true;
- ctx->aconnector->min_vfreq = 48;
- ctx->aconnector->max_vfreq = 120;
- ctx->crtc_state->vrr_supported = true; /* sentinel: must stay set */
-
- get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
-
- /* Writeback: early return leaves vrr_supported sentinel untouched */
- KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported);
-}
-
-/**
- * dm_test_freesync_config_not_capable - Test a non-freesync sink reports UNSUPPORTED
- * @test: The KUnit test context
- */
-static void dm_test_freesync_config_not_capable(struct kunit *test)
-{
- struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
-
- ctx->conn_state->freesync_capable = false;
- ctx->aconnector->min_vfreq = 48;
- ctx->aconnector->max_vfreq = 120;
-
- get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
-
- KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported);
- KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
- (int)VRR_STATE_UNSUPPORTED);
-}
-
-/**
- * dm_test_freesync_config_out_of_range - Test a refresh outside the range is UNSUPPORTED
- * @test: The KUnit test context
- */
-static void dm_test_freesync_config_out_of_range(struct kunit *test)
-{
- struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
-
- ctx->conn_state->freesync_capable = true;
- ctx->aconnector->min_vfreq = 90; /* 60 < 90 -> out of range */
- ctx->aconnector->max_vfreq = 120;
-
- get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
-
- KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported);
- KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
- (int)VRR_STATE_UNSUPPORTED);
-}
-
-/**
- * dm_test_freesync_config_active_variable - Test vrr_enabled yields ACTIVE_VARIABLE
- * @test: The KUnit test context
- */
-static void dm_test_freesync_config_active_variable(struct kunit *test)
-{
- struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
-
- ctx->conn_state->freesync_capable = true;
- ctx->aconnector->min_vfreq = 48;
- ctx->aconnector->max_vfreq = 120;
- ctx->crtc_state->base.vrr_enabled = true;
-
- get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
-
- KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported);
- KUNIT_EXPECT_TRUE(test, ctx->stream->ignore_msa_timing_param);
- KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
- (int)VRR_STATE_ACTIVE_VARIABLE);
- KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.min_refresh_in_uhz,
- 48000000U);
- KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.max_refresh_in_uhz,
- 120000000U);
- KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.vsif_supported);
- KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.btr);
-}
-
-/**
- * dm_test_freesync_config_inactive - Test supported-but-off yields INACTIVE
- * @test: The KUnit test context
- */
-static void dm_test_freesync_config_inactive(struct kunit *test)
-{
- struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
-
- ctx->conn_state->freesync_capable = true;
- ctx->aconnector->min_vfreq = 48;
- ctx->aconnector->max_vfreq = 120;
- ctx->crtc_state->base.vrr_enabled = false;
-
- get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
-
- KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported);
- KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
- (int)VRR_STATE_INACTIVE);
-}
-
-/**
- * dm_test_freesync_config_active_fixed - Test freesync-video mode yields ACTIVE_FIXED
- * @test: The KUnit test context
- */
-static void dm_test_freesync_config_active_fixed(struct kunit *test)
-{
- struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test);
-
- ctx->conn_state->freesync_capable = true;
- ctx->aconnector->min_vfreq = 48;
- ctx->aconnector->max_vfreq = 120;
- /* Pre-set fixed state selects the freesync-video (fixed) path */
- ctx->crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED;
- ctx->crtc_state->freesync_config.fixed_refresh_in_uhz = 60000000;
- ctx->crtc_state->base.vrr_enabled = true; /* ignored on the fixed path */
-
- get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state);
-
- KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported);
- KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state,
- (int)VRR_STATE_ACTIVE_FIXED);
- KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.fixed_refresh_in_uhz,
- 60000000U);
-}
-
-/* Tests for reset_freesync_config_for_crtc() */
-
-/**
- * dm_test_reset_freesync_config - Test reset clears vrr support and info packet
- * @test: The KUnit test context
- */
-static void dm_test_reset_freesync_config(struct kunit *test)
-{
- struct dm_crtc_state *crtc_state;
-
- crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, crtc_state);
-
- crtc_state->vrr_supported = true;
- crtc_state->vrr_infopacket.valid = true;
-
- reset_freesync_config_for_crtc(crtc_state);
-
- KUNIT_EXPECT_FALSE(test, crtc_state->vrr_supported);
- KUNIT_EXPECT_FALSE(test, crtc_state->vrr_infopacket.valid);
-}
-
/* Tests for dm_enable_per_frame_crtc_master_sync() */
/**
@@ -1818,18 +1422,6 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_scaling_state_underscan_enabled),
KUNIT_CASE(dm_test_scaling_state_underscan_disabled),
KUNIT_CASE(dm_test_scaling_state_underscan_border_changed),
- /* is_timing_unchanged_for_freesync */
- KUNIT_CASE(dm_test_timing_unchanged_null_args),
- KUNIT_CASE(dm_test_timing_unchanged_identical_modes),
- KUNIT_CASE(dm_test_timing_unchanged_vrr_shift),
- KUNIT_CASE(dm_test_timing_unchanged_clock_changed),
- /* set_freesync_fixed_config */
- KUNIT_CASE(dm_test_set_freesync_fixed_config_60hz),
- /* is_dc_timing_adjust_needed */
- KUNIT_CASE(dm_test_dc_timing_adjust_pending),
- KUNIT_CASE(dm_test_dc_timing_adjust_active_fixed),
- KUNIT_CASE(dm_test_dc_timing_adjust_vrr_toggle),
- KUNIT_CASE(dm_test_dc_timing_adjust_not_needed),
/* set_multisync_trigger_params */
KUNIT_CASE(dm_test_multisync_trigger_disabled),
KUNIT_CASE(dm_test_multisync_trigger_rising),
@@ -1849,15 +1441,6 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_cp_diff_s3_undesired_to_enabled),
KUNIT_CASE(dm_test_cp_diff_desired_to_enabled),
KUNIT_CASE(dm_test_cp_diff_desired_to_undesired),
- /* get_freesync_config_for_crtc */
- KUNIT_CASE(dm_test_freesync_config_writeback),
- KUNIT_CASE(dm_test_freesync_config_not_capable),
- KUNIT_CASE(dm_test_freesync_config_out_of_range),
- KUNIT_CASE(dm_test_freesync_config_active_variable),
- KUNIT_CASE(dm_test_freesync_config_inactive),
- KUNIT_CASE(dm_test_freesync_config_active_fixed),
- /* reset_freesync_config_for_crtc */
- KUNIT_CASE(dm_test_reset_freesync_config),
/* dm_enable_per_frame_crtc_master_sync */
KUNIT_CASE(dm_test_per_frame_master_sync_single_stream),
KUNIT_CASE(dm_test_per_frame_master_sync_two_streams),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 17/49] drm/amd/display: add cursor module
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (15 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 16/49] drm/amd/display: add FreeSync/VRR module Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-08-03 12:48 ` Timur Kristóf
2026-07-23 20:13 ` [PATCH 18/49] drm/amd/display: add KUnit tests for audio component get_eld Fangzhi Zuo
` (31 subsequent siblings)
48 siblings, 1 reply; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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]
Group the cursor policy and validation helpers from amdgpu_dm.c
into a dedicated amdgpu_dm_cursor.c and amdgpu_dm_cursor.h.
The moved functions are:
dm_check_cursor_fb, dm_check_native_cursor_state,
dm_should_update_native_cursor, dm_get_oriented_plane_size,
dm_get_plane_scale, dm_plane_color_pipeline_active,
dm_crtc_get_cursor_mode.
The cursor helpers exposed by amdgpu_dm_cursor.h are called from
amdgpu_dm.c, so give the three that lacked a namespace prefix the
standard amdgpu_dm_ prefix: amdgpu_dm_check_native_cursor_state,
amdgpu_dm_should_update_native_cursor and
amdgpu_dm_crtc_get_cursor_mode.
The descending-zpos plane iterator shared by these functions and
amdgpu_dm_atomic_check (the for_each_oldnew_plane_in_descending_zpos
macro and its __get_next_zpos helper) moves to amdgpu_dm.h, with
the helper renamed amdgpu_dm_get_next_zpos and made non-static.
Relocate the corresponding KUnit tests into amdgpu_dm_cursor_test.c.
No functional change.
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 436 +----------------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 24 +-
.../amd/display/amdgpu_dm/amdgpu_dm_cursor.c | 443 ++++++++++++++++++
.../amd/display/amdgpu_dm/amdgpu_dm_cursor.h | 52 ++
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../amdgpu_dm/tests/amdgpu_dm_cursor_test.c | 268 +++++++++++
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 232 ---------
8 files changed, 796 insertions(+), 663 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
index 5cb8c143e72f..d1faf2d8370f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
@@ -46,7 +46,8 @@ AMDGPUDM = \
amdgpu_dm_audio.o \
amdgpu_dm_dmub.o \
amdgpu_dm_connector.o \
- amdgpu_dm_freesync.o
+ amdgpu_dm_freesync.o \
+ amdgpu_dm_cursor.o
ifdef CONFIG_DRM_AMD_DC_FP
AMDGPUDM += dc_fpu.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index c36eb8a1d2d1..217ec822e93e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -72,6 +72,7 @@
#include "amdgpu_dm_connector.h"
#include "amdgpu_dm_pp_smu.h"
#include "amdgpu_dm_freesync.h"
+#include "amdgpu_dm_cursor.h"
#include "ivsrcid/ivsrcid_vislands30.h"
@@ -5760,133 +5761,6 @@ static bool should_reset_plane(struct drm_atomic_commit *state,
return false;
}
-static int dm_check_cursor_fb(struct amdgpu_crtc *new_acrtc,
- struct drm_plane_state *new_plane_state,
- struct drm_framebuffer *fb)
-{
- struct amdgpu_device *adev = drm_to_adev(new_acrtc->base.dev);
- struct amdgpu_framebuffer *afb = to_amdgpu_framebuffer(fb);
- unsigned int pitch;
- bool linear;
-
- if (fb->width > new_acrtc->max_cursor_width ||
- fb->height > new_acrtc->max_cursor_height) {
- drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB size %dx%d\n",
- new_plane_state->fb->width,
- new_plane_state->fb->height);
- return -EINVAL;
- }
- if (new_plane_state->src_w != fb->width << 16 ||
- new_plane_state->src_h != fb->height << 16) {
- drm_dbg_atomic(adev_to_drm(adev), "Cropping not supported for cursor plane\n");
- return -EINVAL;
- }
-
- /* Pitch in pixels */
- pitch = fb->pitches[0] / fb->format->cpp[0];
-
- if (fb->width != pitch) {
- drm_dbg_atomic(adev_to_drm(adev), "Cursor FB width %d doesn't match pitch %d",
- fb->width, pitch);
- return -EINVAL;
- }
-
- switch (pitch) {
- case 64:
- case 128:
- case 256:
- /* FB pitch is supported by cursor plane */
- break;
- default:
- drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB pitch %d px\n", pitch);
- return -EINVAL;
- }
-
- /* Core DRM takes care of checking FB modifiers, so we only need to
- * check tiling flags when the FB doesn't have a modifier.
- */
- if (!(fb->flags & DRM_MODE_FB_MODIFIERS)) {
- if (adev->family == AMDGPU_FAMILY_GC_12_0_0) {
- 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;
- } else {
- linear = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_2D_TILED_THIN1 &&
- AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_1D_TILED_THIN1 &&
- AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE) == 0;
- }
- if (!linear) {
- drm_dbg_atomic(adev_to_drm(adev), "Cursor FB not linear");
- return -EINVAL;
- }
- }
-
- return 0;
-}
-
-/*
- * Helper function for checking the cursor in native mode
- */
-static int dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc,
- struct drm_plane *plane,
- struct drm_plane_state *new_plane_state,
- bool enable)
-{
-
- struct amdgpu_crtc *new_acrtc;
- int ret;
-
- if (!enable || !new_plane_crtc ||
- drm_atomic_plane_disabling(plane->state, new_plane_state))
- return 0;
-
- new_acrtc = to_amdgpu_crtc(new_plane_crtc);
-
- if (new_plane_state->src_x != 0 || new_plane_state->src_y != 0) {
- drm_dbg_atomic(new_plane_crtc->dev, "Cropping not supported for cursor plane\n");
- return -EINVAL;
- }
-
- if (new_plane_state->fb) {
- ret = dm_check_cursor_fb(new_acrtc, new_plane_state,
- new_plane_state->fb);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
-STATIC_IFN_KUNIT bool dm_should_update_native_cursor(struct drm_atomic_commit *state,
- struct drm_crtc *old_plane_crtc,
- struct drm_crtc *new_plane_crtc,
- bool enable)
-{
- struct drm_crtc_state *old_crtc_state, *new_crtc_state;
- struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state;
-
- if (!enable) {
- if (old_plane_crtc == NULL)
- return true;
-
- old_crtc_state = drm_atomic_get_old_crtc_state(
- state, old_plane_crtc);
- dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
-
- return dm_old_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE;
- } else {
- if (new_plane_crtc == NULL)
- return true;
-
- new_crtc_state = drm_atomic_get_new_crtc_state(
- state, new_plane_crtc);
- dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
-
- return dm_new_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE;
- }
-}
-EXPORT_IF_KUNIT(dm_should_update_native_cursor);
-
static int dm_update_plane_state(struct dc *dc,
struct drm_atomic_commit *state,
struct drm_plane *plane,
@@ -5911,14 +5785,14 @@ static int dm_update_plane_state(struct dc *dc,
dm_new_plane_state = to_dm_plane_state(new_plane_state);
dm_old_plane_state = to_dm_plane_state(old_plane_state);
- update_native_cursor = dm_should_update_native_cursor(state,
- old_plane_crtc,
- new_plane_crtc,
- enable);
+ update_native_cursor = amdgpu_dm_should_update_native_cursor(state,
+ old_plane_crtc,
+ new_plane_crtc,
+ enable);
if (plane->type == DRM_PLANE_TYPE_CURSOR && update_native_cursor) {
- ret = dm_check_native_cursor_state(new_plane_crtc, plane,
- new_plane_state, enable);
+ ret = amdgpu_dm_check_native_cursor_state(new_plane_crtc, plane,
+ new_plane_state, enable);
if (ret)
return ret;
@@ -6048,8 +5922,8 @@ static int dm_update_plane_state(struct dc *dc,
out:
/* If enabling cursor overlay failed, attempt fallback to native mode */
if (enable && ret == -EINVAL && plane->type == DRM_PLANE_TYPE_CURSOR) {
- ret = dm_check_native_cursor_state(new_plane_crtc, plane,
- new_plane_state, enable);
+ ret = amdgpu_dm_check_native_cursor_state(new_plane_crtc, plane,
+ new_plane_state, enable);
if (ret)
return ret;
@@ -6059,37 +5933,6 @@ static int dm_update_plane_state(struct dc *dc,
return ret;
}
-STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
- int *src_w, int *src_h)
-{
- switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) {
- case DRM_MODE_ROTATE_90:
- case DRM_MODE_ROTATE_270:
- *src_w = plane_state->src_h >> 16;
- *src_h = plane_state->src_w >> 16;
- break;
- case DRM_MODE_ROTATE_0:
- case DRM_MODE_ROTATE_180:
- default:
- *src_w = plane_state->src_w >> 16;
- *src_h = plane_state->src_h >> 16;
- break;
- }
-}
-EXPORT_IF_KUNIT(dm_get_oriented_plane_size);
-
-STATIC_IFN_KUNIT void
-dm_get_plane_scale(struct drm_plane_state *plane_state,
- int *out_plane_scale_w, int *out_plane_scale_h)
-{
- int plane_src_w, plane_src_h;
-
- dm_get_oriented_plane_size(plane_state, &plane_src_w, &plane_src_h);
- *out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 / plane_src_w : 0;
- *out_plane_scale_h = plane_src_h ? plane_state->crtc_h * 1000 / plane_src_h : 0;
-}
-EXPORT_IF_KUNIT(dm_get_plane_scale);
-
/*
* The normalized_zpos value cannot be used by this iterator directly. It's only
* calculated for enabled planes, potentially causing normalized_zpos collisions
@@ -6097,7 +5940,7 @@ EXPORT_IF_KUNIT(dm_get_plane_scale);
* so that the iterator will not generate the same object twice, or loop
* indefinitely.
*/
-static inline struct __drm_planes_state *__get_next_zpos(
+struct __drm_planes_state *amdgpu_dm_get_next_zpos(
struct drm_atomic_commit *state,
struct __drm_planes_state *prev)
{
@@ -6135,19 +5978,6 @@ static inline struct __drm_planes_state *__get_next_zpos(
return &state->planes[highest_i];
}
-/*
- * Use the uniqueness of the plane's (zpos, drm obj ID) combination to iterate
- * by descending zpos, as read from the new plane state. This is the same
- * ordering as defined by drm_atomic_normalize_zpos().
- */
-#define for_each_oldnew_plane_in_descending_zpos(__state, plane, old_plane_state, new_plane_state) \
- for (struct __drm_planes_state *__i = __get_next_zpos((__state), NULL); \
- __i != NULL; __i = __get_next_zpos((__state), __i)) \
- for_each_if(((plane) = __i->ptr, \
- (void)(plane) /* Only to avoid unused-but-set-variable warning */, \
- (old_plane_state) = __i->old_state, \
- (new_plane_state) = __i->new_state, 1))
-
static int add_affected_mst_dsc_crtcs(struct drm_atomic_commit *state, struct drm_crtc *crtc)
{
struct drm_connector *connector;
@@ -6178,244 +6008,6 @@ static int add_affected_mst_dsc_crtcs(struct drm_atomic_commit *state, struct dr
return drm_dp_mst_add_affected_dsc_crtcs(state, &aconnector->mst_root->mst_mgr);
}
-/**
- * DOC: Cursor Modes - Native vs Overlay
- *
- * In native mode, the cursor uses a integrated cursor pipe within each DCN hw
- * plane. It does not require a dedicated hw plane to enable, but it is
- * subjected to the same z-order and scaling as the hw plane. It also has format
- * restrictions, a RGB cursor in native mode cannot be enabled within a non-RGB
- * hw plane.
- *
- * In overlay mode, the cursor uses a separate DCN hw plane, and thus has its
- * own scaling and z-pos. It also has no blending restrictions. It lends to a
- * cursor behavior more akin to a DRM client's expectations. However, it does
- * occupy an extra DCN plane, and therefore will only be used if a DCN plane is
- * available.
- */
-
-/**
- * dm_plane_color_pipeline_active() - Check if a plane's color pipeline active.
- * @state: DRM atomic state
- * @plane: DRM plane to check
- * @use_old: if true, inspect the old colorop states; otherwise the new ones
- *
- * A color pipeline may be selected (color_pipeline != NULL) but still is
- * inactive if every colorop in the chain is bypassed. Only return
- * true when at least one colorop has bypass == false, meaning the cursor
- * would be subjected to the transformation in native mode.
- *
- * Return: true if the pipeline modifies pixels, false otherwise.
- */
-static bool dm_plane_color_pipeline_active(struct drm_atomic_commit *state,
- struct drm_plane *plane,
- bool use_old)
-{
- struct drm_colorop *colorop;
- struct drm_colorop_state *old_colorop_state, *new_colorop_state;
- int i;
-
- for_each_oldnew_colorop_in_state(state, colorop, old_colorop_state, new_colorop_state, i) {
- struct drm_colorop_state *cstate = use_old ? old_colorop_state : new_colorop_state;
-
- if (cstate->colorop->plane != plane)
- continue;
- if (!cstate->bypass)
- return true;
- }
- return false;
-}
-
-/**
- * dm_crtc_get_cursor_mode() - Determine the required cursor mode on crtc
- * @adev: amdgpu device
- * @state: DRM atomic state
- * @dm_crtc_state: amdgpu state for the CRTC containing the cursor
- * @cursor_mode: Returns the required cursor mode on dm_crtc_state
- *
- * Get whether the cursor should be enabled in native mode, or overlay mode, on
- * the dm_crtc_state.
- *
- * The cursor should be enabled in overlay mode if there exists an underlying
- * plane - on which the cursor may be blended - that is either YUV formatted,
- * scaled differently from the cursor, or has a color pipeline active.
- *
- * Since zpos info is required, drm_atomic_normalize_zpos must be called before
- * calling this function.
- *
- * Return: 0 on success, or an error code if getting the cursor plane state
- * failed.
- */
-static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
- struct drm_atomic_commit *state,
- struct dm_crtc_state *dm_crtc_state,
- enum amdgpu_dm_cursor_mode *cursor_mode)
-{
- struct drm_plane_state *old_plane_state, *plane_state, *cursor_state;
- struct drm_crtc_state *crtc_state = &dm_crtc_state->base;
- struct drm_plane *plane;
- bool consider_mode_change = false;
- bool entire_crtc_covered = false;
- bool cursor_changed = false;
- int underlying_scale_w, underlying_scale_h;
- int cursor_scale_w, cursor_scale_h;
- int i;
-
- /* Overlay cursor not supported on HW before DCN
- * DCN401/420 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions
- * as previous DCN generations, so enable native mode on DCN401/420
- *
- * Always set native cursor mode when the CRTC is disabled,
- * to make sure it doesn't cause atomic commits to fail when
- * they are trying to disable the CRTC.
- */
- if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) ||
- amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) ||
- amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) ||
- !dm_crtc_state->base.enable) {
- *cursor_mode = DM_CURSOR_NATIVE_MODE;
- return 0;
- }
-
- /* Init cursor_mode to be the same as current */
- *cursor_mode = dm_crtc_state->cursor_mode;
-
- /*
- * Cursor mode can change if a plane's format changes, scale changes, is
- * enabled/disabled, z-order changes, or color management properties change.
- */
- for_each_oldnew_plane_in_state(state, plane, old_plane_state, plane_state, i) {
- int new_scale_w, new_scale_h, old_scale_w, old_scale_h;
-
- /* Only care about planes on this CRTC */
- if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0)
- continue;
-
- if (plane->type == DRM_PLANE_TYPE_CURSOR)
- cursor_changed = true;
-
- if (drm_atomic_plane_enabling(old_plane_state, plane_state) ||
- drm_atomic_plane_disabling(old_plane_state, plane_state) ||
- old_plane_state->fb->format != plane_state->fb->format) {
- consider_mode_change = true;
- break;
- }
-
- dm_get_plane_scale(plane_state, &new_scale_w, &new_scale_h);
- dm_get_plane_scale(old_plane_state, &old_scale_w, &old_scale_h);
- if (new_scale_w != old_scale_w || new_scale_h != old_scale_h) {
- consider_mode_change = true;
- break;
- }
-
- /*
- * A non-cursor plane moving or resizing (without a scale change)
- * changes how much of the CRTC it covers. This can create or
- * remove a hole under the cursor and thus flip the required
- * cursor mode (native vs overlay), so its destination rect must
- * be re-evaluated too.
- *
- * The cursor plane itself is deliberately excluded: the cursor
- * mode depends on the underlying planes' coverage, not on the
- * cursor's position (see the entire_crtc_covered logic below).
- * Triggering on cursor movement would force every legacy cursor
- * update off its fast path, and in a cursor-only commit - where
- * the underlying planes are not part of the state - the coverage
- * loop would see no covering plane and misevaluate the mode as
- * overlay, regressing flip-vs-cursor-legacy.
- */
- if (plane->type != DRM_PLANE_TYPE_CURSOR &&
- (old_plane_state->crtc_x != plane_state->crtc_x ||
- old_plane_state->crtc_y != plane_state->crtc_y ||
- old_plane_state->crtc_w != plane_state->crtc_w ||
- old_plane_state->crtc_h != plane_state->crtc_h)) {
- consider_mode_change = true;
- break;
- }
-
- if (dm_plane_color_pipeline_active(state, plane, true) !=
- dm_plane_color_pipeline_active(state, plane, false)) {
- consider_mode_change = true;
- break;
- }
- }
-
- if (!consider_mode_change && !crtc_state->zpos_changed)
- return 0;
-
- /*
- * If no cursor change on this CRTC, and not enabled on this CRTC, then
- * no need to set cursor mode. This avoids needlessly locking the cursor
- * state.
- */
- if (!cursor_changed &&
- !(drm_plane_mask(crtc_state->crtc->cursor) & crtc_state->plane_mask)) {
- return 0;
- }
-
- cursor_state = drm_atomic_get_plane_state(state,
- crtc_state->crtc->cursor);
- if (IS_ERR(cursor_state))
- return PTR_ERR(cursor_state);
-
- /* Cursor is disabled */
- if (!cursor_state->fb)
- return 0;
-
- /* For all planes in descending z-order (all of which are below cursor
- * as per zpos definitions), check their scaling and format
- */
- for_each_oldnew_plane_in_descending_zpos(state, plane, old_plane_state, plane_state) {
-
- /* Only care about non-cursor planes on this CRTC */
- if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0 ||
- plane->type == DRM_PLANE_TYPE_CURSOR)
- continue;
-
- /* Underlying plane is YUV format - use overlay cursor */
- if (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)) {
- *cursor_mode = DM_CURSOR_OVERLAY_MODE;
- return 0;
- }
-
- dm_get_plane_scale(plane_state,
- &underlying_scale_w, &underlying_scale_h);
- dm_get_plane_scale(cursor_state,
- &cursor_scale_w, &cursor_scale_h);
-
- /* Underlying plane has different scale - use overlay cursor */
- if (cursor_scale_w != underlying_scale_w &&
- cursor_scale_h != underlying_scale_h) {
- *cursor_mode = DM_CURSOR_OVERLAY_MODE;
- return 0;
- }
-
- /* If this plane covers the whole CRTC, no need to check planes underneath */
- if (plane_state->crtc_x <= 0 && plane_state->crtc_y <= 0 &&
- plane_state->crtc_x + plane_state->crtc_w >= crtc_state->mode.hdisplay &&
- plane_state->crtc_y + plane_state->crtc_h >= crtc_state->mode.vdisplay) {
- entire_crtc_covered = true;
- break;
- }
- }
-
- /* If planes do not cover the entire CRTC, use overlay mode to enable
- * cursor over holes
- */
- if (entire_crtc_covered)
- *cursor_mode = DM_CURSOR_NATIVE_MODE;
- else
- *cursor_mode = DM_CURSOR_OVERLAY_MODE;
-
- return 0;
-}
-
static bool amdgpu_dm_crtc_mem_type_changed(struct drm_device *dev,
struct drm_atomic_commit *state,
struct drm_crtc_state *crtc_state)
@@ -6619,8 +6211,8 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev,
for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
- ret = dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state,
- &dm_new_crtc_state->cursor_mode);
+ ret = amdgpu_dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state,
+ &dm_new_crtc_state->cursor_mode);
if (ret) {
drm_dbg(dev, "Failed to determine cursor mode: %pe\n", ERR_PTR(ret));
goto fail;
@@ -6754,8 +6346,8 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev,
}
/* If HW can only do native cursor, check restrictions again */
- ret = dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state,
- &required_cursor_mode);
+ ret = amdgpu_dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state,
+ &required_cursor_mode);
if (ret) {
drm_dbg_driver(crtc->dev,
"[CRTC:%d:%s] Checking cursor mode failed\n",
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 1ef92a28e3e9..1ddf5bc88a03 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1141,6 +1141,22 @@ void amdgpu_dm_emulated_link_detect(struct dc_link *link);
void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev,
struct dc_sink *sink);
+struct __drm_planes_state *amdgpu_dm_get_next_zpos(struct drm_atomic_commit *state,
+ struct __drm_planes_state *prev);
+
+/*
+ * Use the uniqueness of the plane's (zpos, drm obj ID) combination to iterate
+ * by descending zpos, as read from the new plane state. This is the same
+ * ordering as defined by drm_atomic_normalize_zpos().
+ */
+#define for_each_oldnew_plane_in_descending_zpos(__state, plane, old_plane_state, new_plane_state) \
+ for (struct __drm_planes_state *__i = amdgpu_dm_get_next_zpos((__state), NULL); \
+ __i != NULL; __i = amdgpu_dm_get_next_zpos((__state), __i)) \
+ for_each_if(((plane) = __i->ptr, \
+ (void)(plane) /* Only to avoid unused-but-set-variable warning */, \
+ (old_plane_state) = __i->old_state, \
+ (new_plane_state) = __i->new_state, 1))
+
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
struct amdgpu_ip_block;
bool dm_is_idle(struct amdgpu_ip_block *ip_block);
@@ -1157,19 +1173,11 @@ int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc,
struct dm_atomic_state *dm_atomic_get_new_state(struct drm_atomic_commit *state);
void dm_atomic_destroy_state(struct drm_private_obj *obj,
struct drm_private_state *state);
-bool dm_should_update_native_cursor(struct drm_atomic_commit *state,
- struct drm_crtc *old_plane_crtc,
- struct drm_crtc *new_plane_crtc,
- bool enable);
int dm_plane_layer_index_cmp(const void *a, const void *b);
int fill_plane_color_attributes(const struct drm_plane_state *plane_state,
const enum surface_pixel_format format,
enum dc_color_space *color_space);
bool modereset_required(struct drm_crtc_state *crtc_state);
-void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
- int *src_w, int *src_h);
-void dm_get_plane_scale(struct drm_plane_state *plane_state,
- int *out_plane_scale_w, int *out_plane_scale_h);
bool is_scaling_state_different(const struct dm_connector_state *dm_state,
const struct dm_connector_state *old_dm_state);
void set_multisync_trigger_params(struct dc_stream_state *stream);
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
new file mode 100644
index 000000000000..ab50d58896ab
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: AMD
+ *
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_colorop.h>
+
+#include "dc.h"
+#include "dal_asic_id.h"
+#include "amdgpu.h"
+#include "amdgpu_display.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_plane.h"
+#include "amdgpu_dm_cursor.h"
+#include "dm_helpers.h"
+
+static int dm_check_cursor_fb(struct amdgpu_crtc *new_acrtc,
+ struct drm_plane_state *new_plane_state,
+ struct drm_framebuffer *fb)
+{
+ struct amdgpu_device *adev = drm_to_adev(new_acrtc->base.dev);
+ struct amdgpu_framebuffer *afb = to_amdgpu_framebuffer(fb);
+ unsigned int pitch;
+ bool linear;
+
+ if (fb->width > new_acrtc->max_cursor_width ||
+ fb->height > new_acrtc->max_cursor_height) {
+ drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB size %dx%d\n",
+ new_plane_state->fb->width,
+ new_plane_state->fb->height);
+ return -EINVAL;
+ }
+ if (new_plane_state->src_w != fb->width << 16 ||
+ new_plane_state->src_h != fb->height << 16) {
+ drm_dbg_atomic(adev_to_drm(adev), "Cropping not supported for cursor plane\n");
+ return -EINVAL;
+ }
+
+ /* Pitch in pixels */
+ pitch = fb->pitches[0] / fb->format->cpp[0];
+
+ if (fb->width != pitch) {
+ drm_dbg_atomic(adev_to_drm(adev), "Cursor FB width %d doesn't match pitch %d",
+ fb->width, pitch);
+ return -EINVAL;
+ }
+
+ switch (pitch) {
+ case 64:
+ case 128:
+ case 256:
+ /* FB pitch is supported by cursor plane */
+ break;
+ default:
+ drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB pitch %d px\n", pitch);
+ return -EINVAL;
+ }
+
+ /* Core DRM takes care of checking FB modifiers, so we only need to
+ * 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;
+ } else {
+ linear = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_2D_TILED_THIN1 &&
+ AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_1D_TILED_THIN1 &&
+ AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE) == 0;
+ }
+ if (!linear) {
+ drm_dbg_atomic(adev_to_drm(adev), "Cursor FB not linear");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Helper function for checking the cursor in native mode
+ */
+int amdgpu_dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc,
+ struct drm_plane *plane,
+ struct drm_plane_state *new_plane_state,
+ bool enable)
+{
+
+ struct amdgpu_crtc *new_acrtc;
+ int ret;
+
+ if (!enable || !new_plane_crtc ||
+ drm_atomic_plane_disabling(plane->state, new_plane_state))
+ return 0;
+
+ new_acrtc = to_amdgpu_crtc(new_plane_crtc);
+
+ if (new_plane_state->src_x != 0 || new_plane_state->src_y != 0) {
+ drm_dbg_atomic(new_plane_crtc->dev, "Cropping not supported for cursor plane\n");
+ return -EINVAL;
+ }
+
+ if (new_plane_state->fb) {
+ ret = dm_check_cursor_fb(new_acrtc, new_plane_state,
+ new_plane_state->fb);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+bool amdgpu_dm_should_update_native_cursor(struct drm_atomic_commit *state,
+ struct drm_crtc *old_plane_crtc,
+ struct drm_crtc *new_plane_crtc,
+ bool enable)
+{
+ struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+ struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state;
+
+ if (!enable) {
+ if (old_plane_crtc == NULL)
+ return true;
+
+ old_crtc_state = drm_atomic_get_old_crtc_state(
+ state, old_plane_crtc);
+ dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
+
+ return dm_old_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE;
+ }
+
+ if (new_plane_crtc == NULL)
+ return true;
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(
+ state, new_plane_crtc);
+ dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
+
+ return dm_new_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_should_update_native_cursor);
+
+STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
+ int *src_w, int *src_h)
+{
+ switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) {
+ case DRM_MODE_ROTATE_90:
+ case DRM_MODE_ROTATE_270:
+ *src_w = plane_state->src_h >> 16;
+ *src_h = plane_state->src_w >> 16;
+ break;
+ case DRM_MODE_ROTATE_0:
+ case DRM_MODE_ROTATE_180:
+ default:
+ *src_w = plane_state->src_w >> 16;
+ *src_h = plane_state->src_h >> 16;
+ break;
+ }
+}
+EXPORT_IF_KUNIT(dm_get_oriented_plane_size);
+
+STATIC_IFN_KUNIT void
+dm_get_plane_scale(struct drm_plane_state *plane_state,
+ int *out_plane_scale_w, int *out_plane_scale_h)
+{
+ int plane_src_w, plane_src_h;
+
+ dm_get_oriented_plane_size(plane_state, &plane_src_w, &plane_src_h);
+ *out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 / plane_src_w : 0;
+ *out_plane_scale_h = plane_src_h ? plane_state->crtc_h * 1000 / plane_src_h : 0;
+}
+EXPORT_IF_KUNIT(dm_get_plane_scale);
+
+/**
+ * DOC: Cursor Modes - Native vs Overlay
+ *
+ * In native mode, the cursor uses a integrated cursor pipe within each DCN hw
+ * plane. It does not require a dedicated hw plane to enable, but it is
+ * subjected to the same z-order and scaling as the hw plane. It also has format
+ * restrictions, a RGB cursor in native mode cannot be enabled within a non-RGB
+ * hw plane.
+ *
+ * In overlay mode, the cursor uses a separate DCN hw plane, and thus has its
+ * own scaling and z-pos. It also has no blending restrictions. It lends to a
+ * cursor behavior more akin to a DRM client's expectations. However, it does
+ * occupy an extra DCN plane, and therefore will only be used if a DCN plane is
+ * available.
+ */
+
+/**
+ * dm_plane_color_pipeline_active() - Check if a plane's color pipeline active.
+ * @state: DRM atomic state
+ * @plane: DRM plane to check
+ * @use_old: if true, inspect the old colorop states; otherwise the new ones
+ *
+ * A color pipeline may be selected (color_pipeline != NULL) but still is
+ * inactive if every colorop in the chain is bypassed. Only return
+ * true when at least one colorop has bypass == false, meaning the cursor
+ * would be subjected to the transformation in native mode.
+ *
+ * Return: true if the pipeline modifies pixels, false otherwise.
+ */
+static bool dm_plane_color_pipeline_active(struct drm_atomic_commit *state,
+ struct drm_plane *plane,
+ bool use_old)
+{
+ struct drm_colorop *colorop;
+ struct drm_colorop_state *old_colorop_state, *new_colorop_state;
+ int i;
+
+ for_each_oldnew_colorop_in_state(state, colorop, old_colorop_state, new_colorop_state, i) {
+ struct drm_colorop_state *cstate = use_old ? old_colorop_state : new_colorop_state;
+
+ if (cstate->colorop->plane != plane)
+ continue;
+ if (!cstate->bypass)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * amdgpu_dm_crtc_get_cursor_mode() - Determine the required cursor mode on crtc
+ * @adev: amdgpu device
+ * @state: DRM atomic state
+ * @dm_crtc_state: amdgpu state for the CRTC containing the cursor
+ * @cursor_mode: Returns the required cursor mode on dm_crtc_state
+ *
+ * Get whether the cursor should be enabled in native mode, or overlay mode, on
+ * the dm_crtc_state.
+ *
+ * The cursor should be enabled in overlay mode if there exists an underlying
+ * plane - on which the cursor may be blended - that is either YUV formatted,
+ * scaled differently from the cursor, or has a color pipeline active.
+ *
+ * Since zpos info is required, drm_atomic_normalize_zpos must be called before
+ * calling this function.
+ *
+ * Return: 0 on success, or an error code if getting the cursor plane state
+ * failed.
+ */
+int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
+ struct drm_atomic_commit *state,
+ struct dm_crtc_state *dm_crtc_state,
+ enum amdgpu_dm_cursor_mode *cursor_mode)
+{
+ struct drm_plane_state *old_plane_state, *plane_state, *cursor_state;
+ struct drm_crtc_state *crtc_state = &dm_crtc_state->base;
+ struct drm_plane *plane;
+ bool consider_mode_change = false;
+ bool entire_crtc_covered = false;
+ bool cursor_changed = false;
+ int underlying_scale_w, underlying_scale_h;
+ int cursor_scale_w, cursor_scale_h;
+ int i;
+
+ /* Overlay cursor not supported on HW before DCN
+ * DCN401/420 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions
+ * as previous DCN generations, so enable native mode on DCN401/420
+ */
+ if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) ||
+ amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) ||
+#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
+ *cursor_mode = DM_CURSOR_NATIVE_MODE;
+ return 0;
+ }
+
+ /* Init cursor_mode to be the same as current */
+ *cursor_mode = dm_crtc_state->cursor_mode;
+
+ /*
+ * Cursor mode can change if a plane's format changes, scale changes, is
+ * enabled/disabled, z-order changes, or color management properties change.
+ */
+ for_each_oldnew_plane_in_state(state, plane, old_plane_state, plane_state, i) {
+ int new_scale_w, new_scale_h, old_scale_w, old_scale_h;
+
+ /* Only care about planes on this CRTC */
+ if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0)
+ continue;
+
+ if (plane->type == DRM_PLANE_TYPE_CURSOR)
+ cursor_changed = true;
+
+ if (drm_atomic_plane_enabling(old_plane_state, plane_state) ||
+ drm_atomic_plane_disabling(old_plane_state, plane_state) ||
+ old_plane_state->fb->format != plane_state->fb->format) {
+ consider_mode_change = true;
+ break;
+ }
+
+ dm_get_plane_scale(plane_state, &new_scale_w, &new_scale_h);
+ dm_get_plane_scale(old_plane_state, &old_scale_w, &old_scale_h);
+ if (new_scale_w != old_scale_w || new_scale_h != old_scale_h) {
+ consider_mode_change = true;
+ break;
+ }
+
+ /*
+ * A non-cursor plane moving or resizing (without a scale change)
+ * changes how much of the CRTC it covers. This can create or
+ * remove a hole under the cursor and thus flip the required
+ * cursor mode (native vs overlay), so its destination rect must
+ * be re-evaluated too.
+ *
+ * The cursor plane itself is deliberately excluded: the cursor
+ * mode depends on the underlying planes' coverage, not on the
+ * cursor's position (see the entire_crtc_covered logic below).
+ * Triggering on cursor movement would force every legacy cursor
+ * update off its fast path, and in a cursor-only commit - where
+ * the underlying planes are not part of the state - the coverage
+ * loop would see no covering plane and misevaluate the mode as
+ * overlay, regressing flip-vs-cursor-legacy.
+ */
+ if (plane->type != DRM_PLANE_TYPE_CURSOR &&
+ (old_plane_state->crtc_x != plane_state->crtc_x ||
+ old_plane_state->crtc_y != plane_state->crtc_y ||
+ old_plane_state->crtc_w != plane_state->crtc_w ||
+ old_plane_state->crtc_h != plane_state->crtc_h)) {
+ consider_mode_change = true;
+ break;
+ }
+
+ if (dm_plane_color_pipeline_active(state, plane, true) !=
+ dm_plane_color_pipeline_active(state, plane, false)) {
+ consider_mode_change = true;
+ break;
+ }
+ }
+
+ if (!consider_mode_change && !crtc_state->zpos_changed)
+ return 0;
+
+ /*
+ * If no cursor change on this CRTC, and not enabled on this CRTC, then
+ * no need to set cursor mode. This avoids needlessly locking the cursor
+ * state.
+ */
+ if (!cursor_changed &&
+ !(drm_plane_mask(crtc_state->crtc->cursor) & crtc_state->plane_mask)) {
+ return 0;
+ }
+
+ cursor_state = drm_atomic_get_plane_state(state,
+ crtc_state->crtc->cursor);
+ if (IS_ERR(cursor_state))
+ return PTR_ERR(cursor_state);
+
+ /* Cursor is disabled */
+ if (!cursor_state->fb)
+ return 0;
+
+ /* For all planes in descending z-order (all of which are below cursor
+ * as per zpos definitions), check their scaling and format
+ */
+ for_each_oldnew_plane_in_descending_zpos(state, plane, old_plane_state, plane_state) {
+
+ /* Only care about non-cursor planes on this CRTC */
+ if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0 ||
+ plane->type == DRM_PLANE_TYPE_CURSOR)
+ continue;
+
+ /* Underlying plane is YUV format - use overlay cursor */
+ if (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)) {
+ *cursor_mode = DM_CURSOR_OVERLAY_MODE;
+ return 0;
+ }
+
+ dm_get_plane_scale(plane_state,
+ &underlying_scale_w, &underlying_scale_h);
+ dm_get_plane_scale(cursor_state,
+ &cursor_scale_w, &cursor_scale_h);
+
+ /* Underlying plane has different scale - use overlay cursor */
+ if (cursor_scale_w != underlying_scale_w &&
+ cursor_scale_h != underlying_scale_h) {
+ *cursor_mode = DM_CURSOR_OVERLAY_MODE;
+ return 0;
+ }
+
+ /* If this plane covers the whole CRTC, no need to check planes underneath */
+ if (plane_state->crtc_x <= 0 && plane_state->crtc_y <= 0 &&
+ plane_state->crtc_x + plane_state->crtc_w >= crtc_state->mode.hdisplay &&
+ plane_state->crtc_y + plane_state->crtc_h >= crtc_state->mode.vdisplay) {
+ entire_crtc_covered = true;
+ break;
+ }
+ }
+
+ /* If planes do not cover the entire CRTC, use overlay mode to enable
+ * cursor over holes
+ */
+ if (entire_crtc_covered)
+ *cursor_mode = DM_CURSOR_NATIVE_MODE;
+ else
+ *cursor_mode = DM_CURSOR_OVERLAY_MODE;
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h
new file mode 100644
index 000000000000..5f619e4474aa
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: AMD
+ *
+ */
+
+#ifndef __AMDGPU_DM_CURSOR_H__
+#define __AMDGPU_DM_CURSOR_H__
+
+int amdgpu_dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc,
+ struct drm_plane *plane,
+ struct drm_plane_state *new_plane_state,
+ bool enable);
+
+bool amdgpu_dm_should_update_native_cursor(struct drm_atomic_commit *state,
+ struct drm_crtc *old_plane_crtc,
+ struct drm_crtc *new_plane_crtc,
+ bool enable);
+
+int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
+ struct drm_atomic_commit *state,
+ struct dm_crtc_state *dm_crtc_state,
+ enum amdgpu_dm_cursor_mode *cursor_mode);
+
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
+ int *src_w, int *src_h);
+void dm_get_plane_scale(struct drm_plane_state *plane_state,
+ int *out_plane_scale_w, int *out_plane_scale_h);
+#endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */
+
+#endif /* __AMDGPU_DM_CURSOR_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index e58ffc4dcd84..3a51fccc2a5e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_freesync_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_cursor_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
new file mode 100644
index 000000000000..3da76794ece9
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_cursor.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_plane.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_cursor.h"
+
+/* Tests for amdgpu_dm_should_update_native_cursor() */
+
+/**
+ * dm_test_should_update_native_cursor_without_crtc - Test NULL crtc cases update native cursor
+ * @test: The KUnit test context
+ */
+static void dm_test_should_update_native_cursor_without_crtc(struct kunit *test)
+{
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_update_native_cursor(NULL, NULL, NULL, false));
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_update_native_cursor(NULL, NULL, NULL, true));
+}
+
+/**
+ * dm_test_should_update_native_cursor_disable_native - Test disable path reads old crtc cursor mode
+ * @test: The KUnit test context
+ */
+static void dm_test_should_update_native_cursor_disable_native(struct kunit *test)
+{
+ struct dm_crtc_state *dm_crtc_state;
+ struct drm_atomic_commit *state;
+ struct drm_crtc *crtc;
+
+ state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, state);
+
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+
+ dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
+
+ state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, state->crtcs);
+
+ crtc->index = 0;
+ dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE;
+ state->crtcs[0].old_state = &dm_crtc_state->base;
+
+ KUNIT_EXPECT_TRUE(test,
+ amdgpu_dm_should_update_native_cursor(state, crtc, NULL, false));
+}
+
+/**
+ * dm_test_should_update_native_cursor_enable_overlay - Test enable path reads new crtc cursor mode
+ * @test: The KUnit test context
+ */
+static void dm_test_should_update_native_cursor_enable_overlay(struct kunit *test)
+{
+ struct dm_crtc_state *dm_crtc_state;
+ struct drm_atomic_commit *state;
+ struct drm_crtc *crtc;
+
+ state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, state);
+
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+
+ dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
+
+ state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, state->crtcs);
+
+ crtc->index = 0;
+ dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE;
+ state->crtcs[0].new_state = &dm_crtc_state->base;
+
+ KUNIT_EXPECT_FALSE(test,
+ amdgpu_dm_should_update_native_cursor(state, NULL, crtc, true));
+}
+
+/* Tests for dm_get_oriented_plane_size() */
+
+/**
+ * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0
+ * @test: The KUnit test context
+ */
+static void dm_test_oriented_plane_size_rotate_0(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int src_w = 0;
+ int src_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_0;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+
+ dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
+
+ KUNIT_EXPECT_EQ(test, src_w, 1920);
+ KUNIT_EXPECT_EQ(test, src_h, 1080);
+}
+
+/**
+ * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate 90
+ * @test: The KUnit test context
+ */
+static void dm_test_oriented_plane_size_rotate_90(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int src_w = 0;
+ int src_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_90;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+
+ dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
+
+ KUNIT_EXPECT_EQ(test, src_w, 1080);
+ KUNIT_EXPECT_EQ(test, src_h, 1920);
+}
+
+/**
+ * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate 180
+ * @test: The KUnit test context
+ */
+static void dm_test_oriented_plane_size_rotate_180(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int src_w = 0;
+ int src_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_180;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+
+ dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
+
+ KUNIT_EXPECT_EQ(test, src_w, 1920);
+ KUNIT_EXPECT_EQ(test, src_h, 1080);
+}
+
+/**
+ * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate 270
+ * @test: The KUnit test context
+ */
+static void dm_test_oriented_plane_size_rotate_270(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int src_w = 0;
+ int src_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_270;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+
+ dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
+
+ KUNIT_EXPECT_EQ(test, src_w, 1080);
+ KUNIT_EXPECT_EQ(test, src_h, 1920);
+}
+
+/* Tests for dm_get_plane_scale() */
+
+/**
+ * dm_test_get_plane_scale_identity - Test Get plane scale identity
+ * @test: The KUnit test context
+ */
+static void dm_test_get_plane_scale_identity(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int scale_w = 0;
+ int scale_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_0;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+ plane_state.crtc_w = 1920;
+ plane_state.crtc_h = 1080;
+
+ dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
+
+ KUNIT_EXPECT_EQ(test, scale_w, 1000);
+ KUNIT_EXPECT_EQ(test, scale_h, 1000);
+}
+
+/**
+ * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate 90 identity
+ * @test: The KUnit test context
+ */
+static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int scale_w = 0;
+ int scale_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_90;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+ plane_state.crtc_w = 1080;
+ plane_state.crtc_h = 1920;
+
+ dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
+
+ KUNIT_EXPECT_EQ(test, scale_w, 1000);
+ KUNIT_EXPECT_EQ(test, scale_h, 1000);
+}
+
+/**
+ * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src width
+ * @test: The KUnit test context
+ */
+static void dm_test_get_plane_scale_zero_src_width(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int scale_w = 0;
+ int scale_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_0;
+ plane_state.src_w = 0;
+ plane_state.src_h = 1080 << 16;
+ plane_state.crtc_w = 100;
+ plane_state.crtc_h = 200;
+
+ dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
+
+ KUNIT_EXPECT_EQ(test, scale_w, 0);
+ KUNIT_EXPECT_EQ(test, scale_h, 185);
+}
+
+static struct kunit_case amdgpu_dm_cursor_tests[] = {
+ /* amdgpu_dm_should_update_native_cursor */
+ KUNIT_CASE(dm_test_should_update_native_cursor_without_crtc),
+ KUNIT_CASE(dm_test_should_update_native_cursor_disable_native),
+ KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay),
+ /* dm_get_oriented_plane_size */
+ KUNIT_CASE(dm_test_oriented_plane_size_rotate_0),
+ KUNIT_CASE(dm_test_oriented_plane_size_rotate_90),
+ KUNIT_CASE(dm_test_oriented_plane_size_rotate_180),
+ KUNIT_CASE(dm_test_oriented_plane_size_rotate_270),
+ /* dm_get_plane_scale */
+ KUNIT_CASE(dm_test_get_plane_scale_identity),
+ KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity),
+ KUNIT_CASE(dm_test_get_plane_scale_zero_src_width),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_cursor_test_suite = {
+ .name = "amdgpu_dm_cursor",
+ .test_cases = amdgpu_dm_cursor_tests,
+};
+
+kunit_test_suite(amdgpu_dm_cursor_test_suite);
+
+MODULE_AUTHOR("AMD");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_cursor");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index 428d24259198..3c10eec9b1e0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -227,76 +227,6 @@ static void dm_test_atomic_get_new_state_match(struct kunit *test)
KUNIT_EXPECT_PTR_EQ(test, dm_atomic_get_new_state(state), dm_state);
}
-/**
- * dm_test_should_update_native_cursor_without_crtc - Test NULL crtc cases update native cursor
- * @test: The KUnit test context
- */
-static void dm_test_should_update_native_cursor_without_crtc(struct kunit *test)
-{
- KUNIT_EXPECT_TRUE(test, dm_should_update_native_cursor(NULL, NULL, NULL, false));
- KUNIT_EXPECT_TRUE(test, dm_should_update_native_cursor(NULL, NULL, NULL, true));
-}
-
-/**
- * dm_test_should_update_native_cursor_disable_native - Test disable path reads old crtc cursor mode
- * @test: The KUnit test context
- */
-static void dm_test_should_update_native_cursor_disable_native(struct kunit *test)
-{
- struct dm_crtc_state *dm_crtc_state;
- struct drm_atomic_commit *state;
- struct drm_crtc *crtc;
-
- state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, state);
-
- crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, crtc);
-
- dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
-
- state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, state->crtcs);
-
- crtc->index = 0;
- dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE;
- state->crtcs[0].old_state = &dm_crtc_state->base;
-
- KUNIT_EXPECT_TRUE(test,
- dm_should_update_native_cursor(state, crtc, NULL, false));
-}
-
-/**
- * dm_test_should_update_native_cursor_enable_overlay - Test enable path reads new crtc cursor mode
- * @test: The KUnit test context
- */
-static void dm_test_should_update_native_cursor_enable_overlay(struct kunit *test)
-{
- struct dm_crtc_state *dm_crtc_state;
- struct drm_atomic_commit *state;
- struct drm_crtc *crtc;
-
- state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, state);
-
- crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, crtc);
-
- dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
-
- state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, state->crtcs);
-
- crtc->index = 0;
- dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE;
- state->crtcs[0].new_state = &dm_crtc_state->base;
-
- KUNIT_EXPECT_FALSE(test,
- dm_should_update_native_cursor(state, NULL, crtc, true));
-}
-
/**
* dm_test_atomic_destroy_state_no_context - Test destroying DM atomic state without a DC context
* @test: The KUnit test context
@@ -599,156 +529,6 @@ static void dm_test_modereset_not_required_when_inactive_without_modeset(struct
KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state));
}
-/* Tests for dm_get_oriented_plane_size() */
-
-/**
- * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0
- * @test: The KUnit test context
- */
-static void dm_test_oriented_plane_size_rotate_0(struct kunit *test)
-{
- struct drm_plane_state plane_state = { 0 };
- int src_w = 0;
- int src_h = 0;
-
- plane_state.rotation = DRM_MODE_ROTATE_0;
- plane_state.src_w = 1920 << 16;
- plane_state.src_h = 1080 << 16;
-
- dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
-
- KUNIT_EXPECT_EQ(test, src_w, 1920);
- KUNIT_EXPECT_EQ(test, src_h, 1080);
-}
-
-/**
- * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate 90
- * @test: The KUnit test context
- */
-static void dm_test_oriented_plane_size_rotate_90(struct kunit *test)
-{
- struct drm_plane_state plane_state = { 0 };
- int src_w = 0;
- int src_h = 0;
-
- plane_state.rotation = DRM_MODE_ROTATE_90;
- plane_state.src_w = 1920 << 16;
- plane_state.src_h = 1080 << 16;
-
- dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
-
- KUNIT_EXPECT_EQ(test, src_w, 1080);
- KUNIT_EXPECT_EQ(test, src_h, 1920);
-}
-
-/**
- * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate 180
- * @test: The KUnit test context
- */
-static void dm_test_oriented_plane_size_rotate_180(struct kunit *test)
-{
- struct drm_plane_state plane_state = { 0 };
- int src_w = 0;
- int src_h = 0;
-
- plane_state.rotation = DRM_MODE_ROTATE_180;
- plane_state.src_w = 1920 << 16;
- plane_state.src_h = 1080 << 16;
-
- dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
-
- KUNIT_EXPECT_EQ(test, src_w, 1920);
- KUNIT_EXPECT_EQ(test, src_h, 1080);
-}
-
-/**
- * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate 270
- * @test: The KUnit test context
- */
-static void dm_test_oriented_plane_size_rotate_270(struct kunit *test)
-{
- struct drm_plane_state plane_state = { 0 };
- int src_w = 0;
- int src_h = 0;
-
- plane_state.rotation = DRM_MODE_ROTATE_270;
- plane_state.src_w = 1920 << 16;
- plane_state.src_h = 1080 << 16;
-
- dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
-
- KUNIT_EXPECT_EQ(test, src_w, 1080);
- KUNIT_EXPECT_EQ(test, src_h, 1920);
-}
-
-/* Tests for dm_get_plane_scale() */
-
-/**
- * dm_test_get_plane_scale_identity - Test Get plane scale identity
- * @test: The KUnit test context
- */
-static void dm_test_get_plane_scale_identity(struct kunit *test)
-{
- struct drm_plane_state plane_state = { 0 };
- int scale_w = 0;
- int scale_h = 0;
-
- plane_state.rotation = DRM_MODE_ROTATE_0;
- plane_state.src_w = 1920 << 16;
- plane_state.src_h = 1080 << 16;
- plane_state.crtc_w = 1920;
- plane_state.crtc_h = 1080;
-
- dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
-
- KUNIT_EXPECT_EQ(test, scale_w, 1000);
- KUNIT_EXPECT_EQ(test, scale_h, 1000);
-}
-
-/**
- * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate 90 identity
- * @test: The KUnit test context
- */
-static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test)
-{
- struct drm_plane_state plane_state = { 0 };
- int scale_w = 0;
- int scale_h = 0;
-
- plane_state.rotation = DRM_MODE_ROTATE_90;
- plane_state.src_w = 1920 << 16;
- plane_state.src_h = 1080 << 16;
- plane_state.crtc_w = 1080;
- plane_state.crtc_h = 1920;
-
- dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
-
- KUNIT_EXPECT_EQ(test, scale_w, 1000);
- KUNIT_EXPECT_EQ(test, scale_h, 1000);
-}
-
-/**
- * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src width
- * @test: The KUnit test context
- */
-static void dm_test_get_plane_scale_zero_src_width(struct kunit *test)
-{
- struct drm_plane_state plane_state = { 0 };
- int scale_w = 0;
- int scale_h = 0;
-
- plane_state.rotation = DRM_MODE_ROTATE_0;
- plane_state.src_w = 0;
- plane_state.src_h = 1080 << 16;
- plane_state.crtc_w = 100;
- plane_state.crtc_h = 200;
-
- dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
-
- KUNIT_EXPECT_EQ(test, scale_w, 0);
- KUNIT_EXPECT_EQ(test, scale_h, 185);
-}
-
/* Tests for is_scaling_state_different() */
/**
@@ -1386,9 +1166,6 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_crtc_get_scanoutpos_no_stream),
KUNIT_CASE(dm_test_atomic_get_new_state_empty),
KUNIT_CASE(dm_test_atomic_get_new_state_match),
- KUNIT_CASE(dm_test_should_update_native_cursor_without_crtc),
- KUNIT_CASE(dm_test_should_update_native_cursor_disable_native),
- KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay),
KUNIT_CASE(dm_test_atomic_destroy_state_no_context),
/* dm_plane_layer_index_cmp */
KUNIT_CASE(dm_test_plane_layer_index_cmp_equal),
@@ -1407,15 +1184,6 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_modereset_required_when_inactive_and_modeset),
KUNIT_CASE(dm_test_modereset_not_required_when_active_and_modeset),
KUNIT_CASE(dm_test_modereset_not_required_when_inactive_without_modeset),
- /* dm_get_oriented_plane_size */
- KUNIT_CASE(dm_test_oriented_plane_size_rotate_0),
- KUNIT_CASE(dm_test_oriented_plane_size_rotate_90),
- KUNIT_CASE(dm_test_oriented_plane_size_rotate_180),
- KUNIT_CASE(dm_test_oriented_plane_size_rotate_270),
- /* dm_get_plane_scale */
- KUNIT_CASE(dm_test_get_plane_scale_identity),
- KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity),
- KUNIT_CASE(dm_test_get_plane_scale_zero_src_width),
/* is_scaling_state_different */
KUNIT_CASE(dm_test_scaling_state_same),
KUNIT_CASE(dm_test_scaling_state_scaling_changed),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 18/49] drm/amd/display: add KUnit tests for audio component get_eld
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (16 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 17/49] drm/amd/display: add cursor module Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 19/49] drm/amd/display: add KUnit tests for audio commit path Fangzhi Zuo
` (30 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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 coverage for amdgpu_dm_audio_component_get_eld(), one of the
functions reported as untested by gcov.
The tests use the standard DRM KUnit device plus drm_connector_init()
to build a real connector list, then invoke the bound audio component
get_eld callback. They cover the matching-connector ELD copy path, the
writeback-connector skip, and the no-match (disabled) path. A shared
connector funcs table and cleanup action back the fixtures.
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amdgpu_dm/tests/amdgpu_dm_audio_test.c | 119 ++++++++++++++++++
1 file changed, 119 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
index 3f0108e9a951..d30b7f644f97 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
@@ -8,7 +8,12 @@
#include <kunit/test.h>
#include <linux/device.h>
+#include <drm/drm_atomic_helper.h>
#include <drm/drm_audio_component.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_eld.h>
+#include <drm/drm_kunit_helpers.h>
+#include <drm/drm_probe_helper.h>
#include "dc.h"
#include "dc/inc/core_types.h"
@@ -17,6 +22,20 @@
#include "amdgpu_mode.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_audio.h"
+#include "amdgpu_dm_kunit_test_helpers.h"
+
+static const struct drm_connector_funcs dm_test_audio_connector_funcs = {
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+};
+
+static void dm_test_audio_connector_cleanup(void *data)
+{
+ drm_connector_cleanup(data);
+}
/* Tests for amdgpu_dm_audio_init() */
@@ -569,6 +588,103 @@ static void dm_test_audio_init_pins_zero_count(struct kunit *test)
/* End of tests for amdgpu_dm_audio_init_pins() */
+/* Tests for amdgpu_dm_audio_component_get_eld() */
+
+/**
+ * dm_test_audio_component_get_eld_copies_matching_connector - Test ELD lookup
+ * @test: The KUnit test context
+ *
+ * The bound component callback should find the connector whose audio instance
+ * matches the requested port, report it enabled, and copy the connector ELD.
+ */
+static void dm_test_audio_component_get_eld_copies_matching_connector(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector *wb_connector;
+ struct drm_audio_component *acomp;
+ struct device *kdev;
+ unsigned char buf[DRM_ELD_HEADER_BLOCK_SIZE + 8] = {0};
+ bool enabled = false;
+ int ret;
+
+ kdev = adev->ddev.dev;
+ KUNIT_ASSERT_NOT_NULL(test, kdev);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, acomp);
+ wb_connector = kunit_kzalloc(test, sizeof(*wb_connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, wb_connector);
+ KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wb_connector,
+ &dm_test_audio_connector_funcs,
+ DRM_MODE_CONNECTOR_WRITEBACK), 0);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ dm_test_audio_connector_cleanup, wb_connector), 0);
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconnector->base,
+ &dm_test_audio_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA), 0);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ dm_test_audio_connector_cleanup, &aconnector->base), 0);
+
+ mutex_init(&adev->dm.audio_lock);
+ aconnector->audio_inst = 3;
+ aconnector->base.eld[DRM_ELD_BASELINE_ELD_LEN] = 2;
+ aconnector->base.eld[DRM_ELD_VER] = DRM_ELD_VER_CEA861D;
+ aconnector->base.eld[DRM_ELD_SPEAKER] = DRM_ELD_SPEAKER_FLR;
+ dev_set_drvdata(kdev, &adev->ddev);
+
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_audio_component_bind(kdev, NULL, acomp), 0);
+ ret = acomp->ops->get_eld(kdev, 3, 0, &enabled, buf, sizeof(buf));
+
+ KUNIT_EXPECT_EQ(test, ret, DRM_ELD_HEADER_BLOCK_SIZE + 8);
+ KUNIT_EXPECT_TRUE(test, enabled);
+ KUNIT_EXPECT_EQ(test, memcmp(buf, aconnector->base.eld, ret), 0);
+}
+
+/**
+ * dm_test_audio_component_get_eld_no_match - Test ELD lookup miss
+ * @test: The KUnit test context
+ *
+ * A non-matching connector should leave the audio port disabled and return an
+ * empty ELD size without copying data.
+ */
+static void dm_test_audio_component_get_eld_no_match(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_audio_component *acomp;
+ struct device *kdev;
+ unsigned char buf[DRM_ELD_HEADER_BLOCK_SIZE] = {0x5a};
+ bool enabled = true;
+ int ret;
+
+ kdev = adev->ddev.dev;
+ KUNIT_ASSERT_NOT_NULL(test, kdev);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, acomp);
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconnector->base,
+ &dm_test_audio_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA), 0);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ dm_test_audio_connector_cleanup, &aconnector->base), 0);
+
+ mutex_init(&adev->dm.audio_lock);
+ aconnector->audio_inst = 4;
+ dev_set_drvdata(kdev, &adev->ddev);
+
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_audio_component_bind(kdev, NULL, acomp), 0);
+ ret = acomp->ops->get_eld(kdev, 3, 0, &enabled, buf, sizeof(buf));
+
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_FALSE(test, enabled);
+ KUNIT_EXPECT_EQ(test, buf[0], 0x5a);
+}
+
static struct kunit_case dm_audio_test_cases[] = {
/* amdgpu_dm_audio_init */
KUNIT_CASE(dm_test_audio_init_disabled),
@@ -593,6 +709,9 @@ static struct kunit_case dm_audio_test_cases[] = {
KUNIT_CASE(dm_test_eld_notify_no_component),
KUNIT_CASE(dm_test_eld_notify_null_audio_ops),
KUNIT_CASE(dm_test_eld_notify_null_callback),
+ /* amdgpu_dm_audio_component_get_eld */
+ KUNIT_CASE(dm_test_audio_component_get_eld_copies_matching_connector),
+ KUNIT_CASE(dm_test_audio_component_get_eld_no_match),
{}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 19/49] drm/amd/display: add KUnit tests for audio commit path
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (17 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 18/49] drm/amd/display: add KUnit tests for audio component get_eld Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 20/49] drm/amd/display: Use current mpc pipe in set output transfer func Fangzhi Zuo
` (29 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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 coverage for amdgpu_dm_commit_audio(), the remaining function
reported as untested by gcov.
The tests build hand-rolled drm_atomic_state arrays and back
dc_stream_get_status() with a fake dc->current_state to cover the
removal and addition ELD notifications along with their guard/skip
paths: writeback connector, detached connector, missing new CRTC state,
no modeset, and absent stream status.
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_audio.c | 1 +
.../amdgpu_dm/tests/amdgpu_dm_audio_test.c | 532 ++++++++++++++++++
2 files changed, 533 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
index 4b10c683df8e..199f339839b2 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
@@ -321,6 +321,7 @@ void amdgpu_dm_commit_audio(struct drm_device *dev,
amdgpu_dm_audio_eld_notify(adev, inst);
}
}
+EXPORT_IF_KUNIT(amdgpu_dm_commit_audio);
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
int amdgpu_dm_audio_get_param(void)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
index d30b7f644f97..040a7a9c0335 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
@@ -8,9 +8,11 @@
#include <kunit/test.h>
#include <linux/device.h>
+#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_audio_component.h>
#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
#include <drm/drm_eld.h>
#include <drm/drm_kunit_helpers.h>
#include <drm/drm_probe_helper.h>
@@ -37,6 +39,32 @@ static void dm_test_audio_connector_cleanup(void *data)
drm_connector_cleanup(data);
}
+static struct drm_atomic_commit *dm_test_audio_alloc_atomic_state(struct kunit *test,
+ unsigned int num_connector,
+ unsigned int num_crtc)
+{
+ struct drm_atomic_commit *state;
+
+ state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, state);
+
+ state->num_connector = num_connector;
+ if (num_connector) {
+ state->connectors = kunit_kcalloc(test, num_connector,
+ sizeof(*state->connectors),
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, state->connectors);
+ }
+
+ if (num_crtc) {
+ state->crtcs = kunit_kcalloc(test, num_crtc, sizeof(*state->crtcs),
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, state->crtcs);
+ }
+
+ return state;
+}
+
/* Tests for amdgpu_dm_audio_init() */
/**
@@ -411,6 +439,22 @@ static void dm_test_pin_eld_notify(void *audio_ptr, int port, int pipe)
dm_test_eld_notify_ptr = audio_ptr;
}
+static void dm_test_audio_setup_notify_component(struct kunit *test,
+ struct amdgpu_device *adev)
+{
+ struct drm_audio_component *acomp;
+ struct drm_audio_component_audio_ops *audio_ops;
+
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, acomp);
+ KUNIT_ASSERT_NOT_NULL(test, audio_ops);
+
+ audio_ops->pin_eld_notify = dm_test_pin_eld_notify;
+ acomp->audio_ops = audio_ops;
+ adev->dm.audio_component = acomp;
+}
+
/**
* dm_test_eld_notify_invokes_callback - Test ELD notify forwards to hda driver
* @test: The KUnit test context
@@ -685,6 +729,484 @@ static void dm_test_audio_component_get_eld_no_match(struct kunit *test)
KUNIT_EXPECT_EQ(test, buf[0], 0x5a);
}
+/* Tests for amdgpu_dm_commit_audio() */
+
+/**
+ * dm_test_commit_audio_notifies_removed_connector - Test removal notification
+ * @test: The KUnit test context
+ *
+ * When a connector loses its CRTC, commit_audio should clear its audio
+ * instance and notify the audio component for the old pin.
+ */
+static void dm_test_commit_audio_notifies_removed_connector(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct drm_atomic_commit *state;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector_state *old_conn_state;
+ struct drm_connector_state *new_conn_state;
+ struct drm_crtc *crtc;
+
+ state = dm_test_audio_alloc_atomic_state(test, 1, 0);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL);
+ new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL);
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, old_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, new_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+
+ mutex_init(&adev->dm.audio_lock);
+ dm_test_audio_setup_notify_component(test, adev);
+ aconnector->audio_inst = 5;
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ old_conn_state->crtc = crtc;
+ state->connectors[0].ptr = &aconnector->base;
+ state->connectors[0].old_state = old_conn_state;
+ state->connectors[0].new_state = new_conn_state;
+
+ dm_test_eld_notify_count = 0;
+ dm_test_eld_notify_port = -1;
+
+ amdgpu_dm_commit_audio(&adev->ddev, state);
+
+ KUNIT_EXPECT_EQ(test, aconnector->audio_inst, -1);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 5);
+}
+
+/**
+ * dm_test_commit_audio_notifies_added_connector - Test addition notification
+ * @test: The KUnit test context
+ *
+ * When a modeset enables a connector with a stream status, commit_audio should
+ * store the stream audio instance and notify the audio component for that pin.
+ */
+static void dm_test_commit_audio_notifies_added_connector(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct drm_atomic_commit *state;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector_state *old_conn_state;
+ struct drm_connector_state *new_conn_state;
+ struct drm_crtc *crtc;
+ struct dm_crtc_state *dm_crtc_state;
+ struct dc_stream_state *stream;
+
+ state = dm_test_audio_alloc_atomic_state(test, 1, 1);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL);
+ new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL);
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, old_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, new_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+ KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
+ KUNIT_ASSERT_NOT_NULL(test, stream);
+
+ adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc);
+ adev->dm.dc->current_state = kunit_kzalloc(test,
+ sizeof(*adev->dm.dc->current_state),
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc->current_state);
+ stream->ctx = kunit_kzalloc(test, sizeof(*stream->ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, stream->ctx);
+ stream->ctx->dc = adev->dm.dc;
+ adev->dm.dc->current_state->stream_count = 1;
+ adev->dm.dc->current_state->streams[0] = stream;
+ adev->dm.dc->current_state->stream_status[0].audio_inst = 8;
+
+ mutex_init(&adev->dm.audio_lock);
+ dm_test_audio_setup_notify_component(test, adev);
+ aconnector->audio_inst = -1;
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ crtc->index = 0;
+ new_conn_state->crtc = crtc;
+ dm_crtc_state->base.crtc = crtc;
+ dm_crtc_state->base.mode_changed = true;
+ dm_crtc_state->stream = stream;
+ state->connectors[0].ptr = &aconnector->base;
+ state->connectors[0].old_state = old_conn_state;
+ state->connectors[0].new_state = new_conn_state;
+ state->crtcs[0].ptr = crtc;
+ state->crtcs[0].new_state = &dm_crtc_state->base;
+
+ dm_test_eld_notify_count = 0;
+ dm_test_eld_notify_port = -1;
+
+ amdgpu_dm_commit_audio(&adev->ddev, state);
+
+ KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 8);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 2);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 8);
+}
+
+/**
+ * dm_test_commit_audio_skips_writeback_removal - Test writeback removal skip
+ * @test: The KUnit test context
+ *
+ * Writeback connectors do not represent an HDA audio pin, so removal-style
+ * notifications should leave their audio instance untouched.
+ */
+static void dm_test_commit_audio_skips_writeback_removal(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct drm_atomic_commit *state;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector_state *old_conn_state;
+ struct drm_connector_state *new_conn_state;
+ struct drm_crtc *crtc;
+
+ state = dm_test_audio_alloc_atomic_state(test, 1, 0);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL);
+ new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL);
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, old_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, new_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+
+ mutex_init(&adev->dm.audio_lock);
+ dm_test_audio_setup_notify_component(test, adev);
+ aconnector->audio_inst = 9;
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK;
+ old_conn_state->crtc = crtc;
+ state->connectors[0].ptr = &aconnector->base;
+ state->connectors[0].old_state = old_conn_state;
+ state->connectors[0].new_state = new_conn_state;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_commit_audio(&adev->ddev, state);
+
+ KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 9);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+/**
+ * dm_test_commit_audio_skips_without_new_crtc_state - Test missing CRTC state
+ * @test: The KUnit test context
+ *
+ * A connector still attached to the same CRTC should not notify if the atomic
+ * state does not contain a new CRTC state for that CRTC.
+ */
+static void dm_test_commit_audio_skips_without_new_crtc_state(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct drm_atomic_commit *state;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector_state *old_conn_state;
+ struct drm_connector_state *new_conn_state;
+ struct drm_crtc *crtc;
+
+ state = dm_test_audio_alloc_atomic_state(test, 1, 1);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL);
+ new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL);
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, old_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, new_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+
+ mutex_init(&adev->dm.audio_lock);
+ dm_test_audio_setup_notify_component(test, adev);
+ aconnector->audio_inst = 4;
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ crtc->index = 0;
+ old_conn_state->crtc = crtc;
+ new_conn_state->crtc = crtc;
+ state->connectors[0].ptr = &aconnector->base;
+ state->connectors[0].old_state = old_conn_state;
+ state->connectors[0].new_state = new_conn_state;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_commit_audio(&adev->ddev, state);
+
+ KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 4);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+/**
+ * dm_test_commit_audio_skips_without_stream_status - Test missing DC status
+ * @test: The KUnit test context
+ *
+ * If the new CRTC stream is absent from the current DC state, additions should
+ * be skipped because there is no audio instance to publish.
+ */
+static void dm_test_commit_audio_skips_without_stream_status(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct drm_atomic_commit *state;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector_state *old_conn_state;
+ struct drm_connector_state *new_conn_state;
+ struct drm_crtc *crtc;
+ struct dm_crtc_state *dm_crtc_state;
+ struct dc_stream_state *stream;
+
+ state = dm_test_audio_alloc_atomic_state(test, 1, 1);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL);
+ new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL);
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, old_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, new_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+ KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
+ KUNIT_ASSERT_NOT_NULL(test, stream);
+
+ adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc);
+ adev->dm.dc->current_state = kunit_kzalloc(test,
+ sizeof(*adev->dm.dc->current_state),
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc->current_state);
+ stream->ctx = kunit_kzalloc(test, sizeof(*stream->ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, stream->ctx);
+ stream->ctx->dc = adev->dm.dc;
+
+ mutex_init(&adev->dm.audio_lock);
+ dm_test_audio_setup_notify_component(test, adev);
+ aconnector->audio_inst = -1;
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ crtc->index = 0;
+ new_conn_state->crtc = crtc;
+ dm_crtc_state->base.crtc = crtc;
+ dm_crtc_state->base.mode_changed = true;
+ dm_crtc_state->stream = stream;
+ state->connectors[0].ptr = &aconnector->base;
+ state->connectors[0].old_state = old_conn_state;
+ state->connectors[0].new_state = new_conn_state;
+ state->crtcs[0].ptr = crtc;
+ state->crtcs[0].new_state = &dm_crtc_state->base;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_commit_audio(&adev->ddev, state);
+
+ KUNIT_EXPECT_EQ(test, aconnector->audio_inst, -1);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1);
+}
+
+/**
+ * dm_test_commit_audio_skips_detached_connector - Test detached connector skip
+ * @test: The KUnit test context
+ *
+ * A connector that remains detached across the commit should not notify or
+ * alter the stored audio instance.
+ */
+static void dm_test_commit_audio_skips_detached_connector(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct drm_atomic_commit *state;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector_state *old_conn_state;
+ struct drm_connector_state *new_conn_state;
+
+ state = dm_test_audio_alloc_atomic_state(test, 1, 0);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL);
+ new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, old_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, new_conn_state);
+
+ aconnector->audio_inst = 11;
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ state->connectors[0].ptr = &aconnector->base;
+ state->connectors[0].old_state = old_conn_state;
+ state->connectors[0].new_state = new_conn_state;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_commit_audio(&adev->ddev, state);
+
+ KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 11);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+/**
+ * dm_test_commit_audio_skips_without_modeset - Test no-modeset skip
+ * @test: The KUnit test context
+ *
+ * A connector that stays on the same CRTC should not notify if that CRTC does
+ * not need a modeset.
+ */
+static void dm_test_commit_audio_skips_without_modeset(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct drm_atomic_commit *state;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector_state *old_conn_state;
+ struct drm_connector_state *new_conn_state;
+ struct drm_crtc *crtc;
+ struct dm_crtc_state *dm_crtc_state;
+
+ state = dm_test_audio_alloc_atomic_state(test, 1, 1);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL);
+ new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL);
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, old_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, new_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+ KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
+
+ aconnector->audio_inst = 12;
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ crtc->index = 0;
+ old_conn_state->crtc = crtc;
+ new_conn_state->crtc = crtc;
+ dm_crtc_state->base.crtc = crtc;
+ state->connectors[0].ptr = &aconnector->base;
+ state->connectors[0].old_state = old_conn_state;
+ state->connectors[0].new_state = new_conn_state;
+ state->crtcs[0].ptr = crtc;
+ state->crtcs[0].new_state = &dm_crtc_state->base;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_commit_audio(&adev->ddev, state);
+
+ KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 12);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+/**
+ * dm_test_commit_audio_skips_addition_without_stream - Test NULL stream skip
+ * @test: The KUnit test context
+ *
+ * A modeset still sends the removal-side ELD notification, but the addition
+ * side must skip if the new DM CRTC state has no stream.
+ */
+static void dm_test_commit_audio_skips_addition_without_stream(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct drm_atomic_commit *state;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector_state *old_conn_state;
+ struct drm_connector_state *new_conn_state;
+ struct drm_crtc *crtc;
+ struct dm_crtc_state *dm_crtc_state;
+
+ state = dm_test_audio_alloc_atomic_state(test, 1, 1);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL);
+ new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL);
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, old_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, new_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+ KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
+
+ mutex_init(&adev->dm.audio_lock);
+ dm_test_audio_setup_notify_component(test, adev);
+ aconnector->audio_inst = 13;
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ crtc->index = 0;
+ old_conn_state->crtc = crtc;
+ new_conn_state->crtc = crtc;
+ dm_crtc_state->base.crtc = crtc;
+ dm_crtc_state->base.mode_changed = true;
+ state->connectors[0].ptr = &aconnector->base;
+ state->connectors[0].old_state = old_conn_state;
+ state->connectors[0].new_state = new_conn_state;
+ state->crtcs[0].ptr = crtc;
+ state->crtcs[0].new_state = &dm_crtc_state->base;
+
+ dm_test_eld_notify_count = 0;
+ dm_test_eld_notify_port = -1;
+
+ amdgpu_dm_commit_audio(&adev->ddev, state);
+
+ KUNIT_EXPECT_EQ(test, aconnector->audio_inst, -1);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 13);
+}
+
+/**
+ * dm_test_commit_audio_skips_writeback_addition - Test writeback addition skip
+ * @test: The KUnit test context
+ *
+ * Even with a valid modeset and stream status, writeback connectors must not
+ * publish an HDA audio instance.
+ */
+static void dm_test_commit_audio_skips_writeback_addition(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct drm_atomic_commit *state;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector_state *old_conn_state;
+ struct drm_connector_state *new_conn_state;
+ struct drm_crtc *crtc;
+ struct dm_crtc_state *dm_crtc_state;
+ struct dc_stream_state *stream;
+
+ state = dm_test_audio_alloc_atomic_state(test, 1, 1);
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL);
+ new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL);
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, old_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, new_conn_state);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+ KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
+ KUNIT_ASSERT_NOT_NULL(test, stream);
+
+ adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc);
+ adev->dm.dc->current_state = kunit_kzalloc(test,
+ sizeof(*adev->dm.dc->current_state),
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc->current_state);
+ stream->ctx = kunit_kzalloc(test, sizeof(*stream->ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, stream->ctx);
+ stream->ctx->dc = adev->dm.dc;
+ adev->dm.dc->current_state->stream_count = 1;
+ adev->dm.dc->current_state->streams[0] = stream;
+ adev->dm.dc->current_state->stream_status[0].audio_inst = 14;
+
+ mutex_init(&adev->dm.audio_lock);
+ dm_test_audio_setup_notify_component(test, adev);
+ aconnector->audio_inst = 15;
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK;
+ crtc->index = 0;
+ new_conn_state->crtc = crtc;
+ dm_crtc_state->base.crtc = crtc;
+ dm_crtc_state->base.mode_changed = true;
+ dm_crtc_state->stream = stream;
+ state->connectors[0].ptr = &aconnector->base;
+ state->connectors[0].old_state = old_conn_state;
+ state->connectors[0].new_state = new_conn_state;
+ state->crtcs[0].ptr = crtc;
+ state->crtcs[0].new_state = &dm_crtc_state->base;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_commit_audio(&adev->ddev, state);
+
+ KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 15);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
static struct kunit_case dm_audio_test_cases[] = {
/* amdgpu_dm_audio_init */
KUNIT_CASE(dm_test_audio_init_disabled),
@@ -712,6 +1234,16 @@ static struct kunit_case dm_audio_test_cases[] = {
/* amdgpu_dm_audio_component_get_eld */
KUNIT_CASE(dm_test_audio_component_get_eld_copies_matching_connector),
KUNIT_CASE(dm_test_audio_component_get_eld_no_match),
+ /* amdgpu_dm_commit_audio */
+ KUNIT_CASE(dm_test_commit_audio_notifies_removed_connector),
+ KUNIT_CASE(dm_test_commit_audio_notifies_added_connector),
+ KUNIT_CASE(dm_test_commit_audio_skips_writeback_removal),
+ KUNIT_CASE(dm_test_commit_audio_skips_without_new_crtc_state),
+ KUNIT_CASE(dm_test_commit_audio_skips_without_stream_status),
+ KUNIT_CASE(dm_test_commit_audio_skips_detached_connector),
+ KUNIT_CASE(dm_test_commit_audio_skips_without_modeset),
+ KUNIT_CASE(dm_test_commit_audio_skips_addition_without_stream),
+ KUNIT_CASE(dm_test_commit_audio_skips_writeback_addition),
{}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 20/49] drm/amd/display: Use current mpc pipe in set output transfer func
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (18 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 19/49] drm/amd/display: add KUnit tests for audio commit path Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 21/49] drm/amd/display: Correct vblank_end calc for fams cmd packet Fangzhi Zuo
` (28 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Martin Leung,
Alvin Lee
From: Tomasz Siemek <tomasz.siemek@amd.com>
[why]
Only the top pipe had the output transfer function programmed.
[how]
Program the current pipe from the MPC chain during fast sequence build.
Reviewed-by: Martin Leung <martin.leung@amd.com>
Reviewed-by: Alvin Lee <alvin.lee2@amd.com>
Signed-off-by: Tomasz Siemek <tomasz.siemek@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 4 ++--
1 file changed, 2 insertions(+), 2 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 8b54bab98283..11411fa94665 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
@@ -1111,7 +1111,7 @@ void hwss_build_fast_sequence(struct dc *dc,
(*num_steps)++;
}
}
- if (current_mpc_pipe->stream->update_flags.bits.out_tf) {
+ if (hws->funcs.set_output_transfer_func && current_mpc_pipe->stream->update_flags.bits.out_tf) {
struct set_output_transfer_func_params *otf_params =
&block_sequence[*num_steps].params.set_output_transfer_func_params;
@@ -1119,7 +1119,7 @@ void hwss_build_fast_sequence(struct dc *dc,
otf_params->xfm = current_mpc_pipe->plane_res.xfm;
otf_params->mpc = dc->res_pool->mpc;
otf_params->mpcc_id = current_mpc_pipe->plane_res.hubp->inst;
- otf_params->is_top_pipe = resource_is_pipe_type(pipe_ctx, OPP_HEAD);
+ otf_params->is_top_pipe = resource_is_pipe_type(current_mpc_pipe, OPP_HEAD);
otf_params->stream = current_mpc_pipe->stream;
block_sequence[*num_steps].func = DPP_SET_OUTPUT_TRANSFER_FUNC;
(*num_steps)++;
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 21/49] drm/amd/display: Correct vblank_end calc for fams cmd packet
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (19 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 20/49] drm/amd/display: Use current mpc pipe in set output transfer func Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 22/49] drm/amd/display: Add KUnit test for native backlight registration Fangzhi Zuo
` (27 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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 Carbones,
Dillon Varone
From: Nicholas Carbones <Nicholas.Carbones@amd.com>
[Why]
By changing how we populate vblank_nom, constraining vblank size, the
vblank_end calculation for the fams command packet gets affected. We add
up v_active with a new vblank_nom value for stream pstate's nom_vtotal,
which describes the vtotal and nominal refresh rate, and that causes
underflow.
[How]
Set nom_vtotal equal to the timing's v_total.
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Nicholas Carbones <Nicholas.Carbones@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c
index d5f20c85006e..8f481ef9316f 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c
@@ -1710,7 +1710,7 @@ static void build_pstate_meta_per_stream(struct dml2_pmo_instance *pmo,
/* common */
stream_pstate_meta->valid = true;
stream_pstate_meta->otg_vline_time_us = (double)timing->h_total / timing->pixel_clock_khz * 1000.0;
- stream_pstate_meta->nom_vtotal = stream_descriptor->timing.vblank_nom + stream_descriptor->timing.v_active;
+ stream_pstate_meta->nom_vtotal = stream_descriptor->timing.v_total;
stream_pstate_meta->nom_refresh_rate_hz = timing->pixel_clock_khz * 1000.0 /
(stream_pstate_meta->nom_vtotal * timing->h_total);
stream_pstate_meta->nom_frame_time_us =
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 22/49] drm/amd/display: Add KUnit test for native backlight registration
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (20 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 21/49] drm/amd/display: Correct vblank_end calc for fams cmd packet Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 23/49] drm/amd/display: Add color transfer-function tests Fangzhi Zuo
` (26 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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 case covering the successful path of
amdgpu_dm_register_backlight_device(). The test uses the standard DRM
KUnit device helpers to register a native backlight device, verifies the
calculated backlight properties and cached brightness, and unregisters
the device through KUnit cleanup to avoid leaking a class device across
cases.
Assisted-by: Copilot:GPT-5.6-Terra
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../tests/amdgpu_dm_backlight_test.c | 52 +++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index 618fd5142da0..7ca17f803f9d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -326,6 +326,11 @@ static int dm_test_get_backlight_level_error(const struct dc_link *link)
return DC_ERROR_UNEXPECTED;
}
+static void dm_test_unregister_backlight_device(void *data)
+{
+ backlight_device_unregister(data);
+}
+
static bool dm_test_get_backlight_level_nits(struct dc_link *link,
uint32_t *avg,
uint32_t *peak)
@@ -463,6 +468,52 @@ static void dm_test_register_backlight_device_negative_index(struct kunit *test)
KUNIT_EXPECT_NULL(test, adev->dm.backlight_dev[0]);
}
+/**
+ * dm_test_register_backlight_device_success - Test native backlight registration
+ * @test: The KUnit test context
+ *
+ * A native backlight device must be registered with the calculated
+ * properties, and its initial readback must retain the initial brightness.
+ */
+static void dm_test_register_backlight_device_success(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_dm_backlight_caps *caps;
+ struct dc_link *link = dm_kunit_alloc_link(test);
+ struct drm_minor *primary;
+ unsigned int max;
+
+ setup_test_link_service(test, link);
+ link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_error;
+ primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary);
+ adev->ddev.primary = primary;
+ adev->dm.backlight_link[0] = link;
+
+ aconnector = dm_kunit_alloc_connector(test, adev, link);
+ aconnector->bl_idx = 0;
+ aconnector->base.kdev = adev->ddev.dev;
+ caps = &adev->dm.backlight_caps[0];
+ caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+ caps->ac_level = 50;
+ caps->dc_level = 25;
+ max = 0x101 * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+
+ amdgpu_dm_register_backlight_device(aconnector);
+
+ KUNIT_ASSERT_NOT_NULL(test, adev->dm.backlight_dev[0]);
+ KUNIT_EXPECT_EQ(test, adev->dm.backlight_dev[0]->props.max_brightness, max);
+ KUNIT_EXPECT_EQ(test, adev->dm.backlight_dev[0]->props.brightness,
+ DIV_ROUND_CLOSEST(max * caps->ac_level, 100));
+ KUNIT_EXPECT_EQ(test, adev->dm.brightness[0],
+ adev->dm.backlight_dev[0]->props.brightness);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ dm_test_unregister_backlight_device,
+ adev->dm.backlight_dev[0]), 0);
+}
+
static struct drm_connector *setup_panel_power_savings_connector(struct kunit *test,
struct device **device_out,
struct dm_connector_state **state_out)
@@ -1861,6 +1912,7 @@ static struct kunit_case dm_backlight_test_cases[] = {
KUNIT_CASE(dm_test_backlight_get_brightness_uses_device_index),
/* amdgpu_dm_register_backlight_device */
KUNIT_CASE(dm_test_register_backlight_device_negative_index),
+ KUNIT_CASE(dm_test_register_backlight_device_success),
/* panel_power_savings_show / store */
KUNIT_CASE(dm_test_panel_power_savings_show_maps_disable_to_zero),
KUNIT_CASE(dm_test_panel_power_savings_show_reports_level),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 23/49] drm/amd/display: Add color transfer-function tests
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (21 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 22/49] drm/amd/display: Add KUnit test for native backlight registration Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 24/49] drm/amd/display: Add atomic " Fangzhi Zuo
` (25 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
[WHY]
The color transfer-function calculation helpers were not covered by
the amdgpu_dm_color KUnit suite. They rely on DAL fixed-point math
and run under the KUnit UML build without native floating point.
[HOW]
Expose the five static helpers to KUnit and add direct coverage for
the legacy, 16-bit, and 32-bit input and output transfer-function
paths, including sRGB, linear, and ROM-backed variants.
Assisted-by: Copilot:GPT-5.6-Sol
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_color.c | 36 +++--
.../amd/display/amdgpu_dm/amdgpu_dm_color.h | 14 ++
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 147 ++++++++++++++++++
3 files changed, 184 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
index 658dfc37d4d4..7b68c6846039 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
@@ -618,9 +618,10 @@ EXPORT_IF_KUNIT(__drm_ctm_3x4_to_dc_matrix);
* Returns:
* 0 in case of success, -ENOMEM if fails
*/
-static int __set_legacy_tf(struct dc_transfer_func *func,
- const struct drm_color_lut *lut, uint32_t lut_size,
- bool has_rom)
+STATIC_IFN_KUNIT int
+__set_legacy_tf(struct dc_transfer_func *func,
+ const struct drm_color_lut *lut, uint32_t lut_size,
+ bool has_rom)
{
struct dc_gamma *gamma = NULL;
struct calculate_buffer cal_buffer = {0};
@@ -645,6 +646,7 @@ static int __set_legacy_tf(struct dc_transfer_func *func,
return res ? 0 : -ENOMEM;
}
+EXPORT_IF_KUNIT(__set_legacy_tf);
/**
* __set_output_tf - calculates the output transfer function based on expected input space.
@@ -656,9 +658,10 @@ static int __set_legacy_tf(struct dc_transfer_func *func,
* Returns:
* 0 in case of success. -ENOMEM if fails.
*/
-static int __set_output_tf(struct dc_transfer_func *func,
- const struct drm_color_lut *lut, uint32_t lut_size,
- bool has_rom)
+STATIC_IFN_KUNIT int
+__set_output_tf(struct dc_transfer_func *func,
+ const struct drm_color_lut *lut, uint32_t lut_size,
+ bool has_rom)
{
struct dc_gamma *gamma = NULL;
struct calculate_buffer cal_buffer = {0};
@@ -703,6 +706,7 @@ static int __set_output_tf(struct dc_transfer_func *func,
return res ? 0 : -ENOMEM;
}
+EXPORT_IF_KUNIT(__set_output_tf);
/**
* __set_output_tf_32 - calculates the output transfer function based on expected input space.
@@ -714,9 +718,10 @@ static int __set_output_tf(struct dc_transfer_func *func,
* Returns:
* 0 in case of success. -ENOMEM if fails.
*/
-static int __set_output_tf_32(struct dc_transfer_func *func,
- const struct drm_color_lut32 *lut, uint32_t lut_size,
- bool has_rom)
+STATIC_IFN_KUNIT int
+__set_output_tf_32(struct dc_transfer_func *func,
+ const struct drm_color_lut32 *lut, uint32_t lut_size,
+ bool has_rom)
{
struct dc_gamma *gamma = NULL;
struct calculate_buffer cal_buffer = {0};
@@ -759,6 +764,7 @@ static int __set_output_tf_32(struct dc_transfer_func *func,
return res ? 0 : -ENOMEM;
}
+EXPORT_IF_KUNIT(__set_output_tf_32);
STATIC_IFN_KUNIT void __set_tf_bypass(struct dc_transfer_func *tf)
{
@@ -820,8 +826,9 @@ EXPORT_IF_KUNIT(amdgpu_dm_set_atomic_regamma);
* Returns:
* 0 in case of success. -ENOMEM if fails.
*/
-static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *func,
- const struct drm_color_lut *lut, uint32_t lut_size)
+STATIC_IFN_KUNIT int __set_input_tf(struct dc_color_caps *caps,
+ struct dc_transfer_func *func,
+ const struct drm_color_lut *lut, uint32_t lut_size)
{
struct dc_gamma *gamma = NULL;
bool res;
@@ -844,6 +851,7 @@ static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *f
return res ? 0 : -ENOMEM;
}
+EXPORT_IF_KUNIT(__set_input_tf);
/**
* __set_input_tf_32 - calculates the input transfer function based on expected
@@ -856,8 +864,9 @@ static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *f
* Returns:
* 0 in case of success. -ENOMEM if fails.
*/
-static int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func *func,
- const struct drm_color_lut32 *lut, uint32_t lut_size)
+STATIC_IFN_KUNIT int __set_input_tf_32(struct dc_color_caps *caps,
+ struct dc_transfer_func *func,
+ const struct drm_color_lut32 *lut, uint32_t lut_size)
{
struct dc_gamma *gamma = NULL;
bool res;
@@ -880,6 +889,7 @@ static int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func
return res ? 0 : -ENOMEM;
}
+EXPORT_IF_KUNIT(__set_input_tf_32);
STATIC_IFN_KUNIT
enum dc_transfer_func_predefined
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h
index cec23a020c3d..3e8a3d3fc855 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h
@@ -64,6 +64,20 @@ void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm,
struct fixed31_32 *matrix);
void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm,
struct fixed31_32 *matrix);
+int __set_legacy_tf(struct dc_transfer_func *func,
+ const struct drm_color_lut *lut, uint32_t lut_size,
+ bool has_rom);
+int __set_output_tf(struct dc_transfer_func *func,
+ const struct drm_color_lut *lut, uint32_t lut_size,
+ bool has_rom);
+int __set_output_tf_32(struct dc_transfer_func *func,
+ const struct drm_color_lut32 *lut, uint32_t lut_size,
+ bool has_rom);
+struct dc_color_caps;
+int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *func,
+ const struct drm_color_lut *lut, uint32_t lut_size);
+int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func *func,
+ const struct drm_color_lut32 *lut, uint32_t lut_size);
enum dc_transfer_func_predefined
amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf);
enum dc_transfer_func_predefined
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
index e76e533a8323..de11a606a88c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
@@ -1375,6 +1375,146 @@ static void dm_test_set_tf_distributed_points_pq(struct kunit *test)
KUNIT_EXPECT_EQ(test, tf->sdr_ref_white_level, 80U);
}
+/**
+ * dm_test_set_legacy_tf_identity - Legacy identity LUT uses the sRGB ROM path
+ * @test: KUnit test context
+ */
+static void dm_test_set_legacy_tf_identity(struct kunit *test)
+{
+ struct drm_color_lut *lut;
+ struct dc_transfer_func *tf;
+ int i;
+
+ tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tf);
+ lut = kunit_kcalloc(test, MAX_COLOR_LEGACY_LUT_ENTRIES, sizeof(*lut), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, lut);
+
+ for (i = 0; i < MAX_COLOR_LEGACY_LUT_ENTRIES; i++) {
+ u16 value = i * MAX_DRM_LUT_VALUE / (MAX_COLOR_LEGACY_LUT_ENTRIES - 1);
+
+ lut[i].red = value;
+ lut[i].green = value;
+ lut[i].blue = value;
+ }
+
+ tf->type = TF_TYPE_PREDEFINED;
+ tf->tf = TRANSFER_FUNCTION_SRGB;
+
+ KUNIT_EXPECT_EQ(test,
+ __set_legacy_tf(tf, lut, MAX_COLOR_LEGACY_LUT_ENTRIES, true),
+ 0);
+}
+
+/**
+ * dm_test_set_output_tf_linear - Linear output without a LUT calculates degamma
+ * @test: KUnit test context
+ */
+static void dm_test_set_output_tf_linear(struct kunit *test)
+{
+ struct dc_transfer_func *tf;
+
+ tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tf);
+ tf->type = TF_TYPE_PREDEFINED;
+ tf->tf = TRANSFER_FUNCTION_LINEAR;
+
+ KUNIT_EXPECT_EQ(test, __set_output_tf(tf, NULL, 0, false), 0);
+}
+
+/**
+ * dm_test_set_output_tf_32_srgb_rom - sRGB output uses the no-LUT ROM path
+ * @test: KUnit test context
+ */
+static void dm_test_set_output_tf_32_srgb_rom(struct kunit *test)
+{
+ struct dc_transfer_func *tf;
+
+ tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tf);
+ tf->type = TF_TYPE_PREDEFINED;
+ tf->tf = TRANSFER_FUNCTION_SRGB;
+
+ KUNIT_EXPECT_EQ(test, __set_output_tf_32(tf, NULL, 0, true), 0);
+}
+
+/**
+ * dm_test_set_input_tf_srgb - Predefined sRGB input needs no generated curve
+ * @test: KUnit test context
+ */
+static void dm_test_set_input_tf_srgb(struct kunit *test)
+{
+ struct dc_transfer_func *tf;
+
+ tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tf);
+ tf->type = TF_TYPE_PREDEFINED;
+ tf->tf = TRANSFER_FUNCTION_SRGB;
+
+ KUNIT_EXPECT_EQ(test, __set_input_tf(NULL, tf, NULL, 0), 0);
+}
+
+/**
+ * dm_test_set_input_tf_32_srgb - 32-bit input wrapper accepts predefined sRGB
+ * @test: KUnit test context
+ */
+static void dm_test_set_input_tf_32_srgb(struct kunit *test)
+{
+ struct dc_transfer_func *tf;
+
+ tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tf);
+ tf->type = TF_TYPE_PREDEFINED;
+ tf->tf = TRANSFER_FUNCTION_SRGB;
+
+ KUNIT_EXPECT_EQ(test, __set_input_tf_32(NULL, tf, NULL, 0), 0);
+}
+
+/**
+ * dm_test_set_transfer_funcs_with_luts - LUT-backed transfer functions succeed
+ * @test: KUnit test context
+ */
+static void dm_test_set_transfer_funcs_with_luts(struct kunit *test)
+{
+ struct drm_color_lut32 *lut32;
+ struct drm_color_lut *lut;
+ struct dc_transfer_func *tf;
+
+ lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, sizeof(*lut), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, lut);
+ lut32 = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, sizeof(*lut32), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, lut32);
+ tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tf);
+
+ tf->type = TF_TYPE_DISTRIBUTED_POINTS;
+ tf->tf = TRANSFER_FUNCTION_LINEAR;
+ KUNIT_EXPECT_EQ(test,
+ __set_output_tf(tf, lut, MAX_COLOR_LUT_ENTRIES, false),
+ 0);
+
+ memset(tf, 0, sizeof(*tf));
+ tf->type = TF_TYPE_DISTRIBUTED_POINTS;
+ tf->tf = TRANSFER_FUNCTION_LINEAR;
+ KUNIT_EXPECT_EQ(test,
+ __set_output_tf_32(tf, lut32, MAX_COLOR_LUT_ENTRIES, false),
+ 0);
+
+ memset(tf, 0, sizeof(*tf));
+ tf->type = TF_TYPE_DISTRIBUTED_POINTS;
+ tf->tf = TRANSFER_FUNCTION_SRGB;
+ KUNIT_EXPECT_EQ(test,
+ __set_input_tf(NULL, tf, lut, MAX_COLOR_LUT_ENTRIES),
+ 0);
+
+ memset(tf, 0, sizeof(*tf));
+ tf->type = TF_TYPE_DISTRIBUTED_POINTS;
+ tf->tf = TRANSFER_FUNCTION_SRGB;
+ KUNIT_EXPECT_EQ(test,
+ __set_input_tf_32(NULL, tf, lut32, MAX_COLOR_LUT_ENTRIES),
+ 0);
+}
+
/**
* dm_test_set_atomic_regamma_bypass - No LUT and linear TF: must take bypass path
* @test: KUnit test context
@@ -2384,6 +2524,13 @@ static struct kunit_case dm_color_test_cases[] = {
/* __set_tf_distributed_points */
KUNIT_CASE(dm_test_set_tf_distributed_points_srgb),
KUNIT_CASE(dm_test_set_tf_distributed_points_pq),
+ /* Transfer-function calculation helpers */
+ KUNIT_CASE(dm_test_set_legacy_tf_identity),
+ KUNIT_CASE(dm_test_set_output_tf_linear),
+ KUNIT_CASE(dm_test_set_output_tf_32_srgb_rom),
+ KUNIT_CASE(dm_test_set_input_tf_srgb),
+ KUNIT_CASE(dm_test_set_input_tf_32_srgb),
+ KUNIT_CASE(dm_test_set_transfer_funcs_with_luts),
/* amdgpu_dm_set_atomic_regamma */
KUNIT_CASE(dm_test_set_atomic_regamma_bypass),
/* amdgpu_dm_atomic_shaper_lut */
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 24/49] drm/amd/display: Add atomic transfer-function tests
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (22 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 23/49] drm/amd/display: Add color transfer-function tests Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 25/49] drm/amd/display: Add CRTC and plane degamma tests Fangzhi Zuo
` (24 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
[WHY]
The atomic regamma, shaper, and blend helpers were only exercised on
their linear bypass paths, leaving the generated transfer-function
cases untested.
[HOW]
Add sRGB success-path tests that verify the generated transfer
functions and the corresponding plane color-management enable flags.
Assisted-by: Copilot:GPT-5.6-Sol
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 41 +++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
index de11a606a88c..4945f5429bc1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
@@ -1535,6 +1535,23 @@ static void dm_test_set_atomic_regamma_bypass(struct kunit *test)
KUNIT_EXPECT_EQ(test, (int)out_tf->tf, (int)TRANSFER_FUNCTION_LINEAR);
}
+/**
+ * dm_test_set_atomic_regamma_srgb - Non-linear TF enables generated regamma
+ * @test: KUnit test context
+ */
+static void dm_test_set_atomic_regamma_srgb(struct kunit *test)
+{
+ struct dc_transfer_func *out_tf;
+
+ out_tf = kunit_kzalloc(test, sizeof(*out_tf), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, out_tf);
+
+ KUNIT_EXPECT_EQ(test,
+ amdgpu_dm_set_atomic_regamma(out_tf, NULL, 0, true, TRANSFER_FUNCTION_SRGB),
+ 0);
+ KUNIT_EXPECT_EQ(test, (int)out_tf->type, (int)TF_TYPE_DISTRIBUTED_POINTS);
+}
+
/**
* dm_test_atomic_shaper_lut_bypass - No LUT and linear TF: must take bypass path
* @test: KUnit test context
@@ -1577,6 +1594,28 @@ static void dm_test_atomic_blend_lut_bypass(struct kunit *test)
KUNIT_EXPECT_EQ(test, (int)cm->blend_func.tf, (int)TRANSFER_FUNCTION_LINEAR);
}
+/**
+ * dm_test_atomic_shaper_blend_srgb - Non-linear TFs enable shaper and blend
+ * @test: KUnit test context
+ */
+static void dm_test_atomic_shaper_blend_srgb(struct kunit *test)
+{
+ struct dc_plane_cm *cm;
+
+ cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, cm);
+
+ KUNIT_EXPECT_EQ(test,
+ amdgpu_dm_atomic_shaper_lut(NULL, true, TRANSFER_FUNCTION_SRGB, 0, cm),
+ 0);
+ KUNIT_EXPECT_TRUE(test, cm->flags.bits.shaper_enable);
+
+ KUNIT_EXPECT_EQ(test,
+ amdgpu_dm_atomic_blend_lut(NULL, false, TRANSFER_FUNCTION_SRGB, 0, cm),
+ 0);
+ KUNIT_EXPECT_TRUE(test, cm->flags.bits.blend_enable);
+}
+
/* ---- Tests for __set_colorop_in_tf_1d_curve ---- */
/**
@@ -2533,10 +2572,12 @@ static struct kunit_case dm_color_test_cases[] = {
KUNIT_CASE(dm_test_set_transfer_funcs_with_luts),
/* amdgpu_dm_set_atomic_regamma */
KUNIT_CASE(dm_test_set_atomic_regamma_bypass),
+ KUNIT_CASE(dm_test_set_atomic_regamma_srgb),
/* amdgpu_dm_atomic_shaper_lut */
KUNIT_CASE(dm_test_atomic_shaper_lut_bypass),
/* amdgpu_dm_atomic_blend_lut */
KUNIT_CASE(dm_test_atomic_blend_lut_bypass),
+ KUNIT_CASE(dm_test_atomic_shaper_blend_srgb),
/* __set_colorop_in_tf_1d_curve */
KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_invalid_type),
KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_unsupported_curve),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 25/49] drm/amd/display: Add CRTC and plane degamma tests
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (23 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 24/49] drm/amd/display: Add atomic " Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 26/49] drm/amd/display: Add legacy plane LUT tests Fangzhi Zuo
` (23 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
[WHY]
CRTC degamma classification and its mapping onto the plane input
transfer function lacked both success and conflict coverage.
[HOW]
Exercise legacy regamma detection, atomic and plane degamma LUTs,
BT.709 video mapping, and rejection of simultaneous degamma stages.
Assisted-by: Copilot:GPT-5.6-Sol
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 128 ++++++++++++++++++
1 file changed, 128 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
index 4945f5429bc1..0835177c98d2 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
@@ -2121,6 +2121,36 @@ static void dm_test_check_crtc_color_mgmt_no_luts(struct kunit *test)
KUNIT_EXPECT_FALSE(test, f.crtc_state->cm_is_degamma_srgb);
}
+/**
+ * dm_test_check_crtc_color_mgmt_legacy_regamma - Legacy LUT uses sRGB regamma
+ * @test: KUnit test context
+ */
+static void dm_test_check_crtc_color_mgmt_legacy_regamma(struct kunit *test)
+{
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+
+ f.crtc_state->base.gamma_lut = dm_test_make_lut_blob(test, MAX_COLOR_LEGACY_LUT_ENTRIES);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_check_crtc_color_mgmt(f.crtc_state, true), 0);
+ KUNIT_EXPECT_TRUE(test, f.crtc_state->cm_is_degamma_srgb);
+}
+
+/**
+ * dm_test_check_crtc_color_mgmt_degamma - Atomic degamma is mapped to the plane
+ * @test: KUnit test context
+ */
+static void dm_test_check_crtc_color_mgmt_degamma(struct kunit *test)
+{
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+
+ f.crtc_state->base.degamma_lut =
+ dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_check_crtc_color_mgmt(f.crtc_state, true), 0);
+ KUNIT_EXPECT_TRUE(test, f.crtc_state->cm_has_degamma);
+ KUNIT_EXPECT_FALSE(test, f.crtc_state->cm_is_degamma_srgb);
+}
+
/**
* dm_test_update_crtc_color_mgmt_no_ctm - No CRTC CTM leaves remap bypassed
* @test: KUnit test context
@@ -2214,6 +2244,98 @@ static void dm_test_update_plane_color_mgmt_maps_crtc_degamma(struct kunit *test
(int)TRANSFER_FUNCTION_SRGB);
}
+/**
+ * dm_test_update_plane_color_mgmt_maps_crtc_degamma_lut - CRTC LUT maps to plane degamma
+ * @test: KUnit test context
+ */
+static void dm_test_update_plane_color_mgmt_maps_crtc_degamma_lut(struct kunit *test)
+{
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ int ret;
+
+ f.crtc_state->cm_has_degamma = true;
+ f.crtc_state->cm_is_degamma_srgb = true;
+ f.crtc_state->base.degamma_lut =
+ dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES);
+
+ ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state,
+ f.dc_plane_state);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.type,
+ (int)TF_TYPE_DISTRIBUTED_POINTS);
+ KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.tf,
+ (int)TRANSFER_FUNCTION_SRGB);
+}
+
+/**
+ * dm_test_update_plane_color_mgmt_maps_bt709_degamma - Video uses BT.709 degamma
+ * @test: KUnit test context
+ */
+static void dm_test_update_plane_color_mgmt_maps_bt709_degamma(struct kunit *test)
+{
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ int ret;
+
+ f.crtc_state->cm_is_degamma_srgb = true;
+ f.dc_plane_state->format = SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr;
+
+ ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state,
+ f.dc_plane_state);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.tf,
+ (int)TRANSFER_FUNCTION_BT709);
+}
+
+/**
+ * dm_test_update_plane_color_mgmt_plane_degamma_lut - Non-linear plane LUT succeeds
+ * @test: KUnit test context
+ */
+static void dm_test_update_plane_color_mgmt_plane_degamma_lut(struct kunit *test)
+{
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ struct drm_color_lut *lut;
+ int ret;
+
+ f.dm_plane_state->degamma_lut =
+ dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES);
+ lut = f.dm_plane_state->degamma_lut->data;
+ lut[0].red = MAX_DRM_LUT_VALUE;
+ lut[0].green = MAX_DRM_LUT_VALUE;
+ lut[0].blue = MAX_DRM_LUT_VALUE;
+
+ ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state,
+ f.dc_plane_state);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.type,
+ (int)TF_TYPE_DISTRIBUTED_POINTS);
+}
+
+/**
+ * dm_test_update_plane_color_mgmt_rejects_dual_degamma - Reject two degamma stages
+ * @test: KUnit test context
+ */
+static void dm_test_update_plane_color_mgmt_rejects_dual_degamma(struct kunit *test)
+{
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ struct drm_crtc *crtc;
+ int ret;
+
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, crtc);
+ crtc->dev = &f.adev->ddev;
+ f.crtc_state->base.crtc = crtc;
+ f.crtc_state->cm_has_degamma = true;
+ f.dm_plane_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF;
+
+ ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state,
+ f.dc_plane_state);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
/**
* dm_test_update_plane_color_mgmt_uses_color_caps - DC context provides color caps
* @test: KUnit test context
@@ -2600,11 +2722,17 @@ static struct kunit_case dm_color_test_cases[] = {
KUNIT_CASE(dm_test_colorop_degamma_no_match),
/* CRTC and plane color management update paths */
KUNIT_CASE(dm_test_check_crtc_color_mgmt_no_luts),
+ KUNIT_CASE(dm_test_check_crtc_color_mgmt_legacy_regamma),
+ KUNIT_CASE(dm_test_check_crtc_color_mgmt_degamma),
KUNIT_CASE(dm_test_update_crtc_color_mgmt_no_ctm),
KUNIT_CASE(dm_test_update_crtc_color_mgmt_ctm),
KUNIT_CASE(dm_test_update_plane_color_mgmt_bad_lut3d),
KUNIT_CASE(dm_test_update_plane_color_mgmt_fallback_defaults),
KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_crtc_degamma),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_crtc_degamma_lut),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_bt709_degamma),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_degamma_lut),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_rejects_dual_degamma),
KUNIT_CASE(dm_test_update_plane_color_mgmt_uses_color_caps),
KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_ctm),
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_bypass_pipeline),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 26/49] drm/amd/display: Add legacy plane LUT tests
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (24 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 25/49] drm/amd/display: Add CRTC and plane degamma tests Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 27/49] drm/amd/display: Add truncated colorop tests Fangzhi Zuo
` (22 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
[WHY]
The legacy plane color path did not exercise successful shaper, 3D
LUT, and blend LUT programming as a single transaction.
[HOW]
Provide valid legacy LUT blobs and verify that all three plane
color-management stages are enabled.
Assisted-by: Copilot:GPT-5.6-Sol
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
index 0835177c98d2..025e408a8ae6 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
@@ -2355,6 +2355,33 @@ static void dm_test_update_plane_color_mgmt_uses_color_caps(struct kunit *test)
0);
}
+/**
+ * dm_test_update_plane_color_mgmt_legacy_luts - Legacy shaper and blend LUTs succeed
+ * @test: KUnit test context
+ */
+static void dm_test_update_plane_color_mgmt_legacy_luts(struct kunit *test)
+{
+ const u32 lut3d_entries =
+ MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE;
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ int ret;
+
+ f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true;
+ f.dm_plane_state->shaper_lut =
+ dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES);
+ f.dm_plane_state->lut3d = dm_test_make_lut_blob(test, lut3d_entries);
+ f.dm_plane_state->blend_lut =
+ dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES);
+
+ ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state,
+ f.dc_plane_state);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.shaper_enable);
+ KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable);
+ KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.blend_enable);
+}
+
/**
* dm_test_update_plane_color_mgmt_plane_ctm - Plane CTM maps to gamut remap
* @test: KUnit test context
@@ -2734,6 +2761,7 @@ static struct kunit_case dm_color_test_cases[] = {
KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_degamma_lut),
KUNIT_CASE(dm_test_update_plane_color_mgmt_rejects_dual_degamma),
KUNIT_CASE(dm_test_update_plane_color_mgmt_uses_color_caps),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_legacy_luts),
KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_ctm),
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_bypass_pipeline),
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_multiplier),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 27/49] drm/amd/display: Add truncated colorop tests
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (25 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 26/49] drm/amd/display: Add legacy plane LUT tests Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 28/49] drm/amd/display: Add colorop LUT programming tests Fangzhi Zuo
` (21 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
[WHY]
Truncated colorop pipelines that end after the 3x4 matrix had no
coverage for their fallback exits.
[HOW]
Exercise every remaining pipeline length with bypassed operations
and verify that the plane color stages stay disabled.
Assisted-by: Copilot:GPT-5.6-Sol
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 52 +++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
index 025e408a8ae6..11d42ff59dff 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
@@ -2581,6 +2581,57 @@ static void dm_test_update_plane_color_mgmt_colorop_missing_3x4(struct kunit *te
KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable);
}
+/**
+ * dm_test_update_plane_color_mgmt_colorop_truncated - truncated pipelines fall back
+ * @test: KUnit test context
+ *
+ * Covers each pipeline exit after the 3x4 matrix while all present colorops
+ * remain bypassed, avoiding the floating-point color calculation paths.
+ */
+static void dm_test_update_plane_color_mgmt_colorop_truncated(struct kunit *test)
+{
+ static const enum drm_colorop_type types[] = {
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_MULTIPLIER,
+ DRM_COLOROP_CTM_3X4,
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_1D_LUT,
+ DRM_COLOROP_3D_LUT,
+ DRM_COLOROP_1D_CURVE,
+ };
+ static const enum drm_colorop_curve_1d_type curves[] = {
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ };
+ static const bool bypass[] = {
+ true, true, true, true, true, true, true,
+ };
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct dc_plane_state *dc_plane_state = f.dc_plane_state;
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ struct dm_crtc_state *crtc_state = f.crtc_state;
+ int count, ret;
+
+ f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true;
+
+ for (count = 3; count <= ARRAY_SIZE(types); count++) {
+ f.dm_plane_state->base.color_pipeline =
+ dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, count);
+ memset(f.dc_plane_state, 0, sizeof(*f.dc_plane_state));
+
+ ret = amdgpu_dm_update_plane_color_mgmt(crtc_state, plane_state, dc_plane_state);
+ KUNIT_EXPECT_EQ_MSG(test, ret, 0, "pipeline length %d should fall back", count);
+ KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable);
+ KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable);
+ KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable);
+ }
+}
+
/**
* dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw - no 3D LUT skips 3D ops
* @test: KUnit test context
@@ -2766,6 +2817,7 @@ static struct kunit_case dm_color_test_cases[] = {
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_bypass_pipeline),
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_multiplier),
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_3x4),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_truncated),
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw),
{}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 28/49] drm/amd/display: Add colorop LUT programming tests
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (26 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 27/49] drm/amd/display: Add truncated colorop tests Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 29/49] drm/amd/display: Add KUnit tests for enable_assr Fangzhi Zuo
` (20 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
[WHY]
The enabled colorop shaper, 3D LUT, and blend programming paths were
uncovered, including the fallback for missing 3D LUT data.
[HOW]
Build complete colorop pipelines with valid LUT blobs, verify each
enabled stage, and cover the empty 3D LUT fallback.
Assisted-by: Copilot:GPT-5.6-Sol
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 206 ++++++++++++++++++
1 file changed, 206 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
index 11d42ff59dff..8c2f7ab5f8d3 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
@@ -2676,6 +2676,208 @@ static void dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw(struct kunit *te
KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable);
}
+/**
+ * dm_test_update_plane_color_mgmt_colorop_shaper - enabled shaper TF and LUT succeed
+ * @test: KUnit test context
+ */
+static void dm_test_update_plane_color_mgmt_colorop_shaper(struct kunit *test)
+{
+ static const enum drm_colorop_type types[] = {
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_MULTIPLIER,
+ DRM_COLOROP_CTM_3X4,
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_1D_LUT,
+ DRM_COLOROP_3D_LUT,
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_1D_LUT,
+ };
+ static const enum drm_colorop_curve_1d_type curves[] = {
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ };
+ static const bool bypass[] = {
+ true, true, true, false, false, true, true, true,
+ };
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct drm_colorop_state *shaper_lut_state;
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ struct drm_property_blob *blob;
+ int ret;
+
+ f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true;
+ f.dm_plane_state->base.color_pipeline =
+ dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass,
+ ARRAY_SIZE(types));
+ shaper_lut_state = f.state->colorops[4].new_state;
+ blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, blob);
+ blob->length = MAX_COLOR_LUT_ENTRIES * sizeof(struct drm_color_lut32);
+ blob->data = kunit_kzalloc(test, blob->length, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, blob->data);
+ shaper_lut_state->data = blob;
+
+ ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state,
+ f.dc_plane_state);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.shaper_enable);
+ KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.shaper_func.type,
+ (int)TF_TYPE_DISTRIBUTED_POINTS);
+}
+
+/**
+ * dm_test_update_plane_color_mgmt_colorop_3dlut - enabled 3D LUT succeeds
+ * @test: KUnit test context
+ */
+static void dm_test_update_plane_color_mgmt_colorop_3dlut(struct kunit *test)
+{
+ static const enum drm_colorop_type types[] = {
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_MULTIPLIER,
+ DRM_COLOROP_CTM_3X4,
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_1D_LUT,
+ DRM_COLOROP_3D_LUT,
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_1D_LUT,
+ };
+ static const enum drm_colorop_curve_1d_type curves[] = {
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ };
+ static const bool bypass[] = {
+ true, true, true, true, true, false, true, true,
+ };
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct drm_colorop_state *lut3d_state;
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ struct drm_property_blob *blob;
+ int ret;
+
+ f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true;
+ f.dm_plane_state->base.color_pipeline =
+ dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass,
+ ARRAY_SIZE(types));
+ lut3d_state = f.state->colorops[5].new_state;
+ blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, blob);
+ blob->length = 5 * sizeof(struct drm_color_lut32);
+ blob->data = kunit_kzalloc(test, blob->length, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, blob->data);
+ lut3d_state->data = blob;
+
+ ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state,
+ f.dc_plane_state);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable);
+ KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.shaper_func.type,
+ (int)TF_TYPE_DISTRIBUTED_POINTS);
+ KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.shaper_func.tf,
+ (int)TRANSFER_FUNCTION_LINEAR);
+}
+
+/**
+ * dm_test_update_plane_color_mgmt_colorop_3dlut_no_data - empty 3D LUT falls back
+ * @test: KUnit test context
+ */
+static void dm_test_update_plane_color_mgmt_colorop_3dlut_no_data(struct kunit *test)
+{
+ static const enum drm_colorop_type types[] = {
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_MULTIPLIER,
+ DRM_COLOROP_CTM_3X4,
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_1D_LUT,
+ DRM_COLOROP_3D_LUT,
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_1D_LUT,
+ };
+ static const enum drm_colorop_curve_1d_type curves[] = {
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ };
+ static const bool bypass[] = {
+ true, true, true, true, true, false, true, true,
+ };
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ int ret;
+
+ f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true;
+ f.dm_plane_state->base.color_pipeline =
+ dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass,
+ ARRAY_SIZE(types));
+
+ ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state,
+ f.dc_plane_state);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable);
+}
+
+/**
+ * dm_test_update_plane_color_mgmt_colorop_blend - enabled blend TF and LUT succeed
+ * @test: KUnit test context
+ */
+static void dm_test_update_plane_color_mgmt_colorop_blend(struct kunit *test)
+{
+ static const enum drm_colorop_type types[] = {
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_MULTIPLIER,
+ DRM_COLOROP_CTM_3X4,
+ DRM_COLOROP_1D_CURVE,
+ DRM_COLOROP_1D_LUT,
+ };
+ static const enum drm_colorop_curve_1d_type curves[] = {
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ DRM_COLOROP_1D_CURVE_SRGB_EOTF,
+ };
+ static const bool bypass[] = { true, true, true, false, false };
+ struct dm_test_color_update_fixture f = dm_test_color_update_setup(test);
+ struct drm_colorop_state *blend_lut_state;
+ struct drm_plane_state *plane_state = &f.dm_plane_state->base;
+ struct drm_property_blob *blob;
+ int ret;
+
+ f.dm_plane_state->base.color_pipeline =
+ dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass,
+ ARRAY_SIZE(types));
+ blend_lut_state = f.state->colorops[4].new_state;
+ blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, blob);
+ blob->length = MAX_COLOR_LUT_ENTRIES * sizeof(struct drm_color_lut32);
+ blob->data = kunit_kzalloc(test, blob->length, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, blob->data);
+ blend_lut_state->data = blob;
+
+ ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state,
+ f.dc_plane_state);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.blend_enable);
+ KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.blend_func.type,
+ (int)TF_TYPE_DISTRIBUTED_POINTS);
+}
+
static struct kunit_case dm_color_test_cases[] = {
/* amdgpu_dm_fixpt_from_s3132 */
KUNIT_CASE(dm_test_fixpt_from_s3132_zero),
@@ -2819,6 +3021,10 @@ static struct kunit_case dm_color_test_cases[] = {
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_3x4),
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_truncated),
KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_shaper),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_3dlut),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_3dlut_no_data),
+ KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_blend),
{}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 29/49] drm/amd/display: Add KUnit tests for enable_assr
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (27 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 28/49] drm/amd/display: Add colorop LUT programming tests Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 30/49] drm/amd/display: Add KUnit tests for update_config Fangzhi Zuo
` (19 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Expose enable_assr() for KUnit and cover the "DTM TA not initialized"
path and the full command-build path, using the SR-IOV VF early-return
to bypass psp_dtm_invoke()'s firmware submit.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 4 +-
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 1 +
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 138 ++++++++++++++++++
3 files changed, 142 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index e704b6b7fc0b..7115cc0c7746 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -570,7 +570,8 @@ void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *hdcp_work)
}
EXPORT_IF_KUNIT(hdcp_destroy);
-static bool enable_assr(void *handle, struct dc_link *link)
+STATIC_IFN_KUNIT
+bool enable_assr(void *handle, struct dc_link *link)
{
struct hdcp_workqueue *hdcp_work = handle;
struct mod_hdcp hdcp = hdcp_work->hdcp;
@@ -603,6 +604,7 @@ static bool enable_assr(void *handle, struct dc_link *link)
return true;
}
+EXPORT_IF_KUNIT(enable_assr);
static void update_config(void *handle, struct cp_psp_stream_config *config)
{
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
index a2b08bec58b5..b8ba2888a2ef 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
@@ -123,6 +123,7 @@ void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_ind
struct amdgpu_dm_connector *aconnector);
uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size);
int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version);
+bool enable_assr(void *handle, struct dc_link *link);
#endif
#endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index 5eace7fa4100..1580562727cd 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -14,6 +14,8 @@
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_hdcp.h"
+#include "amdgpu_dm_kunit_test_helpers.h"
+#include "hdcp_psp.h"
static void dummy_work_fn(struct work_struct *work) {}
@@ -1446,6 +1448,138 @@ static void dm_test_hdcp_reset_display_clears_all_state(struct kunit *test)
* hdcp_reset_display().
*/
+/* Tests for enable_assr() */
+
+/**
+ * alloc_test_workqueue_for_assr - workqueue wired to a psp for enable_assr()
+ * @test: KUnit test context for managed allocation
+ * @adev: amdgpu device whose drm_device backs psp->adev (for drm_info())
+ *
+ * Allocates a minimal hdcp_workqueue and a psp_context connected through
+ * hdcp.config.psp.handle, matching the dereference chain enable_assr()
+ * performs. The psp is left with dtm_context.context.initialized == false
+ * (from kzalloc) so enable_assr() takes the "DTM TA not initialized" path.
+ */
+static struct hdcp_workqueue *alloc_test_workqueue_for_assr(struct kunit *test,
+ struct amdgpu_device *adev)
+{
+ struct hdcp_workqueue *work;
+ struct psp_context *psp;
+
+ work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+
+ psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, psp);
+
+ psp->adev = adev;
+ work->hdcp.config.psp.handle = psp;
+
+ return work;
+}
+
+/**
+ * dm_test_enable_assr_uninitialized_dtm_returns_false - DTM TA not initialized
+ * @test: KUnit test context
+ *
+ * When the DTM TA context is not initialized, enable_assr() must take the
+ * early-return path, emit the informational message and return false
+ * without invoking the (real) firmware path.
+ */
+static void dm_test_enable_assr_uninitialized_dtm_returns_false(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_assr(test, adev);
+ struct dc_link *link;
+ bool ret;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ /* kzalloc leaves dtm_context.context.initialized == false */
+ ret = enable_assr(work, link);
+
+ KUNIT_EXPECT_FALSE(test, ret);
+}
+
+/**
+ * dm_test_enable_assr_uninitialized_dtm_ignores_link - link untouched on failure
+ * @test: KUnit test context
+ *
+ * On the "DTM TA not initialized" path enable_assr() returns before reading
+ * any field of @link, so a NULL link must be tolerated and the call must
+ * still return false.
+ */
+static void dm_test_enable_assr_uninitialized_dtm_ignores_link(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_assr(test, adev);
+ bool ret;
+
+ /* link is not dereferenced before the initialized check. */
+ ret = enable_assr(work, NULL);
+
+ KUNIT_EXPECT_FALSE(test, ret);
+}
+
+/**
+ * dm_test_enable_assr_initialized_builds_command_and_fails - full body, invoke bypassed
+ * @test: KUnit test context
+ *
+ * With the DTM TA marked initialized and a valid shared buffer, enable_assr()
+ * runs its full body: it acquires the DTM mutex, clears the shared command,
+ * fills in the ASSR-enable command from @link and pre-sets the status to
+ * GENERIC_FAILURE before invoking the TA.
+ *
+ * psp_dtm_invoke() is prevented from touching real firmware by marking the
+ * device as an SR-IOV virtual function, which makes it return early without
+ * modifying the shared status. The status therefore stays GENERIC_FAILURE, so
+ * enable_assr() must return false. Inspecting the shared command afterwards
+ * proves the body executed and consumed @link.
+ */
+static void dm_test_enable_assr_initialized_builds_command_and_fails(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_assr(test, adev);
+ struct psp_context *psp = work->hdcp.config.psp.handle;
+ struct ta_dtm_shared_memory *dtm_cmd;
+ struct dc_link *link;
+ bool ret;
+
+ dtm_cmd = kunit_kzalloc(test, sizeof(*dtm_cmd), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dtm_cmd);
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ link->link_enc_hw_inst = 3;
+
+ /* Wire up an "initialized" DTM TA with a real shared buffer. */
+ psp->dtm_context.context.initialized = true;
+ psp->dtm_context.context.mem_context.shared_buf = (uint8_t *)dtm_cmd;
+ mutex_init(&psp->dtm_context.mutex);
+
+ /*
+ * Force the SR-IOV VF early-return in psp_dtm_invoke() so no GPU
+ * command is submitted; the shared status is left untouched.
+ */
+ adev->virt.caps |= AMDGPU_SRIOV_CAPS_IS_VF;
+
+ ret = enable_assr(work, link);
+
+ /* Status was never advanced to SUCCESS, so the call must fail. */
+ KUNIT_EXPECT_FALSE(test, ret);
+ /* The command body must have populated the shared buffer. */
+ KUNIT_EXPECT_EQ(test, dtm_cmd->cmd_id, TA_DTM_COMMAND__TOPOLOGY_ASSR_ENABLE);
+ KUNIT_EXPECT_EQ(test,
+ dtm_cmd->dtm_in_message.topology_assr_enable.display_topology_dig_be_index,
+ link->link_enc_hw_inst);
+ KUNIT_EXPECT_EQ(test, dtm_cmd->dtm_status, TA_DTM_STATUS__GENERIC_FAILURE);
+ /* The DTM mutex must be released after the guard scope exits. */
+ KUNIT_EXPECT_FALSE(test, mutex_is_locked(&psp->dtm_context.mutex));
+}
+
+/* End of tests for enable_assr() */
+
static struct kunit_case dm_hdcp_test_cases[] = {
/* hdcp_get_content_protection_from_status() */
KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired),
@@ -1507,6 +1641,10 @@ static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_hdcp_remove_display_enabled_resets_cp),
KUNIT_CASE(dm_test_hdcp_remove_display_null_state_clears_connector),
KUNIT_CASE(dm_test_hdcp_reset_display_clears_all_state),
+ /* enable_assr() */
+ KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_returns_false),
+ KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_ignores_link),
+ KUNIT_CASE(dm_test_enable_assr_initialized_builds_command_and_fails),
{}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 30/49] drm/amd/display: Add KUnit tests for update_config
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (28 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 29/49] drm/amd/display: Add KUnit tests for enable_assr Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 31/49] drm/amd/display: Add KUnit tests for hdcp_create_workqueue Fangzhi Zuo
` (18 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Expose update_config() for KUnit and cover the NULL connector and NULL
dc_link early returns, the dpms_off removal path and the active path
that builds the display/link state and registers the connector. The
active path leaves the DTM TA uninitialized so add_display_to_topology()
returns early without touching firmware.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 4 +-
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 1 +
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 144 ++++++++++++++++++
3 files changed, 148 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index 7115cc0c7746..560bfa43dc8a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -606,7 +606,8 @@ bool enable_assr(void *handle, struct dc_link *link)
}
EXPORT_IF_KUNIT(enable_assr);
-static void update_config(void *handle, struct cp_psp_stream_config *config)
+STATIC_IFN_KUNIT
+void update_config(void *handle, struct cp_psp_stream_config *config)
{
struct hdcp_workqueue *hdcp_work = handle;
struct amdgpu_dm_connector *aconnector;
@@ -692,6 +693,7 @@ static void update_config(void *handle, struct cp_psp_stream_config *config)
hdcp_w->aconnector[conn_index] = aconnector;
process_output(hdcp_w);
}
+EXPORT_IF_KUNIT(update_config);
/**
* DOC: Add sysfs interface for set/get srm
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
index b8ba2888a2ef..30c1e7b1af1a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
@@ -124,6 +124,7 @@ void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_ind
uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size);
int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version);
bool enable_assr(void *handle, struct dc_link *link);
+void update_config(void *handle, struct cp_psp_stream_config *config);
#endif
#endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index 1580562727cd..b75fadd4e14e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -1580,6 +1580,145 @@ static void dm_test_enable_assr_initialized_builds_command_and_fails(struct kuni
/* End of tests for enable_assr() */
+/* Tests for update_config() */
+
+/**
+ * dm_test_update_config_null_connector_is_noop - NULL stream ctx returns early
+ * @test: KUnit test context
+ *
+ * When config->dm_stream_ctx is NULL, update_config() must return before
+ * touching the workqueue, leaving the per-link aconnector array untouched.
+ */
+static void dm_test_update_config_null_connector_is_noop(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_locked(test);
+ struct cp_psp_stream_config config = {0};
+
+ config.dm_stream_ctx = NULL;
+
+ update_config(work, &config);
+
+ KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL);
+}
+
+/**
+ * dm_test_update_config_null_dc_link_is_noop - NULL dc_link returns early
+ * @test: KUnit test context
+ *
+ * A connector without a dc_link must cause update_config() to return before
+ * registering the connector, leaving the aconnector array untouched.
+ */
+static void dm_test_update_config_null_dc_link_is_noop(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_locked(test);
+ struct amdgpu_dm_connector *aconnector;
+ struct cp_psp_stream_config config = {0};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ aconnector->dc_link = NULL;
+
+ config.dm_stream_ctx = aconnector;
+
+ update_config(work, &config);
+
+ KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL);
+}
+
+/**
+ * dm_test_update_config_dpms_off_removes_display - dpms_off path removes display
+ * @test: KUnit test context
+ *
+ * With config->dpms_off set, update_config() must take the removal path:
+ * hdcp_remove_display() reverts an ENABLED connector to DESIRED and clears
+ * its per-link aconnector entry.
+ */
+static void dm_test_update_config_dpms_off_removes_display(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_locked(test);
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0);
+ struct drm_connector_state *conn_state;
+ struct cp_psp_stream_config config = {0};
+
+ conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, conn_state);
+ conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED;
+ aconnector->base.state = conn_state;
+ work->aconnector[0] = aconnector;
+
+ config.dm_stream_ctx = aconnector;
+ config.dpms_off = true;
+
+ update_config(work, &config);
+
+ KUNIT_EXPECT_EQ(test, conn_state->content_protection,
+ DRM_MODE_CONTENT_PROTECTION_DESIRED);
+ KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL);
+
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+}
+
+/**
+ * dm_test_update_config_populates_display_and_link - active path fills state
+ * @test: KUnit test context
+ *
+ * With dpms_off clear, update_config() must build the display and link from
+ * @config, reset the connector's encryption_status to HDCP_OFF, register the
+ * connector and reach process_output() (which enqueues property_validate).
+ *
+ * mod_hdcp_add_display() reaches add_display_to_topology(), which returns
+ * early because the DTM TA is left uninitialized, so no firmware is touched.
+ */
+static void dm_test_update_config_populates_display_and_link(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_locked(test);
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0);
+ struct psp_context *psp;
+ struct cp_psp_stream_config config = {0};
+
+ /* add_display_to_topology() dereferences the psp handle. */
+ psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, psp);
+ work->hdcp.config.psp.handle = psp;
+
+ config.dm_stream_ctx = aconnector;
+ config.dpms_off = false;
+ config.otg_inst = 1;
+ config.dig_fe = 4;
+ config.dig_be = 5;
+ config.stream_enc_idx = 6;
+ config.link_enc_idx = 7;
+ config.dio_output_idx = 8;
+ config.phy_idx = 2;
+
+ update_config(work, &config);
+
+ KUNIT_EXPECT_EQ(test, work->encryption_status[0],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF);
+ KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], aconnector);
+
+ KUNIT_EXPECT_EQ(test, work->display.state, MOD_HDCP_DISPLAY_ACTIVE);
+ KUNIT_EXPECT_EQ(test, work->display.controller,
+ CONTROLLER_ID_D0 + config.otg_inst);
+ KUNIT_EXPECT_EQ(test, work->display.dig_fe, config.dig_fe);
+ KUNIT_EXPECT_EQ(test, work->display.stream_enc_idx, config.stream_enc_idx);
+
+ KUNIT_EXPECT_EQ(test, work->link.dig_be, config.dig_be);
+ KUNIT_EXPECT_EQ(test, work->link.link_enc_idx, config.link_enc_idx);
+ KUNIT_EXPECT_EQ(test, work->link.dio_output_id, config.dio_output_idx);
+ KUNIT_EXPECT_EQ(test, work->link.phy_idx, config.phy_idx);
+
+ KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work));
+
+ cancel_delayed_work_sync(&work->property_validate_dwork);
+ cancel_delayed_work_sync(&work->callback_dwork);
+ cancel_delayed_work_sync(&work->watchdog_timer_dwork);
+}
+
+/* End of tests for update_config() */
+
static struct kunit_case dm_hdcp_test_cases[] = {
/* hdcp_get_content_protection_from_status() */
KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired),
@@ -1645,6 +1784,11 @@ static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_returns_false),
KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_ignores_link),
KUNIT_CASE(dm_test_enable_assr_initialized_builds_command_and_fails),
+ /* update_config() */
+ KUNIT_CASE(dm_test_update_config_null_connector_is_noop),
+ KUNIT_CASE(dm_test_update_config_null_dc_link_is_noop),
+ KUNIT_CASE(dm_test_update_config_dpms_off_removes_display),
+ KUNIT_CASE(dm_test_update_config_populates_display_and_link),
{}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 31/49] drm/amd/display: Add KUnit tests for hdcp_create_workqueue
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (29 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 30/49] drm/amd/display: Add KUnit tests for update_config Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 32/49] drm/amd/display: Add KUnit tests for srm_data_write and srm_data_read Fangzhi Zuo
` (17 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Cover the success path of hdcp_create_workqueue(): the workqueue and SRM
buffers are allocated, max_link is recorded, the cp_psp callbacks and
handle are published and every link's psp handle points at the device
psp. Also cover the dtm_v3_supported branch (set for DCN 3.1, clear
otherwise) and the init loop running for more than one link, alongside
the existing zero-link early-return case.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 103 ++++++++++++++++++
1 file changed, 103 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index b75fadd4e14e..d95babb73711 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -1010,6 +1010,106 @@ static void dm_test_hdcp_create_workqueue_zero_max_links_returns_null(struct kun
KUNIT_EXPECT_PTR_EQ(test, work, NULL);
}
+/**
+ * dm_test_hdcp_create_workqueue_initializes_work - success path wires everything up
+ * @test: KUnit test context
+ *
+ * With a single link, hdcp_create_workqueue() must allocate the workqueue and
+ * both SRM buffers, record max_link, publish the cp_psp callbacks and handle,
+ * and point every link's psp handle at the device psp. A non-matching
+ * dce_version must leave dtm_v3_supported clear. hdcp_destroy() is used to
+ * tear the workqueue down (it also removes the SRM sysfs file created on the
+ * device kobject).
+ */
+static void dm_test_hdcp_create_workqueue_initializes_work(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct cp_psp cp_psp = {0};
+ struct dc *dc = dm_kunit_alloc_dc_with_ctx(test);
+ struct hdcp_workqueue *work;
+
+ /* sysfs_create_bin_file() needs a real device kobject. */
+ adev->dev = adev->ddev.dev;
+ dc->caps.max_links = 1;
+ /* DCE_VERSION_UNKNOWN (0) is not in the DTM v3 list. */
+ dc->ctx->dce_version = DCE_VERSION_UNKNOWN;
+
+ work = hdcp_create_workqueue(adev, &cp_psp, dc);
+
+ KUNIT_ASSERT_NOT_NULL(test, work);
+ KUNIT_EXPECT_EQ(test, work->max_link, 1);
+ KUNIT_EXPECT_NOT_NULL(test, work->srm);
+ KUNIT_EXPECT_NOT_NULL(test, work->srm_temp);
+ KUNIT_EXPECT_PTR_EQ(test, (void *)cp_psp.funcs.update_stream_config,
+ (void *)update_config);
+ KUNIT_EXPECT_PTR_EQ(test, (void *)cp_psp.funcs.enable_assr,
+ (void *)enable_assr);
+ KUNIT_EXPECT_PTR_EQ(test, cp_psp.handle, work);
+ KUNIT_EXPECT_PTR_EQ(test, work[0].hdcp.config.psp.handle, &adev->psp);
+ KUNIT_EXPECT_EQ(test, work[0].hdcp.config.psp.caps.dtm_v3_supported, 0);
+
+ hdcp_destroy(&adev->dev->kobj, work);
+}
+
+/**
+ * dm_test_hdcp_create_workqueue_sets_dtm_v3_for_dcn31 - DCN 3.1 enables DTM v3
+ * @test: KUnit test context
+ *
+ * On a DCN 3.1 device, hdcp_create_workqueue() must set the per-link
+ * dtm_v3_supported cap; an unmatched dce_version must leave it clear.
+ */
+static void dm_test_hdcp_create_workqueue_sets_dtm_v3_for_dcn31(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct cp_psp cp_psp = {0};
+ struct dc *dc = dm_kunit_alloc_dc_with_ctx(test);
+ struct hdcp_workqueue *work;
+
+ adev->dev = adev->ddev.dev;
+ dc->caps.max_links = 1;
+ dc->ctx->dce_version = DCN_VERSION_3_1;
+
+ work = hdcp_create_workqueue(adev, &cp_psp, dc);
+
+ KUNIT_ASSERT_NOT_NULL(test, work);
+ KUNIT_EXPECT_EQ(test, work[0].hdcp.config.psp.caps.dtm_v3_supported, 1);
+
+ hdcp_destroy(&adev->dev->kobj, work);
+}
+
+/**
+ * dm_test_hdcp_create_workqueue_initializes_all_links - loop covers every link
+ * @test: KUnit test context
+ *
+ * With more than one link, hdcp_create_workqueue() must run its init loop for
+ * every entry: each link records max_link, points its psp handle at the device
+ * psp and gets the DTM v3 cap set for a matching dce_version.
+ */
+static void dm_test_hdcp_create_workqueue_initializes_all_links(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct cp_psp cp_psp = {0};
+ struct dc *dc = dm_kunit_alloc_dc_with_ctx(test);
+ struct hdcp_workqueue *work;
+ int i;
+
+ adev->dev = adev->ddev.dev;
+ dc->caps.max_links = 3;
+ dc->ctx->dce_version = DCN_VERSION_3_1;
+
+ work = hdcp_create_workqueue(adev, &cp_psp, dc);
+
+ KUNIT_ASSERT_NOT_NULL(test, work);
+ KUNIT_EXPECT_EQ(test, work->max_link, 3);
+ for (i = 0; i < 3; i++) {
+ KUNIT_EXPECT_PTR_EQ(test, work[i].hdcp.config.psp.handle,
+ &adev->psp);
+ KUNIT_EXPECT_EQ(test, work[i].hdcp.config.psp.caps.dtm_v3_supported, 1);
+ }
+
+ hdcp_destroy(&adev->dev->kobj, work);
+}
+
/* End of tests for hdcp_create_workqueue() */
/* Tests for hdcp_destroy() */
@@ -1765,6 +1865,9 @@ static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate),
/* hdcp_create_workqueue() */
KUNIT_CASE(dm_test_hdcp_create_workqueue_zero_max_links_returns_null),
+ KUNIT_CASE(dm_test_hdcp_create_workqueue_initializes_work),
+ KUNIT_CASE(dm_test_hdcp_create_workqueue_sets_dtm_v3_for_dcn31),
+ KUNIT_CASE(dm_test_hdcp_create_workqueue_initializes_all_links),
/* hdcp_destroy() */
KUNIT_CASE(dm_test_hdcp_destroy_frees_and_removes_sysfs),
KUNIT_CASE(dm_test_hdcp_destroy_zero_links_null_srm),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 32/49] drm/amd/display: Add KUnit tests for srm_data_write and srm_data_read
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (30 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 31/49] drm/amd/display: Add KUnit tests for hdcp_create_workqueue Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 33/49] drm/amd/display: Add KUnit tests for HDCP DDC link adapters Fangzhi Zuo
` (16 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Expose srm_data_write() and srm_data_read() for KUnit and add tests
covering the sysfs SRM write/read paths:
- write stages the buffer into srm_temp and returns count; with the TA
uninitialized psp_set_srm() fails so the committed SRM is unchanged
- read returns -EINVAL when psp_get_srm() returns NULL (TA uninitialized)
- read returns 0 for an empty SRM using the SR-IOV VF bypass so
psp_hdcp_invoke() is a no-op and psp_get_srm() returns a non-NULL buffer
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 6 +-
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 9 ++
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 117 ++++++++++++++++++
3 files changed, 130 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index 560bfa43dc8a..9ea0a120af6f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -743,7 +743,7 @@ EXPORT_IF_KUNIT(update_config);
* -if we try to "1. SET" a newer version and PSP rejects it. That means the format is
* incorrect/corrupted and we should correct our SRM by getting it from PSP
*/
-static ssize_t srm_data_write(struct file *filp, struct kobject *kobj,
+STATIC_IFN_KUNIT ssize_t srm_data_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buffer,
loff_t pos, size_t count)
{
@@ -766,8 +766,9 @@ static ssize_t srm_data_write(struct file *filp, struct kobject *kobj,
return count;
}
+EXPORT_IF_KUNIT(srm_data_write);
-static ssize_t srm_data_read(struct file *filp, struct kobject *kobj,
+STATIC_IFN_KUNIT ssize_t srm_data_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buffer,
loff_t pos, size_t count)
{
@@ -803,6 +804,7 @@ static ssize_t srm_data_read(struct file *filp, struct kobject *kobj,
link_lock(work, false);
return ret;
}
+EXPORT_IF_KUNIT(srm_data_read);
/* From the hdcp spec (5.Renewability) SRM needs to be stored in a non-volatile memory.
*
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
index 30c1e7b1af1a..e700d019f971 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
@@ -45,6 +45,9 @@ struct mod_hdcp_display;
struct cp_psp;
struct amdgpu_device;
struct psp_context;
+struct file;
+struct kobject;
+struct bin_attribute;
struct hdcp_workqueue {
struct work_struct cpirq_work;
@@ -125,6 +128,12 @@ uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *s
int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version);
bool enable_assr(void *handle, struct dc_link *link);
void update_config(void *handle, struct cp_psp_stream_config *config);
+ssize_t srm_data_write(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buffer,
+ loff_t pos, size_t count);
+ssize_t srm_data_read(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buffer,
+ loff_t pos, size_t count);
#endif
#endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index d95babb73711..a8bca05f47a3 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -1348,6 +1348,119 @@ static void dm_test_psp_set_srm_uninitialized_returns_einval(struct kunit *test)
/* End of tests for psp_get_srm() and psp_set_srm() */
+/* Tests for srm_data_write() and srm_data_read() */
+
+/**
+ * dm_test_srm_data_write_uninitialized_ta_keeps_srm - write with TA not initialized
+ * @test: KUnit test context
+ *
+ * srm_data_write() always copies the incoming buffer into work->srm_temp and
+ * returns the byte count. When the HDCP TA is not initialized, psp_set_srm()
+ * fails, so the committed SRM (work->srm / work->srm_size) must stay untouched.
+ */
+static void dm_test_srm_data_write_uninitialized_ta_keeps_srm(struct kunit *test)
+{
+ struct hdcp_workqueue *work;
+ struct psp_context *psp;
+ u8 buf[4] = {0xAA, 0xBB, 0xCC, 0xDD};
+ ssize_t ret;
+
+ work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+ psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, psp);
+
+ work->max_link = 1;
+ mutex_init(&work->mutex);
+ /* kzalloc leaves hdcp_context.context.initialized == false */
+ work->hdcp.config.psp.handle = psp;
+ work->srm_temp = kunit_kzalloc(test, PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work->srm_temp);
+ work->srm = kunit_kzalloc(test, PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work->srm);
+
+ ret = srm_data_write(NULL, NULL, &work->attr, buf, 0, sizeof(buf));
+
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buf));
+ /* Incoming data is always staged into srm_temp. */
+ KUNIT_EXPECT_MEMEQ(test, work->srm_temp, buf, sizeof(buf));
+ /* psp_set_srm() failed, so the committed SRM must be unchanged. */
+ KUNIT_EXPECT_EQ(test, work->srm_size, 0u);
+}
+
+/**
+ * dm_test_srm_data_read_uninitialized_ta_returns_einval - read with TA not initialized
+ * @test: KUnit test context
+ *
+ * When the HDCP TA is not initialized, psp_get_srm() returns NULL, so
+ * srm_data_read() must take the error path and return -EINVAL.
+ */
+static void dm_test_srm_data_read_uninitialized_ta_returns_einval(struct kunit *test)
+{
+ struct hdcp_workqueue *work;
+ struct psp_context *psp;
+ u8 buf[4];
+ ssize_t ret;
+
+ work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+ psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, psp);
+
+ work->max_link = 1;
+ mutex_init(&work->mutex);
+ /* kzalloc leaves hdcp_context.context.initialized == false */
+ work->hdcp.config.psp.handle = psp;
+
+ ret = srm_data_read(NULL, NULL, &work->attr, buf, 0, sizeof(buf));
+
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)-EINVAL);
+}
+
+/**
+ * dm_test_srm_data_read_empty_srm_returns_zero - read of an empty SRM
+ * @test: KUnit test context
+ *
+ * With an initialized TA and the SR-IOV VF bypass, psp_hdcp_invoke() is a
+ * no-op and the zeroed shared buffer yields a SUCCESS status with srm_size 0.
+ * psp_get_srm() then returns a non-NULL (empty) buffer, so srm_data_read()
+ * takes the "nothing left to copy" path and returns 0.
+ */
+static void dm_test_srm_data_read_empty_srm_returns_zero(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct ta_hdcp_shared_memory *hdcp_cmd;
+ struct hdcp_workqueue *work;
+ struct psp_context *psp;
+ u8 buf[4];
+ ssize_t ret;
+
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+
+ work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, work);
+ psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, psp);
+ hdcp_cmd = kunit_kzalloc(test, sizeof(*hdcp_cmd), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, hdcp_cmd);
+
+ work->max_link = 1;
+ mutex_init(&work->mutex);
+ psp->adev = adev;
+ psp->hdcp_context.context.initialized = true;
+ psp->hdcp_context.context.mem_context.shared_buf = (uint8_t *)hdcp_cmd;
+ work->hdcp.config.psp.handle = psp;
+
+ /* SR-IOV VF makes psp_hdcp_invoke() return early without firmware. */
+ adev->virt.caps |= AMDGPU_SRIOV_CAPS_IS_VF;
+
+ ret = srm_data_read(NULL, NULL, &work->attr, buf, 0, sizeof(buf));
+
+ KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
+/* End of tests for srm_data_write() and srm_data_read() */
+
/*
* Tests for hdcp_update_display() / hdcp_remove_display() /
* hdcp_reset_display().
@@ -1877,6 +1990,10 @@ static struct kunit_case dm_hdcp_test_cases[] = {
/* psp_get_srm() / psp_set_srm() */
KUNIT_CASE(dm_test_psp_get_srm_uninitialized_returns_null),
KUNIT_CASE(dm_test_psp_set_srm_uninitialized_returns_einval),
+ /* srm_data_write() / srm_data_read() */
+ KUNIT_CASE(dm_test_srm_data_write_uninitialized_ta_keeps_srm),
+ KUNIT_CASE(dm_test_srm_data_read_uninitialized_ta_returns_einval),
+ KUNIT_CASE(dm_test_srm_data_read_empty_srm_returns_zero),
/* hdcp_update_display() / hdcp_remove_display() / hdcp_reset_display() */
KUNIT_CASE(dm_test_hdcp_update_display_enable_registers_connector),
KUNIT_CASE(dm_test_hdcp_update_display_disable_sets_status_off),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 33/49] drm/amd/display: Add KUnit tests for HDCP DDC link adapters
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (31 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 32/49] drm/amd/display: Add KUnit tests for srm_data_write and srm_data_read Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 34/49] drm/amd/display: Add initialized-branch test for psp_set_srm Fangzhi Zuo
` (15 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Expose the mod_hdcp DDC adapter callbacks for KUnit and add tests:
lp_write_i2c / lp_read_i2c / lp_write_dpcd / lp_read_dpcd use recording
fake i2c and DP aux backends to assert the built payloads (write flag,
address, length, buffer, and the read offset-then-data sequence), plus
failure when the connector is missing.
lp_atomic_write_poll_read_i2c / lp_atomic_write_poll_read_aux cover the
hardware-free early returns (NULL link and a payload too large to convert);
the success path submits fused-IO to the DMCUB and is out of reach for a
unit test.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 50 +--
.../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 16 +
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 378 ++++++++++++++++++
3 files changed, 420 insertions(+), 24 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index 9ea0a120af6f..c887a70ae74d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
@@ -38,8 +38,7 @@
*/
#define PSP_SRM_VERSION_MAX 0xFFFF
-static bool
-lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size)
+STATIC_IFN_KUNIT bool lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size)
{
struct dc_link *link = handle;
struct i2c_payload i2c_payloads[] = {{true, address, size, (void *)data} };
@@ -48,9 +47,9 @@ lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size)
return dm_helpers_submit_i2c(link->ctx, link, &cmd);
}
+EXPORT_IF_KUNIT(lp_write_i2c);
-static bool
-lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size)
+STATIC_IFN_KUNIT bool lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size)
{
struct dc_link *link = handle;
@@ -61,50 +60,53 @@ lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint3
return dm_helpers_submit_i2c(link->ctx, link, &cmd);
}
+EXPORT_IF_KUNIT(lp_read_i2c);
-static bool
-lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size)
+STATIC_IFN_KUNIT bool lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size)
{
struct dc_link *link = handle;
return dm_helpers_dp_write_dpcd(link->ctx, link, address, data, size);
}
+EXPORT_IF_KUNIT(lp_write_dpcd);
-static bool
-lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size)
+STATIC_IFN_KUNIT bool lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size)
{
struct dc_link *link = handle;
return dm_helpers_dp_read_dpcd(link->ctx, link, address, data, size);
}
-
-static bool lp_atomic_write_poll_read_i2c(
- void *handle,
- const struct mod_hdcp_atomic_op_i2c *write,
- const struct mod_hdcp_atomic_op_i2c *poll,
- struct mod_hdcp_atomic_op_i2c *read,
- uint32_t poll_timeout_us,
- uint8_t poll_mask_msb
+EXPORT_IF_KUNIT(lp_read_dpcd);
+
+STATIC_IFN_KUNIT bool lp_atomic_write_poll_read_i2c(
+ void *handle,
+ const struct mod_hdcp_atomic_op_i2c *write,
+ const struct mod_hdcp_atomic_op_i2c *poll,
+ struct mod_hdcp_atomic_op_i2c *read,
+ uint32_t poll_timeout_us,
+ uint8_t poll_mask_msb
)
{
struct dc_link *link = handle;
return dm_atomic_write_poll_read_i2c(link, write, poll, read, poll_timeout_us, poll_mask_msb);
}
-
-static bool lp_atomic_write_poll_read_aux(
- void *handle,
- const struct mod_hdcp_atomic_op_aux *write,
- const struct mod_hdcp_atomic_op_aux *poll,
- struct mod_hdcp_atomic_op_aux *read,
- uint32_t poll_timeout_us,
- uint8_t poll_mask_msb
+EXPORT_IF_KUNIT(lp_atomic_write_poll_read_i2c);
+
+STATIC_IFN_KUNIT bool lp_atomic_write_poll_read_aux(
+ void *handle,
+ const struct mod_hdcp_atomic_op_aux *write,
+ const struct mod_hdcp_atomic_op_aux *poll,
+ struct mod_hdcp_atomic_op_aux *read,
+ uint32_t poll_timeout_us,
+ uint8_t poll_mask_msb
)
{
struct dc_link *link = handle;
return dm_atomic_write_poll_read_aux(link, write, poll, read, poll_timeout_us, poll_mask_msb);
}
+EXPORT_IF_KUNIT(lp_atomic_write_poll_read_aux);
STATIC_IFN_KUNIT
uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
index e700d019f971..665fa4839a25 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h
@@ -48,6 +48,8 @@ struct psp_context;
struct file;
struct kobject;
struct bin_attribute;
+struct mod_hdcp_atomic_op_i2c;
+struct mod_hdcp_atomic_op_aux;
struct hdcp_workqueue {
struct work_struct cpirq_work;
@@ -134,6 +136,20 @@ ssize_t srm_data_write(struct file *filp, struct kobject *kobj,
ssize_t srm_data_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buffer,
loff_t pos, size_t count);
+bool lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size);
+bool lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size);
+bool lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size);
+bool lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size);
+bool lp_atomic_write_poll_read_i2c(void *handle,
+ const struct mod_hdcp_atomic_op_i2c *write,
+ const struct mod_hdcp_atomic_op_i2c *poll,
+ struct mod_hdcp_atomic_op_i2c *read,
+ uint32_t poll_timeout_us, uint8_t poll_mask_msb);
+bool lp_atomic_write_poll_read_aux(void *handle,
+ const struct mod_hdcp_atomic_op_aux *write,
+ const struct mod_hdcp_atomic_op_aux *poll,
+ struct mod_hdcp_atomic_op_aux *read,
+ uint32_t poll_timeout_us, uint8_t poll_mask_msb);
#endif
#endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index a8bca05f47a3..838826d274c2 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -10,6 +10,9 @@
#include <linux/kobject.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
+#include <linux/i2c.h>
+
+#include <drm/display/drm_dp_helper.h>
#include "amdgpu.h"
#include "amdgpu_dm.h"
@@ -1461,6 +1464,369 @@ static void dm_test_srm_data_read_empty_srm_returns_zero(struct kunit *test)
/* End of tests for srm_data_write() and srm_data_read() */
+/* Tests for lp_write_i2c() / lp_read_i2c() / lp_write_dpcd() / lp_read_dpcd() */
+
+/* Defined further below with the display helper tests. */
+static struct amdgpu_dm_connector *alloc_test_connector(struct kunit *test,
+ unsigned int index);
+
+/*
+ * Recording fakes for the DDC layer. The lp_* wrappers build i2c/DPCD
+ * transactions and forward them through dm_helpers_*, which end up calling
+ * i2c_transfer() / drm_dp_dpcd_*(). These fakes capture the resulting
+ * messages so the tests can assert what the wrappers built, without touching
+ * real hardware. KUnit runs cases sequentially, so file-scope capture state
+ * is reset at the start of each test.
+ */
+#define FAKE_DDC_MAX_MSGS 4
+
+static struct fake_i2c_capture {
+ int num;
+ struct i2c_msg msgs[FAKE_DDC_MAX_MSGS];
+} fake_i2c_cap;
+
+static int fake_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+ int num)
+{
+ int i;
+
+ fake_i2c_cap.num = num;
+ for (i = 0; i < num && i < FAKE_DDC_MAX_MSGS; i++)
+ fake_i2c_cap.msgs[i] = msgs[i];
+
+ return num;
+}
+
+static u32 fake_i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm fake_i2c_algo = {
+ .master_xfer = fake_i2c_master_xfer,
+ .functionality = fake_i2c_functionality,
+};
+
+static void fake_i2c_lock_bus(struct i2c_adapter *adap, unsigned int flags) {}
+static int fake_i2c_trylock_bus(struct i2c_adapter *adap, unsigned int flags)
+{
+ return 1;
+}
+static void fake_i2c_unlock_bus(struct i2c_adapter *adap, unsigned int flags) {}
+
+static const struct i2c_lock_operations fake_i2c_lock_ops = {
+ .lock_bus = fake_i2c_lock_bus,
+ .trylock_bus = fake_i2c_trylock_bus,
+ .unlock_bus = fake_i2c_unlock_bus,
+};
+
+static struct fake_aux_capture {
+ int calls;
+ u8 request;
+ unsigned int address;
+ size_t size;
+} fake_aux_cap;
+
+static ssize_t fake_aux_transfer(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
+{
+ fake_aux_cap.calls++;
+ fake_aux_cap.request = msg->request;
+ fake_aux_cap.address = msg->address;
+ fake_aux_cap.size = msg->size;
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+
+ return msg->size;
+}
+
+/**
+ * alloc_test_ddc_link - connector/link wired to the recording i2c + aux fakes
+ * @test: KUnit test context for managed allocation
+ *
+ * Builds an amdgpu_dm_connector with a fake i2c adapter and a fake DP aux, and
+ * points link->priv at the connector so dm_helpers_* find it. Returns the
+ * dc_link that the lp_* wrappers take as their opaque handle.
+ */
+static struct dc_link *alloc_test_ddc_link(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0);
+ struct amdgpu_i2c_adapter *i2c;
+ struct dc_link *link;
+
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ i2c = kunit_kzalloc(test, sizeof(*i2c), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, i2c);
+ i2c->base.algo = &fake_i2c_algo;
+ i2c->base.lock_ops = &fake_i2c_lock_ops;
+ aconnector->i2c = i2c;
+
+ mutex_init(&aconnector->dm_dp_aux.aux.hw_mutex);
+ aconnector->dm_dp_aux.aux.transfer = fake_aux_transfer;
+ /* Skip the DPCD "throw away" probe read so we capture only our access. */
+ aconnector->dm_dp_aux.aux.dpcd_probe_disabled = true;
+
+ link = aconnector->dc_link;
+ link->priv = aconnector;
+
+ return link;
+}
+
+/**
+ * dm_test_lp_write_i2c_builds_single_write_payload - write builds one i2c msg
+ * @test: KUnit test context
+ *
+ * lp_write_i2c() must forward a single write payload carrying the address,
+ * length and data buffer unchanged.
+ */
+static void dm_test_lp_write_i2c_builds_single_write_payload(struct kunit *test)
+{
+ struct dc_link *link = alloc_test_ddc_link(test);
+ u8 data[3] = {0x11, 0x22, 0x33};
+ bool ok;
+
+ memset(&fake_i2c_cap, 0, sizeof(fake_i2c_cap));
+
+ ok = lp_write_i2c(link, 0x3a, data, sizeof(data));
+
+ KUNIT_EXPECT_TRUE(test, ok);
+ KUNIT_ASSERT_EQ(test, fake_i2c_cap.num, 1);
+ /* write => flags without I2C_M_RD */
+ KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].flags, 0);
+ KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].addr, 0x3a);
+ KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].len, (int)sizeof(data));
+ KUNIT_EXPECT_PTR_EQ(test, fake_i2c_cap.msgs[0].buf, (void *)data);
+}
+
+/**
+ * dm_test_lp_read_i2c_builds_offset_then_read - read builds offset + read msgs
+ * @test: KUnit test context
+ *
+ * lp_read_i2c() must build a 1-byte write of the offset followed by a
+ * size-byte read into the caller buffer, both at the same address.
+ */
+static void dm_test_lp_read_i2c_builds_offset_then_read(struct kunit *test)
+{
+ struct dc_link *link = alloc_test_ddc_link(test);
+ u8 data[4];
+ bool ok;
+
+ memset(&fake_i2c_cap, 0, sizeof(fake_i2c_cap));
+
+ ok = lp_read_i2c(link, 0x50, 0x07, data, sizeof(data));
+
+ KUNIT_EXPECT_TRUE(test, ok);
+ KUNIT_ASSERT_EQ(test, fake_i2c_cap.num, 2);
+ /* first: 1-byte write of the offset */
+ KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].flags, 0);
+ KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].addr, 0x50);
+ KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].len, 1);
+ /* second: size-byte read into the caller buffer */
+ KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[1].flags, I2C_M_RD);
+ KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[1].addr, 0x50);
+ KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[1].len, (int)sizeof(data));
+ KUNIT_EXPECT_PTR_EQ(test, fake_i2c_cap.msgs[1].buf, (void *)data);
+}
+
+/**
+ * dm_test_lp_write_dpcd_forwards_native_write - write forwards a native write
+ * @test: KUnit test context
+ *
+ * lp_write_dpcd() must issue a single DP_AUX_NATIVE_WRITE at the requested
+ * address for the requested size.
+ */
+static void dm_test_lp_write_dpcd_forwards_native_write(struct kunit *test)
+{
+ struct dc_link *link = alloc_test_ddc_link(test);
+ u8 data[2] = {0xDE, 0xAD};
+ bool ok;
+
+ memset(&fake_aux_cap, 0, sizeof(fake_aux_cap));
+
+ ok = lp_write_dpcd(link, 0x68000, data, sizeof(data));
+
+ KUNIT_EXPECT_TRUE(test, ok);
+ KUNIT_EXPECT_EQ(test, fake_aux_cap.calls, 1);
+ KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.request, DP_AUX_NATIVE_WRITE);
+ KUNIT_EXPECT_EQ(test, fake_aux_cap.address, 0x68000u);
+ KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.size, (int)sizeof(data));
+}
+
+/**
+ * dm_test_lp_read_dpcd_forwards_native_read - read forwards a native read
+ * @test: KUnit test context
+ *
+ * lp_read_dpcd() must issue a single DP_AUX_NATIVE_READ at the requested
+ * address for the requested size.
+ */
+static void dm_test_lp_read_dpcd_forwards_native_read(struct kunit *test)
+{
+ struct dc_link *link = alloc_test_ddc_link(test);
+ u8 data[4];
+ bool ok;
+
+ memset(&fake_aux_cap, 0, sizeof(fake_aux_cap));
+
+ ok = lp_read_dpcd(link, 0x00220, data, sizeof(data));
+
+ KUNIT_EXPECT_TRUE(test, ok);
+ KUNIT_EXPECT_EQ(test, fake_aux_cap.calls, 1);
+ KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.request, DP_AUX_NATIVE_READ);
+ KUNIT_EXPECT_EQ(test, fake_aux_cap.address, 0x00220u);
+ KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.size, (int)sizeof(data));
+}
+
+/**
+ * dm_test_lp_write_i2c_no_connector_returns_false - missing connector fails
+ * @test: KUnit test context
+ *
+ * When link->priv has no connector, dm_helpers_submit_i2c() cannot proceed,
+ * so lp_write_i2c() must report failure without invoking the adapter.
+ */
+static void dm_test_lp_write_i2c_no_connector_returns_false(struct kunit *test)
+{
+ struct dc_link *link = alloc_test_ddc_link(test);
+ u8 data[2] = {0x01, 0x02};
+ bool ok;
+
+ link->priv = NULL;
+ memset(&fake_i2c_cap, 0, sizeof(fake_i2c_cap));
+
+ ok = lp_write_i2c(link, 0x3a, data, sizeof(data));
+
+ KUNIT_EXPECT_FALSE(test, ok);
+ KUNIT_EXPECT_EQ(test, fake_i2c_cap.num, 0);
+}
+
+/**
+ * dm_test_lp_read_dpcd_no_connector_returns_false - missing connector fails
+ * @test: KUnit test context
+ *
+ * When link->priv has no connector, dm_helpers_dp_read_dpcd() cannot proceed,
+ * so lp_read_dpcd() must report failure without invoking the aux transfer.
+ */
+static void dm_test_lp_read_dpcd_no_connector_returns_false(struct kunit *test)
+{
+ struct dc_link *link = alloc_test_ddc_link(test);
+ u8 data[4];
+ bool ok;
+
+ link->priv = NULL;
+ memset(&fake_aux_cap, 0, sizeof(fake_aux_cap));
+
+ ok = lp_read_dpcd(link, 0x00220, data, sizeof(data));
+
+ KUNIT_EXPECT_FALSE(test, ok);
+ KUNIT_EXPECT_EQ(test, fake_aux_cap.calls, 0);
+}
+
+/* End of tests for lp_write_i2c() / lp_read_i2c() / lp_write_dpcd() / lp_read_dpcd() */
+
+/*
+ * Tests for lp_atomic_write_poll_read_i2c() / lp_atomic_write_poll_read_aux()
+ *
+ * These wrappers cast the opaque handle to a dc_link and forward to the
+ * dc_fused_io helpers. The success path submits a fused-IO command sequence to
+ * the DMCUB, which is out of reach for a unit test, so the coverage here is the
+ * hardware-free early returns: a NULL link and a payload that fails conversion
+ * (op size larger than the fused request buffer).
+ */
+
+/**
+ * dm_test_lp_atomic_i2c_null_handle_returns_false - NULL link fails cleanly
+ * @test: KUnit test context
+ *
+ * With a NULL handle the forwarded dc_link is NULL, so the helper must return
+ * false without dereferencing anything.
+ */
+static void dm_test_lp_atomic_i2c_null_handle_returns_false(struct kunit *test)
+{
+ struct mod_hdcp_atomic_op_i2c op = { 0 };
+
+ KUNIT_EXPECT_FALSE(test,
+ lp_atomic_write_poll_read_i2c(NULL, &op, &op, &op, 0, 0));
+}
+
+/**
+ * dm_test_lp_atomic_i2c_oversized_op_returns_false - bad payload fails conversion
+ * @test: KUnit test context
+ *
+ * An op whose size exceeds the fused request buffer must fail conversion, so
+ * the helper returns false before any fused-IO submission. no_ddc_pin routes
+ * the DDC line through aux_hw_inst, avoiding the GPIO pin dereference.
+ */
+static void dm_test_lp_atomic_i2c_oversized_op_returns_false(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0);
+ struct mod_hdcp_atomic_op_i2c write = { .size = 0x100 };
+ struct mod_hdcp_atomic_op_i2c op = { 0 };
+ struct dc_link *link;
+
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ link = aconnector->dc_link;
+ link->no_ddc_pin = true;
+
+ KUNIT_EXPECT_FALSE(test,
+ lp_atomic_write_poll_read_i2c(link, &write, &op, &op, 0, 0));
+}
+
+/**
+ * dm_test_lp_atomic_aux_null_handle_returns_false - NULL link fails cleanly
+ * @test: KUnit test context
+ *
+ * With a NULL handle the forwarded dc_link is NULL, so the helper must return
+ * false without dereferencing anything.
+ */
+static void dm_test_lp_atomic_aux_null_handle_returns_false(struct kunit *test)
+{
+ struct mod_hdcp_atomic_op_aux op = { 0 };
+
+ KUNIT_EXPECT_FALSE(test,
+ lp_atomic_write_poll_read_aux(NULL, &op, &op, &op, 0, 0));
+}
+
+/**
+ * dm_test_lp_atomic_aux_oversized_op_returns_false - bad payload fails conversion
+ * @test: KUnit test context
+ *
+ * The aux helper reads the DDC line from link->ddc->ddc_pin->pin_data before
+ * converting, so a minimal pin chain is wired up. An op larger than the fused
+ * request buffer then fails conversion and the helper returns false without
+ * any fused-IO submission.
+ */
+static void dm_test_lp_atomic_aux_oversized_op_returns_false(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0);
+ struct mod_hdcp_atomic_op_aux write = { .size = 0x100 };
+ struct mod_hdcp_atomic_op_aux op = { 0 };
+ struct ddc_service *ddc;
+ struct ddc *ddc_pin;
+ struct dc_link *link;
+ void *pin_data;
+
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ddc);
+ ddc_pin = kunit_kzalloc(test, sizeof(*ddc_pin), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ddc_pin);
+ /* Only ->en is read; over-allocate so struct gpio stays opaque here. */
+ pin_data = kunit_kzalloc(test, 128, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, pin_data);
+
+ ddc_pin->pin_data = pin_data;
+ ddc->ddc_pin = ddc_pin;
+
+ link = aconnector->dc_link;
+ link->ddc = ddc;
+
+ KUNIT_EXPECT_FALSE(test,
+ lp_atomic_write_poll_read_aux(link, &write, &op, &op, 0, 0));
+}
+
+/* End of tests for lp_atomic_write_poll_read_i2c() / lp_atomic_write_poll_read_aux() */
+
/*
* Tests for hdcp_update_display() / hdcp_remove_display() /
* hdcp_reset_display().
@@ -1994,6 +2360,18 @@ static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_srm_data_write_uninitialized_ta_keeps_srm),
KUNIT_CASE(dm_test_srm_data_read_uninitialized_ta_returns_einval),
KUNIT_CASE(dm_test_srm_data_read_empty_srm_returns_zero),
+ /* lp_write_i2c() / lp_read_i2c() / lp_write_dpcd() / lp_read_dpcd() */
+ KUNIT_CASE(dm_test_lp_write_i2c_builds_single_write_payload),
+ KUNIT_CASE(dm_test_lp_read_i2c_builds_offset_then_read),
+ KUNIT_CASE(dm_test_lp_write_dpcd_forwards_native_write),
+ KUNIT_CASE(dm_test_lp_read_dpcd_forwards_native_read),
+ KUNIT_CASE(dm_test_lp_write_i2c_no_connector_returns_false),
+ KUNIT_CASE(dm_test_lp_read_dpcd_no_connector_returns_false),
+ /* lp_atomic_write_poll_read_i2c() / lp_atomic_write_poll_read_aux() */
+ KUNIT_CASE(dm_test_lp_atomic_i2c_null_handle_returns_false),
+ KUNIT_CASE(dm_test_lp_atomic_i2c_oversized_op_returns_false),
+ KUNIT_CASE(dm_test_lp_atomic_aux_null_handle_returns_false),
+ KUNIT_CASE(dm_test_lp_atomic_aux_oversized_op_returns_false),
/* hdcp_update_display() / hdcp_remove_display() / hdcp_reset_display() */
KUNIT_CASE(dm_test_hdcp_update_display_enable_registers_connector),
KUNIT_CASE(dm_test_hdcp_update_display_disable_sets_status_off),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 34/49] drm/amd/display: Add initialized-branch test for psp_set_srm
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (32 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 33/49] drm/amd/display: Add KUnit tests for HDCP DDC link adapters Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 35/49] drm/amd/display: Add deeper event_property_update tests Fangzhi Zuo
` (14 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Cover the initialized path of psp_set_srm() using the SR-IOV VF bypass so
psp_hdcp_invoke() is a no-op. The test asserts the SET_SRM command is
staged (cmd_id, srm_buf_size and the copied SRM bytes) and that response
validation fails on the zeroed reply, returning -EINVAL without updating
srm_version.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 47 +++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index 838826d274c2..b359ba5d524b 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -1349,6 +1349,52 @@ static void dm_test_psp_set_srm_uninitialized_returns_einval(struct kunit *test)
KUNIT_EXPECT_EQ(test, srm_version, 0xdead);
}
+/**
+ * dm_test_psp_set_srm_initialized_stages_command - initialized path builds the command
+ * @test: KUnit test context
+ *
+ * With an initialized TA and the SR-IOV VF bypass, psp_hdcp_invoke() is a
+ * no-op, so the shared command buffer keeps the values psp_set_srm() staged.
+ * The function must copy the SRM into the SET_SRM in-message, record its size
+ * and command id, and then fail the response validation (the zeroed reply has
+ * valid_signature == 0), returning -EINVAL without updating srm_version.
+ */
+static void dm_test_psp_set_srm_initialized_stages_command(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct ta_hdcp_shared_memory *hdcp_cmd;
+ struct psp_context *psp;
+ uint32_t srm_version = 0xdead;
+ u8 srm_buf[4] = {0x1, 0x2, 0x3, 0x4};
+ int ret;
+
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+
+ psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, psp);
+ hdcp_cmd = kunit_kzalloc(test, sizeof(*hdcp_cmd), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, hdcp_cmd);
+
+ psp->adev = adev;
+ psp->hdcp_context.context.initialized = true;
+ psp->hdcp_context.context.mem_context.shared_buf = (uint8_t *)hdcp_cmd;
+
+ /* SR-IOV VF makes psp_hdcp_invoke() return early without firmware. */
+ adev->virt.caps |= AMDGPU_SRIOV_CAPS_IS_VF;
+
+ ret = psp_set_srm(psp, srm_buf, sizeof(srm_buf), &srm_version);
+
+ /* Response validation fails (valid_signature == 0 in the zeroed reply). */
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+ KUNIT_EXPECT_EQ(test, srm_version, 0xdead);
+ /* The initialized path must have staged the SET_SRM command. */
+ KUNIT_EXPECT_EQ(test, hdcp_cmd->cmd_id, TA_HDCP_COMMAND__HDCP_SET_SRM);
+ KUNIT_EXPECT_EQ(test, hdcp_cmd->in_msg.hdcp_set_srm.srm_buf_size,
+ (uint32_t)sizeof(srm_buf));
+ KUNIT_EXPECT_MEMEQ(test, hdcp_cmd->in_msg.hdcp_set_srm.srm_buf, srm_buf,
+ sizeof(srm_buf));
+}
+
/* End of tests for psp_get_srm() and psp_set_srm() */
/* Tests for srm_data_write() and srm_data_read() */
@@ -2356,6 +2402,7 @@ static struct kunit_case dm_hdcp_test_cases[] = {
/* psp_get_srm() / psp_set_srm() */
KUNIT_CASE(dm_test_psp_get_srm_uninitialized_returns_null),
KUNIT_CASE(dm_test_psp_set_srm_uninitialized_returns_einval),
+ KUNIT_CASE(dm_test_psp_set_srm_initialized_stages_command),
/* srm_data_write() / srm_data_read() */
KUNIT_CASE(dm_test_srm_data_write_uninitialized_ta_keeps_srm),
KUNIT_CASE(dm_test_srm_data_read_uninitialized_ta_returns_einval),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 35/49] drm/amd/display: Add deeper event_property_update tests
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (33 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 34/49] drm/amd/display: Add initialized-branch test for psp_set_srm Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 36/49] drm/amd/display: adjust floating point format for gamut remap when needed Fangzhi Zuo
` (13 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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>
Expand KUnit coverage for event_property_update() beyond the null
connector skip. Add cases for the disconnected, missing-state and
missing-device skip branches, plus the fully connected path where
hdcp_get_content_protection_from_status() maps HDCP_OFF to DESIRED and
HDCP1 TYPE0 encryption to ENABLED.
The connected-path tests use dm_kunit_alloc_adev() so the drm_device has
an initialised mode_config connection_mutex, and pre-set the connector
state to the expected content protection value so
drm_hdcp_update_content_protection() takes its no-change early return.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 174 +++++++++++++++++-
1 file changed, 173 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
index b359ba5d524b..cfdcc44581b1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c
@@ -473,7 +473,8 @@ static void dm_test_process_output_watchdog_stop_and_needed_requeues(struct kuni
* @test: KUnit test context for managed allocation
*
* Allocates a minimal hdcp_workqueue with property_update_work initialised
- * so event_property_update() can resolve container_of() safely.
+ * so event_property_update() can resolve container_of() safely. The mutex is
+ * initialised as well because the connected path takes guard(mutex).
*/
static struct hdcp_workqueue *alloc_test_workqueue_for_property_update(struct kunit *test)
{
@@ -482,11 +483,41 @@ static struct hdcp_workqueue *alloc_test_workqueue_for_property_update(struct ku
work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, work);
+ mutex_init(&work->mutex);
INIT_WORK(&work->property_update_work, dummy_work_fn);
return work;
}
+/**
+ * alloc_update_connector - connector for event_property_update() tests
+ * @test: KUnit test context for managed allocation
+ * @status: drm connector detection status to assign
+ * @conn_state: drm_connector_state to attach (may be NULL)
+ * @dev: drm_device to attach as connector->dev (may be NULL)
+ *
+ * Allocates an amdgpu_dm_connector wired for the traversal in
+ * event_property_update(). The caller supplies the state and device so the
+ * various skip branches (disconnected, no state, no device) and the fully
+ * connected path can all be exercised.
+ */
+static struct amdgpu_dm_connector *alloc_update_connector(struct kunit *test,
+ enum drm_connector_status status,
+ struct drm_connector_state *conn_state,
+ struct drm_device *dev)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->base.status = status;
+ aconnector->base.state = conn_state;
+ aconnector->base.dev = dev;
+
+ return aconnector;
+}
+
/**
* dm_test_event_property_update_skips_null_connector - null connector is ignored
* @test: KUnit test context
@@ -507,6 +538,142 @@ static void dm_test_event_property_update_skips_null_connector(struct kunit *tes
KUNIT_EXPECT_EQ(test, work->encryption_status[0], before);
}
+/**
+ * dm_test_event_property_update_skips_disconnected - disconnected is skipped
+ * @test: KUnit test context
+ *
+ * A connector whose status is not connector_status_connected must be skipped
+ * before any modeset lock is taken, leaving encryption_status untouched.
+ */
+static void dm_test_event_property_update_skips_disconnected(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test);
+ struct drm_connector_state *conn_state;
+
+ conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, conn_state);
+
+ work->aconnector[1] = alloc_update_connector(test, connector_status_disconnected,
+ conn_state, NULL);
+ work->encryption_status[1] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON;
+
+ event_property_update(&work->property_update_work);
+
+ KUNIT_EXPECT_EQ(test, work->encryption_status[1],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON);
+}
+
+/**
+ * dm_test_event_property_update_skips_null_state - missing state is skipped
+ * @test: KUnit test context
+ *
+ * A connected connector without a drm_connector_state must be skipped before
+ * the modeset lock, leaving encryption_status unchanged.
+ */
+static void dm_test_event_property_update_skips_null_state(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test);
+
+ work->aconnector[2] = alloc_update_connector(test, connector_status_connected,
+ NULL, NULL);
+ work->encryption_status[2] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON;
+
+ event_property_update(&work->property_update_work);
+
+ KUNIT_EXPECT_EQ(test, work->encryption_status[2],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON);
+}
+
+/**
+ * dm_test_event_property_update_skips_null_dev - missing device is skipped
+ * @test: KUnit test context
+ *
+ * A connected connector with state but no drm_device must be skipped before
+ * the modeset lock, leaving encryption_status unchanged.
+ */
+static void dm_test_event_property_update_skips_null_dev(struct kunit *test)
+{
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test);
+ struct drm_connector_state *conn_state;
+
+ conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, conn_state);
+
+ work->aconnector[3] = alloc_update_connector(test, connector_status_connected,
+ conn_state, NULL);
+ work->encryption_status[3] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON;
+
+ event_property_update(&work->property_update_work);
+
+ KUNIT_EXPECT_EQ(test, work->encryption_status[3],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON);
+}
+
+/**
+ * dm_test_event_property_update_desired_when_off - HDCP off maps to DESIRED
+ * @test: KUnit test context
+ *
+ * A fully connected display with HDCP_OFF encryption drives the connected
+ * path: the modeset lock is taken, hdcp_get_content_protection_from_status()
+ * reports DRM_MODE_CONTENT_PROTECTION_DESIRED and
+ * drm_hdcp_update_content_protection() is called. The connector state is
+ * pre-set to DESIRED so the value is unchanged (no sysfs event) and the deep
+ * path completes cleanly.
+ */
+static void dm_test_event_property_update_desired_when_off(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test);
+ struct drm_connector_state *conn_state;
+
+ conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, conn_state);
+ conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+
+ work->aconnector[0] = alloc_update_connector(test, connector_status_connected,
+ conn_state, &adev->ddev);
+ work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF;
+
+ event_property_update(&work->property_update_work);
+
+ KUNIT_EXPECT_EQ(test, conn_state->content_protection,
+ (unsigned int)DRM_MODE_CONTENT_PROTECTION_DESIRED);
+ KUNIT_EXPECT_EQ(test, work->encryption_status[0],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF);
+}
+
+/**
+ * dm_test_event_property_update_enabled_when_encrypted - encrypted maps to ENABLED
+ * @test: KUnit test context
+ *
+ * A fully connected display with TYPE0 content and HDCP1 encryption drives
+ * the connected path where hdcp_get_content_protection_from_status() reports
+ * DRM_MODE_CONTENT_PROTECTION_ENABLED. The connector state is pre-set to
+ * ENABLED so drm_hdcp_update_content_protection() leaves it unchanged.
+ */
+static void dm_test_event_property_update_enabled_when_encrypted(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test);
+ struct drm_connector_state *conn_state;
+
+ conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, conn_state);
+ conn_state->hdcp_content_type = DRM_MODE_HDCP_CONTENT_TYPE0;
+ conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED;
+
+ work->aconnector[0] = alloc_update_connector(test, connector_status_connected,
+ conn_state, &adev->ddev);
+ work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP1_ON;
+
+ event_property_update(&work->property_update_work);
+
+ KUNIT_EXPECT_EQ(test, conn_state->content_protection,
+ (unsigned int)DRM_MODE_CONTENT_PROTECTION_ENABLED);
+ KUNIT_EXPECT_EQ(test, work->encryption_status[0],
+ MOD_HDCP_ENCRYPTION_STATUS_HDCP1_ON);
+}
+
/* End of tests for event_property_update() */
/* Tests for event_callback() */
@@ -2367,6 +2534,11 @@ static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_process_output_watchdog_stop_and_needed_requeues),
/* event_property_update() */
KUNIT_CASE(dm_test_event_property_update_skips_null_connector),
+ KUNIT_CASE(dm_test_event_property_update_skips_disconnected),
+ KUNIT_CASE(dm_test_event_property_update_skips_null_state),
+ KUNIT_CASE(dm_test_event_property_update_skips_null_dev),
+ KUNIT_CASE(dm_test_event_property_update_desired_when_off),
+ KUNIT_CASE(dm_test_event_property_update_enabled_when_encrypted),
/* event_callback() */
KUNIT_CASE(dm_test_event_callback_cancels_callback_dwork),
KUNIT_CASE(dm_test_event_callback_schedules_property_validate),
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 36/49] drm/amd/display: adjust floating point format for gamut remap when needed
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (34 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 35/49] drm/amd/display: Add deeper event_property_update tests Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 37/49] drm/amd/display: Reintroduce "convert dcn42 GPIO translation to lookup tables" Fangzhi Zuo
` (12 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Clay King, Dillon Varone
From: Clay King <clayking@amd.com>
[Why]
The MPCCs different gamut remap matrices allow for S2D13 and S3D12 floating
point formats, but only S2D13 is used. There may be cases where more
integer bits are required.
[How]
Switch to S3D12 if any entries in the remap matrix cannot fit in S2D13.
Otherwise, prefer the extra precision of S2D13. Communicate the max value
that hw can support to dm via dc color caps.
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Clay King <clayking@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../drm/amd/display/dc/basics/conversion.c | 61 +++++++++++-----
.../drm/amd/display/dc/basics/conversion.h | 12 +++-
drivers/gpu/drm/amd/display/dc/dc.h | 1 +
.../drm/amd/display/dc/dce/dce_transform.c | 4 +-
.../amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c | 8 +--
.../amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c | 8 +--
.../amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c | 8 +--
.../amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c | 3 +-
.../drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c | 44 ++++++++----
.../amd/display/dc/mpc/dcn401/dcn401_mpc.c | 69 +++++++++++++------
.../amd/display/dc/mpc/dcn401/dcn401_mpc.h | 6 +-
.../dc/resource/dcn30/dcn30_resource.c | 2 +
.../dc/resource/dcn301/dcn301_resource.c | 2 +
.../dc/resource/dcn302/dcn302_resource.c | 2 +
.../dc/resource/dcn303/dcn303_resource.c | 2 +
.../dc/resource/dcn31/dcn31_resource.c | 2 +
.../dc/resource/dcn314/dcn314_resource.c | 2 +
.../dc/resource/dcn315/dcn315_resource.c | 2 +
.../dc/resource/dcn316/dcn316_resource.c | 2 +
.../dc/resource/dcn32/dcn32_resource.c | 2 +
.../dc/resource/dcn321/dcn321_resource.c | 2 +
.../dc/resource/dcn35/dcn35_resource.c | 2 +
.../dc/resource/dcn351/dcn351_resource.c | 2 +
.../dc/resource/dcn36/dcn36_resource.c | 2 +
.../dc/resource/dcn401/dcn401_resource.c | 2 +
.../dc/resource/dcn42/dcn42_resource.c | 2 +
.../dc/resource/dcn42b/dcn42b_resource.c | 2 +
27 files changed, 186 insertions(+), 70 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dc/basics/conversion.c b/drivers/gpu/drm/amd/display/dc/basics/conversion.c
index a1f2350388e2..1c8d4907245a 100644
--- a/drivers/gpu/drm/amd/display/dc/basics/conversion.c
+++ b/drivers/gpu/drm/amd/display/dc/basics/conversion.c
@@ -26,11 +26,7 @@
#include "dm_services.h"
#include "basics/conversion.h"
-#define DIVIDER 10000
-/* S2D13 value in [-3.00...0.9999] */
-#define S2D13_MIN (-3 * DIVIDER)
-#define S2D13_MAX (3 * DIVIDER)
uint16_t fixed_point_to_int_frac(
struct fixed31_32 arg,
@@ -74,28 +70,44 @@ uint16_t fixed_point_to_int_frac(
return result;
}
/*
- * convert_float_matrix - This converts a double into HW register spec defined format S2D13.
+ * convert_float_matrix - This converts a double into HW register spec defined format S2D13 / S3D12.
*/
void convert_float_matrix(
uint16_t *matrix,
- struct fixed31_32 *flt,
+ const struct fixed31_32 *flt,
+ enum cm_gamut_coef_format format,
uint32_t buffer_size)
{
- const struct fixed31_32 min_2_13 =
- dc_fixpt_from_fraction(S2D13_MIN, DIVIDER);
- const struct fixed31_32 max_2_13 =
- dc_fixpt_from_fraction(S2D13_MAX, DIVIDER);
+ struct fixed31_32 min;
+ struct fixed31_32 max;
+ uint8_t num_int_bits;
+ uint8_t num_dec_bits;
uint32_t i;
+ if (format == CM_GAMUT_REMAP_COEF_FORMAT_S2_13) {
+ min = dc_fixpt_from_fraction(S2D13_MIN, DIVIDER);
+ max = dc_fixpt_from_fraction(S2D13_MAX, DIVIDER);
+ num_int_bits = 2;
+ num_dec_bits = 13;
+ } else if (format == CM_GAMUT_REMAP_COEF_FORMAT_S3_12) {
+ min = dc_fixpt_from_fraction(S3D12_MIN, DIVIDER);
+ max = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
+ num_int_bits = 3;
+ num_dec_bits = 12;
+ } else {
+ ASSERT(false);
+ return;
+ }
+
for (i = 0; i < buffer_size; ++i) {
uint32_t reg_value =
fixed_point_to_int_frac(
dc_fixpt_clamp(
flt[i],
- min_2_13,
- max_2_13),
- 2,
- 13);
+ min,
+ max),
+ num_int_bits,
+ num_dec_bits);
matrix[i] = (uint16_t)reg_value;
}
@@ -129,10 +141,27 @@ static struct fixed31_32 int_frac_to_fixed_point(uint16_t arg,
*/
void convert_hw_matrix(struct fixed31_32 *matrix,
uint16_t *reg,
+ enum cm_gamut_coef_format format,
uint32_t buffer_size)
{
- for (uint32_t i = 0; i < buffer_size; ++i)
- matrix[i] = int_frac_to_fixed_point(reg[i], 2, 13);
+ uint8_t num_int_bits;
+ uint8_t num_dec_bits;
+ uint32_t i;
+
+ if (format == CM_GAMUT_REMAP_COEF_FORMAT_S2_13) {
+ num_int_bits = 2;
+ num_dec_bits = 13;
+ } else if (format == CM_GAMUT_REMAP_COEF_FORMAT_S3_12) {
+ num_int_bits = 3;
+ num_dec_bits = 12;
+ } else {
+ ASSERT(false);
+ return;
+ }
+
+ for (i = 0; i < buffer_size; ++i)
+ matrix[i] = int_frac_to_fixed_point(reg[i],
+ num_int_bits, num_dec_bits);
}
static uint32_t find_gcd(uint32_t a, uint32_t b)
diff --git a/drivers/gpu/drm/amd/display/dc/basics/conversion.h b/drivers/gpu/drm/amd/display/dc/basics/conversion.h
index a433cef78496..c7bed2539fa8 100644
--- a/drivers/gpu/drm/amd/display/dc/basics/conversion.h
+++ b/drivers/gpu/drm/amd/display/dc/basics/conversion.h
@@ -28,6 +28,14 @@
#include "include/fixed31_32.h"
+#define DIVIDER 10000
+
+#define S2D13_MIN (-4 * DIVIDER)
+#define S2D13_MAX (39999)
+
+#define S3D12_MIN (-8 * DIVIDER)
+#define S3D12_MAX (79998)
+
uint16_t fixed_point_to_int_frac(
struct fixed31_32 arg,
uint8_t integer_bits,
@@ -35,7 +43,8 @@ uint16_t fixed_point_to_int_frac(
void convert_float_matrix(
uint16_t *matrix,
- struct fixed31_32 *flt,
+ const struct fixed31_32 *flt,
+ enum cm_gamut_coef_format format,
uint32_t buffer_size);
void reduce_fraction(uint32_t num, uint32_t den,
@@ -43,6 +52,7 @@ void reduce_fraction(uint32_t num, uint32_t den,
void convert_hw_matrix(struct fixed31_32 *matrix,
uint16_t *reg,
+ enum cm_gamut_coef_format format,
uint32_t buffer_size);
static inline unsigned int log_2(unsigned int num)
diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h
index 75c06a1752b5..07ebd98f0d3b 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -315,6 +315,7 @@ struct mpc_color_caps {
struct lut3d_caps mcm_3d_lut_caps;
struct lut3d_caps rmcm_3d_lut_caps;
bool preblend;
+ struct fixed31_32 max_gamut_remap_coeff;
};
/**
diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c b/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c
index 12c85c3afd6a..864491bfd7f7 100644
--- a/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c
+++ b/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c
@@ -1145,8 +1145,8 @@ static void dce_transform_set_gamut_remap(
for (i = 0; i < GAMUT_MATRIX_SIZE; i++)
arr_matrix[i] = adjust->temperature_matrix[i];
- convert_float_matrix(
- arr_reg_val, arr_matrix, GAMUT_MATRIX_SIZE);
+ convert_float_matrix(arr_reg_val, arr_matrix,
+ CM_GAMUT_REMAP_COEF_FORMAT_S2_13, GAMUT_MATRIX_SIZE);
program_gamut_remap(xfm_dce, arr_reg_val);
}
diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c
index 9788628cf0ad..ff17a9c9eed0 100644
--- a/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c
+++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c
@@ -171,8 +171,8 @@ void dpp1_cm_set_gamut_remap(
for (i = 0; i < 12; i++)
arr_matrix[i] = adjust->temperature_matrix[i];
- convert_float_matrix(
- arr_reg_val, arr_matrix, 12);
+ convert_float_matrix(arr_reg_val, arr_matrix,
+ CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12);
program_gamut_remap(dpp, arr_reg_val, GAMUT_REMAP_COEFF);
}
@@ -242,8 +242,8 @@ void dpp1_cm_get_gamut_remap(struct dpp *dpp_base,
}
adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW;
- convert_hw_matrix(adjust->temperature_matrix,
- arr_reg_val, ARRAY_SIZE(arr_reg_val));
+ convert_hw_matrix(adjust->temperature_matrix, arr_reg_val,
+ CM_GAMUT_REMAP_COEF_FORMAT_S2_13, ARRAY_SIZE(arr_reg_val));
}
static void dpp1_cm_program_color_matrix(
diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c
index 26f9485f165d..74cc176d9437 100644
--- a/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c
+++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c
@@ -227,8 +227,8 @@ void dpp2_cm_set_gamut_remap(
for (i = 0; i < 12; i++)
arr_matrix[i] = adjust->temperature_matrix[i];
- convert_float_matrix(
- arr_reg_val, arr_matrix, 12);
+ convert_float_matrix(arr_reg_val, arr_matrix,
+ CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12);
program_gamut_remap(dpp, arr_reg_val, DCN2_GAMUT_REMAP_COEF_A);
}
@@ -285,8 +285,8 @@ void dpp2_cm_get_gamut_remap(struct dpp *dpp_base,
}
adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW;
- convert_hw_matrix(adjust->temperature_matrix,
- arr_reg_val, ARRAY_SIZE(arr_reg_val));
+ convert_hw_matrix(adjust->temperature_matrix, arr_reg_val,
+ CM_GAMUT_REMAP_COEF_FORMAT_S2_13, ARRAY_SIZE(arr_reg_val));
}
void dpp2_program_input_csc(
diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c
index e01d8e9672fc..8fe3474411b7 100644
--- a/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c
+++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c
@@ -392,8 +392,8 @@ void dpp3_cm_set_gamut_remap(
for (i = 0; i < 12; i++)
arr_matrix[i] = adjust->temperature_matrix[i];
- convert_float_matrix(
- arr_reg_val, arr_matrix, 12);
+ convert_float_matrix(arr_reg_val, arr_matrix,
+ CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12);
//current coefficient set in use
REG_GET(CM_GAMUT_REMAP_CONTROL, CM_GAMUT_REMAP_MODE_CURRENT, &gamut_mode);
@@ -460,6 +460,6 @@ void dpp3_cm_get_gamut_remap(struct dpp *dpp_base,
}
adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW;
- convert_hw_matrix(adjust->temperature_matrix,
- arr_reg_val, ARRAY_SIZE(arr_reg_val));
+ convert_hw_matrix(adjust->temperature_matrix, arr_reg_val,
+ CM_GAMUT_REMAP_COEF_FORMAT_S2_13, ARRAY_SIZE(arr_reg_val));
}
diff --git a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c
index 37adf0e6a166..5a611dd14581 100644
--- a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c
+++ b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c
@@ -371,7 +371,8 @@ void dwb3_set_gamut_remap(
for (i = 0; i < 12; i++)
arr_matrix[i] = adjust.temperature_matrix[i];
- convert_float_matrix(arr_reg_val, arr_matrix, 12);
+ convert_float_matrix(arr_reg_val, arr_matrix,
+ CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12);
REG_GET(DWB_GAMUT_REMAP_MODE, DWB_GAMUT_REMAP_MODE_CURRENT, ¤t_mode);
diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c b/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c
index d7a07e29d23a..9cc6fd38da19 100644
--- a/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c
+++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c
@@ -1067,7 +1067,8 @@ static void program_gamut_remap(
struct dcn30_mpc *mpc30,
int mpcc_id,
const uint16_t *regval,
- uint32_t select)
+ uint32_t select,
+ enum cm_gamut_coef_format coef_format)
{
uint16_t selection = 0;
struct color_matrices_reg gam_regs;
@@ -1117,9 +1118,11 @@ static void program_gamut_remap(
&gam_regs);
}
- //select coefficient set to use
+ // select coefficient set + format to use
REG_SET(MPCC_GAMUT_REMAP_MODE[mpcc_id], 0,
MPCC_GAMUT_REMAP_MODE, selection);
+ REG_SET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0,
+ MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format);
}
void mpc3_set_gamut_remap(
@@ -1130,18 +1133,26 @@ void mpc3_set_gamut_remap(
struct dcn30_mpc *mpc30 = TO_DCN30_MPC(mpc);
int i = 0;
uint32_t gamut_mode;
+ uint16_t hw_matrix[12];
+ enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13;
+ struct fixed31_32 abs_max_coef = {0};
if (adjust->gamut_adjust_type != GRAPHICS_GAMUT_ADJUST_TYPE_SW)
- program_gamut_remap(mpc30, mpcc_id, NULL, GAMUT_REMAP_BYPASS);
+ program_gamut_remap(mpc30, mpcc_id, NULL, GAMUT_REMAP_BYPASS, coef_format);
else {
- struct fixed31_32 arr_matrix[12];
- uint16_t arr_reg_val[12];
+ // take largest absolute value of coefficient in temperature matrix
+ // if S2D13 cannot fit value, use S3D12
+ for (i = 0; i < 12; i++) {
+ if (dc_fixpt_le(abs_max_coef, dc_fixpt_abs(adjust->temperature_matrix[i])))
+ abs_max_coef = dc_fixpt_abs(adjust->temperature_matrix[i]);
+ }
- for (i = 0; i < 12; i++)
- arr_matrix[i] = adjust->temperature_matrix[i];
+ if (dc_fixpt_le(abs_max_coef, dc_fixpt_from_fraction(S2D13_MAX, DIVIDER)))
+ coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13;
+ else
+ coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S3_12;
- convert_float_matrix(
- arr_reg_val, arr_matrix, 12);
+ convert_float_matrix(hw_matrix, adjust->temperature_matrix, coef_format, 12);
//current coefficient set in use
REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], MPCC_GAMUT_REMAP_MODE_CURRENT, &gamut_mode);
@@ -1153,19 +1164,21 @@ void mpc3_set_gamut_remap(
else
gamut_mode = 1;
- program_gamut_remap(mpc30, mpcc_id, arr_reg_val, gamut_mode);
+ program_gamut_remap(mpc30, mpcc_id, hw_matrix, gamut_mode, coef_format);
}
}
static void read_gamut_remap(struct dcn30_mpc *mpc30,
int mpcc_id,
uint16_t *regval,
- uint32_t *select)
+ uint32_t *select,
+ enum cm_gamut_coef_format *coef_format)
{
struct color_matrices_reg gam_regs;
- //current coefficient set in use
+ //current coefficient set in use + format
REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], MPCC_GAMUT_REMAP_MODE_CURRENT, select);
+ REG_GET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format);
gam_regs.shifts.csc_c11 = mpc30->mpc_shift->MPCC_GAMUT_REMAP_C11_A;
gam_regs.masks.csc_c11 = mpc30->mpc_mask->MPCC_GAMUT_REMAP_C11_A;
@@ -1202,8 +1215,9 @@ void mpc3_get_gamut_remap(struct mpc *mpc,
struct dcn30_mpc *mpc30 = TO_DCN30_MPC(mpc);
uint16_t arr_reg_val[12] = {0};
uint32_t select;
+ enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13;
- read_gamut_remap(mpc30, mpcc_id, arr_reg_val, &select);
+ read_gamut_remap(mpc30, mpcc_id, arr_reg_val, &select, &coef_format);
if (select == GAMUT_REMAP_BYPASS) {
adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS;
@@ -1211,8 +1225,8 @@ void mpc3_get_gamut_remap(struct mpc *mpc,
}
adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW;
- convert_hw_matrix(adjust->temperature_matrix,
- arr_reg_val, ARRAY_SIZE(arr_reg_val));
+ convert_hw_matrix(adjust->temperature_matrix, arr_reg_val,
+ coef_format, ARRAY_SIZE(arr_reg_val));
}
bool mpc3_program_3dlut(
diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c
index ce1ee2062e41..d8a4e604a34e 100644
--- a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c
+++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c
@@ -302,7 +302,8 @@ void mpc_program_gamut_remap(
unsigned int mpcc_id,
const uint16_t *regval,
enum mpcc_gamut_remap_id gamut_remap_block_id,
- enum mpcc_gamut_remap_mode_select mode_select)
+ enum mpcc_gamut_remap_mode_select mode_select,
+ enum cm_gamut_coef_format coef_format)
{
struct color_matrices_reg gamut_regs;
struct dcn401_mpc *mpc401 = TO_DCN401_MPC(mpc);
@@ -339,8 +340,11 @@ void mpc_program_gamut_remap(
regval,
&gamut_regs);
- //select coefficient set to use, set A (MODE_1) or set B (MODE_2)
- REG_SET(MPCC_GAMUT_REMAP_MODE[mpcc_id], 0, MPCC_GAMUT_REMAP_MODE, mode_select);
+ //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + format
+ REG_SET(MPCC_GAMUT_REMAP_MODE[mpcc_id], 0,
+ MPCC_GAMUT_REMAP_MODE, mode_select);
+ REG_SET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0,
+ MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format);
break;
case MPCC_MCM_FIRST_GAMUT_REMAP:
@@ -373,9 +377,11 @@ void mpc_program_gamut_remap(
regval,
&gamut_regs);
- //select coefficient set to use, set A (MODE_1) or set B (MODE_2)
+ //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + format
REG_SET(MPCC_MCM_FIRST_GAMUT_REMAP_MODE[mpcc_id], 0,
MPCC_MCM_FIRST_GAMUT_REMAP_MODE, mode_select);
+ REG_SET(MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0,
+ MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT, coef_format);
break;
case MPCC_MCM_SECOND_GAMUT_REMAP:
@@ -408,9 +414,11 @@ void mpc_program_gamut_remap(
regval,
&gamut_regs);
- //select coefficient set to use, set A (MODE_1) or set B (MODE_2)
+ //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + format
REG_SET(MPCC_MCM_SECOND_GAMUT_REMAP_MODE[mpcc_id], 0,
MPCC_MCM_SECOND_GAMUT_REMAP_MODE, mode_select);
+ REG_SET(MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0,
+ MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT, coef_format);
break;
default:
@@ -426,19 +434,28 @@ void mpc401_set_gamut_remap(
struct dcn401_mpc *mpc401 = TO_DCN401_MPC(mpc);
unsigned int i = 0;
uint32_t mode_select = 0;
+ uint16_t hw_matrix[12];
+ enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13;
+ struct fixed31_32 abs_max_coef = {0};
if (adjust->gamut_adjust_type != GRAPHICS_GAMUT_ADJUST_TYPE_SW) {
/* Bypass / Disable if type is bypass or hw */
- mpc_program_gamut_remap(mpc, mpcc_id, NULL,
- adjust->mpcc_gamut_remap_block_id, MPCC_GAMUT_REMAP_MODE_SELECT_0);
+ mpc_program_gamut_remap(mpc, mpcc_id, NULL, adjust->mpcc_gamut_remap_block_id,
+ MPCC_GAMUT_REMAP_MODE_SELECT_0, coef_format);
} else {
- struct fixed31_32 arr_matrix[12];
- uint16_t arr_reg_val[12];
+ // take largest absolute value of coefficient in temperature matrix
+ // if S2D13 cannot fit value, use S3D12
+ for (i = 0; i < 12; i++) {
+ if (dc_fixpt_le(abs_max_coef, dc_fixpt_abs(adjust->temperature_matrix[i])))
+ abs_max_coef = dc_fixpt_abs(adjust->temperature_matrix[i]);
+ }
- for (i = 0; i < 12; i++)
- arr_matrix[i] = adjust->temperature_matrix[i];
+ if (dc_fixpt_le(abs_max_coef, dc_fixpt_from_fraction(S2D13_MAX, DIVIDER)))
+ coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13;
+ else
+ coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S3_12;
- convert_float_matrix(arr_reg_val, arr_matrix, 12);
+ convert_float_matrix(hw_matrix, adjust->temperature_matrix, coef_format, 12);
switch (adjust->mpcc_gamut_remap_block_id) {
case MPCC_OGAM_GAMUT_REMAP:
@@ -463,8 +480,8 @@ void mpc401_set_gamut_remap(
else
mode_select = MPCC_GAMUT_REMAP_MODE_SELECT_2;
- mpc_program_gamut_remap(mpc, mpcc_id, arr_reg_val,
- adjust->mpcc_gamut_remap_block_id, mode_select);
+ mpc_program_gamut_remap(mpc, mpcc_id, hw_matrix,
+ adjust->mpcc_gamut_remap_block_id, mode_select, coef_format);
}
}
@@ -472,7 +489,8 @@ void mpc_read_gamut_remap(struct mpc *mpc,
int mpcc_id,
uint16_t *regval,
enum mpcc_gamut_remap_id gamut_remap_block_id,
- uint32_t *mode_select)
+ uint32_t *mode_select,
+ enum cm_gamut_coef_format *coef_format)
{
struct color_matrices_reg gamut_regs = {0};
struct dcn401_mpc *mpc401 = TO_DCN401_MPC(mpc);
@@ -480,7 +498,10 @@ void mpc_read_gamut_remap(struct mpc *mpc,
switch (gamut_remap_block_id) {
case MPCC_OGAM_GAMUT_REMAP:
//current coefficient set in use
- REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], MPCC_GAMUT_REMAP_MODE_CURRENT, mode_select);
+ REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id],
+ MPCC_GAMUT_REMAP_MODE_CURRENT, mode_select);
+ REG_GET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id],
+ MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format);
gamut_regs.shifts.csc_c11 = mpc401->mpc_shift->MPCC_GAMUT_REMAP_C11_A;
gamut_regs.masks.csc_c11 = mpc401->mpc_mask->MPCC_GAMUT_REMAP_C11_A;
@@ -503,7 +524,9 @@ void mpc_read_gamut_remap(struct mpc *mpc,
case MPCC_MCM_FIRST_GAMUT_REMAP:
REG_GET(MPCC_MCM_FIRST_GAMUT_REMAP_MODE[mpcc_id],
- MPCC_MCM_FIRST_GAMUT_REMAP_MODE_CURRENT, mode_select);
+ MPCC_MCM_FIRST_GAMUT_REMAP_MODE_CURRENT, mode_select);
+ REG_GET(MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT[mpcc_id],
+ MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT, coef_format);
gamut_regs.shifts.csc_c11 = mpc401->mpc_shift->MPCC_MCM_FIRST_GAMUT_REMAP_C11_A;
gamut_regs.masks.csc_c11 = mpc401->mpc_mask->MPCC_MCM_FIRST_GAMUT_REMAP_C11_A;
@@ -526,7 +549,9 @@ void mpc_read_gamut_remap(struct mpc *mpc,
case MPCC_MCM_SECOND_GAMUT_REMAP:
REG_GET(MPCC_MCM_SECOND_GAMUT_REMAP_MODE[mpcc_id],
- MPCC_MCM_SECOND_GAMUT_REMAP_MODE_CURRENT, mode_select);
+ MPCC_MCM_SECOND_GAMUT_REMAP_MODE_CURRENT, mode_select);
+ REG_GET(MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT[mpcc_id],
+ MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT, coef_format);
gamut_regs.shifts.csc_c11 = mpc401->mpc_shift->MPCC_MCM_SECOND_GAMUT_REMAP_C11_A;
gamut_regs.masks.csc_c11 = mpc401->mpc_mask->MPCC_MCM_SECOND_GAMUT_REMAP_C11_A;
@@ -565,8 +590,10 @@ void mpc401_get_gamut_remap(struct mpc *mpc,
{
uint16_t arr_reg_val[12] = {0};
uint32_t mode_select = MPCC_GAMUT_REMAP_MODE_SELECT_0;
+ enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13;
- mpc_read_gamut_remap(mpc, mpcc_id, arr_reg_val, adjust->mpcc_gamut_remap_block_id, &mode_select);
+ mpc_read_gamut_remap(mpc, mpcc_id, arr_reg_val,
+ adjust->mpcc_gamut_remap_block_id, &mode_select, &coef_format);
if (mode_select == MPCC_GAMUT_REMAP_MODE_SELECT_0) {
adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS;
@@ -574,8 +601,8 @@ void mpc401_get_gamut_remap(struct mpc *mpc,
}
adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW;
- convert_hw_matrix(adjust->temperature_matrix,
- arr_reg_val, ARRAY_SIZE(arr_reg_val));
+ convert_hw_matrix(adjust->temperature_matrix, arr_reg_val,
+ coef_format, ARRAY_SIZE(arr_reg_val));
}
static const struct mpc_funcs dcn401_mpc_funcs = {
diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h
index 6d842d7b95c7..3d00d9497ed2 100644
--- a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h
+++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h
@@ -242,13 +242,15 @@ void mpc_program_gamut_remap(
unsigned int mpcc_id,
const uint16_t *regval,
enum mpcc_gamut_remap_id gamut_remap_block_id,
- enum mpcc_gamut_remap_mode_select mode_select);
+ enum mpcc_gamut_remap_mode_select mode_select,
+ enum cm_gamut_coef_format coef_format);
void mpc_read_gamut_remap(struct mpc *mpc,
int mpcc_id,
uint16_t *regval,
enum mpcc_gamut_remap_id gamut_remap_block_id,
- uint32_t *mode_select);
+ uint32_t *mode_select,
+ enum cm_gamut_coef_format *coef_format);
void mpc401_get_3dlut_fast_load_status(
struct mpc *mpc,
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c
index d11ab57afcdd..40fc66e64c1b 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c
@@ -31,6 +31,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn20/dcn20_resource.h"
#include "dcn30_resource.h"
@@ -2515,6 +2516,7 @@ static bool dcn30_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.pq = 0;
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.dp_hdmi21_pcon_support = true;
dc->caps.max_v_total = (1 << 15) - 1;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c
index 4ecf448d15b6..551565ae9ef2 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c
@@ -31,6 +31,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn30/dcn30_resource.h"
#include "dcn301_resource.h"
@@ -1516,6 +1517,7 @@ static bool dcn301_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.pq = 0;
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.dp_hdmi21_pcon_support = true;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c
index ae8918a4ad3e..2574e706631d 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c
@@ -27,6 +27,7 @@
#include "dcn302_resource.h"
#include "dcn302/dcn302_dccg.h"
#include "irq/dcn302/irq_service_dcn302.h"
+#include "basics/conversion.h"
#include "dcn30/dcn30_dio_link_encoder.h"
#include "dcn30/dcn30_dio_stream_encoder.h"
@@ -1416,6 +1417,7 @@ static bool dcn302_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.pq = 0;
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.dp_hdmi21_pcon_support = true;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c
index 75e6f4e46f60..8b24628e3e14 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c
@@ -27,6 +27,7 @@
#include "dcn303_resource.h"
#include "dcn303/dcn303_dccg.h"
#include "irq/dcn303/irq_service_dcn303.h"
+#include "basics/conversion.h"
#include "dcn30/dcn30_dio_link_encoder.h"
#include "dcn30/dcn30_dio_stream_encoder.h"
@@ -1360,6 +1361,7 @@ static bool dcn303_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.pq = 0;
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.dp_hdmi21_pcon_support = true;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c
index e29efa452c87..8bb2b8b29cbf 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c
@@ -31,6 +31,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn31_resource.h"
#include "dcn20/dcn20_resource.h"
@@ -2123,6 +2124,7 @@ static bool dcn31_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.pq = 0;
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.num_of_host_routers = 2;
dc->caps.num_of_dpias_per_host_router = 2;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c
index f50b3250dcba..372e05487847 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c
@@ -33,6 +33,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn314_resource.h"
#include "dcn20/dcn20_resource.h"
@@ -2051,6 +2052,7 @@ static bool dcn314_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.pq = 0;
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.max_disp_clock_khz_at_vmin = 650000;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c
index 8297f2f04c16..6e112ef0a49a 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c
@@ -31,6 +31,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn315_resource.h"
#include "dcn20/dcn20_resource.h"
@@ -2095,6 +2096,7 @@ static bool dcn315_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.pq = 0;
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->config.no_native422_support = true;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c
index 046566ad1afe..b9d2567a4180 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c
@@ -31,6 +31,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn316_resource.h"
#include "dcn20/dcn20_resource.h"
@@ -1969,6 +1970,7 @@ static bool dcn316_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.pq = 0;
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
/* read VBIOS LTTPR caps */
{
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c
index 004c5690f876..cd9d2087d14e 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c
@@ -32,6 +32,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn32_resource.h"
#include "dcn20/dcn20_resource.h"
@@ -2448,6 +2449,7 @@ static bool dcn32_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
dc->caps.color.mpc.preblend = true;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
/* Use pipe context based otg sync logic */
dc->config.use_pipe_ctx_sync_logic = true;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c
index 53fd32249310..ff9c1379896e 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c
@@ -31,6 +31,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn32/dcn32_resource.h"
#include "dcn321_resource.h"
@@ -1939,6 +1940,7 @@ static bool dcn321_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
dc->caps.color.mpc.preblend = true;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
/* HACK: Force FRL support until BIOS is ready. */
dc->config.force_hdmi21_frl_enc_enable = true;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c
index 5541b89b1350..1b9b6eb8b322 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c
@@ -32,6 +32,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn35_resource.h"
#include "dml2_0/dml2_wrapper.h"
@@ -2078,6 +2079,7 @@ static bool dcn35_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
dc->caps.color.mpc.preblend = true;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.num_of_host_routers = 2;
dc->caps.num_of_dpias_per_host_router = 2;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c
index 053b4380f57e..2268ced16969 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c
@@ -10,6 +10,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn351_resource.h"
#include "dcn20/dcn20_resource.h"
@@ -2051,6 +2052,7 @@ static bool dcn351_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
dc->caps.color.mpc.preblend = true;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.num_of_host_routers = 2;
dc->caps.num_of_dpias_per_host_router = 2;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c
index 592000cf9250..b1323e76801e 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c
@@ -10,6 +10,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn36_resource.h"
#include "dml2_0/dml2_wrapper.h"
@@ -2048,6 +2049,7 @@ static bool dcn36_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
dc->caps.color.mpc.preblend = true;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.num_of_host_routers = 2;
dc->caps.num_of_dpias_per_host_router = 2;
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c
index f7ba0509e6bd..181cb7135dfb 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c
@@ -10,6 +10,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn401_resource.h"
#include "dcn20/dcn20_resource.h"
@@ -2131,6 +2132,7 @@ static bool dcn401_resource_construct(
dc->caps.color.mpc.ogam_rom_caps.hlg = 0;
dc->caps.color.mpc.ocsc = 1;
dc->caps.color.mpc.preblend = true;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
/* HACK: Force FRL support until BIOS is ready. */
dc->config.force_hdmi21_frl_enc_enable = true;
dc->config.use_spl = true;
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 67673a9e5155..dba1b69137f9 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
@@ -9,6 +9,7 @@
#include "dcn42/dcn42_init.h"
#include "resource.h"
+#include "basics/conversion.h"
#include "include/irq_service_interface.h"
#include "dcn42_resource.h"
@@ -2110,6 +2111,7 @@ static bool dcn42_resource_construct(
dc->caps.color.mpc.rmcm_3d_lut_caps.mem_format_support.float_fp1_5_10 = 1;
dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_rgba = 1;
dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_bgra = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.num_of_host_routers = 3;
dc->caps.num_of_dpias_per_host_router = 2;
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 2334bc5b75b8..dbd6267340ac 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
@@ -13,6 +13,7 @@
#include "resource.h"
#include "include/irq_service_interface.h"
+#include "basics/conversion.h"
#include "dcn42b_resource.h"
#include "dcn20/dcn20_resource.h"
@@ -2080,6 +2081,7 @@ static bool dcn42b_resource_construct(
dc->caps.color.mpc.rmcm_3d_lut_caps.mem_format_support.float_fp1_5_10 = 1;
dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_rgba = 1;
dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_bgra = 1;
+ dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER);
dc->caps.num_of_host_routers = 0;
dc->caps.num_of_dpias_per_host_router = 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 37/49] drm/amd/display: Reintroduce "convert dcn42 GPIO translation to lookup tables"
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (35 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 36/49] drm/amd/display: adjust floating point format for gamut remap when needed Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 38/49] drm/amd/display: Prune per-tile Timing from Apple Studio Display Primary Tile Fangzhi Zuo
` (11 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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 Carbones,
Michael Strauss
From: Nicholas Carbones <Nicholas.Carbones@amd.com>
This reverts commit c371d98717539f687e6d96a63c74a7beb764c138.
Reviewed-by: Michael Strauss <michael.strauss@amd.com>
Signed-off-by: Nicholas Carbones <Nicholas.Carbones@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../dc/gpio/dcn42/hw_translate_dcn42.c | 193 +++++++-----------
.../dc/gpio/dcn42/hw_translate_dcn42.c.orig | 193 +++++++++++-------
2 files changed, 193 insertions(+), 193 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c
index e7e1d9979876..7b2c4cd42450 100644
--- a/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c
+++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c
@@ -39,62 +39,76 @@
* end *********************/
+static const struct gpio_id_offset_entry gpio_offsets[] = {
+ /* HPD */
+ GPIO_ENTRY(HPD0_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_1),
+ GPIO_ENTRY(HPD1_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_2),
+ GPIO_ENTRY(HPD2_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_3),
+ GPIO_ENTRY(HPD3_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_4),
+ GPIO_ENTRY(HPD4_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_5),
+};
+
+
+/* DDC */
+static const struct gpio_ddc_offset_entry ddc_offset_map[] = {
+ { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 },
+ { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 },
+ { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 },
+ { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 },
+ { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 },
+ { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA },
+};
+
+
+static const struct gpio_pin_entry gpio_pins[] = {
+ /* DDC */
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1,
+ DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2,
+ DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3,
+ DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4,
+ DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5,
+ DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA,
+ DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1,
+ DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2,
+ DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3,
+ DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4,
+ DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5,
+ DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+ GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA,
+ DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
+};
+
+
static bool offset_to_id(
uint32_t offset,
uint32_t mask,
enum gpio_id *id,
uint32_t *en)
{
- (void)mask;
- switch (offset) {
- /* HPD */
- case REG(HPD0_DC_HPD_INT_STATUS):
- *id = GPIO_ID_HPD;
- *en = GPIO_HPD_1;
- return true;
- case REG(HPD1_DC_HPD_INT_STATUS):
- *id = GPIO_ID_HPD;
- *en = GPIO_HPD_2;
+ if (dal_hw_translate_gpio_ddc_offset_to_id(
+ ddc_offset_map,
+ ARRAY_SIZE(ddc_offset_map),
+ offset, en))
return true;
- case REG(HPD2_DC_HPD_INT_STATUS):
- *id = GPIO_ID_HPD;
- *en = GPIO_HPD_3;
- return true;
- case REG(HPD3_DC_HPD_INT_STATUS):
- *id = GPIO_ID_HPD;
- *en = GPIO_HPD_4;
- return true;
- case REG(HPD4_DC_HPD_INT_STATUS):
- *id = GPIO_ID_HPD;
- *en = GPIO_HPD_5;
- return true;
- /* DDC */
- /* we don't care about the GPIO_ID for DDC
- * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK
- * directly in the create method
- */
- case REG(DC_GPIO_DDC1_A):
- *en = GPIO_DDC_LINE_DDC1;
- return true;
- case REG(DC_GPIO_DDC2_A):
- *en = GPIO_DDC_LINE_DDC2;
- return true;
- case REG(DC_GPIO_DDC3_A):
- *en = GPIO_DDC_LINE_DDC3;
- return true;
- case REG(DC_GPIO_DDC4_A):
- *en = GPIO_DDC_LINE_DDC4;
- return true;
- case REG(DC_GPIO_DDC5_A):
- *en = GPIO_DDC_LINE_DDC5;
- return true;
- case REG(DC_GPIO_DDCVGA_A):
- *en = GPIO_DDC_LINE_DDC_VGA;
+
+ if (dal_hw_translate_gpio_offset_to_id(
+ gpio_offsets,
+ ARRAY_SIZE(gpio_offsets),
+ offset, mask, id, en))
return true;
- default:
- ASSERT_CRITICAL(false);
- return false;
- }
+
+ ASSERT_CRITICAL(false);
+ return false;
}
@@ -103,81 +117,14 @@ static bool id_to_offset(
uint32_t en,
struct gpio_pin_info *info)
{
- bool result = true;
-
- switch (id) {
- case GPIO_ID_DDC_DATA:
- info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK;
- switch (en) {
- case GPIO_DDC_LINE_DDC1:
- info->offset = REG(DC_GPIO_DDC1_A);
- break;
- case GPIO_DDC_LINE_DDC2:
- info->offset = REG(DC_GPIO_DDC2_A);
- break;
- case GPIO_DDC_LINE_DDC3:
- info->offset = REG(DC_GPIO_DDC3_A);
- break;
- case GPIO_DDC_LINE_DDC4:
- info->offset = REG(DC_GPIO_DDC4_A);
- break;
- case GPIO_DDC_LINE_DDC5:
- info->offset = REG(DC_GPIO_DDC5_A);
- break;
- case GPIO_DDC_LINE_DDC_VGA:
- info->offset = REG(DC_GPIO_DDCVGA_A);
- break;
- case GPIO_DDC_LINE_I2C_PAD:
- default:
- ASSERT_CRITICAL(false);
- result = false;
- }
- break;
- case GPIO_ID_DDC_CLOCK:
- info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK;
- switch (en) {
- case GPIO_DDC_LINE_DDC1:
- info->offset = REG(DC_GPIO_DDC1_A);
- break;
- case GPIO_DDC_LINE_DDC2:
- info->offset = REG(DC_GPIO_DDC2_A);
- break;
- case GPIO_DDC_LINE_DDC3:
- info->offset = REG(DC_GPIO_DDC3_A);
- break;
- case GPIO_DDC_LINE_DDC4:
- info->offset = REG(DC_GPIO_DDC4_A);
- break;
- case GPIO_DDC_LINE_DDC5:
- info->offset = REG(DC_GPIO_DDC5_A);
- break;
- case GPIO_DDC_LINE_DDC_VGA:
- info->offset = REG(DC_GPIO_DDCVGA_A);
- break;
- case GPIO_DDC_LINE_I2C_PAD:
- default:
- ASSERT_CRITICAL(false);
- result = false;
- }
- break;
- case GPIO_ID_SYNC:
- case GPIO_ID_VIP_PAD:
- default:
- ASSERT_CRITICAL(false);
- result = false;
- }
-
- if (result) {
- info->offset_y = info->offset + 2;
- info->offset_en = info->offset + 1;
- info->offset_mask = info->offset - 1;
-
- info->mask_y = info->mask;
- info->mask_en = info->mask;
- info->mask_mask = info->mask;
- }
-
- return result;
+ if (dal_hw_translate_id_to_offset(
+ gpio_pins,
+ ARRAY_SIZE(gpio_pins),
+ id, en, info))
+ return true;
+
+ ASSERT_CRITICAL(false);
+ return false;
}
diff --git a/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c.orig b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c.orig
index 7b2c4cd42450..e7e1d9979876 100644
--- a/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c.orig
+++ b/drivers/gpu/drm/amd/display/dc/gpio/dcn42/hw_translate_dcn42.c.orig
@@ -39,76 +39,62 @@
* end *********************/
-static const struct gpio_id_offset_entry gpio_offsets[] = {
- /* HPD */
- GPIO_ENTRY(HPD0_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_1),
- GPIO_ENTRY(HPD1_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_2),
- GPIO_ENTRY(HPD2_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_3),
- GPIO_ENTRY(HPD3_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_4),
- GPIO_ENTRY(HPD4_DC_HPD_INT_STATUS, GPIO_ID_HPD, GPIO_HPD_5),
-};
-
-
-/* DDC */
-static const struct gpio_ddc_offset_entry ddc_offset_map[] = {
- { REG(DC_GPIO_DDC1_A), GPIO_DDC_LINE_DDC1 },
- { REG(DC_GPIO_DDC2_A), GPIO_DDC_LINE_DDC2 },
- { REG(DC_GPIO_DDC3_A), GPIO_DDC_LINE_DDC3 },
- { REG(DC_GPIO_DDC4_A), GPIO_DDC_LINE_DDC4 },
- { REG(DC_GPIO_DDC5_A), GPIO_DDC_LINE_DDC5 },
- { REG(DC_GPIO_DDCVGA_A), GPIO_DDC_LINE_DDC_VGA },
-};
-
-
-static const struct gpio_pin_entry gpio_pins[] = {
- /* DDC */
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1,
- DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC2,
- DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC3,
- DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC4,
- DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC5,
- DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA,
- DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC1,
- DC_GPIO_DDC1_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC2,
- DC_GPIO_DDC2_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC3,
- DC_GPIO_DDC3_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC4,
- DC_GPIO_DDC4_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC5,
- DC_GPIO_DDC5_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
- GPIO_PIN_ENTRY(GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_DDC_VGA,
- DC_GPIO_DDCVGA_A, DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK),
-};
-
-
static bool offset_to_id(
uint32_t offset,
uint32_t mask,
enum gpio_id *id,
uint32_t *en)
{
- if (dal_hw_translate_gpio_ddc_offset_to_id(
- ddc_offset_map,
- ARRAY_SIZE(ddc_offset_map),
- offset, en))
+ (void)mask;
+ switch (offset) {
+ /* HPD */
+ case REG(HPD0_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_1;
return true;
-
- if (dal_hw_translate_gpio_offset_to_id(
- gpio_offsets,
- ARRAY_SIZE(gpio_offsets),
- offset, mask, id, en))
+ case REG(HPD1_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_2;
return true;
-
- ASSERT_CRITICAL(false);
- return false;
+ case REG(HPD2_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_3;
+ return true;
+ case REG(HPD3_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_4;
+ return true;
+ case REG(HPD4_DC_HPD_INT_STATUS):
+ *id = GPIO_ID_HPD;
+ *en = GPIO_HPD_5;
+ return true;
+ /* DDC */
+ /* we don't care about the GPIO_ID for DDC
+ * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK
+ * directly in the create method
+ */
+ case REG(DC_GPIO_DDC1_A):
+ *en = GPIO_DDC_LINE_DDC1;
+ return true;
+ case REG(DC_GPIO_DDC2_A):
+ *en = GPIO_DDC_LINE_DDC2;
+ return true;
+ case REG(DC_GPIO_DDC3_A):
+ *en = GPIO_DDC_LINE_DDC3;
+ return true;
+ case REG(DC_GPIO_DDC4_A):
+ *en = GPIO_DDC_LINE_DDC4;
+ return true;
+ case REG(DC_GPIO_DDC5_A):
+ *en = GPIO_DDC_LINE_DDC5;
+ return true;
+ case REG(DC_GPIO_DDCVGA_A):
+ *en = GPIO_DDC_LINE_DDC_VGA;
+ return true;
+ default:
+ ASSERT_CRITICAL(false);
+ return false;
+ }
}
@@ -117,14 +103,81 @@ static bool id_to_offset(
uint32_t en,
struct gpio_pin_info *info)
{
- if (dal_hw_translate_id_to_offset(
- gpio_pins,
- ARRAY_SIZE(gpio_pins),
- id, en, info))
- return true;
-
- ASSERT_CRITICAL(false);
- return false;
+ bool result = true;
+
+ switch (id) {
+ case GPIO_ID_DDC_DATA:
+ info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK;
+ switch (en) {
+ case GPIO_DDC_LINE_DDC1:
+ info->offset = REG(DC_GPIO_DDC1_A);
+ break;
+ case GPIO_DDC_LINE_DDC2:
+ info->offset = REG(DC_GPIO_DDC2_A);
+ break;
+ case GPIO_DDC_LINE_DDC3:
+ info->offset = REG(DC_GPIO_DDC3_A);
+ break;
+ case GPIO_DDC_LINE_DDC4:
+ info->offset = REG(DC_GPIO_DDC4_A);
+ break;
+ case GPIO_DDC_LINE_DDC5:
+ info->offset = REG(DC_GPIO_DDC5_A);
+ break;
+ case GPIO_DDC_LINE_DDC_VGA:
+ info->offset = REG(DC_GPIO_DDCVGA_A);
+ break;
+ case GPIO_DDC_LINE_I2C_PAD:
+ default:
+ ASSERT_CRITICAL(false);
+ result = false;
+ }
+ break;
+ case GPIO_ID_DDC_CLOCK:
+ info->mask = DC_GPIO_DDC1_A__DC_GPIO_DDC1CLK_A_MASK;
+ switch (en) {
+ case GPIO_DDC_LINE_DDC1:
+ info->offset = REG(DC_GPIO_DDC1_A);
+ break;
+ case GPIO_DDC_LINE_DDC2:
+ info->offset = REG(DC_GPIO_DDC2_A);
+ break;
+ case GPIO_DDC_LINE_DDC3:
+ info->offset = REG(DC_GPIO_DDC3_A);
+ break;
+ case GPIO_DDC_LINE_DDC4:
+ info->offset = REG(DC_GPIO_DDC4_A);
+ break;
+ case GPIO_DDC_LINE_DDC5:
+ info->offset = REG(DC_GPIO_DDC5_A);
+ break;
+ case GPIO_DDC_LINE_DDC_VGA:
+ info->offset = REG(DC_GPIO_DDCVGA_A);
+ break;
+ case GPIO_DDC_LINE_I2C_PAD:
+ default:
+ ASSERT_CRITICAL(false);
+ result = false;
+ }
+ break;
+ case GPIO_ID_SYNC:
+ case GPIO_ID_VIP_PAD:
+ default:
+ ASSERT_CRITICAL(false);
+ result = false;
+ }
+
+ if (result) {
+ info->offset_y = info->offset + 2;
+ info->offset_en = info->offset + 1;
+ info->offset_mask = info->offset - 1;
+
+ info->mask_y = info->mask;
+ info->mask_en = info->mask;
+ info->mask_mask = info->mask;
+ }
+
+ return result;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 38/49] drm/amd/display: Prune per-tile Timing from Apple Studio Display Primary Tile
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (36 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 37/49] drm/amd/display: Reintroduce "convert dcn42 GPIO translation to lookup tables" Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 39/49] drm/amd/display: Add get replay residency function Fangzhi Zuo
` (10 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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
[why]
The Apple Studio Display primary tile advertises both the full 5120x2880
mode and the per-tile 2560x2880 timing. With the secondary tile already
hidden from userspace, the stray 2560x2880 mode on the primary connector
can still be picked by compositors, defeating the single 5K stream goal.
[how]
Prune the per-tile timing from the primary connector during get_modes:
when the sink carries the disable_second_tile quirk and the connector is
the primary tile (tile_h_loc == 0 && tile_v_loc == 0), drop any probed
mode matching the advertised tile size (tile_h_size x tile_v_size) so
userspace only sees the full 5120x2880 mode.
Fixes: 22ea891606 ("drm/amd/display: hide Apple Studio Display secondary tile")
Reviewed-by: Wayne Lin <wayne.lin@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../display/amdgpu_dm/amdgpu_dm_connector.c | 43 +++++++++++++++++++
1 file changed, 43 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 135107c73971..a8f089d4fb72 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
@@ -2751,6 +2751,47 @@ void amdgpu_set_panel_orientation(struct drm_connector *connector)
native_mode->vdisplay);
}
+/*
+ * The Apple Studio Display primary tile advertises both the full 5120x2880
+ * mode and the per-tile 2560x2880 timing. As the secondary tile is hidden from
+ * userspace (see amdgpu_dm_hide_secondary_tile_from_userspace()), drop the
+ * 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)
+{
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ struct drm_display_mode *mode, *t;
+
+ if (!aconnector->dc_sink)
+ return;
+
+ if (!aconnector->dc_sink->edid_caps.panel_patch.disable_second_tile)
+ return;
+
+ if (!connector->has_tile)
+ return;
+
+ /* Only prune the per-tile timing from the primary tile. */
+ if (connector->tile_h_loc || connector->tile_v_loc)
+ return;
+
+ list_for_each_entry_safe(mode, t, &connector->probed_modes, head) {
+ if (mode->hdisplay != connector->tile_h_size ||
+ mode->vdisplay != connector->tile_v_size)
+ continue;
+
+ drm_dbg_kms(connector->dev,
+ "[CONNECTOR:%d:%s] pruning per-tile %dx%d timing from primary Apple Studio Display tile\n",
+ connector->base.id, connector->name,
+ mode->hdisplay, mode->vdisplay);
+
+ list_del(&mode->head);
+ drm_mode_destroy(connector->dev, mode);
+ aconnector->num_modes--;
+ }
+}
+
STATIC_IFN_KUNIT void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector,
const struct drm_edid *drm_edid)
{
@@ -2763,6 +2804,8 @@ STATIC_IFN_KUNIT void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *co
amdgpu_dm_connector->num_modes =
drm_edid_connector_add_modes(connector);
+ amdgpu_dm_prune_primary_tile_modes(connector);
+
/* sorting the probed modes before calling function
* amdgpu_dm_get_native_mode() since EDID can have
* more than one preferred mode. The modes that are
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 39/49] drm/amd/display: Add get replay residency function
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (37 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 38/49] drm/amd/display: Prune per-tile Timing from Apple Studio Display Primary Tile Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 40/49] drm/amd/display: Fix divide-by-zero in calculate_mcache_setting on zero viewport Fangzhi Zuo
` (9 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Huang, Leon, Robin Chen
From: "Huang, Leon" <Leon.Huang1@amd.com>
[Why&How]
Add dc interface to export link service function for getting Replay
residency
Reviewed-by: Robin Chen <robin.chen@amd.com>
Signed-off-by: Huang, Leon <Leon.Huang1@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c | 7 +++++++
drivers/gpu/drm/amd/display/dc/dc.h | 8 ++++++++
2 files changed, 15 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c
index f25d4b3e11ac..14fc4ef482f1 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c
@@ -534,6 +534,13 @@ bool dc_link_set_pr_general_cmd(struct dc_link *link,
return link->dc->link_srv->dp_pr_set_general_cmd(link, general_cmd_data);
}
+void dc_link_edp_replay_residency(const struct dc_link *link,
+ unsigned int *residency, bool is_start, enum pr_residency_mode mode)
+{
+ if (link && link->dc && link->dc->link_srv)
+ link->dc->link_srv->edp_replay_residency(link, residency, is_start, mode);
+}
+
bool dc_link_get_pr_state(const struct dc_link *link, uint64_t *state)
{
return link->dc->link_srv->dp_pr_get_state(link, state);
diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h
index 07ebd98f0d3b..8eaa219132a6 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -2984,6 +2984,14 @@ bool dc_link_update_pr_state(struct dc_link *link,
bool dc_link_set_pr_general_cmd(struct dc_link *link,
struct dmub_cmd_pr_general_cmd_data *general_cmd_data);
+/*
+ * Measure Panel Replay residency for the given link.
+ * mode: PR_RESIDENCY_MODE_PHY, PR_RESIDENCY_MODE_ALPM, or
+ * PR_RESIDENCY_MODE_ENABLEMENT_PERIOD
+ */
+void dc_link_edp_replay_residency(const struct dc_link *link,
+ unsigned int *residency, bool is_start, enum pr_residency_mode mode);
+
/*
* Get Panel Replay state:
*
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 40/49] drm/amd/display: Fix divide-by-zero in calculate_mcache_setting on zero viewport
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (38 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 39/49] drm/amd/display: Add get replay residency function Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 41/49] drm/amd/display: check if dml21_add_phantom_plane() is successful Fangzhi Zuo
` (8 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, George Zhang, Dillon Varone
From: George Zhang <george.zhang@amd.com>
If a plane reaches calculate_mcache_setting with a zero-area viewport,
calculate_mcache_setting exits early with num_mcaches == 0 and
mvmpg_width/height == 0. This will cause a divide-by-zero panic and can
also cause an underflow on num_mcaches.
Fix this by changing calculate_mcache_setting to bool and adding guards
after each calculate_mcache_row_bytes call. If num_mcaches or
mvmpg_width/height is zero, return a false. Callers will propagate the
failure as a rejected mode, which prevents the panic.
Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5302
Reviewed-by: Sun peng (Leo) Li <sunpeng.li@amd.com>
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: George Zhang <george.zhang@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../src/dml2_core/dml2_core_dcn4_calcs.c | 31 +++++++++++++++----
1 file changed, 25 insertions(+), 6 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 fa78016b32fc..110ab26a6a39 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
@@ -2453,7 +2453,7 @@ static void calculate_mcache_row_bytes(
DML_ASSERT(*p->num_mcaches > 0);
}
-static void calculate_mcache_setting(
+static bool calculate_mcache_setting(
struct dml2_core_internal_scratch *scratch,
struct dml2_core_calcs_calculate_mcache_setting_params *p)
{
@@ -2479,7 +2479,7 @@ static void calculate_mcache_setting(
*p->lc_comb_mcache = 0;
if (!p->dcc_enable)
- return;
+ return true;
l->is_dual_plane = dml_is_420(p->source_format) || p->source_format == dml2_rgbe_alpha;
@@ -2516,7 +2516,14 @@ static void calculate_mcache_setting(
l->l_p.mvmpg_per_mcache_lb = &l->mvmpg_per_mcache_lb_l;
calculate_mcache_row_bytes(scratch, &l->l_p);
- DML_ASSERT(*p->num_mcaches_l > 0);
+ if (*p->num_mcaches_l == 0 ||
+ (p->surf_vert ? l->mvmpg_height_l : l->mvmpg_width_l) == 0) {
+ DML_LOG_VERBOSE("DML::%s: degenerate luma viewport (num_mcaches_l=%u mvmpg_%s_l=%u) — mode not supported\n",
+ __func__, *p->num_mcaches_l,
+ p->surf_vert ? "height" : "width",
+ p->surf_vert ? l->mvmpg_height_l : l->mvmpg_width_l);
+ return false;
+ }
if (l->is_dual_plane) {
l->c_p.num_chans = p->num_chans;
@@ -2552,7 +2559,14 @@ static void calculate_mcache_setting(
l->c_p.mvmpg_per_mcache_lb = &l->mvmpg_per_mcache_lb_c;
calculate_mcache_row_bytes(scratch, &l->c_p);
- DML_ASSERT(*p->num_mcaches_c > 0);
+ if (*p->num_mcaches_c == 0 ||
+ (p->surf_vert ? l->mvmpg_height_c : l->mvmpg_width_c) == 0) {
+ DML_LOG_VERBOSE("DML::%s: degenerate chroma viewport (num_mcaches_c=%u mvmpg_%s_c=%u) — mode not supported\n",
+ __func__, *p->num_mcaches_c,
+ p->surf_vert ? "height" : "width",
+ p->surf_vert ? l->mvmpg_height_c : l->mvmpg_width_c);
+ return false;
+ }
}
// Sharing for iMALL access
@@ -2662,6 +2676,7 @@ static void calculate_mcache_setting(
*p->mcache_shift_granularity_l = l->mvmpg_access_width_l;
*p->mcache_shift_granularity_c = l->mvmpg_access_width_c;
+ return true;
}
static void calculate_mall_bw_overhead_factor(
@@ -9490,7 +9505,10 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out
calculate_mcache_setting_params->mall_comb_mcache_c = &mode_lib->ms.mall_comb_mcache_c[k];
calculate_mcache_setting_params->lc_comb_mcache = &mode_lib->ms.lc_comb_mcache[k];
- calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params);
+ if (!calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params)) {
+ mode_lib->ms.support.ModeSupport = false;
+ return false;
+ }
}
calculate_mall_bw_overhead_factor(
@@ -10969,7 +10987,8 @@ static bool dml_core_mode_programming(struct dml2_core_calcs_mode_programming_ex
calculate_mcache_setting_params->mall_comb_mcache_l = &mode_lib->mp.mall_comb_mcache_l[k];
calculate_mcache_setting_params->mall_comb_mcache_c = &mode_lib->mp.mall_comb_mcache_c[k];
calculate_mcache_setting_params->lc_comb_mcache = &mode_lib->mp.lc_comb_mcache[k];
- calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params);
+ if (!calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params))
+ return false;
}
calculate_mall_bw_overhead_factor(
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 41/49] drm/amd/display: check if dml21_add_phantom_plane() is successful
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (39 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 40/49] drm/amd/display: Fix divide-by-zero in calculate_mcache_setting on zero viewport Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 42/49] drm/amd/display: change dcc_rate from 1 to 2 for log use only Fangzhi Zuo
` (7 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Alex Deucher, Dillon Varone
From: Alex Deucher <alexander.deucher@amd.com>
Verify that the phantom plane was allocated to avoid a later
segfault.
Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4970
Fixes: 70839da63605 ("drm/amd/display: Add new DCN401 sources")
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../drm/amd/display/dc/dml2_0/dml21/dml21_utils.c | 15 +++++++--------
1 file changed, 7 insertions(+), 8 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 16e1315da2fa..012dff4cf3fa 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
@@ -359,14 +359,13 @@ void dml21_handle_phantom_streams_planes(const struct dc *dc, struct dc_state *c
main_plane = main_stream_status->plane_states[dc_plane_index];
/* create phantom planes for subvp enabled plane */
- dml21_add_phantom_plane(dml_ctx,
- dc,
- context,
- phantom_stream,
- main_plane,
- &dml_ctx->v21.mode_programming.programming->plane_programming[dml_plane_index]);
-
- phantoms_added = true;
+ if (dml21_add_phantom_plane(dml_ctx,
+ dc,
+ context,
+ phantom_stream,
+ main_plane,
+ &dml_ctx->v21.mode_programming.programming->plane_programming[dml_plane_index]))
+ phantoms_added = true;
}
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 42/49] drm/amd/display: change dcc_rate from 1 to 2 for log use only
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (40 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 41/49] drm/amd/display: check if dml21_add_phantom_plane() is successful Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 43/49] drm/amd/display: enforce UCLK pstate support in mode_support Fangzhi Zuo
` (6 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Charlene Liu, Dillon Varone,
Matthew Stewart
From: Charlene Liu <Charlene.Liu@amd.com>
[why]
The dcc_rate value does not affect any watermark, TTU or DLG output; it
only affects the Z8 stutter-related logging. The hardware
DisplayModeSupported formula uses a dcc_rate of 4, while DML2 currently
uses 1.
[how]
Change dcc_rate from 1 to 2 so the logged value is closer to the
hardware formula.
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Charlene Liu <Charlene.Liu@amd.com>
Signed-off-by: Matthew Stewart <matthew.stewart2@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../display/dc/dml2_0/dml21/dml21_translation_helper.c | 8 ++++----
1 file changed, 4 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 a76dcde1efbf..47164fa150d9 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
@@ -447,8 +447,8 @@ static void populate_dml21_dummy_surface_cfg(struct dml2_surface_cfg *surface, c
surface->plane0.pitch = ((surface->plane0.width + 127) / 128) * 128;
surface->plane1.pitch = 0;
surface->dcc.enable = false;
- surface->dcc.informative.dcc_rate_plane0 = 1.0;
- surface->dcc.informative.dcc_rate_plane1 = 1.0;
+ surface->dcc.informative.dcc_rate_plane0 = 2.0;
+ surface->dcc.informative.dcc_rate_plane1 = 2.0;
surface->dcc.informative.fraction_of_zero_size_request_plane0 = 0;
surface->dcc.informative.fraction_of_zero_size_request_plane1 = 0;
surface->tiling = dml2_sw_64kb_2d;
@@ -517,8 +517,8 @@ static void populate_dml21_surface_config_from_plane_state(
surface->plane1.height = plane_state->plane_size.chroma_size.height;
surface->plane1.width = plane_state->plane_size.chroma_size.width;
surface->dcc.enable = plane_state->dcc.enable;
- surface->dcc.informative.dcc_rate_plane0 = 1.0;
- surface->dcc.informative.dcc_rate_plane1 = 1.0;
+ surface->dcc.informative.dcc_rate_plane0 = 2.0;
+ surface->dcc.informative.dcc_rate_plane1 = 2.0;
surface->dcc.informative.fraction_of_zero_size_request_plane0 = plane_state->dcc.independent_64b_blks;
surface->dcc.informative.fraction_of_zero_size_request_plane1 = plane_state->dcc.independent_64b_blks_c;
surface->dcc.plane0.pitch = plane_state->dcc.meta_pitch;
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 43/49] drm/amd/display: enforce UCLK pstate support in mode_support
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (41 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 42/49] drm/amd/display: change dcc_rate from 1 to 2 for log use only Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 44/49] drm/amd/display: add DalForceMaxDisplayClock debug option to DML2 Fangzhi Zuo
` (5 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Alexander Chechik,
Dillon Varone, Matthew Stewart
From: Alexander Chechik <alexander.chechik@amd.com>
[Why]
mode_support does not require UCLK pstate today, so later PMO optimization
stages can push a plane's VActive latency-hiding margin below zero without
rechecking that the config still supports UCLK pstate. This can blank the
display on high-bandwidth configs.
[How]
Plumb the PMO-selected per-plane pstate method into mode_support and fail
the config only when UCLK pstate is required (method != na) but not
supported. For planes committed to a vactive method, require a non-negative
VActive latency-hiding margin, and skip the check when all streams are
blanked. No-op the PMO DCN42 pstate test (returning false only on the
initial candidate so the optimize/FAMS2 stage-3 setup still runs), since
reserved time is guaranteed by the override and the vactive margin is now
enforced in core mode_support.
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Alexander Chechik <alexander.chechik@amd.com>
Signed-off-by: Matthew Stewart <matthew.stewart2@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../dml21/src/dml2_core/dml2_core_dcn4.c | 8 +++++
.../src/dml2_core/dml2_core_dcn4_calcs.c | 18 +++++++++++
.../dml21/src/dml2_pmo/dml2_pmo_dcn42.c | 32 ++++++-------------
3 files changed, 36 insertions(+), 22 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c
index 727b01ca18bf..45f1bf5e14bf 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c
@@ -554,6 +554,14 @@ bool core_dcn4_mode_support(struct dml2_core_mode_support_in_out *in_out)
l->mode_support_ex_params.min_clk_index = in_out->min_clk_index;
l->mode_support_ex_params.out_evaluation_info = &in_out->mode_support_result.cfg_support_info.clean_me_up.support_info;
+ for (i = 0; i < l->svp_expanded_display_cfg.num_planes; i++) {
+ if (i < in_out->display_cfg->display_config.num_planes)
+ core->clean_me_up.mode_lib.ms.uclk_pstate_switch_modes[i] =
+ in_out->display_cfg->stage3.pstate_switch_modes[i];
+ else
+ core->clean_me_up.mode_lib.ms.uclk_pstate_switch_modes[i] = dml2_pstate_method_na;
+ }
+
result = dml2_core_calcs_mode_support_ex(&l->mode_support_ex_params);
in_out->mode_support_result.cfg_support_info.is_supported = result;
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 110ab26a6a39..da9a1c4e2bdc 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
@@ -9637,6 +9637,22 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out
DML_LOG_VERBOSE("DML::%s: ROBSupport = %u\n", __func__, mode_lib->ms.support.ROBSupport);
#endif
+ mode_lib->ms.support.global_dram_clock_change_support_required = false;
+
+ if (!display_cfg->overrides.all_streams_blanked) {
+ for (k = 0; k < mode_lib->ms.num_active_planes; k++) {
+ if (mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_na)
+ continue;
+
+ mode_lib->ms.support.global_dram_clock_change_support_required = true;
+
+ if ((mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_vactive ||
+ mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_fw_vactive_drr) &&
+ mode_lib->ms.VActiveLatencyHidingMargin[k] < 0)
+ mode_lib->ms.support.global_dram_clock_change_supported = false;
+ }
+ }
+
/*Mode Support, Voltage State and SOC Configuration*/
{
if (mode_lib->ms.support.ScaleRatioAndTapsSupport
@@ -9683,6 +9699,8 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out
&& mode_lib->ms.support.DCCMetaBufferSizeNotExceeded
&& !mode_lib->ms.support.ExceededMALLSize
&& mode_lib->ms.support.g6_temp_read_support
+ && (mode_lib->ms.support.global_dram_clock_change_supported
+ || !mode_lib->ms.support.global_dram_clock_change_support_required)
&& ((!display_cfg->hostvm_enable && !s->ImmediateFlipRequired) || mode_lib->ms.support.ImmediateFlipSupport)) {
DML_LOG_VERBOSE("DML::%s: mode is supported\n", __func__);
mode_lib->ms.support.ModeSupport = true;
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c
index 790aef95caa6..57a6a7d5b740 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c
@@ -13,8 +13,6 @@
* configurations, ensuring p-state watermark support in the blank period only.
*/
-static const double MIN_VACTIVE_MARGIN_PCT = 0.25; // We need more than non-zero margin because DET buffer granularity can alter vactive latency hiding
-
static const struct dml2_pmo_pstate_strategy dcn42_strategy_list_1_display[] = {
// VBlank only
{
@@ -179,7 +177,7 @@ bool pmo_dcn42_init_for_pstate_support(struct dml2_pmo_init_for_pstate_support_i
// Figure out which streams can do vactive, and also build up implicit SVP and FAMS2 meta
for (stream_index = 0; stream_index < display_config->display_config.num_streams; stream_index++) {
- if (dcn4_get_vactive_pstate_margin(display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) >= (int)(MIN_VACTIVE_MARGIN_PCT * pmo->soc_bb->power_management_parameters.dram_clk_change_blackout_us))
+ if (dcn4_get_vactive_pstate_margin(display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) >= 0)
dcn42_set_bit_in_bitfield(&s->pmo_dcn4.stream_vactive_capability_mask, stream_index);
}
@@ -259,27 +257,17 @@ bool pmo_dcn42_fams2_optimize_for_pstate_support(struct dml2_pmo_optimize_for_ps
bool pmo_dcn42_test_for_pstate_support(struct dml2_pmo_test_for_pstate_support_in_out *in_out)
{
- const struct dml2_pmo_scratch *s = &in_out->instance->scratch;
- bool p_state_supported = true;
- unsigned int stream_index;
-
- if (s->pmo_dcn4.cur_pstate_candidate < 0)
+ /* Return false on the initial candidate (cur_pstate_candidate == -1) so the
+ * optimization phase runs at least one optimize iteration; otherwise the
+ * FAMS2/stage-3 setup in the optimize callback is skipped.
+ */
+ if (in_out->instance->scratch.pmo_dcn4.cur_pstate_candidate < 0)
return false;
- for (stream_index = 0; stream_index < in_out->base_display_config->display_config.num_streams; stream_index++) {
- if (s->pmo_dcn4.pstate_strategy_candidates[s->pmo_dcn4.cur_pstate_candidate].per_stream_pstate_method[stream_index] == dml2_pstate_method_vactive) {
- if (dcn4_get_minimum_reserved_time_us_for_planes(in_out->base_display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) < (int)in_out->instance->soc_bb->power_management_parameters.dram_clk_change_blackout_us ||
- dcn4_get_vactive_pstate_margin(in_out->base_display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) < (int)(MIN_VACTIVE_MARGIN_PCT * in_out->instance->soc_bb->power_management_parameters.dram_clk_change_blackout_us)) {
- p_state_supported = false;
- break;
- }
- } else {
- p_state_supported = false;
- break;
- }
- }
-
- return p_state_supported;
+ /* No-op: reserved time is guaranteed by the override and vactive p-state
+ * margin is now enforced in core mode support.
+ */
+ return true;
}
bool pmo_dcn42_initialize(struct dml2_pmo_initialize_in_out *in_out)
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 44/49] drm/amd/display: add DalForceMaxDisplayClock debug option to DML2
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (42 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 43/49] drm/amd/display: enforce UCLK pstate support in mode_support Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 45/49] drm/amd/display: Fixes for dcn42b_soc_bb.h Fangzhi Zuo
` (4 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Justin Chen, Charlene Liu,
Dillon Varone, Matthew Stewart
From: Justin Chen <Justin.Chen5@amd.com>
[Why & How]
need to apply the debug option check for max displayclk.
Reviewed-by: Charlene Liu <charlene.liu@amd.com>
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Justin Chen <Justin.Chen5@amd.com>
Signed-off-by: Matthew Stewart <matthew.stewart2@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c
index 4e3d54c92e32..aba7dd65c539 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c
@@ -155,6 +155,11 @@ static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_sta
context->bw_ctx.bw.dcn.clk.max_supported_dppclk_khz = in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.clk_values_khz[0];
}
+ if (dc->config.forced_clocks || dc->debug.max_disp_clk) {
+ context->bw_ctx.bw.dcn.clk.bw_dispclk_khz = context->bw_ctx.bw.dcn.clk.max_supported_dispclk_khz;
+ context->bw_ctx.bw.dcn.clk.bw_dppclk_khz = context->bw_ctx.bw.dcn.clk.max_supported_dppclk_khz;
+ }
+
/* get global mall allocation */
if (dc->res_pool->funcs->calculate_mall_ways_from_bytes) {
context->bw_ctx.bw.dcn.clk.num_ways = dc->res_pool->funcs->calculate_mall_ways_from_bytes(dc, context->bw_ctx.bw.dcn.mall_subvp_size_bytes);
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 45/49] drm/amd/display: Fixes for dcn42b_soc_bb.h
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (43 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 44/49] drm/amd/display: add DalForceMaxDisplayClock debug option to DML2 Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 46/49] drm/amd/display: Fix rounding errors in CalculatePrefetchSchedule Fangzhi Zuo
` (3 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Matthew Stewart, Dillon Varone
From: Matthew Stewart <Matthew.Stewart2@amd.com>
[why]
Some values were found to be incorrect.
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Matthew Stewart <Matthew.Stewart2@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h | 4 ++--
1 file changed, 2 insertions(+), 2 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 60ef56419846..5d844ff4b671 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
@@ -210,7 +210,7 @@ static const struct dml2_soc_bb dml2_socbb_dcn42b = {
.fabric_datapath_to_dcn_data_return_bytes = 32,
.return_bus_width_bytes = 64,
.hostvm_min_page_size_kbytes = 4,
- .gpuvm_min_page_size_kbytes = 256,
+ .gpuvm_min_page_size_kbytes = 4,
.gpuvm_max_page_table_levels = 1,
.hostvm_max_non_cached_page_table_levels = 2,
.phy_downspread_percent = 0.38,
@@ -236,7 +236,7 @@ static const struct dml2_ip_capabilities dml2_dcn42b_max_ip_caps = {
.config_return_buffer_segment_size_in_kbytes = 64,
.meta_fifo_size_in_kentries = 32,
.compressed_buffer_segment_size_in_kbytes = 64,
- .cursor_buffer_size = 24,
+ .cursor_buffer_size = 42,
.max_flip_time_us = 110,
.max_flip_time_lines = 50,
.hostvm_mode = 0,
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 46/49] drm/amd/display: Fix rounding errors in CalculatePrefetchSchedule
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (44 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 45/49] drm/amd/display: Fixes for dcn42b_soc_bb.h Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 47/49] drm/amd/display: plumb PMO per-plane pstate methods into mode_support Fangzhi Zuo
` (2 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Matthew Stewart, Dillon Varone
From: Matthew Stewart <Matthew.Stewart2@amd.com>
[why]
Rounding errors were causing mode validation to fail in some cases when
it should not. (IE. Increasing fclk from a lower value could lead to
validation failure, which should not happen.)
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Matthew Stewart <Matthew.Stewart2@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 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 da9a1c4e2bdc..4ca6a4521f11 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
@@ -5622,7 +5622,8 @@ static bool CalculatePrefetchSchedule(struct dml2_core_internal_scratch *scratch
+ 2 * (p->PixelPTEBytesPerRow * p->HostVMInefficiencyFactor + p->meta_row_bytes + tdlut_row_bytes)
+ *p->prefetch_sw_bytes)
/ (*p->Tpre_rounded - *p->Tno_bw);
- s->Tsw_est1 = *p->prefetch_sw_bytes / s->prefetch_bw1;
+ /* due to rounding, VM can clamp to +1/4, Row can clamp to +2/4, giving SW less time */
+ s->Tsw_est1 = *p->prefetch_sw_bytes / s->prefetch_bw1 - 3.0 * s->LineTime / 4.0;
} else
s->prefetch_bw1 = 0;
@@ -5646,7 +5647,8 @@ static bool CalculatePrefetchSchedule(struct dml2_core_internal_scratch *scratch
if (*p->Tpre_rounded - *p->Tno_bw - 2.0 * s->Tr0_trips_rounded > 0) {
s->prefetch_bw2 = (vm_bytes * p->HostVMInefficiencyFactor + *p->prefetch_sw_bytes) /
(*p->Tpre_rounded - *p->Tno_bw - 2.0 * s->Tr0_trips_rounded);
- s->Tsw_est2 = *p->prefetch_sw_bytes / s->prefetch_bw2;
+ /* due to rounding, VM can clamp to +1/4 */
+ s->Tsw_est2 = *p->prefetch_sw_bytes / s->prefetch_bw2 - 1.0 * s->LineTime / 4.0;
} else
s->prefetch_bw2 = 0;
@@ -5660,7 +5662,8 @@ static bool CalculatePrefetchSchedule(struct dml2_core_internal_scratch *scratch
if (*p->Tpre_rounded - s->Tvm_trips_rounded > 0) {
s->prefetch_bw3 = (2 * (p->PixelPTEBytesPerRow * p->HostVMInefficiencyFactor + p->meta_row_bytes + tdlut_row_bytes) + *p->prefetch_sw_bytes) /
(*p->Tpre_rounded - s->Tvm_trips_rounded);
- s->Tsw_est3 = *p->prefetch_sw_bytes / s->prefetch_bw3;
+ /* due to rounding, Row can clamp to +2/4 */
+ s->Tsw_est3 = *p->prefetch_sw_bytes / s->prefetch_bw3 - 2.0 * s->LineTime / 4.0;
} else
s->prefetch_bw3 = 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 47/49] drm/amd/display: plumb PMO per-plane pstate methods into mode_support
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (45 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 46/49] drm/amd/display: Fix rounding errors in CalculatePrefetchSchedule Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 48/49] drm/amd/display: dispatch compressed FRL cap check inside dml1_frl_cap_chk_inter Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 49/49] drm/amd/display: Promote DC to 3.2.391 Fangzhi Zuo
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Alexander Chechik,
Charlene Liu, Matthew Stewart
From: Alexander Chechik <alexander.chechik@amd.com>
[Why]
mode_support reads mode_lib.ms.uclk_pstate_switch_modes to enforce the
vactive pstate margin, but nothing populates it: the memset clears it and
the PMO selection is never passed in. The check always sees na and never
runs, so a plane with negative vactive margin can still pass and blank the
display.
[How]
Add a const per-plane pstate-method pointer to mode_support_ex, point it at
stage3.pstate_switch_modes when stage 3 has run (NULL otherwise), and copy
it into mode_lib.ms after the memset. Scope the support-required check to
the vactive methods it governs (vactive, fw_vactive_drr) so SVP, DRR and
vblank planes are not rejected by the vactive support flag.
Reviewed-by: Charlene Liu <charlene.liu@amd.com>
Signed-off-by: Alexander Chechik <alexander.chechik@amd.com>
Signed-off-by: Matthew Stewart <matthew.stewart2@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
.../dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c | 10 +++-------
.../dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 17 +++++++++++++----
.../src/dml2_core/dml2_core_shared_types.h | 1 +
3 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c
index 45f1bf5e14bf..6ea36aedcf20 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c
@@ -554,13 +554,9 @@ bool core_dcn4_mode_support(struct dml2_core_mode_support_in_out *in_out)
l->mode_support_ex_params.min_clk_index = in_out->min_clk_index;
l->mode_support_ex_params.out_evaluation_info = &in_out->mode_support_result.cfg_support_info.clean_me_up.support_info;
- for (i = 0; i < l->svp_expanded_display_cfg.num_planes; i++) {
- if (i < in_out->display_cfg->display_config.num_planes)
- core->clean_me_up.mode_lib.ms.uclk_pstate_switch_modes[i] =
- in_out->display_cfg->stage3.pstate_switch_modes[i];
- else
- core->clean_me_up.mode_lib.ms.uclk_pstate_switch_modes[i] = dml2_pstate_method_na;
- }
+ l->mode_support_ex_params.uclk_pstate_switch_modes =
+ in_out->display_cfg->stage3.performed ?
+ in_out->display_cfg->stage3.pstate_switch_modes : NULL;
result = dml2_core_calcs_mode_support_ex(&l->mode_support_ex_params);
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 4ca6a4521f11..09452f3e49db 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
@@ -8057,6 +8057,16 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out
memset(&mode_lib->ms, 0, sizeof(struct dml2_core_internal_mode_support));
mode_lib->ms.num_active_planes = display_cfg->num_planes;
+
+ for (k = 0; k < mode_lib->ms.num_active_planes; k++) {
+ if (in_out_params->uclk_pstate_switch_modes &&
+ !dml_is_phantom_pipe(&display_cfg->plane_descriptors[k]))
+ mode_lib->ms.uclk_pstate_switch_modes[k] =
+ in_out_params->uclk_pstate_switch_modes[k];
+ else
+ mode_lib->ms.uclk_pstate_switch_modes[k] = dml2_pstate_method_na;
+ }
+
get_stream_output_bpp(s->OutputBpp, display_cfg);
mode_lib->ms.state_idx = in_out_params->min_clk_index;
@@ -9644,14 +9654,13 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out
if (!display_cfg->overrides.all_streams_blanked) {
for (k = 0; k < mode_lib->ms.num_active_planes; k++) {
- if (mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_na)
+ if (mode_lib->ms.uclk_pstate_switch_modes[k] != dml2_pstate_method_vactive &&
+ mode_lib->ms.uclk_pstate_switch_modes[k] != dml2_pstate_method_fw_vactive_drr)
continue;
mode_lib->ms.support.global_dram_clock_change_support_required = true;
- if ((mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_vactive ||
- mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_fw_vactive_drr) &&
- mode_lib->ms.VActiveLatencyHidingMargin[k] < 0)
+ if (mode_lib->ms.VActiveLatencyHidingMargin[k] < 0)
mode_lib->ms.support.global_dram_clock_change_supported = false;
}
}
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h
index 131cec64aa48..b8af72392176 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
@@ -2333,6 +2333,7 @@ struct dml2_core_calcs_mode_support_ex {
int min_clk_index;
//unsigned int in_state_index;
struct dml2_core_internal_mode_support_info *out_evaluation_info;
+ const enum dml2_pstate_method *uclk_pstate_switch_modes;
};
struct core_display_cfg_support_info;
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 48/49] drm/amd/display: dispatch compressed FRL cap check inside dml1_frl_cap_chk_inter
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (46 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 47/49] drm/amd/display: plumb PMO per-plane pstate methods into mode_support Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 49/49] drm/amd/display: Promote DC to 3.2.391 Fangzhi Zuo
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Fangzhi Zuo
[why]
DSC over HDMI FRL (e.g. 4k144) was pruned by DML mode support
because the compressed FRL cap check was never reached.
dml32_TruncToValidBPP() validates the FRL output by calling
dml1_frl_cap_chk_inter() directly. The compressed-vs-uncompressed
dispatch (if params->compressed -> dml1_frl_cap_chk_compressed()) had
been moved up into the top-level dml1_frl_cap_chk() wrapper, leaving
dml1_frl_cap_chk_inter() as uncompressed-only. As a result a DSC stream
routed through TruncToValidBPP was validated against the full
uncompressed bandwidth, failed the cap check, and the mode was pruned
(vlevel == num_states).
[how]
Move the compressed dispatch back into dml1_frl_cap_chk_inter() so every
caller of _inter() (including TruncToValidBPP) honours params->compressed.
This matches the internal DAL tree.
Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Fangzhi Zuo <Jerry.Zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
---
drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c
index 260f58a97fc7..da2c92e5c02f 100644
--- a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c
+++ b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c
@@ -630,17 +630,17 @@ enum frl_cap_chk_result dml1_frl_cap_chk(struct frl_cap_chk_params *params)
{
struct frl_cap_chk_intermediates inter;
-#if defined (CONFIG_DRM_AMD_DC_FP)
- if (params->compressed)
- return dml1_frl_cap_chk_compressed(params, &inter);
-#endif
-
return dml1_frl_cap_chk_inter(params, &inter);
}
enum frl_cap_chk_result dml1_frl_cap_chk_inter(struct frl_cap_chk_params *params,
struct frl_cap_chk_intermediates *inter)
{
+#if defined (CONFIG_DRM_AMD_DC_FP)
+ if (params->compressed)
+ return dml1_frl_cap_chk_compressed(params, inter);
+#endif
+
return dml1_frl_cap_chk_uncompressed(params, inter);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 49/49] drm/amd/display: Promote DC to 3.2.391
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
` (47 preceding siblings ...)
2026-07-23 20:13 ` [PATCH 48/49] drm/amd/display: dispatch compressed FRL cap check inside dml1_frl_cap_chk_inter Fangzhi Zuo
@ 2026-07-23 20:13 ` Fangzhi Zuo
48 siblings, 0 replies; 51+ messages in thread
From: Fangzhi Zuo @ 2026-07-23 20:13 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, Santhosh, Ashwin
From: "Santhosh, Ashwin" <Ashwin.Santhosh@amd.com>
This DC patchset brings improvements in multiple areas. In summary, we have:
* Expand KUnit test
* dm refactor ongoing
* Fix apple 5k tiled monitor light up
* Fix frl dsc upstream mistake
* dcn42 fixes
Signed-off-by: Santhosh, Ashwin <Ashwin.Santhosh@amd.com>
Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@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 8eaa219132a6..63c0dec85697 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.390"
+#define DC_VER "3.2.391"
/**
* MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC
--
2.53.0
^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH 17/49] drm/amd/display: add cursor module
2026-07-23 20:13 ` [PATCH 17/49] drm/amd/display: add cursor module Fangzhi Zuo
@ 2026-08-03 12:48 ` Timur Kristóf
0 siblings, 0 replies; 51+ messages in thread
From: Timur Kristóf @ 2026-08-03 12:48 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, Fangzhi Zuo
On Thursday, July 23, 2026 10:13:25 PM Central European Summer Time Fangzhi
Zuo wrote:
> From: Alex Hung <alex.hung@amd.com>
>
> [WHAT]
> Group the cursor policy and validation helpers from amdgpu_dm.c
> into a dedicated amdgpu_dm_cursor.c and amdgpu_dm_cursor.h.
> The moved functions are:
>
> dm_check_cursor_fb, dm_check_native_cursor_state,
> dm_should_update_native_cursor, dm_get_oriented_plane_size,
> dm_get_plane_scale, dm_plane_color_pipeline_active,
> dm_crtc_get_cursor_mode.
>
> The cursor helpers exposed by amdgpu_dm_cursor.h are called from
> amdgpu_dm.c, so give the three that lacked a namespace prefix the
> standard amdgpu_dm_ prefix: amdgpu_dm_check_native_cursor_state,
> amdgpu_dm_should_update_native_cursor and
> amdgpu_dm_crtc_get_cursor_mode.
>
> The descending-zpos plane iterator shared by these functions and
> amdgpu_dm_atomic_check (the for_each_oldnew_plane_in_descending_zpos
> macro and its __get_next_zpos helper) moves to amdgpu_dm.h, with
> the helper renamed amdgpu_dm_get_next_zpos and made non-static.
>
> Relocate the corresponding KUnit tests into amdgpu_dm_cursor_test.c.
>
> No functional change.
Hi,
This commit regresses a recent bug fix from commit "Set native cursor mode for
disabled CRTCs". The amdgpu_dm_crtc_get_cursor_mode() function that was added
to amdgpu_dm_cursor.c is not the same as was removed from amdgpu_dm.c
I've submitted a fix here:
https://lists.freedesktop.org/archives/amd-gfx/2026-August/149759.html
Please review.
Thanks & best regards,
Timur
>
> Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
> Signed-off-by: Alex Hung <alex.hung@amd.com>
> Signed-off-by: Fangzhi Zuo <jerry.zuo@amd.com>
> Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
> ---
> .../gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +-
> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 436 +----------------
> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 24 +-
> .../amd/display/amdgpu_dm/amdgpu_dm_cursor.c | 443 ++++++++++++++++++
> .../amd/display/amdgpu_dm/amdgpu_dm_cursor.h | 52 ++
> .../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
> .../amdgpu_dm/tests/amdgpu_dm_cursor_test.c | 268 +++++++++++
> .../display/amdgpu_dm/tests/amdgpu_dm_test.c | 232 ---------
> 8 files changed, 796 insertions(+), 663 deletions(-)
> create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
> create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h
> create mode 100644
> drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
>
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
> b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index
> 5cb8c143e72f..d1faf2d8370f 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
> @@ -46,7 +46,8 @@ AMDGPUDM = \
> amdgpu_dm_audio.o \
> amdgpu_dm_dmub.o \
> amdgpu_dm_connector.o \
> - amdgpu_dm_freesync.o
> + amdgpu_dm_freesync.o \
> + amdgpu_dm_cursor.o
>
> ifdef CONFIG_DRM_AMD_DC_FP
> AMDGPUDM += dc_fpu.o
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
> b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index
> c36eb8a1d2d1..217ec822e93e 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
> @@ -72,6 +72,7 @@
> #include "amdgpu_dm_connector.h"
> #include "amdgpu_dm_pp_smu.h"
> #include "amdgpu_dm_freesync.h"
> +#include "amdgpu_dm_cursor.h"
>
> #include "ivsrcid/ivsrcid_vislands30.h"
>
> @@ -5760,133 +5761,6 @@ static bool should_reset_plane(struct
> drm_atomic_commit *state, return false;
> }
>
> -static int dm_check_cursor_fb(struct amdgpu_crtc *new_acrtc,
> - struct drm_plane_state
*new_plane_state,
> - struct drm_framebuffer *fb)
> -{
> - struct amdgpu_device *adev = drm_to_adev(new_acrtc->base.dev);
> - struct amdgpu_framebuffer *afb = to_amdgpu_framebuffer(fb);
> - unsigned int pitch;
> - bool linear;
> -
> - if (fb->width > new_acrtc->max_cursor_width ||
> - fb->height > new_acrtc->max_cursor_height) {
> - drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB size
%dx%d\n",
> - new_plane_state->fb->width,
> - new_plane_state->fb->height);
> - return -EINVAL;
> - }
> - if (new_plane_state->src_w != fb->width << 16 ||
> - new_plane_state->src_h != fb->height << 16) {
> - drm_dbg_atomic(adev_to_drm(adev), "Cropping not
supported for cursor
> plane\n"); - return -EINVAL;
> - }
> -
> - /* Pitch in pixels */
> - pitch = fb->pitches[0] / fb->format->cpp[0];
> -
> - if (fb->width != pitch) {
> - drm_dbg_atomic(adev_to_drm(adev), "Cursor FB width %d
doesn't match pitch
> %d", - fb->width, pitch);
> - return -EINVAL;
> - }
> -
> - switch (pitch) {
> - case 64:
> - case 128:
> - case 256:
> - /* FB pitch is supported by cursor plane */
> - break;
> - default:
> - drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB pitch
%d px\n", pitch);
> - return -EINVAL;
> - }
> -
> - /* Core DRM takes care of checking FB modifiers, so we only need to
> - * check tiling flags when the FB doesn't have a modifier.
> - */
> - if (!(fb->flags & DRM_MODE_FB_MODIFIERS)) {
> - if (adev->family == AMDGPU_FAMILY_GC_12_0_0) {
> - 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;
> - } else {
> - linear = AMDGPU_TILING_GET(afb->tiling_flags,
ARRAY_MODE) !=
> DC_ARRAY_2D_TILED_THIN1 && -
AMDGPU_TILING_GET(afb->tiling_flags,
> ARRAY_MODE) != DC_ARRAY_1D_TILED_THIN1 && -
> AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE) == 0;
> - }
> - if (!linear) {
> - drm_dbg_atomic(adev_to_drm(adev), "Cursor FB
not linear");
> - return -EINVAL;
> - }
> - }
> -
> - return 0;
> -}
> -
> -/*
> - * Helper function for checking the cursor in native mode
> - */
> -static int dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc,
> - struct drm_plane
*plane,
> - struct drm_plane_state
*new_plane_state,
> - bool enable)
> -{
> -
> - struct amdgpu_crtc *new_acrtc;
> - int ret;
> -
> - if (!enable || !new_plane_crtc ||
> - drm_atomic_plane_disabling(plane->state, new_plane_state))
> - return 0;
> -
> - new_acrtc = to_amdgpu_crtc(new_plane_crtc);
> -
> - if (new_plane_state->src_x != 0 || new_plane_state->src_y != 0) {
> - drm_dbg_atomic(new_plane_crtc->dev, "Cropping not
supported for cursor
> plane\n"); - return -EINVAL;
> - }
> -
> - if (new_plane_state->fb) {
> - ret = dm_check_cursor_fb(new_acrtc, new_plane_state,
> -
new_plane_state->fb);
> - if (ret)
> - return ret;
> - }
> -
> - return 0;
> -}
> -
> -STATIC_IFN_KUNIT bool dm_should_update_native_cursor(struct
> drm_atomic_commit *state, -
struct drm_crtc *old_plane_crtc,
> - struct
drm_crtc *new_plane_crtc,
> - bool
enable)
> -{
> - struct drm_crtc_state *old_crtc_state, *new_crtc_state;
> - struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state;
> -
> - if (!enable) {
> - if (old_plane_crtc == NULL)
> - return true;
> -
> - old_crtc_state = drm_atomic_get_old_crtc_state(
> - state, old_plane_crtc);
> - dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
> -
> - return dm_old_crtc_state->cursor_mode ==
DM_CURSOR_NATIVE_MODE;
> - } else {
> - if (new_plane_crtc == NULL)
> - return true;
> -
> - new_crtc_state = drm_atomic_get_new_crtc_state(
> - state, new_plane_crtc);
> - dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
> -
> - return dm_new_crtc_state->cursor_mode ==
DM_CURSOR_NATIVE_MODE;
> - }
> -}
> -EXPORT_IF_KUNIT(dm_should_update_native_cursor);
> -
> static int dm_update_plane_state(struct dc *dc,
> struct drm_atomic_commit *state,
> struct drm_plane *plane,
> @@ -5911,14 +5785,14 @@ static int dm_update_plane_state(struct dc *dc,
> dm_new_plane_state = to_dm_plane_state(new_plane_state);
> dm_old_plane_state = to_dm_plane_state(old_plane_state);
>
> - update_native_cursor = dm_should_update_native_cursor(state,
> -
old_plane_crtc,
> -
new_plane_crtc,
> -
enable);
> + update_native_cursor = amdgpu_dm_should_update_native_cursor(state,
> +
old_plane_crtc,
> +
new_plane_crtc,
> +
enable);
>
> if (plane->type == DRM_PLANE_TYPE_CURSOR && update_native_cursor)
{
> - ret = dm_check_native_cursor_state(new_plane_crtc,
plane,
> -
new_plane_state, enable);
> + ret =
amdgpu_dm_check_native_cursor_state(new_plane_crtc, plane,
> +
new_plane_state, enable);
> if (ret)
> return ret;
>
> @@ -6048,8 +5922,8 @@ static int dm_update_plane_state(struct dc *dc,
> out:
> /* If enabling cursor overlay failed, attempt fallback to native
mode */
> if (enable && ret == -EINVAL && plane->type ==
DRM_PLANE_TYPE_CURSOR) {
> - ret = dm_check_native_cursor_state(new_plane_crtc,
plane,
> -
new_plane_state, enable);
> + ret =
amdgpu_dm_check_native_cursor_state(new_plane_crtc, plane,
> +
new_plane_state, enable);
> if (ret)
> return ret;
>
> @@ -6059,37 +5933,6 @@ static int dm_update_plane_state(struct dc *dc,
> return ret;
> }
>
> -STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state
> *plane_state, - int
*src_w, int *src_h)
> -{
> - switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) {
> - case DRM_MODE_ROTATE_90:
> - case DRM_MODE_ROTATE_270:
> - *src_w = plane_state->src_h >> 16;
> - *src_h = plane_state->src_w >> 16;
> - break;
> - case DRM_MODE_ROTATE_0:
> - case DRM_MODE_ROTATE_180:
> - default:
> - *src_w = plane_state->src_w >> 16;
> - *src_h = plane_state->src_h >> 16;
> - break;
> - }
> -}
> -EXPORT_IF_KUNIT(dm_get_oriented_plane_size);
> -
> -STATIC_IFN_KUNIT void
> -dm_get_plane_scale(struct drm_plane_state *plane_state,
> - int *out_plane_scale_w, int *out_plane_scale_h)
> -{
> - int plane_src_w, plane_src_h;
> -
> - dm_get_oriented_plane_size(plane_state, &plane_src_w,
&plane_src_h);
> - *out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 /
> plane_src_w : 0; - *out_plane_scale_h = plane_src_h ? plane_state-
>crtc_h *
> 1000 / plane_src_h : 0; -}
> -EXPORT_IF_KUNIT(dm_get_plane_scale);
> -
> /*
> * The normalized_zpos value cannot be used by this iterator directly. It's
> only * calculated for enabled planes, potentially causing normalized_zpos
> collisions @@ -6097,7 +5940,7 @@ EXPORT_IF_KUNIT(dm_get_plane_scale);
> * so that the iterator will not generate the same object twice, or loop
> * indefinitely.
> */
> -static inline struct __drm_planes_state *__get_next_zpos(
> +struct __drm_planes_state *amdgpu_dm_get_next_zpos(
> struct drm_atomic_commit *state,
> struct __drm_planes_state *prev)
> {
> @@ -6135,19 +5978,6 @@ static inline struct __drm_planes_state
> *__get_next_zpos( return &state->planes[highest_i];
> }
>
> -/*
> - * Use the uniqueness of the plane's (zpos, drm obj ID) combination to
> iterate - * by descending zpos, as read from the new plane state. This is
> the same - * ordering as defined by drm_atomic_normalize_zpos().
> - */
> -#define for_each_oldnew_plane_in_descending_zpos(__state, plane,
> old_plane_state, new_plane_state) \ - for (struct __drm_planes_state *__i =
> __get_next_zpos((__state), NULL); \ - __i != NULL; __i =
> __get_next_zpos((__state), __i)) \
> - for_each_if(((plane) = __i->ptr,
\
> - (void)(plane) /* Only to avoid unused-
but-set-variable warning */,
> \ - (old_plane_state) = __i->old_state,
\
> - (new_plane_state) = __i->new_state, 1))
> -
> static int add_affected_mst_dsc_crtcs(struct drm_atomic_commit *state,
> struct drm_crtc *crtc) {
> struct drm_connector *connector;
> @@ -6178,244 +6008,6 @@ static int add_affected_mst_dsc_crtcs(struct
> drm_atomic_commit *state, struct dr return
> drm_dp_mst_add_affected_dsc_crtcs(state, &aconnector->mst_root->mst_mgr); }
>
> -/**
> - * DOC: Cursor Modes - Native vs Overlay
> - *
> - * In native mode, the cursor uses a integrated cursor pipe within each DCN
> hw - * plane. It does not require a dedicated hw plane to enable, but it is
> - * subjected to the same z-order and scaling as the hw plane. It also has
> format - * restrictions, a RGB cursor in native mode cannot be enabled
> within a non-RGB - * hw plane.
> - *
> - * In overlay mode, the cursor uses a separate DCN hw plane, and thus has
> its - * own scaling and z-pos. It also has no blending restrictions. It
> lends to a - * cursor behavior more akin to a DRM client's expectations.
> However, it does - * occupy an extra DCN plane, and therefore will only be
> used if a DCN plane is - * available.
> - */
> -
> -/**
> - * dm_plane_color_pipeline_active() - Check if a plane's color pipeline
> active. - * @state: DRM atomic state
> - * @plane: DRM plane to check
> - * @use_old: if true, inspect the old colorop states; otherwise the new
> ones - *
> - * A color pipeline may be selected (color_pipeline != NULL) but still is
> - * inactive if every colorop in the chain is bypassed. Only return
> - * true when at least one colorop has bypass == false, meaning the cursor
> - * would be subjected to the transformation in native mode.
> - *
> - * Return: true if the pipeline modifies pixels, false otherwise.
> - */
> -static bool dm_plane_color_pipeline_active(struct drm_atomic_commit *state,
> - struct drm_plane
*plane,
> - bool use_old)
> -{
> - struct drm_colorop *colorop;
> - struct drm_colorop_state *old_colorop_state, *new_colorop_state;
> - int i;
> -
> - for_each_oldnew_colorop_in_state(state, colorop,
old_colorop_state,
> new_colorop_state, i) { - struct drm_colorop_state *cstate =
use_old ?
> old_colorop_state : new_colorop_state; -
> - if (cstate->colorop->plane != plane)
> - continue;
> - if (!cstate->bypass)
> - return true;
> - }
> - return false;
> -}
> -
> -/**
> - * dm_crtc_get_cursor_mode() - Determine the required cursor mode on crtc
> - * @adev: amdgpu device
> - * @state: DRM atomic state
> - * @dm_crtc_state: amdgpu state for the CRTC containing the cursor
> - * @cursor_mode: Returns the required cursor mode on dm_crtc_state
> - *
> - * Get whether the cursor should be enabled in native mode, or overlay
> mode, on - * the dm_crtc_state.
> - *
> - * The cursor should be enabled in overlay mode if there exists an
> underlying - * plane - on which the cursor may be blended - that is either
> YUV formatted, - * scaled differently from the cursor, or has a color
> pipeline active. - *
> - * Since zpos info is required, drm_atomic_normalize_zpos must be called
> before - * calling this function.
> - *
> - * Return: 0 on success, or an error code if getting the cursor plane state
> - * failed.
> - */
> -static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
> - struct drm_atomic_commit
*state,
> - struct dm_crtc_state
*dm_crtc_state,
> - enum amdgpu_dm_cursor_mode
*cursor_mode)
> -{
> - struct drm_plane_state *old_plane_state, *plane_state,
*cursor_state;
> - struct drm_crtc_state *crtc_state = &dm_crtc_state->base;
> - struct drm_plane *plane;
> - bool consider_mode_change = false;
> - bool entire_crtc_covered = false;
> - bool cursor_changed = false;
> - int underlying_scale_w, underlying_scale_h;
> - int cursor_scale_w, cursor_scale_h;
> - int i;
> -
> - /* Overlay cursor not supported on HW before DCN
> - * DCN401/420 does not have the cursor-on-scaled-plane or
> cursor-on-yuv-plane restrictions - * as previous DCN generations, so
> enable native mode on DCN401/420 - *
> - * Always set native cursor mode when the CRTC is disabled,
> - * to make sure it doesn't cause atomic commits to fail when
> - * they are trying to disable the CRTC.
> - */
> - if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) ||
> - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) ||
> - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) ||
> - !dm_crtc_state->base.enable) {
> - *cursor_mode = DM_CURSOR_NATIVE_MODE;
> - return 0;
> - }
> -
> - /* Init cursor_mode to be the same as current */
> - *cursor_mode = dm_crtc_state->cursor_mode;
> -
> - /*
> - * Cursor mode can change if a plane's format changes, scale
changes, is
> - * enabled/disabled, z-order changes, or color management
properties
> change. - */
> - for_each_oldnew_plane_in_state(state, plane, old_plane_state,
plane_state,
> i) { - int new_scale_w, new_scale_h, old_scale_w,
old_scale_h;
> -
> - /* Only care about planes on this CRTC */
> - if ((drm_plane_mask(plane) & crtc_state->plane_mask) ==
0)
> - continue;
> -
> - if (plane->type == DRM_PLANE_TYPE_CURSOR)
> - cursor_changed = true;
> -
> - if (drm_atomic_plane_enabling(old_plane_state,
plane_state) ||
> - drm_atomic_plane_disabling(old_plane_state,
plane_state) ||
> - old_plane_state->fb->format != plane_state->fb-
>format) {
> - consider_mode_change = true;
> - break;
> - }
> -
> - dm_get_plane_scale(plane_state, &new_scale_w,
&new_scale_h);
> - dm_get_plane_scale(old_plane_state, &old_scale_w,
&old_scale_h);
> - if (new_scale_w != old_scale_w || new_scale_h !=
old_scale_h) {
> - consider_mode_change = true;
> - break;
> - }
> -
> - /*
> - * A non-cursor plane moving or resizing (without a
scale change)
> - * changes how much of the CRTC it covers. This can
create or
> - * remove a hole under the cursor and thus flip the
required
> - * cursor mode (native vs overlay), so its destination
rect must
> - * be re-evaluated too.
> - *
> - * The cursor plane itself is deliberately excluded:
the cursor
> - * mode depends on the underlying planes' coverage, not
on the
> - * cursor's position (see the entire_crtc_covered logic
below).
> - * Triggering on cursor movement would force every
legacy cursor
> - * update off its fast path, and in a cursor-only commit
- where
> - * the underlying planes are not part of the state -
the coverage
> - * loop would see no covering plane and misevaluate the
mode as
> - * overlay, regressing flip-vs-cursor-legacy.
> - */
> - if (plane->type != DRM_PLANE_TYPE_CURSOR &&
> - (old_plane_state->crtc_x != plane_state->crtc_x ||
> - old_plane_state->crtc_y != plane_state->crtc_y ||
> - old_plane_state->crtc_w != plane_state->crtc_w ||
> - old_plane_state->crtc_h != plane_state->crtc_h)) {
> - consider_mode_change = true;
> - break;
> - }
> -
> - if (dm_plane_color_pipeline_active(state, plane, true)
!=
> - dm_plane_color_pipeline_active(state, plane,
false)) {
> - consider_mode_change = true;
> - break;
> - }
> - }
> -
> - if (!consider_mode_change && !crtc_state->zpos_changed)
> - return 0;
> -
> - /*
> - * If no cursor change on this CRTC, and not enabled on this CRTC,
then
> - * no need to set cursor mode. This avoids needlessly locking the
cursor
> - * state.
> - */
> - if (!cursor_changed &&
> - !(drm_plane_mask(crtc_state->crtc->cursor) & crtc_state-
>plane_mask))
> { - return 0;
> - }
> -
> - cursor_state = drm_atomic_get_plane_state(state,
> -
crtc_state->crtc->cursor);
> - if (IS_ERR(cursor_state))
> - return PTR_ERR(cursor_state);
> -
> - /* Cursor is disabled */
> - if (!cursor_state->fb)
> - return 0;
> -
> - /* For all planes in descending z-order (all of which are below
cursor
> - * as per zpos definitions), check their scaling and format
> - */
> - for_each_oldnew_plane_in_descending_zpos(state, plane,
old_plane_state,
> plane_state) { -
> - /* Only care about non-cursor planes on this CRTC */
> - if ((drm_plane_mask(plane) & crtc_state->plane_mask) ==
0 ||
> - plane->type == DRM_PLANE_TYPE_CURSOR)
> - continue;
> -
> - /* Underlying plane is YUV format - use overlay cursor
*/
> - if (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))
> {
> - *cursor_mode = DM_CURSOR_OVERLAY_MODE;
> - return 0;
> - }
> -
> - dm_get_plane_scale(plane_state,
> - &underlying_scale_w,
&underlying_scale_h);
> - dm_get_plane_scale(cursor_state,
> - &cursor_scale_w,
&cursor_scale_h);
> -
> - /* Underlying plane has different scale - use overlay
cursor */
> - if (cursor_scale_w != underlying_scale_w &&
> - cursor_scale_h != underlying_scale_h) {
> - *cursor_mode = DM_CURSOR_OVERLAY_MODE;
> - return 0;
> - }
> -
> - /* If this plane covers the whole CRTC, no need to
check planes
> underneath */ - if (plane_state->crtc_x <= 0 &&
plane_state->crtc_y <= 0
> &&
> - plane_state->crtc_x + plane_state->crtc_w >=
> crtc_state->mode.hdisplay && - plane_state->crtc_y +
> plane_state->crtc_h >= crtc_state->mode.vdisplay) { -
entire_crtc_covered
> = true;
> - break;
> - }
> - }
> -
> - /* If planes do not cover the entire CRTC, use overlay mode to
enable
> - * cursor over holes
> - */
> - if (entire_crtc_covered)
> - *cursor_mode = DM_CURSOR_NATIVE_MODE;
> - else
> - *cursor_mode = DM_CURSOR_OVERLAY_MODE;
> -
> - return 0;
> -}
> -
> static bool amdgpu_dm_crtc_mem_type_changed(struct drm_device *dev,
> struct
drm_atomic_commit *state,
> struct
drm_crtc_state *crtc_state)
> @@ -6619,8 +6211,8 @@ static int amdgpu_dm_atomic_check(struct drm_device
> *dev, for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
> dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
>
> - ret = dm_crtc_get_cursor_mode(adev, state,
dm_new_crtc_state,
> -
&dm_new_crtc_state->cursor_mode);
> + ret = amdgpu_dm_crtc_get_cursor_mode(adev, state,
dm_new_crtc_state,
> +
&dm_new_crtc_state->cursor_mode);
> if (ret) {
> drm_dbg(dev, "Failed to determine cursor
mode: %pe\n", ERR_PTR(ret));
> goto fail;
> @@ -6754,8 +6346,8 @@ static int amdgpu_dm_atomic_check(struct drm_device
> *dev, }
>
> /* If HW can only do native cursor, check restrictions
again */
> - ret = dm_crtc_get_cursor_mode(adev, state,
dm_new_crtc_state,
> -
&required_cursor_mode);
> + ret = amdgpu_dm_crtc_get_cursor_mode(adev, state,
dm_new_crtc_state,
> +
&required_cursor_mode);
> if (ret) {
> drm_dbg_driver(crtc->dev,
> "[CRTC:%d:%s] Checking
cursor mode failed\n",
> 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
> 1ef92a28e3e9..1ddf5bc88a03 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
> @@ -1141,6 +1141,22 @@ void amdgpu_dm_emulated_link_detect(struct dc_link
> *link); void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device
> *adev, struct dc_sink *sink);
>
> +struct __drm_planes_state *amdgpu_dm_get_next_zpos(struct drm_atomic_commit
> *state, + struct
__drm_planes_state *prev);
> +
> +/*
> + * Use the uniqueness of the plane's (zpos, drm obj ID) combination to
> iterate + * by descending zpos, as read from the new plane state. This is
> the same + * ordering as defined by drm_atomic_normalize_zpos().
> + */
> +#define for_each_oldnew_plane_in_descending_zpos(__state, plane,
> old_plane_state, new_plane_state) \ + for (struct __drm_planes_state *__i =
> amdgpu_dm_get_next_zpos((__state), NULL); \ + __i != NULL; __i =
> amdgpu_dm_get_next_zpos((__state), __i)) \ +
for_each_if(((plane) =
> __i->ptr, \
> + (void)(plane) /* Only to avoid unused-
but-set-variable warning */,
> \ + (old_plane_state) = __i->old_state,
\
> + (new_plane_state) = __i->new_state, 1))
> +
> #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
> struct amdgpu_ip_block;
> bool dm_is_idle(struct amdgpu_ip_block *ip_block);
> @@ -1157,19 +1173,11 @@ int dm_crtc_get_scanoutpos(struct amdgpu_device
> *adev, int crtc, struct dm_atomic_state *dm_atomic_get_new_state(struct
> drm_atomic_commit *state); void dm_atomic_destroy_state(struct
> drm_private_obj *obj,
> struct drm_private_state *state);
> -bool dm_should_update_native_cursor(struct drm_atomic_commit *state,
> - struct drm_crtc
*old_plane_crtc,
> - struct drm_crtc
*new_plane_crtc,
> - bool enable);
> int dm_plane_layer_index_cmp(const void *a, const void *b);
> int fill_plane_color_attributes(const struct drm_plane_state *plane_state,
> const enum surface_pixel_format
format,
> enum dc_color_space
*color_space);
> bool modereset_required(struct drm_crtc_state *crtc_state);
> -void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
> - int *src_w, int *src_h);
> -void dm_get_plane_scale(struct drm_plane_state *plane_state,
> - int *out_plane_scale_w, int
*out_plane_scale_h);
> bool is_scaling_state_different(const struct dm_connector_state *dm_state,
> const struct dm_connector_state
*old_dm_state);
> void set_multisync_trigger_params(struct dc_stream_state *stream);
> 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 new file mode
> 100644
> index 000000000000..ab50d58896ab
> --- /dev/null
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c
> @@ -0,0 +1,443 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * Copyright 2026 Advanced Micro Devices, Inc.
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> "Software"), + * to deal in the Software without restriction, including
> without limitation + * the rights to use, copy, modify, merge, publish,
> distribute, sublicense, + * and/or sell copies of the Software, and to
> permit persons to whom the + * Software is furnished to do so, subject to
> the following conditions: + *
> + * The above copyright notice and this permission notice shall be included
> in + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
> OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR
> ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF
> CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH
> THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * Authors: AMD
> + *
> + */
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_blend.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_colorop.h>
> +
> +#include "dc.h"
> +#include "dal_asic_id.h"
> +#include "amdgpu.h"
> +#include "amdgpu_display.h"
> +#include "amdgpu_dm.h"
> +#include "amdgpu_dm_plane.h"
> +#include "amdgpu_dm_cursor.h"
> +#include "dm_helpers.h"
> +
> +static int dm_check_cursor_fb(struct amdgpu_crtc *new_acrtc,
> + struct drm_plane_state
*new_plane_state,
> + struct drm_framebuffer *fb)
> +{
> + struct amdgpu_device *adev = drm_to_adev(new_acrtc->base.dev);
> + struct amdgpu_framebuffer *afb = to_amdgpu_framebuffer(fb);
> + unsigned int pitch;
> + bool linear;
> +
> + if (fb->width > new_acrtc->max_cursor_width ||
> + fb->height > new_acrtc->max_cursor_height) {
> + drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB size
%dx%d\n",
> + new_plane_state->fb->width,
> + new_plane_state->fb->height);
> + return -EINVAL;
> + }
> + if (new_plane_state->src_w != fb->width << 16 ||
> + new_plane_state->src_h != fb->height << 16) {
> + drm_dbg_atomic(adev_to_drm(adev), "Cropping not
supported for cursor
> plane\n"); + return -EINVAL;
> + }
> +
> + /* Pitch in pixels */
> + pitch = fb->pitches[0] / fb->format->cpp[0];
> +
> + if (fb->width != pitch) {
> + drm_dbg_atomic(adev_to_drm(adev), "Cursor FB width %d
doesn't match pitch
> %d", + fb->width, pitch);
> + return -EINVAL;
> + }
> +
> + switch (pitch) {
> + case 64:
> + case 128:
> + case 256:
> + /* FB pitch is supported by cursor plane */
> + break;
> + default:
> + drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB pitch
%d px\n", pitch);
> + return -EINVAL;
> + }
> +
> + /* Core DRM takes care of checking FB modifiers, so we only need to
> + * 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;
> + } else {
> + linear = AMDGPU_TILING_GET(afb->tiling_flags,
ARRAY_MODE) !=
> DC_ARRAY_2D_TILED_THIN1 && +
AMDGPU_TILING_GET(afb->tiling_flags,
> ARRAY_MODE) != DC_ARRAY_1D_TILED_THIN1 && +
> AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE) == 0;
> + }
> + if (!linear) {
> + drm_dbg_atomic(adev_to_drm(adev), "Cursor FB
not linear");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Helper function for checking the cursor in native mode
> + */
> +int amdgpu_dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc,
> + struct drm_plane
*plane,
> + struct drm_plane_state
*new_plane_state,
> + bool enable)
> +{
> +
> + struct amdgpu_crtc *new_acrtc;
> + int ret;
> +
> + if (!enable || !new_plane_crtc ||
> + drm_atomic_plane_disabling(plane->state, new_plane_state))
> + return 0;
> +
> + new_acrtc = to_amdgpu_crtc(new_plane_crtc);
> +
> + if (new_plane_state->src_x != 0 || new_plane_state->src_y != 0) {
> + drm_dbg_atomic(new_plane_crtc->dev, "Cropping not
supported for cursor
> plane\n"); + return -EINVAL;
> + }
> +
> + if (new_plane_state->fb) {
> + ret = dm_check_cursor_fb(new_acrtc, new_plane_state,
> +
new_plane_state->fb);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +bool amdgpu_dm_should_update_native_cursor(struct drm_atomic_commit *state,
> + struct drm_crtc
*old_plane_crtc,
> + struct drm_crtc
*new_plane_crtc,
> + bool enable)
> +{
> + struct drm_crtc_state *old_crtc_state, *new_crtc_state;
> + struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state;
> +
> + if (!enable) {
> + if (old_plane_crtc == NULL)
> + return true;
> +
> + old_crtc_state = drm_atomic_get_old_crtc_state(
> + state, old_plane_crtc);
> + dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
> +
> + return dm_old_crtc_state->cursor_mode ==
DM_CURSOR_NATIVE_MODE;
> + }
> +
> + if (new_plane_crtc == NULL)
> + return true;
> +
> + new_crtc_state = drm_atomic_get_new_crtc_state(
> + state, new_plane_crtc);
> + dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
> +
> + return dm_new_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE;
> +}
> +EXPORT_IF_KUNIT(amdgpu_dm_should_update_native_cursor);
> +
> +STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state
> *plane_state, + int
*src_w, int *src_h)
> +{
> + switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) {
> + case DRM_MODE_ROTATE_90:
> + case DRM_MODE_ROTATE_270:
> + *src_w = plane_state->src_h >> 16;
> + *src_h = plane_state->src_w >> 16;
> + break;
> + case DRM_MODE_ROTATE_0:
> + case DRM_MODE_ROTATE_180:
> + default:
> + *src_w = plane_state->src_w >> 16;
> + *src_h = plane_state->src_h >> 16;
> + break;
> + }
> +}
> +EXPORT_IF_KUNIT(dm_get_oriented_plane_size);
> +
> +STATIC_IFN_KUNIT void
> +dm_get_plane_scale(struct drm_plane_state *plane_state,
> + int *out_plane_scale_w, int *out_plane_scale_h)
> +{
> + int plane_src_w, plane_src_h;
> +
> + dm_get_oriented_plane_size(plane_state, &plane_src_w,
&plane_src_h);
> + *out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 /
> plane_src_w : 0; + *out_plane_scale_h = plane_src_h ? plane_state-
>crtc_h *
> 1000 / plane_src_h : 0; +}
> +EXPORT_IF_KUNIT(dm_get_plane_scale);
> +
> +/**
> + * DOC: Cursor Modes - Native vs Overlay
> + *
> + * In native mode, the cursor uses a integrated cursor pipe within each DCN
> hw + * plane. It does not require a dedicated hw plane to enable, but it is
> + * subjected to the same z-order and scaling as the hw plane. It also has
> format + * restrictions, a RGB cursor in native mode cannot be enabled
> within a non-RGB + * hw plane.
> + *
> + * In overlay mode, the cursor uses a separate DCN hw plane, and thus has
> its + * own scaling and z-pos. It also has no blending restrictions. It
> lends to a + * cursor behavior more akin to a DRM client's expectations.
> However, it does + * occupy an extra DCN plane, and therefore will only be
> used if a DCN plane is + * available.
> + */
> +
> +/**
> + * dm_plane_color_pipeline_active() - Check if a plane's color pipeline
> active. + * @state: DRM atomic state
> + * @plane: DRM plane to check
> + * @use_old: if true, inspect the old colorop states; otherwise the new
> ones + *
> + * A color pipeline may be selected (color_pipeline != NULL) but still is
> + * inactive if every colorop in the chain is bypassed. Only return
> + * true when at least one colorop has bypass == false, meaning the cursor
> + * would be subjected to the transformation in native mode.
> + *
> + * Return: true if the pipeline modifies pixels, false otherwise.
> + */
> +static bool dm_plane_color_pipeline_active(struct drm_atomic_commit *state,
> + struct drm_plane
*plane,
> + bool use_old)
> +{
> + struct drm_colorop *colorop;
> + struct drm_colorop_state *old_colorop_state, *new_colorop_state;
> + int i;
> +
> + for_each_oldnew_colorop_in_state(state, colorop, old_colorop_state,
> new_colorop_state, i) { + struct drm_colorop_state *cstate =
use_old ?
> old_colorop_state : new_colorop_state; +
> + if (cstate->colorop->plane != plane)
> + continue;
> + if (!cstate->bypass)
> + return true;
> + }
> + return false;
> +}
> +
> +/**
> + * amdgpu_dm_crtc_get_cursor_mode() - Determine the required cursor mode on
> crtc + * @adev: amdgpu device
> + * @state: DRM atomic state
> + * @dm_crtc_state: amdgpu state for the CRTC containing the cursor
> + * @cursor_mode: Returns the required cursor mode on dm_crtc_state
> + *
> + * Get whether the cursor should be enabled in native mode, or overlay
> mode, on + * the dm_crtc_state.
> + *
> + * The cursor should be enabled in overlay mode if there exists an
> underlying + * plane - on which the cursor may be blended - that is either
> YUV formatted, + * scaled differently from the cursor, or has a color
> pipeline active. + *
> + * Since zpos info is required, drm_atomic_normalize_zpos must be called
> before + * calling this function.
> + *
> + * Return: 0 on success, or an error code if getting the cursor plane state
> + * failed.
> + */
> +int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
> + struct drm_atomic_commit
*state,
> + struct dm_crtc_state
*dm_crtc_state,
> + enum amdgpu_dm_cursor_mode
*cursor_mode)
> +{
> + struct drm_plane_state *old_plane_state, *plane_state,
*cursor_state;
> + struct drm_crtc_state *crtc_state = &dm_crtc_state->base;
> + struct drm_plane *plane;
> + bool consider_mode_change = false;
> + bool entire_crtc_covered = false;
> + bool cursor_changed = false;
> + int underlying_scale_w, underlying_scale_h;
> + int cursor_scale_w, cursor_scale_h;
> + int i;
> +
> + /* Overlay cursor not supported on HW before DCN
> + * DCN401/420 does not have the cursor-on-scaled-plane or
> cursor-on-yuv-plane restrictions + * as previous DCN generations, so
> enable native mode on DCN401/420 + */
> + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) ||
> + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) ||
> +#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
> + *cursor_mode = DM_CURSOR_NATIVE_MODE;
> + return 0;
> + }
> +
> + /* Init cursor_mode to be the same as current */
> + *cursor_mode = dm_crtc_state->cursor_mode;
> +
> + /*
> + * Cursor mode can change if a plane's format changes, scale
changes, is
> + * enabled/disabled, z-order changes, or color management
properties
> change. + */
> + for_each_oldnew_plane_in_state(state, plane, old_plane_state,
plane_state,
> i) { + int new_scale_w, new_scale_h, old_scale_w,
old_scale_h;
> +
> + /* Only care about planes on this CRTC */
> + if ((drm_plane_mask(plane) & crtc_state->plane_mask) ==
0)
> + continue;
> +
> + if (plane->type == DRM_PLANE_TYPE_CURSOR)
> + cursor_changed = true;
> +
> + if (drm_atomic_plane_enabling(old_plane_state,
plane_state) ||
> + drm_atomic_plane_disabling(old_plane_state,
plane_state) ||
> + old_plane_state->fb->format != plane_state->fb-
>format) {
> + consider_mode_change = true;
> + break;
> + }
> +
> + dm_get_plane_scale(plane_state, &new_scale_w,
&new_scale_h);
> + dm_get_plane_scale(old_plane_state, &old_scale_w,
&old_scale_h);
> + if (new_scale_w != old_scale_w || new_scale_h !=
old_scale_h) {
> + consider_mode_change = true;
> + break;
> + }
> +
> + /*
> + * A non-cursor plane moving or resizing (without a
scale change)
> + * changes how much of the CRTC it covers. This can
create or
> + * remove a hole under the cursor and thus flip the
required
> + * cursor mode (native vs overlay), so its destination
rect must
> + * be re-evaluated too.
> + *
> + * The cursor plane itself is deliberately excluded: the
cursor
> + * mode depends on the underlying planes' coverage, not
on the
> + * cursor's position (see the entire_crtc_covered logic
below).
> + * Triggering on cursor movement would force every
legacy cursor
> + * update off its fast path, and in a cursor-only commit
- where
> + * the underlying planes are not part of the state - the
coverage
> + * loop would see no covering plane and misevaluate the
mode as
> + * overlay, regressing flip-vs-cursor-legacy.
> + */
> + if (plane->type != DRM_PLANE_TYPE_CURSOR &&
> + (old_plane_state->crtc_x != plane_state->crtc_x ||
> + old_plane_state->crtc_y != plane_state->crtc_y ||
> + old_plane_state->crtc_w != plane_state->crtc_w ||
> + old_plane_state->crtc_h != plane_state->crtc_h)) {
> + consider_mode_change = true;
> + break;
> + }
> +
> + if (dm_plane_color_pipeline_active(state, plane, true) !
=
> + dm_plane_color_pipeline_active(state, plane, false))
{
> + consider_mode_change = true;
> + break;
> + }
> + }
> +
> + if (!consider_mode_change && !crtc_state->zpos_changed)
> + return 0;
> +
> + /*
> + * If no cursor change on this CRTC, and not enabled on this CRTC,
then
> + * no need to set cursor mode. This avoids needlessly locking the
cursor
> + * state.
> + */
> + if (!cursor_changed &&
> + !(drm_plane_mask(crtc_state->crtc->cursor) & crtc_state-
>plane_mask))
> { + return 0;
> + }
> +
> + cursor_state = drm_atomic_get_plane_state(state,
> +
crtc_state->crtc->cursor);
> + if (IS_ERR(cursor_state))
> + return PTR_ERR(cursor_state);
> +
> + /* Cursor is disabled */
> + if (!cursor_state->fb)
> + return 0;
> +
> + /* For all planes in descending z-order (all of which are below
cursor
> + * as per zpos definitions), check their scaling and format
> + */
> + for_each_oldnew_plane_in_descending_zpos(state, plane,
old_plane_state,
> plane_state) { +
> + /* Only care about non-cursor planes on this CRTC */
> + if ((drm_plane_mask(plane) & crtc_state->plane_mask) ==
0 ||
> + plane->type == DRM_PLANE_TYPE_CURSOR)
> + continue;
> +
> + /* Underlying plane is YUV format - use overlay cursor
*/
> + if (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))
> {
> + *cursor_mode = DM_CURSOR_OVERLAY_MODE;
> + return 0;
> + }
> +
> + dm_get_plane_scale(plane_state,
> + &underlying_scale_w,
&underlying_scale_h);
> + dm_get_plane_scale(cursor_state,
> + &cursor_scale_w,
&cursor_scale_h);
> +
> + /* Underlying plane has different scale - use overlay
cursor */
> + if (cursor_scale_w != underlying_scale_w &&
> + cursor_scale_h != underlying_scale_h) {
> + *cursor_mode = DM_CURSOR_OVERLAY_MODE;
> + return 0;
> + }
> +
> + /* If this plane covers the whole CRTC, no need to check
planes
> underneath */ + if (plane_state->crtc_x <= 0 &&
plane_state->crtc_y <= 0
> &&
> + plane_state->crtc_x + plane_state->crtc_w >=
> crtc_state->mode.hdisplay && + plane_state->crtc_y +
> plane_state->crtc_h >= crtc_state->mode.vdisplay) { +
entire_crtc_covered
> = true;
> + break;
> + }
> + }
> +
> + /* If planes do not cover the entire CRTC, use overlay mode to
enable
> + * cursor over holes
> + */
> + if (entire_crtc_covered)
> + *cursor_mode = DM_CURSOR_NATIVE_MODE;
> + else
> + *cursor_mode = DM_CURSOR_OVERLAY_MODE;
> +
> + return 0;
> +}
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h
> b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h new file mode
> 100644
> index 000000000000..5f619e4474aa
> --- /dev/null
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright 2026 Advanced Micro Devices, Inc.
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> "Software"), + * to deal in the Software without restriction, including
> without limitation + * the rights to use, copy, modify, merge, publish,
> distribute, sublicense, + * and/or sell copies of the Software, and to
> permit persons to whom the + * Software is furnished to do so, subject to
> the following conditions: + *
> + * The above copyright notice and this permission notice shall be included
> in + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
> OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR
> ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF
> CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH
> THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * Authors: AMD
> + *
> + */
> +
> +#ifndef __AMDGPU_DM_CURSOR_H__
> +#define __AMDGPU_DM_CURSOR_H__
> +
> +int amdgpu_dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc,
> + struct drm_plane
*plane,
> + struct drm_plane_state
*new_plane_state,
> + bool enable);
> +
> +bool amdgpu_dm_should_update_native_cursor(struct drm_atomic_commit *state,
> + struct drm_crtc
*old_plane_crtc,
> + struct drm_crtc
*new_plane_crtc,
> + bool enable);
> +
> +int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev,
> + struct drm_atomic_commit
*state,
> + struct dm_crtc_state
*dm_crtc_state,
> + enum amdgpu_dm_cursor_mode
*cursor_mode);
> +
> +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
> +void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
> + int *src_w, int *src_h);
> +void dm_get_plane_scale(struct drm_plane_state *plane_state,
> + int *out_plane_scale_w, int
*out_plane_scale_h);
> +#endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */
> +
> +#endif /* __AMDGPU_DM_CURSOR_H__ */
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
> b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index
> e58ffc4dcd84..3a51fccc2a5e 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
> @@ -30,6 +30,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) +=
> amdgpu_dm_mst_types_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) +=
> amdgpu_dm_pp_smu_test.o
> obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o
> obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_freesync_test.o
> +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_cursor_test.o
> obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o
> obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o
> obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o
> diff --git
> a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
> b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c new
> file mode 100644
> index 000000000000..3da76794ece9
> --- /dev/null
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c
> @@ -0,0 +1,268 @@
> +// SPDX-License-Identifier: GPL-2.0 OR MIT
> +/*
> + * KUnit tests for amdgpu_dm_cursor.c
> + *
> + * Copyright 2026 Advanced Micro Devices, Inc.
> + */
> +
> +#include <kunit/test.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_blend.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_plane.h>
> +
> +#include "dc.h"
> +#include "amdgpu.h"
> +#include "amdgpu_mode.h"
> +#include "amdgpu_dm.h"
> +#include "amdgpu_dm_cursor.h"
> +
> +/* Tests for amdgpu_dm_should_update_native_cursor() */
> +
> +/**
> + * dm_test_should_update_native_cursor_without_crtc - Test NULL crtc cases
> update native cursor + * @test: The KUnit test context
> + */
> +static void dm_test_should_update_native_cursor_without_crtc(struct kunit
> *test) +{
> + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_update_native_cursor(NULL,
NULL,
> NULL, false)); + KUNIT_EXPECT_TRUE(test,
> amdgpu_dm_should_update_native_cursor(NULL, NULL, NULL, true)); +}
> +
> +/**
> + * dm_test_should_update_native_cursor_disable_native - Test disable path
> reads old crtc cursor mode + * @test: The KUnit test context
> + */
> +static void dm_test_should_update_native_cursor_disable_native(struct kunit
> *test) +{
> + struct dm_crtc_state *dm_crtc_state;
> + struct drm_atomic_commit *state;
> + struct drm_crtc *crtc;
> +
> + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, state);
> +
> + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, crtc);
> +
> + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state),
GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
> +
> + state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs),
GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, state->crtcs);
> +
> + crtc->index = 0;
> + dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE;
> + state->crtcs[0].old_state = &dm_crtc_state->base;
> +
> + KUNIT_EXPECT_TRUE(test,
> +
amdgpu_dm_should_update_native_cursor(state, crtc, NULL, false));
> +}
> +
> +/**
> + * dm_test_should_update_native_cursor_enable_overlay - Test enable path
> reads new crtc cursor mode + * @test: The KUnit test context
> + */
> +static void dm_test_should_update_native_cursor_enable_overlay(struct kunit
> *test) +{
> + struct dm_crtc_state *dm_crtc_state;
> + struct drm_atomic_commit *state;
> + struct drm_crtc *crtc;
> +
> + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, state);
> +
> + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, crtc);
> +
> + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state),
GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
> +
> + state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs),
GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, state->crtcs);
> +
> + crtc->index = 0;
> + dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE;
> + state->crtcs[0].new_state = &dm_crtc_state->base;
> +
> + KUNIT_EXPECT_FALSE(test,
> +
amdgpu_dm_should_update_native_cursor(state, NULL, crtc, true));
> +}
> +
> +/* Tests for dm_get_oriented_plane_size() */
> +
> +/**
> + * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0
> + * @test: The KUnit test context
> + */
> +static void dm_test_oriented_plane_size_rotate_0(struct kunit *test)
> +{
> + struct drm_plane_state plane_state = { 0 };
> + int src_w = 0;
> + int src_h = 0;
> +
> + plane_state.rotation = DRM_MODE_ROTATE_0;
> + plane_state.src_w = 1920 << 16;
> + plane_state.src_h = 1080 << 16;
> +
> + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
> +
> + KUNIT_EXPECT_EQ(test, src_w, 1920);
> + KUNIT_EXPECT_EQ(test, src_h, 1080);
> +}
> +
> +/**
> + * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate
> 90 + * @test: The KUnit test context
> + */
> +static void dm_test_oriented_plane_size_rotate_90(struct kunit *test)
> +{
> + struct drm_plane_state plane_state = { 0 };
> + int src_w = 0;
> + int src_h = 0;
> +
> + plane_state.rotation = DRM_MODE_ROTATE_90;
> + plane_state.src_w = 1920 << 16;
> + plane_state.src_h = 1080 << 16;
> +
> + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
> +
> + KUNIT_EXPECT_EQ(test, src_w, 1080);
> + KUNIT_EXPECT_EQ(test, src_h, 1920);
> +}
> +
> +/**
> + * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate
> 180 + * @test: The KUnit test context
> + */
> +static void dm_test_oriented_plane_size_rotate_180(struct kunit *test)
> +{
> + struct drm_plane_state plane_state = { 0 };
> + int src_w = 0;
> + int src_h = 0;
> +
> + plane_state.rotation = DRM_MODE_ROTATE_180;
> + plane_state.src_w = 1920 << 16;
> + plane_state.src_h = 1080 << 16;
> +
> + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
> +
> + KUNIT_EXPECT_EQ(test, src_w, 1920);
> + KUNIT_EXPECT_EQ(test, src_h, 1080);
> +}
> +
> +/**
> + * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate
> 270 + * @test: The KUnit test context
> + */
> +static void dm_test_oriented_plane_size_rotate_270(struct kunit *test)
> +{
> + struct drm_plane_state plane_state = { 0 };
> + int src_w = 0;
> + int src_h = 0;
> +
> + plane_state.rotation = DRM_MODE_ROTATE_270;
> + plane_state.src_w = 1920 << 16;
> + plane_state.src_h = 1080 << 16;
> +
> + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
> +
> + KUNIT_EXPECT_EQ(test, src_w, 1080);
> + KUNIT_EXPECT_EQ(test, src_h, 1920);
> +}
> +
> +/* Tests for dm_get_plane_scale() */
> +
> +/**
> + * dm_test_get_plane_scale_identity - Test Get plane scale identity
> + * @test: The KUnit test context
> + */
> +static void dm_test_get_plane_scale_identity(struct kunit *test)
> +{
> + struct drm_plane_state plane_state = { 0 };
> + int scale_w = 0;
> + int scale_h = 0;
> +
> + plane_state.rotation = DRM_MODE_ROTATE_0;
> + plane_state.src_w = 1920 << 16;
> + plane_state.src_h = 1080 << 16;
> + plane_state.crtc_w = 1920;
> + plane_state.crtc_h = 1080;
> +
> + dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
> +
> + KUNIT_EXPECT_EQ(test, scale_w, 1000);
> + KUNIT_EXPECT_EQ(test, scale_h, 1000);
> +}
> +
> +/**
> + * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate
> 90 identity + * @test: The KUnit test context
> + */
> +static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test)
> +{
> + struct drm_plane_state plane_state = { 0 };
> + int scale_w = 0;
> + int scale_h = 0;
> +
> + plane_state.rotation = DRM_MODE_ROTATE_90;
> + plane_state.src_w = 1920 << 16;
> + plane_state.src_h = 1080 << 16;
> + plane_state.crtc_w = 1080;
> + plane_state.crtc_h = 1920;
> +
> + dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
> +
> + KUNIT_EXPECT_EQ(test, scale_w, 1000);
> + KUNIT_EXPECT_EQ(test, scale_h, 1000);
> +}
> +
> +/**
> + * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src
> width + * @test: The KUnit test context
> + */
> +static void dm_test_get_plane_scale_zero_src_width(struct kunit *test)
> +{
> + struct drm_plane_state plane_state = { 0 };
> + int scale_w = 0;
> + int scale_h = 0;
> +
> + plane_state.rotation = DRM_MODE_ROTATE_0;
> + plane_state.src_w = 0;
> + plane_state.src_h = 1080 << 16;
> + plane_state.crtc_w = 100;
> + plane_state.crtc_h = 200;
> +
> + dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
> +
> + KUNIT_EXPECT_EQ(test, scale_w, 0);
> + KUNIT_EXPECT_EQ(test, scale_h, 185);
> +}
> +
> +static struct kunit_case amdgpu_dm_cursor_tests[] = {
> + /* amdgpu_dm_should_update_native_cursor */
> + KUNIT_CASE(dm_test_should_update_native_cursor_without_crtc),
> + KUNIT_CASE(dm_test_should_update_native_cursor_disable_native),
> + KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay),
> + /* dm_get_oriented_plane_size */
> + KUNIT_CASE(dm_test_oriented_plane_size_rotate_0),
> + KUNIT_CASE(dm_test_oriented_plane_size_rotate_90),
> + KUNIT_CASE(dm_test_oriented_plane_size_rotate_180),
> + KUNIT_CASE(dm_test_oriented_plane_size_rotate_270),
> + /* dm_get_plane_scale */
> + KUNIT_CASE(dm_test_get_plane_scale_identity),
> + KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity),
> + KUNIT_CASE(dm_test_get_plane_scale_zero_src_width),
> + {}
> +};
> +
> +static struct kunit_suite amdgpu_dm_cursor_test_suite = {
> + .name = "amdgpu_dm_cursor",
> + .test_cases = amdgpu_dm_cursor_tests,
> +};
> +
> +kunit_test_suite(amdgpu_dm_cursor_test_suite);
> +
> +MODULE_AUTHOR("AMD");
> +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_cursor");
> +MODULE_LICENSE("Dual MIT/GPL");
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
> b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index
> 428d24259198..3c10eec9b1e0 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
> @@ -227,76 +227,6 @@ static void dm_test_atomic_get_new_state_match(struct
> kunit *test) KUNIT_EXPECT_PTR_EQ(test, dm_atomic_get_new_state(state),
> dm_state); }
>
> -/**
> - * dm_test_should_update_native_cursor_without_crtc - Test NULL crtc cases
> update native cursor - * @test: The KUnit test context
> - */
> -static void dm_test_should_update_native_cursor_without_crtc(struct kunit
> *test) -{
> - KUNIT_EXPECT_TRUE(test, dm_should_update_native_cursor(NULL, NULL,
NULL,
> false)); - KUNIT_EXPECT_TRUE(test, dm_should_update_native_cursor(NULL,
> NULL, NULL, true)); -}
> -
> -/**
> - * dm_test_should_update_native_cursor_disable_native - Test disable path
> reads old crtc cursor mode - * @test: The KUnit test context
> - */
> -static void dm_test_should_update_native_cursor_disable_native(struct kunit
> *test) -{
> - struct dm_crtc_state *dm_crtc_state;
> - struct drm_atomic_commit *state;
> - struct drm_crtc *crtc;
> -
> - state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
> - KUNIT_ASSERT_NOT_NULL(test, state);
> -
> - crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
> - KUNIT_ASSERT_NOT_NULL(test, crtc);
> -
> - dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state),
GFP_KERNEL);
> - KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
> -
> - state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs),
GFP_KERNEL);
> - KUNIT_ASSERT_NOT_NULL(test, state->crtcs);
> -
> - crtc->index = 0;
> - dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE;
> - state->crtcs[0].old_state = &dm_crtc_state->base;
> -
> - KUNIT_EXPECT_TRUE(test,
> - dm_should_update_native_cursor(state,
crtc, NULL, false));
> -}
> -
> -/**
> - * dm_test_should_update_native_cursor_enable_overlay - Test enable path
> reads new crtc cursor mode - * @test: The KUnit test context
> - */
> -static void dm_test_should_update_native_cursor_enable_overlay(struct kunit
> *test) -{
> - struct dm_crtc_state *dm_crtc_state;
> - struct drm_atomic_commit *state;
> - struct drm_crtc *crtc;
> -
> - state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
> - KUNIT_ASSERT_NOT_NULL(test, state);
> -
> - crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
> - KUNIT_ASSERT_NOT_NULL(test, crtc);
> -
> - dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state),
GFP_KERNEL);
> - KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state);
> -
> - state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs),
GFP_KERNEL);
> - KUNIT_ASSERT_NOT_NULL(test, state->crtcs);
> -
> - crtc->index = 0;
> - dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE;
> - state->crtcs[0].new_state = &dm_crtc_state->base;
> -
> - KUNIT_EXPECT_FALSE(test,
> - dm_should_update_native_cursor(state,
NULL, crtc, true));
> -}
> -
> /**
> * dm_test_atomic_destroy_state_no_context - Test destroying DM atomic
> state without a DC context * @test: The KUnit test context
> @@ -599,156 +529,6 @@ static void
> dm_test_modereset_not_required_when_inactive_without_modeset(struct
> KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state));
> }
>
> -/* Tests for dm_get_oriented_plane_size() */
> -
> -/**
> - * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0
> - * @test: The KUnit test context
> - */
> -static void dm_test_oriented_plane_size_rotate_0(struct kunit *test)
> -{
> - struct drm_plane_state plane_state = { 0 };
> - int src_w = 0;
> - int src_h = 0;
> -
> - plane_state.rotation = DRM_MODE_ROTATE_0;
> - plane_state.src_w = 1920 << 16;
> - plane_state.src_h = 1080 << 16;
> -
> - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
> -
> - KUNIT_EXPECT_EQ(test, src_w, 1920);
> - KUNIT_EXPECT_EQ(test, src_h, 1080);
> -}
> -
> -/**
> - * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate
> 90 - * @test: The KUnit test context
> - */
> -static void dm_test_oriented_plane_size_rotate_90(struct kunit *test)
> -{
> - struct drm_plane_state plane_state = { 0 };
> - int src_w = 0;
> - int src_h = 0;
> -
> - plane_state.rotation = DRM_MODE_ROTATE_90;
> - plane_state.src_w = 1920 << 16;
> - plane_state.src_h = 1080 << 16;
> -
> - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
> -
> - KUNIT_EXPECT_EQ(test, src_w, 1080);
> - KUNIT_EXPECT_EQ(test, src_h, 1920);
> -}
> -
> -/**
> - * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate
> 180 - * @test: The KUnit test context
> - */
> -static void dm_test_oriented_plane_size_rotate_180(struct kunit *test)
> -{
> - struct drm_plane_state plane_state = { 0 };
> - int src_w = 0;
> - int src_h = 0;
> -
> - plane_state.rotation = DRM_MODE_ROTATE_180;
> - plane_state.src_w = 1920 << 16;
> - plane_state.src_h = 1080 << 16;
> -
> - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
> -
> - KUNIT_EXPECT_EQ(test, src_w, 1920);
> - KUNIT_EXPECT_EQ(test, src_h, 1080);
> -}
> -
> -/**
> - * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate
> 270 - * @test: The KUnit test context
> - */
> -static void dm_test_oriented_plane_size_rotate_270(struct kunit *test)
> -{
> - struct drm_plane_state plane_state = { 0 };
> - int src_w = 0;
> - int src_h = 0;
> -
> - plane_state.rotation = DRM_MODE_ROTATE_270;
> - plane_state.src_w = 1920 << 16;
> - plane_state.src_h = 1080 << 16;
> -
> - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
> -
> - KUNIT_EXPECT_EQ(test, src_w, 1080);
> - KUNIT_EXPECT_EQ(test, src_h, 1920);
> -}
> -
> -/* Tests for dm_get_plane_scale() */
> -
> -/**
> - * dm_test_get_plane_scale_identity - Test Get plane scale identity
> - * @test: The KUnit test context
> - */
> -static void dm_test_get_plane_scale_identity(struct kunit *test)
> -{
> - struct drm_plane_state plane_state = { 0 };
> - int scale_w = 0;
> - int scale_h = 0;
> -
> - plane_state.rotation = DRM_MODE_ROTATE_0;
> - plane_state.src_w = 1920 << 16;
> - plane_state.src_h = 1080 << 16;
> - plane_state.crtc_w = 1920;
> - plane_state.crtc_h = 1080;
> -
> - dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
> -
> - KUNIT_EXPECT_EQ(test, scale_w, 1000);
> - KUNIT_EXPECT_EQ(test, scale_h, 1000);
> -}
> -
> -/**
> - * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate
> 90 identity - * @test: The KUnit test context
> - */
> -static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test)
> -{
> - struct drm_plane_state plane_state = { 0 };
> - int scale_w = 0;
> - int scale_h = 0;
> -
> - plane_state.rotation = DRM_MODE_ROTATE_90;
> - plane_state.src_w = 1920 << 16;
> - plane_state.src_h = 1080 << 16;
> - plane_state.crtc_w = 1080;
> - plane_state.crtc_h = 1920;
> -
> - dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
> -
> - KUNIT_EXPECT_EQ(test, scale_w, 1000);
> - KUNIT_EXPECT_EQ(test, scale_h, 1000);
> -}
> -
> -/**
> - * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src
> width - * @test: The KUnit test context
> - */
> -static void dm_test_get_plane_scale_zero_src_width(struct kunit *test)
> -{
> - struct drm_plane_state plane_state = { 0 };
> - int scale_w = 0;
> - int scale_h = 0;
> -
> - plane_state.rotation = DRM_MODE_ROTATE_0;
> - plane_state.src_w = 0;
> - plane_state.src_h = 1080 << 16;
> - plane_state.crtc_w = 100;
> - plane_state.crtc_h = 200;
> -
> - dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
> -
> - KUNIT_EXPECT_EQ(test, scale_w, 0);
> - KUNIT_EXPECT_EQ(test, scale_h, 185);
> -}
> -
> /* Tests for is_scaling_state_different() */
>
> /**
> @@ -1386,9 +1166,6 @@ static struct kunit_case amdgpu_dm_tests[] = {
> KUNIT_CASE(dm_test_crtc_get_scanoutpos_no_stream),
> KUNIT_CASE(dm_test_atomic_get_new_state_empty),
> KUNIT_CASE(dm_test_atomic_get_new_state_match),
> - KUNIT_CASE(dm_test_should_update_native_cursor_without_crtc),
> - KUNIT_CASE(dm_test_should_update_native_cursor_disable_native),
> - KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay),
> KUNIT_CASE(dm_test_atomic_destroy_state_no_context),
> /* dm_plane_layer_index_cmp */
> KUNIT_CASE(dm_test_plane_layer_index_cmp_equal),
> @@ -1407,15 +1184,6 @@ static struct kunit_case amdgpu_dm_tests[] = {
> KUNIT_CASE(dm_test_modereset_required_when_inactive_and_modeset),
>
KUNIT_CASE(dm_test_modereset_not_required_when_active_and_modeset),
>
KUNIT_CASE(dm_test_modereset_not_required_when_inactive_without_modeset),
> - /* dm_get_oriented_plane_size */
> - KUNIT_CASE(dm_test_oriented_plane_size_rotate_0),
> - KUNIT_CASE(dm_test_oriented_plane_size_rotate_90),
> - KUNIT_CASE(dm_test_oriented_plane_size_rotate_180),
> - KUNIT_CASE(dm_test_oriented_plane_size_rotate_270),
> - /* dm_get_plane_scale */
> - KUNIT_CASE(dm_test_get_plane_scale_identity),
> - KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity),
> - KUNIT_CASE(dm_test_get_plane_scale_zero_src_width),
> /* is_scaling_state_different */
> KUNIT_CASE(dm_test_scaling_state_same),
> KUNIT_CASE(dm_test_scaling_state_scaling_changed),
^ permalink raw reply [flat|nested] 51+ messages in thread
end of thread, other threads:[~2026-08-03 12:48 UTC | newest]
Thread overview: 51+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 20:13 [PATCH 00/49] DC Patches July 20th, 2026 Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 01/49] drm/amd/display: share common DM KUnit helpers Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 02/49] drm/amd/display: Port DCN4+ MCIF ARB programming to new format Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 03/49] drm/amd/display: Fix force FRL rate debug setting Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 04/49] drm/amd/display: Add KUnit tests for link_lock and psp SRM helpers Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 05/49] drm/amd/display: Add KUnit tests for HDCP display helpers Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 06/49] drm/amd/display: Add KUnit tests for event_callback Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 07/49] drm/amd/display: Add KUnit tests for event_property_validate Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 08/49] drm/amd/display: Add KUnit tests for watchdog and cpirq events Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 09/49] drm/amd/display: Add KUnit tests for hdcp_destroy Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 10/49] drm/amd/display: Add AV mute wait frames to dce110_set_avmute Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 11/49] drm/amd/display: revert "convert dcn42 GPIO translation to lookup tables" Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 12/49] drm/amd/display: move scaling helper to connector Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 13/49] drm/amd/display: move stutter quirk to quirks file Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 14/49] drm/amd/display: move watermarks table to pp_smu Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 15/49] drm/amd/display: move GPU mem helpers to services Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 16/49] drm/amd/display: add FreeSync/VRR module Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 17/49] drm/amd/display: add cursor module Fangzhi Zuo
2026-08-03 12:48 ` Timur Kristóf
2026-07-23 20:13 ` [PATCH 18/49] drm/amd/display: add KUnit tests for audio component get_eld Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 19/49] drm/amd/display: add KUnit tests for audio commit path Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 20/49] drm/amd/display: Use current mpc pipe in set output transfer func Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 21/49] drm/amd/display: Correct vblank_end calc for fams cmd packet Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 22/49] drm/amd/display: Add KUnit test for native backlight registration Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 23/49] drm/amd/display: Add color transfer-function tests Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 24/49] drm/amd/display: Add atomic " Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 25/49] drm/amd/display: Add CRTC and plane degamma tests Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 26/49] drm/amd/display: Add legacy plane LUT tests Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 27/49] drm/amd/display: Add truncated colorop tests Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 28/49] drm/amd/display: Add colorop LUT programming tests Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 29/49] drm/amd/display: Add KUnit tests for enable_assr Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 30/49] drm/amd/display: Add KUnit tests for update_config Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 31/49] drm/amd/display: Add KUnit tests for hdcp_create_workqueue Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 32/49] drm/amd/display: Add KUnit tests for srm_data_write and srm_data_read Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 33/49] drm/amd/display: Add KUnit tests for HDCP DDC link adapters Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 34/49] drm/amd/display: Add initialized-branch test for psp_set_srm Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 35/49] drm/amd/display: Add deeper event_property_update tests Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 36/49] drm/amd/display: adjust floating point format for gamut remap when needed Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 37/49] drm/amd/display: Reintroduce "convert dcn42 GPIO translation to lookup tables" Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 38/49] drm/amd/display: Prune per-tile Timing from Apple Studio Display Primary Tile Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 39/49] drm/amd/display: Add get replay residency function Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 40/49] drm/amd/display: Fix divide-by-zero in calculate_mcache_setting on zero viewport Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 41/49] drm/amd/display: check if dml21_add_phantom_plane() is successful Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 42/49] drm/amd/display: change dcc_rate from 1 to 2 for log use only Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 43/49] drm/amd/display: enforce UCLK pstate support in mode_support Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 44/49] drm/amd/display: add DalForceMaxDisplayClock debug option to DML2 Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 45/49] drm/amd/display: Fixes for dcn42b_soc_bb.h Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 46/49] drm/amd/display: Fix rounding errors in CalculatePrefetchSchedule Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 47/49] drm/amd/display: plumb PMO per-plane pstate methods into mode_support Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 48/49] drm/amd/display: dispatch compressed FRL cap check inside dml1_frl_cap_chk_inter Fangzhi Zuo
2026-07-23 20:13 ` [PATCH 49/49] drm/amd/display: Promote DC to 3.2.391 Fangzhi Zuo
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.