[PATCH RFC 14/19] drm/bridge: Add the necessary bits to support bus format negotiation

Neil Armstrong narmstrong at baylibre.com
Thu Aug 22 09:29:40 UTC 2019


On 22/08/2019 09:48, Boris Brezillon wrote:
> On Thu, 22 Aug 2019 03:55:56 +0300
> Laurent Pinchart <laurent.pinchart at ideasonboard.com> wrote:
> 
>> Hi Boris,
>>
>> Thank you for the patch.
>>
>> On Thu, Aug 08, 2019 at 05:11:45PM +0200, Boris Brezillon wrote:
>>> This takes the form of various helpers, plus the addition of 2 new
>>> structs:
>>> * drm_bus_caps: describe the bus capabilities of a bridge/encoder. For
>>>   bridges we have one for the input port and one for the output port.
>>>   Encoders just have one output port.
>>> * drm_bus_cfg: added to the drm_bridge_state. It's supposed to store
>>>   bus configuration information. For now we just have format and flags
>>>   but more fields might be added in the future. Again, each
>>>   drm_bridge_state contains 2 drm_bus_cfg elements: one for the output
>>>   port and one for the input port
>>>
>>> Helpers can be used from bridge drivers' ->atomic_check() implementation
>>> to negotiate the bus format to use. Negotiation happens in reserve order,  
>>
>> s/reserve/reverse/
>>
>>> starting from the last element of the bridge chain (the one directly
>>> connected to the display) to the first element of the chain (the one
>>> connected to the encoder).
>>>
>>> Negotiation usually takes place in 2 steps:
>>> * make sure the input end of the next element in the chain picked a
>>>   format that's supported by the output end of our bridge. That's done
>>>   with drm_atomic_bridge_choose_output_bus_cfg().
>>> * choose a format for the input end of our bridge based on what's
>>>   supported by the previous bridge in the chain. This is achieved by
>>>   calling drm_atomic_bridge_choose_input_bus_cfg()
>>>
>>> Note that those helpers are a bit lax when it comes to unitialized
>>> bus format validation. That's intentional. If we don't do that we'll
>>> basically break things if we start adding support for bus format
>>> negotiation to some elements of the pipeline leaving others on the
>>> side, and that's likely to happen for all external bridges since we
>>> can't necessarily tell where they are used.
>>>
>>> Signed-off-by: Boris Brezillon <boris.brezillon at collabora.com>
>>> ---
>>>  drivers/gpu/drm/drm_bridge.c | 187 +++++++++++++++++++++++++++++++++++
>>>  include/drm/drm_bridge.h     |  46 +++++++++
>>>  include/drm/drm_encoder.h    |   6 ++
>>>  3 files changed, 239 insertions(+)
>>>
>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>> index 9efb27087e70..ef072df42422 100644
>>> --- a/drivers/gpu/drm/drm_bridge.c
>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>> @@ -602,6 +602,193 @@ void drm_atomic_bridge_chain_enable(struct drm_encoder *encoder,
>>>  }
>>>  EXPORT_SYMBOL(drm_atomic_bridge_chain_enable);
>>>  
>>> +int drm_find_best_bus_format(const struct drm_bus_caps *a,
>>> +			     const struct drm_bus_caps *b,
>>> +			     const struct drm_display_mode *mode,
>>> +			     u32 *selected_bus_fmt)
>>> +{
>>> +	unsigned int i, j;
>>> +
>>> +	/*
>>> +	 * Some drm_bridge/drm_encoder don't care about the input/output bus
>>> +	 * format, let's set the format to zero in that case (this is only
>>> +	 * valid if both side of the link don't care).
>>> +	 */
>>> +	if (!a->num_supported_fmts && !b->num_supported_fmts) {
>>> +		*selected_bus_fmt = 0;
>>> +		return 0;
>>> +	} else if (b->num_supported_fmts > 1 && b->supported_fmts) {
>>> +		*selected_bus_fmt = b->supported_fmts[0];
>>> +		return 0;
>>> +	} else if (a->num_supported_fmts > 1 && a->supported_fmts) {
>>> +		*selected_bus_fmt = a->supported_fmts[0];
>>> +		return 0;
>>> +	} else if (!a->num_supported_fmts || !a->supported_fmts ||
>>> +		   !b->num_supported_fmts || !b->supported_fmts) {
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	/*
>>> +	 * TODO:
>>> +	 * Dummy algo picking the first match. We probably want something
>>> +	 * smarter where the narrowest format (in term of bus width) that
>>> +	 * does not incur data loss is picked, and if all possible formats
>>> +	 * are lossy, pick the one that's less lossy among all the choices
>>> +	 * we have. In order to do that we'd need to convert MEDIA_BUS_FMT_
>>> +	 * modes into something like drm_format_info.
>>> +	 */
>>> +	for (i = 0; i < a->num_supported_fmts; i++) {
>>> +		for (j = 0; j < b->num_supported_fmts; j++) {
>>> +			if (a->supported_fmts[i] == b->supported_fmts[j]) {
>>> +				*selected_bus_fmt = a->supported_fmts[i];
>>> +				return 0;
>>> +			}
>>> +		}
>>> +	}
>>> +
>>> +	return -EINVAL;
>>> +}
>>> +EXPORT_SYMBOL(drm_find_best_bus_format);
>>> +
>>> +/**
>>> + * drm_atomic_bridge_choose_input_bus_cfg() - Choose bus config for the input
>>> + *					      end
>>> + * @bridge_state: bridge state
>>> + * @crtc_state: CRTC state
>>> + * @conn_state: connector state
>>> + *
>>> + * Choose a bus format for the input side of a bridge based on what the
>>> + * previous bridge in the chain and this bridge support. Can be called from
>>> + * bridge drivers' &drm_bridge_funcs.atomic_check() implementation.
>>> + *
>>> + * RETURNS:
>>> + * 0 if a matching format was found, a negative error code otherwise
>>> + */
>>> +int
>>> +drm_atomic_bridge_choose_input_bus_cfg(struct drm_bridge_state *bridge_state,
>>> +				       struct drm_crtc_state *crtc_state,
>>> +				       struct drm_connector_state *conn_state)
>>> +{
>>> +	struct drm_bridge *self = bridge_state->bridge;
>>> +	struct drm_bus_caps *prev_output_bus_caps;
>>> +	struct drm_bridge *prev;
>>> +	int ret;
>>> +
>>> +	prev = drm_bridge_chain_get_prev_bridge(self);
>>> +	if (!prev)
>>> +		prev_output_bus_caps = &self->encoder->output_bus_caps;
>>> +	else
>>> +		prev_output_bus_caps = &prev->output_bus_caps;
>>> +
>>> +	ret = drm_find_best_bus_format(prev_output_bus_caps,
>>> +				       &self->input_bus_caps, &crtc_state->mode,
>>> +				       &bridge_state->input_bus_cfg.fmt);
>>> +	if (ret)
>>> +		return ret;  
>>
>> I think most bridges that support multiple input and output formats will
>> not necessarily be able to transcode, meaning that for a particular
>> output format a particular corresponding input format will need to be
>> picked (or possibly one of a subset of input formats). I'm thus not sure
>> how useful this helper will be in practice.
>>
>> I also fear that this negotiation mechanism is too simple and will lock
>> is with support for very simple systems only :-S Let's assume a system
>> with a display device (D), two bridges (B0 and B1) and a connector (C).
>> Let's further assume that the bus format required by the connector is
>> fixed. With this patch series, B1 will pick an output format identical
>> to the format on the connector, and will then pick an input format among
>> the ones supported by both B0 and B1. Let's assume there are multiple
>> options, Fa and Fb, and drm_find_best_bus_format() will pick one of
>> them, say Fa. Then B0 will configure its output with that format, and
>> will proceed with finding an input format supported by D. It could be
>> that the output format Fa for B0 requires an input format that is not
>> supported by D, while Fb would have produced a working pipeline.
> 
> Yes, I assumed all pipeline elements would be able to convert any of
> their supported input formats to any of the output ones, which might
> not be the case in practice. I could add a matrix to expose the
> supported conversions and add a pass of 'format eviction' at bridge
> attach time, but I'm not sure that's enough.
> 
>>
>> I don't think this example is really far-fetched, and while we don't
>> need to support it immediately, we need an API that makes it possible to
>> support it, as changing the API later would require reworking all the
>> drivers that use it.
>>
>>> +
>>> +	/*
>>> +	 * TODO:
>>> +	 * Should we fill/check the ->flag field too?
>>> +	 */
>>> +	return 0;
>>> +}
>>> +EXPORT_SYMBOL(drm_atomic_bridge_choose_input_bus_cfg);
>>> +
>>> +/**
>>> + * drm_atomic_bridge_choose_output_bus_cfg() - Choose bus config for the output
>>> + *					       end
>>> + * @bridge_state: bridge state
>>> + * @crtc_state: CRTC state
>>> + * @conn_state: connector state
>>> + *
>>> + * Choose a bus format for the output side of a bridge based on what the next
>>> + * bridge in the chain and this bridge support. Can be called from bridge
>>> + * drivers' &drm_bridge_funcs.atomic_check() implementation.
>>> + *
>>> + * RETURNS:
>>> + * 0 if a matching format was found, a negative error code otherwise
>>> + */
>>> +int
>>> +drm_atomic_bridge_choose_output_bus_cfg(struct drm_bridge_state *bridge_state,
>>> +					struct drm_crtc_state *crtc_state,
>>> +					struct drm_connector_state *conn_state)
>>> +{
>>> +	struct drm_atomic_state *state = crtc_state->state;
>>> +	struct drm_bridge *self = bridge_state->bridge;
>>> +	struct drm_bridge_state *next_bridge_state;
>>> +	struct drm_bridge *next;
>>> +	u32 fmt;
>>> +
>>> +	next = drm_bridge_chain_get_next_bridge(self);
>>> +	if (!next) {
>>> +		struct drm_connector *conn = conn_state->connector;
>>> +		struct drm_display_info *di = &conn->display_info;
>>> +
>>> +		/*
>>> +		 * FIXME:
>>> +		 * We currently pick the first supported format
>>> +		 * unconditionally. It seems to fit all current use cases but
>>> +		 * might be too limited for panels/displays that can configure
>>> +		 * the bus format dynamically.
>>> +		 */
>>> +		if (di->num_bus_formats && di->bus_formats)
>>> +			bridge_state->output_bus_cfg.fmt = di->bus_formats[0];
>>> +		else
>>> +			bridge_state->output_bus_cfg.fmt = 0;  
>>
>> What is the bridge driver supposed to do in this case ?
> 
> Pick a default value (or a something fixed by a FW property), as it used
> to do before this patchset. There's no way we can convert all bridges at
> once, so we need to support mixed chains formed of bridges that support
> bus format negotiation and bridges that don't.
> 
>>
>>> +
>>> +		bridge_state->output_bus_cfg.flags = di->bus_flags;
>>> +		return 0;
>>> +	}
>>> +
>>> +	/*
>>> +	 * We expect output_bus_caps to contain at least one format. Note
>>> +	 * that don't care about bus format negotiation can simply not
>>> +	 * call this helper.  
>>
>> I'm not sure to parse the second sentence correctly.
> 
> Oops.
> 
> "Note that drivers that don't care about ...".
> I'm just trying to explain why EINVAL is returned when
> ->output_bus_caps is not populated.
> 
>>
>>> +	 */
>>> +	if (!self->output_bus_caps.num_supported_fmts ||!  
>>
>> Extra ! at the end of the line ?
>>
>>> +	    !self->output_bus_caps.supported_fmts)
>>> +		return -EINVAL;
>>> +
>>> +	next_bridge_state = drm_atomic_get_new_bridge_state(state, next);
>>> +
>>> +	/*
>>> +	 * The next bridge is expected to have called
>>> +	 * &drm_atomic_bridge_choose_input_bus_cfg() as part of its
>>> +	 * &drm_bridge_funcs.atomic_check() implementation, but this hook is
>>> +	 * optional, and even if it's implemented, calling
>>> +	 * &drm_atomic_bridge_choose_input_bus_cfg() is not mandated.
>>> +	 * If fmt is zero, that means the next element in the chain doesn't
>>> +	 * care about bus format negotiation (probably because there's only
>>> +	 * one possible setting). In that case, we still have to select one
>>> +	 * bus format for the output port of our bridge, and this is only
>>> +	 * possible if the bridge supports only one format.  
>>
>> Theoritically we could also just pick the first one (or actually any of
>> the supported formats), couldn't we ?
> 
> Hm, not really. I mean, if the bridge supports several output formats,
> there's no way we can tell which one is the right one for this
> specific setup. The idea being that drivers are responsible for picking
> an appropriate output bus-format based on something else (DT prop?)
> when drm_atomic_bridge_choose_output_bus_cfg() returns an error.
> 
>>
>>
>>> +	 */
>>> +	fmt = next_bridge_state->input_bus_cfg.fmt;
>>> +	if (fmt) {
>>> +		unsigned int i;
>>> +
>>> +		for (i = 0; i < self->output_bus_caps.num_supported_fmts; i++) {
>>> +			if (self->output_bus_caps.supported_fmts[i] == fmt)
>>> +				break;
>>> +		}
>>> +
>>> +		if (i == self->output_bus_caps.num_supported_fmts)
>>> +			return -ENOTSUPP;
>>> +	} else if (self->output_bus_caps.num_supported_fmts == 1) {
>>> +		fmt = self->output_bus_caps.supported_fmts[0];
>>> +	} else {
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	/*
>>> +	 * TODO:
>>> +	 * Should we fill/check the ->flag field too?
>>> +	 */
>>> +	bridge_state->output_bus_cfg.fmt = fmt;
>>> +	return 0;
>>> +}
>>> +EXPORT_SYMBOL(drm_atomic_bridge_choose_output_bus_cfg);
>>> +
>>>  static int drm_atomic_bridge_check(struct drm_bridge *bridge,
>>>  				   struct drm_crtc_state *crtc_state,
>>>  				   struct drm_connector_state *conn_state)
>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>> index 2f69adb7b0f3..c578800a05e0 100644
>>> --- a/include/drm/drm_bridge.h
>>> +++ b/include/drm/drm_bridge.h
>>> @@ -33,15 +33,45 @@ struct drm_bridge;
>>>  struct drm_bridge_timings;
>>>  struct drm_panel;
>>>  
>>> +/**
>>> + * struct drm_bus_cfg - bus configuration
>>> + * @fmt: format used on this bus
>>> + * @flags: DRM_BUS_ flags used on this bus
>>> + *
>>> + * Encodes the bus format and bus flags used by one end of the bridge or
>>> + * by the encoder output.
>>> + */
>>> +struct drm_bus_cfg {
>>> +	u32 fmt;
>>> +	u32 flags;
>>> +};
>>> +
>>> +/**
>>> + * struct drm_bus_caps - bus capabilities
>>> + * @supported_fmts: array of MEDIA_BUS_FMT_ formats
>>> + * @num_supported_fmts: size of the supported_fmts array
>>> + *
>>> + * Used by the core to negotiate the bus format at runtime.
>>> + */
>>> +struct drm_bus_caps {
>>> +	const u32 *supported_fmts;
>>> +	unsigned int num_supported_fmts;
>>> +};
>>> +
>>>  /**
>>>   * struct drm_bridge_state - Atomic bridge state object
>>>   * @base: inherit from &drm_private_state
>>>   * @bridge: the bridge this state refers to
>>> + * @input_bus_info: input bus information
>>> + * @output_bus_info: output bus information  
>>
>> I would make this an array, to prepare for bridges that will have more
>> than one input or one output.
> 
> Hm, not sure how you'd represent that. I guess you want to support
> bridges that can activate several inputs+outputs in parallel. Do we
> even support that right now?
> Also, how do we link the input to the output in that case? By their
> position in the array? And we'd need an extra field in bus_caps to
> point to the corresponding bridge input/output port.
> 
> I'm glad we can discuss all those problems, but I'd like to focus on
> what's needed right now, not what could potentially be needed in the
> future. If we can design things to be more future-proof that's great,
> but in my experience, trying to solve problems that do not exist yet
> often leads to complex designs which prove to be wrong when we try to
> apply them to real situations when they finally occur.

I had a thought when implementing bus format negotiation for dw-hdmi.

Depending on the output bus-format, we could have different sets of possible
input drm_bus_caps.

For example, if output is MEDIA_BUS_FMT_RGB888_1X24,
the input bus formats could only be MEDIA_BUS_FMT_RGB888_1X24 or MEDIA_BUS_FMT_YUV8_1X24.
Same for MEDIA_BUS_FMT_RGB101010_1X30, MEDIA_BUS_FMT_RGB121212_1X36, MEDIA_BUS_FMT_RGB161616_1X48...
And the special MEDIA_BUS_FMT_UYYVYY8_0_5X24, where input must be output.

How could we handle this ? I mean, could we have a fixed
output drm_bus_caps and be able to dynamically change the input drm_bus_caps ?

Adding matrix in struct drm_bridge seems over-engineered, no ? Since it should take
in account the actual drm_display_mode.

Maybe by passing a dynamic input drm_bus_caps to drm_atomic_bridge_choose_input_bus_cfg()
and take the default struct drm_bridge one if not provided ?
This would really simplify the bridge atomic_check implementation by reusing
the drm_atomic_bridge_choose_*() functions.

Neil

> 
>>
>>>   */
>>>  struct drm_bridge_state {
>>>  	struct drm_private_state base;
>>>  
>>>  	struct drm_bridge *bridge;
>>> +
>>> +	struct drm_bus_cfg input_bus_cfg;
>>> +	struct drm_bus_cfg output_bus_cfg;
>>>  };
>>>  
>>>  static inline struct drm_bridge_state *
>>> @@ -465,6 +495,9 @@ struct drm_bridge {
>>>  	const struct drm_bridge_funcs *funcs;
>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>  	void *driver_private;
>>> +
>>> +	struct drm_bus_caps input_bus_caps;
>>> +	struct drm_bus_caps output_bus_caps;
>>>  };
>>>  
>>>  static inline struct drm_bridge *
>>> @@ -479,6 +512,14 @@ struct drm_bridge *of_drm_find_bridge(struct device_node *np);
>>>  int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
>>>  		      struct drm_bridge *previous);
>>>  
>>> +int
>>> +drm_atomic_bridge_choose_input_bus_cfg(struct drm_bridge_state *bridge_state,
>>> +				       struct drm_crtc_state *crtc_state,
>>> +				       struct drm_connector_state *conn_state);
>>> +int
>>> +drm_atomic_bridge_choose_output_bus_cfg(struct drm_bridge_state *bridge_state,
>>> 					struct drm_crtc_state *crtc_state,
>>> +					struct drm_connector_state *conn_state);
>>>  struct drm_bridge *
>>>  drm_bridge_chain_get_first_bridge(struct drm_encoder *encoder);
>>>  struct drm_bridge *
>>> @@ -562,6 +603,11 @@ drm_atomic_get_new_bridge_state(struct drm_atomic_state *state,
>>>  	return drm_priv_to_bridge_state(obj_state);
>>>  }
>>>  
>>> +int drm_find_best_bus_format(const struct drm_bus_caps *a,
>>> +			     const struct drm_bus_caps *b,
>>> +			     const struct drm_display_mode *mode,
>>> +			     u32 *selected_bus_fmt);
>>> +
>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>  					u32 connector_type);
>>> diff --git a/include/drm/drm_encoder.h b/include/drm/drm_encoder.h
>>> index 0899f7f34e3a..e891921b3aed 100644
>>> --- a/include/drm/drm_encoder.h
>>> +++ b/include/drm/drm_encoder.h
>>> @@ -25,6 +25,7 @@
>>>  
>>>  #include <linux/list.h>
>>>  #include <linux/ctype.h>
>>> +#include <drm/drm_bridge.h>
>>>  #include <drm/drm_crtc.h>
>>>  #include <drm/drm_mode.h>
>>>  #include <drm/drm_mode_object.h>
>>> @@ -184,6 +185,11 @@ struct drm_encoder {
>>>  	 * functions as part of its encoder enable/disable handling.
>>>  	 */
>>>  	uint32_t custom_bridge_enable_disable_seq : 1;
>>> +
>>> +	/**
>>> +	 * @output_bus_caps: Supported output bus caps
>>> +	 */
>>> +	struct drm_bus_caps output_bus_caps;
>>>  };
>>>  
>>>  #define obj_to_encoder(x) container_of(x, struct drm_encoder, base)
>>>   
>>
> 



More information about the dri-devel mailing list