MLK-18535-3 gpu: imx: add LCDIF core driver
authorFancy Fang <chen.fang@nxp.com>
Wed, 6 Jun 2018 14:26:28 +0000 (22:26 +0800)
committerLeonard Crestez <leonard.crestez@nxp.com>
Wed, 17 Apr 2019 23:51:34 +0000 (02:51 +0300)
The LCDIF core driver is responsible to provide controller
registers configuration and create the platform devices for
the child port nodes. And the platform devices later will
attach to the corresponding DRM/KMS drivers via name match.

Signed-off-by: Fancy Fang <chen.fang@nxp.com>
(cherry picked from commit ca168846dcfe6d498d33f4a977b21b25ca5c6c41)

drivers/gpu/imx/Kconfig
drivers/gpu/imx/Makefile
drivers/gpu/imx/lcdif/Kconfig [new file with mode: 0644]
drivers/gpu/imx/lcdif/Makefile [new file with mode: 0644]
drivers/gpu/imx/lcdif/lcdif-common.c [new file with mode: 0644]
drivers/gpu/imx/lcdif/lcdif-regs.h [new file with mode: 0644]
include/video/imx-lcdif.h [new file with mode: 0644]

index e8ba505..ffe0d30 100644 (file)
@@ -1 +1,2 @@
 source drivers/gpu/imx/ipu-v3/Kconfig
+source drivers/gpu/imx/lcdif/Kconfig
index c3cb114..8f2bd3c 100644 (file)
@@ -1 +1,2 @@
 obj-$(CONFIG_IMX_IPUV3_CORE)   += ipu-v3/
