Skip to content

Commit

Permalink
drm/bridge: Introduce pre_enable_prev_first to alter bridge init order
Browse files Browse the repository at this point in the history
DSI sink devices typically want the DSI host powered up and configured
before they are powered up. pre_enable is the place this would normally
happen, but they are called in reverse order from panel/connector towards
the encoder, which is the "wrong" order.

Add a new flag pre_enable_prev_first that any bridge can set
to swap the order of pre_enable (and post_disable) for that and the
immediately previous bridge.
Should the immediately previous bridge also set the
pre_enable_prev_first flag, the previous bridge to that will be called
before either of those which requested pre_enable_prev_first.

eg:
- Panel
- Bridge 1
- Bridge 2 pre_enable_prev_first
- Bridge 3
- Bridge 4 pre_enable_prev_first
- Bridge 5 pre_enable_prev_first
- Bridge 6
- Encoder
Would result in pre_enable's being called as Panel, Bridge 1, Bridge 3,
Bridge 2, Bridge 6, Bridge 5, Bridge 4, Encoder.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
Tested-by: Frieder Schrempf <frieder.schrempf@kontron.de>
Reviewed-by: Frieder Schrempf <frieder.schrempf@kontron.de>
Link: https://lore.kernel.org/r/20221205173328.1395350-5-dave.stevenson@raspberrypi.com
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
  • Loading branch information
6by9 authored and mripard committed Dec 8, 2022
1 parent 4e910d9 commit 4fb912e
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 24 deletions.
145 changes: 121 additions & 24 deletions drivers/gpu/drm/drm_bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,25 @@ void drm_atomic_bridge_chain_disable(struct drm_bridge *bridge,
}
EXPORT_SYMBOL(drm_atomic_bridge_chain_disable);

static void drm_atomic_bridge_call_post_disable(struct drm_bridge *bridge,
struct drm_atomic_state *old_state)
{
if (old_state && bridge->funcs->atomic_post_disable) {
struct drm_bridge_state *old_bridge_state;

old_bridge_state =
drm_atomic_get_old_bridge_state(old_state,
bridge);
if (WARN_ON(!old_bridge_state))
return;

bridge->funcs->atomic_post_disable(bridge,
old_bridge_state);
} else if (bridge->funcs->post_disable) {
bridge->funcs->post_disable(bridge);
}
}

/**
* drm_atomic_bridge_chain_post_disable - cleans up after disabling all bridges
* in the encoder chain
Expand All @@ -592,36 +611,86 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_disable);
* starting from the first bridge to the last. These are called after completing
* &drm_encoder_helper_funcs.atomic_disable
*
* If a bridge sets @pre_enable_prev_first, then the @post_disable for that
* bridge will be called before the previous one to reverse the @pre_enable
* calling direction.
*
* Note: the bridge passed should be the one closest to the encoder
*/
void drm_atomic_bridge_chain_post_disable(struct drm_bridge *bridge,
struct drm_atomic_state *old_state)
{
struct drm_encoder *encoder;
struct drm_bridge *next, *limit;

if (!bridge)
return;

encoder = bridge->encoder;

list_for_each_entry_from(bridge, &encoder->bridge_chain, chain_node) {
if (bridge->funcs->atomic_post_disable) {
struct drm_bridge_state *old_bridge_state;
limit = NULL;

if (!list_is_last(&bridge->chain_node, &encoder->bridge_chain)) {
next = list_next_entry(bridge, chain_node);

if (next->pre_enable_prev_first) {
/* next bridge had requested that prev
* was enabled first, so disabled last
*/
limit = next;

/* Find the next bridge that has NOT requested
* prev to be enabled first / disabled last
*/
list_for_each_entry_from(next, &encoder->bridge_chain,
chain_node) {
if (next->pre_enable_prev_first) {
next = list_prev_entry(next, chain_node);
limit = next;
break;
}
}

/* Call these bridges in reverse order */
list_for_each_entry_from_reverse(next, &encoder->bridge_chain,
chain_node) {
if (next == bridge)
break;

drm_atomic_bridge_call_post_disable(next,
old_state);
}
}
}

old_bridge_state =
drm_atomic_get_old_bridge_state(old_state,
bridge);
if (WARN_ON(!old_bridge_state))
return;
drm_atomic_bridge_call_post_disable(bridge, old_state);

bridge->funcs->atomic_post_disable(bridge,
old_bridge_state);
} else if (bridge->funcs->post_disable) {
bridge->funcs->post_disable(bridge);
}
if (limit)
/* Jump all bridges that we have already post_disabled */
bridge = limit;
}
}
EXPORT_SYMBOL(drm_atomic_bridge_chain_post_disable);

