All of lore.kernel.org
 help / color / mirror / Atom feed
From: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
To: dri-devel@lists.freedesktop.org
Cc: linux-renesas-soc@vger.kernel.org,
	Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>,
	Kieran Bingham <kieran.bingham@ideasonboard.com>
Subject: [PATCH 3/3] drm: rcar-du: lvds: Fix LVDS PLL disable on D3/E3
Date: Tue, 14 Feb 2023 02:37:36 +0200	[thread overview]
Message-ID: <20230214003736.18871-4-laurent.pinchart+renesas@ideasonboard.com> (raw)
In-Reply-To: <20230214003736.18871-1-laurent.pinchart+renesas@ideasonboard.com>

On R-Car D3 and E3, the LVDS encoder provides the dot (pixel) clock to
the DU, regardless of whether the LVDS output is used or not. When using
the DPAD (RGB) output, the DU driver thus enables and disables the LVDS
PLL manually, while when using the LVDS output, it lets the LVDS bridge
driver handle the PLL configuration internally as part of the atomic
enable and disable operations.

This causes an issue when using the LVDS output. As bridges are disabled
before CRTCs, the current implementation violates the enable/disable
sequences documented in the hardware datasheet, which requires the dot
clock to be enabled before the CRTC is started and disabled after it
gets stopped.

Fix the problem by enabling/disabling the LVDS PLL manually from the DU
regardless of which output is used, and skipping the PLL handling in the
LVDS bridge atomic enable and disable operations.

This is however not enough. Disabling the LVDS encoder while leaving the
PLL on still results in a vertical blanking wait timeout when disabling
the DU. Investigation showed that the culprit is the LVEN bit. For an
unclear reason, clearing the bit when disabling the LVDS encoder blocks
vertical blanking interrupts. We thus have to delay disabling the whole
LVDS encoder, not just disabling the PLL, until the DU is disabled.

We could split the LVDS disable sequence by clearing the LVRES bit in
the LVDS bridge atomic disable handler, and delaying the rest of the
operations, in order to disable the LVDS output at bridge atomic disable
time, before stopping the CRTC. This would make the code more complex,
without a clear benefit, so keep the implementation simple(r).

Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
---
 drivers/gpu/drm/rcar-du/rcar_du_crtc.c |  18 ++--
 drivers/gpu/drm/rcar-du/rcar_lvds.c    | 114 +++++++++++++++----------
 drivers/gpu/drm/rcar-du/rcar_lvds.h    |  12 ++-
 3 files changed, 86 insertions(+), 58 deletions(-)

diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
index 008e172ed43b..71e7fbace38d 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -749,16 +749,17 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc,
 
 	/*
 	 * On D3/E3 the dot clock is provided by the LVDS encoder attached to
-	 * the DU channel. We need to enable its clock output explicitly if
-	 * the LVDS output is disabled.
+	 * the DU channel. We need to enable its clock output explicitly before
+	 * starting the CRTC, as the bridge hasn't been enabled by the atomic
+	 * helpers yet.
 	 */
-	if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index) &&
-	    rstate->outputs == BIT(RCAR_DU_OUTPUT_DPAD0)) {
+	if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index)) {
+		bool dot_clk_only = rstate->outputs == BIT(RCAR_DU_OUTPUT_DPAD0);
 		struct drm_bridge *bridge = rcdu->lvds[rcrtc->index];
 		const struct drm_display_mode *mode =
 			&crtc->state->adjusted_mode;
 
-		rcar_lvds_pclk_enable(bridge, mode->clock * 1000);
+		rcar_lvds_pclk_enable(bridge, mode->clock * 1000, dot_clk_only);
 	}
 
 	/*
@@ -795,15 +796,15 @@ static void rcar_du_crtc_atomic_disable(struct drm_crtc *crtc,
 	rcar_du_crtc_stop(rcrtc);
 	rcar_du_crtc_put(rcrtc);
 
-	if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index) &&
-	    rstate->outputs == BIT(RCAR_DU_OUTPUT_DPAD0)) {
+	if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index)) {
+		bool dot_clk_only = rstate->outputs == BIT(RCAR_DU_OUTPUT_DPAD0);
 		struct drm_bridge *bridge = rcdu->lvds[rcrtc->index];
 
 		/*
 		 * Disable the LVDS clock output, see
 		 * rcar_du_crtc_atomic_enable().
 		 */
