diff options
Diffstat (limited to 'drivers/gpu/drm/exynos/exynos_drm_fimd.c')
| -rw-r--r-- | drivers/gpu/drm/exynos/exynos_drm_fimd.c | 299 |
1 files changed, 167 insertions, 132 deletions
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index 794e56c8798e..2e2247126581 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -29,6 +29,7 @@ #include <drm/exynos_drm.h> #include "exynos_drm_drv.h" +#include "exynos_drm_fb.h" #include "exynos_drm_fbdev.h" #include "exynos_drm_crtc.h" #include "exynos_drm_plane.h" @@ -41,7 +42,6 @@ * CPU Interface. */ -#define FIMD_DEFAULT_FRAMERATE 60 #define MIN_FB_WIDTH_FOR_16WORD_BURST 128 /* position control register for hardware window 0, 2 ~ 4.*/ @@ -59,6 +59,7 @@ #define VIDWnALPHA1(win) (VIDW_ALPHA + 0x04 + (win) * 8) #define VIDWx_BUF_START(win, buf) (VIDW_BUF_START(buf) + (win) * 8) +#define VIDWx_BUF_START_S(win, buf) (VIDW_BUF_START_S(buf) + (win) * 8) #define VIDWx_BUF_END(win, buf) (VIDW_BUF_END(buf) + (win) * 8) #define VIDWx_BUF_SIZE(win, buf) (VIDW_BUF_SIZE(buf) + (win) * 4) @@ -149,11 +150,11 @@ struct fimd_context { struct drm_device *drm_dev; struct exynos_drm_crtc *crtc; struct exynos_drm_plane planes[WINDOWS_NR]; + struct exynos_drm_plane_config configs[WINDOWS_NR]; struct clk *bus_clk; struct clk *lcd_clk; void __iomem *regs; struct regmap *sysreg; - unsigned int default_win; unsigned long irq_flags; u32 vidcon0; u32 vidcon1; @@ -169,7 +170,7 @@ struct fimd_context { struct exynos_drm_panel_info panel; struct fimd_driver_data *driver_data; - struct exynos_drm_display *display; + struct drm_encoder *encoder; }; static const struct of_device_id fimd_driver_dt_match[] = { @@ -187,6 +188,22 @@ static const struct of_device_id fimd_driver_dt_match[] = { }; MODULE_DEVICE_TABLE(of, fimd_driver_dt_match); +static const enum drm_plane_type fimd_win_types[WINDOWS_NR] = { + DRM_PLANE_TYPE_PRIMARY, + DRM_PLANE_TYPE_OVERLAY, + DRM_PLANE_TYPE_OVERLAY, + DRM_PLANE_TYPE_OVERLAY, + DRM_PLANE_TYPE_CURSOR, +}; + +static const uint32_t fimd_formats[] = { + DRM_FORMAT_C8, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, +}; + static inline struct fimd_driver_data *drm_fimd_get_driver_data( struct platform_device *pdev) { @@ -348,13 +365,6 @@ static void fimd_clear_channels(struct exynos_drm_crtc *crtc) pm_runtime_put(ctx->dev); } -static void fimd_iommu_detach_devices(struct fimd_context *ctx) -{ - /* detach this sub driver from iommu mapping if supported. */ - if (is_drm_iommu_supported(ctx->drm_dev)) - drm_iommu_detach_device(ctx->drm_dev, ctx->dev); -} - static u32 fimd_calc_clkdiv(struct fimd_context *ctx, const struct drm_display_mode *mode) { @@ -375,16 +385,6 @@ static u32 fimd_calc_clkdiv(struct fimd_context *ctx, return (clkdiv < 0x100) ? clkdiv : 0xff; } -static bool fimd_mode_fixup(struct exynos_drm_crtc *crtc, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - if (adjusted_mode->vrefresh == 0) - adjusted_mode->vrefresh = FIMD_DEFAULT_FRAMERATE; - - return true; -} - static void fimd_commit(struct exynos_drm_crtc *crtc) { struct fimd_context *ctx = crtc->ctx; @@ -486,9 +486,9 @@ static void fimd_commit(struct exynos_drm_crtc *crtc) } -static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win) +static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win, + uint32_t pixel_format, int width) { - struct exynos_drm_plane *plane = &ctx->planes[win]; unsigned long val; val = WINCONx_ENWIN; @@ -498,11 +498,11 @@ static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win) * So the request format is ARGB8888 then change it to XRGB8888. */ if (ctx->driver_data->has_limited_fmt && !win) { - if (plane->pixel_format == DRM_FORMAT_ARGB8888) - plane->pixel_format = DRM_FORMAT_XRGB8888; + if (pixel_format == DRM_FORMAT_ARGB8888) + pixel_format = DRM_FORMAT_XRGB8888; } - switch (plane->pixel_format) { + switch (pixel_format) { case DRM_FORMAT_C8: val |= WINCON0_BPPMODE_8BPP_PALETTE; val |= WINCONx_BURSTLEN_8WORD; @@ -538,17 +538,15 @@ static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win) break; } - DRM_DEBUG_KMS("bpp = %d\n", plane->bpp); - /* - * In case of exynos, setting dma-burst to 16Word causes permanent - * tearing for very small buffers, e.g. cursor buffer. Burst Mode - * switching which is based on plane size is not recommended as - * plane size varies alot towards the end of the screen and rapid - * movement causes unstable DMA which results into iommu crash/tear. + * Setting dma-burst to 16Word causes permanent tearing for very small + * buffers, e.g. cursor buffer. Burst Mode switching which based on + * plane size is not recommended as plane size varies alot towards the + * end of the screen and rapid movement causes unstable DMA, but it is + * still better to change dma-burst than displaying garbage. */ - if (plane->fb_width < MIN_FB_WIDTH_FOR_16WORD_BURST) { + if (width < MIN_FB_WIDTH_FOR_16WORD_BURST) { val &= ~WINCONx_BURSTLEN_MASK; val |= WINCONx_BURSTLEN_4WORD; } @@ -598,6 +596,16 @@ static void fimd_shadow_protect_win(struct fimd_context *ctx, { u32 reg, bits, val; + /* + * SHADOWCON/PRTCON register is used for enabling timing. + * + * for example, once only width value of a register is set, + * if the dma is started then fimd hardware could malfunction so + * with protect window setting, the register fields with prefix '_F' + * wouldn't be updated at vsync also but updated once unprotect window + * is set. + */ + if (ctx->driver_data->has_shadowcon) { reg = SHADOWCON; bits = SHADOWCON_WINx_PROTECT(win); @@ -614,60 +622,66 @@ static void fimd_shadow_protect_win(struct fimd_context *ctx, writel(val, ctx->regs + reg); } -static void fimd_win_commit(struct exynos_drm_crtc *crtc, unsigned int win) +static void fimd_atomic_begin(struct exynos_drm_crtc *crtc, + struct exynos_drm_plane *plane) { struct fimd_context *ctx = crtc->ctx; - struct exynos_drm_plane *plane; - dma_addr_t dma_addr; - unsigned long val, size, offset; - unsigned int last_x, last_y, buf_offsize, line_size; if (ctx->suspended) return; - if (win < 0 || win >= WINDOWS_NR) - return; + fimd_shadow_protect_win(ctx, plane->zpos, true); +} - plane = &ctx->planes[win]; +static void fimd_atomic_flush(struct exynos_drm_crtc *crtc, + struct exynos_drm_plane *plane) +{ + struct fimd_context *ctx = crtc->ctx; if (ctx->suspended) return; - /* - * SHADOWCON/PRTCON register is used for enabling timing. - * - * for example, once only width value of a register is set, - * if the dma is started then fimd hardware could malfunction so - * with protect window setting, the register fields with prefix '_F' - * wouldn't be updated at vsync also but updated once unprotect window - * is set. - */ + fimd_shadow_protect_win(ctx, plane->zpos, false); +} - /* protect windows */ - fimd_shadow_protect_win(ctx, win, true); +static void fimd_update_plane(struct exynos_drm_crtc *crtc, + struct exynos_drm_plane *plane) +{ + struct exynos_drm_plane_state *state = + to_exynos_plane_state(plane->base.state); + struct fimd_context *ctx = crtc->ctx; + struct drm_framebuffer *fb = state->base.fb; + dma_addr_t dma_addr; + unsigned long val, size, offset; + unsigned int last_x, last_y, buf_offsize, line_size; + unsigned int win = plane->zpos; + unsigned int bpp = fb->bits_per_pixel >> 3; + unsigned int pitch = fb->pitches[0]; + if (ctx->suspended) + return; - offset = plane->src_x * (plane->bpp >> 3); - offset += plane->src_y * plane->pitch; + offset = state->src.x * bpp; + offset += state->src.y * pitch; /* buffer start address */ - dma_addr = plane->dma_addr[0] + offset; + dma_addr = exynos_drm_fb_dma_addr(fb, 0) + offset; val = (unsigned long)dma_addr; writel(val, ctx->regs + VIDWx_BUF_START(win, 0)); /* buffer end address */ - size = plane->pitch * plane->crtc_height; + size = pitch * state->crtc.h; val = (unsigned long)(dma_addr + size); writel(val, ctx->regs + VIDWx_BUF_END(win, 0)); DRM_DEBUG_KMS("start addr = 0x%lx, end addr = 0x%lx, size = 0x%lx\n", (unsigned long)dma_addr, val, size); DRM_DEBUG_KMS("ovl_width = %d, ovl_height = %d\n", - plane->crtc_width, plane->crtc_height); + state->crtc.w, state->crtc.h); /* buffer size */ - buf_offsize = plane->pitch - (plane->crtc_width * (plane->bpp >> 3)); - line_size = plane->crtc_width * (plane->bpp >> 3); + buf_offsize = pitch - (state->crtc.w * bpp); + line_size = state->crtc.w * bpp; val = VIDW_BUF_SIZE_OFFSET(buf_offsize) | VIDW_BUF_SIZE_PAGEWIDTH(line_size) | VIDW_BUF_SIZE_OFFSET_E(buf_offsize) | @@ -675,16 +689,16 @@ static void fimd_win_commit(struct exynos_drm_crtc *crtc, unsigned int win) writel(val, ctx->regs + VIDWx_BUF_SIZE(win, 0)); /* OSD position */ - val = VIDOSDxA_TOPLEFT_X(plane->crtc_x) | - VIDOSDxA_TOPLEFT_Y(plane->crtc_y) | - VIDOSDxA_TOPLEFT_X_E(plane->crtc_x) | - VIDOSDxA_TOPLEFT_Y_E(plane->crtc_y); + val = VIDOSDxA_TOPLEFT_X(state->crtc.x) | + VIDOSDxA_TOPLEFT_Y(state->crtc.y) | + VIDOSDxA_TOPLEFT_X_E(state->crtc.x) | + VIDOSDxA_TOPLEFT_Y_E(state->crtc.y); writel(val, ctx->regs + VIDOSD_A(win)); - last_x = plane->crtc_x + plane->crtc_width; + last_x = state->crtc.x + state->crtc.w; if (last_x) last_x--; - last_y = plane->crtc_y + plane->crtc_height; + last_y = state->crtc.y + state->crtc.h; if (last_y) last_y--; @@ -694,20 +708,20 @@ static void fimd_win_commit(struct exynos_drm_crtc *crtc, unsigned int win) writel(val, ctx->regs + VIDOSD_B(win)); DRM_DEBUG_KMS("osd pos: tx = %d, ty = %d, bx = %d, by = %d\n", - plane->crtc_x, plane->crtc_y, last_x, last_y); + state->crtc.x, state->crtc.y, last_x, last_y); /* OSD size */ if (win != 3 && win != 4) { u32 offset = VIDOSD_D(win); if (win == 0) offset = VIDOSD_C(win); - val = plane->crtc_width * plane->crtc_height; + val = state->crtc.w * state->crtc.h; writel(val, ctx->regs + offset); DRM_DEBUG_KMS("osd size = 0x%x\n", (unsigned int)val); } - fimd_win_set_pixfmt(ctx, win); + fimd_win_set_pixfmt(ctx, win, fb->pixel_format, state->src.w); /* hardware window 0 doesn't support color key. */ if (win != 0) @@ -718,42 +732,28 @@ static void fimd_win_commit(struct exynos_drm_crtc *crtc, unsigned int win) if (ctx->driver_data->has_shadowcon) fimd_enable_shadow_channel_path(ctx, win, true); - /* Enable DMA channel and unprotect windows */ - fimd_shadow_protect_win(ctx, win, false); - if (ctx->i80_if) atomic_set(&ctx->win_updated, 1); } -static void fimd_win_disable(struct exynos_drm_crtc *crtc, unsigned int win) +static void fimd_disable_plane(struct exynos_drm_crtc *crtc, + struct exynos_drm_plane *plane) { struct fimd_context *ctx = crtc->ctx; - struct exynos_drm_plane *plane; - - if (win < 0 || win >= WINDOWS_NR) - return; - - plane = &ctx->planes[win]; + unsigned int win = plane->zpos; if (ctx->suspended) return; - /* protect windows */ - fimd_shadow_protect_win(ctx, win, true); - fimd_enable_video_output(ctx, win, false); if (ctx->driver_data->has_shadowcon) fimd_enable_shadow_channel_path(ctx, win, false); - - /* unprotect windows */ - fimd_shadow_protect_win(ctx, win, false); } static void fimd_enable(struct exynos_drm_crtc *crtc) { struct fimd_context *ctx = crtc->ctx; - int ret; if (!ctx->suspended) return; @@ -762,18 +762,6 @@ static void fimd_enable(struct exynos_drm_crtc *crtc) pm_runtime_get_sync(ctx->dev); - ret = clk_prepare_enable(ctx->bus_clk); - if (ret < 0) { - DRM_ERROR("Failed to prepare_enable the bus clk [%d]\n", ret); - return; - } - - ret = clk_prepare_enable(ctx->lcd_clk); - if (ret < 0) { - DRM_ERROR("Failed to prepare_enable the lcd clk [%d]\n", ret); - return; - } - /* if vblank was enabled status, enable it again. */ if (test_and_clear_bit(0, &ctx->irq_flags)) fimd_enable_vblank(ctx->crtc); @@ -795,7 +783,7 @@ static void fimd_disable(struct exynos_drm_crtc *crtc) * a destroyed buffer later. */ for (i = 0; i < WINDOWS_NR; i++) - fimd_win_disable(crtc, i); + fimd_disable_plane(crtc, &ctx->planes[i]); fimd_enable_vblank(crtc); fimd_wait_for_vblank(crtc); @@ -803,11 +791,7 @@ static void fimd_disable(struct exynos_drm_crtc *crtc) writel(0, ctx->regs + VIDCON0); - clk_disable_unprepare(ctx->lcd_clk); - clk_disable_unprepare(ctx->bus_clk); - pm_runtime_put_sync(ctx->dev); - ctx->suspended = true; } @@ -862,7 +846,7 @@ static void fimd_te_handler(struct exynos_drm_crtc *crtc) } if (test_bit(0, &ctx->irq_flags)) - drm_handle_vblank(ctx->drm_dev, ctx->pipe); + drm_crtc_handle_vblank(&ctx->crtc->base); } static void fimd_dp_clock_enable(struct exynos_drm_crtc *crtc, bool enable) @@ -879,28 +863,29 @@ static void fimd_dp_clock_enable(struct exynos_drm_crtc *crtc, bool enable) return; val = enable ? DP_MIE_CLK_DP_ENABLE : DP_MIE_CLK_DISABLE; - writel(DP_MIE_CLK_DP_ENABLE, ctx->regs + DP_MIE_CLKCON); + writel(val, ctx->regs + DP_MIE_CLKCON); } static const struct exynos_drm_crtc_ops fimd_crtc_ops = { .enable = fimd_enable, .disable = fimd_disable, - .mode_fixup = fimd_mode_fixup, .commit = fimd_commit, .enable_vblank = fimd_enable_vblank, .disable_vblank = fimd_disable_vblank, .wait_for_vblank = fimd_wait_for_vblank, - .win_commit = fimd_win_commit, - .win_disable = fimd_win_disable, + .atomic_begin = fimd_atomic_begin, + .update_plane = fimd_update_plane, + .disable_plane = fimd_disable_plane, + .atomic_flush = fimd_atomic_flush, .te_handler = fimd_te_handler, .clock_enable = fimd_dp_clock_enable, - .clear_channels = fimd_clear_channels, }; static irqreturn_t fimd_irq_handler(int irq, void *dev_id) { struct fimd_context *ctx = (struct fimd_context *)dev_id; - u32 val, clear_bit; + u32 val, clear_bit, start, start_s; + int win; val = readl(ctx->regs + VIDINTCON1); @@ -912,15 +897,25 @@ static irqreturn_t fimd_irq_handler(int irq, void *dev_id) if (ctx->pipe < 0 || !ctx->drm_dev) goto out; - if (ctx->i80_if) { - exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe); + if (!ctx->i80_if) + drm_crtc_handle_vblank(&ctx->crtc->base); + + for (win = 0 ; win < WINDOWS_NR ; win++) { + struct exynos_drm_plane *plane = &ctx->planes[win]; + + if (!plane->pending_fb) + continue; + start = readl(ctx->regs + VIDWx_BUF_START(win, 0)); + start_s = readl(ctx->regs + VIDWx_BUF_START_S(win, 0)); + if (start == start_s) + exynos_drm_crtc_finish_update(ctx->crtc, plane); + } + + if (ctx->i80_if) { /* Exits triggering mode */ atomic_set(&ctx->triggering, 0); } else { - drm_handle_vblank(ctx->drm_dev, ctx->pipe); - exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe); - /* set wait vsync event to zero and wake up queue. */ if (atomic_read(&ctx->wait_vsync_event)) { atomic_set(&ctx->wait_vsync_event, 0); @@ -938,33 +933,37 @@ static int fimd_bind(struct device *dev, struct device *master, void *data) struct drm_device *drm_dev = data; struct exynos_drm_private *priv = drm_dev->dev_private; struct exynos_drm_plane *exynos_plane; - enum drm_plane_type type; - unsigned int zpos; + unsigned int i; int ret; ctx->drm_dev = drm_dev; ctx->pipe = priv->pipe++; - for (zpos = 0; zpos < WINDOWS_NR; zpos++) { - type = (zpos == ctx->default_win) ? DRM_PLANE_TYPE_PRIMARY : - DRM_PLANE_TYPE_OVERLAY; - ret = exynos_plane_init(drm_dev, &ctx->planes[zpos], - 1 << ctx->pipe, type, zpos); + for (i = 0; i < WINDOWS_NR; i++) { + ctx->configs[i].pixel_formats = fimd_formats; + ctx->configs[i].num_pixel_formats = ARRAY_SIZE(fimd_formats); + ctx->configs[i].zpos = i; + ctx->configs[i].type = fimd_win_types[i]; + ret = exynos_plane_init(drm_dev, &ctx->planes[i], + 1 << ctx->pipe, &ctx->configs[i]); if (ret) return ret; } - exynos_plane = &ctx->planes[ctx->default_win]; + exynos_plane = &ctx->planes[DEFAULT_WIN]; ctx->crtc = exynos_drm_crtc_create(drm_dev, &exynos_plane->base, ctx->pipe, EXYNOS_DISPLAY_TYPE_LCD, &fimd_crtc_ops, ctx); if (IS_ERR(ctx->crtc)) return PTR_ERR(ctx->crtc); - if (ctx->display) - exynos_drm_create_enc_conn(drm_dev, ctx->display); + if (ctx->encoder) + exynos_dpi_bind(drm_dev, ctx->encoder); + + if (is_drm_iommu_supported(drm_dev)) + fimd_clear_channels(ctx->crtc); - ret = drm_iommu_attach_device_if_possible(ctx->crtc, drm_dev, dev); + ret = drm_iommu_attach_device(drm_dev, dev); if (ret) priv->pipe--; @@ -978,10 +977,10 @@ static void fimd_unbind(struct device *dev, struct device *master, fimd_disable(ctx->crtc); - fimd_iommu_detach_devices(ctx); + drm_iommu_detach_device(ctx->drm_dev, ctx->dev); - if (ctx->display) - exynos_dpi_remove(ctx->display); + if (ctx->encoder) + exynos_dpi_remove(ctx->encoder); } static const struct component_ops fimd_component_ops = { @@ -1088,10 +1087,9 @@ static int fimd_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ctx); - ctx->display = exynos_dpi_probe(dev); - if (IS_ERR(ctx->display)) { - return PTR_ERR(ctx->display); - } + ctx->encoder = exynos_dpi_probe(dev); + if (IS_ERR(ctx->encoder)) + return PTR_ERR(ctx->encoder); pm_runtime_enable(dev); @@ -1116,12 +1114,49 @@ static int fimd_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int exynos_fimd_suspend(struct device *dev) +{ + struct fimd_context *ctx = dev_get_drvdata(dev); + + clk_disable_unprepare(ctx->lcd_clk); + clk_disable_unprepare(ctx->bus_clk); + + return 0; +} + +static int exynos_fimd_resume(struct device *dev) +{ + struct fimd_context *ctx = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(ctx->bus_clk); + if (ret < 0) { + DRM_ERROR("Failed to prepare_enable the bus clk [%d]\n", ret); + return ret; + } + + ret = clk_prepare_enable(ctx->lcd_clk); + if (ret < 0) { + DRM_ERROR("Failed to prepare_enable the lcd clk [%d]\n", ret); + return ret; + } + + return 0; +} +#endif + +static const struct dev_pm_ops exynos_fimd_pm_ops = { + SET_RUNTIME_PM_OPS(exynos_fimd_suspend, exynos_fimd_resume, NULL) +}; + struct platform_driver fimd_driver = { .probe = fimd_probe, .remove = fimd_remove, .driver = { .name = "exynos4-fb", .owner = THIS_MODULE, + .pm = &exynos_fimd_pm_ops, .of_match_table = fimd_driver_dt_match, }, }; |