static void drm_atomic_bridge_call_pre_enable(struct drm_bridge *bridge,
struct drm_atomic_state *old_state)
{
if (old_state && bridge->funcs->atomic_pre_enable) {
struct drm_bridge_state *old_bridge_state;

old_bridge_state =
drm_atomic_get_old_bridge_state(old_state,
bridge);
if (WARN_ON(!old_bridge_state))
return;

bridge->funcs->atomic_pre_enable(bridge, old_bridge_state);
} else if (bridge->funcs->pre_enable) {
bridge->funcs->pre_enable(bridge);
}
}

/**
* drm_atomic_bridge_chain_pre_enable - prepares for enabling all bridges in
* the encoder chain
Expand All @@ -633,32 +702,60 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_post_disable);
* starting from the last bridge to the first. These are called before calling
* &drm_encoder_helper_funcs.atomic_enable
*
* If a bridge sets @pre_enable_prev_first, then the pre_enable for the
* prev bridge will be called before pre_enable of this bridge.
*
* Note: the bridge passed should be the one closest to the encoder
*/
void drm_atomic_bridge_chain_pre_enable(struct drm_bridge *bridge,
struct drm_atomic_state *old_state)
{
struct drm_encoder *encoder;
struct drm_bridge *iter;
struct drm_bridge *iter, *next, *limit;

if (!bridge)
return;

encoder = bridge->encoder;

list_for_each_entry_reverse(iter, &encoder->bridge_chain, chain_node) {
if (iter->funcs->atomic_pre_enable) {
struct drm_bridge_state *old_bridge_state;
if (iter->pre_enable_prev_first) {
next = iter;
limit = bridge;
list_for_each_entry_from_reverse(next,
&encoder->bridge_chain,
chain_node) {
if (next == bridge)
break;

if (!next->pre_enable_prev_first) {
/* Found first bridge that does NOT
* request prev to be enabled first
*/
limit = list_prev_entry(next, chain_node);
break;
}
}

list_for_each_entry_from(next, &encoder->bridge_chain, chain_node) {
/* Call requested prev bridge pre_enable
* in order.
*/
if (next == iter)
/* At the first bridge to request prev
* bridges called first.
*/
break;

drm_atomic_bridge_call_pre_enable(next, old_state);
}
}

old_bridge_state =
drm_atomic_get_old_bridge_state(old_state,
iter);
if (WARN_ON(!old_bridge_state))
return;
drm_atomic_bridge_call_pre_enable(iter, old_state);

iter->funcs->atomic_pre_enable(iter, old_bridge_state);
} else if (iter->funcs->pre_enable) {
iter->funcs->pre_enable(iter);
}
if (iter->pre_enable_prev_first)
/* Jump all bridges that we have already pre_enabled */
iter = limit;

if (iter == bridge)
break;
Expand Down
8 changes: 8 additions & 0 deletions include/drm/drm_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,14 @@ struct drm_bridge {
* modes.
*/
bool interlace_allowed;
/**
* @pre_enable_prev_first: The bridge requires that the prev
* bridge @pre_enable function is called before its @pre_enable,
* and conversely for post_disable. This is most frequently a
* requirement for DSI devices which need the host to be initialised
* before the peripheral.
*/
bool pre_enable_prev_first;
/**
* @ddc: Associated I2C adapter for DDC access, if any.
*/
Expand Down

0 comments on commit 4fb912e

Please sign in to comment.