]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/gpu/drm/exynos/exynos_drm_fimd.c
Merge tag 'for-3.3-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb...
[karo-tx-linux.git] / drivers / gpu / drm / exynos / exynos_drm_fimd.c
index db3b3d9e731d86475d734884e2ec113a8be5f914..b6a737d196ae556c6b038ad8243540a76f2271af 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/clk.h>
+#include <linux/pm_runtime.h>
 
 #include <drm/exynos_drm.h>
 #include <plat/regs-fb-v4.h>
@@ -68,6 +69,7 @@ struct fimd_win_data {
        void __iomem            *vaddr;
        unsigned int            buf_offsize;
        unsigned int            line_size;      /* bytes */
+       bool                    enabled;
 };
 
 struct fimd_context {
@@ -84,6 +86,8 @@ struct fimd_context {
        unsigned long                   irq_flags;
        u32                             vidcon0;
        u32                             vidcon1;
+       bool                            suspended;
+       struct mutex                    lock;
 
        struct fb_videomode             *timing;
 };
@@ -119,7 +123,7 @@ static int fimd_display_power_on(struct device *dev, int mode)
 {
        DRM_DEBUG_KMS("%s\n", __FILE__);
 
-       /* TODO. */
+       /* TODO */
 
        return 0;
 }
@@ -132,12 +136,69 @@ static struct exynos_drm_display_ops fimd_display_ops = {
        .power_on = fimd_display_power_on,
 };
 
+static void fimd_dpms(struct device *subdrv_dev, int mode)
+{
+       struct fimd_context *ctx = get_fimd_context(subdrv_dev);
+
+       DRM_DEBUG_KMS("%s, %d\n", __FILE__, mode);
+
+       mutex_lock(&ctx->lock);
+
+       switch (mode) {
+       case DRM_MODE_DPMS_ON:
+               /*
+                * enable fimd hardware only if suspended status.
+                *
+                * P.S. fimd_dpms function would be called at booting time so
+                * clk_enable could be called double time.
+                */
+               if (ctx->suspended)
+                       pm_runtime_get_sync(subdrv_dev);
+               break;
+       case DRM_MODE_DPMS_STANDBY:
+       case DRM_MODE_DPMS_SUSPEND:
+       case DRM_MODE_DPMS_OFF:
+               if (!ctx->suspended)
+                       pm_runtime_put_sync(subdrv_dev);
+               break;
+       default:
+               DRM_DEBUG_KMS("unspecified mode %d\n", mode);
+               break;
+       }
+
+       mutex_unlock(&ctx->lock);
+}
+
+static void fimd_apply(struct device *subdrv_dev)
+{
+       struct fimd_context *ctx = get_fimd_context(subdrv_dev);
+       struct exynos_drm_manager *mgr = &ctx->subdrv.manager;
+       struct exynos_drm_manager_ops *mgr_ops = mgr->ops;
+       struct exynos_drm_overlay_ops *ovl_ops = mgr->overlay_ops;
+       struct fimd_win_data *win_data;
+       int i;
+
+       DRM_DEBUG_KMS("%s\n", __FILE__);
+
+       for (i = 0; i < WINDOWS_NR; i++) {
+               win_data = &ctx->win_data[i];
+               if (win_data->enabled && (ovl_ops && ovl_ops->commit))
+                       ovl_ops->commit(subdrv_dev, i);
+       }
+
+       if (mgr_ops && mgr_ops->commit)
+               mgr_ops->commit(subdrv_dev);
+}
+
 static void fimd_commit(struct device *dev)
 {
        struct fimd_context *ctx = get_fimd_context(dev);
        struct fb_videomode *timing = ctx->timing;
        u32 val;
 
+       if (ctx->suspended)
+               return;
+
        DRM_DEBUG_KMS("%s\n", __FILE__);
 
        /* setup polarity values from machine code. */
@@ -177,40 +238,6 @@ static void fimd_commit(struct device *dev)
        writel(val, ctx->regs + VIDCON0);
 }
 
-static void fimd_disable(struct device *dev)
-{
-       struct fimd_context *ctx = get_fimd_context(dev);
-       struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
-       struct drm_device *drm_dev = subdrv->drm_dev;
-       struct exynos_drm_manager *manager = &subdrv->manager;
-       u32 val;
-
-       DRM_DEBUG_KMS("%s\n", __FILE__);
-
-       /* fimd dma off */
-       val = readl(ctx->regs + VIDCON0);
-       val &= ~(VIDCON0_ENVID | VIDCON0_ENVID_F);
-       writel(val, ctx->regs + VIDCON0);
-
-       /*
-        * if vblank is enabled status with dma off then
-        * it disables vsync interrupt.
-        */
-       if (drm_dev->vblank_enabled[manager->pipe] &&
-               atomic_read(&drm_dev->vblank_refcount[manager->pipe])) {
-               drm_vblank_put(drm_dev, manager->pipe);
-
-               /*
-                * if vblank_disable_allowed is 0 then disable
-                * vsync interrupt right now else the vsync interrupt
-                * would be disabled by drm timer once a current process
-                * gives up ownershop of vblank event.
-                */
-               if (!drm_dev->vblank_disable_allowed)
-                       drm_vblank_off(drm_dev, manager->pipe);
-       }
-}
-
 static int fimd_enable_vblank(struct device *dev)
 {
        struct fimd_context *ctx = get_fimd_context(dev);
@@ -218,6 +245,9 @@ static int fimd_enable_vblank(struct device *dev)
 
        DRM_DEBUG_KMS("%s\n", __FILE__);
 
+       if (ctx->suspended)
+               return -EPERM;
+
        if (!test_and_set_bit(0, &ctx->irq_flags)) {
                val = readl(ctx->regs + VIDINTCON0);
 
@@ -242,6 +272,9 @@ static void fimd_disable_vblank(struct device *dev)
 
        DRM_DEBUG_KMS("%s\n", __FILE__);
 
+       if (ctx->suspended)
+               return;
+
        if (test_and_clear_bit(0, &ctx->irq_flags)) {
                val = readl(ctx->regs + VIDINTCON0);
 
@@ -253,8 +286,9 @@ static void fimd_disable_vblank(struct device *dev)
 }
 
 static struct exynos_drm_manager_ops fimd_manager_ops = {
+       .dpms = fimd_dpms,
+       .apply = fimd_apply,
        .commit = fimd_commit,
-       .disable = fimd_disable,
        .enable_vblank = fimd_enable_vblank,
        .disable_vblank = fimd_disable_vblank,
 };
@@ -264,6 +298,7 @@ static void fimd_win_mode_set(struct device *dev,
 {
        struct fimd_context *ctx = get_fimd_context(dev);
        struct fimd_win_data *win_data;
+       int win;
        unsigned long offset;
 
        DRM_DEBUG_KMS("%s\n", __FILE__);
@@ -273,12 +308,19 @@ static void fimd_win_mode_set(struct device *dev,
                return;
        }
 
+       win = overlay->zpos;
+       if (win == DEFAULT_ZPOS)
+               win = ctx->default_win;
+
+       if (win < 0 || win > WINDOWS_NR)
+               return;
+
        offset = overlay->fb_x * (overlay->bpp >> 3);
        offset += overlay->fb_y * overlay->pitch;
 
        DRM_DEBUG_KMS("offset = 0x%lx, pitch = %x\n", offset, overlay->pitch);
 
-       win_data = &ctx->win_data[ctx->default_win];
+       win_data = &ctx->win_data[win];
 
        win_data->offset_x = overlay->crtc_x;
        win_data->offset_y = overlay->crtc_y;
@@ -286,8 +328,8 @@ static void fimd_win_mode_set(struct device *dev,
        win_data->ovl_height = overlay->crtc_height;
        win_data->fb_width = overlay->fb_width;
        win_data->fb_height = overlay->fb_height;
-       win_data->dma_addr = overlay->dma_addr + offset;
-       win_data->vaddr = overlay->vaddr + offset;
+       win_data->dma_addr = overlay->dma_addr[0] + offset;
+       win_data->vaddr = overlay->vaddr[0] + offset;
        win_data->bpp = overlay->bpp;
        win_data->buf_offsize = (overlay->fb_width - overlay->crtc_width) *
                                (overlay->bpp >> 3);
@@ -381,15 +423,21 @@ static void fimd_win_set_colkey(struct device *dev, unsigned int win)
        writel(keycon1, ctx->regs + WKEYCON1_BASE(win));
 }
 
-static void fimd_win_commit(struct device *dev)
+static void fimd_win_commit(struct device *dev, int zpos)
 {
        struct fimd_context *ctx = get_fimd_context(dev);
        struct fimd_win_data *win_data;
-       int win = ctx->default_win;
+       int win = zpos;
        unsigned long val, alpha, size;
 
        DRM_DEBUG_KMS("%s\n", __FILE__);
 
+       if (ctx->suspended)
+               return;
+
+       if (win == DEFAULT_ZPOS)
+               win = ctx->default_win;
+
        if (win < 0 || win > WINDOWS_NR)
                return;
 
@@ -472,24 +520,37 @@ static void fimd_win_commit(struct device *dev)
        if (win != 0)
                fimd_win_set_colkey(dev, win);
 
+       /* wincon */
+       val = readl(ctx->regs + WINCON(win));
+       val |= WINCONx_ENWIN;
+       writel(val, ctx->regs + WINCON(win));
+
        /* Enable DMA channel and unprotect windows */
        val = readl(ctx->regs + SHADOWCON);
        val |= SHADOWCON_CHx_ENABLE(win);
        val &= ~SHADOWCON_WINx_PROTECT(win);
        writel(val, ctx->regs + SHADOWCON);
+
+       win_data->enabled = true;
 }
 
-static void fimd_win_disable(struct device *dev)
+static void fimd_win_disable(struct device *dev, int zpos)
 {
        struct fimd_context *ctx = get_fimd_context(dev);
-       int win = ctx->default_win;
+       struct fimd_win_data *win_data;
+       int win = zpos;
        u32 val;
 
        DRM_DEBUG_KMS("%s\n", __FILE__);
 
+       if (win == DEFAULT_ZPOS)
+               win = ctx->default_win;
+
        if (win < 0 || win > WINDOWS_NR)
                return;
 
+       win_data = &ctx->win_data[win];
+
        /* protect windows */
        val = readl(ctx->regs + SHADOWCON);
        val |= SHADOWCON_WINx_PROTECT(win);
@@ -505,6 +566,8 @@ static void fimd_win_disable(struct device *dev)
        val &= ~SHADOWCON_CHx_ENABLE(win);
        val &= ~SHADOWCON_WINx_PROTECT(win);
        writel(val, ctx->regs + SHADOWCON);
+
+       win_data->enabled = false;
 }
 
 static struct exynos_drm_overlay_ops fimd_overlay_ops = {
@@ -540,9 +603,17 @@ static void fimd_finish_pageflip(struct drm_device *drm_dev, int crtc)
                wake_up_interruptible(&e->base.file_priv->event_wait);
        }
 
-       if (is_checked)
+       if (is_checked) {
                drm_vblank_put(drm_dev, crtc);
 
+               /*
+                * don't off vblank if vblank_disable_allowed is 1,
+                * because vblank would be off by timer handler.
+                */
+               if (!drm_dev->vblank_disable_allowed)
+                       drm_vblank_off(drm_dev, crtc);
+       }
+
        spin_unlock_irqrestore(&drm_dev->event_lock, flags);
 }
 
@@ -560,19 +631,14 @@ static irqreturn_t fimd_irq_handler(int irq, void *dev_id)
                /* VSYNC interrupt */
                writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1);
 
-       /*
-        * in case that vblank_disable_allowed is 1, it could induce
-        * the problem that manager->pipe could be -1 because with
-        * disable callback, vsync interrupt isn't disabled and at this moment,
-        * vsync interrupt could occur. the vsync interrupt would be disabled
-        * by timer handler later.
-        */
-       if (manager->pipe == -1)
-               return IRQ_HANDLED;
+       /* check the crtc is detached already from encoder */
+       if (manager->pipe < 0)
+               goto out;
 
        drm_handle_vblank(drm_dev, manager->pipe);
        fimd_finish_pageflip(drm_dev, manager->pipe);
 
+out:
        return IRQ_HANDLED;
 }
 
@@ -590,6 +656,13 @@ static int fimd_subdrv_probe(struct drm_device *drm_dev, struct device *dev)
         */
        drm_dev->irq_enabled = 1;
 
+       /*
+        * with vblank_disable_allowed = 1, vblank interrupt will be disabled
+        * by drm timer once a current process gives up ownership of
+        * vblank event.(after drm_vblank_put function is called)
+        */
+       drm_dev->vblank_disable_allowed = 1;
+
        return 0;
 }
 
@@ -662,6 +735,46 @@ static void fimd_clear_win(struct fimd_context *ctx, int win)
        writel(val, ctx->regs + SHADOWCON);
 }
 
