]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
drm/imx: Add i.MX8qxp Display Controller display engine
authorLiu Ying <victor.liu@nxp.com>
Mon, 14 Apr 2025 03:50:18 +0000 (11:50 +0800)
committerLiu Ying <victor.liu@nxp.com>
Wed, 14 May 2025 07:08:54 +0000 (15:08 +0800)
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 FrameGen driver, TCon driver and display engine driver
are components to be aggregated by a master registered in the upcoming
DRM driver.

Reviewed-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Liu Ying <victor.liu@nxp.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Link: https://lore.kernel.org/r/20250414035028.1561475-10-victor.liu@nxp.com
drivers/gpu/drm/imx/Kconfig
drivers/gpu/drm/imx/Makefile
drivers/gpu/drm/imx/dc/Kconfig [new file with mode: 0644]
drivers/gpu/drm/imx/dc/Makefile [new file with mode: 0644]
drivers/gpu/drm/imx/dc/dc-de.c [new file with mode: 0644]
drivers/gpu/drm/imx/dc/dc-de.h [new file with mode: 0644]
drivers/gpu/drm/imx/dc/dc-drv.c [new file with mode: 0644]
drivers/gpu/drm/imx/dc/dc-drv.h [new file with mode: 0644]
drivers/gpu/drm/imx/dc/dc-fg.c [new file with mode: 0644]
drivers/gpu/drm/imx/dc/dc-tc.c [new file with mode: 0644]

