diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 915bbdc350ac..f8ad5ed6e0e7 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -53,6 +53,26 @@ static void tegra_dc_cursor_commit(struct tegra_dc *dc) tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); } +/* + * Reads the active copy of a register. This takes the dc->lock spinlock to + * prevent races with the VBLANK processing which also needs access to the + * active copy of some registers. + */ +static u32 tegra_dc_readl_active(struct tegra_dc *dc, unsigned long offset) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&dc->lock, flags); + + tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS); + value = tegra_dc_readl(dc, offset); + tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS); + + spin_unlock_irqrestore(&dc->lock, flags); + return value; +} + /* * Double-buffered registers have two copies: ASSEMBLY and ACTIVE. When the * *_ACT_REQ bits are set the ASSEMBLY copy is latched into the ACTIVE copy. @@ -935,12 +955,47 @@ static const struct drm_crtc_funcs tegra_crtc_funcs = { .destroy = tegra_dc_destroy, }; +static void tegra_dc_stop(struct tegra_dc *dc) +{ + u32 value; + + /* stop the display controller */ + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + + tegra_dc_commit(dc); +} + +static bool tegra_dc_idle(struct tegra_dc *dc) +{ + u32 value; + + value = tegra_dc_readl_active(dc, DC_CMD_DISPLAY_COMMAND); + + return (value & DISP_CTRL_MODE_MASK) == 0; +} + +static int tegra_dc_wait_idle(struct tegra_dc *dc, unsigned long timeout) +{ + timeout = jiffies + msecs_to_jiffies(timeout); + + while (time_before(jiffies, timeout)) { + if (tegra_dc_idle(dc)) + return 0; + + usleep_range(1000, 2000); + } + + dev_dbg(dc->dev, "timeout waiting for DC to become idle\n"); + return -ETIMEDOUT; +} + static void tegra_crtc_disable(struct drm_crtc *crtc) { struct tegra_dc *dc = to_tegra_dc(crtc); struct drm_device *drm = crtc->dev; struct drm_plane *plane; - u32 value; drm_for_each_legacy_plane(plane, &drm->mode_config.plane_list) { if (plane->crtc == crtc) { @@ -954,10 +1009,15 @@ static void tegra_crtc_disable(struct drm_crtc *crtc) } } - /* stop the display controller */ - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + if (!tegra_dc_idle(dc)) { + tegra_dc_stop(dc); + + /* + * Ignore the return value, there isn't anything useful to do + * in case this fails. + */ + tegra_dc_wait_idle(dc, 100); + } drm_crtc_vblank_off(crtc); tegra_dc_commit(dc);