+static int fimd_power_on(struct fimd_context *ctx, bool enable)
+{
+       struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
+       struct device *dev = subdrv->manager.dev;
+
+       DRM_DEBUG_KMS("%s\n", __FILE__);
+
+       if (enable != false && enable != true)
+               return -EINVAL;
+
+       if (enable) {
+               int ret;
+
+               ret = clk_enable(ctx->bus_clk);
+               if (ret < 0)
+                       return ret;
+
+               ret = clk_enable(ctx->lcd_clk);
+               if  (ret < 0) {
+                       clk_disable(ctx->bus_clk);
+                       return ret;
+               }
+
+               ctx->suspended = false;
+
+               /* if vblank was enabled status, enable it again. */
+               if (test_and_clear_bit(0, &ctx->irq_flags))
+                       fimd_enable_vblank(dev);
+
+               fimd_apply(dev);
+       } else {
+               clk_disable(ctx->lcd_clk);
+               clk_disable(ctx->bus_clk);
+
+               ctx->suspended = true;
+       }
+
+       return 0;
+}
+
 static int __devinit fimd_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
@@ -739,9 +852,6 @@ static int __devinit fimd_probe(struct platform_device *pdev)
 
        ctx->irq = res->start;
 
-       for (win = 0; win < WINDOWS_NR; win++)
-               fimd_clear_win(ctx, win);
-
        ret = request_irq(ctx->irq, fimd_irq_handler, 0, "drm_fimd", ctx);
        if (ret < 0) {
                dev_err(dev, "irq request failed.\n");
@@ -769,7 +879,17 @@ static int __devinit fimd_probe(struct platform_device *pdev)
        subdrv->manager.display_ops = &fimd_display_ops;
        subdrv->manager.dev = dev;
 
+       mutex_init(&ctx->lock);
+
        platform_set_drvdata(pdev, ctx);
+
+       pm_runtime_set_active(dev);
+       pm_runtime_enable(dev);
+       pm_runtime_get_sync(dev);
+
+       for (win = 0; win < WINDOWS_NR; win++)
+               fimd_clear_win(ctx, win);
+
        exynos_drm_subdrv_register(subdrv);
 
        return 0;
@@ -797,14 +917,25 @@ err_clk_get:
 
 static int __devexit fimd_remove(struct platform_device *pdev)
 {
+       struct device *dev = &pdev->dev;
        struct fimd_context *ctx = platform_get_drvdata(pdev);
 
        DRM_DEBUG_KMS("%s\n", __FILE__);
 
        exynos_drm_subdrv_unregister(&ctx->subdrv);
 
+       if (ctx->suspended)
+               goto out;
+
        clk_disable(ctx->lcd_clk);
        clk_disable(ctx->bus_clk);
+
+       pm_runtime_set_suspended(dev);
+       pm_runtime_put_sync(dev);
+
+out:
+       pm_runtime_disable(dev);
+
        clk_put(ctx->lcd_clk);
        clk_put(ctx->bus_clk);
 
@@ -818,12 +949,70 @@ static int __devexit fimd_remove(struct platform_device *pdev)
        return 0;
 }
 
+#ifdef CONFIG_PM_SLEEP
+static int fimd_suspend(struct device *dev)
+{
+       struct fimd_context *ctx = get_fimd_context(dev);
+
+       if (pm_runtime_suspended(dev))
+               return 0;
+
+       /*
+        * do not use pm_runtime_suspend(). if pm_runtime_suspend() is
+        * called here, an error would be returned by that interface
+        * because the usage_count of pm runtime is more than 1.
+        */
+       return fimd_power_on(ctx, false);
+}
+
+static int fimd_resume(struct device *dev)
+{
+       struct fimd_context *ctx = get_fimd_context(dev);
+
+       /*
+        * if entered to sleep when lcd panel was on, the usage_count
+        * of pm runtime would still be 1 so in this case, fimd driver
+        * should be on directly not drawing on pm runtime interface.
+        */
+       if (!pm_runtime_suspended(dev))
+               return fimd_power_on(ctx, true);
+
+       return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int fimd_runtime_suspend(struct device *dev)
+{
+       struct fimd_context *ctx = get_fimd_context(dev);
+
+       DRM_DEBUG_KMS("%s\n", __FILE__);
+
+       return fimd_power_on(ctx, false);
+}
+
+static int fimd_runtime_resume(struct device *dev)
+{
+       struct fimd_context *ctx = get_fimd_context(dev);
+
+       DRM_DEBUG_KMS("%s\n", __FILE__);
+
+       return fimd_power_on(ctx, true);
+}
+#endif
+
+static const struct dev_pm_ops fimd_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(fimd_suspend, fimd_resume)
+       SET_RUNTIME_PM_OPS(fimd_runtime_suspend, fimd_runtime_resume, NULL)
+};
+
 static struct platform_driver fimd_driver = {
        .probe          = fimd_probe,
        .remove         = __devexit_p(fimd_remove),
        .driver         = {
                .name   = "exynos4-fb",
                .owner  = THIS_MODULE,
+               .pm     = &fimd_pm_ops,
        },
 };