diff mbox series

[v4,04/15] drm/bridge: tc358764: add drm_panel_bridge support

Message ID 20200726203324.3722593-5-sam@ravnborg.org (mailing list archive)
State New, archived
Headers show
Series drm/bridge: support chained bridges + panel updates | expand

Commit Message

Sam Ravnborg July 26, 2020, 8:33 p.m. UTC
Prepare the tc358764 bridge driver for use in a chained setup by
replacing direct use of drm_panel with drm_panel_bridge support.

The bridge panel will use the connector type reported by the panel,
where the connector for this driver hardcodes DRM_MODE_CONNECTOR_LVDS.

The tc358764 did not any additional info the the connector so the
connector creation is passed to the bridge panel driver.

v3:
  - Merge with patch to make connector creation optional to avoid
    creating two connectors (Laurent)
  - Pass connector creation to bridge panel, as this bridge driver
    did not add any extra info to the connector.
  - Set bridge.type to DRM_MODE_CONNECTOR_LVDS.

v2:
  - Use PTR_ERR_OR_ZERO() (kbuild test robot)

Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Cc: kbuild test robot <lkp@intel.com>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Neil Armstrong <narmstrong@baylibre.com>
Cc: Jonas Karlman <jonas@kwiboo.se>
Cc: Jernej Skrabec <jernej.skrabec@siol.net>
---
 drivers/gpu/drm/bridge/tc358764.c | 107 +++++-------------------------
 1 file changed, 16 insertions(+), 91 deletions(-)

Comments

Laurent Pinchart July 26, 2020, 9:37 p.m. UTC | #1
Hi Sam,

Thank you for the patch.

On Sun, Jul 26, 2020 at 10:33:13PM +0200, Sam Ravnborg wrote:
> Prepare the tc358764 bridge driver for use in a chained setup by
> replacing direct use of drm_panel with drm_panel_bridge support.
> 
> The bridge panel will use the connector type reported by the panel,
> where the connector for this driver hardcodes DRM_MODE_CONNECTOR_LVDS.
> 
> The tc358764 did not any additional info the the connector so the

Did you mean s/did not any/did not add any/ ?
s/the the/to the/

> connector creation is passed to the bridge panel driver.
> 
> v3:
>   - Merge with patch to make connector creation optional to avoid
>     creating two connectors (Laurent)
>   - Pass connector creation to bridge panel, as this bridge driver
>     did not add any extra info to the connector.
>   - Set bridge.type to DRM_MODE_CONNECTOR_LVDS.
> 
> v2:
>   - Use PTR_ERR_OR_ZERO() (kbuild test robot)
> 
> Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: kbuild test robot <lkp@intel.com>
> Cc: Andrzej Hajda <a.hajda@samsung.com>
> Cc: Neil Armstrong <narmstrong@baylibre.com>
> Cc: Jonas Karlman <jonas@kwiboo.se>
> Cc: Jernej Skrabec <jernej.skrabec@siol.net>
> ---
>  drivers/gpu/drm/bridge/tc358764.c | 107 +++++-------------------------
>  1 file changed, 16 insertions(+), 91 deletions(-)
> 
> diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
> index a277739fab58..fdde4cfdc724 100644
> --- a/drivers/gpu/drm/bridge/tc358764.c
> +++ b/drivers/gpu/drm/bridge/tc358764.c
> @@ -153,10 +153,9 @@ static const char * const tc358764_supplies[] = {
>  struct tc358764 {
>  	struct device *dev;
>  	struct drm_bridge bridge;
> -	struct drm_connector connector;
>  	struct regulator_bulk_data supplies[ARRAY_SIZE(tc358764_supplies)];
>  	struct gpio_desc *gpio_reset;
> -	struct drm_panel *panel;
> +	struct drm_bridge *panel_bridge;
>  	int error;
>  };
>  
> @@ -210,12 +209,6 @@ static inline struct tc358764 *bridge_to_tc358764(struct drm_bridge *bridge)
>  	return container_of(bridge, struct tc358764, bridge);
>  }
>  
> -static inline
> -struct tc358764 *connector_to_tc358764(struct drm_connector *connector)
> -{
> -	return container_of(connector, struct tc358764, connector);
> -}
> -
>  static int tc358764_init(struct tc358764 *ctx)
>  {
>  	u32 v = 0;
> @@ -278,43 +271,11 @@ static void tc358764_reset(struct tc358764 *ctx)
>  	usleep_range(1000, 2000);
>  }
>  
> -static int tc358764_get_modes(struct drm_connector *connector)
> -{
> -	struct tc358764 *ctx = connector_to_tc358764(connector);
> -
> -	return drm_panel_get_modes(ctx->panel, connector);
> -}
> -
> -static const
> -struct drm_connector_helper_funcs tc358764_connector_helper_funcs = {
> -	.get_modes = tc358764_get_modes,
> -};
> -
> -static const struct drm_connector_funcs tc358764_connector_funcs = {
> -	.fill_modes = drm_helper_probe_single_connector_modes,
> -	.destroy = drm_connector_cleanup,
> -	.reset = drm_atomic_helper_connector_reset,
> -	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> -	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> -};
> -
> -static void tc358764_disable(struct drm_bridge *bridge)
> -{
> -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> -	int ret = drm_panel_disable(bridge_to_tc358764(bridge)->panel);
> -
> -	if (ret < 0)
> -		dev_err(ctx->dev, "error disabling panel (%d)\n", ret);
> -}
> -
>  static void tc358764_post_disable(struct drm_bridge *bridge)
>  {
>  	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>  	int ret;
>  
> -	ret = drm_panel_unprepare(ctx->panel);
> -	if (ret < 0)
> -		dev_err(ctx->dev, "error unpreparing panel (%d)\n", ret);
>  	tc358764_reset(ctx);
>  	usleep_range(10000, 15000);
>  	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> @@ -335,70 +296,28 @@ static void tc358764_pre_enable(struct drm_bridge *bridge)
>  	ret = tc358764_init(ctx);
>  	if (ret < 0)
>  		dev_err(ctx->dev, "error initializing bridge (%d)\n", ret);
> -	ret = drm_panel_prepare(ctx->panel);
> -	if (ret < 0)
> -		dev_err(ctx->dev, "error preparing panel (%d)\n", ret);
> -}
> -
> -static void tc358764_enable(struct drm_bridge *bridge)
> -{
> -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> -	int ret = drm_panel_enable(ctx->panel);
> -
> -	if (ret < 0)
> -		dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
>  }
>  
>  static int tc358764_attach(struct drm_bridge *bridge,
>  			   enum drm_bridge_attach_flags flags)
> -{
> -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> -	struct drm_device *drm = bridge->dev;
> -	int ret;
> -
> -	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
> -		DRM_ERROR("Fix bridge driver to make connector optional!");
> -		return -EINVAL;
> -	}
> -
> -	ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
> -	ret = drm_connector_init(drm, &ctx->connector,
> -				 &tc358764_connector_funcs,
> -				 DRM_MODE_CONNECTOR_LVDS);
> -	if (ret) {
> -		DRM_ERROR("Failed to initialize connector\n");
> -		return ret;
> -	}
> -
> -	drm_connector_helper_add(&ctx->connector,
> -				 &tc358764_connector_helper_funcs);
> -	drm_connector_attach_encoder(&ctx->connector, bridge->encoder);
> -	drm_panel_attach(ctx->panel, &ctx->connector);
> -	ctx->connector.funcs->reset(&ctx->connector);
> -
> -	return 0;
> -}
> -
> -static void tc358764_detach(struct drm_bridge *bridge)
>  {
>  	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>  
> -	drm_panel_detach(ctx->panel);
> -	ctx->panel = NULL;
> +	return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
> +				 bridge, flags);
>  }
>  
>  static const struct drm_bridge_funcs tc358764_bridge_funcs = {
> -	.disable = tc358764_disable,
>  	.post_disable = tc358764_post_disable,
> -	.enable = tc358764_enable,
>  	.pre_enable = tc358764_pre_enable,
>  	.attach = tc358764_attach,
> -	.detach = tc358764_detach,
>  };
>  
>  static int tc358764_parse_dt(struct tc358764 *ctx)
>  {
> +	struct drm_bridge *panel_bridge;
>  	struct device *dev = ctx->dev;
> +	struct drm_panel *panel;
>  	int ret;
>  
>  	ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> @@ -407,12 +326,17 @@ static int tc358764_parse_dt(struct tc358764 *ctx)
>  		return PTR_ERR(ctx->gpio_reset);
>  	}
>  
> -	ret = drm_of_find_panel_or_bridge(ctx->dev->of_node, 1, 0, &ctx->panel,
> -					  NULL);
> -	if (ret && ret != -EPROBE_DEFER)
> -		dev_err(dev, "cannot find panel (%d)\n", ret);
> +	ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
> +	if (ret)
> +		return ret;
>  
> -	return ret;
> +	panel_bridge = devm_drm_panel_bridge_add(dev, panel);
> +

