This patch is adding support for the for the LCDIFv3 LCD controller
found on i.MX8MP and i.MX93 SoCs.

It is currently supporting DRM_FORMAT_XRGB8888 media_bus format for now.

While porting from linux mainline the following changes were made:

- limited parallel support to DRM_FORMAT_XBGR8888
- added support for MEDIA_BUS_FMT_RGB888_1X24 bus_format
- when converting yuv to rgb mode only support limited range BT.601
- also support PARA_LINE_PATTERN for BGR888 on MEDIA_BUS_FMT_RGB888_1X24
- added an initial flush on atomic_enable

This was tested on i.MX93.

Signed-off-by: Michael Grzeschik <[email protected]>
---
 drivers/video/Kconfig      |   7 +
 drivers/video/Makefile     |   1 +
 drivers/video/lcdif_drv.c  |  74 +++++++
 drivers/video/lcdif_drv.h  |  39 ++++
 drivers/video/lcdif_kms.c  | 495 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/video/lcdif_regs.h | 266 ++++++++++++++++++++++++
 6 files changed, 882 insertions(+)

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 
b2eccd5db7fe05999f3fe64db214fb569b3fb1df..739eb721253351ff0ab028aca094e5f1e4bcf487
 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -52,6 +52,13 @@ config DRIVER_VIDEO_IMX_IPU_OVERLAY
        bool "i.MX31/35 framebuffer overlay support"
        depends on DRIVER_VIDEO_IMX_IPU && (ARCH_IMX35 || ARCH_IMX31)
 
+config DRIVER_VIDEO_LCDIF
+       bool "i.MX9 framebuffer driver"
+       depends on ARCH_IMX9 || ARCH_IMX93
+       help
+         Add support for the LCDIFv3 LCD controller found on
+         i.MX8MP and i.MX93 SoCs.
+
 config DRIVER_VIDEO_STM
        bool "i.MX23/28 framebuffer driver"
        depends on ARCH_MXS
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 
470a5abaa450b6477b7cf5a78b2a747f9a3ec743..e4f3f2a88561c230ffbe2b708fa24d23420c84d5
 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_DRIVER_VIDEO_STM32_LTDC) += stm32_ltdc.o
 obj-$(CONFIG_DRIVER_VIDEO_STM32_DSI) += stm32_dsi.o
 obj-$(CONFIG_DRIVER_VIDEO_IMX) += imx.o
 obj-$(CONFIG_DRIVER_VIDEO_IMX_IPU) += imx-ipu-fb.o
+obj-$(CONFIG_DRIVER_VIDEO_LCDIF) += lcdif_drv.o lcdif_kms.o
 obj-$(CONFIG_DRIVER_VIDEO_PXA) += pxa.o
 obj-$(CONFIG_DRIVER_VIDEO_SDL) += sdl.o
 obj-$(CONFIG_DRIVER_VIDEO_BCM283X) += bcm2835.o
