diff options
Diffstat (limited to 'drivers/gpu/drm/exynos/exynos_drm_fimd.c')
-rw-r--r-- | drivers/gpu/drm/exynos/exynos_drm_fimd.c | 213 |
1 files changed, 150 insertions, 63 deletions
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index 40fd6ccfcd6..33161ad3820 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -19,6 +19,7 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/pm_runtime.h> +#include <linux/component.h> #include <video/of_display_timing.h> #include <video/of_videomode.h> @@ -38,6 +39,7 @@ */ #define FIMD_DEFAULT_FRAMERATE 60 +#define MIN_FB_WIDTH_FOR_16WORD_BURST 128 /* position control register for hardware window 0, 2 ~ 4.*/ #define VIDOSD_A(win) (VIDOSD_BASE + 0x00 + (win) * 16) @@ -122,6 +124,7 @@ struct fimd_context { struct exynos_drm_panel_info panel; struct fimd_driver_data *driver_data; + struct exynos_drm_display *display; }; static const struct of_device_id fimd_driver_dt_match[] = { @@ -143,13 +146,57 @@ static inline struct fimd_driver_data *drm_fimd_get_driver_data( return (struct fimd_driver_data *)of_id->data; } +static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr) +{ + struct fimd_context *ctx = mgr->ctx; + + if (ctx->suspended) + return; + + atomic_set(&ctx->wait_vsync_event, 1); + + /* + * wait for FIMD to signal VSYNC interrupt or return after + * timeout which is set to 50ms (refresh rate of 20). + */ + if (!wait_event_timeout(ctx->wait_vsync_queue, + !atomic_read(&ctx->wait_vsync_event), + HZ/20)) + DRM_DEBUG_KMS("vblank wait timed out.\n"); +} + + +static void fimd_clear_channel(struct exynos_drm_manager *mgr) +{ + struct fimd_context *ctx = mgr->ctx; + int win, ch_enabled = 0; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + /* Check if any channel is enabled. */ + for (win = 0; win < WINDOWS_NR; win++) { + u32 val = readl(ctx->regs + SHADOWCON); + if (val & SHADOWCON_CHx_ENABLE(win)) { + val &= ~SHADOWCON_CHx_ENABLE(win); + writel(val, ctx->regs + SHADOWCON); + ch_enabled = 1; + } + } + + /* Wait for vsync, as disable channel takes effect at next vsync */ + if (ch_enabled) + fimd_wait_for_vblank(mgr); +} + static int fimd_mgr_initialize(struct exynos_drm_manager *mgr, - struct drm_device *drm_dev, int pipe) + struct drm_device *drm_dev) { struct fimd_context *ctx = mgr->ctx; + struct exynos_drm_private *priv; + priv = drm_dev->dev_private; - ctx->drm_dev = drm_dev; - ctx->pipe = pipe; + mgr->drm_dev = ctx->drm_dev = drm_dev; + mgr->pipe = ctx->pipe = priv->pipe++; /* * enable drm irq mode. @@ -169,8 +216,14 @@ static int fimd_mgr_initialize(struct exynos_drm_manager *mgr, drm_dev->vblank_disable_allowed = true; /* attach this sub driver to iommu mapping if supported. */ - if (is_drm_iommu_supported(ctx->drm_dev)) + if (is_drm_iommu_supported(ctx->drm_dev)) { + /* + * If any channel is already active, iommu will throw + * a PAGE FAULT when enabled. So clear any channel if enabled. + */ + fimd_clear_channel(mgr); drm_iommu_attach_device(ctx->drm_dev, ctx->dev); + } return 0; } @@ -324,25 +377,6 @@ static void fimd_disable_vblank(struct exynos_drm_manager *mgr) } } -static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr) -{ - struct fimd_context *ctx = mgr->ctx; - - if (ctx->suspended) - return; - - atomic_set(&ctx->wait_vsync_event, 1); - - /* - * wait for FIMD to signal VSYNC interrupt or return after - * timeout which is set to 50ms (refresh rate of 20). - */ - if (!wait_event_timeout(ctx->wait_vsync_queue, - !atomic_read(&ctx->wait_vsync_event), - HZ/20)) - DRM_DEBUG_KMS("vblank wait timed out.\n"); -} - static void fimd_win_mode_set(struct exynos_drm_manager *mgr, struct exynos_drm_overlay *overlay) { @@ -446,6 +480,19 @@ static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win) DRM_DEBUG_KMS("bpp = %d\n", win_data->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 overlay size is not recommended as + * overlay size varies alot towards the end of the screen and rapid + * movement causes unstable DMA which results into iommu crash/tear. + */ + + if (win_data->fb_width < MIN_FB_WIDTH_FOR_16WORD_BURST) { + val &= ~WINCONx_BURSTLEN_MASK; + val |= WINCONx_BURSTLEN_4WORD; + } + writel(val, ctx->regs + WINCON(win)); } @@ -656,19 +703,6 @@ static void fimd_win_disable(struct exynos_drm_manager *mgr, int zpos) win_data->enabled = false; } -static void fimd_clear_win(struct fimd_context *ctx, int win) -{ - writel(0, ctx->regs + WINCON(win)); - writel(0, ctx->regs + VIDOSD_A(win)); - writel(0, ctx->regs + VIDOSD_B(win)); - writel(0, ctx->regs + VIDOSD_C(win)); - - if (win == 1 || win == 2) - writel(0, ctx->regs + VIDOSD_D(win)); - - fimd_shadow_protect_win(ctx, win, false); -} - static void fimd_window_suspend(struct exynos_drm_manager *mgr) { struct fimd_context *ctx = mgr->ctx; @@ -707,6 +741,8 @@ static void fimd_apply(struct exynos_drm_manager *mgr) win_data = &ctx->win_data[i]; if (win_data->enabled) fimd_win_commit(mgr, i); + else + fimd_win_disable(mgr, i); } fimd_commit(mgr); @@ -803,8 +839,6 @@ static void fimd_dpms(struct exynos_drm_manager *mgr, int mode) } static struct exynos_drm_manager_ops fimd_manager_ops = { - .initialize = fimd_mgr_initialize, - .remove = fimd_mgr_remove, .dpms = fimd_dpms, .mode_fixup = fimd_mode_fixup, .mode_set = fimd_mode_set, @@ -849,20 +883,64 @@ out: return IRQ_HANDLED; } +static int fimd_bind(struct device *dev, struct device *master, void *data) +{ + struct fimd_context *ctx = fimd_manager.ctx; + struct drm_device *drm_dev = data; + + fimd_mgr_initialize(&fimd_manager, drm_dev); + exynos_drm_crtc_create(&fimd_manager); + if (ctx->display) + exynos_drm_create_enc_conn(drm_dev, ctx->display); + + return 0; + +} + +static void fimd_unbind(struct device *dev, struct device *master, + void *data) +{ + struct exynos_drm_manager *mgr = dev_get_drvdata(dev); + struct fimd_context *ctx = fimd_manager.ctx; + struct drm_crtc *crtc = mgr->crtc; + + fimd_dpms(mgr, DRM_MODE_DPMS_OFF); + + if (ctx->display) + exynos_dpi_remove(dev); + + fimd_mgr_remove(mgr); + + crtc->funcs->destroy(crtc); +} + +static const struct component_ops fimd_component_ops = { + .bind = fimd_bind, + .unbind = fimd_unbind, +}; + static int fimd_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct fimd_context *ctx; struct resource *res; - int win; int ret = -EINVAL; - if (!dev->of_node) - return -ENODEV; + ret = exynos_drm_component_add(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC, + fimd_manager.type); + if (ret) + return ret; + + if (!dev->of_node) { + ret = -ENODEV; + goto err_del_component; + } ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + if (!ctx) { + ret = -ENOMEM; + goto err_del_component; + } ctx->dev = dev; ctx->suspended = true; @@ -875,32 +953,37 @@ static int fimd_probe(struct platform_device *pdev) ctx->bus_clk = devm_clk_get(dev, "fimd"); if (IS_ERR(ctx->bus_clk)) { dev_err(dev, "failed to get bus clock\n"); - return PTR_ERR(ctx->bus_clk); + ret = PTR_ERR(ctx->bus_clk); + goto err_del_component; } ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd"); if (IS_ERR(ctx->lcd_clk)) { dev_err(dev, "failed to get lcd clock\n"); - return PTR_ERR(ctx->lcd_clk); + ret = PTR_ERR(ctx->lcd_clk); + goto err_del_component; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ctx->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(ctx->regs)) - return PTR_ERR(ctx->regs); + if (IS_ERR(ctx->regs)) { + ret = PTR_ERR(ctx->regs); + goto err_del_component; + } res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "vsync"); if (!res) { dev_err(dev, "irq request failed.\n"); - return -ENXIO; + ret = -ENXIO; + goto err_del_component; } ret = devm_request_irq(dev, res->start, fimd_irq_handler, 0, "drm_fimd", ctx); if (ret) { dev_err(dev, "irq request failed.\n"); - return ret; + goto err_del_component; } ctx->driver_data = drm_fimd_get_driver_data(pdev); @@ -910,30 +993,34 @@ static int fimd_probe(struct platform_device *pdev) platform_set_drvdata(pdev, &fimd_manager); fimd_manager.ctx = ctx; - exynos_drm_manager_register(&fimd_manager); - exynos_dpi_probe(ctx->dev); + ctx->display = exynos_dpi_probe(dev); + if (IS_ERR(ctx->display)) + return PTR_ERR(ctx->display); - pm_runtime_enable(dev); + pm_runtime_enable(&pdev->dev); - for (win = 0; win < WINDOWS_NR; win++) - fimd_clear_win(ctx, win); + ret = component_add(&pdev->dev, &fimd_component_ops); + if (ret) + goto err_disable_pm_runtime; - return 0; + return ret; + +err_disable_pm_runtime: + pm_runtime_disable(&pdev->dev); + +err_del_component: + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC); + return ret; } static int fimd_remove(struct platform_device *pdev) { - struct exynos_drm_manager *mgr = platform_get_drvdata(pdev); - - exynos_dpi_remove(&pdev->dev); - - exynos_drm_manager_unregister(&fimd_manager); - - fimd_dpms(mgr, DRM_MODE_DPMS_OFF); - pm_runtime_disable(&pdev->dev); + component_del(&pdev->dev, &fimd_component_ops); + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC); + return 0; } |