Skip to content

Commit

Permalink
drm/bridge: Add the necessary bits to support bus format negotiation
Browse files Browse the repository at this point in the history
drm_bridge_state is extended to describe the input and output bus
configurations. These bus configurations are exposed through the
drm_bus_cfg struct which encodes the configuration of a physical
bus between two components in an output pipeline, usually between
two bridges, an encoder and a bridge, or a bridge and a connector.

The bus configuration is stored in drm_bridge_state separately for
the input and output buses, as seen from the point of view of each
bridge. The bus configuration of a bridge output is usually identical
to the configuration of the next bridge's input, but may differ if
the signals are modified between the two bridges, for instance by an
inverter on the board. The input and output configurations of a
bridge may differ if the bridge modifies the signals internally,
for instance by performing format conversion, or*modifying signals
polarities.

Bus format negotiation is automated by the core, drivers just have
to implement the ->atomic_get_{output,input}_bus_fmts() hooks if they
want to take part to this negotiation. Negotiation happens in reverse
order, starting from the last element of the chain (the one directly
connected to the display) up to the first element of the chain (the one
connected to the encoder).
During this negotiation all supported formats are tested until we find
one that works, meaning that the formats array should be in decreasing
preference order (assuming the driver has a preference order).

Note that the bus format negotiation works even if some elements in the
chain don't implement the ->atomic_get_{output,input}_bus_fmts() hooks.
In that case, the core advertises only MEDIA_BUS_FMT_FIXED and lets
the previous bridge element decide what to do (most of the time, bridge
drivers will pick a default bus format or extract this piece of
information from somewhere else, like a FW property).

v10:
* Add changelog to the commit message

v9:
* No changes

v8:
* Fix a test in drm_atomic_bridge_chain_select_bus_fmts() (Reported by
  Jonas)

v7:
* Adapt the code to deal with the fact that not all bridges in the
  chain have a bridge state

v5 -> v6:
* No changes

v4:
* Enhance the doc
* Fix typos
* Rename some parameters/fields
* Reword the commit message

v3:
* Fix the commit message (Reported by Laurent)
* Document the fact that bus formats should not be directly modified by
  drivers (Suggested by Laurent)
* Document the fact that format order matters (Suggested by Laurent)
* Propagate bus flags by default
* Document the fact that drivers can tweak bus flags if needed
* Let ->atomic_get_{output,input}_bus_fmts() allocate the bus format
  array (Suggested by Laurent)
* Add a drm_atomic_helper_bridge_propagate_bus_fmt()
* Mandate that bridge drivers return accurate input_fmts even if they
  are known to be the first element in the bridge chain

v2:
* Rework things to support more complex use cases

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
[narmstrong: fixed doc in include/drm/drm_bridge.h:69 fmt->format]
Reviewed-by: Jernej Skrabec <jernej.skrabec@siol.net>
Tested-by: Jonas Karlman <jonas@kwiboo.se>
Link: https://patchwork.freedesktop.org/patch/msgid/20200128135514.108171-7-boris.brezillon@collabora.com
  • Loading branch information
Boris Brezillon committed Jan 31, 2020
1 parent 5061b8a commit f32df58
Show file tree
Hide file tree
Showing 5 changed files with 425 additions and 1 deletion.
41 changes: 41 additions & 0 deletions drivers/gpu/drm/drm_atomic_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -3536,3 +3536,44 @@ int drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc,
return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_legacy_gamma_set);

/**
* drm_atomic_helper_bridge_propagate_bus_fmt() - Propagate output format to
* the input end of a bridge
* @bridge: bridge control structure
* @bridge_state: new bridge state
* @crtc_state: new CRTC state
* @conn_state: new connector state
* @output_fmt: tested output bus format
* @num_input_fmts: will contain the size of the returned array
*
* This helper is a pluggable implementation of the
* &drm_bridge_funcs.atomic_get_input_bus_fmts operation for bridges that don't
* modify the bus configuration between their input and their output. It
* returns an array of input formats with a single element set to @output_fmt.
*
* RETURNS:
* a valid format array of size @num_input_fmts, or NULL if the allocation
* failed
*/
u32 *
drm_atomic_helper_bridge_propagate_bus_fmt(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state,
u32 output_fmt,
unsigned int *num_input_fmts)
{
u32 *input_fmts;

input_fmts = kzalloc(sizeof(*input_fmts), GFP_KERNEL);
if (!input_fmts) {
*num_input_fmts = 0;
return NULL;
}

*num_input_fmts = 1;
input_fmts[0] = output_fmt;
return input_fmts;
}
EXPORT_SYMBOL(drm_atomic_helper_bridge_propagate_bus_fmt);
253 changes: 252 additions & 1 deletion drivers/gpu/drm/drm_bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -628,13 +628,247 @@ static int drm_atomic_bridge_check(struct drm_bridge *bridge,
return 0;
}