-		rcar_lvds_pclk_disable(bridge);
+		rcar_lvds_pclk_disable(bridge, dot_clk_only);
 	}
 
 	if ((rcdu->info->dsi_clk_mask & BIT(rcrtc->index)) &&
@@ -815,7 +816,6 @@ static void rcar_du_crtc_atomic_disable(struct drm_crtc *crtc,
 		 * Disable the DSI clock output, see
 		 * rcar_du_crtc_atomic_enable().
 		 */
-
 		rcar_mipi_dsi_pclk_disable(bridge);
 	}
 
diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c
index 70cdd5ec64d5..ca215b588fd7 100644
--- a/drivers/gpu/drm/rcar-du/rcar_lvds.c
+++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c
@@ -269,8 +269,8 @@ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk,
 		pll->pll_m, pll->pll_n, pll->pll_e, pll->div);
 }
 
-static void __rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds,
-					unsigned int freq, bool dot_clock_only)
+static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds,
+				      unsigned int freq, bool dot_clock_only)
 {
 	struct pll_info pll = { .diff = (unsigned long)-1 };
 	u32 lvdpllcr;
@@ -305,11 +305,6 @@ static void __rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds,
 		rcar_lvds_write(lvds, LVDDIV, 0);
 }
 
-static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq)
-{
-	__rcar_lvds_pll_setup_d3_e3(lvds, freq, false);
-}
-
 /* -----------------------------------------------------------------------------
  * Enable/disable
  */
@@ -425,8 +420,12 @@ static void rcar_lvds_enable(struct drm_bridge *bridge,
 	/*
 	 * PLL clock configuration on all instances but the companion in
 	 * dual-link mode.
+	 *
+	 * The extended PLL has been turned on by an explicit call to
+	 * rcar_lvds_pclk_enable() from the DU driver.
 	 */
-	if (lvds->link_type == RCAR_LVDS_SINGLE_LINK || lvds->companion) {
+	if ((lvds->link_type == RCAR_LVDS_SINGLE_LINK || lvds->companion) &&
+	    !(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
 		const struct drm_crtc_state *crtc_state =
 			drm_atomic_get_new_crtc_state(state, crtc);
 		const struct drm_display_mode *mode =
@@ -491,11 +490,56 @@ static void rcar_lvds_enable(struct drm_bridge *bridge,
 	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
 }
 
+static void rcar_lvds_disable(struct drm_bridge *bridge)
+{
+	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+	u32 lvdcr0;
+
+	/*
+	 * Clear the LVDCR0 bits in the order specified by the hardware
+	 * documentation, ending with a write of 0 to the full register to
+	 * clear all remaining bits.
+	 */
+	lvdcr0 = rcar_lvds_read(lvds, LVDCR0);
+
+	lvdcr0 &= ~LVDCR0_LVRES;
+	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+
+	if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN3_LVEN) {
+		lvdcr0 &= ~LVDCR0_LVEN;
+		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+	}
+
+	if (lvds->info->quirks & RCAR_LVDS_QUIRK_PWD) {
+		lvdcr0 &= ~LVDCR0_PWD;
+		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+	}
+
+	if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
+		lvdcr0 &= ~LVDCR0_PLLON;
+		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+	}
+
+	rcar_lvds_write(lvds, LVDCR0, 0);
+	rcar_lvds_write(lvds, LVDCR1, 0);
+
+	/* The extended PLL is turned off in rcar_lvds_pclk_disable(). */
+	if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL))
+		rcar_lvds_write(lvds, LVDPLLCR, 0);
+
+	/* Disable the companion LVDS encoder in dual-link mode. */
+	if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion)
+		rcar_lvds_disable(lvds->companion);
+
+	pm_runtime_put_sync(lvds->dev);
+}
+
 /* -----------------------------------------------------------------------------
  * Clock - D3/E3 only
  */
 
