Re: [PATCH v3 3/7] drm/bridge-connector: implement glue code for HDMI connector

From: Maxime Ripard
Date: Thu May 30 2024 - 05:04:39 EST


Hi,

On Thu, May 30, 2024 at 02:12:26AM GMT, Dmitry Baryshkov wrote:
> In order to let bridge chains implement HDMI connector infrastructure,
> add necessary glue code to the drm_bridge_connector. In case there is a
> bridge that sets DRM_BRIDGE_OP_HDMI, drm_bridge_connector will register
> itself as a HDMI connector and provide proxy drm_connector_hdmi_funcs
> implementation.
>
> Note, to simplify implementation, there can be only one bridge in a
> chain that sets DRM_BRIDGE_OP_HDMI. Setting more than one is considered
> an error. This limitation can be lifted later, if the need arises.
>
> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx>
> ---
> drivers/gpu/drm/drm_bridge_connector.c | 101 ++++++++++++++++++++++++++++++++-
> drivers/gpu/drm/drm_debugfs.c | 2 +
> include/drm/drm_bridge.h | 82 ++++++++++++++++++++++++++
> 3 files changed, 182 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_bridge_connector.c b/drivers/gpu/drm/drm_bridge_connector.c
> index e093fc8928dc..8dca3eda5381 100644
> --- a/drivers/gpu/drm/drm_bridge_connector.c
> +++ b/drivers/gpu/drm/drm_bridge_connector.c
> @@ -18,6 +18,7 @@
> #include <drm/drm_managed.h>
> #include <drm/drm_modeset_helper_vtables.h>
> #include <drm/drm_probe_helper.h>
> +#include <drm/display/drm_hdmi_state_helper.h>
>
> /**
> * DOC: overview
> @@ -87,6 +88,13 @@ struct drm_bridge_connector {
> * connector modes detection, if any (see &DRM_BRIDGE_OP_MODES).
> */
> struct drm_bridge *bridge_modes;
> + /**
> + * @bridge_hdmi:
> + *
> + * The bridge in the chain that implements necessary support for the
> + * HDMI connector infrastructure, if any (see &DRM_BRIDGE_OP_HDMI).
> + */
> + struct drm_bridge *bridge_hdmi;
> };
>
> #define to_drm_bridge_connector(x) \
> @@ -287,6 +295,61 @@ static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs
> .disable_hpd = drm_bridge_connector_disable_hpd,
> };
>
> +static enum drm_mode_status
> +drm_bridge_connector_tmds_char_rate_valid(const struct drm_connector *connector,
> + const struct drm_display_mode *mode,
> + unsigned long long tmds_rate)
> +{
> + struct drm_bridge_connector *bridge_connector =
> + to_drm_bridge_connector(connector);
> + struct drm_bridge *bridge;
> +
> + bridge = bridge_connector->bridge_hdmi;
> + if (bridge)
> + return bridge->funcs->tmds_char_rate_valid ?
> + bridge->funcs->tmds_char_rate_valid(bridge, mode, tmds_rate) :
> + MODE_OK;
> +
> + return MODE_ERROR;
> +}

It's kind of a nitpick, but I think the following syntax is clearer:

if (bridge)
if (bridge->funcs->tmds_char_rate_valid)
return bridge->funcs->tmds_char_rate_valid(bridge, mode, tmds_rate);
else
return MODE_OK;

return MODE_ERROR;