static int select_bus_fmt_recursive(struct drm_bridge *first_bridge,
struct drm_bridge *cur_bridge,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state,
u32 out_bus_fmt)
{
struct drm_bridge_state *cur_state;
unsigned int num_in_bus_fmts, i;
struct drm_bridge *prev_bridge;
u32 *in_bus_fmts;
int ret;

prev_bridge = drm_bridge_get_prev_bridge(cur_bridge);
cur_state = drm_atomic_get_new_bridge_state(crtc_state->state,
cur_bridge);

/*
* If bus format negotiation is not supported by this bridge, let's
* pass MEDIA_BUS_FMT_FIXED to the previous bridge in the chain and
* hope that it can handle this situation gracefully (by providing
* appropriate default values).
*/
if (!cur_bridge->funcs->atomic_get_input_bus_fmts) {
if (cur_bridge != first_bridge) {
ret = select_bus_fmt_recursive(first_bridge,
prev_bridge, crtc_state,
conn_state,
MEDIA_BUS_FMT_FIXED);
if (ret)
return ret;
}

/*
* Driver does not implement the atomic state hooks, but that's
* fine, as long as it does not access the bridge state.
*/
if (cur_state) {
cur_state->input_bus_cfg.format = MEDIA_BUS_FMT_FIXED;
cur_state->output_bus_cfg.format = out_bus_fmt;
}

return 0;
}

/*
* If the driver implements ->atomic_get_input_bus_fmts() it
* should also implement the atomic state hooks.
*/
if (WARN_ON(!cur_state))
return -EINVAL;

in_bus_fmts = cur_bridge->funcs->atomic_get_input_bus_fmts(cur_bridge,
cur_state,
crtc_state,
conn_state,
out_bus_fmt,
&num_in_bus_fmts);
if (!num_in_bus_fmts)
return -ENOTSUPP;
else if (!in_bus_fmts)
return -ENOMEM;

if (first_bridge == cur_bridge) {
cur_state->input_bus_cfg.format = in_bus_fmts[0];
cur_state->output_bus_cfg.format = out_bus_fmt;
kfree(in_bus_fmts);
return 0;
}

for (i = 0; i < num_in_bus_fmts; i++) {
ret = select_bus_fmt_recursive(first_bridge, prev_bridge,
crtc_state, conn_state,
in_bus_fmts[i]);
if (ret != -ENOTSUPP)
break;
}

if (!ret) {
cur_state->input_bus_cfg.format = in_bus_fmts[i];
cur_state->output_bus_cfg.format = out_bus_fmt;
}

kfree(in_bus_fmts);
return ret;
}

