summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/gpu/imx/Kconfig1
-rw-r--r--drivers/gpu/imx/Makefile1
-rw-r--r--drivers/gpu/imx/lcdif/Kconfig8
-rw-r--r--drivers/gpu/imx/lcdif/Makefile3
-rw-r--r--drivers/gpu/imx/lcdif/lcdif-common.c628
-rw-r--r--drivers/gpu/imx/lcdif/lcdif-regs.h128
-rw-r--r--include/video/imx-lcdif.h37
7 files changed, 806 insertions, 0 deletions
diff --git a/drivers/gpu/imx/Kconfig b/drivers/gpu/imx/Kconfig
index 57277de697dd..91bcf026e992 100644
--- a/drivers/gpu/imx/Kconfig
+++ b/drivers/gpu/imx/Kconfig
@@ -1 +1,2 @@
source "drivers/gpu/imx/ipu-v3/Kconfig"
+source "drivers/gpu/imx/lcdif/Kconfig"
diff --git a/drivers/gpu/imx/Makefile b/drivers/gpu/imx/Makefile
index c3cb11425cf5..981fd5db25a4 100644
--- a/drivers/gpu/imx/Makefile
+++ b/drivers/gpu/imx/Makefile
@@ -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
index 000000000000..50a214e3eb01
--- /dev/null
+++ b/drivers/gpu/imx/lcdif/Kconfig
@@ -0,0 +1,8 @@
+config IMX_LCDIF_CORE
+ tristate "i.MX LCDIF core support"
+ depends on ARCH_MXC
+ 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
index 000000000000..8c7ce5ccce95
--- /dev/null
+++ b/drivers/gpu/imx/lcdif/Makefile
@@ -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
index 000000000000..252bb17a2b62
--- /dev/null
+++ b/drivers/gpu/imx/lcdif/lcdif-common.c
@@ -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
index 000000000000..0cfec71d6af1
--- /dev/null
+++ b/drivers/gpu/imx/lcdif/lcdif-regs.h
@@ -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
index 000000000000..634a61e9e059
--- /dev/null
+++ b/include/video/imx-lcdif.h
@@ -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