drm/exynos/fimc: avoid clearing overflow bits
[linux-2.6-block.git] / drivers / gpu / drm / exynos / exynos_drm_fimd.c
index 33161ad382016bac8ad5a6515432c935cd102583..2f896df2a7348851ff21b5d05155daef66b5f45a 100644 (file)
@@ -20,6 +20,8 @@
 #include <linux/of_device.h>
 #include <linux/pm_runtime.h>
 #include <linux/component.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
 
 #include <video/of_display_timing.h>
 #include <video/of_videomode.h>
 /* color key value register for hardware window 1 ~ 4. */
 #define WKEYCON1_BASE(x)               ((WKEYCON1 + 0x140) + ((x - 1) * 8))
 
+/* I80 / RGB trigger control register */
+#define TRIGCON                                0x1A4
+#define TRGMODE_I80_RGB_ENABLE_I80     (1 << 0)
+#define SWTRGCMD_I80_RGB_ENABLE                (1 << 1)
+
+/* display mode change control register except exynos4 */
+#define VIDOUT_CON                     0x000
+#define VIDOUT_CON_F_I80_LDI0          (0x2 << 8)
+
+/* I80 interface control for main LDI register */
+#define I80IFCONFAx(x)                 (0x1B0 + (x) * 4)
+#define I80IFCONFBx(x)                 (0x1B8 + (x) * 4)
+#define LCD_CS_SETUP(x)                        ((x) << 16)
+#define LCD_WR_SETUP(x)                        ((x) << 12)
+#define LCD_WR_ACTIVE(x)               ((x) << 8)
+#define LCD_WR_HOLD(x)                 ((x) << 4)
+#define I80IFEN_ENABLE                 (1 << 0)
+
 /* FIMD has totally five hardware windows. */
 #define WINDOWS_NR     5
 
 
 struct fimd_driver_data {
        unsigned int timing_base;
+       unsigned int lcdblk_offset;
+       unsigned int lcdblk_vt_shift;
+       unsigned int lcdblk_bypass_shift;
 
        unsigned int has_shadowcon:1;
        unsigned int has_clksel:1;
        unsigned int has_limited_fmt:1;
+       unsigned int has_vidoutcon:1;
 };
 
 static struct fimd_driver_data s3c64xx_fimd_driver_data = {
@@ -80,14 +104,29 @@ static struct fimd_driver_data s3c64xx_fimd_driver_data = {
        .has_limited_fmt = 1,
 };
 
+static struct fimd_driver_data exynos3_fimd_driver_data = {
+       .timing_base = 0x20000,
+       .lcdblk_offset = 0x210,
+       .lcdblk_bypass_shift = 1,
+       .has_shadowcon = 1,
+       .has_vidoutcon = 1,
+};
+
 static struct fimd_driver_data exynos4_fimd_driver_data = {
        .timing_base = 0x0,
+       .lcdblk_offset = 0x210,
+       .lcdblk_vt_shift = 10,
+       .lcdblk_bypass_shift = 1,
        .has_shadowcon = 1,
 };
 
 static struct fimd_driver_data exynos5_fimd_driver_data = {
        .timing_base = 0x20000,
+       .lcdblk_offset = 0x214,
+       .lcdblk_vt_shift = 24,
+       .lcdblk_bypass_shift = 15,
        .has_shadowcon = 1,
+       .has_vidoutcon = 1,
 };
 
 struct fimd_win_data {
@@ -112,15 +151,22 @@ struct fimd_context {
        struct clk                      *bus_clk;
        struct clk                      *lcd_clk;
        void __iomem                    *regs;
+       struct regmap                   *sysreg;
        struct drm_display_mode         mode;
        struct fimd_win_data            win_data[WINDOWS_NR];
        unsigned int                    default_win;
        unsigned long                   irq_flags;
+       u32                             vidcon0;
        u32                             vidcon1;
+       u32                             vidout_con;
+       u32                             i80ifcon;
+       bool                            i80_if;
        bool                            suspended;
        int                             pipe;
        wait_queue_head_t               wait_vsync_queue;
        atomic_t                        wait_vsync_event;
+       atomic_t                        win_updated;
+       atomic_t                        triggering;
 
        struct exynos_drm_panel_info panel;
        struct fimd_driver_data *driver_data;
@@ -130,12 +176,15 @@ struct fimd_context {
 static const struct of_device_id fimd_driver_dt_match[] = {
        { .compatible = "samsung,s3c6400-fimd",
          .data = &s3c64xx_fimd_driver_data },
+       { .compatible = "samsung,exynos3250-fimd",
+         .data = &exynos3_fimd_driver_data },
        { .compatible = "samsung,exynos4210-fimd",
          .data = &exynos4_fimd_driver_data },
        { .compatible = "samsung,exynos5250-fimd",
          .data = &exynos5_fimd_driver_data },
        {},
 };
+MODULE_DEVICE_TABLE(of, fimd_driver_dt_match);
 
 static inline struct fimd_driver_data *drm_fimd_get_driver_data(
        struct platform_device *pdev)
@@ -165,7 +214,6 @@ static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr)
                DRM_DEBUG_KMS("vblank wait timed out.\n");
 }
 
-
 static void fimd_clear_channel(struct exynos_drm_manager *mgr)
 {
        struct fimd_context *ctx = mgr->ctx;
@@ -175,17 +223,31 @@ static void fimd_clear_channel(struct exynos_drm_manager *mgr)
 
        /* 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);
+               u32 val = readl(ctx->regs + WINCON(win));
+
+               if (val & WINCONx_ENWIN) {
+                       /* wincon */
+                       val &= ~WINCONx_ENWIN;
+                       writel(val, ctx->regs + WINCON(win));
+
+                       /* unprotect windows */
+                       if (ctx->driver_data->has_shadowcon) {
+                               val = readl(ctx->regs + SHADOWCON);
+                               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)
+       if (ch_enabled) {
+               unsigned int state = ctx->suspended;
+
+               ctx->suspended = 0;
                fimd_wait_for_vblank(mgr);
+               ctx->suspended = state;
+       }
 }
 
 static int fimd_mgr_initialize(struct exynos_drm_manager *mgr,
@@ -243,6 +305,14 @@ static u32 fimd_calc_clkdiv(struct fimd_context *ctx,
        unsigned long ideal_clk = mode->htotal * mode->vtotal * mode->vrefresh;
        u32 clkdiv;
 
+       if (ctx->i80_if) {
+               /*
+                * The frame done interrupt should be occurred prior to the
+                * next TE signal.
+                */
+               ideal_clk *= 2;
+       }
+
        /* Find the clock divider value that gets us closest to ideal_clk */
        clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->lcd_clk), ideal_clk);
 
@@ -271,11 +341,10 @@ static void fimd_commit(struct exynos_drm_manager *mgr)
 {
        struct fimd_context *ctx = mgr->ctx;
        struct drm_display_mode *mode = &ctx->mode;
-       struct fimd_driver_data *driver_data;
-       u32 val, clkdiv, vidcon1;
-       int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd;
+       struct fimd_driver_data *driver_data = ctx->driver_data;
+       void *timing_base = ctx->regs + driver_data->timing_base;
+       u32 val, clkdiv;
 
-       driver_data = ctx->driver_data;
        if (ctx->suspended)
                return;
 
@@ -283,33 +352,65 @@ static void fimd_commit(struct exynos_drm_manager *mgr)
        if (mode->htotal == 0 || mode->vtotal == 0)
                return;
 
-       /* setup polarity values */
-       vidcon1 = ctx->vidcon1;
-       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
-               vidcon1 |= VIDCON1_INV_VSYNC;
-       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
-               vidcon1 |= VIDCON1_INV_HSYNC;
-       writel(vidcon1, ctx->regs + driver_data->timing_base + VIDCON1);
-
-       /* setup vertical timing values. */
-       vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
-       vbpd = mode->crtc_vtotal - mode->crtc_vsync_end;
-       vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay;
-
-       val = VIDTCON0_VBPD(vbpd - 1) |
-               VIDTCON0_VFPD(vfpd - 1) |
-               VIDTCON0_VSPW(vsync_len - 1);
-       writel(val, ctx->regs + driver_data->timing_base + VIDTCON0);
-
-       /* setup horizontal timing values.  */
-       hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
-       hbpd = mode->crtc_htotal - mode->crtc_hsync_end;
-       hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay;
-
-       val = VIDTCON1_HBPD(hbpd - 1) |
-               VIDTCON1_HFPD(hfpd - 1) |
-               VIDTCON1_HSPW(hsync_len - 1);
-       writel(val, ctx->regs + driver_data->timing_base + VIDTCON1);
+       if (ctx->i80_if) {
+               val = ctx->i80ifcon | I80IFEN_ENABLE;
+               writel(val, timing_base + I80IFCONFAx(0));
+
+               /* disable auto frame rate */
+               writel(0, timing_base + I80IFCONFBx(0));
+
+               /* set video type selection to I80 interface */
+               if (ctx->sysreg && regmap_update_bits(ctx->sysreg,
+                                       driver_data->lcdblk_offset,
+                                       0x3 << driver_data->lcdblk_vt_shift,
+                                       0x1 << driver_data->lcdblk_vt_shift)) {
+                       DRM_ERROR("Failed to update sysreg for I80 i/f.\n");
+                       return;
+               }
+       } else {
+               int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd;
+               u32 vidcon1;
+
+               /* setup polarity values */
+               vidcon1 = ctx->vidcon1;
+               if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+                       vidcon1 |= VIDCON1_INV_VSYNC;
+               if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+                       vidcon1 |= VIDCON1_INV_HSYNC;
+               writel(vidcon1, ctx->regs + driver_data->timing_base + VIDCON1);
+
+               /* setup vertical timing values. */
+               vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
+               vbpd = mode->crtc_vtotal - mode->crtc_vsync_end;
+               vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay;
+
+               val = VIDTCON0_VBPD(vbpd - 1) |
+                       VIDTCON0_VFPD(vfpd - 1) |
+                       VIDTCON0_VSPW(vsync_len - 1);
+               writel(val, ctx->regs + driver_data->timing_base + VIDTCON0);
+
+               /* setup horizontal timing values.  */
+               hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
+               hbpd = mode->crtc_htotal - mode->crtc_hsync_end;
+               hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay;
+
+               val = VIDTCON1_HBPD(hbpd - 1) |
+                       VIDTCON1_HFPD(hfpd - 1) |
+                       VIDTCON1_HSPW(hsync_len - 1);
+               writel(val, ctx->regs + driver_data->timing_base + VIDTCON1);
+       }
+
+       if (driver_data->has_vidoutcon)
+               writel(ctx->vidout_con, timing_base + VIDOUT_CON);
+
+       /* set bypass selection */
+       if (ctx->sysreg && regmap_update_bits(ctx->sysreg,
+                               driver_data->lcdblk_offset,
+                               0x1 << driver_data->lcdblk_bypass_shift,
+                               0x1 << driver_data->lcdblk_bypass_shift)) {
+               DRM_ERROR("Failed to update sysreg for bypass setting.\n");
+               return;
+       }
 
        /* setup horizontal and vertical display size. */
        val = VIDTCON2_LINEVAL(mode->vdisplay - 1) |
@@ -322,7 +423,8 @@ static void fimd_commit(struct exynos_drm_manager *mgr)
         * fields of register with prefix '_F' would be updated
         * at vsync(same as dma start)
         */
-       val = VIDCON0_ENVID | VIDCON0_ENVID_F;
+       val = ctx->vidcon0;
+       val |= VIDCON0_ENVID | VIDCON0_ENVID_F;
 
        if (ctx->driver_data->has_clksel)
                val |= VIDCON0_CLKSEL_LCD;
@@ -660,6 +762,9 @@ static void fimd_win_commit(struct exynos_drm_manager *mgr, int zpos)
        }
 
        win_data->enabled = true;
+
+       if (ctx->i80_if)
+               atomic_set(&ctx->win_updated, 1);
 }
 
 static void fimd_win_disable(struct exynos_drm_manager *mgr, int zpos)
@@ -838,6 +943,58 @@ static void fimd_dpms(struct exynos_drm_manager *mgr, int mode)
        }
 }
 