/*
* This function is called by &drm_atomic_bridge_chain_check() just before
* calling &drm_bridge_funcs.atomic_check() on all elements of the chain.
* It performs bus format negotiation between bridge elements. The negotiation
* happens in reverse order, starting from the last element in the chain up to
* @bridge.
*
* Negotiation starts by retrieving supported output bus formats on the last
* bridge element and testing them one by one. The test is recursive, meaning
* that for each tested output format, the whole chain will be walked backward,
* and each element will have to choose an input bus format that can be
* transcoded to the requested output format. When a bridge element does not
* support transcoding into a specific output format -ENOTSUPP is returned and
* the next bridge element will have to try a different format. If none of the
* combinations worked, -ENOTSUPP is returned and the atomic modeset will fail.
*
* This implementation is relying on
* &drm_bridge_funcs.atomic_get_output_bus_fmts() and
* &drm_bridge_funcs.atomic_get_input_bus_fmts() to gather supported
* input/output formats.
*
* When &drm_bridge_funcs.atomic_get_output_bus_fmts() is not implemented by
* the last element of the chain, &drm_atomic_bridge_chain_select_bus_fmts()
* tries a single format: &drm_connector.display_info.bus_formats[0] if
* available, MEDIA_BUS_FMT_FIXED otherwise.
*
* When &drm_bridge_funcs.atomic_get_input_bus_fmts() is not implemented,
* &drm_atomic_bridge_chain_select_bus_fmts() skips the negotiation on the
* bridge element that lacks this hook and asks the previous element in the
* chain to try MEDIA_BUS_FMT_FIXED. It's up to bridge drivers to decide what
* to do in that case (fail if they want to enforce bus format negotiation, or
* provide a reasonable default if they need to support pipelines where not
* all elements support bus format negotiation).
*/
static int
drm_atomic_bridge_chain_select_bus_fmts(struct drm_bridge *bridge,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct drm_connector *conn = conn_state->connector;
struct drm_encoder *encoder = bridge->encoder;
struct drm_bridge_state *last_bridge_state;
unsigned int i, num_out_bus_fmts;
struct drm_bridge *last_bridge;
u32 *out_bus_fmts;
int ret = 0;

last_bridge = list_last_entry(&encoder->bridge_chain,
struct drm_bridge, chain_node);
last_bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state,
last_bridge);

if (last_bridge->funcs->atomic_get_output_bus_fmts) {
const struct drm_bridge_funcs *funcs = last_bridge->funcs;

/*
* If the driver implements ->atomic_get_output_bus_fmts() it
* should also implement the atomic state hooks.
*/
if (WARN_ON(!last_bridge_state))
return -EINVAL;

out_bus_fmts = funcs->atomic_get_output_bus_fmts(last_bridge,
last_bridge_state,
crtc_state,
conn_state,
&num_out_bus_fmts);
if (!num_out_bus_fmts)
return -ENOTSUPP;
else if (!out_bus_fmts)
return -ENOMEM;
} else {
num_out_bus_fmts = 1;
out_bus_fmts = kmalloc(sizeof(*out_bus_fmts), GFP_KERNEL);
if (!out_bus_fmts)
return -ENOMEM;

if (conn->display_info.num_bus_formats &&
conn->display_info.bus_formats)
out_bus_fmts[0] = conn->display_info.bus_formats[0];
else
out_bus_fmts[0] = MEDIA_BUS_FMT_FIXED;
}

for (i = 0; i < num_out_bus_fmts; i++) {
ret = select_bus_fmt_recursive(bridge, last_bridge, crtc_state,
conn_state, out_bus_fmts[i]);
if (ret != -ENOTSUPP)
break;
}

kfree(out_bus_fmts);

return ret;
}

static void
drm_atomic_bridge_propagate_bus_flags(struct drm_bridge *bridge,
struct drm_connector *conn,
struct drm_atomic_state *state)
{
struct drm_bridge_state *bridge_state, *next_bridge_state;
struct drm_bridge *next_bridge;
u32 output_flags = 0;

bridge_state = drm_atomic_get_new_bridge_state(state, bridge);

/* No bridge state attached to this bridge => nothing to propagate. */
if (!bridge_state)
return;

next_bridge = drm_bridge_get_next_bridge(bridge);

/*
* Let's try to apply the most common case here, that is, propagate
* display_info flags for the last bridge, and propagate the input
* flags of the next bridge element to the output end of the current
* bridge when the bridge is not the last one.
* There are exceptions to this rule, like when signal inversion is
* happening at the board level, but that's something drivers can deal
* with from their &drm_bridge_funcs.atomic_check() implementation by
* simply overriding the flags value we've set here.
*/
if (!next_bridge) {
output_flags = conn->display_info.bus_flags;
} else {
next_bridge_state = drm_atomic_get_new_bridge_state(state,
next_bridge);
/*
* No bridge state attached to the next bridge, just leave the
* flags to 0.
*/
if (next_bridge_state)
output_flags = next_bridge_state->input_bus_cfg.flags;
}

bridge_state->output_bus_cfg.flags = output_flags;

/*
* Propage the output flags to the input end of the bridge. Again, it's
* not necessarily what all bridges want, but that's what most of them
* do, and by doing that by default we avoid forcing drivers to
* duplicate the "dummy propagation" logic.
*/
bridge_state->input_bus_cfg.flags = output_flags;
}

