i.MX8qxp Display Controller display engine consists of all processing
units that operate in a display clock domain.  Add minimal feature
support with FrameGen and TCon so that the engine can output display
timings.  The display engine driver as a master binds FrameGen and
TCon drivers as components.  While at it, the display engine driver
is a component to be bound with the upcoming DRM driver.

Signed-off-by: Liu Ying <victor....@nxp.com>
---
v2:
* Use OF alias id to get instance id.
* Add dev member to struct dc_tc.

 drivers/gpu/drm/imx/Kconfig     |   1 +
 drivers/gpu/drm/imx/Makefile    |   1 +
 drivers/gpu/drm/imx/dc/Kconfig  |   5 +
 drivers/gpu/drm/imx/dc/Makefile |   5 +
 drivers/gpu/drm/imx/dc/dc-de.c  | 151 +++++++++++++
 drivers/gpu/drm/imx/dc/dc-de.h  |  62 ++++++
 drivers/gpu/drm/imx/dc/dc-drv.c |  32 +++
 drivers/gpu/drm/imx/dc/dc-drv.h |  24 +++
 drivers/gpu/drm/imx/dc/dc-fg.c  | 366 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-tc.c  | 137 ++++++++++++
 10 files changed, 784 insertions(+)
 create mode 100644 drivers/gpu/drm/imx/dc/Kconfig
 create mode 100644 drivers/gpu/drm/imx/dc/Makefile
 create mode 100644 drivers/gpu/drm/imx/dc/dc-de.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-de.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-drv.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-drv.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fg.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-tc.c

diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
index 03535a15dd8f..3e8c6edbc17c 100644
--- a/drivers/gpu/drm/imx/Kconfig
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
+source "drivers/gpu/drm/imx/dc/Kconfig"
 source "drivers/gpu/drm/imx/dcss/Kconfig"
 source "drivers/gpu/drm/imx/ipuv3/Kconfig"
 source "drivers/gpu/drm/imx/lcdc/Kconfig"
diff --git a/drivers/gpu/drm/imx/Makefile b/drivers/gpu/drm/imx/Makefile
index 86f38e7c7422..c7b317640d71 100644
--- a/drivers/gpu/drm/imx/Makefile
+++ b/drivers/gpu/drm/imx/Makefile
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 
+obj-$(CONFIG_DRM_IMX8_DC) += dc/
 obj-$(CONFIG_DRM_IMX_DCSS) += dcss/
 obj-$(CONFIG_DRM_IMX) += ipuv3/
 obj-$(CONFIG_DRM_IMX_LCDC) += lcdc/
diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
new file mode 100644
index 000000000000..32d7471c49d0
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/Kconfig
@@ -0,0 +1,5 @@
+config DRM_IMX8_DC
+       tristate "Freescale i.MX8 Display Controller Graphics"
+       depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
+       help
+         enable Freescale i.MX8 Display Controller(DC) graphics support
diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
new file mode 100644
index 000000000000..56de82d53d4d
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+imx8-dc-drm-objs := dc-de.o dc-drv.o dc-fg.o dc-tc.o
+
+obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
diff --git a/drivers/gpu/drm/imx/dc/dc-de.c b/drivers/gpu/drm/imx/dc/dc-de.c
new file mode 100644
index 000000000000..2c8268b76b08
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-de.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_managed.h>
+
+#include "dc-de.h"
+#include "dc-drv.h"
+
+#define POLARITYCTRL           0xc
+#define  POLEN_HIGH            BIT(2)
+
+struct dc_de_priv {
+       struct dc_de engine;
+       void __iomem *reg_top;
+};
+
+static inline struct dc_de_priv *to_de_priv(struct dc_de *de)
+{
+       return container_of(de, struct dc_de_priv, engine);
+}
+
+static inline void
+dc_dec_write(struct dc_de *de, unsigned int offset, u32 value)
+{
+       struct dc_de_priv *priv = to_de_priv(de);
+
+       writel(value, priv->reg_top + offset);
+}
+
+static void dc_dec_init(struct dc_de *de)
+{
+       dc_dec_write(de, POLARITYCTRL, POLEN_HIGH);
+}
+
+static int dc_de_bind(struct device *dev, struct device *master, void *data)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct dc_drm_device *dc_drm = data;
+       struct dc_de_priv *priv;
+       int ret;
+
+       priv = drmm_kzalloc(&dc_drm->base, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->reg_top = devm_platform_ioremap_resource_byname(pdev, "top");
+       if (IS_ERR(priv->reg_top))
+               return PTR_ERR(priv->reg_top);
+
+       priv->engine.irq_shdld = platform_get_irq_byname(pdev, "shdload");
+       if (priv->engine.irq_shdld < 0)
+               return priv->engine.irq_shdld;
+
+       priv->engine.irq_framecomplete =
+                               platform_get_irq_byname(pdev, "framecomplete");
+       if (priv->engine.irq_framecomplete < 0)
+               return priv->engine.irq_framecomplete;
+
+       priv->engine.irq_seqcomplete =
+                               platform_get_irq_byname(pdev, "seqcomplete");
+       if (priv->engine.irq_seqcomplete < 0)
+               return priv->engine.irq_seqcomplete;
+
+       priv->engine.id = of_alias_get_id(dev->of_node, "dc0-display-engine");
+       if (priv->engine.id < 0) {
+               dev_err(dev, "failed to get alias id: %d\n", priv->engine.id);
+               return priv->engine.id;
+       }
+
+       priv->engine.dev = dev;
+
+       dev_set_drvdata(dev, priv);
+
+       ret = devm_pm_runtime_enable(dev);
+       if (ret)
+               return ret;
+
+       dc_drm->de[priv->engine.id] = &priv->engine;
+
+       return 0;
+}
+
+static const struct component_ops dc_de_ops = {
+       .bind = dc_de_bind,
+};
+
+static int dc_de_probe(struct platform_device *pdev)
+{
+       int ret;
+
+       ret = devm_of_platform_populate(&pdev->dev);
+       if (ret < 0)
+               return ret;
+
+       ret = component_add(&pdev->dev, &dc_de_ops);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret,
+                                    "failed to add component\n");
+
+       return 0;
+}
+
+static void dc_de_remove(struct platform_device *pdev)
+{
+       component_del(&pdev->dev, &dc_de_ops);
+}
+
+static int dc_de_runtime_resume(struct device *dev)
+{
+       struct dc_de_priv *priv = dev_get_drvdata(dev);
+       struct dc_de *engine = &priv->engine;
+
+       dc_dec_init(engine);
+       dc_fg_init(engine->fg);
+       dc_tc_init(engine->tc);
+
+       return 0;
+}
+
+static const struct dev_pm_ops dc_de_pm_ops = {
+       RUNTIME_PM_OPS(NULL, dc_de_runtime_resume, NULL)
+};
+
+static const struct of_device_id dc_de_dt_ids[] = {
+       { .compatible = "fsl,imx8qxp-dc-display-engine", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_de_dt_ids);
+
+struct platform_driver dc_de_driver = {
+       .probe = dc_de_probe,
+       .remove_new = dc_de_remove,
+       .driver = {
+               .name = "imx8-dc-display-engine",
+               .of_match_table = dc_de_dt_ids,
+               .pm = pm_sleep_ptr(&dc_de_pm_ops),
+       },
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
new file mode 100644
index 000000000000..3417059c40b9
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-de.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2024 NXP
+ */
+
+#ifndef __DC_DISPLAY_ENGINE_H__
+#define __DC_DISPLAY_ENGINE_H__
+
+#include <linux/device.h>
+#include <drm/drm_modes.h>
+
+#define DC_DISPLAYS    2
+
+enum dc_fg_syncmode {
+       FG_SYNCMODE_OFF,        /* No side-by-side synchronization. */
+};
+
+enum dc_fg_dm {
+       FG_DM_CONSTCOL = 0x1,   /* Constant Color Background is shown. */
+       FG_DM_SEC_ON_TOP = 0x5, /* Both inputs overlaid with secondary on top. 
*/
+};
+
+struct dc_fg {
+};
+
+struct dc_tc {
+       struct device *dev;
+};
+
+struct dc_de {
+       struct device *dev;
+       struct dc_fg *fg;
+       struct dc_tc *tc;
+       int irq_shdld;
+       int irq_framecomplete;
+       int irq_seqcomplete;
+       int id;
+};
+
+/* Frame Generator Unit */
+void dc_fg_syncmode(struct dc_fg *fg, enum dc_fg_syncmode mode);
+void dc_fg_cfg_videomode(struct dc_fg *fg, struct drm_display_mode *m);
+void dc_fg_displaymode(struct dc_fg *fg, enum dc_fg_dm mode);
+void dc_fg_panic_displaymode(struct dc_fg *fg, enum dc_fg_dm mode);
+void dc_fg_enable(struct dc_fg *fg);
+void dc_fg_disable(struct dc_fg *fg);
+void dc_fg_shdtokgen(struct dc_fg *fg);
+u32 dc_fg_get_frame_index(struct dc_fg *fg);
+int dc_fg_get_line_index(struct dc_fg *fg);
+bool dc_fg_wait_for_frame_index_moving(struct dc_fg *fg);
+bool dc_fg_secondary_requests_to_read_empty_fifo(struct dc_fg *fg);
+void dc_fg_secondary_clear_channel_status(struct dc_fg *fg);
+int dc_fg_wait_for_secondary_syncup(struct dc_fg *fg);
+void dc_fg_enable_clock(struct dc_fg *fg);
+void dc_fg_disable_clock(struct dc_fg *fg);
+enum drm_mode_status dc_fg_check_clock(struct dc_fg *fg, int clk_khz);
+void dc_fg_init(struct dc_fg *fg);
+
+/* Timing Controller Unit */
+void dc_tc_init(struct dc_tc *tc);
+
+#endif /* __DC_DISPLAY_ENGINE_H__ */
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
new file mode 100644
index 000000000000..e5910a82dd4d
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-drv.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "dc-drv.h"
+
+static struct platform_driver * const dc_drivers[] = {
+       &dc_de_driver,
+       &dc_fg_driver,
+       &dc_tc_driver,
+};
+
+static int __init dc_drm_init(void)
+{
+       return platform_register_drivers(dc_drivers, ARRAY_SIZE(dc_drivers));
+}
+
+static void __exit dc_drm_exit(void)
+{
+       platform_unregister_drivers(dc_drivers, ARRAY_SIZE(dc_drivers));
+}
+
+module_init(dc_drm_init);
+module_exit(dc_drm_exit);
+
+MODULE_DESCRIPTION("i.MX8 Display Controller DRM Driver");
+MODULE_AUTHOR("Liu Ying <victor....@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
new file mode 100644
index 000000000000..e1290d9a0a99
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-drv.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2024 NXP
+ */
+
+#ifndef __DC_DRV_H__
+#define __DC_DRV_H__
+
+#include <linux/platform_device.h>
+
+#include <drm/drm_device.h>
+
+#include "dc-de.h"
+
+struct dc_drm_device {
+       struct drm_device base;
+       struct dc_de *de[DC_DISPLAYS];
+};
+
+extern struct platform_driver dc_de_driver;
+extern struct platform_driver dc_fg_driver;
+extern struct platform_driver dc_tc_driver;
+
+#endif /* __DC_DRV_H__ */
diff --git a/drivers/gpu/drm/imx/dc/dc-fg.c b/drivers/gpu/drm/imx/dc/dc-fg.c
new file mode 100644
index 000000000000..3e9a8abee93e
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-fg.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_managed.h>
+#include <drm/drm_modes.h>
+
+#include "dc-de.h"
+#include "dc-drv.h"
+
+#define FGSTCTRL               0x8
+#define  FGSYNCMODE_MASK       0x6
+#define  FGSYNCMODE(n)         ((n) << 6)
+#define  SHDEN                 BIT(0)
+
+#define HTCFG1                 0xc
+#define  HTOTAL(n)             ((((n) - 1) & 0x3fff) << 16)
+#define  HACT(n)               ((n) & 0x3fff)
+
+#define HTCFG2                 0x10
+#define  HSEN                  BIT(31)
+#define  HSBP(n)               ((((n) - 1) & 0x3fff) << 16)
+#define  HSYNC(n)              (((n) - 1) & 0x3fff)
+
+#define VTCFG1                 0x14
+#define  VTOTAL(n)             ((((n) - 1) & 0x3fff) << 16)
+#define  VACT(n)               ((n) & 0x3fff)
+
+#define VTCFG2                 0x18
+#define  VSEN                  BIT(31)
+#define  VSBP(n)               ((((n) - 1) & 0x3fff) << 16)
+#define  VSYNC(n)              (((n) - 1) & 0x3fff)
+
+#define PKICKCONFIG            0x2c
+#define SKICKCONFIG            0x30
+#define  EN                    BIT(31)
+#define  ROW(n)                        (((n) & 0x3fff) << 16)
+#define  COL(n)                        ((n) & 0x3fff)
+
+#define PACFG                  0x54
+#define SACFG                  0x58
+#define  STARTX(n)             (((n) + 1) & 0x3fff)
+#define  STARTY(n)             (((((n) + 1) & 0x3fff)) << 16)
+
+#define FGINCTRL               0x5c
+#define FGINCTRLPANIC          0x60
+#define  FGDM_MASK             0x7
+#define  ENPRIMALPHA           BIT(3)
+#define  ENSECALPHA            BIT(4)
+
+#define FGCCR                  0x64
+#define  CCGREEN(g)            (((g) & 0x3ff) << 10)
+
+#define FGENABLE               0x68
+#define  FGEN                  BIT(0)
+
+#define FGSLR                  0x6c
+#define  SHDTOKGEN             BIT(0)
+
+#define FGTIMESTAMP            0x74
+#define  FRAMEINDEX_SHIFT      14
+#define  FRAMEINDEX_MASK       (0x3ffff << FRAMEINDEX_SHIFT)
+#define  LINEINDEX_MASK                0x3fff
+
+#define FGCHSTAT               0x78
+#define  SECSYNCSTAT           BIT(24)
+#define  SFIFOEMPTY            BIT(16)
+
+#define FGCHSTATCLR            0x7c
+#define  CLRSECSTAT            BIT(16)
+
+#define KHZ                    1000
+
+struct dc_fg_priv {
+       struct dc_fg fg;
+       struct device *dev;
+       void __iomem *reg;
+       struct clk *clk_disp;
+};
+
+static inline struct dc_fg_priv *to_fg_priv(struct dc_fg *fg)
+{
+       return container_of(fg, struct dc_fg_priv, fg);
+}
+
+static inline u32 dc_fg_read(struct dc_fg *fg, unsigned int offset)
+{
+       struct dc_fg_priv *priv = to_fg_priv(fg);
+
+       return readl(priv->reg + offset);
+}
+
+static inline void
+dc_fg_write(struct dc_fg *fg, unsigned int offset, u32 value)
+{
+       struct dc_fg_priv *priv = to_fg_priv(fg);
+
+       writel(value, priv->reg + offset);
+}
+
+static inline void
+dc_fg_write_mask(struct dc_fg *fg, unsigned int offset, u32 mask, u32 value)
+{
+       u32 tmp;
+
+       tmp = dc_fg_read(fg, offset);
+       tmp &= ~mask;
+       dc_fg_write(fg, offset, tmp | value);
+}
+
+static void dc_fg_enable_shden(struct dc_fg *fg)
+{
+       dc_fg_write_mask(fg, FGSTCTRL, SHDEN, SHDEN);
+}
+
+void dc_fg_syncmode(struct dc_fg *fg, enum dc_fg_syncmode mode)
+{
+       dc_fg_write_mask(fg, FGSTCTRL, FGSYNCMODE_MASK, FGSYNCMODE(mode));
+}
+
+void dc_fg_cfg_videomode(struct dc_fg *fg, struct drm_display_mode *m)
+{
+       struct dc_fg_priv *priv = to_fg_priv(fg);
+       u32 hact, htotal, hsync, hsbp;
+       u32 vact, vtotal, vsync, vsbp;
+       u32 kick_row, kick_col;
+       int ret;
+
+       hact = m->crtc_hdisplay;
+       htotal = m->crtc_htotal;
+       hsync = m->crtc_hsync_end - m->crtc_hsync_start;
+       hsbp = m->crtc_htotal - m->crtc_hsync_start;
+
+       vact = m->crtc_vdisplay;
+       vtotal = m->crtc_vtotal;
+       vsync = m->crtc_vsync_end - m->crtc_vsync_start;
+       vsbp = m->crtc_vtotal - m->crtc_vsync_start;
+
+       /* video mode */
+       dc_fg_write(fg, HTCFG1, HACT(hact)   | HTOTAL(htotal));
+       dc_fg_write(fg, HTCFG2, HSYNC(hsync) | HSBP(hsbp) | HSEN);
+       dc_fg_write(fg, VTCFG1, VACT(vact)   | VTOTAL(vtotal));
+       dc_fg_write(fg, VTCFG2, VSYNC(vsync) | VSBP(vsbp) | VSEN);
+
+       kick_col = hact + 1;
+       kick_row = vact;
+
+       /* pkickconfig */
+       dc_fg_write(fg, PKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
+
+       /* skikconfig */
+       dc_fg_write(fg, SKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
+
+       /* primary and secondary area position configuration */
+       dc_fg_write(fg, PACFG, STARTX(0) | STARTY(0));
+       dc_fg_write(fg, SACFG, STARTX(0) | STARTY(0));
+
+       /* alpha */
+       dc_fg_write_mask(fg, FGINCTRL,      ENPRIMALPHA | ENSECALPHA, 0);
+       dc_fg_write_mask(fg, FGINCTRLPANIC, ENPRIMALPHA | ENSECALPHA, 0);
+
+       /* constant color is green(used in panic mode)  */
+       dc_fg_write(fg, FGCCR, CCGREEN(0x3ff));
+
+       ret = clk_set_rate(priv->clk_disp, m->clock * KHZ);
+       if (ret < 0)
+               dev_err(priv->dev,
+                       "failed to set display clock rate: %d\n", ret);
+}
+
+void dc_fg_displaymode(struct dc_fg *fg, enum dc_fg_dm mode)
+{
+       dc_fg_write_mask(fg, FGINCTRL, FGDM_MASK, mode);
+}
+
+void dc_fg_panic_displaymode(struct dc_fg *fg, enum dc_fg_dm mode)
+{
+       dc_fg_write_mask(fg, FGINCTRLPANIC, FGDM_MASK, mode);
+}
+
+void dc_fg_enable(struct dc_fg *fg)
+{
+       dc_fg_write(fg, FGENABLE, FGEN);
+}
+
+void dc_fg_disable(struct dc_fg *fg)
+{
+       dc_fg_write(fg, FGENABLE, 0);
+}
+
+void dc_fg_shdtokgen(struct dc_fg *fg)
+{
+       dc_fg_write(fg, FGSLR, SHDTOKGEN);
+}
+
+u32 dc_fg_get_frame_index(struct dc_fg *fg)
+{
+       u32 val = dc_fg_read(fg, FGTIMESTAMP);
+
+       return (val & FRAMEINDEX_MASK) >> FRAMEINDEX_SHIFT;
+}
+
+int dc_fg_get_line_index(struct dc_fg *fg)
+{
+       u32 val = dc_fg_read(fg, FGTIMESTAMP);
+
+       return val & LINEINDEX_MASK;
+}
+
+bool dc_fg_wait_for_frame_index_moving(struct dc_fg *fg)
+{
+       unsigned long timeout = jiffies + msecs_to_jiffies(100);
+       u32 frame_index, last_frame_index;
+
+       frame_index = dc_fg_get_frame_index(fg);
+       do {
+               last_frame_index = frame_index;
+               frame_index = dc_fg_get_frame_index(fg);
+       } while (last_frame_index == frame_index &&
+                time_before(jiffies, timeout));
+
+       return last_frame_index != frame_index;
+}
+
+bool dc_fg_secondary_requests_to_read_empty_fifo(struct dc_fg *fg)
+{
+       u32 val;
+
+       val = dc_fg_read(fg, FGCHSTAT);
+
+       return !!(val & SFIFOEMPTY);
+}
+
+void dc_fg_secondary_clear_channel_status(struct dc_fg *fg)
+{
+       dc_fg_write(fg, FGCHSTATCLR, CLRSECSTAT);
+}
+
+int dc_fg_wait_for_secondary_syncup(struct dc_fg *fg)
+{
+       struct dc_fg_priv *priv = to_fg_priv(fg);
+       u32 val;
+
+       return readl_poll_timeout(priv->reg + FGCHSTAT, val,
+                                 val & SECSYNCSTAT, 5, 100000);
+}
+
+void dc_fg_enable_clock(struct dc_fg *fg)
+{
+       struct dc_fg_priv *priv = to_fg_priv(fg);
+       int ret;
+
+       ret = clk_prepare_enable(priv->clk_disp);
+       if (ret)
+               dev_err(priv->dev, "failed to enable display clock: %d\n", ret);
+}
+
+void dc_fg_disable_clock(struct dc_fg *fg)
+{
+       struct dc_fg_priv *priv = to_fg_priv(fg);
+
+       clk_disable_unprepare(priv->clk_disp);
+}
+
+enum drm_mode_status dc_fg_check_clock(struct dc_fg *fg, int clk_khz)
+{
+       struct dc_fg_priv *priv = to_fg_priv(fg);
+       unsigned long rounded_rate;
+
+       rounded_rate = clk_round_rate(priv->clk_disp, clk_khz * KHZ);
+
+       if (rounded_rate != clk_khz * KHZ)
+               return MODE_NOCLOCK;
+
+       return MODE_OK;
+}
+
+void dc_fg_init(struct dc_fg *fg)
+{
+       dc_fg_enable_shden(fg);
+       dc_fg_syncmode(fg, FG_SYNCMODE_OFF);
+}
+
+static int dc_fg_bind(struct device *dev, struct device *master, void *data)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct dc_drm_device *dc_drm = data;
+       struct dc_fg_priv *priv;
+       struct dc_de *de;
+       int id;
+
+       priv = drmm_kzalloc(&dc_drm->base, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->reg = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(priv->reg))
+               return PTR_ERR(priv->reg);
+
+       priv->clk_disp = devm_clk_get(dev, NULL);
+       if (IS_ERR(priv->clk_disp))
+               return dev_err_probe(dev, PTR_ERR(priv->clk_disp),
+                                    "failed to get display clock\n");
+
+       id = of_alias_get_id(dev->of_node, "dc0-framegen");
+       if (id < 0) {
+               dev_err(dev, "failed to get alias id: %d\n", id);
+               return id;
+       }
+
+       priv->dev = dev;
+
+       de = dc_drm->de[id];
+       de->fg = &priv->fg;
+
+       return 0;
+}
+
+static const struct component_ops dc_fg_ops = {
+       .bind = dc_fg_bind,
+};
+
+static int dc_fg_probe(struct platform_device *pdev)
+{
+       int ret;
+
+       ret = component_add(&pdev->dev, &dc_fg_ops);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret,
+                                    "failed to add component\n");
+
+       return 0;
+}
+
+static void dc_fg_remove(struct platform_device *pdev)
+{
+       component_del(&pdev->dev, &dc_fg_ops);
+}
+
+static const struct of_device_id dc_fg_dt_ids[] = {
+       { .compatible = "fsl,imx8qxp-dc-framegen", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_fg_dt_ids);
+
+struct platform_driver dc_fg_driver = {
+       .probe = dc_fg_probe,
+       .remove_new = dc_fg_remove,
+       .driver = {
+               .name = "imx8-dc-framegen",
+               .of_match_table = dc_fg_dt_ids,
+       },
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-tc.c b/drivers/gpu/drm/imx/dc/dc-tc.c
new file mode 100644
index 000000000000..29f42496a409
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-tc.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_managed.h>
+
+#include "dc-drv.h"
+#include "dc-de.h"
+
+#define TCON_CTRL      0x410
+#define  CTRL_RST_VAL  0x01401408
+
+/* red: MAPBIT 29-20, green: MAPBIT 19-10, blue: MAPBIT 9-0 */
+#define MAPBIT3_0      0x418
+#define MAPBIT7_4      0x41c
+#define MAPBIT11_8     0x420
+#define MAPBIT15_12    0x424
+#define MAPBIT19_16    0x428
+#define MAPBIT23_20    0x42c
+#define MAPBIT27_24    0x430
+#define MAPBIT31_28    0x434
+#define MAPBIT34_32    0x438
+
+struct dc_tc_priv {
+       struct dc_tc tc;
+       void __iomem *reg;
+};
+
+static inline struct dc_tc_priv *to_tc_priv(struct dc_tc *tc)
+{
+       return container_of(tc, struct dc_tc_priv, tc);
+}
+
+static inline void dc_tc_write(struct dc_tc *tc, unsigned int offset, u32 
value)
+{
+       struct dc_tc_priv *priv = to_tc_priv(tc);
+
+       writel(value, priv->reg + offset);
+}
+
+static void dc_tc_set_fmt(struct dc_tc *tc)
+{
+       /*
+        * The pixels reach TCON are always in 30-bit BGR format.
+        * The first bridge always receives pixels in 30-bit RGB format.
+        * So, map the format to MEDIA_BUS_FMT_RGB101010_1X30.
+        */
+       dc_tc_write(tc, MAPBIT3_0,   0x17161514);
+       dc_tc_write(tc, MAPBIT7_4,   0x1b1a1918);
+       dc_tc_write(tc, MAPBIT11_8,  0x0b0a1d1c);
+       dc_tc_write(tc, MAPBIT15_12, 0x0f0e0d0c);
+       dc_tc_write(tc, MAPBIT19_16, 0x13121110);
+       dc_tc_write(tc, MAPBIT23_20, 0x03020100);
+       dc_tc_write(tc, MAPBIT27_24, 0x07060504);
+       dc_tc_write(tc, MAPBIT31_28, 0x00000908);
+}
+
+void dc_tc_init(struct dc_tc *tc)
+{
+       /* reset TCON_CTRL to POR default so that TCON works in bypass mode */
+       dc_tc_write(tc, TCON_CTRL, CTRL_RST_VAL);
+       dc_tc_set_fmt(tc);
+}
+
+static int dc_tc_bind(struct device *dev, struct device *master, void *data)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct dc_drm_device *dc_drm = data;
+       struct dc_tc_priv *priv;
+       struct dc_de *de;
+       int id;
+
+       priv = drmm_kzalloc(&dc_drm->base, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->reg = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(priv->reg))
+               return PTR_ERR(priv->reg);
+
+       id = of_alias_get_id(dev->of_node, "dc0-tcon");
+       if (id < 0) {
+               dev_err(dev, "failed to get alias id: %d\n", id);
+               return id;
+       }
+
+       de = dc_drm->de[id];
+       de->tc = &priv->tc;
+       de->tc->dev = dev;
+
+       return 0;
+}
+
+static const struct component_ops dc_tc_ops = {
+       .bind = dc_tc_bind,
+};
+
+static int dc_tc_probe(struct platform_device *pdev)
+{
+       int ret;
+
+       ret = component_add(&pdev->dev, &dc_tc_ops);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret,
+                                    "failed to add component\n");
+
+       return 0;
+}
+
+static void dc_tc_remove(struct platform_device *pdev)
+{
+       component_del(&pdev->dev, &dc_tc_ops);
+}
+
+static const struct of_device_id dc_tc_dt_ids[] = {
+       { .compatible = "fsl,imx8qxp-dc-tcon", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_tc_dt_ids);
+
+struct platform_driver dc_tc_driver = {
+       .probe = dc_tc_probe,
+       .remove_new = dc_tc_remove,
+       .driver = {
+               .name = "imx8-dc-tcon",
+               .of_match_table = dc_tc_dt_ids,
+       },
+};
-- 
2.34.1

Reply via email to