[PATCH 08/14] drm/imx: dc: Use TCON operation mode

Liu Ying
2025-07-04T10:42:26Z

In TCON operation mode, sync signals from FrameGen are ignored, but
a much more customized output timing can be generated by the TCON
module. By using TCON operaton mode, generate KACHUNK signal along
with HSYNC/VSYNC/data enable signals. The KACHUNK signal is used as
a synchronization signal inside the prefetch engine(DPRC + PRG(s),
attached to FetchUnit(s)). Carefully switch TCON bypass mode to TCON
operation mode when CRTC is being enabled so that the prefetch engine
may evade the first dumb frame generated by the display controller.

Since TCON BYPASS bit is controlled by KMS driver when doing atomic
commits, drop the bit setting when initializing TCON. This also
avoids accidentally initializing TCON BYPASS bit to 1 when driver
module removing and re-installing where an upcoming patch would
disable a CRTC at boot in TCON operation mode if needed.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
drivers/gpu/drm/imx/dc/dc-crtc.c | 28 ++++++++++
drivers/gpu/drm/imx/dc/dc-de.h | 2 +
drivers/gpu/drm/imx/dc/dc-kms.h | 2 +
drivers/gpu/drm/imx/dc/dc-tc.c | 114 +++++++++++++++++++++++++++++++++++++--
4 files changed, 142 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/imx/dc/dc-crtc.c b/drivers/gpu/drm/imx/dc/dc-crtc.c
index 45a87df1ad6a8bd768aa5ed38d6f03f14052b3d7..9e9e86cd5202bcb0bb4d5627dbcefcc3f4e2ead0 100644
--- a/drivers/gpu/drm/imx/dc/dc-crtc.c
+++ b/drivers/gpu/drm/imx/dc/dc-crtc.c
@@ -6,8 +6,10 @@
#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/interrupt.h>
+#include <linux/irqflags.h>
#include <linux/irqreturn.h>
#include <linux/pm_runtime.h>
+#include <linux/preempt.h>
#include <linux/spinlock.h>

#include <drm/drm_atomic.h>
@@ -68,6 +70,14 @@ do { \
__func__); \
} while (0)

+#define DC_CRTC_WAIT_FOR_FRAMEGEN_FRAME_INDEX_MOVING(fg) \
+do { \
+ if (!dc_fg_wait_for_frame_index_moving(fg)) \
+ dc_crtc_err(crtc, \
+ "%s: FrameGen frame index isn't moving\n", \
+ __func__); \
+} while (0)
+
static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
{
return container_of(crtc, struct dc_crtc, base);
@@ -229,6 +239,7 @@ dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
enum dc_link_id cf_link;
+ unsigned long flags;
int idx, ret;

dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
@@ -249,6 +260,7 @@ dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
enable_irq(dc_crtc->irq_ed_safe_shdload);

dc_fg_cfg_videomode(dc_crtc->fg, adj);
+ dc_tc_cfg_videomode(dc_crtc->tc, adj);

dc_cf_framedimensions(dc_crtc->cf_cont,
adj->crtc_hdisplay, adj->crtc_vdisplay);
@@ -273,7 +285,22 @@ dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
dc_fg_shdtokgen(dc_crtc->fg);
+
+ /* Don't relinquish CPU until TCON is set to operation mode. */
+ local_irq_save(flags);
+ preempt_disable();
+
dc_fg_enable(dc_crtc->fg);
+ /*
+ * Turn TCON into operation mode as soon as the first dumb
+ * frame is generated by DC(we don't relinquish CPU to ensure
+ * this). This makes DPR/PRG be able to evade the frame.
+ */
+ DC_CRTC_WAIT_FOR_FRAMEGEN_FRAME_INDEX_MOVING(dc_crtc->fg);
+ dc_tc_set_operation_mode(dc_crtc->tc);
+
+ local_irq_restore(flags);
+ preempt_enable();

DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdload_done);
DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdload_done);
@@ -561,6 +588,7 @@ int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
dc_crtc->ed_cont = pe->ed_cont[crtc_index];
dc_crtc->ed_safe = pe->ed_safe[crtc_index];
dc_crtc->fg = de->fg;
+ dc_crtc->tc = de->tc;

dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
index 211f3fcc1a9ad642617d3b22e35ea923f75e645b..c39f2ef5eea98c3eb6ae9b5392f9bf9f7e33e7c5 100644
--- a/drivers/gpu/drm/imx/dc/dc-de.h
+++ b/drivers/gpu/drm/imx/dc/dc-de.h
@@ -54,6 +54,8 @@ 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_set_operation_mode(struct dc_tc *tc);
+void dc_tc_cfg_videomode(struct dc_tc *tc, struct drm_display_mode *m);
void dc_tc_init(struct dc_tc *tc);

#endif /* __DC_DISPLAY_ENGINE_H__ */
diff --git a/drivers/gpu/drm/imx/dc/dc-kms.h b/drivers/gpu/drm/imx/dc/dc-kms.h
index cd7860eff986a272f6983ad0f3cc87dbf40c2851..a25d47eebd28792e4b53b4ecc89907ce00430c2c 100644
--- a/drivers/gpu/drm/imx/dc/dc-kms.h
+++ b/drivers/gpu/drm/imx/dc/dc-kms.h
@@ -50,6 +50,8 @@ struct dc_crtc {
struct dc_ed *ed_safe;
/** @fg: framegen */
struct dc_fg *fg;
+ /** @tc: tcon */
+ struct dc_tc *tc;
/**
* @irq_dec_framecomplete:
*
diff --git a/drivers/gpu/drm/imx/dc/dc-tc.c b/drivers/gpu/drm/imx/dc/dc-tc.c
index 0bfd381b2cea15444c399f3ad261e2d061ea1c9f..6f1dc71f1b40cb4d99ca177172bd0066f39e8314 100644
--- a/drivers/gpu/drm/imx/dc/dc-tc.c
+++ b/drivers/gpu/drm/imx/dc/dc-tc.c
@@ -9,11 +9,30 @@
#include <linux/platform_device.h>
#include <linux/regmap.h>

+#include <drm/drm_modes.h>
+
#include "dc-drv.h"
#include "dc-de.h"

#define TCON_CTRL 0x410
-#define CTRL_RST_VAL 0x01401408
+#define SPLITPOSITION_MASK GENMASK(29, 16)
+#define SPLITPOSITION(n) FIELD_PREP(SPLITPOSITION_MASK, (n))
+#define DUAL_SWAP BIT(15)
+#define MINILVDS_OPCODE_MASK GENMASK(14, 12)
+#define MODE_4PAIRS FIELD_PREP(MINILVDS_OPCODE_MASK, 0x1)
+#define LVDS_CLOCK_INV BIT(11)
+#define LVDS_BALANCE BIT(10)
+#define LVDSMODE BIT(9)
+#define ENLVDS BIT(8)
+#define INV_CTRL_MASK GENMASK(7, 4)
+#define BYPASS BIT(3)
+#define TCON_SYNC BIT(2)
+#define CHANNELMODE_MASK GENMASK(1, 0)
+#define CTRL_RST_MASK \
+ (SPLITPOSITION_MASK | DUAL_SWAP | MINILVDS_OPCODE_MASK | \
+ LVDS_CLOCK_INV | LVDS_BALANCE | LVDSMODE | ENLVDS | \
+ INV_CTRL_MASK | TCON_SYNC | CHANNELMODE_MASK)
+#define CTRL_RST_VAL (SPLITPOSITION(0x140) | MODE_4PAIRS | LVDS_BALANCE)

/* red: MAPBIT 29-20, green: MAPBIT 19-10, blue: MAPBIT 9-0 */
#define MAPBIT3_0 0x418
@@ -25,6 +44,16 @@
#define MAPBIT27_24 0x430
#define MAPBIT31_28 0x434

+#define SPGPOSON(n) (0x460 + (n) * 16)
+#define SPGMASKON(n) (0x464 + (n) * 16)
+#define SPGPOSOFF(n) (0x468 + (n) * 16)
+#define SPGMASKOFF(n) (0x46c + (n) * 16)
+#define X(n) FIELD_PREP(GENMASK(30, 16), (n))
+#define Y(n) FIELD_PREP(GENMASK(14, 0), (n))
+
+#define SMXSIGS(n) (0x520 + (n) * 8)
+#define SMXFCTTABLE(n) (0x524 + (n) * 8)
+
static const struct dc_subdev_info dc_tc_info[] = {
{ .reg_start = 0x5618c800, .id = 0, },
{ .reg_start = 0x5618e400, .id = 1, },
@@ -33,6 +62,8 @@ static const struct dc_subdev_info dc_tc_info[] = {
static const struct regmap_range dc_tc_regmap_ranges[] = {
regmap_reg_range(TCON_CTRL, TCON_CTRL),
regmap_reg_range(MAPBIT3_0, MAPBIT31_28),
+ regmap_reg_range(SPGPOSON(0), SPGMASKOFF(4)),
+ regmap_reg_range(SMXSIGS(0), SMXFCTTABLE(3)),
};

static const struct regmap_access_table dc_tc_regmap_access_table = {
@@ -47,7 +78,7 @@ static const struct regmap_config dc_tc_regmap_config = {
.fast_io = true,
.wr_table = &dc_tc_regmap_access_table,
.rd_table = &dc_tc_regmap_access_table,
- .max_register = MAPBIT31_28,
+ .max_register = SMXFCTTABLE(3),
};

/*
@@ -60,10 +91,85 @@ static const u32 dc_tc_mapbit[] = {
0x13121110, 0x03020100, 0x07060504, 0x00000908,
};

+void dc_tc_set_operation_mode(struct dc_tc *tc)
+{
+ regmap_write_bits(tc->reg, TCON_CTRL, BYPASS, 0);
+}
+
+void dc_tc_cfg_videomode(struct dc_tc *tc, struct drm_display_mode *m)
+{
+ int hdisplay, hsync_start, hsync_end;
+ int vdisplay, vsync_start, vsync_end;
+ int y;
+
+ hdisplay = m->hdisplay;
+ vdisplay = m->vdisplay;
+ hsync_start = m->hsync_start;
+ vsync_start = m->vsync_start;
+ hsync_end = m->hsync_end;
+ vsync_end = m->vsync_end;
+
+ /*
+ * Turn TCON into operation mode later after the first dumb frame is
+ * generated by DC. This makes DPR/PRG be able to evade the frame.
+ */
+ regmap_write_bits(tc->reg, TCON_CTRL, BYPASS, BYPASS);
+
+ /* dsp_control[0]: HSYNC */
+ regmap_write(tc->reg, SPGPOSON(0), X(hsync_start));
+ regmap_write(tc->reg, SPGMASKON(0), 0xffff);
+
+ regmap_write(tc->reg, SPGPOSOFF(0), X(hsync_end));
+ regmap_write(tc->reg, SPGMASKOFF(0), 0xffff);
+
+ regmap_write(tc->reg, SMXSIGS(0), 0x2);
+ regmap_write(tc->reg, SMXFCTTABLE(0), 0x1);
+
+ /* dsp_control[1]: VSYNC */
+ regmap_write(tc->reg, SPGPOSON(1), X(hsync_start) | Y(vsync_start - 1));
+ regmap_write(tc->reg, SPGMASKON(1), 0x0);
+
+ regmap_write(tc->reg, SPGPOSOFF(1), X(hsync_start) | Y(vsync_end - 1));
+ regmap_write(tc->reg, SPGMASKOFF(1), 0x0);
+
+ regmap_write(tc->reg, SMXSIGS(1), 0x3);
+ regmap_write(tc->reg, SMXFCTTABLE(1), 0x1);
+
+ /* dsp_control[2]: data enable */
+ /* horizontal */
+ regmap_write(tc->reg, SPGPOSON(2), 0x0);
+ regmap_write(tc->reg, SPGMASKON(2), 0xffff);
+
+ regmap_write(tc->reg, SPGPOSOFF(2), X(hdisplay));
+ regmap_write(tc->reg, SPGMASKOFF(2), 0xffff);
+
+ /* vertical */
+ regmap_write(tc->reg, SPGPOSON(3), 0x0);
+ regmap_write(tc->reg, SPGMASKON(3), 0x7fff0000);
+
+ regmap_write(tc->reg, SPGPOSOFF(3), Y(vdisplay));
+ regmap_write(tc->reg, SPGMASKOFF(3), 0x7fff0000);
+
+ regmap_write(tc->reg, SMXSIGS(2), 0x2c);
+ regmap_write(tc->reg, SMXFCTTABLE(2), 0x8);
+
+ /* dsp_control[3]: KACHUNK */
+ y = vdisplay + 1;
+
+ regmap_write(tc->reg, SPGPOSON(4), X(0x0) | Y(y));
+ regmap_write(tc->reg, SPGMASKON(4), 0x0);
+
+ regmap_write(tc->reg, SPGPOSOFF(4), X(0x20) | Y(y));
+ regmap_write(tc->reg, SPGMASKOFF(4), 0x0);
+
+ regmap_write(tc->reg, SMXSIGS(3), 0x6);
+ regmap_write(tc->reg, SMXFCTTABLE(3), 0x2);
+}
+
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);
+ /* reset TCON_CTRL to POR default except for touching BYPASS bit */
+ regmap_write_bits(tc->reg, TCON_CTRL, CTRL_RST_MASK, CTRL_RST_VAL);

/* set format */
regmap_bulk_write(tc->reg, MAPBIT3_0, dc_tc_mapbit,

--
2.34.1