Skip to content

Commit

Permalink
drm/connector: hdmi: Compute bpc and format automatically
Browse files Browse the repository at this point in the history
Now that we have all the infrastructure needed, we can add some code
that will, for a given connector state and mode, compute the best output
format and bpc.

The algorithm is equivalent to the one already found in i915 and vc4.

Cc: Ville Syrjälä <ville.syrjala@linux.intel.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-15-c5af16c3aae2@kernel.org
Signed-off-by: Maxime Ripard <mripard@kernel.org>
  • Loading branch information
Maxime Ripard committed May 28, 2024
1 parent a6cb58a commit 26ff1c3
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 12 deletions.
217 changes: 214 additions & 3 deletions drivers/gpu/drm/display/drm_hdmi_state_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <drm/drm_atomic.h>
#include <drm/drm_connector.h>
#include <drm/drm_edid.h>
#include <drm/drm_print.h>

#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
Expand Down Expand Up @@ -48,6 +50,126 @@ connector_state_get_mode(const struct drm_connector_state *conn_state)
return &crtc_state->mode;
}

static bool
sink_supports_format_bpc(const struct drm_connector *connector,
const struct drm_display_info *info,
const struct drm_display_mode *mode,
unsigned int format, unsigned int bpc)
{
struct drm_device *dev = connector->dev;
u8 vic = drm_match_cea_mode(mode);

/*
* CTA-861-F, section 5.4 - Color Coding & Quantization states
* that the bpc must be 8, 10, 12 or 16 except for the default
* 640x480 VIC1 where the value must be 8.
*
* The definition of default here is ambiguous but the spec
* refers to VIC1 being the default timing in several occasions
* so our understanding is that for the default timing (ie,
* VIC1), the bpc must be 8.
*/
if (vic == 1 && bpc != 8) {
drm_dbg_kms(dev, "VIC1 requires a bpc of 8, got %u\n", bpc);
return false;
}

if (!info->is_hdmi &&
(format != HDMI_COLORSPACE_RGB || bpc != 8)) {
drm_dbg_kms(dev, "DVI Monitors require an RGB output at 8 bpc\n");
return false;
}

if (!(connector->hdmi.supported_formats & BIT(format))) {
drm_dbg_kms(dev, "%s format unsupported by the connector.\n",
drm_hdmi_connector_get_output_format_name(format));
return false;
}

switch (format) {
case HDMI_COLORSPACE_RGB:
drm_dbg_kms(dev, "RGB Format, checking the constraints.\n");

/*
* In some cases, like when the EDID readout fails, or
* is not an HDMI compliant EDID for some reason, the
* color_formats field will be blank and not report any
* format supported. In such a case, assume that RGB is
* supported so we can keep things going and light up
* the display.
*/
if (!(info->color_formats & DRM_COLOR_FORMAT_RGB444))
drm_warn(dev, "HDMI Sink doesn't support RGB, something's wrong.\n");

if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) {
drm_dbg_kms(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
return false;
}

if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) {
drm_dbg_kms(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
return false;
}

drm_dbg_kms(dev, "RGB format supported in that configuration.\n");

return true;

case HDMI_COLORSPACE_YUV420:
/* TODO: YUV420 is unsupported at the moment. */
drm_dbg_kms(dev, "YUV420 format isn't supported yet.\n");
return false;

case HDMI_COLORSPACE_YUV422:
drm_dbg_kms(dev, "YUV422 format, checking the constraints.\n");

if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR422)) {
drm_dbg_kms(dev, "Sink doesn't support YUV422.\n");
return false;
}

if (bpc > 12) {
drm_dbg_kms(dev, "YUV422 only supports 12 bpc or lower.\n");
return false;
}

/*
* HDMI Spec 1.3 - Section 6.5 Pixel Encodings and Color Depth
* states that Deep Color is not relevant for YUV422 so we
* don't need to check the Deep Color bits in the EDIDs here.
*/

drm_dbg_kms(dev, "YUV422 format supported in that configuration.\n");

return true;

case HDMI_COLORSPACE_YUV444:
drm_dbg_kms(dev, "YUV444 format, checking the constraints.\n");

if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR444)) {
drm_dbg_kms(dev, "Sink doesn't support YUV444.\n");
return false;
}

if (bpc == 10 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_30)) {
drm_dbg_kms(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
return false;
}

if (bpc == 12 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_36)) {
drm_dbg_kms(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
return false;
}

drm_dbg_kms(dev, "YUV444 format supported in that configuration.\n");

return true;
}

drm_dbg_kms(dev, "Unsupported pixel format.\n");
return false;
}