diff --git a/drivers/video/lcdif_drv.c b/drivers/video/lcdif_drv.c
new file mode 100644
index 
0000000000000000000000000000000000000000..c89f4c197f7d9cce6df1885743fe50ad78e57547
--- /dev/null
+++ b/drivers/video/lcdif_drv.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2022 Marek Vasut <[email protected]>
+ *
+ * This code is based on drivers/gpu/drm/mxsfb/mxsfb*
+ */
+
+#include <linux/clk.h>
+#include <dma.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <video/videomode.h>
+
+#include "lcdif_drv.h"
+#include "lcdif_regs.h"
+
+#include <fb.h>
+#include <video/vpl.h>
+
+static int lcdif_probe(struct device *dev)
+{
+       struct lcdif_drm_private *lcdif;
+       struct resource *res;
+       int ret;
+
+       lcdif = xzalloc(sizeof(*lcdif));
+       if (!lcdif)
+               return -ENOMEM;
+
+       lcdif->dev = dev;
+
+       res = dev_get_resource(dev, IORESOURCE_MEM, 0);
+       if (IS_ERR(res))
+               return PTR_ERR(res);
+
+       lcdif->base = IOMEM(res->start);
+       if (IS_ERR(lcdif->base))
+               return PTR_ERR(lcdif->base);
+
+       lcdif->clk = clk_get(lcdif->dev, "pix");
+       if (IS_ERR(lcdif->clk))
+               return PTR_ERR(lcdif->clk);
+
+       lcdif->clk_axi = clk_get(lcdif->dev, "axi");
+       if (IS_ERR(lcdif->clk_axi))
+               return PTR_ERR(lcdif->clk_axi);
+
+       lcdif->clk_disp_axi = clk_get(lcdif->dev, "disp_axi");
+       if (IS_ERR(lcdif->clk_disp_axi))
+               return PTR_ERR(lcdif->clk_disp_axi);
+
+       ret = lcdif_kms_init(lcdif);
+       if (ret < 0) {
+               dev_err(lcdif->dev, "Failed to initialize KMS pipeline\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static const struct of_device_id lcdif_dt_ids[] = {
+       { .compatible = "fsl,imx8mp-lcdif" },
+       { .compatible = "fsl,imx93-lcdif" },
+       { /* sentinel */ }
+};
+
+static struct driver lcdif_platform_driver = {
+       .probe          = lcdif_probe,
+       .name           = "imx-lcdif",
+       .of_compatible  = lcdif_dt_ids,
+};
+device_platform_driver(lcdif_platform_driver);
diff --git a/drivers/video/lcdif_drv.h b/drivers/video/lcdif_drv.h
new file mode 100644
index 
0000000000000000000000000000000000000000..991ba273ccf59b921a5c10d6ccd5d9bfe2db7504
--- /dev/null
+++ b/drivers/video/lcdif_drv.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022 Marek Vasut <[email protected]>
+ *
+ * i.MX8MP/i.MXRT LCDIFv3 LCD controller driver.
+ */
+
+#ifndef __LCDIF_DRV_H__
+#define __LCDIF_DRV_H__
+
+#include <video/vpl.h>
+
+struct clk;
+
+struct lcdif_drm_private {
+       void __iomem                    *base;  /* registers */
+
+       int                             id;
+
+       u32                             line_length;
+       u32                             max_yres;
+       int                             crtc_endpoint_id;
+       struct device_node              *port;
+       struct fb_info                  info;
+
+       dma_addr_t                      paddr;
+
+       struct clk                      *clk;
+       struct clk                      *clk_axi;
+       struct clk                      *clk_disp_axi;
+
+       struct device                   *dev;
+       struct vpl                      vpl;
+       struct fb_videomode             *mode;
+};
+
+int lcdif_kms_init(struct lcdif_drm_private *lcdif);
+
+#endif /* __LCDIF_DRV_H__ */
diff --git a/drivers/video/lcdif_kms.c b/drivers/video/lcdif_kms.c
new file mode 100644
index 
0000000000000000000000000000000000000000..033df231869a61df19f0cd60dbee0124aeec45bc
--- /dev/null
+++ b/drivers/video/lcdif_kms.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2022 Marek Vasut <[email protected]>
+ *
+ * This code is based on drivers/gpu/drm/mxsfb/mxsfb*
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <video/media-bus-format.h>
+
+#include <video/drm/drm_connector.h>
+#include <of_graph.h>
+#include <fb.h>
+#include <dma.h>
+
+#include <video/vpl.h>
+#include <video/videomode.h>
+
+#include <video/fourcc.h>
+
+#include "lcdif_drv.h"
+#include "lcdif_regs.h"
+
+struct lcdif_crtc_state {
+       u32                     bus_format;
+       u32                     bus_flags;
+};
+
+static void lcdif_set_formats(struct lcdif_drm_private *lcdif,
+                             const u32 format,
+                             const u32 bus_format)
+{
+       bool in_yuv = false;
+       bool out_yuv = false;
+
+       switch (bus_format) {
+       case MEDIA_BUS_FMT_RGB565_1X16:
+               writel(DISP_PARA_LINE_PATTERN_RGB565,
+                      lcdif->base + LCDC_V8_DISP_PARA);
+               break;
+       case MEDIA_BUS_FMT_BGR888_1X24:
+               writel(DISP_PARA_LINE_PATTERN_BGR888,
+                      lcdif->base + LCDC_V8_DISP_PARA);
+               break;
+       case MEDIA_BUS_FMT_RGB888_1X24:
+               writel(DISP_PARA_LINE_PATTERN_RGB888,
+                      lcdif->base + LCDC_V8_DISP_PARA);
+               break;
+       case MEDIA_BUS_FMT_UYVY8_1X16:
+               writel(DISP_PARA_LINE_PATTERN_UYVY_H,
+                      lcdif->base + LCDC_V8_DISP_PARA);
+               out_yuv = true;
+               break;
+       default:
+               dev_err(lcdif->dev, "Unknown media bus format 0x%x\n", 
bus_format);
+               break;
+       }
+
+       switch (format) {
+       /* RGB Formats */
+       case DRM_FORMAT_RGB565:
+               writel(CTRLDESCL0_5_BPP_16_RGB565,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               break;
+       case DRM_FORMAT_RGB888:
+               writel(CTRLDESCL0_5_BPP_24_RGB888,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               break;
+       case DRM_FORMAT_XRGB1555:
+               writel(CTRLDESCL0_5_BPP_16_ARGB1555,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               break;
+       case DRM_FORMAT_XRGB4444:
+               writel(CTRLDESCL0_5_BPP_16_ARGB4444,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               break;
+       case DRM_FORMAT_XBGR8888:
+               writel(CTRLDESCL0_5_BPP_32_ABGR8888,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               break;
+       case DRM_FORMAT_XRGB8888:
+               writel(CTRLDESCL0_5_BPP_32_ARGB8888,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               break;
+
+       /* YUV Formats */
+       case DRM_FORMAT_YUYV:
+               writel(CTRLDESCL0_5_BPP_YCbCr422 | 
CTRLDESCL0_5_YUV_FORMAT_VY2UY1,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               in_yuv = true;
+               break;
+       case DRM_FORMAT_YVYU:
+               writel(CTRLDESCL0_5_BPP_YCbCr422 | 
CTRLDESCL0_5_YUV_FORMAT_UY2VY1,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               in_yuv = true;
+               break;
+       case DRM_FORMAT_UYVY:
+               writel(CTRLDESCL0_5_BPP_YCbCr422 | 
CTRLDESCL0_5_YUV_FORMAT_Y2VY1U,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               in_yuv = true;
+               break;
+       case DRM_FORMAT_VYUY:
+               writel(CTRLDESCL0_5_BPP_YCbCr422 | 
CTRLDESCL0_5_YUV_FORMAT_Y2UY1V,
+                      lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               in_yuv = true;
+               break;
+
+       default:
+               dev_err(lcdif->dev, "Unknown pixel format 0x%x\n", format);
+               break;
+       }
+
+       /*
+        * The CSC differentiates between "YCbCr" and "YUV", but the reference
+        * manual doesn't detail how they differ. Experiments showed that the
+        * luminance value is unaffected, only the calculations involving chroma
+        * values differ. The YCbCr mode behaves as expected, with chroma values
+        * being offset by 128. The YUV mode isn't fully understood.
+        */
+       if (!in_yuv && out_yuv) {
+               /* RGB -> YCbCr */
+               writel(CSC0_CTRL_CSC_MODE_RGB2YCbCr,
+                      lcdif->base + LCDC_V8_CSC0_CTRL);
+
+               /*
+                * CSC: BT.601 Limited Range RGB to YCbCr coefficients.
+                *
+                * |Y |   | 0.2568  0.5041  0.0979|   |R|   |16 |
+                * |Cb| = |-0.1482 -0.2910  0.4392| * |G| + |128|
+                * |Cr|   | 0.4392  0.4392 -0.3678|   |B|   |128|
+                */
+               writel(CSC0_COEF0_A2(0x081) | CSC0_COEF0_A1(0x041),
+                      lcdif->base + LCDC_V8_CSC0_COEF0);
+               writel(CSC0_COEF1_B1(0x7db) | CSC0_COEF1_A3(0x019),
+                      lcdif->base + LCDC_V8_CSC0_COEF1);
+               writel(CSC0_COEF2_B3(0x070) | CSC0_COEF2_B2(0x7b6),
+                      lcdif->base + LCDC_V8_CSC0_COEF2);
+               writel(CSC0_COEF3_C2(0x7a2) | CSC0_COEF3_C1(0x070),
+                      lcdif->base + LCDC_V8_CSC0_COEF3);
+               writel(CSC0_COEF4_D1(0x010) | CSC0_COEF4_C3(0x7ee),
+                      lcdif->base + LCDC_V8_CSC0_COEF4);
+               writel(CSC0_COEF5_D3(0x080) | CSC0_COEF5_D2(0x080),
+                      lcdif->base + LCDC_V8_CSC0_COEF5);
+       } else if (in_yuv && !out_yuv) {
+               /* YCbCr -> RGB */
+               /*
+                * BT.601 limited range:
+                *
+                * |R|   |1.1644  0.0000  1.5960|   |Y  - 16 |
+                * |G| = |1.1644 -0.3917 -0.8129| * |Cb - 128|
+                * |B|   |1.1644  2.0172  0.0000|   |Cr - 128|
+                */
+               const u32 coeffs[6] = {
+                               CSC0_COEF0_A1(0x12a) | CSC0_COEF0_A2(0x000),
+                               CSC0_COEF1_A3(0x199) | CSC0_COEF1_B1(0x12a),
+                               CSC0_COEF2_B2(0x79c) | CSC0_COEF2_B3(0x730),
+                               CSC0_COEF3_C1(0x12a) | CSC0_COEF3_C2(0x204),
+                               CSC0_COEF4_C3(0x000) | CSC0_COEF4_D1(0x1f0),
+                               CSC0_COEF5_D2(0x180) | CSC0_COEF5_D3(0x180),
+                       };
+
+               writel(CSC0_CTRL_CSC_MODE_YCbCr2RGB,
+                      lcdif->base + LCDC_V8_CSC0_CTRL);
+
+               writel(coeffs[0], lcdif->base + LCDC_V8_CSC0_COEF0);
+               writel(coeffs[1], lcdif->base + LCDC_V8_CSC0_COEF1);
+               writel(coeffs[2], lcdif->base + LCDC_V8_CSC0_COEF2);
+               writel(coeffs[3], lcdif->base + LCDC_V8_CSC0_COEF3);
+               writel(coeffs[4], lcdif->base + LCDC_V8_CSC0_COEF4);
+               writel(coeffs[5], lcdif->base + LCDC_V8_CSC0_COEF5);
+       } else {
+               /* RGB -> RGB, YCbCr -> YCbCr: bypass colorspace converter. */
+               writel(CSC0_CTRL_BYPASS, lcdif->base + LCDC_V8_CSC0_CTRL);
+       }
+}
+
+static void lcdif_set_mode(struct lcdif_drm_private *lcdif,
+                          struct drm_display_mode *m,
+                          u32 bus_flags)
+{
+       u32 ctrl = 0;
+
+       if (m->flags & DRM_MODE_FLAG_NHSYNC)
+               ctrl |= CTRL_INV_HS;
+       if (m->flags & DRM_MODE_FLAG_NVSYNC)
+               ctrl |= CTRL_INV_VS;
+       if (bus_flags & DRM_BUS_FLAG_DE_LOW)
+               ctrl |= CTRL_INV_DE;
+       if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+               ctrl |= CTRL_INV_PXCK;
+
+       writel(ctrl, lcdif->base + LCDC_V8_CTRL);
+
+       writel(DISP_SIZE_DELTA_Y(m->vdisplay) |
+              DISP_SIZE_DELTA_X(m->hdisplay),
+              lcdif->base + LCDC_V8_DISP_SIZE);
+
+       writel(HSYN_PARA_BP_H(m->htotal - m->hsync_end) |
+              HSYN_PARA_FP_H(m->hsync_start - m->hdisplay),
+              lcdif->base + LCDC_V8_HSYN_PARA);
+
+       writel(VSYN_PARA_BP_V(m->vtotal - m->vsync_end) |
+              VSYN_PARA_FP_V(m->vsync_start - m->vdisplay),
+              lcdif->base + LCDC_V8_VSYN_PARA);
+
+       writel(VSYN_HSYN_WIDTH_PW_V(m->vsync_end - m->vsync_start) |
+              VSYN_HSYN_WIDTH_PW_H(m->hsync_end - m->hsync_start),
+              lcdif->base + LCDC_V8_VSYN_HSYN_WIDTH);
+
+       writel(CTRLDESCL0_1_HEIGHT(m->vdisplay) |
+              CTRLDESCL0_1_WIDTH(m->hdisplay),
+              lcdif->base + LCDC_V8_CTRLDESCL0_1);
+
+       /*
+        * Undocumented P_SIZE and T_SIZE register but those written in the
+        * downstream kernel those registers control the AXI burst size. As of
+        * now there are two known values:
+        *  1 - 128Byte
+        *  2 - 256Byte
+        * Downstream set it to 256B burst size to improve the memory
+        * efficiency so set it here too.
+        */
+       /* NOTE: Since this driver is currently fixed to DRM_FORMAT_XRGB8888
+        * we asume a stride of vdisplay * 4
+        */
+       ctrl = CTRLDESCL0_3_P_SIZE(2) | CTRLDESCL0_3_T_SIZE(2) |
+              CTRLDESCL0_3_PITCH(m->hdisplay * 4);
+       writel(ctrl, lcdif->base + LCDC_V8_CTRLDESCL0_3);
+}
+
+static void lcdif_enable_controller(struct lcdif_drm_private *lcdif)
+{
+       u32 reg;
+
+       /* Set FIFO Panic watermarks, low 1/3, high 2/3 . */
+       writel(FIELD_PREP(PANIC0_THRES_LOW_MASK, 1 * PANIC0_THRES_MAX / 3) |
+              FIELD_PREP(PANIC0_THRES_HIGH_MASK, 2 * PANIC0_THRES_MAX / 3),
+              lcdif->base + LCDC_V8_PANIC0_THRES);
+
+       /*
+        * Enable FIFO Panic, this does not generate interrupt, but
+        * boosts NoC priority based on FIFO Panic watermarks.
+        */
+       writel(INT_ENABLE_D1_PLANE_PANIC_EN,
+              lcdif->base + LCDC_V8_INT_ENABLE_D1);
+
+       reg = readl(lcdif->base + LCDC_V8_DISP_PARA);
+       reg |= DISP_PARA_DISP_ON;
+       writel(reg, lcdif->base + LCDC_V8_DISP_PARA);
+
+       reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5);
+       reg |= CTRLDESCL0_5_EN;
+       writel(reg, lcdif->base + LCDC_V8_CTRLDESCL0_5);
+}
+
+static void lcdif_disable_controller(struct lcdif_drm_private *lcdif)
+{
+       u32 reg;
+       int ret;
+
+       reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5);
+       reg &= ~CTRLDESCL0_5_EN;
+       writel(reg, lcdif->base + LCDC_V8_CTRLDESCL0_5);
+
+       ret = readl_poll_timeout(lcdif->base + LCDC_V8_CTRLDESCL0_5,
+                                reg, !(reg & CTRLDESCL0_5_EN),
+                                36000);        /* Wait ~2 frame times max */
+       if (ret)
+               dev_err(lcdif->dev, "Failed to disable controller!\n");
+
+       reg = readl(lcdif->base + LCDC_V8_DISP_PARA);
+       reg &= ~DISP_PARA_DISP_ON;
+       writel(reg, lcdif->base + LCDC_V8_DISP_PARA);
+
+       /* Disable FIFO Panic NoC priority booster. */
+       writel(0, lcdif->base + LCDC_V8_INT_ENABLE_D1);
+}
+
+static void lcdif_reset_block(struct lcdif_drm_private *lcdif)
+{
+       writel(CTRL_SW_RESET, lcdif->base + LCDC_V8_CTRL + REG_SET);
+       readl(lcdif->base + LCDC_V8_CTRL);
+       writel(CTRL_SW_RESET, lcdif->base + LCDC_V8_CTRL + REG_CLR);
+       readl(lcdif->base + LCDC_V8_CTRL);
+}
+
+static void lcdif_crtc_mode_set_nofb(struct lcdif_drm_private *lcdif,
+                                    struct drm_display_mode *m,
+                                    struct lcdif_crtc_state *lcdif_crtc_state)
+{
+       dev_dbg(lcdif->dev, "Pixel clock: %dkHz (actual: %dkHz)\n",
+                            m->clock, (int)(clk_get_rate(lcdif->clk) / 1000));
+       dev_dbg(lcdif->dev, "Bridge bus_flags: 0x%08X\n",
+                            lcdif_crtc_state->bus_flags);
+       dev_dbg(lcdif->dev, "Mode flags: 0x%08X\n", m->flags);
+
+       /* Mandatory eLCDIF reset as per the Reference Manual */
+       lcdif_reset_block(lcdif);
+
+       /* NOTE: This driver is currently fixed to DRM_FORMAT_XRGB8888 */
+       lcdif_set_formats(lcdif, DRM_FORMAT_XRGB8888, 
lcdif_crtc_state->bus_format);
+
+       lcdif_set_mode(lcdif, m, lcdif_crtc_state->bus_flags);
+}
+
+static void lcdif_crtc_atomic_flush(struct fb_info *info)
+{
+       struct lcdif_drm_private *lcdif = container_of(info, struct 
lcdif_drm_private, info);
+       u32 reg;
+
+       reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5);
+       reg |= CTRLDESCL0_5_SHADOW_LOAD_EN;
+       writel(reg, lcdif->base + LCDC_V8_CTRLDESCL0_5);
+}
+
+static void lcdif_crtc_atomic_enable(struct lcdif_drm_private *lcdif,
+                                    struct drm_display_mode *mode,
+                                    struct lcdif_crtc_state *vcstate)
+{
+       dma_addr_t paddr;
+       u32 reg;
+
+       clk_set_rate(lcdif->clk, mode->clock * 1000);
+
+       lcdif_crtc_mode_set_nofb(lcdif, mode, vcstate);
+
+       /* Write cur_buf as well to avoid an initial corrupt frame */
+       paddr = lcdif->paddr;
+       if (paddr) {
+               writel(lower_32_bits(paddr),
+                      lcdif->base + LCDC_V8_CTRLDESCL_LOW0_4);
+               writel(CTRLDESCL_HIGH0_4_ADDR_HIGH(upper_32_bits(paddr)),
+                      lcdif->base + LCDC_V8_CTRLDESCL_HIGH0_4);
+               /* initial flush of the current data */
+               reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5);
+               reg |= CTRLDESCL0_5_SHADOW_LOAD_EN;
+               writel(reg, lcdif->base + LCDC_V8_CTRLDESCL0_5);
+       }
+       lcdif_enable_controller(lcdif);
+}
+
+static void lcdif_enable_fb_controller(struct fb_info *info)
+{
+       struct lcdif_drm_private *lcdif = container_of(info, struct 
lcdif_drm_private, info);
+       struct drm_display_mode mode = {};
+       struct lcdif_crtc_state vcstate = {
+               .bus_format = 0,
+               .bus_flags = 0,
+       };
+       struct drm_display_info display_info = {};
+       int ret;
+
+       if (!info->mode) {
+               dev_err(lcdif->dev, "no modes, cannot enable\n");
+               return;
+       }
+
+       fb_videomode_to_drm_display_mode(info->mode, &mode);
+
+       ret = vpl_ioctl(&lcdif->vpl, lcdif->id, VPL_GET_BUS_FORMAT, 
&vcstate.bus_format);
+       if (ret < 0) {
+               dev_err(lcdif->dev, "Cannot determine bus format\n");
+               return;
+       }
+
+       ret = vpl_ioctl(&lcdif->vpl, lcdif->id, VPL_GET_DISPLAY_INFO, 
&display_info);
+       if (ret < 0) {
+               dev_err(lcdif->dev, "Cannot get display info\n");
+               return;
+       }
+
+       vcstate.bus_flags = display_info.bus_flags;
+
+       dev_info(lcdif->dev, "vp%d: bus_format: 0x%08x bus_flags: 0x%08x\n",
+                lcdif->id, vcstate.bus_format, display_info.bus_flags);
+
+       vpl_ioctl_prepare(&lcdif->vpl, lcdif->id, info->mode);
+
+       lcdif_crtc_atomic_enable(lcdif, &mode, &vcstate);
+
+       vpl_ioctl_enable(&lcdif->vpl, lcdif->id);
+}
+
+static void lcdif_disable_fb_controller(struct fb_info *info)
+{
+       struct lcdif_drm_private *lcdif = container_of(info, struct 
lcdif_drm_private, info);
+
+       lcdif_disable_controller(lcdif);
+}
+
+static struct fb_ops lcdif_fb_ops = {
+       .fb_enable = lcdif_enable_fb_controller,
+       .fb_disable = lcdif_disable_fb_controller,
+       .fb_flush = lcdif_crtc_atomic_flush,
+};
+
+/* 
-----------------------------------------------------------------------------
+ * Initialization
+ */
+
+static struct fb_bitfield red    = { .offset = 16, .length = 8, };
+static struct fb_bitfield green  = { .offset =  8, .length = 8, };
+static struct fb_bitfield blue   = { .offset =  0, .length = 8, };
+static struct fb_bitfield transp = { .offset = 24, .length = 8, };
+
+static int lcdif_register_fb(struct lcdif_drm_private *lcdif)
+{
+       struct fb_info *info = &lcdif->info;
+       u32 xmax = 0, ymax = 0;
+       int i, ret;
+
+       info->fbops = &lcdif_fb_ops;
+       info->bits_per_pixel = 32;
+       info->red = red;
+       info->green = green;
+       info->blue = blue;
+       info->transp = transp;
+       info->dev.parent = lcdif->dev;
+
+       ret = vpl_ioctl(&lcdif->vpl, 0, VPL_GET_VIDEOMODES, &info->modes);
+       if (ret) {
+               dev_err(lcdif->dev, "failed to get modes: %s\n", 
strerror(-ret));
+               return ret;
+       }
+
+       if (info->modes.num_modes) {
+               for (i = 0; i < info->modes.num_modes; i++) {
+                       xmax = max(xmax, info->modes.modes[i].xres);
+                       ymax = max(ymax, info->modes.modes[i].yres);
+               }
+               info->xres = info->modes.modes[info->modes.native_mode].xres;
+               info->yres = info->modes.modes[info->modes.native_mode].yres;
+       } else {
+               dev_notice(lcdif->dev, "no modes found on lcdif%d\n", 
lcdif->id);
+               xmax = info->xres = 640;
+               ymax = info->yres = 480;
+       }
+
+       lcdif->line_length = xmax * (info->bits_per_pixel >> 3);
+       lcdif->max_yres = ymax;
+
+       info->line_length = lcdif->line_length;
+       info->screen_base = dma_alloc_writecombine(DMA_DEVICE_BROKEN,
+                               info->line_length * lcdif->max_yres,
+                               &lcdif->paddr);
+
+       if (!info->screen_base)
+               return -ENOMEM;
+
+       ret = register_framebuffer(info);
+       if (ret)
+               return ret;
+
+       dev_info(lcdif->dev, "Registered %s on LCDIF%d, type primary\n",
+                info->cdev.name, lcdif->id);
+
+       return 0;
+}
+
+int lcdif_kms_init(struct lcdif_drm_private *lcdif)
+{
+       struct device *dev;
+       struct device_node *port;
+       struct device_node *ep;
+       struct of_endpoint endpoint;
+       int ret;
+
+       dev = lcdif->dev;
+
+       port = of_graph_get_port_by_id(dev->of_node, 0);
+       if (!port) {
+               dev_err(lcdif->dev, "no port node found for video_port0\n");
+               return -ENOENT;
+       }
+
+       for_each_child_of_node(port, ep) {
+               of_graph_parse_endpoint(ep, &endpoint);
+               lcdif->crtc_endpoint_id = endpoint.id;
+       }
+
+       lcdif->port = port;
+       lcdif->vpl.node = dev->of_node;
+
+       ret = vpl_register(&lcdif->vpl);
+       if (ret)
+               return ret;
+
+       lcdif_register_fb(lcdif);
+
+       return 0;
+}
diff --git a/drivers/video/lcdif_regs.h b/drivers/video/lcdif_regs.h
new file mode 100644
index 
0000000000000000000000000000000000000000..91ef697f494d580957f4ef43c0e675c4cdca92e4
--- /dev/null
+++ b/drivers/video/lcdif_regs.h
@@ -0,0 +1,266 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022 Marek Vasut <[email protected]>
+ *
+ * i.MX8MP/i.MXRT LCDIF LCD controller driver.
+ */
+
+#ifndef __LCDIF_REGS_H__
+#define __LCDIF_REGS_H__
+
+#define REG_SET        4
+#define REG_CLR        8
+
+/* V8 register set */
+#define LCDC_V8_CTRL                   0x00
+#define LCDC_V8_DISP_PARA              0x10
+#define LCDC_V8_DISP_SIZE              0x14
+#define LCDC_V8_HSYN_PARA              0x18
+#define LCDC_V8_VSYN_PARA              0x1c
+#define LCDC_V8_VSYN_HSYN_WIDTH                0x20
+#define LCDC_V8_INT_STATUS_D0          0x24
+#define LCDC_V8_INT_ENABLE_D0          0x28
+#define LCDC_V8_INT_STATUS_D1          0x30
+#define LCDC_V8_INT_ENABLE_D1          0x34
+#define LCDC_V8_CTRLDESCL0_1           0x200
+#define LCDC_V8_CTRLDESCL0_3           0x208
+#define LCDC_V8_CTRLDESCL_LOW0_4       0x20c
+#define LCDC_V8_CTRLDESCL_HIGH0_4      0x210
+#define LCDC_V8_CTRLDESCL0_5           0x214
+#define LCDC_V8_CSC0_CTRL              0x21c
+#define LCDC_V8_CSC0_COEF0             0x220
+#define LCDC_V8_CSC0_COEF1             0x224
+#define LCDC_V8_CSC0_COEF2             0x228
+#define LCDC_V8_CSC0_COEF3             0x22c
+#define LCDC_V8_CSC0_COEF4             0x230
+#define LCDC_V8_CSC0_COEF5             0x234
+#define LCDC_V8_PANIC0_THRES           0x238
+
+#define CTRL_SFTRST                    BIT(31)
+#define CTRL_CLKGATE                   BIT(30)
+#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_BUS_WIDTH_16              (0 << 10)
+#define CTRL_BUS_WIDTH_8               (1 << 10)
+#define CTRL_BUS_WIDTH_18              (2 << 10)
+#define CTRL_BUS_WIDTH_24              (3 << 10)
+#define CTRL_BUS_WIDTH_MASK            (0x3 << 10)
+#define CTRL_WORD_LENGTH_16            (0 << 8)
+#define CTRL_WORD_LENGTH_8             (1 << 8)
+#define CTRL_WORD_LENGTH_18            (2 << 8)
+#define CTRL_WORD_LENGTH_24            (3 << 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_RECOVER_ON_UNDERFLOW     BIT(24)
+#define CTRL1_FIFO_CLEAR               BIT(21)
+#define CTRL1_SET_BYTE_PACKAGING(x)    (((x) & 0xf) << 16)
+#define CTRL1_GET_BYTE_PACKAGING(x)    (((x) >> 16) & 0xf)
+#define CTRL1_CUR_FRAME_DONE_IRQ_EN    BIT(13)
+#define CTRL1_CUR_FRAME_DONE_IRQ       BIT(9)
+
+#define CTRL2_SET_OUTSTANDING_REQS_1   0
+#define CTRL2_SET_OUTSTANDING_REQS_2   (0x1 << 21)
+#define CTRL2_SET_OUTSTANDING_REQS_4   (0x2 << 21)
+#define CTRL2_SET_OUTSTANDING_REQS_8   (0x3 << 21)
+#define CTRL2_SET_OUTSTANDING_REQS_16  (0x4 << 21)
+#define CTRL2_SET_OUTSTANDING_REQS_MASK        (0x7 << 21)
+
+#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_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 DEBUG0_HSYNC                   BIT(26)
+#define DEBUG0_VSYNC                   BIT(25)
+
+#define AS_CTRL_PS_DISABLE             BIT(23)
+#define AS_CTRL_ALPHA_INVERT           BIT(20)
+#define AS_CTRL_ALPHA(a)               (((a) & 0xff) << 8)
+#define AS_CTRL_FORMAT_RGB565          (0xe << 4)
+#define AS_CTRL_FORMAT_RGB444          (0xd << 4)
+#define AS_CTRL_FORMAT_RGB555          (0xc << 4)
+#define AS_CTRL_FORMAT_ARGB4444                (0x9 << 4)
+#define AS_CTRL_FORMAT_ARGB1555                (0x8 << 4)
+#define AS_CTRL_FORMAT_RGB888          (0x4 << 4)
+#define AS_CTRL_FORMAT_ARGB8888                (0x0 << 4)
+#define AS_CTRL_ENABLE_COLORKEY                BIT(3)
+#define AS_CTRL_ALPHA_CTRL_ROP         (3 << 1)
+#define AS_CTRL_ALPHA_CTRL_MULTIPLY    (2 << 1)
+#define AS_CTRL_ALPHA_CTRL_OVERRIDE    (1 << 1)
+#define AS_CTRL_ALPHA_CTRL_EMBEDDED    (0 << 1)
+#define AS_CTRL_AS_ENABLE              BIT(0)
+
+/* V8 register set */
+#define CTRL_SW_RESET                  BIT(31)
+#define CTRL_FETCH_START_OPTION_FPV    0
+#define CTRL_FETCH_START_OPTION_PWV    BIT(8)
+#define CTRL_FETCH_START_OPTION_BPV    BIT(9)
+#define CTRL_FETCH_START_OPTION_RESV   GENMASK(9, 8)
+#define CTRL_FETCH_START_OPTION_MASK   GENMASK(9, 8)
+#define CTRL_NEG                       BIT(4)
+#define CTRL_INV_PXCK                  BIT(3)
+#define CTRL_INV_DE                    BIT(2)
+#define CTRL_INV_VS                    BIT(1)
+#define CTRL_INV_HS                    BIT(0)
+
+#define DISP_PARA_DISP_ON              BIT(31)
+#define DISP_PARA_SWAP_EN              BIT(30)
+#define DISP_PARA_LINE_PATTERN_UYVY_H  (0xd << 26)
+#define DISP_PARA_LINE_PATTERN_RGB565  (0x7 << 26)
+#define DISP_PARA_LINE_PATTERN_BGR888  (0x5 << 26)
+#define DISP_PARA_LINE_PATTERN_RGB888  (0x0 << 26)
+#define DISP_PARA_LINE_PATTERN_MASK    GENMASK(29, 26)
+#define DISP_PARA_DISP_MODE_MASK       GENMASK(25, 24)
+#define DISP_PARA_BGND_R_MASK          GENMASK(23, 16)
+#define DISP_PARA_BGND_G_MASK          GENMASK(15, 8)
+#define DISP_PARA_BGND_B_MASK          GENMASK(7, 0)
+
+#define DISP_SIZE_DELTA_Y(n)           (((n) & 0xffff) << 16)
+#define DISP_SIZE_DELTA_Y_MASK         GENMASK(31, 16)
+#define DISP_SIZE_DELTA_X(n)           ((n) & 0xffff)
+#define DISP_SIZE_DELTA_X_MASK         GENMASK(15, 0)
+
+#define HSYN_PARA_BP_H(n)              (((n) & 0xffff) << 16)
+#define HSYN_PARA_BP_H_MASK            GENMASK(31, 16)
+#define HSYN_PARA_FP_H(n)              ((n) & 0xffff)
+#define HSYN_PARA_FP_H_MASK            GENMASK(15, 0)
+
+#define VSYN_PARA_BP_V(n)              (((n) & 0xffff) << 16)
+#define VSYN_PARA_BP_V_MASK            GENMASK(31, 16)
+#define VSYN_PARA_FP_V(n)              ((n) & 0xffff)
+#define VSYN_PARA_FP_V_MASK            GENMASK(15, 0)
+
+#define VSYN_HSYN_WIDTH_PW_V(n)                (((n) & 0xffff) << 16)
+#define VSYN_HSYN_WIDTH_PW_V_MASK      GENMASK(31, 16)
+#define VSYN_HSYN_WIDTH_PW_H(n)                ((n) & 0xffff)
+#define VSYN_HSYN_WIDTH_PW_H_MASK      GENMASK(15, 0)
+
+#define INT_STATUS_D0_FIFO_EMPTY       BIT(24)
+#define INT_STATUS_D0_DMA_DONE         BIT(16)
+#define INT_STATUS_D0_DMA_ERR          BIT(8)
+#define INT_STATUS_D0_VS_BLANK         BIT(2)
+#define INT_STATUS_D0_UNDERRUN         BIT(1)
+#define INT_STATUS_D0_VSYNC            BIT(0)
+
+#define INT_ENABLE_D0_FIFO_EMPTY_EN    BIT(24)
+#define INT_ENABLE_D0_DMA_DONE_EN      BIT(16)
+#define INT_ENABLE_D0_DMA_ERR_EN       BIT(8)
+#define INT_ENABLE_D0_VS_BLANK_EN      BIT(2)
+#define INT_ENABLE_D0_UNDERRUN_EN      BIT(1)
+#define INT_ENABLE_D0_VSYNC_EN         BIT(0)
+
+#define INT_STATUS_D1_PLANE_PANIC      BIT(0)
+
+#define INT_ENABLE_D1_PLANE_PANIC_EN   BIT(0)
+
+#define CTRLDESCL0_1_HEIGHT(n)         (((n) & 0xffff) << 16)
+#define CTRLDESCL0_1_HEIGHT_MASK       GENMASK(31, 16)
+#define CTRLDESCL0_1_WIDTH(n)          ((n) & 0xffff)
+#define CTRLDESCL0_1_WIDTH_MASK                GENMASK(15, 0)
+
+#define CTRLDESCL0_3_P_SIZE(n)         (((n) << 20) & CTRLDESCL0_3_P_SIZE_MASK)
+#define CTRLDESCL0_3_P_SIZE_MASK       GENMASK(22, 20)
+#define CTRLDESCL0_3_T_SIZE(n)         (((n) << 16) & CTRLDESCL0_3_T_SIZE_MASK)
+#define CTRLDESCL0_3_T_SIZE_MASK       GENMASK(17, 16)
+#define CTRLDESCL0_3_PITCH(n)          ((n) & 0xffff)
+#define CTRLDESCL0_3_PITCH_MASK                GENMASK(15, 0)
+
+#define CTRLDESCL_HIGH0_4_ADDR_HIGH(n) ((n) & 0xf)
+#define CTRLDESCL_HIGH0_4_ADDR_HIGH_MASK       GENMASK(3, 0)
+
+#define CTRLDESCL0_5_EN                        BIT(31)
+#define CTRLDESCL0_5_SHADOW_LOAD_EN    BIT(30)
+#define CTRLDESCL0_5_BPP_16_RGB565     (0x4 << 24)
+#define CTRLDESCL0_5_BPP_16_ARGB1555   (0x5 << 24)
+#define CTRLDESCL0_5_BPP_16_ARGB4444   (0x6 << 24)
+#define CTRLDESCL0_5_BPP_YCbCr422      (0x7 << 24)
+#define CTRLDESCL0_5_BPP_24_RGB888     (0x8 << 24)
+#define CTRLDESCL0_5_BPP_32_ARGB8888   (0x9 << 24)
+#define CTRLDESCL0_5_BPP_32_ABGR8888   (0xa << 24)
+#define CTRLDESCL0_5_BPP_MASK          GENMASK(27, 24)
+#define CTRLDESCL0_5_YUV_FORMAT_Y2VY1U (0x0 << 14)
+#define CTRLDESCL0_5_YUV_FORMAT_Y2UY1V (0x1 << 14)
+#define CTRLDESCL0_5_YUV_FORMAT_VY2UY1 (0x2 << 14)
+#define CTRLDESCL0_5_YUV_FORMAT_UY2VY1 (0x3 << 14)
+#define CTRLDESCL0_5_YUV_FORMAT_MASK   GENMASK(15, 14)
+
+#define CSC0_CTRL_CSC_MODE_YUV2RGB     (0x0 << 1)
+#define CSC0_CTRL_CSC_MODE_YCbCr2RGB   (0x1 << 1)
+#define CSC0_CTRL_CSC_MODE_RGB2YUV     (0x2 << 1)
+#define CSC0_CTRL_CSC_MODE_RGB2YCbCr   (0x3 << 1)
+#define CSC0_CTRL_CSC_MODE_MASK                GENMASK(2, 1)
+#define CSC0_CTRL_BYPASS               BIT(0)
+
+#define CSC0_COEF0_A2(n)               (((n) << 16) & CSC0_COEF0_A2_MASK)
+#define CSC0_COEF0_A2_MASK             GENMASK(26, 16)
+#define CSC0_COEF0_A1(n)               ((n) & CSC0_COEF0_A1_MASK)
+#define CSC0_COEF0_A1_MASK             GENMASK(10, 0)
+
+#define CSC0_COEF1_B1(n)               (((n) << 16) & CSC0_COEF1_B1_MASK)
+#define CSC0_COEF1_B1_MASK             GENMASK(26, 16)
+#define CSC0_COEF1_A3(n)               ((n) & CSC0_COEF1_A3_MASK)
+#define CSC0_COEF1_A3_MASK             GENMASK(10, 0)
+
+#define CSC0_COEF2_B3(n)               (((n) << 16) & CSC0_COEF2_B3_MASK)
+#define CSC0_COEF2_B3_MASK             GENMASK(26, 16)
+#define CSC0_COEF2_B2(n)               ((n) & CSC0_COEF2_B2_MASK)
+#define CSC0_COEF2_B2_MASK             GENMASK(10, 0)
+
+#define CSC0_COEF3_C2(n)               (((n) << 16) & CSC0_COEF3_C2_MASK)
+#define CSC0_COEF3_C2_MASK             GENMASK(26, 16)
+#define CSC0_COEF3_C1(n)               ((n) & CSC0_COEF3_C1_MASK)
+#define CSC0_COEF3_C1_MASK             GENMASK(10, 0)
+
+#define CSC0_COEF4_D1(n)               (((n) << 16) & CSC0_COEF4_D1_MASK)
+#define CSC0_COEF4_D1_MASK             GENMASK(24, 16)
+#define CSC0_COEF4_C3(n)               ((n) & CSC0_COEF4_C3_MASK)
+#define CSC0_COEF4_C3_MASK             GENMASK(10, 0)
+
+#define CSC0_COEF5_D3(n)               (((n) << 16) & CSC0_COEF5_D3_MASK)
+#define CSC0_COEF5_D3_MASK             GENMASK(24, 16)
+#define CSC0_COEF5_D2(n)               ((n) & CSC0_COEF5_D2_MASK)
+#define CSC0_COEF5_D2_MASK             GENMASK(8, 0)
+
+#define PANIC0_THRES_LOW_MASK          GENMASK(24, 16)
+#define PANIC0_THRES_HIGH_MASK         GENMASK(8, 0)
+#define PANIC0_THRES_MAX               511
+
+#define LCDIF_MIN_XRES                 120
+#define LCDIF_MIN_YRES                 120
+#define LCDIF_MAX_XRES                 0xffff
+#define LCDIF_MAX_YRES                 0xffff
+
+#endif /* __LCDIF_REGS_H__ */

---
base-commit: 0633a4db9d2351234b565b55978ce068b387eb2d
change-id: 20251113-imx-lcdif-e7915e9f0ed0

Best regards,
-- 
Michael Grzeschik <[email protected]>



Reply via email to