+obj-$(CONFIG_IMX_LCDIF_CORE)   += lcdif/
diff --git a/drivers/gpu/imx/lcdif/Kconfig b/drivers/gpu/imx/lcdif/Kconfig
new file mode 100644 (file)
index 0000000..326cebd
--- /dev/null
@@ -0,0 +1,8 @@
+config IMX_LCDIF_CORE
+       tristate "i.MX LCDIF core support"
+       depends on ARCH_FSL_IMX8MM
+       depends on RESET_CONTROLLER
+       help
+         Choose this if you have a NXP i.MX8MM platform and want to use the
+         LCDIF display controller. This option only enables LCDIF base support.
+
diff --git a/drivers/gpu/imx/lcdif/Makefile b/drivers/gpu/imx/lcdif/Makefile
new file mode 100644 (file)
index 0000000..8c7ce5c
--- /dev/null
@@ -0,0 +1,3 @@
+obj-$(CONFIG_IMX_LCDIF_CORE) += imx-lcdif-core.o
+
+imx-lcdif-core-objs := lcdif-common.o
diff --git a/drivers/gpu/imx/lcdif/lcdif-common.c b/drivers/gpu/imx/lcdif/lcdif-common.c
new file mode 100644 (file)
index 0000000..252bb17
--- /dev/null
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/busfreq-imx.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <drm/drm_fourcc.h>
+#include <video/imx-lcdif.h>
+#include <video/videomode.h>
+
+#include "lcdif-regs.h"
+
+#define DRIVER_NAME "imx-lcdif"
+
+/* TODO: add this to platform data later */
+#define DISP_MIX_SFT_RSTN_CSR          0x00
+#define DISP_MIX_CLK_EN_CSR            0x04
+
+/* 'DISP_MIX_SFT_RSTN_CSR' bit fields */
+#define BUS_RSTN_BLK_SYNC_SFT_EN       BIT(6)
+
+/* 'DISP_MIX_CLK_EN_CSR' bit fields */
+#define BUS_BLK_CLK_SFT_EN             BIT(12)
+#define LCDIF_PIXEL_CLK_SFT_EN         BIT(7)
+#define LCDIF_APB_CLK_SFT_EN           BIT(6)
+
+struct lcdif_soc {
+       struct device *dev;
+
+       int irq;
+       void __iomem *base;
+       struct regmap *gpr;
+
+       struct clk *clk_pix;
+       struct clk *clk_disp_axi;
+       struct clk *clk_disp_apb;
+};
+
+struct lcdif_soc_pdata {
+       bool hsync_invert;
+       bool vsync_invert;
+       bool de_invert;
+};
+
+struct lcdif_platform_reg {
+       struct lcdif_client_platformdata pdata;
+       char *name;
+};
+
+struct lcdif_platform_reg client_reg[] = {
+       {
+               .pdata = { },
+               .name  = "imx-lcdif-crtc",
+       },
+};
+
+struct lcdif_soc_pdata imx8mm_pdata = {
+       .hsync_invert = true,
+       .vsync_invert = true,
+       .de_invert    = true,
+};
+
+static const struct of_device_id imx_lcdif_dt_ids[] = {
+       { .compatible = "fsl,imx8mm-lcdif", .data = &imx8mm_pdata, },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, lcdif_dt_ids);
+
+void disp_mix_bus_rstn_reset(struct regmap *gpr, bool reset)
+{
+       if (!reset)
+               /* release reset */
+               regmap_update_bits(gpr, DISP_MIX_SFT_RSTN_CSR,
+                                  BUS_RSTN_BLK_SYNC_SFT_EN,
+                                  BUS_RSTN_BLK_SYNC_SFT_EN);
+       else
+               /* hold reset */
+               regmap_update_bits(gpr, DISP_MIX_SFT_RSTN_CSR,
+                                  BUS_RSTN_BLK_SYNC_SFT_EN,
+                                  0x0);
+}
+
+void disp_mix_lcdif_clks_enable(struct regmap *gpr, bool enable)
+{
+       if (enable)
+               /* enable lcdif clks */
+               regmap_update_bits(gpr, DISP_MIX_CLK_EN_CSR,
+                                  LCDIF_PIXEL_CLK_SFT_EN | LCDIF_APB_CLK_SFT_EN,
+                                  LCDIF_PIXEL_CLK_SFT_EN | LCDIF_APB_CLK_SFT_EN);
+       else
+               /* disable lcdif clks */
+               regmap_update_bits(gpr, DISP_MIX_CLK_EN_CSR,
+                                  LCDIF_PIXEL_CLK_SFT_EN | LCDIF_APB_CLK_SFT_EN,
+                                  0x0);
+}
+
+static int lcdif_enable_clocks(struct lcdif_soc *lcdif)
+{
+       int ret;
+
+       if (lcdif->clk_disp_axi) {
+               ret = clk_prepare_enable(lcdif->clk_disp_axi);
+               if (ret)
+                       return ret;
+       }
+
+       if (lcdif->clk_disp_apb) {
+               ret = clk_prepare_enable(lcdif->clk_disp_apb);
+               if (ret)
+                       goto disable_disp_axi;
+       }
+
+       ret = clk_prepare_enable(lcdif->clk_pix);
+       if (ret)
+               goto disable_disp_apb;
+
+       return 0;
+
+disable_disp_apb:
+       if (lcdif->clk_disp_apb)
+               clk_disable_unprepare(lcdif->clk_disp_apb);
+disable_disp_axi:
+       if (lcdif->clk_disp_axi)
+               clk_disable_unprepare(lcdif->clk_disp_axi);
+
+       return ret;
+}
+
+static void lcdif_disable_clocks(struct lcdif_soc *lcdif)
+{
+       clk_disable_unprepare(lcdif->clk_pix);
+
+       if (lcdif->clk_disp_axi)
+               clk_disable_unprepare(lcdif->clk_disp_axi);
+
+       if (lcdif->clk_disp_apb)
+               clk_disable_unprepare(lcdif->clk_disp_apb);
+}
+
+int lcdif_vblank_irq_get(struct lcdif_soc *lcdif)
+{
+       return lcdif->irq;
+}
+EXPORT_SYMBOL(lcdif_vblank_irq_get);
+
+void lcdif_dump_registers(struct lcdif_soc *lcdif)
+{
+       pr_info("%#x    : %#x\n", LCDIF_CTRL,
+                                 readl(lcdif->base + LCDIF_CTRL));
+       pr_info("%#x    : %#x\n", LCDIF_CTRL1,
+                                 readl(lcdif->base + LCDIF_CTRL1));
+       pr_info("%#x    : %#x\n", LCDIF_CTRL2,
+                                 readl(lcdif->base + LCDIF_CTRL2));
+       pr_info("%#x    : %#x\n", LCDIF_TRANSFER_COUNT,
+                                 readl(lcdif->base + LCDIF_TRANSFER_COUNT));
+       pr_info("%#x    : %#x\n", LCDIF_CUR_BUF,
+                                 readl(lcdif->base + LCDIF_CUR_BUF));
+       pr_info("%#x    : %#x\n", LCDIF_NEXT_BUF,
+                                 readl(lcdif->base + LCDIF_NEXT_BUF));
+       pr_info("%#x    : %#x\n", LCDIF_VDCTRL0,
+                                 readl(lcdif->base + LCDIF_VDCTRL0));
+       pr_info("%#x    : %#x\n", LCDIF_VDCTRL1,
+                                 readl(lcdif->base + LCDIF_VDCTRL1));
+       pr_info("%#x    : %#x\n", LCDIF_VDCTRL2,
+                                 readl(lcdif->base + LCDIF_VDCTRL2));
+       pr_info("%#x    : %#x\n", LCDIF_VDCTRL3,
+                                 readl(lcdif->base + LCDIF_VDCTRL3));
+       pr_info("%#x    : %#x\n", LCDIF_VDCTRL4,
+                                 readl(lcdif->base + LCDIF_VDCTRL4));
+}
+EXPORT_SYMBOL(lcdif_dump_registers);
+
+void lcdif_vblank_irq_enable(struct lcdif_soc *lcdif)
+{
+       writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+       writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, lcdif->base + LCDIF_CTRL1 + REG_SET);
+}
+EXPORT_SYMBOL(lcdif_vblank_irq_enable);
+
+void lcdif_vblank_irq_disable(struct lcdif_soc *lcdif)
+{
+       writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+       writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+}
+EXPORT_SYMBOL(lcdif_vblank_irq_disable);
+
+void lcdif_vblank_irq_clear(struct lcdif_soc *lcdif)
+{
+       writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+}
+EXPORT_SYMBOL(lcdif_vblank_irq_clear);
+
+int lcdif_set_pix_fmt(struct lcdif_soc *lcdif, u32 format)
+{
+       struct drm_format_name_buf format_name;
+       u32 ctrl = 0, ctrl1 = 0;
+
+       /* TODO: lcdif should be disabled to set pixel format */
+
+       ctrl  = readl(lcdif->base + LCDIF_CTRL);
+       ctrl1 = readl(lcdif->base + LCDIF_CTRL1);
+
+       /* clear pixel format related bits */
+       ctrl  &= ~(CTRL_SHIFT_NUM(0x3f)  | CTRL_INPUT_SWIZZLE(0x3) |
+                  CTRL_CSC_SWIZZLE(0x3) | CTRL_SET_BUS_WIDTH(0x3) |
+                  CTRL_SET_WORD_LENGTH(0x3));
+
+       ctrl1 &= ~CTRL1_SET_BYTE_PACKAGING(0xf);
+
+       /* default is 'RGB' order */
+       writel(CTRL2_ODD_LINE_PATTERN(0x7) |
+              CTRL2_EVEN_LINE_PATTERN(0x7),
+              lcdif->base + LCDIF_CTRL2 + REG_CLR);
+
+       switch (format) {
+               /* bpp 16 */
+       case DRM_FORMAT_RGB565:
+       case DRM_FORMAT_BGR565:
+       case DRM_FORMAT_ARGB1555:
+       case DRM_FORMAT_XRGB1555:
+       case DRM_FORMAT_ABGR1555:
+       case DRM_FORMAT_XBGR1555:
+               /* Data format */
+               ctrl = (format == DRM_FORMAT_RGB565 ||
+                       format == DRM_FORMAT_BGR565) ?
+                       (ctrl & ~CTRL_DF16) : (ctrl | CTRL_DF16);
+
+               ctrl |= CTRL_SET_BUS_WIDTH(0x0);
+               ctrl |= CTRL_SET_WORD_LENGTH(0x0);
+
+               /* Byte packing */
+               ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf);
+
+               /* 'BGR' order */
+               if (format == DRM_FORMAT_BGR565)
+                       writel(CTRL2_ODD_LINE_PATTERN(0x5) |
+                              CTRL2_EVEN_LINE_PATTERN(0x5),
+                              lcdif->base + LCDIF_CTRL2 + REG_SET);
+               break;
+               /* bpp 32 */
+       case DRM_FORMAT_ARGB8888:
+       case DRM_FORMAT_XRGB8888:
+       case DRM_FORMAT_ABGR8888:
+       case DRM_FORMAT_XBGR8888:
+       case DRM_FORMAT_RGBA8888:
+       case DRM_FORMAT_RGBX8888:
+               /*Data format */
+               ctrl &= ~CTRL_DF24;
+               ctrl |= CTRL_SET_BUS_WIDTH(3);
+               ctrl |= CTRL_SET_WORD_LENGTH(3);
+
+               if (format == DRM_FORMAT_RGBA8888 ||
+                   format == DRM_FORMAT_RGBX8888)
+                       ctrl |= CTRL_SHIFT_DIR(1) | CTRL_SHIFT_NUM(8);
+
+               /* Byte packing */
+               ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7);
+
+               /* 'BGR' order */
+               if (format == DRM_FORMAT_ABGR8888 ||
+                   format == DRM_FORMAT_XBGR8888)
+                       writel(CTRL2_ODD_LINE_PATTERN(0x5) |
+                              CTRL2_EVEN_LINE_PATTERN(0x5),
+                              lcdif->base + LCDIF_CTRL2 + REG_SET);
+               break;
+       default:
+               dev_err(lcdif->dev, "unsupported pixel format: %s\n",
+                       drm_get_format_name(format, &format_name));
+               return -EINVAL;
+       }
+
+       writel(ctrl,  lcdif->base + LCDIF_CTRL);
+       writel(ctrl1, lcdif->base + LCDIF_CTRL1);
+
+       return 0;
+}
+EXPORT_SYMBOL(lcdif_set_pix_fmt);
+
+void lcdif_set_fb_addr(struct lcdif_soc *lcdif, int id, u32 addr)
+{
+       switch (id) {
+       case 0:
+               /* primary plane */
+               writel(addr, lcdif->base + LCDIF_NEXT_BUF);
+               break;
+       default:
+               /* TODO: add overlay support */
+               return;
+       }
+}
+EXPORT_SYMBOL(lcdif_set_fb_addr);
+
+void lcdif_set_mode(struct lcdif_soc *lcdif, struct videomode *vmode)
+{
+       const struct of_device_id *of_id =
+                       of_match_device(imx_lcdif_dt_ids, lcdif->dev);
+       const struct lcdif_soc_pdata *soc_pdata = of_id->data;
+       u32 vdctrl0, vdctrl1, vdctrl2, vdctrl3, vdctrl4, htotal;
+
+       /* Clear the FIFO */
+       writel(CTRL1_FIFO_CLEAR, lcdif->base + LCDIF_CTRL1 + REG_SET);
+       writel(CTRL1_FIFO_CLEAR, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+
+       /* set pixel clock rate */
+       clk_disable_unprepare(lcdif->clk_pix);
+       clk_set_rate(lcdif->clk_pix, vmode->pixelclock);
+       clk_prepare_enable(lcdif->clk_pix);
+
+       /* config display timings */
+       writel(TRANSFER_COUNT_SET_VCOUNT(vmode->vactive) |
+              TRANSFER_COUNT_SET_HCOUNT(vmode->hactive),
+              lcdif->base + LCDIF_TRANSFER_COUNT);
+
+       vdctrl0 = VDCTRL0_ENABLE_PRESENT                |
+                 VDCTRL0_VSYNC_PERIOD_UNIT             |
+                 VDCTRL0_VSYNC_PULSE_WIDTH_UNIT        |
+                 VDCTRL0_SET_VSYNC_PULSE_WIDTH(vmode->vsync_len);
+
+       /* Polarities */
+       if (soc_pdata) {
+               if ((soc_pdata->hsync_invert &&
+                    vmode->flags & DISPLAY_FLAGS_HSYNC_LOW) ||
+                   (!soc_pdata->hsync_invert &&
+                    vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH))
+                               vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH;
+
+               if ((soc_pdata->vsync_invert &&
+                    vmode->flags & DISPLAY_FLAGS_VSYNC_LOW) ||
+                   (!soc_pdata->vsync_invert &&
+                    vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH))
+                               vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH;
+
+               if ((soc_pdata->de_invert &&
+                    vmode->flags & DISPLAY_FLAGS_DE_LOW) ||
+                   (!soc_pdata->de_invert &&
+                    vmode->flags & DISPLAY_FLAGS_DE_HIGH))
+                               vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH;
+       } else {
+               if (vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH)
+                       vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH;
+               if (vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH)
+                       vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH;
+               if (vmode->flags & DISPLAY_FLAGS_DE_HIGH)
+                       vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH;
+       }
+
+       if (vmode->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
+               vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING;
+
+       writel(vdctrl0, lcdif->base + LCDIF_VDCTRL0);
+
+       vdctrl1 = vmode->vactive + vmode->vsync_len +
+                 vmode->vfront_porch + vmode->vback_porch;
+       writel(vdctrl1, lcdif->base + LCDIF_VDCTRL1);
+
+       htotal = vmode->hactive + vmode->hsync_len +
+                vmode->hfront_porch + vmode->hback_porch;
+       vdctrl2 = VDCTRL2_SET_HSYNC_PULSE_WIDTH(vmode->hsync_len) |
+                 VDCTRL2_SET_HSYNC_PERIOD(htotal);
+       writel(vdctrl2, lcdif->base + LCDIF_VDCTRL2);
+
+       vdctrl3 = SET_HOR_WAIT_CNT(vmode->hsync_len + vmode->hback_porch) |
+                 SET_VERT_WAIT_CNT(vmode->vsync_len + vmode->vback_porch);
+       writel(vdctrl3, lcdif->base + LCDIF_VDCTRL3);
+
+       vdctrl4 = SET_DOTCLK_H_VALID_DATA_CNT(vmode->hactive);
+       writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4);
+}
+EXPORT_SYMBOL(lcdif_set_mode);
+
+void lcdif_enable_controller(struct lcdif_soc *lcdif)
+{
+       u32 ctrl2, vdctrl4;
+
+       ctrl2   = readl(lcdif->base + LCDIF_CTRL2);
+       vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4);
+
+       ctrl2 &= ~CTRL2_OUTSTANDING_REQS(0x7);
+       ctrl2 |= CTRL2_OUTSTANDING_REQS(REQ_16);
+       writel(ctrl2, lcdif->base + LCDIF_CTRL2);
+
+       /* Continous dotclock mode */
+       writel(CTRL_BYPASS_COUNT | CTRL_DOTCLK_MODE,
+              lcdif->base + LCDIF_CTRL + REG_SET);
+
+       /* enable the SYNC signals first, then the DMA engine */
+       vdctrl4 |= VDCTRL4_SYNC_SIGNALS_ON;
+       writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4);
+
+       /* enable underflow recovery */
+       writel(CTRL1_RECOVERY_ON_UNDERFLOW,
+              lcdif->base + LCDIF_CTRL1 + REG_SET);
+
+       /* run lcdif */
+       writel(CTRL_MASTER, lcdif->base + LCDIF_CTRL + REG_SET);
+       writel(CTRL_RUN, lcdif->base + LCDIF_CTRL + REG_SET);
+}
+EXPORT_SYMBOL(lcdif_enable_controller);
+
+void lcdif_disable_controller(struct lcdif_soc *lcdif)
+{
+       int ret;
+       u32 ctrl, vdctrl4;
+
+       writel(CTRL_RUN, lcdif->base + LCDIF_CTRL + REG_CLR);
+       writel(CTRL_DOTCLK_MODE, lcdif->base + LCDIF_CTRL + REG_CLR);
+
+       ret = readl_poll_timeout(lcdif->base + LCDIF_CTRL, ctrl,
+                                !(ctrl & CTRL_RUN), 0, 1000);
+       if (WARN_ON(ret))
+               dev_err(lcdif->dev, "disable lcdif run timeout\n");
+
+       writel(CTRL_MASTER, lcdif->base + LCDIF_CTRL + REG_CLR);
+
+       vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4);
+       vdctrl4 &= ~VDCTRL4_SYNC_SIGNALS_ON;
+       writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4);
+}
+EXPORT_SYMBOL(lcdif_disable_controller);
+
+static int platform_remove_device_fn(struct device *dev, void *data)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+
+       platform_device_unregister(pdev);
+
+       return 0;
+}
+
+static void platform_device_unregister_children(struct platform_device *pdev)
+{
+       device_for_each_child(&pdev->dev, NULL, platform_remove_device_fn);
+}
+
+static int lcdif_add_client_devices(struct lcdif_soc *lcdif)
+{
+       int ret = 0, i;
+       struct device *dev = lcdif->dev;
+       struct platform_device *pdev = NULL;
+       struct device_node *of_node;
+
+       for (i = 0; i < ARRAY_SIZE(client_reg); i++) {
+               of_node = of_graph_get_port_by_id(dev->of_node, i);
+               if (!of_node) {
+                       dev_info(dev, "no port@%d node in %s\n",
+                                i, dev->of_node->full_name);
+                       continue;
+               }
+               of_node_put(of_node);
+
+               pdev = platform_device_alloc(client_reg[i].name, i);
+               if (!pdev) {
+                       dev_err(dev, "Can't allocate port pdev\n");
+                       ret = -ENOMEM;
+                       goto err_register;
+               }
+
+               pdev->dev.parent = dev;
+               client_reg[i].pdata.of_node = of_node;
+
+               ret = platform_device_add_data(pdev, &client_reg[i].pdata,
+                                              sizeof(client_reg[i].pdata));
+               if (!ret)
+                       ret = platform_device_add(pdev);
+               if (ret) {
+                       platform_device_put(pdev);
+                       goto err_register;
+               }
+
+               pdev->dev.of_node = of_node;
+       }
+
+       if (!pdev)
+               return -ENODEV;
+
+       return 0;
+
+err_register:
+       platform_device_unregister_children(to_platform_device(dev));
+       return ret;
+}
+
+static int imx_lcdif_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+       struct lcdif_soc *lcdif;
+       struct resource *res;
+
+       dev_dbg(dev, "%s: probe begin\n", __func__);
+
+       lcdif = devm_kzalloc(dev, sizeof(*lcdif), GFP_KERNEL);
+       if (!lcdif) {
+               dev_err(dev, "Can't allocate 'lcdif_soc' structure\n");
+               return -ENOMEM;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res)
+               return -ENODEV;
+
+       lcdif->irq = platform_get_irq(pdev, 0);
+       if (lcdif->irq < 0)
+               return -ENODEV;
+
+       lcdif->clk_pix = devm_clk_get(dev, "pix");
+       if (IS_ERR(lcdif->clk_pix))
+               return PTR_ERR(lcdif->clk_pix);
+
+       lcdif->clk_disp_axi = devm_clk_get(dev, "disp-axi");
+       if (IS_ERR(lcdif->clk_disp_axi))
+               lcdif->clk_disp_axi = NULL;
+
+       lcdif->clk_disp_apb = devm_clk_get(dev, "disp-apb");
+       if (IS_ERR(lcdif->clk_disp_apb))
+               lcdif->clk_disp_apb = NULL;
+
+       lcdif->base = devm_ioremap_resource(dev, res);
+       if (IS_ERR(lcdif->base))
+               return PTR_ERR(lcdif->base);
+
+       lcdif->gpr = syscon_regmap_lookup_by_phandle(np, "lcdif-gpr");
+       if (IS_ERR(lcdif->gpr))
+               return PTR_ERR(lcdif->gpr);
+
+       lcdif->dev = dev;
+       platform_set_drvdata(pdev, lcdif);
+
+       pm_runtime_enable(dev);
+
+       disp_mix_bus_rstn_reset(lcdif->gpr, false);
+       disp_mix_lcdif_clks_enable(lcdif->gpr, true);
+
+       /* Pull LCDIF out of reset */
+       pm_runtime_get_sync(dev);
+       writel(0x0, lcdif->base + LCDIF_CTRL);
+       pm_runtime_put(dev);
+
+       dev_dbg(dev, "%s: probe end\n", __func__);
+
+       return lcdif_add_client_devices(lcdif);
+}
+
+static int imx_lcdif_remove(struct platform_device *pdev)
+{
+       pm_runtime_disable(&pdev->dev);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx_lcdif_suspend(struct device *dev)
+{
+       return 0;
+}
+
+static int imx_lcdif_resume(struct device *dev)
+{
+       return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int imx_lcdif_runtime_suspend(struct device *dev)
+{
+       struct lcdif_soc *lcdif = dev_get_drvdata(dev);
+
+       lcdif_disable_clocks(lcdif);
+
+       release_bus_freq(BUS_FREQ_HIGH);
+
+       return 0;
+}
+
+static int imx_lcdif_runtime_resume(struct device *dev)
+{
+       int ret = 0;
+       struct lcdif_soc *lcdif = dev_get_drvdata(dev);
+
+       request_bus_freq(BUS_FREQ_HIGH);
+
+       ret = lcdif_enable_clocks(lcdif);
+       if (ret)
+               release_bus_freq(BUS_FREQ_HIGH);
+
+       return ret;
+}
+#endif
+
+static const struct dev_pm_ops imx_lcdif_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(imx_lcdif_suspend, imx_lcdif_resume)
+       SET_RUNTIME_PM_OPS(imx_lcdif_runtime_suspend,
+                          imx_lcdif_runtime_resume, NULL)
+};
+
+struct platform_driver imx_lcdif_driver = {
+       .probe    = imx_lcdif_probe,
+       .remove   = imx_lcdif_remove,
+       .driver   = {
+               .name = DRIVER_NAME,
+               .of_match_table = imx_lcdif_dt_ids,
+               .pm = &imx_lcdif_pm_ops,
+       },
+};
+
+module_platform_driver(imx_lcdif_driver);
+
+MODULE_DESCRIPTION("NXP i.MX LCDIF Display Controller driver");
+MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/imx/lcdif/lcdif-regs.h b/drivers/gpu/imx/lcdif/lcdif-regs.h
new file mode 100644 (file)
index 0000000..0cfec71
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LCDIF_REGS_H
+#define __LCDIF_REGS_H
+
+#define REG_SET        4
+#define REG_CLR        8
+
+/* regs offset */
+#define LCDIF_CTRL                     0x00
+#define LCDIF_CTRL1                    0X10
+#define LCDIF_CTRL2                    0X20
+#define LCDIF_TRANSFER_COUNT           0x30
+#define LCDIF_CUR_BUF                  0x40
+#define LCDIF_NEXT_BUF                 0x50
+#define LCDIF_TIMING                   0x60
+#define LCDIF_VDCTRL0                  0x70
+#define LCDIF_VDCTRL1                  0x80
+#define LCDIF_VDCTRL2                  0x90
+#define LCDIF_VDCTRL3                  0xa0
+#define LCDIF_VDCTRL4                  0xb0
+
+/* reg bit manipulation */
+#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s))
+#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s))
+#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s))
+
+#define SWIZZLE_LE             0 /* Little-Endian or No swap */
+#define SWIZZLE_BE             1 /* Big-Endian or swap all */
+#define SWIZZLE_HWD            2 /* Swap half-words */
+#define SWIZZLE_HWD_BYTE       3 /* Swap bytes within each half-word */
+
+/* regs bit fields */
+#define CTRL_SFTRST                    BIT(31)
+#define CTRL_CLKGATE                   BIT(30)
+#define CTRL_SHIFT_DIR(x)              REG_PUT((x), 26, 26)
+#define CTRL_SHIFT_NUM(x)              REG_PUT((x), 25, 21)
+#define CTRL_BYPASS_COUNT              BIT(19)
+#define CTRL_VSYNC_MODE                        BIT(18)
+#define CTRL_DOTCLK_MODE               BIT(17)
+#define CTRL_DATA_SELECT               BIT(16)
+#define CTRL_INPUT_SWIZZLE(x)          REG_PUT((x), 15, 14)
+#define CTRL_CSC_SWIZZLE(x)            REG_PUT((x), 13, 12)
+#define CTRL_SET_BUS_WIDTH(x)          REG_PUT((x), 11, 10)
+#define CTRL_GET_BUS_WIDTH(x)          REG_GET((x), 11, 10)
+#define CTRL_BUS_WIDTH_MASK            REG_PUT((0x3), 11, 10)
+#define CTRL_SET_WORD_LENGTH(x)                REG_PUT((x), 9, 8)
+#define CTRL_GET_WORD_LENGTH(x)                REG_GET((x), 9, 8)
+#define CTRL_MASTER                    BIT(5)
+#define CTRL_DF16                      BIT(3)
+#define CTRL_DF18                      BIT(2)
+#define CTRL_DF24                      BIT(1)
+#define CTRL_RUN                       BIT(0)
+
+#define CTRL1_RECOVERY_ON_UNDERFLOW    BIT(24)
+#define CTRL1_FIFO_CLEAR               BIT(21)
+#define CTRL1_SET_BYTE_PACKAGING(x)    REG_PUT((x), 19, 16)
+#define CTRL1_GET_BYTE_PACKAGING(x)    REG_GET((x), 19, 16)
+#define CTRL1_CUR_FRAME_DONE_IRQ_EN    BIT(13)
+#define CTRL1_CUR_FRAME_DONE_IRQ       BIT(9)
+
+#define REQ_1  0
+#define REQ_2  1
+#define REQ_4  2
+#define REQ_8  3
+#define REQ_16 4
+
+#define CTRL2_OUTSTANDING_REQS(x)      REG_PUT((x), 23, 21)
+#define CTRL2_ODD_LINE_PATTERN(x)      REG_PUT((x), 18, 16)
+#define CTRL2_EVEN_LINE_PATTERN(x)     REG_PUT((x), 14, 12)
+
+#define TRANSFER_COUNT_SET_VCOUNT(x)   (((x) & 0xffff) << 16)
+#define TRANSFER_COUNT_GET_VCOUNT(x)   (((x) >> 16) & 0xffff)
+#define TRANSFER_COUNT_SET_HCOUNT(x)   ((x) & 0xffff)
+#define TRANSFER_COUNT_GET_HCOUNT(x)   ((x) & 0xffff)
+
+#define VDCTRL0_ENABLE_PRESENT         BIT(28)
+#define VDCTRL0_VSYNC_ACT_HIGH         BIT(27)
+#define VDCTRL0_HSYNC_ACT_HIGH         BIT(26)
+#define VDCTRL0_DOTCLK_ACT_FALLING     BIT(25)
+#define VDCTRL0_ENABLE_ACT_HIGH                BIT(24)
+#define VDCTRL0_VSYNC_PERIOD_UNIT      BIT(21)
+#define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT BIT(20)
+#define VDCTRL0_HALF_LINE              BIT(19)
+#define VDCTRL0_HALF_LINE_MODE         BIT(18)
+#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff)
+#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff)
+
+#define VDCTRL2_SET_HSYNC_PULSE_WIDTH(x) (((x) & 0x3fff) << 18)
+#define VDCTRL2_GET_HSYNC_PULSE_WIDTH(x) (((x) >> 18) & 0x3fff)
+#define VDCTRL2_SET_HSYNC_PERIOD(x)    ((x) & 0x3ffff)
+#define VDCTRL2_GET_HSYNC_PERIOD(x)    ((x) & 0x3ffff)
+
+#define VDCTRL3_MUX_SYNC_SIGNALS       BIT(29)
+#define VDCTRL3_VSYNC_ONLY             BIT(28)
+#define SET_HOR_WAIT_CNT(x)            (((x) & 0xfff) << 16)
+#define GET_HOR_WAIT_CNT(x)            (((x) >> 16) & 0xfff)
+#define SET_VERT_WAIT_CNT(x)           ((x) & 0xffff)
+#define GET_VERT_WAIT_CNT(x)           ((x) & 0xffff)
+
+#define VDCTRL4_SET_DOTCLK_DLY(x)      (((x) & 0x7) << 29) /* v4 only */
+#define VDCTRL4_GET_DOTCLK_DLY(x)      (((x) >> 29) & 0x7) /* v4 only */
+#define VDCTRL4_SYNC_SIGNALS_ON                BIT(18)
+#define SET_DOTCLK_H_VALID_DATA_CNT(x) ((x) & 0x3ffff)
+
+#define STMLCDIF_8BIT  1 /* pixel data bus to the display is of 8 bit width */
+#define STMLCDIF_16BIT 0 /* pixel data bus to the display is of 16 bit width */
+#define STMLCDIF_18BIT 2 /* pixel data bus to the display is of 18 bit width */
+#define STMLCDIF_24BIT 3 /* pixel data bus to the display is of 24 bit width */
+
+#define MIN_XRES                       120
+#define MIN_YRES                       120
+#define MAX_XRES                       0xffff
+#define MAX_YRES                       0xffff
+
+#endif
diff --git a/include/video/imx-lcdif.h b/include/video/imx-lcdif.h
new file mode 100644 (file)
index 0000000..634a61e
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __IMX_LCDIF_H__
+#define __IMX_LCDIF_H__
+
+struct lcdif_soc;
+struct videomode;
+
+struct lcdif_client_platformdata {
+       struct device_node *of_node;
+};
+
+int  lcdif_vblank_irq_get(struct lcdif_soc *lcdif);
+void lcdif_vblank_irq_enable(struct lcdif_soc *lcdif);
+void lcdif_vblank_irq_disable(struct lcdif_soc *lcdif);
+void lcdif_vblank_irq_clear(struct lcdif_soc *lcdif);
+
+int  lcdif_set_pix_fmt(struct lcdif_soc *lcdif, u32 format);
+void lcdif_set_fb_addr(struct lcdif_soc *lcdif, int id, u32 addr);
+void lcdif_set_mode(struct lcdif_soc *lcdif, struct videomode *vmode);
+void lcdif_enable_controller(struct lcdif_soc *lcdif);
+void lcdif_disable_controller(struct lcdif_soc *lcdif);
+void lcdif_dump_registers(struct lcdif_soc *lcdif);
+
+#endif