-int rcar_lvds_pclk_enable(struct drm_bridge *bridge, unsigned long freq)
+int rcar_lvds_pclk_enable(struct drm_bridge *bridge, unsigned long freq,
+			  bool dot_clk_only)
 {
 	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
 	int ret;
@@ -509,13 +553,13 @@ int rcar_lvds_pclk_enable(struct drm_bridge *bridge, unsigned long freq)
 	if (ret)
 		return ret;
 
-	__rcar_lvds_pll_setup_d3_e3(lvds, freq, true);
+	rcar_lvds_pll_setup_d3_e3(lvds, freq, dot_clk_only);
 
 	return 0;
 }
 EXPORT_SYMBOL_GPL(rcar_lvds_pclk_enable);
 
-void rcar_lvds_pclk_disable(struct drm_bridge *bridge)
+void rcar_lvds_pclk_disable(struct drm_bridge *bridge, bool dot_clk_only)
 {
 	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
 
@@ -524,6 +568,9 @@ void rcar_lvds_pclk_disable(struct drm_bridge *bridge)
 
 	dev_dbg(lvds->dev, "disabling LVDS PLL\n");
 
+	if (!dot_clk_only)
+		rcar_lvds_disable(bridge);
+
 	rcar_lvds_write(lvds, LVDPLLCR, 0);
 
 	pm_runtime_put_sync(lvds->dev);
@@ -552,42 +599,21 @@ static void rcar_lvds_atomic_disable(struct drm_bridge *bridge,
 				     struct drm_bridge_state *old_bridge_state)
 {
 	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
-	u32 lvdcr0;
 
 	/*
-	 * Clear the LVDCR0 bits in the order specified by the hardware
-	 * documentation, ending with a write of 0 to the full register to
-	 * clear all remaining bits.
+	 * For D3 and E3, disabling the LVDS encoder before the DU would stall
+	 * the DU, causing a vblank wait timeout when stopping the DU. This has
+	 * been traced to clearing the LVEN bit, but the exact reason is
+	 * unknown. Keep the encoder enabled, it will be disabled by an explicit
+	 * call to rcar_lvds_pclk_disable() from the DU driver.
+	 *
+	 * We could clear the LVRES bit already to disable the LVDS output, but
+	 * that's likely pointless.
 	 */
-	lvdcr0 = rcar_lvds_read(lvds, LVDCR0);
+	if (lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)
+		return;
 
-	lvdcr0 &= ~LVDCR0_LVRES;
-	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-
-	if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN3_LVEN) {
-		lvdcr0 &= ~LVDCR0_LVEN;
-		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-	}
-
-	if (lvds->info->quirks & RCAR_LVDS_QUIRK_PWD) {
-		lvdcr0 &= ~LVDCR0_PWD;
-		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-	}
-
-	if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
-		lvdcr0 &= ~LVDCR0_PLLON;
-		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-	}
-
-	rcar_lvds_write(lvds, LVDCR0, 0);
-	rcar_lvds_write(lvds, LVDCR1, 0);
-	rcar_lvds_write(lvds, LVDPLLCR, 0);
-
-	/* Disable the companion LVDS encoder in dual-link mode. */
-	if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion)
-		rcar_lvds_atomic_disable(lvds->companion, old_bridge_state);
-
-	pm_runtime_put_sync(lvds->dev);
+	rcar_lvds_disable(bridge);
 }
 
 static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
