Skip to contentSkip to conversation

drm/bridge: cdns-dsi: Fix the color-shift issue

PATCH Superseded Aradhya Bhatia 2 participants 22 messages Jan 11, 2025 – Jan 13, 2025

Subsystems (3)

v6 of 11 Superseded Latest is v11 →

lore
Share & tools
lore
#0 lore raw

v6 cover drm/bridge: cdns-dsi: Fix the color-shift issue

Hello all,

This series provides some crucial fixes and improvements for the Cadence's DSI TX (cdns-dsi) controller found commonly in Texas Instruments' J7 family of SoCs as well as in Sitara AM62P and AM62L SoCs.

Along with that, this series aims to fix the color-shift issue that has been going on with the DSI controller. This controller requires to be enabled before the previous entity enables its stream[0]. It's a strict requirement which, if not followed, causes the colors to "shift" on the display. The fix happens in 2 steps.

1. The bridge pre_enable calls have been shifted before the crtc_enable and the bridge post_disable calls have been shifted after the crtc_disable. This has been done as per the definition of bridge pre_enable.

"The display pipe (i.e. clocks and timing signals) feeding this bridge will not yet be running when this callback is called".

Since CRTC is also a source feeding the bridge, it should not be enabled before the bridges in the pipeline are pre_enabled.

The sequence of enable after this patch will look like:

bridge[n]_pre_enable
...
bridge[1]_pre_enable

crtc_enable
encoder_enable

bridge[1]_enable
...
bridge[n]_enable

and vice-versa for the bridge chain disable sequence.

2. The cdns-dsi enable / disable sequences have now been moved to pre_enable and post_disable sequences. This is the only way to have cdns-dsi drivers be up and ready before the previous entity is enables its streaming.

The DSI also spec requires the Clock and Data Lanes be ready before the DSI TX enables its stream[0]. A patch has been added to make the code wait for that to happen. Going ahead with further DSI (and DSS configuration), while the lanes are not ready, has been found to be another reason for shift in colors.

These patches have been tested with J721E based BeagleboneAI64 along with a RaspberryPi 7" DSI panel. The extra patches can be found in the "next_dsi-v6_5-test_NEW" branch of my github fork[1] for anyone who would like to test them.

* Important note about the authorship of patches *

All the patches in the previous revisions of this series, as well as a majority of this revision too have been authored when I owned a "ti.com" based email id, i.e. <[email protected]>. This email id is not in use anymore, and all the work done later has been part of my personal work. Since the original patches were authored using TI's email id, I have maintained the original authorships as they are, as well as their sign offs.

I have further added another another sign off that uses my current (and personal) email id, the one that is being used to send this revision, i.e. <[email protected]>.

Thanks,
Aradhya

[0]: Section 12.6.5.7.3: "Start-up Procedure" [For DSI TX controller]
in TDA4VM Technical Reference Manual https://www.ti.com/lit/zip/spruil1

[1]: https://github.com/aradhya07/linux-ab/tree/next_dsi-v6_5-test_NEW

Change Log:
- Changes in v6:
- Reword patch 3 to better explain the fixes around phy de-init.
- Fix the Lane ready timeout condition in patch 7.
- Fix the dsi _bridge_atomic_check() implementation by adding a new bridge state structure in patch 10.
- Rework and combine patches v5:11/13 and v5:12/13 to v6:11/12.
- Generate the patches of these series using the "patience" algorithm.
Note: All patches, except v6:11/12, *do not* differ from their default (greedy) algorithm variants.
For patch 11, the patience algorithm significantly improves the readability.
- Rename and move the Bridge enable/disable enums from public to private in patch 11.
- Add R-b tags of Tomi Valkeinen in patch 6, and Dmitry Baryshkov in patch 2.

- Changes in v5:
- Fix subject and description in patch 1/13.
- Add patch to check the return value of phy_mipi_dphy_get_default_config() (patch: 6/13).
- Change the Clk and Data Lane ready timeout from forever to 5s.
- Print an error instead of calling WARN_ON_ONCE in patch 7/13.
- Drop patch v4-07/11: "drm/bridge: cdns-dsi: Reset the DCS write FIFO". There has been some inconsistencies found with this patch upon further testing. This patch was being used to enable a DSI panel based on ILITEK ILI9881C bridge. This will be debugged separately.
- Add patch to move the DSI mode check from _atomic_enable() to _atomic_check() (patch: 10/13).
- Split patch v4-10/11 into 2 patches - 11/13 and 12/13.
Patch 11/13 separates out the Encoder-Bridge operations into a helper function *without* changing the logic. Patch 12/13 then changes the order of the encoder-bridge operations as was intended in the original patch.
- Add detailed comment for patch 13/13.
- Add Tomi Valkeinen's R-b in patches 1, 2, 4, 5, 7, 8, 9, 13.

- Changes in v4:
- Add new patch, "drm/bridge: cdns-dsi: Move to devm_drm_of_get_bridge()", to update to an auto-managed way of finding next bridge in the chain.
- Drop patch "drm/bridge: cdns-dsi: Fix the phy_initialized variable" and add "drm/bridge: cdns-dsi: Fix Phy _init() and _exit()" that properly de-initializes the Phy and maintains the initialization state.
- Reword patch "drm/bridge: cdns-dsi: Reset the DCS write FIFO" to explain the HW concerns better.
- Add R-b tag from Dmitry Baryshkov for patches 1/11 and 8/11.

- Changes in v3:
- Reword the commit message for patch "drm/bridge: cdns-dsi: Fix OF node pointer".
- Add a new helper API to figure out DSI host input pixel format in patch "drm/mipi-dsi: Add helper to find input format".
- Use a common function for bridge pre-enable and enable, and bridge disable and post-disable, to avoid code duplication.
- Add T-b tag from Dominik Haller in patch 5/10. (Missed to add it in v2).
- Add R-b tag from Dmitry Baryshkov for patch 8/10.

- Changes in v2:
- Drop patch "drm/tidss: Add CRTC mode_fixup"
- Split patch "drm/bridge: cdns-dsi: Fix minor bugs" into 4 separate ones
- Drop support for early_enable/late_disable APIs and instead re-order the pre_enable / post_disable APIs to be called before / after crtc_enable / crtc_disable.
- Drop support for early_enable/late_disable in cdns-dsi and use pre_enable/post_disable APIs instead to do bridge enable/disable.

Previous versions:

v1: https://lore.kernel.org/all/[email protected]/
v2: https://lore.kernel.org/all/[email protected]/
v3: https://lore.kernel.org/all/[email protected]/
v4: https://lore.kernel.org/all/[email protected]/
v5: https://lore.kernel.org/all/[email protected]/

Aradhya Bhatia (12):
drm/bridge: cdns-dsi: Fix connecting to next bridge drm/bridge: cdns-dsi: Move to devm_drm_of_get_bridge() drm/bridge: cdns-dsi: Fix phy de-init and flag it so drm/bridge: cdns-dsi: Fix the link and phy init order drm/bridge: cdns-dsi: Fix the clock variable for mode_valid() drm/bridge: cdns-dsi: Check return value when getting default PHY
config
drm/bridge: cdns-dsi: Wait for Clk and Data Lanes to be ready drm/mipi-dsi: Add helper to find input format
drm/bridge: cdns-dsi: Support atomic bridge APIs drm/bridge: cdns-dsi: Move DSI mode check to _atomic_check() drm/atomic-helper: Re-order bridge chain pre-enable and post-disable drm/bridge: cdns-dsi: Use pre_enable/post_disable to enable/disable

 .../gpu/drm/bridge/cadence/cdns-dsi-core.c    | 222 ++++++++++---
 .../gpu/drm/bridge/cadence/cdns-dsi-core.h    |   3 +-
 drivers/gpu/drm/drm_atomic_helper.c           | 300 +++++++++++-------
 drivers/gpu/drm/drm_mipi_dsi.c                |  37 +++
 include/drm/drm_mipi_dsi.h                    |   1 +
 5 files changed, 390 insertions(+), 173 deletions(-)

base-commit: 2b88851f583d3c4e40bcd40cfe1965241ec229dd

Signature · 1 line
2.34.1
#1 lore raw

PATCH v6 1/12 drm/bridge: cdns-dsi: Fix connecting to next bridge

From: Aradhya Bhatia <[email protected]>

Fix the OF node pointer passed to the of_drm_find_bridge() call to find the next bridge in the display chain.

The code to find the next panel (and create its panel-bridge) works fine, but to find the next (non-panel) bridge does not.