> +static int drm_bridge_connector_clear_infoframe(struct drm_connector *connector,
> + enum hdmi_infoframe_type type)
> +{
> + struct drm_bridge_connector *bridge_connector =
> + to_drm_bridge_connector(connector);
> + struct drm_bridge *bridge;
> +
> + bridge = bridge_connector->bridge_hdmi;
> + if (bridge)
> + return bridge->funcs->clear_infoframe ?
> + bridge->funcs->clear_infoframe(bridge, type) :
> + 0;
> +
> + return -EINVAL;
> +}
> +
> +static int drm_bridge_connector_write_infoframe(struct drm_connector *connector,
> + enum hdmi_infoframe_type type,
> + const u8 *buffer, size_t len)
> +{
> + struct drm_bridge_connector *bridge_connector =
> + to_drm_bridge_connector(connector);
> + struct drm_bridge *bridge;
> +
> + bridge = bridge_connector->bridge_hdmi;
> + if (bridge)
> + return bridge->funcs->write_infoframe(bridge, type, buffer, len);
> +
> + return -EINVAL;
> +}
> +
> +static const struct drm_connector_hdmi_funcs drm_bridge_connector_hdmi_funcs = {
> + .tmds_char_rate_valid = drm_bridge_connector_tmds_char_rate_valid,
> + .clear_infoframe = drm_bridge_connector_clear_infoframe,
> + .write_infoframe = drm_bridge_connector_write_infoframe,
> +};
> +
> /* -----------------------------------------------------------------------------
> * Bridge Connector Initialisation
> */
> @@ -312,6 +375,10 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> struct drm_connector *connector;
> struct i2c_adapter *ddc = NULL;
> struct drm_bridge *bridge, *panel_bridge = NULL;
> + const char *vendor = "Unknown";
> + const char *product = "Unknown";
> + unsigned int supported_formats = BIT(HDMI_COLORSPACE_RGB);
> + unsigned int max_bpc = 8;
> int connector_type;
> int ret;
>
> @@ -348,6 +415,25 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> bridge_connector->bridge_detect = bridge;
> if (bridge->ops & DRM_BRIDGE_OP_MODES)
> bridge_connector->bridge_modes = bridge;
> + if (bridge->ops & DRM_BRIDGE_OP_HDMI) {
> + if (bridge_connector->bridge_hdmi)
> + return ERR_PTR(-EBUSY);
> + if (!bridge->funcs->write_infoframe)
> + return ERR_PTR(-EINVAL);
> +
> + bridge_connector->bridge_hdmi = bridge;
> +
> + if (bridge->supported_formats)
> + supported_formats = bridge->supported_formats;
> + if (bridge->max_bpc)
> + max_bpc = bridge->max_bpc;
> + }
> +
> + if (bridge->vendor)
> + vendor = bridge->vendor;
> +
> + if (bridge->product)
> + product = bridge->product;

I don't think we should allow HDMI bridges without a product or vendor.
We should at the very least warn or return an error there, preferably
the latter to start with. We can always relax the rule if it turns out
to be too strict later on.

