On Thu, 7 Dec 2023 at 15:50, Maxime Ripard <mripard@xxxxxxxxxx> wrote: > > Most of the HDMI controllers have an upper TMDS character rate limit > they can't exceed. On "embedded"-grade display controllers, it will > typically be lower than what high-grade monitors can provide these days, > so drivers will filter the TMDS character rate based on the controller > capabilities. > > To make that easier to handle for drivers, let's provide an optional > hook to be implemented by drivers so they can tell the HDMI controller > helpers if a given TMDS character rate is reachable for them or not. > > This will then be useful to figure out the best format and bpc count for > a given mode. > > Signed-off-by: Maxime Ripard <mripard@xxxxxxxxxx> Reviewed-by: Dave Stevenson <dave.stevenson@xxxxxxxxxxxxxxx> > --- > drivers/gpu/drm/drm_atomic_state_helper.c | 9 +++ > drivers/gpu/drm/drm_connector.c | 4 ++ > .../gpu/drm/tests/drm_atomic_state_helper_test.c | 69 ++++++++++++++++++++++ > drivers/gpu/drm/tests/drm_connector_test.c | 15 +++++ > include/drm/drm_connector.h | 30 ++++++++++ > 5 files changed, 127 insertions(+) > > diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c > index 74bc3cc53c2d..a36edda590f8 100644 > --- a/drivers/gpu/drm/drm_atomic_state_helper.c > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c > @@ -687,11 +687,20 @@ hdmi_clock_valid(const struct drm_connector *connector, > const struct drm_display_mode *mode, > unsigned long long clock) > { > + const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs; > const struct drm_display_info *info = &connector->display_info; > > if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000) > return MODE_CLOCK_HIGH; > > + if (funcs && funcs->tmds_char_rate_valid) { > + enum drm_mode_status status; > + > + status = funcs->tmds_char_rate_valid(connector, mode, clock); > + if (status != MODE_OK) > + return status; > + } > + > return MODE_OK; > } > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c > index 667326b09acc..9f314fee26ce 100644 > --- a/drivers/gpu/drm/drm_connector.c > +++ b/drivers/gpu/drm/drm_connector.c > @@ -457,6 +457,7 @@ EXPORT_SYMBOL(drmm_connector_init); > * @dev: DRM device > * @connector: A pointer to the HDMI connector to init > * @funcs: callbacks for this connector > + * @hdmi_funcs: HDMI-related callbacks for this connector > * @connector_type: user visible type of the connector > * @ddc: optional pointer to the associated ddc adapter > * @supported_formats: Bitmask of @hdmi_colorspace listing supported output formats > @@ -476,6 +477,7 @@ EXPORT_SYMBOL(drmm_connector_init); > int drmm_connector_hdmi_init(struct drm_device *dev, > struct drm_connector *connector, > const struct drm_connector_funcs *funcs, > + const struct drm_connector_hdmi_funcs *hdmi_funcs, > int connector_type, > struct i2c_adapter *ddc, > unsigned long supported_formats, > @@ -512,6 +514,8 @@ int drmm_connector_hdmi_init(struct drm_device *dev, > if (max_bpc > 8) > drm_connector_attach_hdr_output_metadata_property(connector); > > + connector->hdmi.funcs = hdmi_funcs; > + > return 0; > } > EXPORT_SYMBOL(drmm_connector_hdmi_init); > diff --git a/drivers/gpu/drm/tests/drm_atomic_state_helper_test.c b/drivers/gpu/drm/tests/drm_atomic_state_helper_test.c > index d76fafb91025..e7dbdd4a4e7f 100644 > --- a/drivers/gpu/drm/tests/drm_atomic_state_helper_test.c > +++ b/drivers/gpu/drm/tests/drm_atomic_state_helper_test.c > @@ -110,6 +110,21 @@ static int set_connector_edid(struct kunit *test, struct drm_connector *connecto > return 0; > } > > +static const struct drm_connector_hdmi_funcs dummy_connector_hdmi_funcs = { > +}; > + > +static enum drm_mode_status > +reject_connector_tmds_char_rate_valid(const struct drm_connector *connector, > + const struct drm_display_mode *mode, > + unsigned long long tmds_rate) > +{ > + return MODE_BAD; > +} > + > +static const struct drm_connector_hdmi_funcs reject_connector_hdmi_funcs = { > + .tmds_char_rate_valid = reject_connector_tmds_char_rate_valid, > +}; > + > static int dummy_connector_get_modes(struct drm_connector *connector) > { > struct drm_atomic_helper_connector_hdmi_priv *priv = > @@ -192,6 +207,7 @@ drm_atomic_helper_connector_hdmi_init(struct kunit *test, > conn = &priv->connector; > ret = drmm_connector_hdmi_init(drm, conn, > &dummy_connector_funcs, > + &dummy_connector_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > NULL, > formats, > @@ -956,6 +972,58 @@ static void drm_test_check_tmds_char_rate_rgb_12bpc(struct kunit *test) > KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, preferred->clock * 1500); > } > > +/* > + * Test that if we filter a rate through our hook, it's indeed rejected > + * by the whole atomic_check logic. > + * > + * We do so by first doing a commit on the pipeline to make sure that it > + * works, change the HDMI helpers pointer, and then try the same commit > + * again to see if it fails as it should. > + */ > +static void drm_test_check_hdmi_funcs_reject_rate(struct kunit *test) > +{ > + struct drm_atomic_helper_connector_hdmi_priv *priv; > + struct drm_modeset_acquire_ctx *ctx; > + struct drm_atomic_state *state; > + struct drm_display_mode *preferred; > + struct drm_crtc_state *crtc_state; > + struct drm_connector *conn; > + struct drm_device *drm; > + struct drm_crtc *crtc; > + int ret; > + > + priv = drm_atomic_helper_connector_hdmi_init(test, > + BIT(HDMI_COLORSPACE_RGB), > + 8); > + KUNIT_ASSERT_NOT_NULL(test, priv); > + > + 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); > + > + drm = &priv->drm; > + crtc = priv->crtc; > + ret = light_up_connector(test, drm, crtc, conn, preferred, ctx); > + KUNIT_ASSERT_EQ(test, ret, 0); > + > + /* You shouldn't be doing that at home. */ > + conn->hdmi.funcs = &reject_connector_hdmi_funcs; > + > + state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx); > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); > + > + crtc_state = drm_atomic_get_crtc_state(state, crtc); > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); > + > + crtc_state->connectors_changed = true; > + > + ret = drm_atomic_check_only(state); > + KUNIT_EXPECT_LT(test, ret, 0); > +} > + > static struct kunit_case drm_atomic_helper_connector_hdmi_check_tests[] = { > KUNIT_CASE(drm_test_check_broadcast_rgb_auto_cea_mode), > KUNIT_CASE(drm_test_check_broadcast_rgb_auto_cea_mode_vic_1), > @@ -965,6 +1033,7 @@ static struct kunit_case drm_atomic_helper_connector_hdmi_check_tests[] = { > KUNIT_CASE(drm_test_check_broadcast_rgb_limited_cea_mode_vic_1), > KUNIT_CASE(drm_test_check_broadcast_rgb_crtc_mode_changed), > KUNIT_CASE(drm_test_check_broadcast_rgb_crtc_mode_not_changed), > + KUNIT_CASE(drm_test_check_hdmi_funcs_reject_rate), > KUNIT_CASE(drm_test_check_output_bpc_crtc_mode_changed), > KUNIT_CASE(drm_test_check_output_bpc_crtc_mode_not_changed), > KUNIT_CASE(drm_test_check_tmds_char_rate_rgb_8bpc), > diff --git a/drivers/gpu/drm/tests/drm_connector_test.c b/drivers/gpu/drm/tests/drm_connector_test.c > index 0a838924a546..6a3651b08c81 100644 > --- a/drivers/gpu/drm/tests/drm_connector_test.c > +++ b/drivers/gpu/drm/tests/drm_connector_test.c > @@ -22,6 +22,9 @@ struct drm_connector_init_priv { > struct i2c_adapter ddc; > }; > > +static const struct drm_connector_hdmi_funcs dummy_hdmi_funcs = { > +}; > + > static const struct drm_connector_funcs dummy_funcs = { > .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, > .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > @@ -187,6 +190,7 @@ static void drm_test_connector_hdmi_init_valid(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > &priv->ddc, > BIT(HDMI_COLORSPACE_RGB), > @@ -205,6 +209,7 @@ static void drm_test_connector_hdmi_init_null_ddc(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > NULL, > BIT(HDMI_COLORSPACE_RGB), > @@ -223,6 +228,7 @@ static void drm_test_connector_hdmi_init_bpc_invalid(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > &priv->ddc, > BIT(HDMI_COLORSPACE_RGB), > @@ -241,6 +247,7 @@ static void drm_test_connector_hdmi_init_bpc_null(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > &priv->ddc, > BIT(HDMI_COLORSPACE_RGB), > @@ -263,6 +270,7 @@ static void drm_test_connector_hdmi_init_bpc_8(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > &priv->ddc, > BIT(HDMI_COLORSPACE_RGB), > @@ -297,6 +305,7 @@ static void drm_test_connector_hdmi_init_bpc_10(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > &priv->ddc, > BIT(HDMI_COLORSPACE_RGB), > @@ -331,6 +340,7 @@ static void drm_test_connector_hdmi_init_bpc_12(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > &priv->ddc, > BIT(HDMI_COLORSPACE_RGB), > @@ -361,6 +371,7 @@ static void drm_test_connector_hdmi_init_formats_empty(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > &priv->ddc, > 0, > @@ -379,6 +390,7 @@ static void drm_test_connector_hdmi_init_formats_no_rgb(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > &priv->ddc, > BIT(HDMI_COLORSPACE_YUV422), > @@ -398,6 +410,7 @@ static void drm_test_connector_hdmi_init_type_valid(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > connector_type, > &priv->ddc, > BIT(HDMI_COLORSPACE_RGB), > @@ -431,6 +444,7 @@ static void drm_test_connector_hdmi_init_type_invalid(struct kunit *test) > > ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > connector_type, > &priv->ddc, > BIT(HDMI_COLORSPACE_RGB), > @@ -695,6 +709,7 @@ static void drm_test_drm_connector_attach_broadcast_rgb_property_hdmi_connector( > > ret = drmm_connector_hdmi_init(&priv->drm, connector, > &dummy_funcs, > + &dummy_hdmi_funcs, > DRM_MODE_CONNECTOR_HDMIA, > &priv->ddc, > BIT(HDMI_COLORSPACE_RGB), > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h > index 59016d9c17f5..3eaf4d54364d 100644 > --- a/include/drm/drm_connector.h > +++ b/include/drm/drm_connector.h > @@ -1093,6 +1093,30 @@ struct drm_connector_state { > } hdmi; > }; > > +/** > + * struct drm_connector_hdmi_funcs - drm_hdmi_connector control functions > + */ > +struct drm_connector_hdmi_funcs { > + /** > + * @tmds_char_rate_valid: > + * > + * This callback is invoked at atomic_check time to figure out > + * whether a particular TMDS character rate is supported by the > + * driver. > + * > + * The @tmds_char_rate_valid callback is optional. > + * > + * Returns: > + * > + * Either &drm_mode_status.MODE_OK or one of the failure reasons > + * in &enum drm_mode_status. > + */ > + enum drm_mode_status > + (*tmds_char_rate_valid)(const struct drm_connector *connector, > + const struct drm_display_mode *mode, > + unsigned long long tmds_rate); > +}; > + > /** > * struct drm_connector_funcs - control connectors on a given device > * > @@ -1967,6 +1991,11 @@ struct drm_connector { > * supported by the controller. > */ > unsigned long supported_formats; > + > + /** > + * @funcs: HDMI connector Control Functions > + */ > + const struct drm_connector_hdmi_funcs *funcs; > } hdmi; > }; > > @@ -1989,6 +2018,7 @@ int drmm_connector_init(struct drm_device *dev, > int drmm_connector_hdmi_init(struct drm_device *dev, > struct drm_connector *connector, > const struct drm_connector_funcs *funcs, > + const struct drm_connector_hdmi_funcs *hdmi_funcs, > int connector_type, > struct i2c_adapter *ddc, > unsigned long supported_formats, > > -- > 2.43.0 >