To find the next bridge in the pipeline, we need to pass "np" - the OF node pointer of the next entity in the devicetree chain. Passing "of_node" to of_drm_find_bridge (which is what the code does currently) will fetch the bridge for the cdns-dsi which is not what's required.

Fix that.

Fixes e19233955d9e ("drm/bridge: Add Cadence DSI driver")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +1 -1
index c7a0247e06ad..2f897ea5e80a 100644
@@ -952,7 +952,7 @@ static int cdns_dsi_attach(struct mipi_dsi_host *host,
 		bridge = drm_panel_bridge_add_typed(panel,
 						    DRM_MODE_CONNECTOR_DSI);
 	} else {
-		bridge = of_drm_find_bridge(dev->dev.of_node);
+		bridge = of_drm_find_bridge(np);
 		if (!bridge)
 			bridge = ERR_PTR(-EINVAL);
 	}
Signature · 1 line
2.34.1
#2 lore raw

PATCH v6 2/12 drm/bridge: cdns-dsi: Move to devm_drm_of_get_bridge()

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Instead of manually finding the next bridge/panel, and maintaining the panel-bridge (in-case the next entity is a panel), switch to using the automatically managing devm_drm_of_get_bridge() API.

Drop the drm_panel support completely from the driver while at it.

---
 .../gpu/drm/bridge/cadence/cdns-dsi-core.c    | 28 ++-----------------
 .../gpu/drm/bridge/cadence/cdns-dsi-core.h    |  2 --
 2 files changed, 3 insertions(+), 27 deletions(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +3 -25
index 2f897ea5e80a..056583e81155 100644
@@ -920,8 +920,6 @@ static int cdns_dsi_attach(struct mipi_dsi_host *host,
 	struct cdns_dsi_output *output = &dsi->output;
 	struct cdns_dsi_input *input = &dsi->input;
 	struct drm_bridge *bridge;
-	struct drm_panel *panel;
-	struct device_node *np;
 	int ret;
 
 	/*
@@ -939,26 +937,10 @@ static int cdns_dsi_attach(struct mipi_dsi_host *host,
 	/*
 	 * The host <-> device link might be described using an OF-graph
 	 * representation, in this case we extract the device of_node from
-	 * this representation, otherwise we use dsidev->dev.of_node which
-	 * should have been filled by the core.
+	 * this representation.
 	 */
-	np = of_graph_get_remote_node(dsi->base.dev->of_node, DSI_OUTPUT_PORT,
-				      dev->channel);
-	if (!np)
-		np = of_node_get(dev->dev.of_node);
-
-	panel = of_drm_find_panel(np);
-	if (!IS_ERR(panel)) {
-		bridge = drm_panel_bridge_add_typed(panel,
-						    DRM_MODE_CONNECTOR_DSI);
-	} else {
-		bridge = of_drm_find_bridge(np);
-		if (!bridge)
-			bridge = ERR_PTR(-EINVAL);
-	}
-
-	of_node_put(np);
-
+	bridge = devm_drm_of_get_bridge(dsi->base.dev, dsi->base.dev->of_node,
+					DSI_OUTPUT_PORT, dev->channel);
 	if (IS_ERR(bridge)) {
 		ret = PTR_ERR(bridge);
 		dev_err(host->dev, "failed to add DSI device %s (err = %d)",
@@ -968,7 +950,6 @@ static int cdns_dsi_attach(struct mipi_dsi_host *host,
 
 	output->dev = dev;
 	output->bridge = bridge;
-	output->panel = panel;
 
 	/*
 	 * The DSI output has been properly configured, we can now safely
@@ -984,12 +965,9 @@ static int cdns_dsi_detach(struct mipi_dsi_host *host,
 			   struct mipi_dsi_device *dev)
 {
 	struct cdns_dsi *dsi = to_cdns_dsi(host);
-	struct cdns_dsi_output *output = &dsi->output;
 	struct cdns_dsi_input *input = &dsi->input;
 
 	drm_bridge_remove(&input->bridge);
-	if (output->panel)
-		drm_panel_bridge_remove(output->bridge);
 
 	return 0;
 }
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h +0 -2
index ca7ea2da635c..5db5dbbbcaad 100644
@@ -10,7 +10,6 @@
 
 #include <drm/drm_bridge.h>
 #include <drm/drm_mipi_dsi.h>
-#include <drm/drm_panel.h>
 
 #include <linux/bits.h>
 #include <linux/completion.h>
@@ -21,7 +20,6 @@ struct reset_control;
 
 struct cdns_dsi_output {
 	struct mipi_dsi_device *dev;
-	struct drm_panel *panel;
 	struct drm_bridge *bridge;
 	union phy_configure_opts phy_opts;
 };
Signature · 1 line
2.34.1
#3 lore raw

PATCH v6 3/12 drm/bridge: cdns-dsi: Fix phy de-init and flag it so

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

The driver code doesn't have a de-initialization path as yet, and so it does not clear the phy_initialized flag while suspending. This is a problem because after resume the driver looks at this flag to determine if a Phy re-initialization is required or not. It is in fact required because the hardware is resuming from a suspend, but the driver does not carry out any re-initialization causing the D-Phy to not work at all.

Add the counterpart of phy_power_on(), that is phy_power_off() from the _bridge_disable() and clear the flags so that the Phy can be initialized again when required.

Move the Phy initialization from _bridge_enable() to _resume(), and de-initialize during the _suspend() - so that the phy_{init, exit}() take place once every resume/suspend cycle.

The order of calls still remains the same. phy_init() needs to be called before phy_power_on() - which happens still. What this patch changes is the frequency of the phy_init() call. Instead of it being called once every bridge enable/disable cycle, it is now being called once every resume/suspend cycle. This move has been considered safe after numerous tests with the hardware.

Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +8 -2
index 056583e81155..47550891427c 100644
@@ -672,6 +672,10 @@ static void cdns_dsi_bridge_disable(struct drm_bridge *bridge)
 	if (dsi->platform_ops && dsi->platform_ops->disable)
 		dsi->platform_ops->disable(dsi);
 
+	phy_power_off(dsi->dphy);
+	dsi->link_initialized = false;
+	dsi->phy_initialized = false;
+
 	pm_runtime_put(dsi->base.dev);
 }
 
@@ -698,7 +702,6 @@ static void cdns_dsi_hs_init(struct cdns_dsi *dsi)
 	       DPHY_CMN_PDN | DPHY_PLL_PDN,
 	       dsi->regs + MCTL_DPHY_CFG0);
 
-	phy_init(dsi->dphy);
 	phy_set_mode(dsi->dphy, PHY_MODE_MIPI_DPHY);
 	phy_configure(dsi->dphy, &output->phy_opts);
 	phy_power_on(dsi->dphy);
@@ -1120,6 +1123,8 @@ static int __maybe_unused cdns_dsi_resume(struct device *dev)
 	clk_prepare_enable(dsi->dsi_p_clk);
 	clk_prepare_enable(dsi->dsi_sys_clk);
 
+	phy_init(dsi->dphy);
+
 	return 0;
 }
 
@@ -1127,10 +1132,11 @@ static int __maybe_unused cdns_dsi_suspend(struct device *dev)
 {
 	struct cdns_dsi *dsi = dev_get_drvdata(dev);
 
+	phy_exit(dsi->dphy);
+
 	clk_disable_unprepare(dsi->dsi_sys_clk);
 	clk_disable_unprepare(dsi->dsi_p_clk);
 	reset_control_assert(dsi->dsi_p_rst);
-	dsi->link_initialized = false;
 	return 0;
 }
 
Signature · 1 line
2.34.1
#4 lore raw

Re: [PATCH v6 03/12] drm/bridge: cdns-dsi: Fix phy de-init and flag it so

Quoted 9 lines · Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

The driver code doesn't have a de-initialization path as yet, and so it does not clear the phy_initialized flag while suspending. This is a problem because after resume the driver looks at this flag to determine if a Phy re-initialization is required or not. It is in fact required because the hardware is resuming from a suspend, but the driver does not carry out any re-initialization causing the D-Phy to not work at all.

Quoted 7 lines

Add the counterpart of phy_power_on(), that is phy_power_off() from the _bridge_disable() and clear the flags so that the Phy can be initialized again when required.

Move the Phy initialization from _bridge_enable() to _resume(), and de-initialize during the _suspend() - so that the phy_{init, exit}() take place once every resume/suspend cycle.

Is it okay to call phy_init() before writing MCTL_DPHY_CFG0 ?

Quoted 14 lines

The order of calls still remains the same. phy_init() needs to be called before phy_power_on() - which happens still. What this patch changes is the frequency of the phy_init() call. Instead of it being called once every bridge enable/disable cycle, it is now being called once every resume/suspend cycle. This move has been considered safe after numerous tests with the hardware.

Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
Signature · 2 lines
With best wishes
Dmitry
#5 lore raw

Re: [PATCH v6 03/12] drm/bridge: cdns-dsi: Fix phy de-init and flag it so

Thank you for reviewing the patches, Dmitry.

Quoted 14 lines · Dmitry Baryshkov
Quoted 13 lines · Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

The driver code doesn't have a de-initialization path as yet, and so it does not clear the phy_initialized flag while suspending. This is a problem because after resume the driver looks at this flag to determine if a Phy re-initialization is required or not. It is in fact required because the hardware is resuming from a suspend, but the driver does not carry out any re-initialization causing the D-Phy to not work at all.

Add the counterpart of phy_power_on(), that is phy_power_off() from the _bridge_disable() and clear the flags so that the Phy can be initialized again when required.

Quoted 5 lines
Quoted 3 lines

Move the Phy initialization from _bridge_enable() to _resume(), and de-initialize during the _suspend() - so that the phy_{init, exit}() take place once every resume/suspend cycle.

Is it okay to call phy_init() before writing MCTL_DPHY_CFG0 ?

The phy_init() is a no-op when we look at the D-Phy driver, which does not implement the _init() hook at all. So, in this case, all phy_init() call ever manages to do is book-keeping. Book-keeping that isn't required to be done every time we do a bridge enable/disable.

But despite the no-op nature of the call, I guess it would still not make sense to call it before the reset assert done in MCTL_DPHY_CFG0.

Instead of moving it to resume(), I can keep phy_init() as is, and add phy_exit() in the bridge disable path, instead of the suspend path.

Regards
Aradhya

Quoted 16 lines
Quoted 15 lines

The order of calls still remains the same. phy_init() needs to be called before phy_power_on() - which happens still. What this patch changes is the frequency of the phy_init() call. Instead of it being called once every bridge enable/disable cycle, it is now being called once every resume/suspend cycle. This move has been considered safe after numerous tests with the hardware.

Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
#6 lore raw

Re: [PATCH v6 03/12] drm/bridge: cdns-dsi: Fix phy de-init and flag it so

Quoted 21 lines · Aradhya Bhatia

Thank you for reviewing the patches, Dmitry.

Quoted 18 lines · Dmitry Baryshkov
Quoted 16 lines · Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

The driver code doesn't have a de-initialization path as yet, and so it does not clear the phy_initialized flag while suspending. This is a problem because after resume the driver looks at this flag to determine if a Phy re-initialization is required or not. It is in fact required because the hardware is resuming from a suspend, but the driver does not carry out any re-initialization causing the D-Phy to not work at all.

Add the counterpart of phy_power_on(), that is phy_power_off() from the _bridge_disable() and clear the flags so that the Phy can be initialized again when required.

Move the Phy initialization from _bridge_enable() to _resume(), and de-initialize during the _suspend() - so that the phy_{init, exit}() take place once every resume/suspend cycle.

Quoted 8 lines
Quoted 1 line

Is it okay to call phy_init() before writing MCTL_DPHY_CFG0 ?

The phy_init() is a no-op when we look at the D-Phy driver, which does not implement the _init() hook at all. So, in this case, all phy_init() call ever manages to do is book-keeping. Book-keeping that isn't required to be done every time we do a bridge enable/disable. But despite the no-op nature of the call, I guess it would still not make sense to call it before the reset assert done in MCTL_DPHY_CFG0.

Yes, please.

Quoted 24 lines

Instead of moving it to resume(), I can keep phy_init() as is, and add phy_exit() in the bridge disable path, instead of the suspend path.

Regards
Aradhya

Quoted 17 lines
Quoted 16 lines

The order of calls still remains the same. phy_init() needs to be called before phy_power_on() - which happens still. What this patch changes is the frequency of the phy_init() call. Instead of it being called once every bridge enable/disable cycle, it is now being called once every resume/suspend cycle. This move has been considered safe after numerous tests with the hardware.

Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
Signature · 2 lines
With best wishes
Dmitry
#7 lore raw

PATCH v6 4/12 drm/bridge: cdns-dsi: Fix the link and phy init order

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

The order of init of DSI link and DSI phy is wrong. The DSI link needs to be configured before the DSI phy is getting configured. Otherwise, the D-Phy is unable to lock in on the incoming PLL Reference clock[0].

Fix the order of inits.

[0]: See section 12.6.5.7.3 "Start-up Procedure" in J721E SoC TRM
TRM Link: http://www.ti.com/lit/pdf/spruil1

Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +1 -1
index 47550891427c..3b3c45df1399 100644
@@ -778,8 +778,8 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge)
 
 	WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, false));
 
-	cdns_dsi_hs_init(dsi);
 	cdns_dsi_init_link(dsi);
+	cdns_dsi_hs_init(dsi);
 
 	writel(HBP_LEN(dsi_cfg.hbp) | HSA_LEN(dsi_cfg.hsa),
 	       dsi->regs + VID_HSIZE1);
Signature · 1 line
2.34.1
#8 lore raw

PATCH v6 5/12 drm/bridge: cdns-dsi: Fix the clock variable for mode_valid()

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Allow the D-Phy config checks to use mode->clock instead of mode->crtc_clock during mode_valid checks, like everywhere else in the driver.

Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +2 -1
index 3b3c45df1399..9c743fde2861 100644
@@ -568,13 +568,14 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
 	struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy;
 	unsigned long dsi_hss_hsa_hse_hbp;
 	unsigned int nlanes = output->dev->lanes;
+	int mode_clock = (mode_valid_check ? mode->clock : mode->crtc_clock);
 	int ret;
 
 	ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check);
 	if (ret)
 		return ret;
 
-	phy_mipi_dphy_get_default_config(mode->crtc_clock * 1000,
+	phy_mipi_dphy_get_default_config(mode_clock * 1000,
 					 mipi_dsi_pixel_format_to_bpp(output->dev->format),
 					 nlanes, phy_cfg);
 
Signature · 1 line
2.34.1
#9 lore raw

Re: [PATCH v6 05/12] drm/bridge: cdns-dsi: Fix the clock variable for mode_valid()

Quoted 5 lines · Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Allow the D-Phy config checks to use mode->clock instead of mode->crtc_clock during mode_valid checks, like everywhere else in the driver.

Please describe why, not what.

Quoted 32 lines
Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +2 -1
index 3b3c45df1399..9c743fde2861 100644
@@ -568,13 +568,14 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
 	struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy;
 	unsigned long dsi_hss_hsa_hse_hbp;
 	unsigned int nlanes = output->dev->lanes;
+	int mode_clock = (mode_valid_check ? mode->clock : mode->crtc_clock);
 	int ret;
 
 	ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check);
 	if (ret)
 		return ret;
 
-	phy_mipi_dphy_get_default_config(mode->crtc_clock * 1000,
+	phy_mipi_dphy_get_default_config(mode_clock * 1000,
 					 mipi_dsi_pixel_format_to_bpp(output->dev->format),
 					 nlanes, phy_cfg);
 
Signature · 1 line
2.34.1
Signature · 2 lines
With best wishes
Dmitry
#10 lore raw

Re: [PATCH v6 05/12] drm/bridge: cdns-dsi: Fix the clock variable for mode_valid()

Quoted 8 lines · Dmitry Baryshkov
Quoted 5 lines · Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Allow the D-Phy config checks to use mode->clock instead of mode->crtc_clock during mode_valid checks, like everywhere else in the driver.

Please describe why, not what.

It is unclear why the rest of the code uses mode->crtc_* parameters at all during the non mode validation phase.

But during that phase, the crtc_* parameters are not generated (duplicated in this case) from the regular ones, and so the validation fails. The patch prevents that from happening by streamlining with other instances.

I will update the commit text with this.

Regards
Aradhya

Quoted 34 lines
Quoted 33 lines
Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +2 -1
index 3b3c45df1399..9c743fde2861 100644
@@ -568,13 +568,14 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
 	struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy;
 	unsigned long dsi_hss_hsa_hse_hbp;
 	unsigned int nlanes = output->dev->lanes;
+	int mode_clock = (mode_valid_check ? mode->clock : mode->crtc_clock);
 	int ret;
 
 	ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check);
 	if (ret)
 		return ret;
 
-	phy_mipi_dphy_get_default_config(mode->crtc_clock * 1000,
+	phy_mipi_dphy_get_default_config(mode_clock * 1000,
 					 mipi_dsi_pixel_format_to_bpp(output->dev->format),
 					 nlanes, phy_cfg);
 
Signature · 1 line
2.34.1
#11 lore raw

Re: [PATCH v6 05/12] drm/bridge: cdns-dsi: Fix the clock variable for mode_valid()

Quoted 14 lines · Aradhya Bhatia
Quoted 8 lines · Dmitry Baryshkov
Quoted 5 lines · Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Allow the D-Phy config checks to use mode->clock instead of mode->crtc_clock during mode_valid checks, like everywhere else in the driver.

Please describe why, not what.

It is unclear why the rest of the code uses mode->crtc_* parameters at all during the non mode validation phase.

Quoted 6 lines

But during that phase, the crtc_* parameters are not generated (duplicated in this case) from the regular ones, and so the validation fails. The patch prevents that from happening by streamlining with other instances.

I will update the commit text with this.

SGTM

Quoted 38 lines

Regards
Aradhya

Quoted 34 lines
Quoted 33 lines
Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +2 -1
index 3b3c45df1399..9c743fde2861 100644
@@ -568,13 +568,14 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
 	struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy;
 	unsigned long dsi_hss_hsa_hse_hbp;
 	unsigned int nlanes = output->dev->lanes;
+	int mode_clock = (mode_valid_check ? mode->clock : mode->crtc_clock);
 	int ret;
 
 	ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check);
 	if (ret)
 		return ret;
 
-	phy_mipi_dphy_get_default_config(mode->crtc_clock * 1000,
+	phy_mipi_dphy_get_default_config(mode_clock * 1000,
 					 mipi_dsi_pixel_format_to_bpp(output->dev->format),
 					 nlanes, phy_cfg);
 
Signature · 1 line
2.34.1
Signature · 2 lines
With best wishes
Dmitry
#12 lore raw

PATCH v6 6/12 drm/bridge: cdns-dsi: Check return value when getting default PHY config

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Check for the return value of the phy_mipi_dphy_get_default_config() call, and incase of an error, return back the same.

Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +5 -3
index 9c743fde2861..6b051e91b71c 100644
@@ -575,9 +575,11 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
 	if (ret)
 		return ret;
 
-	phy_mipi_dphy_get_default_config(mode_clock * 1000,
-					 mipi_dsi_pixel_format_to_bpp(output->dev->format),
-					 nlanes, phy_cfg);
+	ret = phy_mipi_dphy_get_default_config(mode_clock * 1000,
+					       mipi_dsi_pixel_format_to_bpp(output->dev->format),
+					       nlanes, phy_cfg);
+	if (ret)
+		return ret;
 
 	ret = cdns_dsi_adjust_phy_config(dsi, dsi_cfg, phy_cfg, mode, mode_valid_check);
 	if (ret)
Signature · 1 line
2.34.1
Reviewed-by on v6 6/12
#13 lore raw

Re: [PATCH v6 06/12] drm/bridge: cdns-dsi: Check return value when getting default PHY config

Quoted 5 lines · Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Check for the return value of the phy_mipi_dphy_get_default_config() call, and incase of an error, return back the same.

Quoted 8 lines
Fixes fced5a364dee ("drm/bridge: cdns: Convert to phy framework")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
Signature · 2 lines
With best wishes
Dmitry
#14 lore raw

PATCH v6 7/12 drm/bridge: cdns-dsi: Wait for Clk and Data Lanes to be ready

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Once the DSI Link and DSI Phy are initialized, the code needs to wait for Clk and Data Lanes to be ready, before continuing configuration. This is in accordance with the DSI Start-up procedure, found in the Technical Reference Manual of Texas Instrument's J721E SoC[0] which houses this DSI TX controller.

If the previous bridge (or crtc/encoder) are configured pre-maturely, the input signal FIFO gets corrupt. This introduces a color-shift on the display.

Allow the driver to wait for the clk and data lanes to get ready during DSI enable.

[0]: See section 12.6.5.7.3 "Start-up Procedure" in J721E SoC TRM
TRM Link: http://www.ti.com/lit/pdf/spruil1

Fixes e19233955d9e ("drm/bridge: Add Cadence DSI driver")
---
 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +14 -1
index 6b051e91b71c..bb180165ac4f 100644
@@ -767,7 +767,7 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge)
 	struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy;
 	unsigned long tx_byte_period;
 	struct cdns_dsi_cfg dsi_cfg;
-	u32 tmp, reg_wakeup, div;
+	u32 tmp, reg_wakeup, div, status;
 	int nlanes;
 
 	if (WARN_ON(pm_runtime_get_sync(dsi->base.dev) < 0))
@@ -784,6 +784,19 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge)
 	cdns_dsi_init_link(dsi);
 	cdns_dsi_hs_init(dsi);
 
+	/*
+	 * Now that the DSI Link and DSI Phy are initialized,
+	 * wait for the CLK and Data Lanes to be ready.
+	 */
+	tmp = CLK_LANE_RDY;
+	for (int i = 0; i < nlanes; i++)
+		tmp |= DATA_LANE_RDY(i);
+
+	if (readl_poll_timeout(dsi->regs + MCTL_MAIN_STS, status,
+			       (tmp == (status & tmp)), 100, 500000))
+		dev_err(dsi->base.dev,
+			"Timed Out: DSI-DPhy Clock and Data Lanes not ready.\n");
+
 	writel(HBP_LEN(dsi_cfg.hbp) | HSA_LEN(dsi_cfg.hsa),
 	       dsi->regs + VID_HSIZE1);
 	writel(HFP_LEN(dsi_cfg.hfp) | HACT_LEN(dsi_cfg.hact),
Signature · 1 line
2.34.1
#15 lore raw

PATCH v6 8/12 drm/mipi-dsi: Add helper to find input format

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Add a helper API that can be used by the DSI hosts to find the required input bus format for the given output dsi pixel format.

---
 drivers/gpu/drm/drm_mipi_dsi.c | 37 ++++++++++++++++++++++++++++++++++
 include/drm/drm_mipi_dsi.h     |  1 +
 2 files changed, 38 insertions(+)
drivers/gpu/drm/drm_mipi_dsi.c +37 -0
index 5e5c5f84daac..076826f2445a 100644
@@ -36,6 +36,8 @@
 #include <drm/drm_mipi_dsi.h>
 #include <drm/drm_print.h>
 
+#include <linux/media-bus-format.h>
+
 #include <video/mipi_display.h>
 
 /**
@@ -870,6 +872,41 @@ ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params,
 }
 EXPORT_SYMBOL(mipi_dsi_generic_read);
 
+/**
+ * drm_mipi_dsi_get_input_bus_fmt() - Get the required MEDIA_BUS_FMT_* based
+ *				      input pixel format for a given DSI output
+ *				      pixel format
+ * @dsi_format: pixel format that a DSI host needs to output
+ *
+ * Various DSI hosts can use this function during their
+ * &drm_bridge_funcs.atomic_get_input_bus_fmts operation to ascertain
+ * the MEDIA_BUS_FMT_* pixel format required as input.
+ *
+ * RETURNS:
+ * a 32-bit MEDIA_BUS_FMT_* value on success or 0 in case of failure.
+ */
+u32 drm_mipi_dsi_get_input_bus_fmt(enum mipi_dsi_pixel_format dsi_format)
+{
+	switch (dsi_format) {
+	case MIPI_DSI_FMT_RGB888:
+		return MEDIA_BUS_FMT_RGB888_1X24;
+
+	case MIPI_DSI_FMT_RGB666:
+		return MEDIA_BUS_FMT_RGB666_1X24_CPADHI;
+
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		return MEDIA_BUS_FMT_RGB666_1X18;
+
+	case MIPI_DSI_FMT_RGB565:
+		return MEDIA_BUS_FMT_RGB565_1X16;
+
+	default:
+		/* Unsupported DSI Format */
+		return 0;
+	}
+}
+EXPORT_SYMBOL(drm_mipi_dsi_get_input_bus_fmt);
+
 /**
  * mipi_dsi_dcs_write_buffer() - transmit a DCS command with payload
  * @dsi: DSI peripheral device
include/drm/drm_mipi_dsi.h +1 -0
index 94400a78031f..9e2804e3a2b0 100644
@@ -293,6 +293,7 @@ void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
 				  const void *payload, size_t size);
 ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params,
 			      size_t num_params, void *data, size_t size);
+u32 drm_mipi_dsi_get_input_bus_fmt(enum mipi_dsi_pixel_format dsi_format);
 
 #define mipi_dsi_msleep(ctx, delay)	\
 	do {				\
Signature · 1 line
2.34.1
#16 lore raw

PATCH v6 9/12 drm/bridge: cdns-dsi: Support atomic bridge APIs

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

Change the existing (and deprecated) bridge hooks, to the bridge atomic APIs.

Add drm helpers for duplicate_state, destroy_state, and bridge_reset bridge hooks.

Further add support for the input format negotiation hook.

---
 .../gpu/drm/bridge/cadence/cdns-dsi-core.c    | 51 ++++++++++++++++---
 1 file changed, 43 insertions(+), 8 deletions(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +43 -8
index bb180165ac4f..acef2171719b 100644
@@ -658,7 +658,8 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge,
 	return MODE_OK;
 }
 
-static void cdns_dsi_bridge_disable(struct drm_bridge *bridge)
+static void cdns_dsi_bridge_atomic_disable(struct drm_bridge *bridge,
+					   struct drm_bridge_state *old_bridge_state)
 {
 	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
 	struct cdns_dsi *dsi = input_to_dsi(input);
@@ -682,7 +683,8 @@ static void cdns_dsi_bridge_disable(struct drm_bridge *bridge)
 	pm_runtime_put(dsi->base.dev);
 }
 
-static void cdns_dsi_bridge_post_disable(struct drm_bridge *bridge)
+static void cdns_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge,
+						struct drm_bridge_state *old_bridge_state)
 {
 	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
 	struct cdns_dsi *dsi = input_to_dsi(input);
@@ -758,7 +760,8 @@ static void cdns_dsi_init_link(struct cdns_dsi *dsi)
 	dsi->link_initialized = true;
 }
 
-static void cdns_dsi_bridge_enable(struct drm_bridge *bridge)
+static void cdns_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
+					  struct drm_bridge_state *old_bridge_state)
 {
 	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
 	struct cdns_dsi *dsi = input_to_dsi(input);
@@ -911,7 +914,8 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge)
 	writel(tmp, dsi->regs + MCTL_MAIN_EN);
 }
 
-static void cdns_dsi_bridge_pre_enable(struct drm_bridge *bridge)
+static void cdns_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
+					      struct drm_bridge_state *old_bridge_state)
 {
 	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
 	struct cdns_dsi *dsi = input_to_dsi(input);
@@ -923,13 +927,44 @@ static void cdns_dsi_bridge_pre_enable(struct drm_bridge *bridge)
 	cdns_dsi_hs_init(dsi);
 }
 
+static u32 *cdns_dsi_bridge_get_input_bus_fmts(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)
+{
+	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
+	struct cdns_dsi *dsi = input_to_dsi(input);
+	struct cdns_dsi_output *output = &dsi->output;
+	u32 *input_fmts;
+
+	*num_input_fmts = 0;
+
+	input_fmts = kzalloc(sizeof(*input_fmts), GFP_KERNEL);
+	if (!input_fmts)
+		return NULL;
+
+	input_fmts[0] = drm_mipi_dsi_get_input_bus_fmt(output->dev->format);
+	if (!input_fmts[0])
+		return NULL;
+
+	*num_input_fmts = 1;
+
+	return input_fmts;
+}
+
 static const struct drm_bridge_funcs cdns_dsi_bridge_funcs = {
 	.attach = cdns_dsi_bridge_attach,
 	.mode_valid = cdns_dsi_bridge_mode_valid,
-	.disable = cdns_dsi_bridge_disable,
-	.pre_enable = cdns_dsi_bridge_pre_enable,
-	.enable = cdns_dsi_bridge_enable,
-	.post_disable = cdns_dsi_bridge_post_disable,
+	.atomic_disable = cdns_dsi_bridge_atomic_disable,
+	.atomic_pre_enable = cdns_dsi_bridge_atomic_pre_enable,
+	.atomic_enable = cdns_dsi_bridge_atomic_enable,
+	.atomic_post_disable = cdns_dsi_bridge_atomic_post_disable,
+	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+	.atomic_reset = drm_atomic_helper_bridge_reset,
+	.atomic_get_input_bus_fmts = cdns_dsi_bridge_get_input_bus_fmts,
 };
 
 static int cdns_dsi_attach(struct mipi_dsi_host *host,
Signature · 1 line
2.34.1
#17 lore raw

PATCH v6 10/12 drm/bridge: cdns-dsi: Move DSI mode check to _atomic_check()

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

At present, the DSI mode configuration check happens during the _atomic_enable() phase, which is not really the best place for this. Moreover, if the mode is not valid, the driver gives a warning and continues the hardware configuration.

Move the DSI mode configuration check to _atomic_check() instead, which can properly report back any invalid mode, before the _enable phase even begins.

---
 .../gpu/drm/bridge/cadence/cdns-dsi-core.c    | 87 +++++++++++++++++--
 .../gpu/drm/bridge/cadence/cdns-dsi-core.h    |  1 +
 2 files changed, 83 insertions(+), 5 deletions(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +82 -5
index acef2171719b..b6de0cbba9c2 100644
@@ -425,6 +425,17 @@
 #define DSI_NULL_FRAME_OVERHEAD		6
 #define DSI_EOT_PKT_SIZE		4
 
+struct cdns_dsi_bridge_state {
+	struct drm_bridge_state base;
+	struct cdns_dsi_cfg dsi_cfg;
+};
+
+static inline struct cdns_dsi_bridge_state *
+to_cdns_dsi_bridge_state(struct drm_bridge_state *bridge_state)
+{
+	return container_of(bridge_state, struct cdns_dsi_bridge_state, base);
+}
+
 static inline struct cdns_dsi *input_to_dsi(struct cdns_dsi_input *input)
 {
 	return container_of(input, struct cdns_dsi, input);
@@ -766,6 +777,9 @@ static void cdns_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
 	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
 	struct cdns_dsi *dsi = input_to_dsi(input);
 	struct cdns_dsi_output *output = &dsi->output;
+	struct drm_atomic_state *state = old_bridge_state->base.state;
+	struct cdns_dsi_bridge_state *dsi_state;
+	struct drm_bridge_state *new_bridge_state;
 	struct drm_display_mode *mode;
 	struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy;
 	unsigned long tx_byte_period;
@@ -776,14 +790,19 @@ static void cdns_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
 	if (WARN_ON(pm_runtime_get_sync(dsi->base.dev) < 0))
 		return;
 
+	new_bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
+	if (WARN_ON(!new_bridge_state))
+		return;
+
+	dsi_state = to_cdns_dsi_bridge_state(new_bridge_state);
+	dsi_cfg = dsi_state->dsi_cfg;
+
 	if (dsi->platform_ops && dsi->platform_ops->enable)
 		dsi->platform_ops->enable(dsi);
 
 	mode = &bridge->encoder->crtc->state->adjusted_mode;
 	nlanes = output->dev->lanes;
 
-	WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, false));
-
 	cdns_dsi_init_link(dsi);
 	cdns_dsi_hs_init(dsi);
 
@@ -954,6 +973,63 @@ static u32 *cdns_dsi_bridge_get_input_bus_fmts(struct drm_bridge *bridge,
 	return input_fmts;
 }
 
+static int cdns_dsi_bridge_atomic_check(struct drm_bridge *bridge,
+					struct drm_bridge_state *bridge_state,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state)
+{
+	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
+	struct cdns_dsi *dsi = input_to_dsi(input);
+	struct cdns_dsi_bridge_state *dsi_state = to_cdns_dsi_bridge_state(bridge_state);
+	struct drm_display_mode *mode = &crtc_state->mode;
+	struct cdns_dsi_cfg *dsi_cfg = &dsi_state->dsi_cfg;
+
+	return cdns_dsi_check_conf(dsi, mode, dsi_cfg, false);
+}
+
+static struct drm_bridge_state *
+cdns_dsi_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
+{
+	struct cdns_dsi_bridge_state *dsi_state;
+
+	if (WARN_ON(!bridge->base.state))
+		return NULL;
+
+	dsi_state = kzalloc(sizeof(*dsi_state), GFP_KERNEL);
+	if (!dsi_state)
+		return NULL;
+
+	__drm_atomic_helper_bridge_duplicate_state(bridge, &dsi_state->base);
+
+	return &dsi_state->base;
+}
+
+static void
+cdns_dsi_bridge_atomic_destroy_state(struct drm_bridge *bridge,
+				     struct drm_bridge_state *state)
+{
+	struct cdns_dsi_bridge_state *dsi_state;
+
+	dsi_state = to_cdns_dsi_bridge_state(state);
+
+	kfree(dsi_state);
+}
+
+static struct drm_bridge_state *
+cdns_dsi_bridge_atomic_reset(struct drm_bridge *bridge)
+{
+	struct cdns_dsi_bridge_state *dsi_state;
+
+	dsi_state = kzalloc(sizeof(*dsi_state), GFP_KERNEL);
+	if (!dsi_state)
+		return NULL;
+
+	memset(dsi_state, 0, sizeof(*dsi_state));
+	dsi_state->base.bridge = bridge;
+
+	return &dsi_state->base;
+}
+
 static const struct drm_bridge_funcs cdns_dsi_bridge_funcs = {
 	.attach = cdns_dsi_bridge_attach,
 	.mode_valid = cdns_dsi_bridge_mode_valid,
@@ -961,9 +1037,10 @@ static const struct drm_bridge_funcs cdns_dsi_bridge_funcs = {
 	.atomic_pre_enable = cdns_dsi_bridge_atomic_pre_enable,
 	.atomic_enable = cdns_dsi_bridge_atomic_enable,
 	.atomic_post_disable = cdns_dsi_bridge_atomic_post_disable,
-	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
-	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
-	.atomic_reset = drm_atomic_helper_bridge_reset,
+	.atomic_check = cdns_dsi_bridge_atomic_check,
+	.atomic_duplicate_state = cdns_dsi_bridge_atomic_duplicate_state,
+	.atomic_destroy_state = cdns_dsi_bridge_atomic_destroy_state,
+	.atomic_reset = cdns_dsi_bridge_atomic_reset,
 	.atomic_get_input_bus_fmts = cdns_dsi_bridge_get_input_bus_fmts,
 };
 
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h +1 -0
index 5db5dbbbcaad..b785df45bc59 100644
@@ -77,6 +77,7 @@ struct cdns_dsi {
 	bool link_initialized;
 	bool phy_initialized;
 	struct phy *dphy;
+	struct cdns_dsi_cfg dsi_cfg;
 };
 
 #endif /* !__CDNS_DSI_H__ */
Signature · 1 line
2.34.1
#18 lore raw

Re: [PATCH v6 10/12] drm/bridge: cdns-dsi: Move DSI mode check to _atomic_check()

Quoted 23 lines · Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

At present, the DSI mode configuration check happens during the _atomic_enable() phase, which is not really the best place for this. Moreover, if the mode is not valid, the driver gives a warning and continues the hardware configuration.

Move the DSI mode configuration check to _atomic_check() instead, which can properly report back any invalid mode, before the _enable phase even begins.

---
 .../gpu/drm/bridge/cadence/cdns-dsi-core.c    | 87 +++++++++++++++++--
 .../gpu/drm/bridge/cadence/cdns-dsi-core.h    |  1 +
 2 files changed, 83 insertions(+), 5 deletions(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h +0 -0
index 5db5dbbbcaad..b785df45bc59 100644

drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h struct cdns_dsi { · line 80

Quoted 4 lines
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h +1 -0
@@ -77,6 +77,7 @@ struct cdns_dsi {
 	bool link_initialized;
 	bool phy_initialized;
 	struct phy *dphy;
+	struct cdns_dsi_cfg dsi_cfg;

Is this still something necessary / useful? I think the point was to move dsi_cfg to the state, while this is a non-state struct.

Quoted 6 lines
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h +0 -0
 };
 
 #endif /* !__CDNS_DSI_H__ */
Signature · 1 line
2.34.1
Signature · 2 lines
With best wishes
Dmitry
#19 lore raw

Re: [PATCH v6 10/12] drm/bridge: cdns-dsi: Move DSI mode check to _atomic_check()

Quoted 19 lines · Dmitry Baryshkov
Quoted 18 lines · Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

At present, the DSI mode configuration check happens during the _atomic_enable() phase, which is not really the best place for this. Moreover, if the mode is not valid, the driver gives a warning and continues the hardware configuration.

Move the DSI mode configuration check to _atomic_check() instead, which can properly report back any invalid mode, before the _enable phase even begins.

---
 .../gpu/drm/bridge/cadence/cdns-dsi-core.c    | 87 +++++++++++++++++--
 .../gpu/drm/bridge/cadence/cdns-dsi-core.h    |  1 +
 2 files changed, 83 insertions(+), 5 deletions(-)
Quoted 12 lines

drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h struct cdns_dsi { · line 80

Quoted 9 lines
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h +1 -0
index 5db5dbbbcaad..b785df45bc59 100644
@@ -77,6 +77,7 @@ struct cdns_dsi {
 	bool link_initialized;
 	bool phy_initialized;
 	struct phy *dphy;
+	struct cdns_dsi_cfg dsi_cfg;

Is this still something necessary / useful? I think the point was to move dsi_cfg to the state, while this is a non-state struct.

No, this isn't necessary. This is a stray piece of code. Looks like I missed it. Thank you! I will drop this in the next revision.

Regards
Aradhya

Quoted 8 lines
Quoted 7 lines
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h +0 -0
 };
 
 #endif /* !__CDNS_DSI_H__ */
Signature · 1 line
2.34.1
#20 lore raw

PATCH v6 11/12 drm/atomic-helper: Re-order bridge chain pre-enable and post-disable

In reply to Aradhya Bhatia

Move the bridge pre_enable call before crtc enable, and the bridge post_disable call after the crtc disable.

The sequence of enable after this patch will look like:

	bridge[n]_pre_enable
	...
	bridge[1]_pre_enable

	crtc_enable
	encoder_enable

	bridge[1]_enable
	...
	bridge[n]_enable

And, the disable sequence for the display pipeline will look like:

	bridge[n]_disable
	...
	bridge[1]_disable

	encoder_disable
	crtc_disable

	bridge[1]_post_disable
	...
	bridge[n]_post_disable

The definition of bridge pre_enable hook says that,
"The display pipe (i.e. clocks and timing signals) feeding this bridge will not yet be running when this callback is called".

Since CRTC is also a source feeding the bridge, it should not be enabled before the bridges in the pipeline are pre_enabled. Fix that by re-ordering the sequence of bridge pre_enable and bridge post_disable.

---
 drivers/gpu/drm/drm_atomic_helper.c | 300 +++++++++++++++++-----------
 1 file changed, 181 insertions(+), 119 deletions(-)
drivers/gpu/drm/drm_atomic_helper.c +181 -119
index 5186d2114a50..ad6290a4a528 100644
@@ -74,6 +74,12 @@
  * also shares the &struct drm_plane_helper_funcs function table with the plane
  * helpers.
  */
+
+enum bridge_chain_operation_type {
+	DRM_BRIDGE_PRE_ENABLE_OR_POST_DISABLE,
+	DRM_BRIDGE_ENABLE_OR_DISABLE,
+};
+
 static void
 drm_atomic_helper_plane_changed(struct drm_atomic_state *state,
 				struct drm_plane_state *old_plane_state,
@@ -1122,74 +1128,12 @@ crtc_needs_disable(struct drm_crtc_state *old_state,
 }
 
 static void
-disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
+crtc_disable(struct drm_device *dev, struct drm_atomic_state *old_state)
 {
-	struct drm_connector *connector;
-	struct drm_connector_state *old_conn_state, *new_conn_state;
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
 	int i;
 
-	for_each_oldnew_connector_in_state(old_state, connector, old_conn_state, new_conn_state, i) {
-		const struct drm_encoder_helper_funcs *funcs;
-		struct drm_encoder *encoder;
-		struct drm_bridge *bridge;
-
-		/*
-		 * Shut down everything that's in the changeset and currently
-		 * still on. So need to check the old, saved state.
-		 */
-		if (!old_conn_state->crtc)
-			continue;
-
-		old_crtc_state = drm_atomic_get_old_crtc_state(old_state, old_conn_state->crtc);
-
-		if (new_conn_state->crtc)
-			new_crtc_state = drm_atomic_get_new_crtc_state(
-						old_state,
-						new_conn_state->crtc);
-		else
-			new_crtc_state = NULL;
-
-		if (!crtc_needs_disable(old_crtc_state, new_crtc_state) ||
-		    !drm_atomic_crtc_needs_modeset(old_conn_state->crtc->state))
-			continue;
-
-		encoder = old_conn_state->best_encoder;
-
-		/* We shouldn't get this far if we didn't previously have
-		 * an encoder.. but WARN_ON() rather than explode.
-		 */
-		if (WARN_ON(!encoder))
-			continue;
-
-		funcs = encoder->helper_private;
-
-		drm_dbg_atomic(dev, "disabling [ENCODER:%d:%s]\n",
-			       encoder->base.id, encoder->name);
-
-		/*
-		 * Each encoder has at most one connector (since we always steal
-		 * it away), so we won't call disable hooks twice.
-		 */
-		bridge = drm_bridge_chain_get_first_bridge(encoder);
-		drm_atomic_bridge_chain_disable(bridge, old_state);
-
-		/* Right function depends upon target state. */
-		if (funcs) {
-			if (funcs->atomic_disable)
-				funcs->atomic_disable(encoder, old_state);
-			else if (new_conn_state->crtc && funcs->prepare)
-				funcs->prepare(encoder);
-			else if (funcs->disable)
-				funcs->disable(encoder);
-			else if (funcs->dpms)
-				funcs->dpms(encoder, DRM_MODE_DPMS_OFF);
-		}
-
-		drm_atomic_bridge_chain_post_disable(bridge, old_state);
-	}
-
 	for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
 		const struct drm_crtc_helper_funcs *funcs;
 		int ret;
@@ -1206,7 +1150,6 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
 		drm_dbg_atomic(dev, "disabling [CRTC:%d:%s]\n",
 			       crtc->base.id, crtc->name);
 
-
 		/* Right function depends upon target state. */
 		if (new_crtc_state->enable && funcs->prepare)
 			funcs->prepare(crtc);
@@ -1236,6 +1179,97 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
 	}
 }
 
+static void
+encoder_bridge_chain_disable(struct drm_device *dev, struct drm_atomic_state *old_state,
+			     enum bridge_chain_operation_type op_type)
+{
+	struct drm_connector *connector;
+	struct drm_connector_state *old_conn_state, *new_conn_state;
+	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+	int i;
+
+	for_each_oldnew_connector_in_state(old_state, connector, old_conn_state, new_conn_state, i) {
+		const struct drm_encoder_helper_funcs *funcs;
+		struct drm_encoder *encoder;
+		struct drm_bridge *bridge;
+
+		/*
+		 * Shut down everything that's in the changeset and currently
+		 * still on. So need to check the old, saved state.
+		 */
+		if (!old_conn_state->crtc)
+			continue;
+
+		old_crtc_state = drm_atomic_get_old_crtc_state(old_state, old_conn_state->crtc);
+
+		if (new_conn_state->crtc)
+			new_crtc_state = drm_atomic_get_new_crtc_state(
+						old_state,
+						new_conn_state->crtc);
+		else
+			new_crtc_state = NULL;
+
+		if (!crtc_needs_disable(old_crtc_state, new_crtc_state) ||
+		    !drm_atomic_crtc_needs_modeset(old_conn_state->crtc->state))
+			continue;
+
+		encoder = old_conn_state->best_encoder;
+
+		/* We shouldn't get this far if we didn't previously have
+		 * an encoder.. but WARN_ON() rather than explode.
+		 */
+		if (WARN_ON(!encoder))
+			continue;
+
+		/*
+		 * Each encoder has at most one connector (since we always steal
+		 * it away), so we won't call disable hooks twice.
+		 */
+		bridge = drm_bridge_chain_get_first_bridge(encoder);
+
+		switch (op_type) {
+		case DRM_BRIDGE_ENABLE_OR_DISABLE:
+			funcs = encoder->helper_private;
+
+			drm_dbg_atomic(dev, "disabling [ENCODER:%d:%s]\n",
+				       encoder->base.id, encoder->name);
+
+			drm_atomic_bridge_chain_disable(bridge, old_state);
+
+			/* Right function depends upon target state. */
+			if (funcs) {
+				if (funcs->atomic_disable)
+					funcs->atomic_disable(encoder, old_state);
+				else if (new_conn_state->crtc && funcs->prepare)
+					funcs->prepare(encoder);
+				else if (funcs->disable)
+					funcs->disable(encoder);
+				else if (funcs->dpms)
+					funcs->dpms(encoder, DRM_MODE_DPMS_OFF);
+			}
+
+			break;
+
+		case DRM_BRIDGE_PRE_ENABLE_OR_POST_DISABLE:
+			drm_atomic_bridge_chain_post_disable(bridge, old_state);
+			break;
+
+		default:
+			drm_err(dev, "Unrecognized Encoder/Bridge operation (%d).\n", op_type);
+		}
+	}
+}
+
+static void
+disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
+{
+	encoder_bridge_chain_disable(dev, old_state, DRM_BRIDGE_ENABLE_OR_DISABLE);
+
+	crtc_disable(dev, old_state);
+
+	encoder_bridge_chain_disable(dev, old_state, DRM_BRIDGE_PRE_ENABLE_OR_POST_DISABLE);
+}
+
 /**
  * drm_atomic_helper_update_legacy_modeset_state - update legacy modeset state
  * @dev: DRM device
@@ -1445,28 +1479,69 @@ static void drm_atomic_helper_commit_writebacks(struct drm_device *dev,
 	}
 }
 
-/**
- * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
- * @dev: DRM device
- * @old_state: atomic state object with old state structures
- *
- * This function enables all the outputs with the new configuration which had to
- * be turned off for the update.
- *
- * For compatibility with legacy CRTC helpers this should be called after
- * drm_atomic_helper_commit_planes(), which is what the default commit function
- * does. But drivers with different needs can group the modeset commits together
- * and do the plane commits at the end. This is useful for drivers doing runtime
- * PM since planes updates then only happen when the CRTC is actually enabled.
- */
-void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
-					      struct drm_atomic_state *old_state)
+static void
+encoder_bridge_chain_enable(struct drm_device *dev, struct drm_atomic_state *old_state,
+			    enum bridge_chain_operation_type op_type)
+{
+	struct drm_connector *connector;
+	struct drm_connector_state *new_conn_state;
+	int i;
+
+	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
+		const struct drm_encoder_helper_funcs *funcs;
+		struct drm_encoder *encoder;
+		struct drm_bridge *bridge;
+
+		if (!new_conn_state->best_encoder)
+			continue;
+
+		if (!new_conn_state->crtc->state->active ||
+		    !drm_atomic_crtc_needs_modeset(new_conn_state->crtc->state))
+			continue;
+
+		encoder = new_conn_state->best_encoder;
+		/*
+		 * Each encoder has at most one connector (since we always steal
+		 * it away), so we won't call enable hooks twice.
+		 */
+		bridge = drm_bridge_chain_get_first_bridge(encoder);
+
+		switch (op_type) {
+		case DRM_BRIDGE_PRE_ENABLE_OR_POST_DISABLE:
+			drm_atomic_bridge_chain_pre_enable(bridge, old_state);
+			break;
+
+		case DRM_BRIDGE_ENABLE_OR_DISABLE:
+			funcs = encoder->helper_private;
+
+			drm_dbg_atomic(dev, "enabling [ENCODER:%d:%s]\n",
+				       encoder->base.id, encoder->name);
+
+			if (funcs) {
+				if (funcs->atomic_enable)
+					funcs->atomic_enable(encoder, old_state);
+				else if (funcs->enable)
+					funcs->enable(encoder);
+				else if (funcs->commit)
+					funcs->commit(encoder);
+			}
+
+			drm_atomic_bridge_chain_enable(bridge, old_state);
+			break;
+
+		default:
+			drm_err(dev, "Unrecognized Encoder/Bridge Operation (%d).\n", op_type);
+			break;
+		}
+	}
+}
+
+static void
+crtc_enable(struct drm_device *dev, struct drm_atomic_state *old_state)
 {
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *old_crtc_state;
 	struct drm_crtc_state *new_crtc_state;
-	struct drm_connector *connector;
-	struct drm_connector_state *new_conn_state;
 	int i;
 
 	for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
@@ -1490,43 +1565,30 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
 				funcs->commit(crtc);
 		}
 	}
-
-	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
-		const struct drm_encoder_helper_funcs *funcs;
-		struct drm_encoder *encoder;
-		struct drm_bridge *bridge;
-
-		if (!new_conn_state->best_encoder)
-			continue;
-
-		if (!new_conn_state->crtc->state->active ||
-		    !drm_atomic_crtc_needs_modeset(new_conn_state->crtc->state))
-			continue;
-
-		encoder = new_conn_state->best_encoder;
-		funcs = encoder->helper_private;
-
-		drm_dbg_atomic(dev, "enabling [ENCODER:%d:%s]\n",
-			       encoder->base.id, encoder->name);
-
-		/*
-		 * Each encoder has at most one connector (since we always steal
-		 * it away), so we won't call enable hooks twice.
-		 */
-		bridge = drm_bridge_chain_get_first_bridge(encoder);
-		drm_atomic_bridge_chain_pre_enable(bridge, old_state);
-
-		if (funcs) {
-			if (funcs->atomic_enable)
-				funcs->atomic_enable(encoder, old_state);
-			else if (funcs->enable)
-				funcs->enable(encoder);
-			else if (funcs->commit)
-				funcs->commit(encoder);
-		}
-
-		drm_atomic_bridge_chain_enable(bridge, old_state);
-	}
+}
+
+/**
+ * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
+ * @dev: DRM device
+ * @old_state: atomic state object with old state structures
+ *
+ * This function enables all the outputs with the new configuration which had to
+ * be turned off for the update.
+ *
+ * For compatibility with legacy CRTC helpers this should be called after
+ * drm_atomic_helper_commit_planes(), which is what the default commit function
+ * does. But drivers with different needs can group the modeset commits together
+ * and do the plane commits at the end. This is useful for drivers doing runtime
+ * PM since planes updates then only happen when the CRTC is actually enabled.
+ */
+void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
+					      struct drm_atomic_state *old_state)
+{
+	encoder_bridge_chain_enable(dev, old_state, DRM_BRIDGE_PRE_ENABLE_OR_POST_DISABLE);
+
+	crtc_enable(dev, old_state);
+
+	encoder_bridge_chain_enable(dev, old_state, DRM_BRIDGE_ENABLE_OR_DISABLE);
 
 	drm_atomic_helper_commit_writebacks(dev, old_state);
 }
Signature · 1 line
2.34.1
#21 lore raw

PATCH v6 12/12 drm/bridge: cdns-dsi: Use pre_enable/post_disable to enable/disable

In reply to Aradhya Bhatia

From: Aradhya Bhatia <[email protected]>

The cdns-dsi controller requires that it be turned on completely before the input DPI's source has begun streaming[0]. Not having that, allows for a small window before cdns-dsi enable and after cdns-dsi disable where the previous entity (in this case tidss's videoport) to continue streaming DPI video signals. This small window where cdns-dsi is disabled but is still receiving signals causes the input FIFO of cdns-dsi to get corrupted. This causes the colors to shift on the output display. The colors can either shift by one color component (R->G, G->B, B->R), or by two color components (R->B, G->R, B->G).

Since tidss's videoport starts streaming via crtc enable hooks, we need cdns-dsi to be up and running before that. Now that the bridges are pre_enabled before crtc is enabled, and post_disabled after crtc is disabled, use the pre_enable and post_disable hooks to get cdns-dsi ready and running before the tidss videoport to get pass the color shift issues.

[0]: See section 12.6.5.7.3 "Start-up Procedure" in J721E SoC TRM
TRM Link: http://www.ti.com/lit/pdf/spruil1

---
 .../gpu/drm/bridge/cadence/cdns-dsi-core.c    | 62 ++++++++++---------
 1 file changed, 34 insertions(+), 28 deletions(-)
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c +34 -28
index b6de0cbba9c2..143c37b1f252 100644
@@ -669,13 +669,28 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge,
 	return MODE_OK;
 }
 
-static void cdns_dsi_bridge_atomic_disable(struct drm_bridge *bridge,
-					   struct drm_bridge_state *old_bridge_state)
+static void cdns_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge,
+						struct drm_bridge_state *old_bridge_state)
 {
 	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
 	struct cdns_dsi *dsi = input_to_dsi(input);
 	u32 val;
 
+	/*
+	 * The cdns-dsi controller needs to be disabled after it's DPI source
+	 * has stopped streaming. If this is not followed, there is a brief
+	 * window before DPI source is disabled and after cdns-dsi controller
+	 * has been disabled where the DPI stream is still on, but the cdns-dsi
+	 * controller is not ready anymore to accept the incoming signals. This
+	 * is one of the reasons why a shift in pixel colors is observed on
+	 * displays that have cdns-dsi as one of the bridges.
+	 *
+	 * To mitigate this, disable this bridge from the bridge post_disable()
+	 * hook, instead of the bridge _disable() hook. The bridge post_disable()
+	 * hook gets called after the CRTC disable, where often many DPI sources
+	 * disable their streams.
+	 */
+
 	val = readl(dsi->regs + MCTL_MAIN_DATA_CTL);
 	val &= ~(IF_VID_SELECT_MASK | IF_VID_MODE | VID_EN | HOST_EOT_GEN |
 		 DISP_EOT_GEN);
@@ -694,15 +709,6 @@ static void cdns_dsi_bridge_atomic_disable(struct drm_bridge *bridge,
 	pm_runtime_put(dsi->base.dev);
 }
 
-static void cdns_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge,
-						struct drm_bridge_state *old_bridge_state)
-{
-	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
-	struct cdns_dsi *dsi = input_to_dsi(input);
-
-	pm_runtime_put(dsi->base.dev);
-}
-
 static void cdns_dsi_hs_init(struct cdns_dsi *dsi)
 {
 	struct cdns_dsi_output *output = &dsi->output;
@@ -771,8 +777,8 @@ static void cdns_dsi_init_link(struct cdns_dsi *dsi)
 	dsi->link_initialized = true;
 }
 
