drm: rcar-du: lvds: Fix LVDS PLL disable on D3/E3

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>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen+renesas@ideasonboard.com>
This commit is contained in:
Laurent Pinchart 2023-02-13 15:25:15 +02:00
parent 650e788136
commit ec1c6ff81e
3 changed files with 114 additions and 85 deletions

View file

@ -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,16 @@ 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_du_crtc_atomic_enable(). When the LVDS output is used,
* this also disables the LVDS encoder.
*/
rcar_lvds_pclk_disable(bridge);
rcar_lvds_pclk_disable(bridge, dot_clk_only);
}
if ((rcdu->info->dsi_clk_mask & BIT(rcrtc->index)) &&
@ -815,7 +817,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);
}

View file

@ -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,65 +490,7 @@ static void rcar_lvds_enable(struct drm_bridge *bridge,
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
}
/* -----------------------------------------------------------------------------
* Clock - D3/E3 only
*/
int rcar_lvds_pclk_enable(struct drm_bridge *bridge, unsigned long freq)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
int ret;
if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)))
return -ENODEV;
dev_dbg(lvds->dev, "enabling LVDS PLL, freq=%luHz\n", freq);
ret = pm_runtime_resume_and_get(lvds->dev);
if (ret)
return ret;
__rcar_lvds_pll_setup_d3_e3(lvds, freq, true);
return 0;
}
EXPORT_SYMBOL_GPL(rcar_lvds_pclk_enable);
void rcar_lvds_pclk_disable(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)))
return;
dev_dbg(lvds->dev, "disabling LVDS PLL\n");
rcar_lvds_write(lvds, LVDPLLCR, 0);
pm_runtime_put_sync(lvds->dev);
}
EXPORT_SYMBOL_GPL(rcar_lvds_pclk_disable);
/* -----------------------------------------------------------------------------
* Bridge
*/
static void rcar_lvds_atomic_enable(struct drm_bridge *bridge,
struct drm_bridge_state *old_bridge_state)
{
struct drm_atomic_state *state = old_bridge_state->base.state;
struct drm_connector *connector;
struct drm_crtc *crtc;
connector = drm_atomic_get_new_connector_for_encoder(state,
bridge->encoder);
crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
rcar_lvds_enable(bridge, state, crtc, connector);
}
static void rcar_lvds_atomic_disable(struct drm_bridge *bridge,
struct drm_bridge_state *old_bridge_state)
static void rcar_lvds_disable(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
u32 lvdcr0;
@ -581,15 +522,100 @@ static void rcar_lvds_atomic_disable(struct drm_bridge *bridge,
rcar_lvds_write(lvds, LVDCR0, 0);
rcar_lvds_write(lvds, LVDCR1, 0);
rcar_lvds_write(lvds, LVDPLLCR, 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_atomic_disable(lvds->companion, old_bridge_state);
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,
bool dot_clk_only)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
int ret;
if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)))
return -ENODEV;
dev_dbg(lvds->dev, "enabling LVDS PLL, freq=%luHz\n", freq);
ret = pm_runtime_resume_and_get(lvds->dev);
if (ret)
return ret;
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, bool dot_clk_only)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)))
return;
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);
}
EXPORT_SYMBOL_GPL(rcar_lvds_pclk_disable);
/* -----------------------------------------------------------------------------
* Bridge
*/
static void rcar_lvds_atomic_enable(struct drm_bridge *bridge,
struct drm_bridge_state *old_bridge_state)
{
struct drm_atomic_state *state = old_bridge_state->base.state;
struct drm_connector *connector;
struct drm_crtc *crtc;
connector = drm_atomic_get_new_connector_for_encoder(state,
bridge->encoder);
crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
rcar_lvds_enable(bridge, state, crtc, connector);
}
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);
/*
* 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.
*/
if (lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)
return;
rcar_lvds_disable(bridge);
}
static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
@ -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[] = {

View file

@ -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;