+static void fimd_trigger(struct device *dev)
+{
+       struct exynos_drm_manager *mgr = get_fimd_manager(dev);
+       struct fimd_context *ctx = mgr->ctx;
+       struct fimd_driver_data *driver_data = ctx->driver_data;
+       void *timing_base = ctx->regs + driver_data->timing_base;
+       u32 reg;
+
+       atomic_set(&ctx->triggering, 1);
+
+       reg = readl(ctx->regs + VIDINTCON0);
+       reg |= (VIDINTCON0_INT_ENABLE | VIDINTCON0_INT_I80IFDONE |
+                                               VIDINTCON0_INT_SYSMAINCON);
+       writel(reg, ctx->regs + VIDINTCON0);
+
+       reg = readl(timing_base + TRIGCON);
+       reg |= (TRGMODE_I80_RGB_ENABLE_I80 | SWTRGCMD_I80_RGB_ENABLE);
+       writel(reg, timing_base + TRIGCON);
+}
+
+static void fimd_te_handler(struct exynos_drm_manager *mgr)
+{
+       struct fimd_context *ctx = mgr->ctx;
+
+       /* Checks the crtc is detached already from encoder */
+       if (ctx->pipe < 0 || !ctx->drm_dev)
+               return;
+
+        /*
+        * Skips to trigger if in triggering state, because multiple triggering
+        * requests can cause panel reset.
+        */
+       if (atomic_read(&ctx->triggering))
+               return;
+
+       /*
+        * If there is a page flip request, triggers and handles the page flip
+        * event so that current fb can be updated into panel GRAM.
+        */
+       if (atomic_add_unless(&ctx->win_updated, -1, 0))
+               fimd_trigger(ctx->dev);
+
+       /* Wakes up vsync event queue */
+       if (atomic_read(&ctx->wait_vsync_event)) {
+               atomic_set(&ctx->wait_vsync_event, 0);
+               wake_up(&ctx->wait_vsync_queue);
+
+               if (!atomic_read(&ctx->triggering))
+                       drm_handle_vblank(ctx->drm_dev, ctx->pipe);
+       }
+}
+
 static struct exynos_drm_manager_ops fimd_manager_ops = {
        .dpms = fimd_dpms,
        .mode_fixup = fimd_mode_fixup,
@@ -849,6 +1006,7 @@ static struct exynos_drm_manager_ops fimd_manager_ops = {
        .win_mode_set = fimd_win_mode_set,
        .win_commit = fimd_win_commit,
        .win_disable = fimd_win_disable,
+       .te_handler = fimd_te_handler,
 };
 
 static struct exynos_drm_manager fimd_manager = {
@@ -859,26 +1017,40 @@ static struct exynos_drm_manager fimd_manager = {
 static irqreturn_t fimd_irq_handler(int irq, void *dev_id)
 {
        struct fimd_context *ctx = (struct fimd_context *)dev_id;
-       u32 val;
+       u32 val, clear_bit;
 
        val = readl(ctx->regs + VIDINTCON1);
 
-       if (val & VIDINTCON1_INT_FRAME)
-               /* VSYNC interrupt */
-               writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1);
+       clear_bit = ctx->i80_if ? VIDINTCON1_INT_I80 : VIDINTCON1_INT_FRAME;
+       if (val & clear_bit)
+               writel(clear_bit, ctx->regs + VIDINTCON1);
 
        /* check the crtc is detached already from encoder */
        if (ctx->pipe < 0 || !ctx->drm_dev)
                goto out;
 
-       drm_handle_vblank(ctx->drm_dev, ctx->pipe);
-       exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe);
+       if (ctx->i80_if) {
+               /* unset I80 frame done interrupt */
+               val = readl(ctx->regs + VIDINTCON0);
+               val &= ~(VIDINTCON0_INT_I80IFDONE | VIDINTCON0_INT_SYSMAINCON);
+               writel(val, ctx->regs + VIDINTCON0);
 
-       /* set wait vsync event to zero and wake up queue. */
-       if (atomic_read(&ctx->wait_vsync_event)) {
-               atomic_set(&ctx->wait_vsync_event, 0);
-               wake_up(&ctx->wait_vsync_queue);
+               /* exit triggering mode */
+               atomic_set(&ctx->triggering, 0);
+
+               drm_handle_vblank(ctx->drm_dev, ctx->pipe);
+               exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe);
+       } 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);
+                       wake_up(&ctx->wait_vsync_queue);
+               }
        }
