Commit c96fdfde authored by Andrzej Hajda's avatar Andrzej Hajda Committed by Inki Dae

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: default avatarTobias Jakobi <tjakobi@math.uni-bielefeld.de>
Signed-off-by: default avatarAndrzej Hajda <a.hajda@samsung.com>
Signed-off-by: default avatarInki Dae <inki.dae@samsung.com>
parent 14e022f3
...@@ -198,6 +198,7 @@ struct fimd_context { ...@@ -198,6 +198,7 @@ struct fimd_context {
atomic_t wait_vsync_event; atomic_t wait_vsync_event;
atomic_t win_updated; atomic_t win_updated;
atomic_t triggering; atomic_t triggering;
u32 clkdiv;
const struct fimd_driver_data *driver_data; const struct fimd_driver_data *driver_data;
struct drm_encoder *encoder; struct drm_encoder *encoder;
...@@ -389,15 +390,18 @@ static void fimd_clear_channels(struct exynos_drm_crtc *crtc) ...@@ -389,15 +390,18 @@ static void fimd_clear_channels(struct exynos_drm_crtc *crtc)
pm_runtime_put(ctx->dev); 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; u32 clkdiv;
if (mode->clock == 0) { if (mode->clock == 0) {
DRM_ERROR("Mode has zero clock value.\n"); DRM_INFO("Mode has zero clock value.\n");
return 0xff; return -EINVAL;
} }
ideal_clk = mode->clock * 1000; ideal_clk = mode->clock * 1000;
...@@ -410,10 +414,23 @@ static u32 fimd_calc_clkdiv(struct fimd_context *ctx, ...@@ -410,10 +414,23 @@ static u32 fimd_calc_clkdiv(struct fimd_context *ctx,
ideal_clk *= 2; 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 */ /* 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) static void fimd_setup_trigger(struct fimd_context *ctx)
...@@ -442,7 +459,7 @@ static void fimd_commit(struct exynos_drm_crtc *crtc) ...@@ -442,7 +459,7 @@ static void fimd_commit(struct exynos_drm_crtc *crtc)
struct drm_display_mode *mode = &crtc->base.state->adjusted_mode; struct drm_display_mode *mode = &crtc->base.state->adjusted_mode;
const struct fimd_driver_data *driver_data = ctx->driver_data; const struct fimd_driver_data *driver_data = ctx->driver_data;
void *timing_base = ctx->regs + driver_data->timing_base; void *timing_base = ctx->regs + driver_data->timing_base;
u32 val, clkdiv; u32 val;
if (ctx->suspended) if (ctx->suspended)
return; return;
...@@ -543,9 +560,8 @@ static void fimd_commit(struct exynos_drm_crtc *crtc) ...@@ -543,9 +560,8 @@ static void fimd_commit(struct exynos_drm_crtc *crtc)
if (ctx->driver_data->has_clksel) if (ctx->driver_data->has_clksel)
val |= VIDCON0_CLKSEL_LCD; val |= VIDCON0_CLKSEL_LCD;
clkdiv = fimd_calc_clkdiv(ctx, mode); if (ctx->clkdiv > 1)
if (clkdiv > 1) val |= VIDCON0_CLKVAL_F(ctx->clkdiv - 1) | VIDCON0_CLKDIR;
val |= VIDCON0_CLKVAL_F(clkdiv - 1) | VIDCON0_CLKDIR;
writel(val, ctx->regs + VIDCON0); writel(val, ctx->regs + VIDCON0);
} }
...@@ -939,6 +955,7 @@ static const struct exynos_drm_crtc_ops fimd_crtc_ops = { ...@@ -939,6 +955,7 @@ static const struct exynos_drm_crtc_ops fimd_crtc_ops = {
.update_plane = fimd_update_plane, .update_plane = fimd_update_plane,
.disable_plane = fimd_disable_plane, .disable_plane = fimd_disable_plane,
.atomic_flush = fimd_atomic_flush, .atomic_flush = fimd_atomic_flush,
.atomic_check = fimd_atomic_check,
.te_handler = fimd_te_handler, .te_handler = fimd_te_handler,
}; };
......
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