You can drop this blank line.

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +	if (IS_ERR(panel_bridge))
> +		return PTR_ERR(panel_bridge);
> +
> +	ctx->panel_bridge = panel_bridge;
> +	return 0;
>  }
>  
>  static int tc358764_configure_regulators(struct tc358764 *ctx)
> @@ -458,6 +382,7 @@ static int tc358764_probe(struct mipi_dsi_device *dsi)
>  		return ret;
>  
>  	ctx->bridge.funcs = &tc358764_bridge_funcs;
> +	ctx->bridge.type = DRM_MODE_CONNECTOR_LVDS;
>  	ctx->bridge.of_node = dev->of_node;
>  
>  	drm_bridge_add(&ctx->bridge);
Marek Szyprowski Aug. 27, 2020, 11:39 a.m. UTC | #2
Hi Sam,

On 26.07.2020 22:33, Sam Ravnborg wrote:
> Prepare the tc358764 bridge driver for use in a chained setup by
> replacing direct use of drm_panel with drm_panel_bridge support.
>
> The bridge panel will use the connector type reported by the panel,
> where the connector for this driver hardcodes DRM_MODE_CONNECTOR_LVDS.
>
> The tc358764 did not any additional info the the connector so the
> connector creation is passed to the bridge panel driver.
>
> v3:
>    - Merge with patch to make connector creation optional to avoid
>      creating two connectors (Laurent)
>    - Pass connector creation to bridge panel, as this bridge driver
>      did not add any extra info to the connector.
>    - Set bridge.type to DRM_MODE_CONNECTOR_LVDS.
>
> v2:
>    - Use PTR_ERR_OR_ZERO() (kbuild test robot)
>
> Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: kbuild test robot <lkp@intel.com>
> Cc: Andrzej Hajda <a.hajda@samsung.com>
> Cc: Neil Armstrong <narmstrong@baylibre.com>
> Cc: Jonas Karlman <jonas@kwiboo.se>
> Cc: Jernej Skrabec <jernej.skrabec@siol.net>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

I've noticed that this patch has been merged recently to linux-next. 
Sadly it causes regression on Samsung Exynos5250-based Arndale board.

It can be observed by the following warning during boot:

------------[ cut here ]------------
WARNING: CPU: 1 PID: 1 at drivers/gpu/drm/drm_atomic_state_helper.c:494 
drm_atomic_helper_connector_duplicate_state+0x60/0x68
Modules linked in:
CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.8.0-rc2-00501-g1644127f83bc 
#1526
Hardware name: Samsung Exynos (Flattened Device Tree)
[<c011184c>] (unwind_backtrace) from [<c010d250>] (show_stack+0x10/0x14)
[<c010d250>] (show_stack) from [<c0517ce4>] (dump_stack+0xbc/0xe8)
[<c0517ce4>] (dump_stack) from [<c01270a8>] (__warn+0xf0/0x108)
[<c01270a8>] (__warn) from [<c0127170>] (warn_slowpath_fmt+0xb0/0xb8)
[<c0127170>] (warn_slowpath_fmt) from [<c05e81f0>] 
(drm_atomic_helper_connector_duplicate_state+0x60/0x68)
[<c05e81f0>] (drm_atomic_helper_connector_duplicate_state) from 
[<c06014b8>] (drm_atomic_get_connector_state+0xfc/0x184)
[<c06014b8>] (drm_atomic_get_connector_state) from [<c0602238>] 
(__drm_atomic_helper_set_config+0x2a0/0x368)
[<c0602238>] (__drm_atomic_helper_set_config) from [<c06183b8>] 
(drm_client_modeset_commit_atomic+0x180/0x284)
[<c06183b8>] (drm_client_modeset_commit_atomic) from [<c061859c>] 
(drm_client_modeset_commit_locked+0x64/0x1cc)
[<c061859c>] (drm_client_modeset_commit_locked) from [<c0618728>] 
(drm_client_modeset_commit+0x24/0x40)
[<c0618728>] (drm_client_modeset_commit) from [<c05eb6b4>] 
(drm_fb_helper_restore_fbdev_mode_unlocked+0x50/0x94)
[<c05eb6b4>] (drm_fb_helper_restore_fbdev_mode_unlocked) from 
[<c05eb728>] (drm_fb_helper_set_par+0x30/0x5c)
[<c05eb728>] (drm_fb_helper_set_par) from [<c055dedc>] 
(fbcon_init+0x5c8/0x65c)
[<c055dedc>] (fbcon_init) from [<c05a8530>] (visual_init+0xc0/0x108)
[<c05a8530>] (visual_init) from [<c05aaca4>] 
(do_bind_con_driver+0x180/0x39c)
[<c05aaca4>] (do_bind_con_driver) from [<c05ab244>] 
(do_take_over_console+0x140/0x1cc)
[<c05ab244>] (do_take_over_console) from [<c055ac04>] 
(do_fbcon_takeover+0x84/0xe0)
[<c055ac04>] (do_fbcon_takeover) from [<c0553820>] 
(register_framebuffer+0x1cc/0x2dc)
[<c0553820>] (register_framebuffer) from [<c05eb19c>] 
(__drm_fb_helper_initial_config_and_unlock+0x3f0/0x5e8)
[<c05eb19c>] (__drm_fb_helper_initial_config_and_unlock) from 
[<c05d941c>] (drm_kms_helper_hotplug_event+0x24/0x30)
[<c05d941c>] (drm_kms_helper_hotplug_event) from [<c0628f74>] 
(exynos_dsi_host_attach+0x184/0x2d8)
[<c0628f74>] (exynos_dsi_host_attach) from [<c0634120>] 
(tc358764_probe+0x13c/0x1ac)
[<c0634120>] (tc358764_probe) from [<c064cce4>] (really_probe+0x200/0x48c)
[<c064cce4>] (really_probe) from [<c064d0d8>] 
(driver_probe_device+0x78/0x1fc)
[<c064d0d8>] (driver_probe_device) from [<c064d4c0>] 
(device_driver_attach+0x58/0x60)
[<c064d4c0>] (device_driver_attach) from [<c064d5a4>] 
(__driver_attach+0xdc/0x174)
[<c064d5a4>] (__driver_attach) from [<c064aaf0>] 
(bus_for_each_dev+0x68/0xb4)
[<c064aaf0>] (bus_for_each_dev) from [<c064be24>] 
(bus_add_driver+0x158/0x214)
[<c064be24>] (bus_add_driver) from [<c064e478>] (driver_register+0x78/0x110)
[<c064e478>] (driver_register) from [<c0102378>] 
(do_one_initcall+0x8c/0x424)
[<c0102378>] (do_one_initcall) from [<c1001158>] 
(kernel_init_freeable+0x190/0x204)
[<c1001158>] (kernel_init_freeable) from [<c0ab835c>] 
(kernel_init+0x8/0x118)
[<c0ab835c>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20)
Exception stack(0xee8ddfb0 to 0xee8ddff8)
dfa0:                                     00000000 00000000 00000000 
00000000
dfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
00000000
dfe0: 00000000 00000000 00000000 00000000 00000013 00000000
irq event stamp: 171647
hardirqs last  enabled at (171653): [<c019ec00>] vprintk_emit+0x2ac/0x2ec
hardirqs last disabled at (171658): [<c019eab8>] vprintk_emit+0x164/0x2ec
softirqs last  enabled at (171486): [<c010174c>] __do_softirq+0x50c/0x608
softirqs last disabled at (171473): [<c0130340>] irq_exit+0x168/0x16c
---[ end trace 33117a16f066466a ]---

Then calling modetest end with segmentation fault. I'm not able to check 
currently if there is anything on the display because of having only 
remote access to the board. If this is important I will try to ask 
someone to help checking at the board's display at the office.

Best regards
Sam Ravnborg Aug. 30, 2020, 8:42 p.m. UTC | #3
Hi Marek.

On Thu, Aug 27, 2020 at 01:39:06PM +0200, Marek Szyprowski wrote:
> Hi Sam,
> 
> On 26.07.2020 22:33, Sam Ravnborg wrote:
> > Prepare the tc358764 bridge driver for use in a chained setup by
> > replacing direct use of drm_panel with drm_panel_bridge support.
> >
> > The bridge panel will use the connector type reported by the panel,
> > where the connector for this driver hardcodes DRM_MODE_CONNECTOR_LVDS.
> >
> > The tc358764 did not any additional info the the connector so the
> > connector creation is passed to the bridge panel driver.
> >
> > v3:
> >    - Merge with patch to make connector creation optional to avoid
> >      creating two connectors (Laurent)
> >    - Pass connector creation to bridge panel, as this bridge driver
> >      did not add any extra info to the connector.
> >    - Set bridge.type to DRM_MODE_CONNECTOR_LVDS.
> >
> > v2:
> >    - Use PTR_ERR_OR_ZERO() (kbuild test robot)
> >
> > Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
> > Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Cc: kbuild test robot <lkp@intel.com>
> > Cc: Andrzej Hajda <a.hajda@samsung.com>
> > Cc: Neil Armstrong <narmstrong@baylibre.com>
> > Cc: Jonas Karlman <jonas@kwiboo.se>
> > Cc: Jernej Skrabec <jernej.skrabec@siol.net>
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
> I've noticed that this patch has been merged recently to linux-next. 
> Sadly it causes regression on Samsung Exynos5250-based Arndale board.