/**
* drm_atomic_bridge_chain_check() - Do an atomic check on the bridge chain
* @bridge: bridge control structure
* @crtc_state: new CRTC state
* @conn_state: new connector state
*
* Calls &drm_bridge_funcs.atomic_check() (falls back on
* First trigger a bus format negotiation before calling
* &drm_bridge_funcs.atomic_check() (falls back on
* &drm_bridge_funcs.mode_fixup()) op for all the bridges in the encoder chain,
* starting from the last bridge to the first. These are called before calling
* &drm_encoder_helper_funcs.atomic_check()
Expand All @@ -646,16 +880,33 @@ int drm_atomic_bridge_chain_check(struct drm_bridge *bridge,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct drm_connector *conn = conn_state->connector;
struct drm_encoder *encoder;
struct drm_bridge *iter;
int ret;

if (!bridge)
return 0;

ret = drm_atomic_bridge_chain_select_bus_fmts(bridge, crtc_state,
conn_state);
if (ret)
return ret;

encoder = bridge->encoder;
list_for_each_entry_reverse(iter, &encoder->bridge_chain, chain_node) {
int ret;

/*
* Bus flags are propagated by default. If a bridge needs to
* tweak the input bus flags for any reason, it should happen
* in its &drm_bridge_funcs.atomic_check() implementation such
* that preceding bridges in the chain can propagate the new
* bus flags.
*/
drm_atomic_bridge_propagate_bus_flags(iter, conn,
crtc_state->state);

ret = drm_atomic_bridge_check(iter, crtc_state, conn_state);
if (ret)
return ret;
Expand Down
42 changes: 42 additions & 0 deletions include/drm/drm_atomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,38 @@ drm_atomic_crtc_effectively_active(const struct drm_crtc_state *state)
return state->active || state->self_refresh_active;
}

/**
* struct drm_bus_cfg - bus configuration
*
* This structure stores the configuration of a physical bus between two
* components in an output pipeline, usually between two bridges, an encoder
* and a bridge, or a bridge and a connector.
*
* The bus configuration is stored in &drm_bridge_state separately for the
* input and output buses, as seen from the point of view of each bridge. The
* bus configuration of a bridge output is usually identical to the
* configuration of the next bridge's input, but may differ if the signals are
* modified between the two bridges, for instance by an inverter on the board.
* The input and output configurations of a bridge may differ if the bridge
* modifies the signals internally, for instance by performing format
* conversion, or modifying signals polarities.
*/
struct drm_bus_cfg {
/**
* @format: format used on this bus (one of the MEDIA_BUS_FMT_* format)
*
* This field should not be directly modified by drivers
* (&drm_atomic_bridge_chain_select_bus_fmts() takes care of the bus
* format negotiation).
*/
u32 format;

/**
* @flags: DRM_BUS_* flags used on this bus
*/
u32 flags;
};

/**
* struct drm_bridge_state - Atomic bridge state object
*/
Expand All @@ -1008,6 +1040,16 @@ struct drm_bridge_state {
* @bridge: the bridge this state refers to
*/
struct drm_bridge *bridge;

/**
* @input_bus_cfg: input bus configuration
*/
struct drm_bus_cfg input_bus_cfg;

/**
* @output_bus_cfg: input bus configuration
*/
struct drm_bus_cfg output_bus_cfg;
};

static inline struct drm_bridge_state *
Expand Down
8 changes: 8 additions & 0 deletions include/drm/drm_atomic_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,12 @@ drm_atomic_plane_disabling(struct drm_plane_state *old_plane_state,
return old_plane_state->crtc && !new_plane_state->crtc;
}

u32 *
drm_atomic_helper_bridge_propagate_bus_fmt(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state,
u32 output_fmt,
unsigned int *num_input_fmts);

#endif /* DRM_ATOMIC_HELPER_H_ */
Loading

0 comments on commit f32df58

Please sign in to comment.