+
 out:
        return IRQ_HANDLED;
 }
@@ -923,6 +1095,7 @@ static int fimd_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
        struct fimd_context *ctx;
+       struct device_node *i80_if_timings;
        struct resource *res;
        int ret = -EINVAL;
 
@@ -944,12 +1117,51 @@ static int fimd_probe(struct platform_device *pdev)
 
        ctx->dev = dev;
        ctx->suspended = true;
+       ctx->driver_data = drm_fimd_get_driver_data(pdev);
 
        if (of_property_read_bool(dev->of_node, "samsung,invert-vden"))
                ctx->vidcon1 |= VIDCON1_INV_VDEN;
        if (of_property_read_bool(dev->of_node, "samsung,invert-vclk"))
                ctx->vidcon1 |= VIDCON1_INV_VCLK;
 
+       i80_if_timings = of_get_child_by_name(dev->of_node, "i80-if-timings");
+       if (i80_if_timings) {
+               u32 val;
+
+               ctx->i80_if = true;
+
+               if (ctx->driver_data->has_vidoutcon)
+                       ctx->vidout_con |= VIDOUT_CON_F_I80_LDI0;
+               else
+                       ctx->vidcon0 |= VIDCON0_VIDOUT_I80_LDI0;
+               /*
+                * The user manual describes that this "DSI_EN" bit is required
+                * to enable I80 24-bit data interface.
+                */
+               ctx->vidcon0 |= VIDCON0_DSI_EN;
+
+               if (of_property_read_u32(i80_if_timings, "cs-setup", &val))
+                       val = 0;
+               ctx->i80ifcon = LCD_CS_SETUP(val);
+               if (of_property_read_u32(i80_if_timings, "wr-setup", &val))
+                       val = 0;
+               ctx->i80ifcon |= LCD_WR_SETUP(val);
+               if (of_property_read_u32(i80_if_timings, "wr-active", &val))
+                       val = 1;
+               ctx->i80ifcon |= LCD_WR_ACTIVE(val);
+               if (of_property_read_u32(i80_if_timings, "wr-hold", &val))
+                       val = 0;
+               ctx->i80ifcon |= LCD_WR_HOLD(val);
+       }
+       of_node_put(i80_if_timings);
+
+       ctx->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
+                                                       "samsung,sysreg");
+       if (IS_ERR(ctx->sysreg)) {
+               dev_warn(dev, "failed to get system register.\n");
+               ctx->sysreg = NULL;
+       }
+
        ctx->bus_clk = devm_clk_get(dev, "fimd");
        if (IS_ERR(ctx->bus_clk)) {
                dev_err(dev, "failed to get bus clock\n");
@@ -972,7 +1184,8 @@ static int fimd_probe(struct platform_device *pdev)
                goto err_del_component;
        }
 
-       res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "vsync");
+       res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+                                          ctx->i80_if ? "lcd_sys" : "vsync");
        if (!res) {
                dev_err(dev, "irq request failed.\n");
                ret = -ENXIO;
@@ -986,7 +1199,6 @@ static int fimd_probe(struct platform_device *pdev)
                goto err_del_component;
        }
 
-       ctx->driver_data = drm_fimd_get_driver_data(pdev);
        init_waitqueue_head(&ctx->wait_vsync_queue);
        atomic_set(&ctx->wait_vsync_event, 0);