Thanks for reporting this!

I did not find time to focus on this bug this weekend. It is on my todo
list for the coming weekend.

Anything you could do to narrow down this a bit to help finding the root
cause?

Ideas:
- Trying to find out what part of the connector that cuases troubles
- Posting the full kernel boot log, to help identifying something.
  Bonus if we get a working and non-working log - so we can compare.
- Migrate exonys to the new model
  That would not fix the bug, so that would be a natural step 2
- Identify the exact code-patch in the exonys driver that is used.
  drm_bridge_attach() is called in several places
- And likely much more that I just forgot

Any help would be appreciated - I did not find the culprint from first
glance. I may still be obvious but I just failed to spot it.

	Sam

> 
> It can be observed by the following warning during boot:
> 
> ------------[ cut here ]------------
> WARNING: CPU: 1 PID: 1 at drivers/gpu/drm/drm_atomic_state_helper.c:494 
> drm_atomic_helper_connector_duplicate_state+0x60/0x68
> Modules linked in:
> CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.8.0-rc2-00501-g1644127f83bc 
> #1526
> Hardware name: Samsung Exynos (Flattened Device Tree)
> [<c011184c>] (unwind_backtrace) from [<c010d250>] (show_stack+0x10/0x14)
> [<c010d250>] (show_stack) from [<c0517ce4>] (dump_stack+0xbc/0xe8)
> [<c0517ce4>] (dump_stack) from [<c01270a8>] (__warn+0xf0/0x108)
> [<c01270a8>] (__warn) from [<c0127170>] (warn_slowpath_fmt+0xb0/0xb8)
> [<c0127170>] (warn_slowpath_fmt) from [<c05e81f0>] 
> (drm_atomic_helper_connector_duplicate_state+0x60/0x68)
> [<c05e81f0>] (drm_atomic_helper_connector_duplicate_state) from 
> [<c06014b8>] (drm_atomic_get_connector_state+0xfc/0x184)
> [<c06014b8>] (drm_atomic_get_connector_state) from [<c0602238>] 
> (__drm_atomic_helper_set_config+0x2a0/0x368)
> [<c0602238>] (__drm_atomic_helper_set_config) from [<c06183b8>] 
> (drm_client_modeset_commit_atomic+0x180/0x284)
> [<c06183b8>] (drm_client_modeset_commit_atomic) from [<c061859c>] 
> (drm_client_modeset_commit_locked+0x64/0x1cc)
> [<c061859c>] (drm_client_modeset_commit_locked) from [<c0618728>] 
> (drm_client_modeset_commit+0x24/0x40)
> [<c0618728>] (drm_client_modeset_commit) from [<c05eb6b4>] 
> (drm_fb_helper_restore_fbdev_mode_unlocked+0x50/0x94)
> [<c05eb6b4>] (drm_fb_helper_restore_fbdev_mode_unlocked) from 
> [<c05eb728>] (drm_fb_helper_set_par+0x30/0x5c)
> [<c05eb728>] (drm_fb_helper_set_par) from [<c055dedc>] 
> (fbcon_init+0x5c8/0x65c)
> [<c055dedc>] (fbcon_init) from [<c05a8530>] (visual_init+0xc0/0x108)
> [<c05a8530>] (visual_init) from [<c05aaca4>] 
> (do_bind_con_driver+0x180/0x39c)
> [<c05aaca4>] (do_bind_con_driver) from [<c05ab244>] 
> (do_take_over_console+0x140/0x1cc)
> [<c05ab244>] (do_take_over_console) from [<c055ac04>] 
> (do_fbcon_takeover+0x84/0xe0)
> [<c055ac04>] (do_fbcon_takeover) from [<c0553820>] 
> (register_framebuffer+0x1cc/0x2dc)
> [<c0553820>] (register_framebuffer) from [<c05eb19c>] 
> (__drm_fb_helper_initial_config_and_unlock+0x3f0/0x5e8)
> [<c05eb19c>] (__drm_fb_helper_initial_config_and_unlock) from 
> [<c05d941c>] (drm_kms_helper_hotplug_event+0x24/0x30)
> [<c05d941c>] (drm_kms_helper_hotplug_event) from [<c0628f74>] 
> (exynos_dsi_host_attach+0x184/0x2d8)
> [<c0628f74>] (exynos_dsi_host_attach) from [<c0634120>] 
> (tc358764_probe+0x13c/0x1ac)
> [<c0634120>] (tc358764_probe) from [<c064cce4>] (really_probe+0x200/0x48c)
> [<c064cce4>] (really_probe) from [<c064d0d8>] 
> (driver_probe_device+0x78/0x1fc)
> [<c064d0d8>] (driver_probe_device) from [<c064d4c0>] 
> (device_driver_attach+0x58/0x60)
> [<c064d4c0>] (device_driver_attach) from [<c064d5a4>] 
> (__driver_attach+0xdc/0x174)
> [<c064d5a4>] (__driver_attach) from [<c064aaf0>] 
> (bus_for_each_dev+0x68/0xb4)
> [<c064aaf0>] (bus_for_each_dev) from [<c064be24>] 
> (bus_add_driver+0x158/0x214)
> [<c064be24>] (bus_add_driver) from [<c064e478>] (driver_register+0x78/0x110)
> [<c064e478>] (driver_register) from [<c0102378>] 
> (do_one_initcall+0x8c/0x424)
> [<c0102378>] (do_one_initcall) from [<c1001158>] 
> (kernel_init_freeable+0x190/0x204)
> [<c1001158>] (kernel_init_freeable) from [<c0ab835c>] 
> (kernel_init+0x8/0x118)
> [<c0ab835c>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20)
> Exception stack(0xee8ddfb0 to 0xee8ddff8)
> dfa0:                                     00000000 00000000 00000000 
> 00000000
> dfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
> 00000000
> dfe0: 00000000 00000000 00000000 00000000 00000013 00000000
> irq event stamp: 171647
> hardirqs last  enabled at (171653): [<c019ec00>] vprintk_emit+0x2ac/0x2ec
> hardirqs last disabled at (171658): [<c019eab8>] vprintk_emit+0x164/0x2ec
> softirqs last  enabled at (171486): [<c010174c>] __do_softirq+0x50c/0x608
> softirqs last disabled at (171473): [<c0130340>] irq_exit+0x168/0x16c
> ---[ end trace 33117a16f066466a ]---
> 
> Then calling modetest end with segmentation fault. I'm not able to check 
> currently if there is anything on the display because of having only 
> remote access to the board. If this is important I will try to ask 
> someone to help checking at the board's display at the office.
> 
> Best regards
> -- 
> Marek Szyprowski, PhD
> Samsung R&D Institute Poland
Andrzej Hajda Sept. 3, 2020, 6:20 a.m. UTC | #4
Hi Sam,

On 30.08.2020 22:42, Sam Ravnborg wrote:
> Hi Marek.
>
> On Thu, Aug 27, 2020 at 01:39:06PM +0200, Marek Szyprowski wrote:
>> Hi Sam,
>>
>> On 26.07.2020 22:33, Sam Ravnborg wrote:
>>> Prepare the tc358764 bridge driver for use in a chained setup by
>>> replacing direct use of drm_panel with drm_panel_bridge support.
>>>
>>> The bridge panel will use the connector type reported by the panel,
>>> where the connector for this driver hardcodes DRM_MODE_CONNECTOR_LVDS.
>>>
>>> The tc358764 did not any additional info the the connector so the
>>> connector creation is passed to the bridge panel driver.
>>>
>>> v3:
>>>     - Merge with patch to make connector creation optional to avoid
>>>       creating two connectors (Laurent)
>>>     - Pass connector creation to bridge panel, as this bridge driver
>>>       did not add any extra info to the connector.
>>>     - Set bridge.type to DRM_MODE_CONNECTOR_LVDS.
>>>
>>> v2:
>>>     - Use PTR_ERR_OR_ZERO() (kbuild test robot)
>>>
>>> Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
>>> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>> Cc: kbuild test robot <lkp@intel.com>
>>> Cc: Andrzej Hajda <a.hajda@samsung.com>
>>> Cc: Neil Armstrong <narmstrong@baylibre.com>
>>> Cc: Jonas Karlman <jonas@kwiboo.se>
>>> Cc: Jernej Skrabec <jernej.skrabec@siol.net>
>>> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>> I've noticed that this patch has been merged recently to linux-next.
>> Sadly it causes regression on Samsung Exynos5250-based Arndale board.
> Thanks for reporting this!
>
> I did not find time to focus on this bug this weekend. It is on my todo
> list for the coming weekend.
>
> Anything you could do to narrow down this a bit to help finding the root
> cause?
>
> Ideas:
> - Trying to find out what part of the connector that cuases troubles
> - Posting the full kernel boot log, to help identifying something.
>    Bonus if we get a working and non-working log - so we can compare.
> - Migrate exonys to the new model
>    That would not fix the bug, so that would be a natural step 2
> - Identify the exact code-patch in the exonys driver that is used.
>    drm_bridge_attach() is called in several places
> - And likely much more that I just forgot


