[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