> if (!drm_bridge_get_next_bridge(bridge))
> connector_type = bridge->type;
> @@ -370,9 +456,18 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> return ERR_PTR(-EINVAL);
> }
>
> - ret = drmm_connector_init(drm, connector,
> - &drm_bridge_connector_funcs,
> - connector_type, ddc);
> + if (bridge_connector->bridge_hdmi)
> + ret = drmm_connector_hdmi_init(drm, connector,
> + vendor, product,
> + &drm_bridge_connector_funcs,
> + &drm_bridge_connector_hdmi_funcs,
> + connector_type, ddc,
> + supported_formats,
> + max_bpc);
> + else
> + ret = drmm_connector_init(drm, connector,
> + &drm_bridge_connector_funcs,
> + connector_type, ddc);
> if (ret) {
> kfree(bridge_connector);
> return ERR_PTR(ret);
> diff --git a/drivers/gpu/drm/drm_debugfs.c b/drivers/gpu/drm/drm_debugfs.c
> index dd39a5b7a711..e385d90ef893 100644
> --- a/drivers/gpu/drm/drm_debugfs.c
> +++ b/drivers/gpu/drm/drm_debugfs.c
> @@ -762,6 +762,8 @@ static int bridges_show(struct seq_file *m, void *data)
> drm_puts(&p, " hpd");
> if (bridge->ops & DRM_BRIDGE_OP_MODES)
> drm_puts(&p, " modes");
> + if (bridge->ops & DRM_BRIDGE_OP_HDMI)
> + drm_puts(&p, " hdmi");
> drm_puts(&p, "\n");
> }
>
> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> index 4baca0d9107b..c45e539fe276 100644
> --- a/include/drm/drm_bridge.h
> +++ b/include/drm/drm_bridge.h
> @@ -630,6 +630,54 @@ struct drm_bridge_funcs {
> */
> void (*hpd_disable)(struct drm_bridge *bridge);
>
> + /**
> + * @tmds_char_rate_valid:
> + *
> + * Check whether a particular TMDS character rate is supported by the
> + * driver.
> + *
> + * This callback is optional and should only be implemented by the
> + * bridges that take part in the HDMI connector implementation. Bridges
> + * that implement it shall set set the DRM_BRIDGE_OP_HDMI flag in their
> + * &drm_bridge->ops.
> + *
> + * 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_bridge *bridge,
> + const struct drm_display_mode *mode,
> + unsigned long long tmds_rate);

Would an HDMI prefix make sense here?

> + /**
> + * @clear_infoframe:
> + *
> + * This callback clears the infoframes in the hardware during commit.
> + * It will be called multiple times, once for every disabled infoframe
> + * type.
> + *
> + * This callback is optional and should only be implemented by the
> + * bridges that take part in the HDMI connector implementation. Bridges
> + * that implement it shall set set the DRM_BRIDGE_OP_HDMI flag in their
> + * &drm_bridge->ops.
> + */
> + int (*clear_infoframe)(struct drm_bridge *bridge,
> + enum hdmi_infoframe_type type);

Missing newline there.

> + /**
> + * @write_infoframe:
> + *
> + * Program the infoframe into the hardware. It will be called multiple
> + * times, once for every updated infoframe type.
> + *
> + * This callback is optional but it must be implemented by bridges that
> + * set the DRM_BRIDGE_OP_HDMI flag in their &drm_bridge->ops.
> + */
> + int (*write_infoframe)(struct drm_bridge *bridge,
> + enum hdmi_infoframe_type type,
> + const u8 *buffer, size_t len);
> +
> /**
> * @debugfs_init:
> *
> @@ -705,6 +753,16 @@ enum drm_bridge_ops {
> * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> */
> DRM_BRIDGE_OP_MODES = BIT(3),
> + /**
> + * @DRM_BRIDGE_OP_HDMI: The bridge provides HDMI connector operations,
> + * including infoframes support. Bridges that set this flag must
> + * implement the &drm_bridge_funcs->write_infoframe callback.
> + *
> + * Note: currently there can be at most one bridge in a chain that sets
> + * this bit. This is to simplify corresponding glue code in connector
> + * drivers.
> + */
> + DRM_BRIDGE_OP_HDMI = BIT(4),
> };
>
> /**
> @@ -773,6 +831,30 @@ struct drm_bridge {
> * @hpd_cb.
> */
> void *hpd_data;
> +
> + /**
> + * @vendor: Vendor of the product to be used for the SPD InfoFrame
> + * generation.
> + */
> + const char *vendor;
> +
> + /**
> + * @product: Name of the product to be used for the SPD InfoFrame
> + * generation.
> + */
> + const char *product;
> +
> + /**
> + * @supported_formats: Bitmask of @hdmi_colorspace listing supported
> + * output formats. This is only relevant if @DRM_BRIDGE_OP_HDMI is set.
> + */
> + unsigned int supported_formats;
> +
> + /**
> + * @max_bpc: Maximum bits per char the HDMI bridge supports. This is
> + * only relevant if @DRM_BRIDGE_OP_HDMI is set.
> + */

We could probably document that the only allowed values are 8, 10 or 12.

Thanks!
Maxime

Attachment: signature.asc
Description: PGP signature