index 03535a15dd8f310aa91545137c3996b23a170286..3e8c6edbc17c28bf53cd775ad06a2585e6001e7c 100644 (file)
@@ -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"
index 86f38e7c742227c7ed0fa4b163e91ef37177b5ee..c7b317640d71d65e19455d6058cd178e62c30c2b 100644 (file)
@@ -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 (file)
index 0000000..e1ef76d
--- /dev/null
@@ -0,0 +1,7 @@
+config DRM_IMX8_DC
+       tristate "Freescale i.MX8 Display Controller Graphics"
+       depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
+       select REGMAP
+       select REGMAP_MMIO
+       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 (file)
index 0000000..56de82d
--- /dev/null
@@ -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 (file)
index 0000000..5a31255
--- /dev/null
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "dc-de.h"
+#include "dc-drv.h"
+
+#define POLARITYCTRL           0xc
+#define  POLEN_HIGH            BIT(2)
+
+static const struct dc_subdev_info dc_de_info[] = {
+       { .reg_start = 0x5618b400, .id = 0, },
+       { .reg_start = 0x5618b420, .id = 1, },
+};
+
+static const struct regmap_range dc_de_regmap_ranges[] = {
+       regmap_reg_range(POLARITYCTRL, POLARITYCTRL),
+};
+
+static const struct regmap_access_table dc_de_regmap_access_table = {
+       .yes_ranges = dc_de_regmap_ranges,
+       .n_yes_ranges = ARRAY_SIZE(dc_de_regmap_ranges),
+};
+
+static const struct regmap_config dc_de_top_regmap_config = {
+       .name = "top",
+       .reg_bits = 32,
+       .reg_stride = 4,
+       .val_bits = 32,
+       .fast_io = true,
+       .wr_table = &dc_de_regmap_access_table,
+       .rd_table = &dc_de_regmap_access_table,
+       .max_register = POLARITYCTRL,
+};
+
+static inline void dc_dec_init(struct dc_de *de)
+{
+       regmap_write_bits(de->reg_top, POLARITYCTRL, 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 resource *res_top;
+       void __iomem *base_top;
+       struct dc_de *de;
+       int ret, id;
+
+       de = devm_kzalloc(dev, sizeof(*de), GFP_KERNEL);
+       if (!de)
+               return -ENOMEM;
+
+       base_top = devm_platform_get_and_ioremap_resource(pdev, 0, &res_top);
+       if (IS_ERR(base_top))
+               return PTR_ERR(base_top);
+
+       de->reg_top = devm_regmap_init_mmio(dev, base_top,
+                                           &dc_de_top_regmap_config);
+       if (IS_ERR(de->reg_top))
+               return PTR_ERR(de->reg_top);
+
+       de->irq_shdload = platform_get_irq_byname(pdev, "shdload");
+       if (de->irq_shdload < 0)
+               return de->irq_shdload;
+
+       de->irq_framecomplete = platform_get_irq_byname(pdev, "framecomplete");
+       if (de->irq_framecomplete < 0)
+               return de->irq_framecomplete;
+
+       de->irq_seqcomplete = platform_get_irq_byname(pdev, "seqcomplete");
+       if (de->irq_seqcomplete < 0)
+               return de->irq_seqcomplete;
+
+       de->dev = dev;
+
+       dev_set_drvdata(dev, de);
+
+       ret = devm_pm_runtime_enable(dev);
+       if (ret)
+               return ret;
+
+       id = dc_subdev_get_id(dc_de_info, ARRAY_SIZE(dc_de_info), res_top);
+       if (id < 0) {
+               dev_err(dev, "failed to get instance number: %d\n", id);
+               return id;
+       }
+
+       dc_drm->de[id] = de;
+
+       return 0;
+}
+
+/*
+ * It's possible to get the child device pointers from the child component
+ * bind callbacks, but it depends on the component helper behavior to bind
+ * the display engine component first.  To avoid the dependency, post bind
+ * to get the pointers from dc_drm in a safe manner.
+ */
+void dc_de_post_bind(struct dc_drm_device *dc_drm)
+{
+       struct dc_de *de;
+       int i;
+
+       for (i = 0; i < DC_DISPLAYS; i++) {
+               de = dc_drm->de[i];
+               de->fg = dc_drm->fg[i];
+               de->tc = dc_drm->tc[i];
+       }
+}
+
+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 *de = dev_get_drvdata(dev);
+
+       dc_dec_init(de);
+       dc_fg_init(de->fg);
+       dc_tc_init(de->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 = dc_de_remove,
+       .driver = {
+               .name = "imx8-dc-display-engine",
+               .suppress_bind_attrs = true,
+               .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 (file)
index 0000000..5dd311c
--- /dev/null
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2024 NXP
+ */
+
+#ifndef __DC_DISPLAY_ENGINE_H__
+#define __DC_DISPLAY_ENGINE_H__
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <drm/drm_modes.h>
+
+#define DC_DISPLAYS    2
+
+struct dc_fg {
+       struct device *dev;
+       struct regmap *reg;
+       struct clk *clk_disp;
+};
+
+struct dc_tc {
+       struct device *dev;
+       struct regmap *reg;
+};
+
+struct dc_de {
+       struct device *dev;
+       struct regmap *reg_top;
+       struct dc_fg *fg;
+       struct dc_tc *tc;
+       int irq_shdload;
+       int irq_framecomplete;
+       int irq_seqcomplete;
+};
+
+/* Frame Generator Unit */
+void dc_fg_cfg_videomode(struct dc_fg *fg, struct drm_display_mode *m);
+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);
+u32 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 (file)
index 0000000..e5910a8
--- /dev/null
@@ -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.liu@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 (file)
index 0000000..65ae9c7
--- /dev/null
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2024 NXP
+ */
+
+#ifndef __DC_DRV_H__
+#define __DC_DRV_H__
+
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#include <drm/drm_device.h>
+
+#include "dc-de.h"
+
+/**
+ * struct dc_drm_device - DC specific drm_device
+ */
+struct dc_drm_device {
+       /** @base: base drm_device structure */
+       struct drm_device base;
+       /** @de: display engine list */
+       struct dc_de *de[DC_DISPLAYS];
+       /** @fg: framegen list */
+       struct dc_fg *fg[DC_DISPLAYS];
+       /** @tc: tcon list */
+       struct dc_tc *tc[DC_DISPLAYS];
+};
+
+struct dc_subdev_info {
+       resource_size_t reg_start;
+       int id;
+};
+
+extern struct platform_driver dc_de_driver;
+extern struct platform_driver dc_fg_driver;
+extern struct platform_driver dc_tc_driver;
+
+static inline int dc_subdev_get_id(const struct dc_subdev_info *info,
+                                  int info_cnt, struct resource *res)
+{
+       int i;
+
+       if (!res)
+               return -EINVAL;
+
+       for (i = 0; i < info_cnt; i++)
+               if (info[i].reg_start == res->start)
+                       return info[i].id;
+
+       return -EINVAL;
+}
+
+void dc_de_post_bind(struct dc_drm_device *dc_drm);
+
+#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 (file)
index 0000000..7f6c185
--- /dev/null
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <drm/drm_modes.h>
+
+#include "dc-de.h"
+#include "dc-drv.h"
+
+#define FGSTCTRL               0x8
+#define  FGSYNCMODE_MASK       GENMASK(2, 1)
+#define  FGSYNCMODE(x)         FIELD_PREP(FGSYNCMODE_MASK, (x))
+#define  SHDEN                 BIT(0)
+
+#define HTCFG1                 0xc
+#define  HTOTAL(x)             FIELD_PREP(GENMASK(29, 16), ((x) - 1))
+#define  HACT(x)               FIELD_PREP(GENMASK(13, 0), (x))
+
+#define HTCFG2                 0x10
+#define  HSEN                  BIT(31)
+#define  HSBP(x)               FIELD_PREP(GENMASK(29, 16), ((x) - 1))
+#define  HSYNC(x)              FIELD_PREP(GENMASK(13, 0), ((x) - 1))
+
+#define VTCFG1                 0x14
+#define  VTOTAL(x)             FIELD_PREP(GENMASK(29, 16), ((x) - 1))
+#define  VACT(x)               FIELD_PREP(GENMASK(13, 0), (x))
+
+#define VTCFG2                 0x18
+#define  VSEN                  BIT(31)
+#define  VSBP(x)               FIELD_PREP(GENMASK(29, 16), ((x) - 1))
+#define  VSYNC(x)              FIELD_PREP(GENMASK(13, 0), ((x) - 1))
+
+#define PKICKCONFIG            0x2c
+#define SKICKCONFIG            0x30
+#define  EN                    BIT(31)
+#define  ROW(x)                        FIELD_PREP(GENMASK(29, 16), (x))
+#define  COL(x)                        FIELD_PREP(GENMASK(13, 0), (x))
+
+#define PACFG                  0x54
+#define SACFG                  0x58
+#define  STARTY(x)             FIELD_PREP(GENMASK(29, 16), ((x) + 1))
+#define  STARTX(x)             FIELD_PREP(GENMASK(13, 0), ((x) + 1))
+
+#define FGINCTRL               0x5c
+#define FGINCTRLPANIC          0x60
+#define  FGDM_MASK             GENMASK(2, 0)
+#define  ENPRIMALPHA           BIT(3)
+#define  ENSECALPHA            BIT(4)
+
+#define FGCCR                  0x64
+#define  CCGREEN(x)            FIELD_PREP(GENMASK(19, 10), (x))
+
+#define FGENABLE               0x68
+#define  FGEN                  BIT(0)
+
+#define FGSLR                  0x6c
+#define  SHDTOKGEN             BIT(0)
+
+#define FGTIMESTAMP            0x74
+#define  FRAMEINDEX(x)         FIELD_GET(GENMASK(31, 14), (x))
+#define  LINEINDEX(x)          FIELD_GET(GENMASK(13, 0), (x))
+
+#define FGCHSTAT               0x78
+#define  SECSYNCSTAT           BIT(24)
+#define  SFIFOEMPTY            BIT(16)
+
+#define FGCHSTATCLR            0x7c
+#define  CLRSECSTAT            BIT(16)
+
+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. */
+};
+
+static const struct dc_subdev_info dc_fg_info[] = {
+       { .reg_start = 0x5618b800, .id = 0, },
+       { .reg_start = 0x5618d400, .id = 1, },
+};
+
+static const struct regmap_range dc_fg_regmap_write_ranges[] = {
+       regmap_reg_range(FGSTCTRL, VTCFG2),
+       regmap_reg_range(PKICKCONFIG, SKICKCONFIG),
+       regmap_reg_range(PACFG, FGSLR),
+       regmap_reg_range(FGCHSTATCLR, FGCHSTATCLR),
+};
+
+static const struct regmap_range dc_fg_regmap_read_ranges[] = {
+       regmap_reg_range(FGSTCTRL, VTCFG2),
+       regmap_reg_range(PKICKCONFIG, SKICKCONFIG),
+       regmap_reg_range(PACFG, FGENABLE),
+       regmap_reg_range(FGTIMESTAMP, FGCHSTAT),
+};
+
+static const struct regmap_access_table dc_fg_regmap_write_table = {
+       .yes_ranges = dc_fg_regmap_write_ranges,
+       .n_yes_ranges = ARRAY_SIZE(dc_fg_regmap_write_ranges),
+};
+
+static const struct regmap_access_table dc_fg_regmap_read_table = {
+       .yes_ranges = dc_fg_regmap_read_ranges,
+       .n_yes_ranges = ARRAY_SIZE(dc_fg_regmap_read_ranges),
+};
+
+static const struct regmap_config dc_fg_regmap_config = {
+       .reg_bits = 32,
+       .reg_stride = 4,
+       .val_bits = 32,
+       .fast_io = true,
+       .wr_table = &dc_fg_regmap_write_table,
+       .rd_table = &dc_fg_regmap_read_table,
+       .max_register = FGCHSTATCLR,
+};
+
+static inline void dc_fg_enable_shden(struct dc_fg *fg)
+{
+       regmap_write_bits(fg->reg, FGSTCTRL, SHDEN, SHDEN);
+}
+
+static inline void dc_fg_syncmode(struct dc_fg *fg, enum dc_fg_syncmode mode)
+{
+       regmap_write_bits(fg->reg, FGSTCTRL, FGSYNCMODE_MASK, FGSYNCMODE(mode));
+}
+
+void dc_fg_cfg_videomode(struct dc_fg *fg, struct drm_display_mode *m)
+{
+       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 */
+       regmap_write(fg->reg, HTCFG1, HACT(hact)   | HTOTAL(htotal));
+       regmap_write(fg->reg, HTCFG2, HSYNC(hsync) | HSBP(hsbp) | HSEN);
+       regmap_write(fg->reg, VTCFG1, VACT(vact)   | VTOTAL(vtotal));
+       regmap_write(fg->reg, VTCFG2, VSYNC(vsync) | VSBP(vsbp) | VSEN);
+
+       kick_col = hact + 1;
+       kick_row = vact;
+
+       /* pkickconfig */
+       regmap_write(fg->reg, PKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
+
+       /* skikconfig */
+       regmap_write(fg->reg, SKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
+
+       /* primary and secondary area position configuration */
+       regmap_write(fg->reg, PACFG, STARTX(0) | STARTY(0));
+       regmap_write(fg->reg, SACFG, STARTX(0) | STARTY(0));
+
+       /* alpha */
+       regmap_write_bits(fg->reg, FGINCTRL,      ENPRIMALPHA | ENSECALPHA, 0);
+       regmap_write_bits(fg->reg, FGINCTRLPANIC, ENPRIMALPHA | ENSECALPHA, 0);
+
+       /* constant color is green(used in panic mode)  */
+       regmap_write(fg->reg, FGCCR, CCGREEN(0x3ff));
+
+       ret = clk_set_rate(fg->clk_disp, m->clock * HZ_PER_KHZ);
+       if (ret < 0)
+               dev_err(fg->dev, "failed to set display clock rate: %d\n", ret);
+}
+
+static inline void dc_fg_displaymode(struct dc_fg *fg, enum dc_fg_dm mode)
+{
+       regmap_write_bits(fg->reg, FGINCTRL, FGDM_MASK, mode);
+}
+
+static inline void dc_fg_panic_displaymode(struct dc_fg *fg, enum dc_fg_dm mode)
+{
+       regmap_write_bits(fg->reg, FGINCTRLPANIC, FGDM_MASK, mode);
+}
+
+void dc_fg_enable(struct dc_fg *fg)
+{
+       regmap_write(fg->reg, FGENABLE, FGEN);
+}
+
+void dc_fg_disable(struct dc_fg *fg)
+{
+       regmap_write(fg->reg, FGENABLE, 0);
+}
+
+void dc_fg_shdtokgen(struct dc_fg *fg)
+{
+       regmap_write(fg->reg, FGSLR, SHDTOKGEN);
+}
+
+u32 dc_fg_get_frame_index(struct dc_fg *fg)
+{
+       u32 val;
+
+       regmap_read(fg->reg, FGTIMESTAMP, &val);
+
+       return FRAMEINDEX(val);
+}
+
+u32 dc_fg_get_line_index(struct dc_fg *fg)
+{
+       u32 val;
+
+       regmap_read(fg->reg, FGTIMESTAMP, &val);
+
+       return LINEINDEX(val);
+}
+
+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;
+
+       regmap_read(fg->reg, FGCHSTAT, &val);
+
+       return !!(val & SFIFOEMPTY);
+}
+
+void dc_fg_secondary_clear_channel_status(struct dc_fg *fg)
+{
+       regmap_write(fg->reg, FGCHSTATCLR, CLRSECSTAT);
+}
+
+int dc_fg_wait_for_secondary_syncup(struct dc_fg *fg)
+{
+       unsigned int val;
+
+       return regmap_read_poll_timeout(fg->reg, FGCHSTAT, val,
+                                       val & SECSYNCSTAT, 5, 100000);
+}
+
+void dc_fg_enable_clock(struct dc_fg *fg)
+{
+       int ret;
+
+       ret = clk_prepare_enable(fg->clk_disp);
+       if (ret)
+               dev_err(fg->dev, "failed to enable display clock: %d\n", ret);
+}
+
+void dc_fg_disable_clock(struct dc_fg *fg)
+{
+       clk_disable_unprepare(fg->clk_disp);
+}
+
+enum drm_mode_status dc_fg_check_clock(struct dc_fg *fg, int clk_khz)
+{
+       unsigned long rounded_rate;
+
+       rounded_rate = clk_round_rate(fg->clk_disp, clk_khz * HZ_PER_KHZ);
+
+       if (rounded_rate != clk_khz * HZ_PER_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);
+       dc_fg_displaymode(fg, FG_DM_SEC_ON_TOP);
+       dc_fg_panic_displaymode(fg, FG_DM_CONSTCOL);
+}
+
+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 resource *res;
+       void __iomem *base;
+       struct dc_fg *fg;
+       int id;
+
+       fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL);
+       if (!fg)
+               return -ENOMEM;
+
+       base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+       if (IS_ERR(base))
+               return PTR_ERR(base);
+
+       fg->reg = devm_regmap_init_mmio(dev, base, &dc_fg_regmap_config);
+       if (IS_ERR(fg->reg))
+               return PTR_ERR(fg->reg);
+
+       fg->clk_disp = devm_clk_get(dev, NULL);
+       if (IS_ERR(fg->clk_disp))
+               return dev_err_probe(dev, PTR_ERR(fg->clk_disp),
+                                    "failed to get display clock\n");
+
+       id = dc_subdev_get_id(dc_fg_info, ARRAY_SIZE(dc_fg_info), res);
+       if (id < 0) {
+               dev_err(dev, "failed to get instance number: %d\n", id);
+               return id;
+       }
+
+       fg->dev = dev;
+       dc_drm->fg[id] = 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 = dc_fg_remove,
+       .driver = {
+               .name = "imx8-dc-framegen",
+               .suppress_bind_attrs = true,
+               .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 (file)
index 0000000..0bfd381
--- /dev/null
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.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
+
+static const struct dc_subdev_info dc_tc_info[] = {
+       { .reg_start = 0x5618c800, .id = 0, },
+       { .reg_start = 0x5618e400, .id = 1, },
+};
+
+static const struct regmap_range dc_tc_regmap_ranges[] = {
+       regmap_reg_range(TCON_CTRL, TCON_CTRL),
+       regmap_reg_range(MAPBIT3_0, MAPBIT31_28),
+};
+
+static const struct regmap_access_table dc_tc_regmap_access_table = {
+       .yes_ranges = dc_tc_regmap_ranges,
+       .n_yes_ranges = ARRAY_SIZE(dc_tc_regmap_ranges),
+};
+
+static const struct regmap_config dc_tc_regmap_config = {
+       .reg_bits = 32,
+       .reg_stride = 4,
+       .val_bits = 32,
+       .fast_io = true,
+       .wr_table = &dc_tc_regmap_access_table,
+       .rd_table = &dc_tc_regmap_access_table,
+       .max_register = MAPBIT31_28,
+};
+
+/*
+ * 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.
+ */
+static const u32 dc_tc_mapbit[] = {
+       0x17161514, 0x1b1a1918, 0x0b0a1d1c, 0x0f0e0d0c,
+       0x13121110, 0x03020100, 0x07060504, 0x00000908,
+};
+
+void dc_tc_init(struct dc_tc *tc)
+{
+       /* reset TCON_CTRL to POR default so that TCON works in bypass mode */
+       regmap_write(tc->reg, TCON_CTRL, CTRL_RST_VAL);
+
+       /* set format */
+       regmap_bulk_write(tc->reg, MAPBIT3_0, dc_tc_mapbit,
+                         ARRAY_SIZE(dc_tc_mapbit));
+}
+
+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 resource *res;
+       void __iomem *base;
+       struct dc_tc *tc;
+       int id;
+
+       tc = devm_kzalloc(dev, sizeof(*tc), GFP_KERNEL);
+       if (!tc)
+               return -ENOMEM;
+
+       base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+       if (IS_ERR(base))
+               return PTR_ERR(base);
+
+       tc->reg = devm_regmap_init_mmio(dev, base, &dc_tc_regmap_config);
+       if (IS_ERR(tc->reg))
+               return PTR_ERR(tc->reg);
+
+       id = dc_subdev_get_id(dc_tc_info, ARRAY_SIZE(dc_tc_info), res);
+       if (id < 0) {
+               dev_err(dev, "failed to get instance number: %d\n", id);
+               return id;
+       }
+
+       tc->dev = dev;
+       dc_drm->tc[id] = tc;
+
+       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 = dc_tc_remove,
+       .driver = {
+               .name = "imx8-dc-tcon",
+               .suppress_bind_attrs = true,
+               .of_match_table = dc_tc_dt_ids,
+       },
+};