static enum drm_mode_status
hdmi_clock_valid(const struct drm_connector *connector,
const struct drm_display_mode *mode,
Expand Down Expand Up @@ -92,6 +214,97 @@ hdmi_compute_clock(const struct drm_connector *connector,
return 0;
}

static bool
hdmi_try_format_bpc(const struct drm_connector *connector,
struct drm_connector_state *conn_state,
const struct drm_display_mode *mode,
unsigned int bpc, enum hdmi_colorspace fmt)
{
const struct drm_display_info *info = &connector->display_info;
struct drm_device *dev = connector->dev;
int ret;

drm_dbg_kms(dev, "Trying %s output format\n",
drm_hdmi_connector_get_output_format_name(fmt));

if (!sink_supports_format_bpc(connector, info, mode, fmt, bpc)) {
drm_dbg_kms(dev, "%s output format not supported with %u bpc\n",
drm_hdmi_connector_get_output_format_name(fmt),
bpc);
return false;
}

ret = hdmi_compute_clock(connector, conn_state, mode, bpc, fmt);
if (ret) {
drm_dbg_kms(dev, "Couldn't compute clock for %s output format and %u bpc\n",
drm_hdmi_connector_get_output_format_name(fmt),
bpc);
return false;
}

drm_dbg_kms(dev, "%s output format supported with %u (TMDS char rate: %llu Hz)\n",
drm_hdmi_connector_get_output_format_name(fmt),
bpc, conn_state->hdmi.tmds_char_rate);

return true;
}

static int
hdmi_compute_format(const struct drm_connector *connector,
struct drm_connector_state *conn_state,
const struct drm_display_mode *mode,
unsigned int bpc)
{
struct drm_device *dev = connector->dev;

/*
* TODO: Add support for YCbCr420 output for HDMI 2.0 capable
* devices, for modes that only support YCbCr420.
*/
if (hdmi_try_format_bpc(connector, conn_state, mode, bpc, HDMI_COLORSPACE_RGB)) {
conn_state->hdmi.output_format = HDMI_COLORSPACE_RGB;
return 0;
}

drm_dbg_kms(dev, "Failed. No Format Supported for that bpc count.\n");

return -EINVAL;
}

static int
hdmi_compute_config(const struct drm_connector *connector,
struct drm_connector_state *conn_state,
const struct drm_display_mode *mode)
{
struct drm_device *dev = connector->dev;
unsigned int max_bpc = clamp_t(unsigned int,
conn_state->max_bpc,
8, connector->max_bpc);
unsigned int bpc;
int ret;

for (bpc = max_bpc; bpc >= 8; bpc -= 2) {
drm_dbg_kms(dev, "Trying with a %d bpc output\n", bpc);

ret = hdmi_compute_format(connector, conn_state, mode, bpc);
if (ret)
continue;

conn_state->hdmi.output_bpc = bpc;

drm_dbg_kms(dev,
"Mode %ux%u @ %uHz: Found configuration: bpc: %u, fmt: %s, clock: %llu\n",
mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode),
conn_state->hdmi.output_bpc,
drm_hdmi_connector_get_output_format_name(conn_state->hdmi.output_format),
conn_state->hdmi.tmds_char_rate);

return 0;
}

return -EINVAL;
}

/**
* drm_atomic_helper_connector_hdmi_check() - Helper to check HDMI connector atomic state
* @connector: DRM Connector
Expand All @@ -115,9 +328,7 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector,
connector_state_get_mode(new_conn_state);
int ret;

ret = hdmi_compute_clock(connector, new_conn_state, mode,
new_conn_state->hdmi.output_bpc,
new_conn_state->hdmi.output_format);
ret = hdmi_compute_config(connector, new_conn_state, mode);
if (ret)
return ret;

Expand Down
25 changes: 16 additions & 9 deletions drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ static int light_up_connector(struct kunit *test,
conn_state = drm_atomic_get_connector_state(state, connector);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);

conn_state->hdmi.output_bpc = connector->max_bpc;
conn_state->hdmi.output_format = HDMI_COLORSPACE_RGB;

ret = drm_atomic_set_crtc_for_connector(conn_state, crtc);
KUNIT_EXPECT_EQ(test, ret, 0);

Expand Down Expand Up @@ -253,10 +250,15 @@ static void drm_test_check_output_bpc_crtc_mode_changed(struct kunit *test)
10);
KUNIT_ASSERT_NOT_NULL(test, priv);

conn = &priv->connector;
ret = set_connector_edid(test, conn,
test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz,
ARRAY_SIZE(test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz));
KUNIT_ASSERT_EQ(test, ret, 0);

ctx = drm_kunit_helper_acquire_ctx_alloc(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);

conn = &priv->connector;
preferred = find_preferred_mode(conn);
KUNIT_ASSERT_NOT_NULL(test, preferred);

Expand All @@ -274,11 +276,11 @@ static void drm_test_check_output_bpc_crtc_mode_changed(struct kunit *test)
old_conn_state = drm_atomic_get_old_connector_state(state, conn);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_conn_state);

new_conn_state->hdmi.output_bpc = 8;
new_conn_state->max_requested_bpc = 8;

KUNIT_ASSERT_NE(test,
old_conn_state->hdmi.output_bpc,
new_conn_state->hdmi.output_bpc);
old_conn_state->max_requested_bpc,
new_conn_state->max_requested_bpc);

ret = drm_atomic_check_only(state);
KUNIT_ASSERT_EQ(test, ret, 0);
Expand Down Expand Up @@ -322,10 +324,15 @@ static void drm_test_check_output_bpc_crtc_mode_not_changed(struct kunit *test)
10);
KUNIT_ASSERT_NOT_NULL(test, priv);

conn = &priv->connector;
ret = set_connector_edid(test, conn,
test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz,
ARRAY_SIZE(test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz));
KUNIT_ASSERT_EQ(test, ret, 0);

ctx = drm_kunit_helper_acquire_ctx_alloc(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);

conn = &priv->connector;
preferred = find_preferred_mode(conn);
KUNIT_ASSERT_NOT_NULL(test, preferred);

Expand Down Expand Up @@ -672,7 +679,7 @@ static void drm_test_check_format_value(struct kunit *test)

conn = &priv->connector;
conn_state = conn->state;
KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB);
KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, 0);
}

/*
Expand Down

0 comments on commit 26ff1c3

Please sign in to comment.