dmesg WARN suggest there is no connector registered and it can be true.

I guess drm_panel_bridge does not support dynamic connector registration,

so if the bridge appears after drm dev is created it will not be 
properly registered.

I will try look closer at the patch but I guess the above can be the 
main reason.


Regards

Andrzej


>
> Any help would be appreciated - I did not find the culprint from first
> glance. I may still be obvious but I just failed to spot it.
>
> 	Sam
>
>> It can be observed by the following warning during boot:
>>
>> ------------[ cut here ]------------
>> WARNING: CPU: 1 PID: 1 at drivers/gpu/drm/drm_atomic_state_helper.c:494
>> drm_atomic_helper_connector_duplicate_state+0x60/0x68
>> Modules linked in:
>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.8.0-rc2-00501-g1644127f83bc
>> #1526
>> Hardware name: Samsung Exynos (Flattened Device Tree)
>> [<c011184c>] (unwind_backtrace) from [<c010d250>] (show_stack+0x10/0x14)
>> [<c010d250>] (show_stack) from [<c0517ce4>] (dump_stack+0xbc/0xe8)
>> [<c0517ce4>] (dump_stack) from [<c01270a8>] (__warn+0xf0/0x108)
>> [<c01270a8>] (__warn) from [<c0127170>] (warn_slowpath_fmt+0xb0/0xb8)
>> [<c0127170>] (warn_slowpath_fmt) from [<c05e81f0>]
>> (drm_atomic_helper_connector_duplicate_state+0x60/0x68)
>> [<c05e81f0>] (drm_atomic_helper_connector_duplicate_state) from
>> [<c06014b8>] (drm_atomic_get_connector_state+0xfc/0x184)
>> [<c06014b8>] (drm_atomic_get_connector_state) from [<c0602238>]
>> (__drm_atomic_helper_set_config+0x2a0/0x368)
>> [<c0602238>] (__drm_atomic_helper_set_config) from [<c06183b8>]
>> (drm_client_modeset_commit_atomic+0x180/0x284)
>> [<c06183b8>] (drm_client_modeset_commit_atomic) from [<c061859c>]
>> (drm_client_modeset_commit_locked+0x64/0x1cc)
>> [<c061859c>] (drm_client_modeset_commit_locked) from [<c0618728>]
>> (drm_client_modeset_commit+0x24/0x40)
>> [<c0618728>] (drm_client_modeset_commit) from [<c05eb6b4>]
>> (drm_fb_helper_restore_fbdev_mode_unlocked+0x50/0x94)
>> [<c05eb6b4>] (drm_fb_helper_restore_fbdev_mode_unlocked) from
>> [<c05eb728>] (drm_fb_helper_set_par+0x30/0x5c)
>> [<c05eb728>] (drm_fb_helper_set_par) from [<c055dedc>]
>> (fbcon_init+0x5c8/0x65c)
>> [<c055dedc>] (fbcon_init) from [<c05a8530>] (visual_init+0xc0/0x108)
>> [<c05a8530>] (visual_init) from [<c05aaca4>]
>> (do_bind_con_driver+0x180/0x39c)
>> [<c05aaca4>] (do_bind_con_driver) from [<c05ab244>]
>> (do_take_over_console+0x140/0x1cc)
>> [<c05ab244>] (do_take_over_console) from [<c055ac04>]
>> (do_fbcon_takeover+0x84/0xe0)
>> [<c055ac04>] (do_fbcon_takeover) from [<c0553820>]
>> (register_framebuffer+0x1cc/0x2dc)
>> [<c0553820>] (register_framebuffer) from [<c05eb19c>]
>> (__drm_fb_helper_initial_config_and_unlock+0x3f0/0x5e8)
>> [<c05eb19c>] (__drm_fb_helper_initial_config_and_unlock) from
>> [<c05d941c>] (drm_kms_helper_hotplug_event+0x24/0x30)
>> [<c05d941c>] (drm_kms_helper_hotplug_event) from [<c0628f74>]
>> (exynos_dsi_host_attach+0x184/0x2d8)
>> [<c0628f74>] (exynos_dsi_host_attach) from [<c0634120>]
>> (tc358764_probe+0x13c/0x1ac)
>> [<c0634120>] (tc358764_probe) from [<c064cce4>] (really_probe+0x200/0x48c)
>> [<c064cce4>] (really_probe) from [<c064d0d8>]
>> (driver_probe_device+0x78/0x1fc)
>> [<c064d0d8>] (driver_probe_device) from [<c064d4c0>]
>> (device_driver_attach+0x58/0x60)
>> [<c064d4c0>] (device_driver_attach) from [<c064d5a4>]
>> (__driver_attach+0xdc/0x174)
>> [<c064d5a4>] (__driver_attach) from [<c064aaf0>]
>> (bus_for_each_dev+0x68/0xb4)
>> [<c064aaf0>] (bus_for_each_dev) from [<c064be24>]
>> (bus_add_driver+0x158/0x214)
>> [<c064be24>] (bus_add_driver) from [<c064e478>] (driver_register+0x78/0x110)
>> [<c064e478>] (driver_register) from [<c0102378>]
>> (do_one_initcall+0x8c/0x424)
>> [<c0102378>] (do_one_initcall) from [<c1001158>]
>> (kernel_init_freeable+0x190/0x204)
>> [<c1001158>] (kernel_init_freeable) from [<c0ab835c>]
>> (kernel_init+0x8/0x118)
>> [<c0ab835c>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20)
>> Exception stack(0xee8ddfb0 to 0xee8ddff8)
>> dfa0:                                     00000000 00000000 00000000
>> 00000000
>> dfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000
>> 00000000
>> dfe0: 00000000 00000000 00000000 00000000 00000013 00000000
>> irq event stamp: 171647
>> hardirqs last  enabled at (171653): [<c019ec00>] vprintk_emit+0x2ac/0x2ec
>> hardirqs last disabled at (171658): [<c019eab8>] vprintk_emit+0x164/0x2ec
>> softirqs last  enabled at (171486): [<c010174c>] __do_softirq+0x50c/0x608
>> softirqs last disabled at (171473): [<c0130340>] irq_exit+0x168/0x16c
>> ---[ end trace 33117a16f066466a ]---
>>
>> Then calling modetest end with segmentation fault. I'm not able to check
>> currently if there is anything on the display because of having only
>> remote access to the board. If this is important I will try to ask
>> someone to help checking at the board's display at the office.
>>
>> Best regards
>> -- 
>> Marek Szyprowski, PhD
>> Samsung R&D Institute Poland
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://protect2.fireeye.com/url?k=4965500c-14b1ec64-4964db43-0cc47a3356b2-f40ef8e76ffb9eeb&q=1&u=https%3A%2F%2Flists.freedesktop.org%2Fmailman%2Flistinfo%2Fdri-devel
>
Andrzej Hajda Sept. 3, 2020, 9:40 a.m. UTC | #5
On 26.07.2020 22:33, Sam Ravnborg wrote:
> Prepare the tc358764 bridge driver for use in a chained setup by
> replacing direct use of drm_panel with drm_panel_bridge support.
>
> The bridge panel will use the connector type reported by the panel,
> where the connector for this driver hardcodes DRM_MODE_CONNECTOR_LVDS.
>
> The tc358764 did not any additional info the the connector so the
> connector creation is passed to the bridge panel driver.
>
> v3:
>    - Merge with patch to make connector creation optional to avoid
>      creating two connectors (Laurent)
>    - Pass connector creation to bridge panel, as this bridge driver
>      did not add any extra info to the connector.
>    - Set bridge.type to DRM_MODE_CONNECTOR_LVDS.
>
> v2:
>    - Use PTR_ERR_OR_ZERO() (kbuild test robot)
>
> Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: kbuild test robot <lkp@intel.com>
> Cc: Andrzej Hajda <a.hajda@samsung.com>
> Cc: Neil Armstrong <narmstrong@baylibre.com>
> Cc: Jonas Karlman <jonas@kwiboo.se>
> Cc: Jernej Skrabec <jernej.skrabec@siol.net>
> ---
>   drivers/gpu/drm/bridge/tc358764.c | 107 +++++-------------------------
>   1 file changed, 16 insertions(+), 91 deletions(-)
>
> diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
> index a277739fab58..fdde4cfdc724 100644
> --- a/drivers/gpu/drm/bridge/tc358764.c
> +++ b/drivers/gpu/drm/bridge/tc358764.c
> @@ -153,10 +153,9 @@ static const char * const tc358764_supplies[] = {
>   struct tc358764 {
>   	struct device *dev;
>   	struct drm_bridge bridge;
> -	struct drm_connector connector;
>   	struct regulator_bulk_data supplies[ARRAY_SIZE(tc358764_supplies)];
>   	struct gpio_desc *gpio_reset;
> -	struct drm_panel *panel;
> +	struct drm_bridge *panel_bridge;
>   	int error;
>   };
>   
> @@ -210,12 +209,6 @@ static inline struct tc358764 *bridge_to_tc358764(struct drm_bridge *bridge)
>   	return container_of(bridge, struct tc358764, bridge);
>   }
>   
> -static inline
> -struct tc358764 *connector_to_tc358764(struct drm_connector *connector)
> -{
> -	return container_of(connector, struct tc358764, connector);
> -}
> -
>   static int tc358764_init(struct tc358764 *ctx)
>   {
>   	u32 v = 0;
> @@ -278,43 +271,11 @@ static void tc358764_reset(struct tc358764 *ctx)
>   	usleep_range(1000, 2000);
>   }
>   
> -static int tc358764_get_modes(struct drm_connector *connector)
> -{
> -	struct tc358764 *ctx = connector_to_tc358764(connector);
> -
> -	return drm_panel_get_modes(ctx->panel, connector);
> -}
> -
> -static const
> -struct drm_connector_helper_funcs tc358764_connector_helper_funcs = {
> -	.get_modes = tc358764_get_modes,
> -};
> -
> -static const struct drm_connector_funcs tc358764_connector_funcs = {
> -	.fill_modes = drm_helper_probe_single_connector_modes,
> -	.destroy = drm_connector_cleanup,
> -	.reset = drm_atomic_helper_connector_reset,
> -	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> -	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> -};
> -
> -static void tc358764_disable(struct drm_bridge *bridge)
> -{
> -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> -	int ret = drm_panel_disable(bridge_to_tc358764(bridge)->panel);
> -
> -	if (ret < 0)
> -		dev_err(ctx->dev, "error disabling panel (%d)\n", ret);
> -}
> -
>   static void tc358764_post_disable(struct drm_bridge *bridge)
>   {
>   	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>   	int ret;
>   
> -	ret = drm_panel_unprepare(ctx->panel);
> -	if (ret < 0)
> -		dev_err(ctx->dev, "error unpreparing panel (%d)\n", ret);


Using this bridge_panel thing you reverse order of hw 
initialization/de-initialization, this is incorrect.

For example:

- panel_unprepare should be called before tc35* turn off,

- panel_prepare should be called after tc35* on.

This is why I avoid the whole "bridge chaining" - it enforces ridiculous 
order of initialization.


>   	tc358764_reset(ctx);
>   	usleep_range(10000, 15000);
>   	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> @@ -335,70 +296,28 @@ static void tc358764_pre_enable(struct drm_bridge *bridge)
>   	ret = tc358764_init(ctx);
>   	if (ret < 0)
>   		dev_err(ctx->dev, "error initializing bridge (%d)\n", ret);
> -	ret = drm_panel_prepare(ctx->panel);
> -	if (ret < 0)
> -		dev_err(ctx->dev, "error preparing panel (%d)\n", ret);
> -}
> -
> -static void tc358764_enable(struct drm_bridge *bridge)
> -{
> -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> -	int ret = drm_panel_enable(ctx->panel);
> -
> -	if (ret < 0)
> -		dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
>   }
>   
>   static int tc358764_attach(struct drm_bridge *bridge,
>   			   enum drm_bridge_attach_flags flags)
> -{
> -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> -	struct drm_device *drm = bridge->dev;
> -	int ret;
> -
> -	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
> -		DRM_ERROR("Fix bridge driver to make connector optional!");
> -		return -EINVAL;
> -	}
> -
> -	ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
> -	ret = drm_connector_init(drm, &ctx->connector,
> -				 &tc358764_connector_funcs,
> -				 DRM_MODE_CONNECTOR_LVDS);
> -	if (ret) {
> -		DRM_ERROR("Failed to initialize connector\n");
> -		return ret;
> -	}
> -
> -	drm_connector_helper_add(&ctx->connector,
> -				 &tc358764_connector_helper_funcs);
> -	drm_connector_attach_encoder(&ctx->connector, bridge->encoder);
> -	drm_panel_attach(ctx->panel, &ctx->connector);
> -	ctx->connector.funcs->reset(&ctx->connector);


