[PATCH] drm/bridge: Fix the bridge chain order for pre_enable / post_disable
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Fri Oct 22 04:43:44 UTC 2021
Hi Doug,
Thank you for the patch.
On Thu, Oct 21, 2021 at 12:29:01PM -0700, Douglas Anderson wrote:
> Right now, the chaining order of
> pre_enable/enable/disable/post_disable looks like this:
>
> pre_enable: start from connector and move to encoder
> enable: start from encoder and move to connector
> disable: start from connector and move to encoder
> post_disable: start from encoder and move to connector
>
> In the above, it can be seen that at least pre_enable() and
> post_disable() are opposites of each other and enable() and disable()
> are opposites. However, it seems broken that pre_enable() and enable()
> would not move in the same direction. In other parts of Linux you can
> see that various stages move in the same order. For instance, during
> system suspend the "early" calls run in the same order as the normal
> calls run in the same order as the "late" calls run in the same order
> as the "noirq" calls.
>
> Let fix the above so that it makes more sense. Now we'll have:
>
> pre_enable: start from encoder and move to connector
> enable: start from encoder and move to connector
> disable: start from connector and move to encoder
> post_disable: start from connector and move to encoder
>
> This order is chosen because if there are parent-child relationships
> anywhere I would expect that the encoder would be a parent and the
> connector a child--not the other way around.
>
> This can be important when using the DP AUX bus to instantiate a
> panel. The DP AUX bus is likely part of a bridge driver and is a
> parent of the panel. We'd like the bridge to be pre_enabled before the
> panel and the panel to be post_disabled before the
> bridge. Specifically, this allows pm_runtime_put_sync_suspend() in a
> bridge driver's post_suspend to work properly even a panel is under
> it.
>
> NOTE: it's entirely possible that this change could break someone who
> was relying on the old order. Hopefully this isn't the case, but if
> this does break someone it seems like it's better to do it sonner
> rather than later so we can fix everyone to handle the order that
> makes the most sense.
I'm less hopeful than you are on this, changing the order of operations
is very risky. I'm also concerned about hardware damage, the pre-enable
operation is often used to power up devices, and powering up a source
before a sink is dangerous as many devices don't like having I/O voltage
applied to their pins before they get powered up. If you really want to
land this, the patch needs very very broad testing, as well as a plan to
address the power up issue.
> A FURTHER NOTE: Looking closer at commit 4e5763f03e10 ("drm/bridge:
> ti-sn65dsi86: Wrap panel with panel-bridge") you can see that patch
> inadvertently changed the order of things. The order used to be
> correct (panel prepare was at the tail of the bridge enable) but it
> became backwards. We'll restore the original order with this patch.
>
> Fixes: 4e5763f03e10 ("drm/bridge: ti-sn65dsi86: Wrap panel with panel-bridge")
> Fixes: 05193dc38197 ("drm/bridge: Make the bridge chain a double-linked list")
> Signed-off-by: Douglas Anderson <dianders at chromium.org>
> ---
>
> drivers/gpu/drm/drm_bridge.c | 28 ++++++++++++++--------------
> 1 file changed, 14 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> index c96847fc0ebc..98808af59afd 100644
> --- a/drivers/gpu/drm/drm_bridge.c
> +++ b/drivers/gpu/drm/drm_bridge.c
> @@ -583,18 +583,14 @@ EXPORT_SYMBOL(drm_bridge_chain_mode_set);
> void drm_bridge_chain_pre_enable(struct drm_bridge *bridge)
> {
> struct drm_encoder *encoder;
> - struct drm_bridge *iter;
>
> if (!bridge)
> return;
>
> encoder = bridge->encoder;
> - list_for_each_entry_reverse(iter, &encoder->bridge_chain, chain_node) {
> - if (iter->funcs->pre_enable)
> - iter->funcs->pre_enable(iter);
> -
> - if (iter == bridge)
> - break;
> + list_for_each_entry_from(bridge, &encoder->bridge_chain, chain_node) {
> + if (bridge->funcs->pre_enable)
> + bridge->funcs->pre_enable(bridge);
> }
> }
> EXPORT_SYMBOL(drm_bridge_chain_pre_enable);
> @@ -684,26 +680,30 @@ void drm_atomic_bridge_chain_post_disable(struct drm_bridge *bridge,
> struct drm_atomic_state *old_state)
> {
> struct drm_encoder *encoder;
> + struct drm_bridge *iter;
>
> if (!bridge)
> return;
>
> encoder = bridge->encoder;
> - list_for_each_entry_from(bridge, &encoder->bridge_chain, chain_node) {
> - if (bridge->funcs->atomic_post_disable) {
> + list_for_each_entry_reverse(iter, &encoder->bridge_chain, chain_node) {
> + if (iter->funcs->atomic_post_disable) {
> struct drm_bridge_state *old_bridge_state;
>
> old_bridge_state =
> drm_atomic_get_old_bridge_state(old_state,
> - bridge);
> + iter);
> 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);
> + iter->funcs->atomic_post_disable(iter,
> + old_bridge_state);
> + } else if (iter->funcs->post_disable) {
> + iter->funcs->post_disable(iter);
> }
> +
> + if (iter == bridge)
> + break;
> }
> }
> EXPORT_SYMBOL(drm_atomic_bridge_chain_post_disable);
--
Regards,
Laurent Pinchart
More information about the dri-devel
mailing list