-static void cdns_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
-					  struct drm_bridge_state *old_bridge_state)
+static void cdns_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
+					      struct drm_bridge_state *old_bridge_state)
 {
 	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
 	struct cdns_dsi *dsi = input_to_dsi(input);
@@ -787,6 +793,21 @@ static void cdns_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
 	u32 tmp, reg_wakeup, div, status;
 	int nlanes;
 
+	/*
+	 * The cdns-dsi controller needs to be enabled before it's DPI source
+	 * has begun streaming. If this is not followed, there is a brief window
+	 * after DPI source enable and before cdns-dsi controller enable where
+	 * the DPI stream is on, but the cdns-dsi controller is not ready to
+	 * accept the incoming signals. This is one of the reasons why a shift
+	 * in pixel colors is observed on displays that have cdns-dsi as one of
+	 * the bridges.
+	 *
+	 * To mitigate this, enable this bridge from the bridge pre_enable()
+	 * hook, instead of the bridge _enable() hook. The bridge pre_enable()
+	 * hook gets called before the CRTC enable, where often many DPI sources
+	 * enable their streams.
+	 */
+
 	if (WARN_ON(pm_runtime_get_sync(dsi->base.dev) < 0))
 		return;
 
@@ -933,19 +954,6 @@ static void cdns_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
 	writel(tmp, dsi->regs + MCTL_MAIN_EN);
 }
 