I guess lack of calling .reset here is direct cause of WARN reported by 
Marek.


Summarizing my findings:

1. drm_panel_bridge does not fit to this scenario - it relays on 'bridge 
chaining" which has crazy assumption that order of hw initalization in 
the display chain follows the same fixed order of calls for all hw.

2. tc35* bridge allocates/deallocates connector dynamically - to safely 
handle drivers load/unload, and to avoid multiple deferred probe issues 
, drm_panel_bridge does not support it.


This and previous patch violates both points.


Regards

Andrzej


> -
> -	return 0;
> -}
> -
> -static void tc358764_detach(struct drm_bridge *bridge)
>   {
>   	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>   
> -	drm_panel_detach(ctx->panel);
> -	ctx->panel = NULL;
> +	return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
> +				 bridge, flags);
>   }
>   
>   static const struct drm_bridge_funcs tc358764_bridge_funcs = {
> -	.disable = tc358764_disable,
>   	.post_disable = tc358764_post_disable,
> -	.enable = tc358764_enable,
>   	.pre_enable = tc358764_pre_enable,
>   	.attach = tc358764_attach,
> -	.detach = tc358764_detach,
>   };
>   
>   static int tc358764_parse_dt(struct tc358764 *ctx)
>   {
> +	struct drm_bridge *panel_bridge;
>   	struct device *dev = ctx->dev;
> +	struct drm_panel *panel;
>   	int ret;
>   
>   	ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> @@ -407,12 +326,17 @@ static int tc358764_parse_dt(struct tc358764 *ctx)
>   		return PTR_ERR(ctx->gpio_reset);
>   	}
>   
> -	ret = drm_of_find_panel_or_bridge(ctx->dev->of_node, 1, 0, &ctx->panel,
> -					  NULL);
> -	if (ret && ret != -EPROBE_DEFER)
> -		dev_err(dev, "cannot find panel (%d)\n", ret);
> +	ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
> +	if (ret)
> +		return ret;
>   
> -	return ret;
> +	panel_bridge = devm_drm_panel_bridge_add(dev, panel);
> +
> +	if (IS_ERR(panel_bridge))
> +		return PTR_ERR(panel_bridge);
> +
> +	ctx->panel_bridge = panel_bridge;
> +	return 0;
>   }
>   
>   static int tc358764_configure_regulators(struct tc358764 *ctx)
> @@ -458,6 +382,7 @@ static int tc358764_probe(struct mipi_dsi_device *dsi)
>   		return ret;
>   
>   	ctx->bridge.funcs = &tc358764_bridge_funcs;
> +	ctx->bridge.type = DRM_MODE_CONNECTOR_LVDS;
>   	ctx->bridge.of_node = dev->of_node;
>   
>   	drm_bridge_add(&ctx->bridge);
Laurent Pinchart Sept. 3, 2020, 9:59 a.m. UTC | #6
Hi Andrzej,

