summaryrefslogtreecommitdiff
path: root/drivers/gpu/imx/lcdif/lcdif-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/imx/lcdif/lcdif-common.c')
-rw-r--r--drivers/gpu/imx/lcdif/lcdif-common.c799
1 files changed, 799 insertions, 0 deletions
diff --git a/drivers/gpu/imx/lcdif/lcdif-common.c b/drivers/gpu/imx/lcdif/lcdif-common.c
new file mode 100644
index 000000000000..06abc666ad52
--- /dev/null
+++ b/drivers/gpu/imx/lcdif/lcdif-common.c
@@ -0,0 +1,799 @@
+/*
+ * 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/media-bus-format.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 <linux/types.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;
+ atomic_t rpm_suspended;
+
+ 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);
+
+#ifdef CONFIG_PM
+static int imx_lcdif_runtime_suspend(struct device *dev);
+static int imx_lcdif_runtime_resume(struct device *dev);
+#else
+static int imx_lcdif_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+static int imx_lcdif_runtime_resume(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+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);
+
+static uint32_t lcdif_get_bpp_from_fmt(uint32_t format)
+{
+ /* TODO: only support RGB for now */
+
+ switch (format) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ABGR1555:
+ case DRM_FORMAT_XBGR1555:
+ return 16;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_RGBX8888:
+ return 32;
+ default:
+ /* unsupported format */
+ return 0;
+ }
+}
+
+/*
+ * Get the bus format supported by LCDIF
+ * according to drm fourcc format
+ */
+int lcdif_get_bus_fmt_from_pix_fmt(struct lcdif_soc *lcdif,
+ uint32_t format)
+{
+ uint32_t bpp;
+
+ bpp = lcdif_get_bpp_from_fmt(format);
+ if (!bpp)
+ return -EINVAL;
+
+ switch (bpp) {
+ case 16:
+ return MEDIA_BUS_FMT_RGB565_1X16;
+ case 18:
+ return MEDIA_BUS_FMT_RGB666_1X18;
+ case 24:
+ case 32:
+ return MEDIA_BUS_FMT_RGB888_1X24;
+ default:
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(lcdif_get_bus_fmt_from_pix_fmt);
+
+int lcdif_set_pix_fmt(struct lcdif_soc *lcdif, u32 format)
+{
+ 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_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_WORD_LENGTH(0x0);
+
+ /* Byte packing */
+ ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf);
+
+ /* 'BGR' order */
+ if (format == DRM_FORMAT_BGR565 ||
+ format == DRM_FORMAT_ABGR1555 ||
+ format == DRM_FORMAT_XBGR1555)
+ 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_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));
+ 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_bus_fmt(struct lcdif_soc *lcdif, u32 bus_format)
+{
+ u32 bus_width;
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_16BIT);
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_18BIT);
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_24BIT);
+ break;
+ default:
+ dev_err(lcdif->dev, "unknown bus format: %#x\n", bus_format);
+ return;
+ }
+
+ writel(CTRL_SET_BUS_WIDTH(0x3), lcdif->base + LCDIF_CTRL + REG_CLR);
+ writel(bus_width, lcdif->base + LCDIF_CTRL + REG_SET);
+}
+EXPORT_SYMBOL(lcdif_set_bus_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_fb_hcrop(struct lcdif_soc *lcdif, u32 src_w,
+ u32 fb_w, bool crop)
+{
+ u32 mask_cnt, htotal, hcount;
+ u32 vdctrl2, vdctrl3, vdctrl4, transfer_count;
+ u32 pigeon_12_0, pigeon_12_1, pigeon_12_2;
+
+ if (!crop) {
+ writel(0x0, lcdif->base + HW_EPDC_PIGEON_12_0);
+ writel(0x0, lcdif->base + HW_EPDC_PIGEON_12_1);
+
+ return;
+ }
+
+ /* transfer_count's hcount, vdctrl2's htotal and vdctrl4's
+ * H_VALID_DATA_CNT should use fb width instead of hactive
+ * when requires cropping.
+ * */
+ transfer_count = readl(lcdif->base + LCDIF_TRANSFER_COUNT);
+ hcount = TRANSFER_COUNT_GET_HCOUNT(transfer_count);
+
+ transfer_count &= ~TRANSFER_COUNT_SET_HCOUNT(0xffff);
+ transfer_count |= TRANSFER_COUNT_SET_HCOUNT(fb_w);
+ writel(transfer_count, lcdif->base + LCDIF_TRANSFER_COUNT);
+
+ vdctrl2 = readl(lcdif->base + LCDIF_VDCTRL2);
+ htotal = VDCTRL2_GET_HSYNC_PERIOD(vdctrl2);
+ htotal += fb_w - hcount;
+ vdctrl2 &= ~VDCTRL2_SET_HSYNC_PERIOD(0x3ffff);
+ vdctrl2 |= VDCTRL2_SET_HSYNC_PERIOD(htotal);
+ writel(vdctrl2, lcdif->base + LCDIF_VDCTRL2);
+
+ vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4);
+ vdctrl4 &= ~SET_DOTCLK_H_VALID_DATA_CNT(0x3ffff);
+ vdctrl4 |= SET_DOTCLK_H_VALID_DATA_CNT(fb_w);
+ writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4);
+
+ /* configure related pigeon registers */
+ vdctrl3 = readl(lcdif->base + LCDIF_VDCTRL3);
+ mask_cnt = GET_HOR_WAIT_CNT(vdctrl3) - 5;
+
+ pigeon_12_0 = PIGEON_12_0_SET_STATE_MASK(0x24) |
+ PIGEON_12_0_SET_MASK_CNT(mask_cnt) |
+ PIGEON_12_0_SET_MASK_CNT_SEL(0x6) |
+ PIGEON_12_0_POL_ACTIVE_LOW |
+ PIGEON_12_0_EN;
+ writel(pigeon_12_0, lcdif->base + HW_EPDC_PIGEON_12_0);
+
+ pigeon_12_1 = PIGEON_12_1_SET_CLR_CNT(src_w) |
+ PIGEON_12_1_SET_SET_CNT(0x0);
+ writel(pigeon_12_1, lcdif->base + HW_EPDC_PIGEON_12_1);
+
+ pigeon_12_2 = 0x0;
+ writel(pigeon_12_2, lcdif->base + HW_EPDC_PIGEON_12_2);
+}
+EXPORT_SYMBOL(lcdif_set_fb_hcrop);
+
+
+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);
+
+ atomic_set(&lcdif->rpm_suspended, 0);
+ pm_runtime_enable(dev);
+ atomic_inc(&lcdif->rpm_suspended);
+
+ 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 imx_lcdif_runtime_suspend(dev);
+}
+
+static int imx_lcdif_resume(struct device *dev)
+{
+ return imx_lcdif_runtime_resume(dev);
+}
+#else
+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);
+
+ if (atomic_inc_return(&lcdif->rpm_suspended) > 1)
+ return 0;
+
+ 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);
+
+ if (unlikely(!atomic_read(&lcdif->rpm_suspended))) {
+ dev_warn(lcdif->dev, "Unbalanced %s!\n", __func__);
+ return 0;
+ }
+
+ if (!atomic_dec_and_test(&lcdif->rpm_suspended))
+ return 0;
+
+ request_bus_freq(BUS_FREQ_HIGH);
+
+ ret = lcdif_enable_clocks(lcdif);
+ if (ret) {
+ release_bus_freq(BUS_FREQ_HIGH);
+ return ret;
+ }
+
+ disp_mix_bus_rstn_reset(lcdif->gpr, false);
+ disp_mix_lcdif_clks_enable(lcdif->gpr, true);
+
+ /* Pull LCDIF out of reset */
+ writel(0x0, lcdif->base + LCDIF_CTRL);
+
+ 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");