From c96fdfdeca5657740c50b09c46f073edd7a2d3a0 Mon Sep 17 00:00:00 2001 From: Andrzej Hajda Date: Fri, 23 Sep 2016 12:43:29 +0200 Subject: [PATCH] drm/exynos/fimd: add clock rate checking In case of some platforms fimd clocks can be configured to very low values, as a result refresh rate can be very low and driver/drm-core will timeout waiting for vblanks, it will result in premature removal of framebuffers and will cause oopses. The patch adds atomic_check callback to fimd to prevent setting such modes. Reported-by: Tobias Jakobi Signed-off-by: Andrzej Hajda Signed-off-by: Inki Dae --- drivers/gpu/drm/exynos/exynos_drm_fimd.c | 39 +++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index d472164..27efe30 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -198,6 +198,7 @@ struct fimd_context { atomic_t wait_vsync_event; atomic_t win_updated; atomic_t triggering; + u32 clkdiv; const struct fimd_driver_data *driver_data; struct drm_encoder *encoder; @@ -389,15 +390,18 @@ static void fimd_clear_channels(struct exynos_drm_crtc *crtc) pm_runtime_put(ctx->dev); } -static u32 fimd_calc_clkdiv(struct fimd_context *ctx, - const struct drm_display_mode *mode) + +static int fimd_atomic_check(struct exynos_drm_crtc *crtc, + struct drm_crtc_state *state) { - unsigned long ideal_clk; + struct drm_display_mode *mode = &state->adjusted_mode; + struct fimd_context *ctx = crtc->ctx; + unsigned long ideal_clk, lcd_rate; u32 clkdiv; if (mode->clock == 0) { - DRM_ERROR("Mode has zero clock value.\n"); - return 0xff; + DRM_INFO("Mode has zero clock value.\n"); + return -EINVAL; } ideal_clk = mode->clock * 1000; @@ -410,10 +414,23 @@ static u32 fimd_calc_clkdiv(struct fimd_context *ctx, ideal_clk *= 2; } + lcd_rate = clk_get_rate(ctx->lcd_clk); + if (2 * lcd_rate < ideal_clk) { + DRM_INFO("sclk_fimd clock too low(%lu) for requested pixel clock(%lu)\n", + lcd_rate, ideal_clk); + return -EINVAL; + } + /* Find the clock divider value that gets us closest to ideal_clk */ - clkdiv = DIV_ROUND_CLOSEST(clk_get_rate(ctx->lcd_clk), ideal_clk); + clkdiv = DIV_ROUND_CLOSEST(lcd_rate, ideal_clk); + if (clkdiv >= 0x200) { + DRM_INFO("requested pixel clock(%lu) too low\n", ideal_clk); + return -EINVAL; + } + + ctx->clkdiv = (clkdiv < 0x100) ? clkdiv : 0xff; - return (clkdiv < 0x100) ? clkdiv : 0xff; + return 0; } static void fimd_setup_trigger(struct fimd_context *ctx) @@ -442,7 +459,7 @@ static void fimd_commit(struct exynos_drm_crtc *crtc) struct drm_display_mode *mode = &crtc->base.state->adjusted_mode; const struct fimd_driver_data *driver_data = ctx->driver_data; void *timing_base = ctx->regs + driver_data->timing_base; - u32 val, clkdiv; + u32 val; if (ctx->suspended) return; @@ -543,9 +560,8 @@ static void fimd_commit(struct exynos_drm_crtc *crtc) if (ctx->driver_data->has_clksel) val |= VIDCON0_CLKSEL_LCD; - clkdiv = fimd_calc_clkdiv(ctx, mode); - if (clkdiv > 1) - val |= VIDCON0_CLKVAL_F(clkdiv - 1) | VIDCON0_CLKDIR; + if (ctx->clkdiv > 1) + val |= VIDCON0_CLKVAL_F(ctx->clkdiv - 1) | VIDCON0_CLKDIR; writel(val, ctx->regs + VIDCON0); } @@ -939,6 +955,7 @@ static const struct exynos_drm_crtc_ops fimd_crtc_ops = { .update_plane = fimd_update_plane, .disable_plane = fimd_disable_plane, .atomic_flush = fimd_atomic_flush, + .atomic_check = fimd_atomic_check, .te_handler = fimd_te_handler, }; -- 2.7.4