drm/exynos: fimd: support LCD I80 interface
authorYoungJun Cho <yj44.cho@samsung.com>
Thu, 17 Jul 2014 09:01:21 +0000 (18:01 +0900)
committerInki Dae <daeinki@gmail.com>
Sun, 3 Aug 2014 07:52:15 +0000 (16:52 +0900)
To support MIPI command mode based I80 interface panel,
FIMD should do followings:
- Sets LCD I80 interface timings configuration.
- Uses "lcd_sys" as an IRQ resource and sets relevant IRQ configuration.
- Sets LCD block configuration for I80 interface.
- Sets ideal(pixel) clock is 2 times faster than the original one
  to generate frame done IRQ prior to the next TE signal.
- Implements trigger feature that transfers image data if there is page
  flip request, and implements TE handler to call trigger function.

Signed-off-by: YoungJun Cho <yj44.cho@samsung.com>
Acked-by: Inki Dae <inki.dae@samsung.com>
Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Inki Dae <inki.dae@samsung.com>
drivers/gpu/drm/exynos/Kconfig
drivers/gpu/drm/exynos/exynos_drm_fimd.c
include/video/samsung_fimd.h

index 178d2a9672a8245020f39f0059f013483a5a5169..9ba1aaeb80704968d3cca028af65e05ad0565d1e 100644 (file)
@@ -28,6 +28,7 @@ config DRM_EXYNOS_FIMD
        bool "Exynos DRM FIMD"
        depends on DRM_EXYNOS && !FB_S3C
        select FB_MODE_HELPERS
+       select MFD_SYSCON
        help
          Choose this option if you want to use Exynos FIMD for DRM.
 
index 33161ad382016bac8ad5a6515432c935cd102583..28a3168e24a77eabf790f15abb718cc1bf1d9191 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 = {
@@ -82,12 +106,19 @@ static struct fimd_driver_data s3c64xx_fimd_driver_data = {
 
 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 +143,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;
@@ -243,6 +281,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 +317,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 +328,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 +399,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 +738,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 +919,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 +982,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 +993,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 +1071,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 +1093,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 +1160,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 +1175,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);
 
index b0393209679b083e14aa2adeb5077a20975c3321..eaad58b5be4aa6675842b77b31e7d6dc4217e29b 100644 (file)
@@ -19,6 +19,7 @@
 /* VIDCON0 */
 
 #define VIDCON0                                        0x00
+#define VIDCON0_DSI_EN                         (1 << 30)
 #define VIDCON0_INTERLACE                      (1 << 29)
 #define VIDCON0_VIDOUT_MASK                    (0x7 << 26)
 #define VIDCON0_VIDOUT_SHIFT                   26
 #define VIDINTCON0_INT_ENABLE                  (1 << 0)
 
 #define VIDINTCON1                             0x134
-#define VIDINTCON1_INT_I180                    (1 << 2)
+#define VIDINTCON1_INT_I80                     (1 << 2)
 #define VIDINTCON1_INT_FRAME                   (1 << 1)
 #define VIDINTCON1_INT_FIFO                    (1 << 0)