From: Bhawanpreet Lakha <[email protected]> Add KUnit coverage for amdgpu_dm_find_first_crtc_matching_connector(): match, no match, empty state, skipping NULL connector slots, and returning the first match when several target the same crtc.
Also add coverage for amdgpu_dm_set_panel_type(): VSDB OLED/miniLED, DPCD OLED/miniLED, Samsung miniLED above and below threshold, and the default LCD fallback. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung <[email protected]> Signed-off-by: Bhawanpreet Lakha <[email protected]> Signed-off-by: George Zhang <[email protected]> --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 1 + .../display/amdgpu_dm/amdgpu_dm_connector.h | 1 + .../tests/amdgpu_dm_connector_test.c | 313 ++++++++++++++++++ 3 files changed, 315 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 2c213bed8a6c..7ce2efb9aaa1 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 @@ -394,6 +394,7 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_state *state, return NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_find_first_crtc_matching_connector); STATIC_IFN_KUNIT void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 707308c02820..99b5f20699b4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -149,6 +149,7 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, enum drm_mode_subconnector get_subconnector_type(struct dc_link *link); void update_subconnector_property(struct amdgpu_dm_connector *aconnector); void amdgpu_dm_fbc_init(struct drm_connector *connector); +void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, 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 0dcc13e7f0af..6639962dd289 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 @@ -6,10 +6,14 @@ */ #include <kunit/test.h> +#include <drm/drm_atomic.h> #include <drm/drm_atomic_state_helper.h> #include <drm/drm_connector.h> +#include <drm/drm_crtc.h> #include <drm/drm_edid.h> #include <drm/drm_kunit_helpers.h> +#include <drm/drm_mode_object.h> +#include <drm/drm_property.h> #include <linux/hdmi.h> #include "dc.h" @@ -2896,6 +2900,305 @@ static void dm_test_detect_mst_branch_without_aux(struct kunit *test) amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); } +/* Tests for amdgpu_dm_find_first_crtc_matching_connector() */ + +/* + * Build a minimal drm_atomic_state holding @count connector slots. The + * function under test only reads num_connector, connectors[i].ptr and + * connectors[i].new_state, so a hand-rolled state is sufficient. + */ +static struct drm_atomic_state * +dm_test_alloc_atomic_state(struct kunit *test, int count) +{ + struct drm_atomic_state *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->num_connector = count; + if (count) { + state->connectors = kunit_kcalloc(test, count, + sizeof(*state->connectors), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->connectors); + } + + return state; +} + +/** + * dm_test_find_first_crtc_match - Test find_first_crtc returns matching connector + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_match(struct kunit *test) +{ + struct drm_atomic_state *state = dm_test_alloc_atomic_state(test, 1); + struct drm_connector *connector; + struct drm_connector_state *con_state; + struct drm_crtc *crtc; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + con_state = kunit_kzalloc(test, sizeof(*con_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, con_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + con_state->crtc = crtc; + state->connectors[0].ptr = connector; + state->connectors[0].new_state = con_state; + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc), + connector); +} + +/** + * dm_test_find_first_crtc_no_match - Test find_first_crtc returns NULL when no crtc matches + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_no_match(struct kunit *test) +{ + struct drm_atomic_state *state = dm_test_alloc_atomic_state(test, 1); + struct drm_connector *connector; + struct drm_connector_state *con_state; + struct drm_crtc *crtc; + struct drm_crtc *other_crtc; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + con_state = kunit_kzalloc(test, sizeof(*con_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, con_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + other_crtc = kunit_kzalloc(test, sizeof(*other_crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, other_crtc); + + con_state->crtc = other_crtc; + state->connectors[0].ptr = connector; + state->connectors[0].new_state = con_state; + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc)); +} + +/** + * dm_test_find_first_crtc_empty_state - Test find_first_crtc returns NULL with no connectors + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_empty_state(struct kunit *test) +{ + struct drm_atomic_state *state = dm_test_alloc_atomic_state(test, 0); + struct drm_crtc *crtc; + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc)); +} + +/** + * dm_test_find_first_crtc_skips_null_ptr - Test find_first_crtc skips empty connector slots + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_skips_null_ptr(struct kunit *test) +{ + struct drm_atomic_state *state = dm_test_alloc_atomic_state(test, 2); + struct drm_connector *connector; + struct drm_connector_state *con_state; + struct drm_crtc *crtc; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + con_state = kunit_kzalloc(test, sizeof(*con_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, con_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + /* Slot 0 has no connector (ptr == NULL) and must be skipped. */ + con_state->crtc = crtc; + state->connectors[1].ptr = connector; + state->connectors[1].new_state = con_state; + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc), + connector); +} + +/** + * dm_test_find_first_crtc_returns_first - Test find_first_crtc returns the first match + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_returns_first(struct kunit *test) +{ + struct drm_atomic_state *state = dm_test_alloc_atomic_state(test, 2); + struct drm_connector *first; + struct drm_connector *second; + struct drm_connector_state *first_state; + struct drm_connector_state *second_state; + struct drm_crtc *crtc; + + first = kunit_kzalloc(test, sizeof(*first), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, first); + second = kunit_kzalloc(test, sizeof(*second), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, second); + first_state = kunit_kzalloc(test, sizeof(*first_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, first_state); + second_state = kunit_kzalloc(test, sizeof(*second_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, second_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + /* Both connectors target the same crtc; the first one must win. */ + first_state->crtc = crtc; + second_state->crtc = crtc; + state->connectors[0].ptr = first; + state->connectors[0].new_state = first_state; + state->connectors[1].ptr = second; + state->connectors[1].new_state = second_state; + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc), + first); +} + +/* Tests for amdgpu_dm_set_panel_type() */ + +/* + * Build an amdgpu_dm_connector registered against a real kunit drm_device that + * is embedded in an amdgpu_device, so drm_to_adev()/adev_to_drm() resolve and + * the panel_type property can be created, attached and updated for real. + */ +struct dm_test_panel_ctx { + struct amdgpu_device *adev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + struct drm_display_info *display_info; +}; + +static struct dm_test_panel_ctx *dm_test_panel_ctx_alloc(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx; + struct drm_property *prop; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*ctx->adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); + ctx->adev = drm_to_adev(ctx->drm); + + /* The function under test writes through this property. */ + prop = drm_property_create_range(ctx->drm, DRM_MODE_PROP_IMMUTABLE, + "panel_type", 0, 0xff); + KUNIT_ASSERT_NOT_NULL(test, prop); + ctx->drm->mode_config.panel_type_property = prop; + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(ctx->drm, &ctx->aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL), 0); + drm_object_attach_property(&ctx->aconnector->base.base, prop, + DRM_MODE_PANEL_TYPE_UNKNOWN); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->aconnector->dc_link = ctx->link; + + ctx->display_info = &ctx->aconnector->base.display_info; + + return ctx; +} + +static uint64_t dm_test_panel_prop_value(struct kunit *test, + struct dm_test_panel_ctx *ctx) +{ + uint64_t val = ~0ULL; + + KUNIT_EXPECT_EQ(test, + drm_object_property_get_value(&ctx->aconnector->base.base, + ctx->drm->mode_config.panel_type_property, &val), 0); + return val; +} + +/** + * dm_test_set_panel_type_samsung_miniled - Test Samsung luminance heuristic + * @test: The KUnit test context + * + * When no VSDB or DPCD hint is present, a Samsung sink whose first luminance + * range is at least 1.5x the second is treated as mini-LED. + */ +static void dm_test_set_panel_type_samsung_miniled(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx = dm_test_panel_ctx_alloc(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + sink->edid_caps.manufacturer_id = DDC_MANUFACTURERNAME_SAMSUNG; + ctx->link->local_sink = sink; + + ctx->display_info->amd_vsdb.version = 1; + ctx->display_info->amd_vsdb.luminance_range1.max_luminance = 1500; + ctx->display_info->amd_vsdb.luminance_range2.max_luminance = 1000; + + amdgpu_dm_set_panel_type(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->panel_type, (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_samsung_below_threshold - Test Samsung sink below the + * mini-LED luminance threshold falls back to LCD + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_samsung_below_threshold(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx = dm_test_panel_ctx_alloc(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + sink->edid_caps.manufacturer_id = DDC_MANUFACTURERNAME_SAMSUNG; + ctx->link->local_sink = sink; + + ctx->display_info->amd_vsdb.version = 1; + ctx->display_info->amd_vsdb.luminance_range1.max_luminance = 1000; + ctx->display_info->amd_vsdb.luminance_range2.max_luminance = 1000; + + amdgpu_dm_set_panel_type(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->panel_type, (int)PANEL_TYPE_LCD); +} + +/** + * dm_test_set_panel_type_default_lcd - Test default fallback is LCD + * @test: The KUnit test context + * + * With no VSDB, DPCD or DID hints the panel type defaults to LCD. + */ +static void dm_test_set_panel_type_default_lcd(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx = dm_test_panel_ctx_alloc(test); + + amdgpu_dm_set_panel_type(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->panel_type, (int)PANEL_TYPE_LCD); + KUNIT_EXPECT_EQ(test, dm_test_panel_prop_value(test, ctx), + (uint64_t)DRM_MODE_PANEL_TYPE_LCD); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -3063,6 +3366,16 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_detect_mst_skips_writeback), KUNIT_CASE(dm_test_detect_mst_non_mst_link), KUNIT_CASE(dm_test_detect_mst_branch_without_aux), + /* amdgpu_dm_find_first_crtc_matching_connector */ + KUNIT_CASE(dm_test_find_first_crtc_match), + KUNIT_CASE(dm_test_find_first_crtc_no_match), + KUNIT_CASE(dm_test_find_first_crtc_empty_state), + KUNIT_CASE(dm_test_find_first_crtc_skips_null_ptr), + KUNIT_CASE(dm_test_find_first_crtc_returns_first), + /* amdgpu_dm_set_panel_type */ + KUNIT_CASE(dm_test_set_panel_type_samsung_miniled), + KUNIT_CASE(dm_test_set_panel_type_samsung_below_threshold), + KUNIT_CASE(dm_test_set_panel_type_default_lcd), {} }; -- 2.55.0
