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

From: Dmitry Baryshkov
Date: Thu May 30 2024 - 07:29:27 EST


On Thu, May 30, 2024 at 11:01:26AM +0200, Maxime Ripard wrote:
> 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;

Ack

>
> > +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.

My goal was to be able to override the vendor/product after the HDMI
bridge. Something like setting 'Qualcomm / Snapdragon' in HDMI driver,
but then e.g. display-connector overriding it to e.g. '96board /
DB820c'. Maybe it's an overkill and we should just set vendor / product
from the HDMI bridge.

>
> > 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?

Yes, and in other callbacks too.

>
> > + /**
> > + * @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



--
With best wishes
Dmitry