-static void cdns_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
-					      struct drm_bridge_state *old_bridge_state)
-{
-	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
-	struct cdns_dsi *dsi = input_to_dsi(input);
-
-	if (WARN_ON(pm_runtime_get_sync(dsi->base.dev) < 0))
-		return;
-
-	cdns_dsi_init_link(dsi);
-	cdns_dsi_hs_init(dsi);
-}
-
 static u32 *cdns_dsi_bridge_get_input_bus_fmts(struct drm_bridge *bridge,
 					       struct drm_bridge_state *bridge_state,
 					       struct drm_crtc_state *crtc_state,
@@ -1033,9 +1041,7 @@ cdns_dsi_bridge_atomic_reset(struct drm_bridge *bridge)
 static const struct drm_bridge_funcs cdns_dsi_bridge_funcs = {
 	.attach = cdns_dsi_bridge_attach,
 	.mode_valid = cdns_dsi_bridge_mode_valid,
-	.atomic_disable = cdns_dsi_bridge_atomic_disable,
 	.atomic_pre_enable = cdns_dsi_bridge_atomic_pre_enable,
-	.atomic_enable = cdns_dsi_bridge_atomic_enable,
 	.atomic_post_disable = cdns_dsi_bridge_atomic_post_disable,
 	.atomic_check = cdns_dsi_bridge_atomic_check,
 	.atomic_duplicate_state = cdns_dsi_bridge_atomic_duplicate_state,
Signature · 1 line
2.34.1