On Thu, Sep 03, 2020 at 11:40:58AM +0200, Andrzej Hajda wrote:
> On 26.07.2020 22:33, Sam Ravnborg wrote:
> > Prepare the tc358764 bridge driver for use in a chained setup by
> > replacing direct use of drm_panel with drm_panel_bridge support.
> >
> > The bridge panel will use the connector type reported by the panel,
> > where the connector for this driver hardcodes DRM_MODE_CONNECTOR_LVDS.
> >
> > The tc358764 did not any additional info the the connector so the
> > connector creation is passed to the bridge panel driver.
> >
> > v3:
> >    - Merge with patch to make connector creation optional to avoid
> >      creating two connectors (Laurent)
> >    - Pass connector creation to bridge panel, as this bridge driver
> >      did not add any extra info to the connector.
> >    - Set bridge.type to DRM_MODE_CONNECTOR_LVDS.
> >
> > v2:
> >    - Use PTR_ERR_OR_ZERO() (kbuild test robot)
> >
> > Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
> > Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Cc: kbuild test robot <lkp@intel.com>
> > Cc: Andrzej Hajda <a.hajda@samsung.com>
> > Cc: Neil Armstrong <narmstrong@baylibre.com>
> > Cc: Jonas Karlman <jonas@kwiboo.se>
> > Cc: Jernej Skrabec <jernej.skrabec@siol.net>
> > ---
> >   drivers/gpu/drm/bridge/tc358764.c | 107 +++++-------------------------
> >   1 file changed, 16 insertions(+), 91 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
> > index a277739fab58..fdde4cfdc724 100644
> > --- a/drivers/gpu/drm/bridge/tc358764.c
> > +++ b/drivers/gpu/drm/bridge/tc358764.c
> > @@ -153,10 +153,9 @@ static const char * const tc358764_supplies[] = {
> >   struct tc358764 {
> >   	struct device *dev;
> >   	struct drm_bridge bridge;
> > -	struct drm_connector connector;
> >   	struct regulator_bulk_data supplies[ARRAY_SIZE(tc358764_supplies)];
> >   	struct gpio_desc *gpio_reset;
> > -	struct drm_panel *panel;
> > +	struct drm_bridge *panel_bridge;
> >   	int error;
> >   };
> >   
> > @@ -210,12 +209,6 @@ static inline struct tc358764 *bridge_to_tc358764(struct drm_bridge *bridge)
> >   	return container_of(bridge, struct tc358764, bridge);
> >   }
> >   
> > -static inline
> > -struct tc358764 *connector_to_tc358764(struct drm_connector *connector)
> > -{
> > -	return container_of(connector, struct tc358764, connector);
> > -}
> > -
> >   static int tc358764_init(struct tc358764 *ctx)
> >   {
> >   	u32 v = 0;
> > @@ -278,43 +271,11 @@ static void tc358764_reset(struct tc358764 *ctx)
> >   	usleep_range(1000, 2000);
> >   }
> >   
> > -static int tc358764_get_modes(struct drm_connector *connector)
> > -{
> > -	struct tc358764 *ctx = connector_to_tc358764(connector);
> > -
> > -	return drm_panel_get_modes(ctx->panel, connector);
> > -}
> > -
> > -static const
> > -struct drm_connector_helper_funcs tc358764_connector_helper_funcs = {
> > -	.get_modes = tc358764_get_modes,
> > -};
> > -
> > -static const struct drm_connector_funcs tc358764_connector_funcs = {
> > -	.fill_modes = drm_helper_probe_single_connector_modes,
> > -	.destroy = drm_connector_cleanup,
> > -	.reset = drm_atomic_helper_connector_reset,
> > -	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> > -	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> > -};
> > -
> > -static void tc358764_disable(struct drm_bridge *bridge)
> > -{
> > -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> > -	int ret = drm_panel_disable(bridge_to_tc358764(bridge)->panel);
> > -
> > -	if (ret < 0)
> > -		dev_err(ctx->dev, "error disabling panel (%d)\n", ret);
> > -}
> > -
> >   static void tc358764_post_disable(struct drm_bridge *bridge)
> >   {
> >   	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> >   	int ret;
> >   
> > -	ret = drm_panel_unprepare(ctx->panel);
> > -	if (ret < 0)
> > -		dev_err(ctx->dev, "error unpreparing panel (%d)\n", ret);
> 
> 
> Using this bridge_panel thing you reverse order of hw 
> initialization/de-initialization, this is incorrect.
> 
> For example:
> 
> - panel_unprepare should be called before tc35* turn off,
> 
> - panel_prepare should be called after tc35* on.
> 
> This is why I avoid the whole "bridge chaining" - it enforces ridiculous 
> order of initialization.
> 
> 
> >   	tc358764_reset(ctx);
> >   	usleep_range(10000, 15000);
> >   	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > @@ -335,70 +296,28 @@ static void tc358764_pre_enable(struct drm_bridge *bridge)
> >   	ret = tc358764_init(ctx);
> >   	if (ret < 0)
> >   		dev_err(ctx->dev, "error initializing bridge (%d)\n", ret);
> > -	ret = drm_panel_prepare(ctx->panel);
> > -	if (ret < 0)
> > -		dev_err(ctx->dev, "error preparing panel (%d)\n", ret);
> > -}
> > -
> > -static void tc358764_enable(struct drm_bridge *bridge)
> > -{
> > -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> > -	int ret = drm_panel_enable(ctx->panel);
> > -
> > -	if (ret < 0)
> > -		dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
> >   }
> >   
> >   static int tc358764_attach(struct drm_bridge *bridge,
> >   			   enum drm_bridge_attach_flags flags)
> > -{
> > -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> > -	struct drm_device *drm = bridge->dev;
> > -	int ret;
> > -
> > -	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
> > -		DRM_ERROR("Fix bridge driver to make connector optional!");
> > -		return -EINVAL;
> > -	}
> > -
> > -	ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
> > -	ret = drm_connector_init(drm, &ctx->connector,
> > -				 &tc358764_connector_funcs,
> > -				 DRM_MODE_CONNECTOR_LVDS);
> > -	if (ret) {
> > -		DRM_ERROR("Failed to initialize connector\n");
> > -		return ret;
> > -	}
> > -
> > -	drm_connector_helper_add(&ctx->connector,
> > -				 &tc358764_connector_helper_funcs);
> > -	drm_connector_attach_encoder(&ctx->connector, bridge->encoder);
> > -	drm_panel_attach(ctx->panel, &ctx->connector);
> > -	ctx->connector.funcs->reset(&ctx->connector);
> 
> 
> I guess lack of calling .reset here is direct cause of WARN reported by 
> Marek.
> 
> 
> Summarizing my findings:
> 
> 1. drm_panel_bridge does not fit to this scenario - it relays on 'bridge 
> chaining" which has crazy assumption that order of hw initalization in 
> the display chain follows the same fixed order of calls for all hw.

This would need to be addressed in the bridge core. I don't want to go
back to manual chaining of operations, that opens the door to creating
incompatibilities between bridges and display controllers. The pre/post
enable/disable operations probably need to be better defined, and if a
sink requires a smaller granularity, then new operations need to be
added.

> 2. tc35* bridge allocates/deallocates connector dynamically - to safely 
> handle drivers load/unload, and to avoid multiple deferred probe issues 
> , drm_panel_bridge does not support it.
> 
> This and previous patch violates both points.
> 
> > -
> > -	return 0;
> > -}
> > -
> > -static void tc358764_detach(struct drm_bridge *bridge)
> >   {
> >   	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> >   
> > -	drm_panel_detach(ctx->panel);
> > -	ctx->panel = NULL;
> > +	return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
> > +				 bridge, flags);
> >   }
> >   
> >   static const struct drm_bridge_funcs tc358764_bridge_funcs = {
> > -	.disable = tc358764_disable,
> >   	.post_disable = tc358764_post_disable,
> > -	.enable = tc358764_enable,
> >   	.pre_enable = tc358764_pre_enable,
> >   	.attach = tc358764_attach,
> > -	.detach = tc358764_detach,
> >   };
> >   
> >   static int tc358764_parse_dt(struct tc358764 *ctx)
> >   {
> > +	struct drm_bridge *panel_bridge;
> >   	struct device *dev = ctx->dev;
> > +	struct drm_panel *panel;
> >   	int ret;
> >   
> >   	ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> > @@ -407,12 +326,17 @@ static int tc358764_parse_dt(struct tc358764 *ctx)
> >   		return PTR_ERR(ctx->gpio_reset);
> >   	}
> >   
> > -	ret = drm_of_find_panel_or_bridge(ctx->dev->of_node, 1, 0, &ctx->panel,
> > -					  NULL);
> > -	if (ret && ret != -EPROBE_DEFER)
> > -		dev_err(dev, "cannot find panel (%d)\n", ret);
> > +	ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
> > +	if (ret)
> > +		return ret;
> >   
> > -	return ret;
> > +	panel_bridge = devm_drm_panel_bridge_add(dev, panel);
> > +
> > +	if (IS_ERR(panel_bridge))
> > +		return PTR_ERR(panel_bridge);
> > +
> > +	ctx->panel_bridge = panel_bridge;
> > +	return 0;
> >   }
> >   
> >   static int tc358764_configure_regulators(struct tc358764 *ctx)
> > @@ -458,6 +382,7 @@ static int tc358764_probe(struct mipi_dsi_device *dsi)
> >   		return ret;
> >   
> >   	ctx->bridge.funcs = &tc358764_bridge_funcs;
> > +	ctx->bridge.type = DRM_MODE_CONNECTOR_LVDS;
> >   	ctx->bridge.of_node = dev->of_node;
> >   
> >   	drm_bridge_add(&ctx->bridge);
Andrzej Hajda Sept. 3, 2020, 3:10 p.m. UTC | #7
Hi Laurent,

