From eb5650d1f367722d0136de18998749008fe7d2a7 Mon Sep 17 00:00:00 2001 From: Adrian Betschart Date: Thu, 10 Sep 2026 20:56:48 +0200 Subject: [PATCH] drm/amd/display: Default HDMI RGB output to limited range on CTA modes With the Broadcast RGB property at Automatic, amdgpu_dm_get_output_color_space() selects COLOR_SPACE_SRGB for RGB output, so DC neither compresses the pixels nor, without a QS-capable sink, signals full range in the AVI InfoFrame. A sink that follows CTA-861 treats the default quantization of a CTA video format as limited and expands 16-235 to 0-255: everything below 16 is crushed to black, everything above 235 clips. Follow the CTA-861 default for Automatic instead, as the DRM HDMI state helper (hdmi_is_limited_range()) and i915 do: limited range on an HDMI sink for CTA modes other than 640x480, full range elsewhere. Full and Limited keep their explicit meaning. The same rule applies to the BT.2020 RGB branch. With commit 892659399f64 ("drm/amd/display: Propagate HDMI RGB quantization selectability") the AVI InfoFrame then carries the matching Q value on sinks that support selection. Measured on a Radeon RX 7600 (DCN 3.2.1) driving a JVC DLA-RS4100 at 1920x1080p24 RGB 12 bpc: the sink reports the signal as limited range while the picture shows crushed shadows; rendering limited range in the client makes it match a reference source. The KUnit fixtures that reach amdgpu_dm_get_output_color_space() now carry a connector, since Automatic reads its display_info, and five cases cover the new rule. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5796 Reviewed-by: George Zhang Signed-off-by: Adrian Betschart Assisted-by: Claude Code:claude-fable-5-1 Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit d23f735d0eac52c673d91e21d146b09588537efc) --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 22 +++- .../tests/amdgpu_dm_connector_test.c | 117 ++++++++++++++++++ 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 154e1f35dcb1..dbd1e8429a4d 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 @@ -732,6 +732,24 @@ get_aspect_ratio(const struct drm_display_mode *mode_in) } EXPORT_IF_KUNIT(get_aspect_ratio); +/* + * CTA-861 5.1: RGB video formats default to limited range except 640x480 (VIC 1). + * With Broadcast RGB left at Automatic follow that default, as i915 does. + */ +static bool rgb_output_is_limited_range(const struct dc_crtc_timing *dc_crtc_timing, + const struct drm_connector_state *connector_state) +{ + switch (connector_state->hdmi.broadcast_rgb) { + case DRM_HDMI_BROADCAST_RGB_FULL: + return false; + case DRM_HDMI_BROADCAST_RGB_LIMITED: + return true; + default: + return connector_state->connector->display_info.is_hdmi && + dc_crtc_timing->vic > 1; + } +} + enum dc_color_space amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, const struct drm_connector_state *connector_state) @@ -757,7 +775,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, case DRM_MODE_COLORIMETRY_BT2020_RGB: case DRM_MODE_COLORIMETRY_BT2020_YCC: if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { - if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + if (rgb_output_is_limited_range(dc_crtc_timing, connector_state)) color_space = COLOR_SPACE_2020_RGB_LIMITEDRANGE; else color_space = COLOR_SPACE_2020_RGB_FULLRANGE; @@ -769,7 +787,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, default: if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { color_space = COLOR_SPACE_SRGB; - if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + if (rgb_output_is_limited_range(dc_crtc_timing, connector_state)) color_space = COLOR_SPACE_SRGB_LIMITED; /* * 27030khz is the separation point between HDTV and SDTV 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 212a7536e65b..59ce682f491b 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 @@ -409,7 +409,9 @@ static void dm_test_output_color_space_default_rgb_full(struct kunit *test) { struct dc_crtc_timing timing = {}; struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + state.connector = connector; timing.pixel_encoding = PIXEL_ENCODING_RGB; state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; @@ -559,7 +561,9 @@ static void dm_test_output_color_space_bt2020_rgb(struct kunit *test) { struct dc_crtc_timing timing = {}; struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + state.connector = connector; timing.pixel_encoding = PIXEL_ENCODING_RGB; state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; @@ -584,6 +588,110 @@ static void dm_test_output_color_space_bt2020_rgb_limited(struct kunit *test) (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); } +/** + * dm_test_output_color_space_default_rgb_auto_hdmi_cta - Automatic follows the CTA-861 default + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_auto_hdmi_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB_LIMITED); +} + +/** + * dm_test_output_color_space_default_rgb_auto_hdmi_vga - 640x480 (VIC 1) stays full range + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_auto_hdmi_vga(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 1; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_default_rgb_auto_dp_cta - Automatic on a non-HDMI sink is full range + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_auto_dp_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_default_rgb_full_hdmi_cta - Full overrides the CTA-861 default + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_full_hdmi_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_FULL; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_bt2020_rgb_auto_hdmi_cta - BT.2020 RGB follows the same default + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_rgb_auto_hdmi_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); +} + /** * dm_test_output_color_space_bt2020_ycc - Test Output color space bt2020 ycc * @test: The KUnit test context @@ -647,7 +755,9 @@ static void dm_test_output_color_space_bt2020_ycc_rgb_encoding(struct kunit *tes { struct dc_crtc_timing timing = {}; struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + state.connector = connector; timing.pixel_encoding = PIXEL_ENCODING_RGB; state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; @@ -3425,6 +3535,7 @@ static struct dm_test_fill_ctx *dm_test_fill_ctx_alloc(struct kunit *test) ctx->conn_state = drmm_kzalloc(ctx->drm, sizeof(*ctx->conn_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); + ctx->conn_state->connector = &ctx->aconnector->base; ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->stream); ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); @@ -3818,6 +3929,7 @@ static struct dm_test_stream_ctx *dm_test_stream_ctx_alloc(struct kunit *test) ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + ctx->dm_state->base.connector = &ctx->aconnector->base; ctx->dm_state->scaling = RMX_OFF; ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); @@ -5458,6 +5570,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_output_color_space_oprgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_limited), + KUNIT_CASE(dm_test_output_color_space_default_rgb_auto_hdmi_cta), + KUNIT_CASE(dm_test_output_color_space_default_rgb_auto_hdmi_vga), + KUNIT_CASE(dm_test_output_color_space_default_rgb_auto_dp_cta), + KUNIT_CASE(dm_test_output_color_space_default_rgb_full_hdmi_cta), + KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_auto_hdmi_cta), KUNIT_CASE(dm_test_output_color_space_bt2020_ycc), KUNIT_CASE(dm_test_output_color_space_default_ycbcr709_y_only), KUNIT_CASE(dm_test_output_color_space_default_ycbcr601),