@@ -924,14 +950,12 @@ static const struct rcar_lvds_device_info rcar_lvds_r8a77990_info = {
 	.gen = 3,
 	.quirks = RCAR_LVDS_QUIRK_GEN3_LVEN | RCAR_LVDS_QUIRK_EXT_PLL
 		| RCAR_LVDS_QUIRK_DUAL_LINK,
-	.pll_setup = rcar_lvds_pll_setup_d3_e3,
 };
 
 static const struct rcar_lvds_device_info rcar_lvds_r8a77995_info = {
 	.gen = 3,
 	.quirks = RCAR_LVDS_QUIRK_GEN3_LVEN | RCAR_LVDS_QUIRK_PWD
 		| RCAR_LVDS_QUIRK_EXT_PLL | RCAR_LVDS_QUIRK_DUAL_LINK,
-	.pll_setup = rcar_lvds_pll_setup_d3_e3,
 };
 
 static const struct of_device_id rcar_lvds_of_table[] = {
diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.h b/drivers/gpu/drm/rcar-du/rcar_lvds.h
index bee7033b60d6..887c63500000 100644
--- a/drivers/gpu/drm/rcar-du/rcar_lvds.h
+++ b/drivers/gpu/drm/rcar-du/rcar_lvds.h
@@ -13,17 +13,21 @@
 struct drm_bridge;
 
 #if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
-int rcar_lvds_pclk_enable(struct drm_bridge *bridge, unsigned long freq);
-void rcar_lvds_pclk_disable(struct drm_bridge *bridge);
+int rcar_lvds_pclk_enable(struct drm_bridge *bridge, unsigned long freq,
+			  bool dot_clk_only);
+void rcar_lvds_pclk_disable(struct drm_bridge *bridge, bool dot_clk_only);
 bool rcar_lvds_dual_link(struct drm_bridge *bridge);
 bool rcar_lvds_is_connected(struct drm_bridge *bridge);
 #else
 static inline int rcar_lvds_pclk_enable(struct drm_bridge *bridge,
-					unsigned long freq)
+					unsigned long freq, bool dot_clk_only)
 {
 	return -ENOSYS;
 }
-static inline void rcar_lvds_pclk_disable(struct drm_bridge *bridge) { }
+static inline void rcar_lvds_pclk_disable(struct drm_bridge *bridge,
+					  bool dot_clock_only)
+{
+}
 static inline bool rcar_lvds_dual_link(struct drm_bridge *bridge)
 {
 	return false;
-- 
Regards,

Laurent Pinchart


  parent reply	other threads:[~2023-02-14  0:37 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-02-14  0:37 [PATCH 0/3] drm: rcar-du: Fix vblank wait timeout when stopping LVDS output Laurent Pinchart
2023-02-14  0:37 ` [PATCH 1/3] drm: rcar-du: lvds: Call function directly instead of through pointer Laurent Pinchart
2023-02-15  6:18   ` Tomi Valkeinen
2023-02-14  0:37 ` [PATCH 2/3] drm: rcar-du: lvds: Move LVDS enable code to separate code section Laurent Pinchart
2023-02-14  8:25   ` Geert Uytterhoeven
2023-02-14  8:25     ` Geert Uytterhoeven
2023-02-15  6:20   ` Tomi Valkeinen
2023-02-15  8:34     ` Laurent Pinchart
2023-02-15  8:34       ` Laurent Pinchart
2023-02-14  0:37 ` Laurent Pinchart [this message]
2023-02-15  6:29   ` [PATCH 3/3] drm: rcar-du: lvds: Fix LVDS PLL disable on D3/E3 Tomi Valkeinen
2023-02-15 10:19     ` Laurent Pinchart
2023-02-15 10:19       ` Laurent Pinchart

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230214003736.18871-4-laurent.pinchart+renesas@ideasonboard.com \
    --to=laurent.pinchart+renesas@ideasonboard.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=kieran.bingham@ideasonboard.com \
    --cc=linux-renesas-soc@vger.kernel.org \
    --cc=tomi.valkeinen@ideasonboard.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.