On 03.09.2020 11:59, Laurent Pinchart wrote:
> Hi Andrzej,
>
> On Thu, Sep 03, 2020 at 11:40:58AM +0200, Andrzej Hajda wrote:
>> On 26.07.2020 22:33, Sam Ravnborg wrote:
>>> Prepare the tc358764 bridge driver for use in a chained setup by
>>> replacing direct use of drm_panel with drm_panel_bridge support.
>>>
>>> The bridge panel will use the connector type reported by the panel,
>>> where the connector for this driver hardcodes DRM_MODE_CONNECTOR_LVDS.
>>>
>>> The tc358764 did not any additional info the the connector so the
>>> connector creation is passed to the bridge panel driver.
>>>
>>> v3:
>>>     - Merge with patch to make connector creation optional to avoid
>>>       creating two connectors (Laurent)
>>>     - Pass connector creation to bridge panel, as this bridge driver
>>>       did not add any extra info to the connector.
>>>     - Set bridge.type to DRM_MODE_CONNECTOR_LVDS.
>>>
>>> v2:
>>>     - Use PTR_ERR_OR_ZERO() (kbuild test robot)
>>>
>>> Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
>>> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>> Cc: kbuild test robot <lkp@intel.com>
>>> Cc: Andrzej Hajda <a.hajda@samsung.com>
>>> Cc: Neil Armstrong <narmstrong@baylibre.com>
>>> Cc: Jonas Karlman <jonas@kwiboo.se>
>>> Cc: Jernej Skrabec <jernej.skrabec@siol.net>
>>> ---
>>>    drivers/gpu/drm/bridge/tc358764.c | 107 +++++-------------------------
>>>    1 file changed, 16 insertions(+), 91 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
>>> index a277739fab58..fdde4cfdc724 100644
>>> --- a/drivers/gpu/drm/bridge/tc358764.c
>>> +++ b/drivers/gpu/drm/bridge/tc358764.c
>>> @@ -153,10 +153,9 @@ static const char * const tc358764_supplies[] = {
>>>    struct tc358764 {
>>>    	struct device *dev;
>>>    	struct drm_bridge bridge;
>>> -	struct drm_connector connector;
>>>    	struct regulator_bulk_data supplies[ARRAY_SIZE(tc358764_supplies)];
>>>    	struct gpio_desc *gpio_reset;
>>> -	struct drm_panel *panel;
>>> +	struct drm_bridge *panel_bridge;
>>>    	int error;
>>>    };
>>>    
>>> @@ -210,12 +209,6 @@ static inline struct tc358764 *bridge_to_tc358764(struct drm_bridge *bridge)
>>>    	return container_of(bridge, struct tc358764, bridge);
>>>    }
>>>    
>>> -static inline
>>> -struct tc358764 *connector_to_tc358764(struct drm_connector *connector)
>>> -{
>>> -	return container_of(connector, struct tc358764, connector);
>>> -}
>>> -
>>>    static int tc358764_init(struct tc358764 *ctx)
>>>    {
>>>    	u32 v = 0;
>>> @@ -278,43 +271,11 @@ static void tc358764_reset(struct tc358764 *ctx)
>>>    	usleep_range(1000, 2000);
>>>    }
>>>    
>>> -static int tc358764_get_modes(struct drm_connector *connector)
>>> -{
>>> -	struct tc358764 *ctx = connector_to_tc358764(connector);
>>> -
>>> -	return drm_panel_get_modes(ctx->panel, connector);
>>> -}
>>> -
>>> -static const
>>> -struct drm_connector_helper_funcs tc358764_connector_helper_funcs = {
>>> -	.get_modes = tc358764_get_modes,
>>> -};
>>> -
>>> -static const struct drm_connector_funcs tc358764_connector_funcs = {
>>> -	.fill_modes = drm_helper_probe_single_connector_modes,
>>> -	.destroy = drm_connector_cleanup,
>>> -	.reset = drm_atomic_helper_connector_reset,
>>> -	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
>>> -	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>>> -};
>>> -
>>> -static void tc358764_disable(struct drm_bridge *bridge)
>>> -{
>>> -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>>> -	int ret = drm_panel_disable(bridge_to_tc358764(bridge)->panel);
>>> -
>>> -	if (ret < 0)
>>> -		dev_err(ctx->dev, "error disabling panel (%d)\n", ret);
>>> -}
>>> -
>>>    static void tc358764_post_disable(struct drm_bridge *bridge)
>>>    {
>>>    	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>>>    	int ret;
>>>    
>>> -	ret = drm_panel_unprepare(ctx->panel);
>>> -	if (ret < 0)
>>> -		dev_err(ctx->dev, "error unpreparing panel (%d)\n", ret);
>>
>> Using this bridge_panel thing you reverse order of hw
>> initialization/de-initialization, this is incorrect.
>>
>> For example:
>>
>> - panel_unprepare should be called before tc35* turn off,
>>
>> - panel_prepare should be called after tc35* on.
>>
>> This is why I avoid the whole "bridge chaining" - it enforces ridiculous
>> order of initialization.
>>
>>
>>>    	tc358764_reset(ctx);
>>>    	usleep_range(10000, 15000);
>>>    	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>> @@ -335,70 +296,28 @@ static void tc358764_pre_enable(struct drm_bridge *bridge)
>>>    	ret = tc358764_init(ctx);
>>>    	if (ret < 0)
>>>    		dev_err(ctx->dev, "error initializing bridge (%d)\n", ret);
>>> -	ret = drm_panel_prepare(ctx->panel);
>>> -	if (ret < 0)
>>> -		dev_err(ctx->dev, "error preparing panel (%d)\n", ret);
>>> -}
>>> -
>>> -static void tc358764_enable(struct drm_bridge *bridge)
>>> -{
>>> -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>>> -	int ret = drm_panel_enable(ctx->panel);
>>> -
>>> -	if (ret < 0)
>>> -		dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
>>>    }
>>>    
>>>    static int tc358764_attach(struct drm_bridge *bridge,
>>>    			   enum drm_bridge_attach_flags flags)
>>> -{
>>> -	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>>> -	struct drm_device *drm = bridge->dev;
>>> -	int ret;
>>> -
>>> -	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
>>> -		DRM_ERROR("Fix bridge driver to make connector optional!");
>>> -		return -EINVAL;
>>> -	}
>>> -
>>> -	ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
>>> -	ret = drm_connector_init(drm, &ctx->connector,
>>> -				 &tc358764_connector_funcs,
>>> -				 DRM_MODE_CONNECTOR_LVDS);
>>> -	if (ret) {
>>> -		DRM_ERROR("Failed to initialize connector\n");
>>> -		return ret;
>>> -	}
>>> -
>>> -	drm_connector_helper_add(&ctx->connector,
>>> -				 &tc358764_connector_helper_funcs);
>>> -	drm_connector_attach_encoder(&ctx->connector, bridge->encoder);
>>> -	drm_panel_attach(ctx->panel, &ctx->connector);
>>> -	ctx->connector.funcs->reset(&ctx->connector);
>>
>> I guess lack of calling .reset here is direct cause of WARN reported by
>> Marek.
>>
>>
>> Summarizing my findings:
>>
>> 1. drm_panel_bridge does not fit to this scenario - it relays on 'bridge
>> chaining" which has crazy assumption that order of hw initalization in
>> the display chain follows the same fixed order of calls for all hw.
> This would need to be addressed in the bridge core. I don't want to go
> back to manual chaining of operations, that opens the door to creating
> incompatibilities between bridges and display controllers. The pre/post
> enable/disable operations probably need to be better defined, and if a
> sink requires a smaller granularity, then new operations need to be
> added.


Bigger granularity of operations is source of incompatibilities. We have 
already multiple issues with only two operations - pre_enable, 
post_enable, developers are confused what put where, especially if they 
can test bridge driver only with only fixed chain determined by the 
platform they are working on.

Adding new operations will make things worse. On the other hand explicit 
calling ops of downstream device has following advantages:

- we see explicitly how the bridge and its sink is initialized, it is 
even easier to compare it with docs,

- in case the stream is split or duplicated to two or more sinks, and/or 
eventually joined then later, it is much easier and straightforward to 
program it with explicit ops. With bridge chaining it is impossible 
without workarounds - all the cases with dual DSI etc.


Regards

Andrzej


