Commit c4842d4d authored by Linus Walleij's avatar Linus Walleij

drm/mcde: Fix display pipeline restart

To make sure that the MCDE is in a reasonable state during
set-up, perform a reset by power cycling the block by dropping
the on-chip regulator reference after probe. The display
subsystem (DSS) has no dedicated reset line so dropping
the EPOD regulator is the only real way of resetting it.

We introduce code to enable and disable the regulator in
the display enable/disable callbacks.

We move the generic MCDE setup such as muxing of DPI
signals and masking of interrupts to the display
handling.

When we drop the power to the whole display subsystem, not
only MCDE but also the DSI links lose their state. Therefore
we move the DSI block reset and hardware initialization
code to the mcde_dsi_bridge_pre_enable() callback so this
happens every time we start up the bridge, as we may have
lost the power.

We move the final disablement of the interrupts and
clocks to the mcde_dsi_bridge_post_disable() callback
rather than have it in the mcde_dsi_bridge_disable()
callback, as some control messages may still be sent
over the DSI host after the bridge has been shut down.

This (together with a patch for the corresponding
panel) makes the Samsung GT-S7710 successfully disable
and re-enable its display, cutting all power while
disabled and re-initializing the hardware when coming
back up.
Acked-by: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: default avatarLinus Walleij <linus.walleij@linaro.org>
Cc: newbytee@protonmail.com
Cc: Stephan Gerhold <stephan@gerhold.net>
Link: https://patchwork.freedesktop.org/patch/msgid/20200808223122.1492124-3-linus.walleij@linaro.org
parent f6fd1d70
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/dma-buf.h> #include <linux/dma-buf.h>
#include <linux/regulator/consumer.h>
#include <drm/drm_device.h> #include <drm/drm_device.h>
#include <drm/drm_fb_cma_helper.h> #include <drm/drm_fb_cma_helper.h>
...@@ -879,6 +880,14 @@ static void mcde_display_enable(struct drm_simple_display_pipe *pipe, ...@@ -879,6 +880,14 @@ static void mcde_display_enable(struct drm_simple_display_pipe *pipe,
u32 formatter_frame; u32 formatter_frame;
u32 pkt_div; u32 pkt_div;
u32 val; u32 val;
int ret;
/* This powers up the entire MCDE block and the DSI hardware */
ret = regulator_enable(mcde->epod);
if (ret) {
dev_err(drm->dev, "can't re-enable EPOD regulator\n");
return;
}
dev_info(drm->dev, "enable MCDE, %d x %d format %s\n", dev_info(drm->dev, "enable MCDE, %d x %d format %s\n",
mode->hdisplay, mode->vdisplay, mode->hdisplay, mode->vdisplay,
...@@ -889,6 +898,26 @@ static void mcde_display_enable(struct drm_simple_display_pipe *pipe, ...@@ -889,6 +898,26 @@ static void mcde_display_enable(struct drm_simple_display_pipe *pipe,
return; return;
} }
/* Set up the main control, watermark level at 7 */
val = 7 << MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT;
/* 24 bits DPI: connect LSB Ch B to D[0:7] */
val |= 3 << MCDE_CONF0_OUTMUX0_SHIFT;
/* TV out: connect LSB Ch B to D[8:15] */
val |= 3 << MCDE_CONF0_OUTMUX1_SHIFT;
/* Don't care about this muxing */
val |= 0 << MCDE_CONF0_OUTMUX2_SHIFT;
/* 24 bits DPI: connect MID Ch B to D[24:31] */
val |= 4 << MCDE_CONF0_OUTMUX3_SHIFT;
/* 5: 24 bits DPI: connect MSB Ch B to D[32:39] */
val |= 5 << MCDE_CONF0_OUTMUX4_SHIFT;
/* Syncmux bits zero: DPI channel A and B on output pins A and B resp */
writel(val, mcde->regs + MCDE_CONF0);
/* Clear any pending interrupts */
mcde_display_disable_irqs(mcde);
writel(0, mcde->regs + MCDE_IMSCERR);
writel(0xFFFFFFFF, mcde->regs + MCDE_RISERR);
dev_info(drm->dev, "output in %s mode, format %dbpp\n", dev_info(drm->dev, "output in %s mode, format %dbpp\n",
(mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ?
"VIDEO" : "CMD", "VIDEO" : "CMD",
...@@ -1005,6 +1034,11 @@ static void mcde_display_enable(struct drm_simple_display_pipe *pipe, ...@@ -1005,6 +1034,11 @@ static void mcde_display_enable(struct drm_simple_display_pipe *pipe,
dev_dbg(mcde->dev, "started MCDE video FIFO flow\n"); dev_dbg(mcde->dev, "started MCDE video FIFO flow\n");
} }
/* Enable automatic clock gating */
val = readl(mcde->regs + MCDE_CR);
val |= MCDE_CR_MCDEEN | MCDE_CR_AUTOCLKG_EN;
writel(val, mcde->regs + MCDE_CR);
dev_info(drm->dev, "MCDE display is enabled\n"); dev_info(drm->dev, "MCDE display is enabled\n");
} }
...@@ -1014,6 +1048,7 @@ static void mcde_display_disable(struct drm_simple_display_pipe *pipe) ...@@ -1014,6 +1048,7 @@ static void mcde_display_disable(struct drm_simple_display_pipe *pipe)
struct drm_device *drm = crtc->dev; struct drm_device *drm = crtc->dev;
struct mcde *mcde = to_mcde(drm); struct mcde *mcde = to_mcde(drm);
struct drm_pending_vblank_event *event; struct drm_pending_vblank_event *event;
int ret;
drm_crtc_vblank_off(crtc); drm_crtc_vblank_off(crtc);
...@@ -1029,6 +1064,12 @@ static void mcde_display_disable(struct drm_simple_display_pipe *pipe) ...@@ -1029,6 +1064,12 @@ static void mcde_display_disable(struct drm_simple_display_pipe *pipe)
spin_unlock_irq(&crtc->dev->event_lock); spin_unlock_irq(&crtc->dev->event_lock);
} }
ret = regulator_disable(mcde->epod);
if (ret)
dev_err(drm->dev, "can't disable EPOD regulator\n");
/* Make sure we are powered down (before we may power up again) */
usleep_range(50000, 70000);
dev_info(drm->dev, "MCDE display is disabled\n"); dev_info(drm->dev, "MCDE display is disabled\n");
} }
......
...@@ -9,6 +9,45 @@ ...@@ -9,6 +9,45 @@
#ifndef _MCDE_DRM_H_ #ifndef _MCDE_DRM_H_
#define _MCDE_DRM_H_ #define _MCDE_DRM_H_
/* Shared basic registers */
#define MCDE_CR 0x00000000
#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_SHIFT 0
#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_MASK 0x0000003F
#define MCDE_CR_IFIFOCTRLEN BIT(15)
#define MCDE_CR_UFRECOVERY_MODE_V422 BIT(16)
#define MCDE_CR_WRAP_MODE_V422_SHIFT BIT(17)
#define MCDE_CR_AUTOCLKG_EN BIT(30)
#define MCDE_CR_MCDEEN BIT(31)
#define MCDE_CONF0 0x00000004
#define MCDE_CONF0_SYNCMUX0 BIT(0)
#define MCDE_CONF0_SYNCMUX1 BIT(1)
#define MCDE_CONF0_SYNCMUX2 BIT(2)
#define MCDE_CONF0_SYNCMUX3 BIT(3)
#define MCDE_CONF0_SYNCMUX4 BIT(4)
#define MCDE_CONF0_SYNCMUX5 BIT(5)
#define MCDE_CONF0_SYNCMUX6 BIT(6)
#define MCDE_CONF0_SYNCMUX7 BIT(7)
#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT 12
#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_MASK 0x00007000
#define MCDE_CONF0_OUTMUX0_SHIFT 16
#define MCDE_CONF0_OUTMUX0_MASK 0x00070000
#define MCDE_CONF0_OUTMUX1_SHIFT 19
#define MCDE_CONF0_OUTMUX1_MASK 0x00380000
#define MCDE_CONF0_OUTMUX2_SHIFT 22
#define MCDE_CONF0_OUTMUX2_MASK 0x01C00000
#define MCDE_CONF0_OUTMUX3_SHIFT 25
#define MCDE_CONF0_OUTMUX3_MASK 0x0E000000
#define MCDE_CONF0_OUTMUX4_SHIFT 28
#define MCDE_CONF0_OUTMUX4_MASK 0x70000000
#define MCDE_SSP 0x00000008
#define MCDE_AIS 0x00000100
#define MCDE_IMSCERR 0x00000110
#define MCDE_RISERR 0x00000120
#define MCDE_MISERR 0x00000130
#define MCDE_SISERR 0x00000140
enum mcde_flow_mode { enum mcde_flow_mode {
/* One-shot mode: flow stops after one frame */ /* One-shot mode: flow stops after one frame */
MCDE_COMMAND_ONESHOT_FLOW, MCDE_COMMAND_ONESHOT_FLOW,
......
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/delay.h>
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h> #include <drm/drm_bridge.h>
...@@ -82,44 +83,6 @@ ...@@ -82,44 +83,6 @@
#define DRIVER_DESC "DRM module for MCDE" #define DRIVER_DESC "DRM module for MCDE"
#define MCDE_CR 0x00000000
#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_SHIFT 0
#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_MASK 0x0000003F
#define MCDE_CR_IFIFOCTRLEN BIT(15)
#define MCDE_CR_UFRECOVERY_MODE_V422 BIT(16)
#define MCDE_CR_WRAP_MODE_V422_SHIFT BIT(17)
#define MCDE_CR_AUTOCLKG_EN BIT(30)
#define MCDE_CR_MCDEEN BIT(31)
#define MCDE_CONF0 0x00000004
#define MCDE_CONF0_SYNCMUX0 BIT(0)
#define MCDE_CONF0_SYNCMUX1 BIT(1)
#define MCDE_CONF0_SYNCMUX2 BIT(2)
#define MCDE_CONF0_SYNCMUX3 BIT(3)
#define MCDE_CONF0_SYNCMUX4 BIT(4)
#define MCDE_CONF0_SYNCMUX5 BIT(5)
#define MCDE_CONF0_SYNCMUX6 BIT(6)
#define MCDE_CONF0_SYNCMUX7 BIT(7)
#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT 12
#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_MASK 0x00007000
#define MCDE_CONF0_OUTMUX0_SHIFT 16
#define MCDE_CONF0_OUTMUX0_MASK 0x00070000
#define MCDE_CONF0_OUTMUX1_SHIFT 19
#define MCDE_CONF0_OUTMUX1_MASK 0x00380000
#define MCDE_CONF0_OUTMUX2_SHIFT 22
#define MCDE_CONF0_OUTMUX2_MASK 0x01C00000
#define MCDE_CONF0_OUTMUX3_SHIFT 25
#define MCDE_CONF0_OUTMUX3_MASK 0x0E000000
#define MCDE_CONF0_OUTMUX4_SHIFT 28
#define MCDE_CONF0_OUTMUX4_MASK 0x70000000
#define MCDE_SSP 0x00000008
#define MCDE_AIS 0x00000100
#define MCDE_IMSCERR 0x00000110
#define MCDE_RISERR 0x00000120
#define MCDE_MISERR 0x00000130
#define MCDE_SISERR 0x00000140
#define MCDE_PID 0x000001FC #define MCDE_PID 0x000001FC
#define MCDE_PID_METALFIX_VERSION_SHIFT 0 #define MCDE_PID_METALFIX_VERSION_SHIFT 0
#define MCDE_PID_METALFIX_VERSION_MASK 0x000000FF #define MCDE_PID_METALFIX_VERSION_MASK 0x000000FF
...@@ -293,7 +256,6 @@ static int mcde_probe(struct platform_device *pdev) ...@@ -293,7 +256,6 @@ static int mcde_probe(struct platform_device *pdev)
struct component_match *match = NULL; struct component_match *match = NULL;
struct resource *res; struct resource *res;
u32 pid; u32 pid;
u32 val;
int irq; int irq;
int ret; int ret;
int i; int i;
...@@ -402,27 +364,7 @@ static int mcde_probe(struct platform_device *pdev) ...@@ -402,27 +364,7 @@ static int mcde_probe(struct platform_device *pdev)
goto clk_disable; goto clk_disable;
} }
/* Set up the main control, watermark level at 7 */ /* Disable and clear any pending interrupts */
val = 7 << MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT;
/* 24 bits DPI: connect LSB Ch B to D[0:7] */
val |= 3 << MCDE_CONF0_OUTMUX0_SHIFT;
/* TV out: connect LSB Ch B to D[8:15] */
val |= 3 << MCDE_CONF0_OUTMUX1_SHIFT;
/* Don't care about this muxing */
val |= 0 << MCDE_CONF0_OUTMUX2_SHIFT;
/* 24 bits DPI: connect MID Ch B to D[24:31] */
val |= 4 << MCDE_CONF0_OUTMUX3_SHIFT;
/* 5: 24 bits DPI: connect MSB Ch B to D[32:39] */
val |= 5 << MCDE_CONF0_OUTMUX4_SHIFT;
/* Syncmux bits zero: DPI channel A and B on output pins A and B resp */
writel(val, mcde->regs + MCDE_CONF0);
/* Enable automatic clock gating */
val = readl(mcde->regs + MCDE_CR);
val |= MCDE_CR_MCDEEN | MCDE_CR_AUTOCLKG_EN;
writel(val, mcde->regs + MCDE_CR);
/* Clear any pending interrupts */
mcde_display_disable_irqs(mcde); mcde_display_disable_irqs(mcde);
writel(0, mcde->regs + MCDE_IMSCERR); writel(0, mcde->regs + MCDE_IMSCERR);
writel(0xFFFFFFFF, mcde->regs + MCDE_RISERR); writel(0xFFFFFFFF, mcde->regs + MCDE_RISERR);
...@@ -452,12 +394,28 @@ static int mcde_probe(struct platform_device *pdev) ...@@ -452,12 +394,28 @@ static int mcde_probe(struct platform_device *pdev)
ret = PTR_ERR(match); ret = PTR_ERR(match);
goto clk_disable; goto clk_disable;
} }
/*
* Perform an invasive reset of the MCDE and all blocks by
* cutting the power to the subsystem, then bring it back up
* later when we enable the display as a result of
* component_master_add_with_match().
*/
ret = regulator_disable(mcde->epod);
if (ret) {
dev_err(dev, "can't disable EPOD regulator\n");
return ret;
}
/* Wait 50 ms so we are sure we cut the power */
usleep_range(50000, 70000);
ret = component_master_add_with_match(&pdev->dev, &mcde_drm_comp_ops, ret = component_master_add_with_match(&pdev->dev, &mcde_drm_comp_ops,
match); match);
if (ret) { if (ret) {
dev_err(dev, "failed to add component master\n"); dev_err(dev, "failed to add component master\n");
goto clk_disable; goto clk_disable;
} }
return 0; return 0;
clk_disable: clk_disable:
......
...@@ -43,6 +43,7 @@ struct mcde_dsi { ...@@ -43,6 +43,7 @@ struct mcde_dsi {
struct drm_bridge *bridge_out; struct drm_bridge *bridge_out;
struct mipi_dsi_host dsi_host; struct mipi_dsi_host dsi_host;
struct mipi_dsi_device *mdsi; struct mipi_dsi_device *mdsi;
const struct drm_display_mode *mode;
struct clk *hs_clk; struct clk *hs_clk;
struct clk *lp_clk; struct clk *lp_clk;
unsigned long hs_freq; unsigned long hs_freq;
...@@ -885,7 +886,25 @@ static void mcde_dsi_bridge_pre_enable(struct drm_bridge *bridge) ...@@ -885,7 +886,25 @@ static void mcde_dsi_bridge_pre_enable(struct drm_bridge *bridge)
dev_info(d->dev, "DSI HS clock rate %lu Hz\n", dev_info(d->dev, "DSI HS clock rate %lu Hz\n",
d->hs_freq); d->hs_freq);
/* Assert RESET through the PRCMU, active low */
/* FIXME: which DSI block? */
regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0);
usleep_range(100, 200);
/* De-assert RESET again */
regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
PRCM_DSI_SW_RESET_DSI0_SW_RESETN,
PRCM_DSI_SW_RESET_DSI0_SW_RESETN);
/* Start up the hardware */
mcde_dsi_start(d);
if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) { if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
/* Set up the video mode from the DRM mode */
mcde_dsi_setup_video_mode(d, d->mode);
/* Put IF1 into video mode */ /* Put IF1 into video mode */
val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL);
val |= DSI_MCTL_MAIN_DATA_CTL_IF1_MODE; val |= DSI_MCTL_MAIN_DATA_CTL_IF1_MODE;
...@@ -926,13 +945,12 @@ static void mcde_dsi_bridge_mode_set(struct drm_bridge *bridge, ...@@ -926,13 +945,12 @@ static void mcde_dsi_bridge_mode_set(struct drm_bridge *bridge,
return; return;
} }
d->mode = mode;
dev_info(d->dev, "set DSI master to %dx%d %u Hz %s mode\n", dev_info(d->dev, "set DSI master to %dx%d %u Hz %s mode\n",
mode->hdisplay, mode->vdisplay, mode->clock * 1000, mode->hdisplay, mode->vdisplay, mode->clock * 1000,
(d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? "VIDEO" : "CMD" (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? "VIDEO" : "CMD"
); );
if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO)
mcde_dsi_setup_video_mode(d, mode);
} }
static void mcde_dsi_wait_for_command_mode_stop(struct mcde_dsi *d) static void mcde_dsi_wait_for_command_mode_stop(struct mcde_dsi *d)
...@@ -981,9 +999,6 @@ static void mcde_dsi_bridge_disable(struct drm_bridge *bridge) ...@@ -981,9 +999,6 @@ static void mcde_dsi_bridge_disable(struct drm_bridge *bridge)
struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
u32 val; u32 val;
/* Disable all error interrupts */
writel(0, d->regs + DSI_VID_MODE_STS_CTL);
if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) { if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
/* Stop video mode */ /* Stop video mode */
val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL);
...@@ -994,8 +1009,20 @@ static void mcde_dsi_bridge_disable(struct drm_bridge *bridge) ...@@ -994,8 +1009,20 @@ static void mcde_dsi_bridge_disable(struct drm_bridge *bridge)
/* Stop command mode */ /* Stop command mode */
mcde_dsi_wait_for_command_mode_stop(d); mcde_dsi_wait_for_command_mode_stop(d);
} }
}
/* Stop clocks */ static void mcde_dsi_bridge_post_disable(struct drm_bridge *bridge)
{
struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
/*
* Stop clocks and terminate any DSI traffic here so the panel can
* send commands to shut down the display using DSI direct write until
* this point.
*/
/* Disable all error interrupts */
writel(0, d->regs + DSI_VID_MODE_STS_CTL);
clk_disable_unprepare(d->hs_clk); clk_disable_unprepare(d->hs_clk);
clk_disable_unprepare(d->lp_clk); clk_disable_unprepare(d->lp_clk);
} }
...@@ -1028,6 +1055,7 @@ static const struct drm_bridge_funcs mcde_dsi_bridge_funcs = { ...@@ -1028,6 +1055,7 @@ static const struct drm_bridge_funcs mcde_dsi_bridge_funcs = {
.disable = mcde_dsi_bridge_disable, .disable = mcde_dsi_bridge_disable,
.enable = mcde_dsi_bridge_enable, .enable = mcde_dsi_bridge_enable,
.pre_enable = mcde_dsi_bridge_pre_enable, .pre_enable = mcde_dsi_bridge_pre_enable,
.post_disable = mcde_dsi_bridge_post_disable,
}; };
static int mcde_dsi_bind(struct device *dev, struct device *master, static int mcde_dsi_bind(struct device *dev, struct device *master,
...@@ -1063,21 +1091,6 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, ...@@ -1063,21 +1091,6 @@ static int mcde_dsi_bind(struct device *dev, struct device *master,
return PTR_ERR(d->lp_clk); return PTR_ERR(d->lp_clk);
} }
/* Assert RESET through the PRCMU, active low */
/* FIXME: which DSI block? */
regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0);
usleep_range(100, 200);
/* De-assert RESET again */
regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
PRCM_DSI_SW_RESET_DSI0_SW_RESETN,
PRCM_DSI_SW_RESET_DSI0_SW_RESETN);
/* Start up the hardware */
mcde_dsi_start(d);
/* Look for a panel as a child to this node */ /* Look for a panel as a child to this node */
for_each_available_child_of_node(dev->of_node, child) { for_each_available_child_of_node(dev->of_node, child) {
panel = of_drm_find_panel(child); panel = of_drm_find_panel(child);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment