On Tue, Sep 23, 2025 at 10:07:58AM +0800, Liu Ying wrote: > 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 <[email protected]>
Reviewed-by: Frank Li <[email protected]> --- > 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 >