>> 2. tc35* bridge allocates/deallocates connector dynamically - to safely
>> handle drivers load/unload, and to avoid multiple deferred probe issues
>> , drm_panel_bridge does not support it.
>>
>> This and previous patch violates both points.
>>
>>> -
>>> -	return 0;
>>> -}
>>> -
>>> -static void tc358764_detach(struct drm_bridge *bridge)
>>>    {
>>>    	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>>>    
>>> -	drm_panel_detach(ctx->panel);
>>> -	ctx->panel = NULL;
>>> +	return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
>>> +				 bridge, flags);
>>>    }
>>>    
>>>    static const struct drm_bridge_funcs tc358764_bridge_funcs = {
>>> -	.disable = tc358764_disable,
>>>    	.post_disable = tc358764_post_disable,
>>> -	.enable = tc358764_enable,
>>>    	.pre_enable = tc358764_pre_enable,
>>>    	.attach = tc358764_attach,
>>> -	.detach = tc358764_detach,
>>>    };
>>>    
>>>    static int tc358764_parse_dt(struct tc358764 *ctx)
>>>    {
>>> +	struct drm_bridge *panel_bridge;
>>>    	struct device *dev = ctx->dev;
>>> +	struct drm_panel *panel;
>>>    	int ret;
>>>    
>>>    	ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
>>> @@ -407,12 +326,17 @@ static int tc358764_parse_dt(struct tc358764 *ctx)
>>>    		return PTR_ERR(ctx->gpio_reset);
>>>    	}
>>>    
>>> -	ret = drm_of_find_panel_or_bridge(ctx->dev->of_node, 1, 0, &ctx->panel,
>>> -					  NULL);
>>> -	if (ret && ret != -EPROBE_DEFER)
>>> -		dev_err(dev, "cannot find panel (%d)\n", ret);
>>> +	ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
>>> +	if (ret)
>>> +		return ret;
>>>    
>>> -	return ret;
>>> +	panel_bridge = devm_drm_panel_bridge_add(dev, panel);
>>> +
>>> +	if (IS_ERR(panel_bridge))
>>> +		return PTR_ERR(panel_bridge);
>>> +
>>> +	ctx->panel_bridge = panel_bridge;
>>> +	return 0;
>>>    }
>>>    
>>>    static int tc358764_configure_regulators(struct tc358764 *ctx)
>>> @@ -458,6 +382,7 @@ static int tc358764_probe(struct mipi_dsi_device *dsi)
>>>    		return ret;
>>>    
>>>    	ctx->bridge.funcs = &tc358764_bridge_funcs;
>>> +	ctx->bridge.type = DRM_MODE_CONNECTOR_LVDS;
>>>    	ctx->bridge.of_node = dev->of_node;
>>>    
>>>    	drm_bridge_add(&ctx->bridge);
diff mbox series

Patch

diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
index a277739fab58..fdde4cfdc724 100644
--- a/drivers/gpu/drm/bridge/tc358764.c
+++ b/drivers/gpu/drm/bridge/tc358764.c
@@ -153,10 +153,9 @@  static const char * const tc358764_supplies[] = {
 struct tc358764 {
 	struct device *dev;
 	struct drm_bridge bridge;
-	struct drm_connector connector;
 	struct regulator_bulk_data supplies[ARRAY_SIZE(tc358764_supplies)];
 	struct gpio_desc *gpio_reset;
-	struct drm_panel *panel;
+	struct drm_bridge *panel_bridge;
 	int error;
 };
 
@@ -210,12 +209,6 @@  static inline struct tc358764 *bridge_to_tc358764(struct drm_bridge *bridge)
 	return container_of(bridge, struct tc358764, bridge);
 }
 
-static inline
-struct tc358764 *connector_to_tc358764(struct drm_connector *connector)
-{
-	return container_of(connector, struct tc358764, connector);
-}
-
 static int tc358764_init(struct tc358764 *ctx)
 {
 	u32 v = 0;
@@ -278,43 +271,11 @@  static void tc358764_reset(struct tc358764 *ctx)
 	usleep_range(1000, 2000);
 }
 
-static int tc358764_get_modes(struct drm_connector *connector)
-{
-	struct tc358764 *ctx = connector_to_tc358764(connector);
-
-	return drm_panel_get_modes(ctx->panel, connector);
-}
-
-static const
-struct drm_connector_helper_funcs tc358764_connector_helper_funcs = {
-	.get_modes = tc358764_get_modes,
-};
-
-static const struct drm_connector_funcs tc358764_connector_funcs = {
-	.fill_modes = drm_helper_probe_single_connector_modes,
-	.destroy = drm_connector_cleanup,
-	.reset = drm_atomic_helper_connector_reset,
-	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
-	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
-};
-
-static void tc358764_disable(struct drm_bridge *bridge)
-{
-	struct tc358764 *ctx = bridge_to_tc358764(bridge);
-	int ret = drm_panel_disable(bridge_to_tc358764(bridge)->panel);
-
-	if (ret < 0)
-		dev_err(ctx->dev, "error disabling panel (%d)\n", ret);
-}
-
 static void tc358764_post_disable(struct drm_bridge *bridge)
 {
 	struct tc358764 *ctx = bridge_to_tc358764(bridge);
 	int ret;
 
-	ret = drm_panel_unprepare(ctx->panel);
-	if (ret < 0)
-		dev_err(ctx->dev, "error unpreparing panel (%d)\n", ret);
 	tc358764_reset(ctx);
 	usleep_range(10000, 15000);
 	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
@@ -335,70 +296,28 @@  static void tc358764_pre_enable(struct drm_bridge *bridge)
 	ret = tc358764_init(ctx);
 	if (ret < 0)
 		dev_err(ctx->dev, "error initializing bridge (%d)\n", ret);
-	ret = drm_panel_prepare(ctx->panel);
-	if (ret < 0)
-		dev_err(ctx->dev, "error preparing panel (%d)\n", ret);
-}
-
-static void tc358764_enable(struct drm_bridge *bridge)
-{
-	struct tc358764 *ctx = bridge_to_tc358764(bridge);
-	int ret = drm_panel_enable(ctx->panel);
-
-	if (ret < 0)
-		dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
 }
 
 static int tc358764_attach(struct drm_bridge *bridge,
 			   enum drm_bridge_attach_flags flags)
-{
-	struct tc358764 *ctx = bridge_to_tc358764(bridge);
-	struct drm_device *drm = bridge->dev;
-	int ret;
-
-	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
-		DRM_ERROR("Fix bridge driver to make connector optional!");
-		return -EINVAL;
-	}
-
-	ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
-	ret = drm_connector_init(drm, &ctx->connector,
-				 &tc358764_connector_funcs,
-				 DRM_MODE_CONNECTOR_LVDS);
-	if (ret) {
-		DRM_ERROR("Failed to initialize connector\n");
-		return ret;
-	}
-
-	drm_connector_helper_add(&ctx->connector,
-				 &tc358764_connector_helper_funcs);
-	drm_connector_attach_encoder(&ctx->connector, bridge->encoder);
-	drm_panel_attach(ctx->panel, &ctx->connector);
-	ctx->connector.funcs->reset(&ctx->connector);
-
-	return 0;
-}
-
-static void tc358764_detach(struct drm_bridge *bridge)
 {
 	struct tc358764 *ctx = bridge_to_tc358764(bridge);
 
-	drm_panel_detach(ctx->panel);
-	ctx->panel = NULL;
+	return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
+				 bridge, flags);
 }
 
 static const struct drm_bridge_funcs tc358764_bridge_funcs = {
-	.disable = tc358764_disable,
 	.post_disable = tc358764_post_disable,
-	.enable = tc358764_enable,
 	.pre_enable = tc358764_pre_enable,
 	.attach = tc358764_attach,
-	.detach = tc358764_detach,
 };
 
 static int tc358764_parse_dt(struct tc358764 *ctx)
 {
+	struct drm_bridge *panel_bridge;
 	struct device *dev = ctx->dev;
+	struct drm_panel *panel;
 	int ret;
 
 	ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
@@ -407,12 +326,17 @@  static int tc358764_parse_dt(struct tc358764 *ctx)
 		return PTR_ERR(ctx->gpio_reset);
 	}
 
-	ret = drm_of_find_panel_or_bridge(ctx->dev->of_node, 1, 0, &ctx->panel,
-					  NULL);
-	if (ret && ret != -EPROBE_DEFER)
-		dev_err(dev, "cannot find panel (%d)\n", ret);
+	ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
+	if (ret)
+		return ret;
 
-	return ret;
+	panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+
+	if (IS_ERR(panel_bridge))
+		return PTR_ERR(panel_bridge);
+
+	ctx->panel_bridge = panel_bridge;
+	return 0;
 }
 
 static int tc358764_configure_regulators(struct tc358764 *ctx)
@@ -458,6 +382,7 @@  static int tc358764_probe(struct mipi_dsi_device *dsi)
 		return ret;
 
 	ctx->bridge.funcs = &tc358764_bridge_funcs;
+	ctx->bridge.type = DRM_MODE_CONNECTOR_LVDS;
 	ctx->bridge.of_node = dev->of_node;
 
 	drm_bridge_add(&ctx->bridge);