Devicetree
 help / color / mirror / Atom feed
* [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support
@ 2026-07-13  9:58 AngeloGioacchino Del Regno
  2026-07-13  9:58 ` [PATCH v2 01/10] dt-bindings: display: mediatek: dsc: Add MT8196 compatible AngeloGioacchino Del Regno
                   ` (9 more replies)
  0 siblings, 10 replies; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin

Changes in v2:
 - Rebased on next-20260706

This series adds support for:
 - Display Stream Compression (DSC) for DSI and DisplayPort for both
   legacy (8188/92/95 and others) and for Kompanio Ultra MT8196 SoCs
 - Write DMA (WDMA) Engine (for legacy only at this time) as a step
   to enable Writeback support (coming later with a restructuring of
   the entire mediatek-drm driver)
 - Newer MIPI DSI IP revisions, found in MT8189 and MT8196 SoCs

AngeloGioacchino Del Regno (10):
  dt-bindings: display: mediatek: dsc: Add MT8196 compatible
  drm/mediatek: Implement Display Stream Compression support
  dt-bindings: display: mediatek: dsi: Document MT8189 and MT8196
  drm/mediatek: mtk_dsi: Enable interrupt at component bind time
  drm/mediatek: mtk_dsi: Transfer register offsets to per-SoC const
  drm/mediatek: mtk_dsi: Add support for MT8189
  drm/mediatek: mtk_dsi: Add support for MT8196
  drm/mediatek: mtk_dsi: Enable PM Runtime on probe
  dt-bindings: display: mediatek: wdma: Add compatibles for more SoCs
  drm/mediatek: Add Write DMA (WDMA) Engine for Writeback support

 .../display/mediatek/mediatek,dsc.yaml        |   4 +-
 .../display/mediatek/mediatek,dsi.yaml        |   2 +
 .../display/mediatek/mediatek,wdma.yaml       |   5 +
 drivers/gpu/drm/mediatek/Makefile             |   2 +
 drivers/gpu/drm/mediatek/mtk_crtc.c           |  21 +
 drivers/gpu/drm/mediatek/mtk_ddp_comp.c       |  64 +-
 drivers/gpu/drm/mediatek/mtk_ddp_comp.h       |   9 +
 drivers/gpu/drm/mediatek/mtk_disp_drv.h       |  29 +
 drivers/gpu/drm/mediatek/mtk_disp_dsc.c       | 435 +++++++++++
 drivers/gpu/drm/mediatek/mtk_disp_wdma.c      | 611 +++++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_drv.c        |   8 +
 drivers/gpu/drm/mediatek/mtk_drm_drv.h        |   2 +
 drivers/gpu/drm/mediatek/mtk_dsi.c            | 733 ++++++++++++++----
 13 files changed, 1741 insertions(+), 184 deletions(-)
 create mode 100644 drivers/gpu/drm/mediatek/mtk_disp_dsc.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_disp_wdma.c

-- 
2.54.0


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v2 01/10] dt-bindings: display: mediatek: dsc: Add MT8196 compatible
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13  9:58 ` [PATCH v2 02/10] drm/mediatek: Implement Display Stream Compression support AngeloGioacchino Del Regno
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin

Add compatible for the Display Stream Compression (DSC) IP found
in the display controller of the MT8196 SoC.

This IP is compatible with the one found in MT8195.

Acked-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 .../devicetree/bindings/display/mediatek/mediatek,dsc.yaml    | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,dsc.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsc.yaml
index a5b88eb97e3b..c8b3e86943e4 100644
--- a/Documentation/devicetree/bindings/display/mediatek/mediatek,dsc.yaml
+++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsc.yaml
@@ -23,7 +23,9 @@ properties:
       - enum:
           - mediatek,mt8195-disp-dsc
       - items:
-          - const: mediatek,mt8188-disp-dsc
+          - enum:
+              - mediatek,mt8188-disp-dsc
+              - mediatek,mt8196-disp-dsc
           - const: mediatek,mt8195-disp-dsc
 
   reg:
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 02/10] drm/mediatek: Implement Display Stream Compression support
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
  2026-07-13  9:58 ` [PATCH v2 01/10] dt-bindings: display: mediatek: dsc: Add MT8196 compatible AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13 10:13   ` sashiko-bot
  2026-07-13  9:58 ` [PATCH v2 03/10] dt-bindings: display: mediatek: dsi: Document MT8189 and MT8196 AngeloGioacchino Del Regno
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin

Add a real driver for the Display Stream Compression (DSC) Display
Controller IP, implementing support for DSC v1.1 to v1.2.

In order to do this, it was necessary to remove the basic DSC IP
bypass setup from mtk_ddp_comp: this functionality is retained in
the new mtk_disp_dsc driver, which checks if DSC was actually
requested by other components (with the only one that currently
supports this being DSI) and, if not, it will set BYPASS mode in
the DSC IP.

Like before, the BYPASS mode is set before starting the DSC IP,
but unlike before, this is being done in the component start
callback instead of the config one.
Notably, the config callback is called by mtk_crtc always
immediately before the calling start callback, so the order of
register writes is retained.
The only real difference is that now this is being done through
CPU writes instead of CMDQ, but since that's called only once
and since it's just three registers, the performance impact will
not be minimal and not even measurable.

As anticipated, DSC handling was also introduced in the mtk_dsi
driver: when performing dsi_host_attach, the driver now checks
if the DSI panel adds the DSC configuration structure to the
mipi_dsi_device structure and, if it does, it will store a
pointer in the driver-local mtk_dsi structure's `dsc` member.

The DSI driver will then check whether the DSC configuration
that comes from the panel is valid (in regard to MediaTek DSI)
and will call the DRM API's DSC helpers to calculate and set
all of the const and RC parameters for the actual DSC setup.

For the time being, even though the latest MediaTek SoCs do
support DSC v1.2, only DSC v1.1 pre-scr support is implemented
as an initial contribution (which is rather big, and 1.2 would
make it even bigger - but that can anyway be implemented later).

As a last step for validation of DSC parameters in DSI, a check
for the hdisplay against DSC slice sidth and one for vdisplay
against DSC slice height was added to the mode_valid callback,
making sure that H/V are, as expected, multiples of slice W/H.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 drivers/gpu/drm/mediatek/Makefile       |   1 +
 drivers/gpu/drm/mediatek/mtk_crtc.c     |  21 ++
 drivers/gpu/drm/mediatek/mtk_ddp_comp.c |  43 +--
 drivers/gpu/drm/mediatek/mtk_ddp_comp.h |   9 +
 drivers/gpu/drm/mediatek/mtk_disp_drv.h |   9 +
 drivers/gpu/drm/mediatek/mtk_disp_dsc.c | 435 ++++++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_drv.c  |   2 +
 drivers/gpu/drm/mediatek/mtk_drm_drv.h  |   1 +
 drivers/gpu/drm/mediatek/mtk_dsi.c      | 110 +++++-
 9 files changed, 581 insertions(+), 50 deletions(-)
 create mode 100644 drivers/gpu/drm/mediatek/mtk_disp_dsc.c

diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile
index 952d294642fb..03b3470ea5b5 100644
--- a/drivers/gpu/drm/mediatek/Makefile
+++ b/drivers/gpu/drm/mediatek/Makefile
@@ -5,6 +5,7 @@ mediatek-drm-y := mtk_crtc.o \
 		  mtk_disp_aal.o \
 		  mtk_disp_ccorr.o \
 		  mtk_disp_color.o \
+		  mtk_disp_dsc.o \
 		  mtk_disp_gamma.o \
 		  mtk_disp_merge.o \
 		  mtk_disp_ovl.o \
diff --git a/drivers/gpu/drm/mediatek/mtk_crtc.c b/drivers/gpu/drm/mediatek/mtk_crtc.c
index 8e552cdc3b53..d4be40b2574a 100644
--- a/drivers/gpu/drm/mediatek/mtk_crtc.c
+++ b/drivers/gpu/drm/mediatek/mtk_crtc.c
@@ -22,6 +22,7 @@
 
 #include "mtk_crtc.h"
 #include "mtk_ddp_comp.h"
+#include "mtk_disp_drv.h"
 #include "mtk_drm_drv.h"
 #include "mtk_plane.h"
 
@@ -344,6 +345,8 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc)
 	struct drm_connector *connector;
 	struct drm_encoder *encoder;
 	struct drm_connector_list_iter conn_iter;
+	struct mtk_ddp_comp *comp_dsi = NULL, *comp_dsc = NULL;
+	struct drm_dsc_config *dsc_cfg;
 	struct drm_device *dev = mtk_crtc->base.dev;
 	unsigned int width, height, vrefresh, bpc = MTK_MAX_BPC;
 	int ret;
@@ -398,6 +401,17 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc)
 		if (!mtk_ddp_comp_add(mtk_crtc->ddp_comp[i], mtk_crtc->mutex))
 			mtk_mutex_add_comp(mtk_crtc->mutex,
 					   mtk_crtc->ddp_comp[i]->id);
+
+		/* For now, only single DSI is supported */
+		if (mtk_crtc->ddp_comp[i]->id >= DDP_COMPONENT_DSI0 &&
+		    mtk_crtc->ddp_comp[i]->id <= DDP_COMPONENT_DSI3)
+			if (!comp_dsi)
+				comp_dsi = mtk_crtc->ddp_comp[i];
+
+		if (mtk_crtc->ddp_comp[i]->id == DDP_COMPONENT_DSC0 ||
+		    mtk_crtc->ddp_comp[i]->id == DDP_COMPONENT_DSC1)
+			if (!comp_dsc)
+				comp_dsc = mtk_crtc->ddp_comp[i];
 	}
 	if (!mtk_ddp_comp_add(mtk_crtc->ddp_comp[i], mtk_crtc->mutex))
 		mtk_mutex_add_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id);
@@ -413,6 +427,13 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc)
 		mtk_ddp_comp_start(comp);
 	}
 
+	/* Setup the DSC if present, with the config coming from DSI */
+	if (comp_dsc && comp_dsi) {
+		dsc_cfg = mtk_dsi_get_dsc_config(comp_dsi->dev);
+		if (dsc_cfg)
+			mtk_ddp_comp_dsc_setup(comp_dsc, dsc_cfg);
+	}
+
 	/* Initially configure all planes */
 	for (i = 0; i < mtk_crtc->layer_nr; i++) {
 		struct drm_plane *plane = &mtk_crtc->planes[i];
diff --git a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c
index 9672ea1f91a2..13aaf12ecbe5 100644
--- a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c
+++ b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c
@@ -40,12 +40,6 @@
 #define DITHER_LSB_ERR_SHIFT_G(x)		(((x) & 0x7) << 12)
 #define DITHER_ADD_LSHIFT_G(x)			(((x) & 0x7) << 4)
 
-#define DISP_REG_DSC_CON			0x0000
-#define DSC_EN					BIT(0)
-#define DSC_DUAL_INOUT				BIT(2)
-#define DSC_BYPASS				BIT(4)
-#define DSC_UFOE_SEL				BIT(16)
-
 #define DISP_REG_OD_EN				0x0000
 #define DISP_REG_OD_CFG				0x0020
 #define OD_RELAYMODE				BIT(0)
@@ -187,36 +181,6 @@ static void mtk_dither_set(struct device *dev, unsigned int bpc,
 			      DISP_DITHERING, cmdq_pkt);
 }
 
-static void mtk_dsc_config(struct device *dev, unsigned int w,
-			   unsigned int h, unsigned int vrefresh,
-			   unsigned int bpc, struct cmdq_pkt *cmdq_pkt)
-{
-	struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev);
-
-	/* dsc bypass mode */
-	mtk_ddp_write_mask(cmdq_pkt, DSC_BYPASS, &priv->cmdq_reg, priv->regs,
-			   DISP_REG_DSC_CON, DSC_BYPASS);
-	mtk_ddp_write_mask(cmdq_pkt, DSC_UFOE_SEL, &priv->cmdq_reg, priv->regs,
-			   DISP_REG_DSC_CON, DSC_UFOE_SEL);
-	mtk_ddp_write_mask(cmdq_pkt, DSC_DUAL_INOUT, &priv->cmdq_reg, priv->regs,
-			   DISP_REG_DSC_CON, DSC_DUAL_INOUT);
-}
-
-static void mtk_dsc_start(struct device *dev)
-{
-	struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev);
-
-	/* write with mask to reserve the value set in mtk_dsc_config */
-	mtk_ddp_write_mask(NULL, DSC_EN, &priv->cmdq_reg, priv->regs, DISP_REG_DSC_CON, DSC_EN);
-}
-
-static void mtk_dsc_stop(struct device *dev)
-{
-	struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev);
-
-	writel_relaxed(0x0, priv->regs + DISP_REG_DSC_CON);
-}
-
 static void mtk_od_config(struct device *dev, unsigned int w,
 			  unsigned int h, unsigned int vrefresh,
 			  unsigned int bpc, struct cmdq_pkt *cmdq_pkt)
@@ -309,9 +273,9 @@ static const struct mtk_ddp_comp_funcs ddp_dpi = {
 };
 
 static const struct mtk_ddp_comp_funcs ddp_dsc = {
-	.clk_enable = mtk_ddp_clk_enable,
-	.clk_disable = mtk_ddp_clk_disable,
-	.config = mtk_dsc_config,
+	.clk_enable = mtk_dsc_clk_enable,
+	.clk_disable = mtk_dsc_clk_disable,
+	.dsc_setup = mtk_dsc_setup,
 	.start = mtk_dsc_start,
 	.stop = mtk_dsc_stop,
 };
@@ -671,6 +635,7 @@ int mtk_ddp_comp_init(struct device *dev, struct device_node *node, struct mtk_d
 	    type == MTK_DISP_BLS ||
 	    type == MTK_DISP_CCORR ||
 	    type == MTK_DISP_COLOR ||
+	    type == MTK_DISP_DSC ||
 	    type == MTK_DISP_GAMMA ||
 	    type == MTK_DISP_MERGE ||
 	    type == MTK_DISP_OVL ||
diff --git a/drivers/gpu/drm/mediatek/mtk_ddp_comp.h b/drivers/gpu/drm/mediatek/mtk_ddp_comp.h
index 3f3d43f4330d..99bf1e1015da 100644
--- a/drivers/gpu/drm/mediatek/mtk_ddp_comp.h
+++ b/drivers/gpu/drm/mediatek/mtk_ddp_comp.h
@@ -20,6 +20,7 @@ struct drm_crtc;
 struct drm_device;
 struct mtk_plane_state;
 struct drm_crtc_state;
+struct drm_dsc_config;
 
 enum mtk_ddp_comp_type {
 	MTK_DISP_AAL,
@@ -56,6 +57,7 @@ struct mtk_ddp_comp_funcs {
 	void (*config)(struct device *dev, unsigned int w,
 		       unsigned int h, unsigned int vrefresh,
 		       unsigned int bpc, struct cmdq_pkt *cmdq_pkt);
+	void (*dsc_setup)(struct device *dev, struct drm_dsc_config *dsc);
 	void (*start)(struct device *dev);
 	void (*stop)(struct device *dev);
 	void (*register_vblank_cb)(struct device *dev,
@@ -149,6 +151,13 @@ static inline void mtk_ddp_comp_config(struct mtk_ddp_comp *comp,
 		comp->funcs->config(comp->dev, w, h, vrefresh, bpc, cmdq_pkt);
 }
 
+static inline void mtk_ddp_comp_dsc_setup(struct mtk_ddp_comp *comp,
+					  struct drm_dsc_config *dsc)
+{
+	if (comp->funcs && comp->funcs->dsc_setup)
+		comp->funcs->dsc_setup(comp->dev, dsc);
+}
+
 static inline void mtk_ddp_comp_start(struct mtk_ddp_comp *comp)
 {
 	if (comp->funcs && comp->funcs->start)
diff --git a/drivers/gpu/drm/mediatek/mtk_disp_drv.h b/drivers/gpu/drm/mediatek/mtk_disp_drv.h
index 679d413bf10b..5e2d8748120a 100644
--- a/drivers/gpu/drm/mediatek/mtk_disp_drv.h
+++ b/drivers/gpu/drm/mediatek/mtk_disp_drv.h
@@ -12,6 +12,8 @@
 #include "mtk_mdp_rdma.h"
 #include "mtk_plane.h"
 
+struct drm_dsc_config;
+
 int mtk_aal_clk_enable(struct device *dev);
 void mtk_aal_clk_disable(struct device *dev);
 void mtk_aal_config(struct device *dev, unsigned int w,
@@ -47,9 +49,16 @@ void mtk_dpi_start(struct device *dev);
 void mtk_dpi_stop(struct device *dev);
 unsigned int mtk_dpi_encoder_index(struct device *dev);
 
+int mtk_dsc_clk_enable(struct device *dev);
+void mtk_dsc_clk_disable(struct device *dev);
+void mtk_dsc_setup(struct device *dev, struct drm_dsc_config *dsc_cfg);
+void mtk_dsc_start(struct device *dev);
+void mtk_dsc_stop(struct device *dev);
+
 void mtk_dsi_ddp_start(struct device *dev);
 void mtk_dsi_ddp_stop(struct device *dev);
 unsigned int mtk_dsi_encoder_index(struct device *dev);
+struct drm_dsc_config *mtk_dsi_get_dsc_config(struct device *dev);
 
 int mtk_gamma_clk_enable(struct device *dev);
 void mtk_gamma_clk_disable(struct device *dev);
diff --git a/drivers/gpu/drm/mediatek/mtk_disp_dsc.c b/drivers/gpu/drm/mediatek/mtk_disp_dsc.c
new file mode 100644
index 000000000000..79b66bd7e5f3
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_disp_dsc.c
@@ -0,0 +1,435 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021 MediaTek Inc.
+ * Copyright (c) 2025 Collabora Ltd
+ *                    AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/soc/mediatek/mtk-cmdq.h>
+
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+
+#include "mtk_crtc.h"
+#include "mtk_ddp_comp.h"
+#include "mtk_disp_drv.h"
+
+#define DISP_REG_DSC_CON		0x0
+#  define DSC_EN			BIT(0)
+#  define DSC_DUAL_INOUT		BIT(2)
+#  define DSC_IN_SRC_SEL		BIT(3)
+#  define DSC_BYPASS			BIT(4)
+#  define DSC_RELAY			BIT(5)
+#  define DSC_V1_1_EXT			BIT(6)
+#  define DSC_PT_MEM_EN			BIT(7)
+#  define DSC_SW_RESET			BIT(8)
+#  define DSC_EMPTY_FLAG_SEL		GENMASK(15, 14)
+ #  define DSC_EMPTY_FLAG_NORMAL_DET	0
+ #  define DSC_EMPTY_FLAG_ALWAYS_HIGH	1
+ #  define DSC_EMPTY_FLAG_ALWAYS_LOW	2
+ #  define DSC_EMPTY_FLAG_DO_NOT_SEND	3
+#  define DSC_UFOE_SEL			BIT(16)
+#  define DSC_OUTPUT_SWAP		BIT(18)
+#  define DSC_ZERO_FIFO_STALL_DISABLE	BIT(20)
+
+#define DISP_REG_DSC_SPR		0x14
+#define DISP_REG_DSC_PIC_W		0x18
+#  define DSC_PIC_WIDTH			GENMASK(15, 0)
+#  define DSC_PIC_GROUP_WIDTH_M1	GENMASK(31, 16)
+
+#define DISP_REG_DSC_PIC_H		0x1c
+#  define DSC_PIC_HEIGHT		GENMASK(15, 0)
+#  define DSC_PIC_HEIGHT_EXT_M1		GENMASK(31, 16)
+
+#define DISP_REG_DSC_SLICE_W		0x20
+#  define DSC_SLICE_WIDTH		GENMASK(15, 0)
+#  define DSC_SLICE_GROUP_WIDTH_M1	GENMASK(31, 16)
+
+#define DISP_REG_DSC_SLICE_H		0x24
+#  define DSC_SLICE_HEIGHT_M1		GENMASK(15, 0)
+#  define DSC_SLICE_NUM_M1		GENMASK(29, 16)
+#  define DSC_SLICE_WIDTH_MOD3		GENMASK(31, 30)
+
+#define DISP_REG_DSC_CHUNK_SIZE		0x28
+
+#define DISP_REG_DSC_BUF_SIZE		0x2c
+#  define DISP_DSC_BUF_SIZE_MASK	GENMASK(23, 0)
+
+#define DISP_REG_DSC_MODE		0x30
+#  define DSC_RGB_SWAP			BIT(2)
+#  define DSC_INIT_DELAY_HEIGHT		GENMASK(11, 8)
+
+#define DISP_REG_DSC_CFG		0x34
+#  define DSC_CFG_FLATNESS_DET_THRES	GENMASK(4, 0)
+#  define DSC_CFG_FLATNESS_8BITS	2
+#  define DSC_CFG_FLATNESS_10BITS	8
+#  define DSC_CFG_ICH_EN		BIT(5)
+#  define DSC_CFG_ICH_LINE_CLEAR	GENMASK(7, 6)
+#  define DSC_CFG_V1P1			BIT(8)
+#  define DSC_CFG_IDLE_MODE		BIT(9)
+#  define DSC_CFG_CRC_EN		BIT(12)
+#  define DSC_CFG_DSC12_BUGFIX		BIT(14)
+#  define DSC_CFG_CORE_CHECKSUM		BIT(15)
+
+#define DISP_REG_DSC_PAD		0x38
+#  define DSC_PAD_NUMBER		GENMASK(2, 0)
+
+#define DISP_REG_DSC_ENC_WIDTH		0x3c
+#  define DSC_ENC_WIDTH_SLICE		GENMASK(15, 0)
+#  define DSC_ENC_WIDTH_PIC		GENMASK(31, 16)
+
+#define DISP_REG_DSC_PIC_PRE_PAD_SIZE	0x40
+#  define DSC_PIC_PREPAD_HEIGHT		GENMASK(15, 0)
+#  define DSC_PIC_PREPAD_WIDTH		GENMASK(31, 16)
+
+#define DISP_REG_DSC_DBG_CON		0x60
+#  define DSC_CKSM_CAL_EN		BIT(9)
+
+#define DISP_REG_DSC_OUTBUF		0x70
+#  define DSC_OBUF_SIZE			GENMASK(11, 0)
+
+#define DISP_REG_DSC_PPS(x)		(0x80 + (x * 4)) /* 0..19 */
+#  define DSC_P0_UP_LINE_BUF_DEPTH	GENMASK(3, 0)
+#  define DSC_P0_BPC			GENMASK(7, 4)
+#  define DSC_P0_BPP			GENMASK(17, 8)
+#  define DSC_P0_RCT_ON			BIT(18)
+#  define DSC_P0_BLOCK_PRED_EN		BIT(19)
+#  define DSC_P1_INITIAL_XMIT_DELAY	GENMASK(15, 0)
+#  define DSC_P1_INITIAL_DEC_DELAY	GENMASK(31, 16)
+#  define DSC_P2_INITIAL_SCALE_VALUE	GENMASK(15, 0)
+#  define DSC_P2_SCALE_INCR_INTERVAL	GENMASK(31, 16)
+#  define DSC_P3_SCALE_DECR_INTERVAL	GENMASK(15, 0)
+#  define DSC_P3_FIRST_LINE_BPG_OFFSET	GENMASK(31, 16)
+#  define DSC_P4_NFL_BPG_OFFSET		GENMASK(15, 0)
+#  define DSC_P4_SLICE_BPG_OFFSET	GENMASK(31, 16)
+#  define DSC_P5_INITIAL_OFFSET		GENMASK(15, 0)
+#  define DSC_P5_FINAL_OFFSET		GENMASK(31, 16)
+#  define DSC_P6_FLATNESS_MIN_QP	GENMASK(4, 0)
+#  define DSC_P6_FLATNESS_MAX_QP	GENMASK(12, 8)
+#  define DSC_P6_RC_MODEL_SIZE		GENMASK(31, 16)
+#  define DSC_P7_RC_EDGE_FACTOR		GENMASK(7, 0)
+#  define DSC_P7_RC_QUANT_INCR_LIMIT0	GENMASK(12, 8)
+#  define DSC_P7_RC_QUANT_INCR_LIMIT1	GENMASK(20, 16)
+#  define DSC_P7_RC_TGT_OFFSET_HI	GENMASK(27, 24)
+#  define DSC_P7_RC_TGT_OFFSET_LO	GENMASK(31, 28)
+#  define DSC_P12_RC_RANGE_MIN_QP	GENMASK(4, 0)
+#  define DSC_P12_RC_RANGE_MAX_QP	GENMASK(9, 5)
+#  define DSC_P12_RC_RANGE_BPG_OFFSET	GENMASK(15, 10)
+
+#define DISP_REG_DSC_SHADOW		0x200
+#  define DSC_FORCE_COMMIT		BIT(0)
+#  define DSC_BYPASS_SHADOW		BIT(1)
+#  define DSC_READ_WORKING		BIT(2)
+#  define DSC_SHADOW_DSC_VERSION_MINOR	GENMASK(8, 5)
+
+struct mtk_dsc {
+	struct clk		*clk;
+	void __iomem		*reg;
+	struct cmdq_client_reg	cmdq_reg;
+	bool dsc_config_done;
+};
+
+int mtk_dsc_clk_enable(struct device *dev)
+{
+	struct mtk_dsc *disp_dsc = dev_get_drvdata(dev);
+
+	return clk_prepare_enable(disp_dsc->clk);
+}
+
+void mtk_dsc_clk_disable(struct device *dev)
+{
+	struct mtk_dsc *disp_dsc = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(disp_dsc->clk);
+}
+
+static void mtk_dsc_pps_setup(struct mtk_dsc *disp_dsc, struct drm_dsc_config *dsc_cfg)
+{
+	u32 data;
+	int i, j;
+
+	/* PPS 0 - Note: Fractional BPP not supported, check handled in mtk_dsi! */
+	data = FIELD_PREP(DSC_P0_UP_LINE_BUF_DEPTH, dsc_cfg->line_buf_depth);
+	data |= FIELD_PREP(DSC_P0_BPC, dsc_cfg->bits_per_component);
+	data |= FIELD_PREP(DSC_P0_BPP, dsc_cfg->bits_per_pixel);
+	data |= DSC_P0_RCT_ON | DSC_P0_BLOCK_PRED_EN;
+	writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(0));
+
+	/* PPS 1 */
+	data = FIELD_PREP(DSC_P1_INITIAL_XMIT_DELAY, dsc_cfg->initial_xmit_delay);
+	data |= FIELD_PREP(DSC_P1_INITIAL_DEC_DELAY, dsc_cfg->initial_dec_delay);
+	writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(1));
+
+	/* PPS 2 */
+	data = FIELD_PREP(DSC_P2_INITIAL_SCALE_VALUE, dsc_cfg->initial_scale_value);
+	data |= FIELD_PREP(DSC_P2_SCALE_INCR_INTERVAL, dsc_cfg->scale_increment_interval);
+	writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(2));
+
+	/* PPS 3 */
+	data = FIELD_PREP(DSC_P3_SCALE_DECR_INTERVAL, dsc_cfg->scale_decrement_interval);
+	data |= FIELD_PREP(DSC_P3_FIRST_LINE_BPG_OFFSET, dsc_cfg->first_line_bpg_offset);
+	writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(3));
+
+	/* PPS 4 */
+	data = FIELD_PREP(DSC_P4_NFL_BPG_OFFSET, dsc_cfg->nfl_bpg_offset);
+	data |= FIELD_PREP(DSC_P4_SLICE_BPG_OFFSET, dsc_cfg->slice_bpg_offset);
+	writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(4));
+
+	/* PPS 5 */
+	data = FIELD_PREP(DSC_P5_INITIAL_OFFSET, dsc_cfg->initial_offset);
+	data |= FIELD_PREP(DSC_P5_FINAL_OFFSET, dsc_cfg->final_offset);
+	writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(5));
+
+	/* PPS 6 */
+	data = FIELD_PREP(DSC_P6_FLATNESS_MIN_QP, dsc_cfg->flatness_min_qp);
+	data |= FIELD_PREP(DSC_P6_FLATNESS_MAX_QP, dsc_cfg->flatness_max_qp);
+	writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(6));
+
+	/* PPS 7 */
+	data = FIELD_PREP(DSC_P7_RC_EDGE_FACTOR, dsc_cfg->rc_edge_factor);
+	data |= FIELD_PREP(DSC_P7_RC_QUANT_INCR_LIMIT0, dsc_cfg->rc_quant_incr_limit0);
+	data |= FIELD_PREP(DSC_P7_RC_QUANT_INCR_LIMIT1, dsc_cfg->rc_quant_incr_limit1);
+	data |= FIELD_PREP(DSC_P7_RC_TGT_OFFSET_HI, dsc_cfg->rc_tgt_offset_high);
+	data |= FIELD_PREP(DSC_P7_RC_TGT_OFFSET_LO, dsc_cfg->rc_tgt_offset_low);
+	writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(7));
+
+	/* PPS 8..11 - Each register holds 4 RC buffer thresholds (PPS 11 has two) */
+	for (i = 0; i < 4; i++) {
+		u8 block_num = i * 4;
+		data = 0;
+
+		for (j = 0; j < 4; j++) {
+			u8 buf_index = block_num + j;
+			u8 data_shift = j * 8;
+
+			/* rc_buf_thresh holds 14 elements in total */
+			if (buf_index > 13)
+				break;
+
+			data |= dsc_cfg->rc_buf_thresh[buf_index] << data_shift;
+		}
+		writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(8 + i));
+	}
+
+	/* PPS 12..19 - Each register holds two sets of RC range parameters (19 has one) */
+	for (i = 0; i < 7; i++) {
+		struct drm_dsc_rc_range_parameters *rcrp = dsc_cfg->rc_range_params;
+		u8 block_num = i * 2;
+		data = 0;
+
+		for (j = 0; j < 2; j++) {
+			u8 buf_index = block_num + j;
+			u8 data_shift = j * 16;
+			u16 range_data;
+
+			range_data = FIELD_PREP(DSC_P12_RC_RANGE_MIN_QP,
+						 rcrp[buf_index].range_min_qp);
+			range_data |= FIELD_PREP(DSC_P12_RC_RANGE_MAX_QP,
+						 rcrp[buf_index].range_max_qp);
+			range_data |= FIELD_PREP(DSC_P12_RC_RANGE_BPG_OFFSET,
+						 rcrp[buf_index].range_bpg_offset);
+
+			range_data <<= data_shift;
+			data |= range_data;
+
+			/* rc_range_params holds 15 elements in total */
+			if (buf_index == 14)
+				break;
+		}
+		writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(12 + i));
+	}
+}
+
+void mtk_dsc_setup(struct device *dev, struct drm_dsc_config *dsc_cfg)
+{
+	struct mtk_dsc *disp_dsc = dev_get_drvdata(dev);
+	u32 dsc_slice_w, dsc_slice_h, dsc_mode, dsc_cfg_rval, dsc_shadow;
+	u32 dsc_dbg_con, dsc_con, dsc_enc_width, dsc_pic_w, dsc_pic_h;
+	u16 pic_group_width, pic_height_ext_num, slice_group_width;
+	u16 chunk_size, dsc_pad_num, dsc_pre_pad_sz;
+
+	pic_height_ext_num = dsc_cfg->pic_height + dsc_cfg->slice_height - 1;
+	pic_group_width = dsc_cfg->slice_width * 4;
+	pic_group_width /= 3;
+
+	slice_group_width = dsc_cfg->slice_width + 2;
+	slice_group_width /= 3;
+
+	if (dsc_cfg->slice_chunk_size)
+		chunk_size = dsc_cfg->slice_chunk_size;
+	else
+		chunk_size = dsc_cfg->slice_width * dsc_cfg->bits_per_pixel / 8 / 16;
+
+	dsc_enc_width = FIELD_PREP(DSC_ENC_WIDTH_PIC, dsc_cfg->pic_width) |
+			FIELD_PREP(DSC_ENC_WIDTH_SLICE, dsc_cfg->slice_width);
+
+	dsc_pic_w = FIELD_PREP(DSC_PIC_GROUP_WIDTH_M1, pic_group_width - 1);
+	dsc_pic_w |= FIELD_PREP(DSC_PIC_WIDTH, dsc_cfg->pic_width);
+	dsc_pic_h = FIELD_PREP(DSC_PIC_HEIGHT_EXT_M1, pic_height_ext_num - 1);
+	dsc_pic_h |= FIELD_PREP(DSC_PIC_HEIGHT, dsc_cfg->pic_height - 1);
+
+	dsc_slice_w = FIELD_PREP(DSC_SLICE_GROUP_WIDTH_M1, slice_group_width - 1);
+	dsc_slice_w |= FIELD_PREP(DSC_SLICE_WIDTH, dsc_cfg->slice_width);
+	dsc_slice_h = FIELD_PREP(DSC_SLICE_WIDTH_MOD3, dsc_cfg->slice_width % 3);
+	dsc_slice_h |= FIELD_PREP(DSC_SLICE_NUM_M1,
+				  (pic_height_ext_num / dsc_cfg->slice_height) - 1);
+	dsc_slice_h |= FIELD_PREP(DSC_SLICE_HEIGHT_M1, dsc_cfg->slice_height - 1);
+
+	dsc_pad_num = 3 - ((chunk_size * 2) % 3) % 3;
+	dsc_pad_num = FIELD_PREP(DSC_PAD_NUMBER, dsc_pad_num);
+
+	dsc_pre_pad_sz = FIELD_PREP(DSC_PIC_PREPAD_HEIGHT, dsc_cfg->pic_height);
+	dsc_pre_pad_sz |= FIELD_PREP(DSC_PIC_PREPAD_WIDTH, dsc_cfg->pic_width);
+
+	dsc_mode = FIELD_PREP(DSC_INIT_DELAY_HEIGHT, 4);
+	dsc_mode |= FIELD_PREP(DSC_RGB_SWAP, 0);
+
+	/* Must enable checksum calc in DBG if enabling core checksum in CFG */
+	dsc_cfg_rval = DSC_CFG_ICH_EN | DSC_CFG_CRC_EN | DSC_CFG_DSC12_BUGFIX |
+		       DSC_CFG_CORE_CHECKSUM;
+	dsc_dbg_con = DSC_CKSM_CAL_EN;
+
+	if (dsc_cfg->bits_per_component == 8)
+		dsc_cfg_rval |= FIELD_PREP_CONST(DSC_CFG_FLATNESS_DET_THRES,
+						 DSC_CFG_FLATNESS_8BITS);
+	else
+		dsc_cfg_rval |= FIELD_PREP_CONST(DSC_CFG_FLATNESS_DET_THRES,
+						 DSC_CFG_FLATNESS_10BITS);
+
+	dsc_shadow = FIELD_PREP(DSC_SHADOW_DSC_VERSION_MINOR,
+				dsc_cfg->dsc_version_minor);
+	dsc_shadow |= DSC_FORCE_COMMIT | DSC_BYPASS_SHADOW;
+
+	writel(0, disp_dsc->reg + DISP_REG_DSC_SPR);
+	writel(dsc_enc_width, disp_dsc->reg + DISP_REG_DSC_ENC_WIDTH);
+	writel(dsc_pic_w, disp_dsc->reg + DISP_REG_DSC_PIC_W);
+	writel(dsc_pic_h, disp_dsc->reg + DISP_REG_DSC_PIC_H);
+	writel(dsc_slice_w, disp_dsc->reg + DISP_REG_DSC_SLICE_W);
+	writel(dsc_slice_h, disp_dsc->reg + DISP_REG_DSC_SLICE_H);
+	writel(((chunk_size * 4) / 3) << 16 | chunk_size,
+	       disp_dsc->reg + DISP_REG_DSC_CHUNK_SIZE);
+	writel(dsc_pre_pad_sz, disp_dsc->reg + DISP_REG_DSC_PIC_PRE_PAD_SIZE);
+	writel(dsc_pad_num, disp_dsc->reg + DISP_REG_DSC_PAD);
+	writel(FIELD_PREP(DISP_DSC_BUF_SIZE_MASK, chunk_size * dsc_cfg->slice_height),
+	       disp_dsc->reg + DISP_REG_DSC_BUF_SIZE);
+	writel(dsc_mode, disp_dsc->reg + DISP_REG_DSC_MODE);
+	writel(dsc_cfg_rval, disp_dsc->reg + DISP_REG_DSC_CFG);
+	writel(dsc_dbg_con, disp_dsc->reg + DISP_REG_DSC_DBG_CON);
+	writel(FIELD_PREP_CONST(DSC_OBUF_SIZE, 1040), disp_dsc->reg + DISP_REG_DSC_OUTBUF);
+	writel(dsc_shadow, disp_dsc->reg + DISP_REG_DSC_SHADOW);
+
+	dsc_con = FIELD_PREP_CONST(DSC_EMPTY_FLAG_SEL, DSC_EMPTY_FLAG_ALWAYS_LOW);
+	dsc_con |= DSC_V1_1_EXT | DSC_UFOE_SEL | DSC_PT_MEM_EN;
+	dsc_con |= DSC_ZERO_FIFO_STALL_DISABLE;
+
+	writel(dsc_con, disp_dsc->reg + DISP_REG_DSC_CON);
+
+	mtk_dsc_pps_setup(disp_dsc, dsc_cfg);
+
+	disp_dsc->dsc_config_done = true;
+}
+
+void mtk_dsc_start(struct device *dev)
+{
+	struct mtk_dsc *disp_dsc = dev_get_drvdata(dev);
+
+	/* If no DSC or config not done, set bypass mode */
+	if (!disp_dsc->dsc_config_done) {
+		mtk_ddp_write_mask(NULL, DSC_BYPASS, &disp_dsc->cmdq_reg,
+				   disp_dsc->reg, DISP_REG_DSC_CON, DSC_BYPASS);
+		mtk_ddp_write_mask(NULL, DSC_UFOE_SEL, &disp_dsc->cmdq_reg,
+				   disp_dsc->reg, DISP_REG_DSC_CON, DSC_UFOE_SEL);
+		mtk_ddp_write_mask(NULL, DSC_DUAL_INOUT, &disp_dsc->cmdq_reg,
+				   disp_dsc->reg, DISP_REG_DSC_CON, DSC_DUAL_INOUT);
+	}
+
+	mtk_ddp_write_mask(NULL, DSC_EN, &disp_dsc->cmdq_reg,
+			   disp_dsc->reg, DISP_REG_DSC_CON, DSC_EN);
+}
+
+void mtk_dsc_stop(struct device *dev)
+{
+	struct mtk_dsc *disp_dsc = dev_get_drvdata(dev);
+
+	writel(0, disp_dsc->reg + DISP_REG_DSC_CON);
+}
+
+static int mtk_dsc_bind(struct device *dev, struct device *master, void *data)
+{
+	return 0;
+}
+
+static void mtk_dsc_unbind(struct device *dev, struct device *master, void *data)
+{
+}
+
+static const struct component_ops mtk_dsc_component_ops = {
+	.bind	= mtk_dsc_bind,
+	.unbind = mtk_dsc_unbind,
+};
+
+static int mtk_dsc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_dsc *priv;
+	struct resource *res;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(priv->clk))
+		return dev_err_probe(dev, PTR_ERR(priv->clk),
+				     "failed to get clk\n");
+
+	priv->reg = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+	if (IS_ERR(priv->reg))
+		return dev_err_probe(dev, PTR_ERR(priv->reg),
+				     "failed to do ioremap\n");
+
+#if IS_REACHABLE(CONFIG_MTK_CMDQ)
+	ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get gce client reg\n");
+#endif
+
+	platform_set_drvdata(pdev, priv);
+
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return ret;
+
+	ret = component_add(dev, &mtk_dsc_component_ops);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to add component\n");
+
+	return 0;
+}
+
+static void mtk_dsc_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &mtk_dsc_component_ops);
+}
+
+static const struct of_device_id mtk_dsc_driver_dt_match[] = {
+	{ .compatible = "mediatek,mt8195-disp-dsc" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mtk_dsc_driver_dt_match);
+
+struct platform_driver mtk_disp_dsc_driver = {
+	.probe		= mtk_dsc_probe,
+	.remove		= mtk_dsc_remove,
+	.driver		= {
+		.name	= "mediatek-disp-dsc",
+		.of_match_table = mtk_dsc_driver_dt_match,
+	},
+};
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
index c86a3f54f35b..bd2d17017bd2 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
@@ -1164,6 +1164,7 @@ static int mtk_drm_probe(struct platform_device *pdev)
 		if (comp_type == MTK_DISP_AAL ||
 		    comp_type == MTK_DISP_CCORR ||
 		    comp_type == MTK_DISP_COLOR ||
+		    comp_type == MTK_DISP_DSC ||
 		    comp_type == MTK_DISP_GAMMA ||
 		    comp_type == MTK_DISP_MERGE ||
 		    comp_type == MTK_DISP_OVL ||
@@ -1272,6 +1273,7 @@ static struct platform_driver * const mtk_drm_drivers[] = {
 	&mtk_disp_aal_driver,
 	&mtk_disp_ccorr_driver,
 	&mtk_disp_color_driver,
+	&mtk_disp_dsc_driver,
 	&mtk_disp_gamma_driver,
 	&mtk_disp_merge_driver,
 	&mtk_disp_ovl_adaptor_driver,
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
index d48da019a4a3..43aac2d956e7 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.h
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
@@ -71,6 +71,7 @@ struct mtk_drm_private {
 extern struct platform_driver mtk_disp_aal_driver;
 extern struct platform_driver mtk_disp_ccorr_driver;
 extern struct platform_driver mtk_disp_color_driver;
+extern struct platform_driver mtk_disp_dsc_driver;
 extern struct platform_driver mtk_disp_gamma_driver;
 extern struct platform_driver mtk_disp_merge_driver;
 extern struct platform_driver mtk_disp_ovl_adaptor_driver;
diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index 3f3f56eed3f9..3e22553ca990 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -18,6 +18,8 @@
 #include <video/mipi_display.h>
 #include <video/videomode.h>
 
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
@@ -71,11 +73,12 @@
 
 #define DSI_PSCTRL		0x1c
 #define DSI_PS_WC			GENMASK(13, 0)
-#define DSI_PS_SEL			GENMASK(17, 16)
+#define DSI_PS_SEL			GENMASK(19, 16)
 #define PACKED_PS_16BIT_RGB565		0
 #define PACKED_PS_18BIT_RGB666		1
 #define LOOSELY_PS_24BIT_RGB666		2
 #define PACKED_PS_24BIT_RGB888		3
+#define COMPRESSED_PS_DSC		5
 
 #define DSI_VSA_NL		0x20
 #define DSI_VBP_NL		0x24
@@ -203,6 +206,7 @@ struct mtk_dsi {
 	struct drm_bridge bridge;
 	struct drm_bridge *next_bridge;
 	struct drm_connector *connector;
+	struct drm_dsc_config *dsc;
 	struct phy *phy;
 
 	void __iomem *regs;
@@ -395,7 +399,7 @@ static void mtk_dsi_rxtx_control(struct mtk_dsi *dsi)
 
 static void mtk_dsi_ps_control(struct mtk_dsi *dsi, bool config_vact)
 {
-	u32 dsi_buf_bpp, ps_val, ps_wc, vact_nl;
+	u32 dsi_buf_bpp, ps_val, ps_wc, size_val, vact_nl;
 
 	if (dsi->format == MIPI_DSI_FMT_RGB565)
 		dsi_buf_bpp = 2;
@@ -403,7 +407,10 @@ static void mtk_dsi_ps_control(struct mtk_dsi *dsi, bool config_vact)
 		dsi_buf_bpp = 3;
 
 	/* Word count */
-	ps_wc = FIELD_PREP(DSI_PS_WC, dsi->vm.hactive * dsi_buf_bpp);
+	if (dsi->dsc)
+		ps_wc = FIELD_PREP(DSI_PS_WC, 2 * dsi->dsc->slice_chunk_size);
+	else
+		ps_wc = FIELD_PREP(DSI_PS_WC, dsi->vm.hactive * dsi_buf_bpp);
 	ps_val = ps_wc;
 
 	/* Pixel Stream type */
@@ -411,7 +418,10 @@ static void mtk_dsi_ps_control(struct mtk_dsi *dsi, bool config_vact)
 	default:
 		fallthrough;
 	case MIPI_DSI_FMT_RGB888:
-		ps_val |= FIELD_PREP(DSI_PS_SEL, PACKED_PS_24BIT_RGB888);
+		if (dsi->dsc)
+			ps_val |= FIELD_PREP(DSI_PS_SEL, COMPRESSED_PS_DSC);
+		else
+			ps_val |= FIELD_PREP(DSI_PS_SEL, PACKED_PS_24BIT_RGB888);
 		break;
 	case MIPI_DSI_FMT_RGB666:
 		ps_val |= FIELD_PREP(DSI_PS_SEL, LOOSELY_PS_24BIT_RGB666);
@@ -430,6 +440,18 @@ static void mtk_dsi_ps_control(struct mtk_dsi *dsi, bool config_vact)
 		writel(ps_wc, dsi->regs + DSI_HSTX_CKL_WC);
 	}
 	writel(ps_val, dsi->regs + DSI_PSCTRL);
+
+	if (dsi->driver_data->has_size_ctl) {
+		size_val = FIELD_PREP(DSI_HEIGHT, dsi->vm.vactive);
+		if (dsi->dsc)
+			size_val |= FIELD_PREP(DSI_WIDTH,
+					       (ps_wc + dsi_buf_bpp - 1) /
+					       dsi_buf_bpp);
+		else
+			size_val |= FIELD_PREP(DSI_WIDTH, dsi->vm.hactive);
+
+		writel(size_val, dsi->regs + DSI_SIZE_CON);
+	}
 }
 
 static void mtk_dsi_config_vdo_timing_per_frame_lp(struct mtk_dsi *dsi)
@@ -565,7 +587,43 @@ static void mtk_dsi_config_vdo_timing_per_line_lp(struct mtk_dsi *dsi)
 	writel(horizontal_frontporch_byte, dsi->regs + DSI_HFP_WC);
 }
 
-static void mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
+static int mtk_dsi_set_dsc_params(struct mtk_dsi *dsi)
+{
+	struct drm_dsc_config *dsc = dsi->dsc;
+	struct device *dev = dsi->host.dev;
+	int ret;
+
+	if (dsc->bits_per_pixel & GENMASK(7, 0)) {
+		dev_err(dev, "Fractional bits_per_pixel not supported\n");
+		return -EINVAL;
+	}
+
+	if (dsc->bits_per_component != 8) {
+		dev_err(dev, "%u bits per component is not supported\n",
+			dsc->bits_per_component);
+		return -EINVAL;
+	}
+
+	dsc->simple_422 = false;
+	dsc->convert_rgb = true;
+	dsc->vbr_enable = false;
+
+	drm_dsc_set_const_params(dsc);
+	drm_dsc_set_rc_buf_thresh(dsc);
+
+	ret = drm_dsc_setup_rc_params(dsc, DRM_DSC_1_1_PRE_SCR);
+	if (ret) {
+		dev_err(dev, "Cannot find DSC RC params\n");
+		return ret;
+	}
+
+	dsc->initial_scale_value = drm_dsc_initial_scale_value(dsc);
+	dsc->line_buf_depth = dsc->bits_per_component + 1;
+
+	return drm_dsc_compute_rc_parameters(dsc);
+}
+
+static int mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
 {
 	struct videomode *vm = &dsi->vm;
 
@@ -574,17 +632,17 @@ static void mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
 	writel(vm->vfront_porch, dsi->regs + DSI_VFP_NL);
 	writel(vm->vactive, dsi->regs + DSI_VACT_NL);
 
-	if (dsi->driver_data->has_size_ctl)
-		writel(FIELD_PREP(DSI_HEIGHT, vm->vactive) |
-			FIELD_PREP(DSI_WIDTH, vm->hactive),
-			dsi->regs + DSI_SIZE_CON);
-
 	if (dsi->driver_data->support_per_frame_lp)
 		mtk_dsi_config_vdo_timing_per_frame_lp(dsi);
 	else
 		mtk_dsi_config_vdo_timing_per_line_lp(dsi);
 
 	mtk_dsi_ps_control(dsi, false);
+
+	if (dsi->dsc)
+		return mtk_dsi_set_dsc_params(dsi);
+
+	return 0;
 }
 
 static void mtk_dsi_start(struct mtk_dsi *dsi)
@@ -741,12 +799,17 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi)
 
 	mtk_dsi_ps_control(dsi, true);
 	mtk_dsi_set_vm_cmd(dsi);
-	mtk_dsi_config_vdo_timing(dsi);
+	ret = mtk_dsi_config_vdo_timing(dsi);
+	if (ret)
+		goto err_disable_digital_clk;
+
 	mtk_dsi_set_interrupt_enable(dsi);
 	mtk_dsi_lane_ready(dsi);
 	mtk_dsi_clk_hs_mode(dsi, 1);
 
 	return 0;
+err_disable_digital_clk:
+	clk_disable_unprepare(dsi->digital_clk);
 err_disable_engine_clk:
 	clk_disable_unprepare(dsi->engine_clk);
 err_phy_power_off:
@@ -883,6 +946,21 @@ mtk_dsi_bridge_mode_valid(struct drm_bridge *bridge,
 	if (mode->clock * bpp / dsi->lanes > 1500000)
 		return MODE_CLOCK_HIGH;
 
+	if (dsi->dsc) {
+		if (mode->hdisplay % dsi->dsc->slice_width) {
+			dev_err(dsi->host.dev,
+				"DSC: hdisplay %u is not a multiple of slice width %u\n",
+				dsi->dsc->slice_width, mode->hdisplay);
+			return MODE_H_ILLEGAL;
+		}
+		if (mode->vdisplay % dsi->dsc->slice_height) {
+			dev_err(dsi->host.dev,
+				"DSC: vdisplay %u is not a multiple of slice height %u\n",
+				dsi->dsc->slice_height, mode->vdisplay);
+			return MODE_V_ILLEGAL;
+		}
+	}
+
 	return MODE_OK;
 }
 
@@ -913,6 +991,13 @@ void mtk_dsi_ddp_stop(struct device *dev)
 	mtk_dsi_poweroff(dsi);
 }
 
+struct drm_dsc_config *mtk_dsi_get_dsc_config(struct device *dev)
+{
+	struct mtk_dsi *dsi = dev_get_drvdata(dev);
+
+	return dsi->dsc;
+}
+
 static int mtk_dsi_encoder_init(struct drm_device *drm, struct mtk_dsi *dsi)
 {
 	int ret;
@@ -1006,6 +1091,9 @@ static int mtk_dsi_host_attach(struct mipi_dsi_host *host,
 			return PTR_ERR(dsi->next_bridge);
 	}
 
+	if (device->dsc)
+		dsi->dsc = device->dsc;
+
 	drm_bridge_add(&dsi->bridge);
 
 	ret = component_add(host->dev, &mtk_dsi_component_ops);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 03/10] dt-bindings: display: mediatek: dsi: Document MT8189 and MT8196
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
  2026-07-13  9:58 ` [PATCH v2 01/10] dt-bindings: display: mediatek: dsc: Add MT8196 compatible AngeloGioacchino Del Regno
  2026-07-13  9:58 ` [PATCH v2 02/10] drm/mediatek: Implement Display Stream Compression support AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13  9:58 ` [PATCH v2 04/10] drm/mediatek: mtk_dsi: Enable interrupt at component bind time AngeloGioacchino Del Regno
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin,
	Krzysztof Kozlowski

Add compatible strings for MT8189 and MT8196 SoCs.
These are compatible with the DSI IPs found in the MT8188/95 and
others, but with differences making them not fully compatible.

Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 .../devicetree/bindings/display/mediatek/mediatek,dsi.yaml      | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.yaml
index 27ffbccc2a08..b5cdfe0eaca4 100644
--- a/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.yaml
+++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.yaml
@@ -30,6 +30,8 @@ properties:
           - mediatek,mt8183-dsi
           - mediatek,mt8186-dsi
           - mediatek,mt8188-dsi
+          - mediatek,mt8189-dsi
+          - mediatek,mt8196-dsi
       - items:
           - enum:
               - mediatek,mt6795-dsi
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 04/10] drm/mediatek: mtk_dsi: Enable interrupt at component bind time
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
                   ` (2 preceding siblings ...)
  2026-07-13  9:58 ` [PATCH v2 03/10] dt-bindings: display: mediatek: dsi: Document MT8189 and MT8196 AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13 10:11   ` sashiko-bot
  2026-07-13  9:58 ` [PATCH v2 05/10] drm/mediatek: mtk_dsi: Transfer register offsets to per-SoC const AngeloGioacchino Del Regno
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin

Having the DSI interrupt enabled before actually binding the DSI
component to the display controller driver is both useless and
dangerous: the main purpose of this interrupt is to signal CMD
done, LP RX data ready, or VideoMode done, or to reset the HW
engine if this doesn't come.

Should this interrupt come too late (during probe), the HW will
be reset only at the next occurrence of a timeout, which slows
down boot and may render artifacts to the DSI display.

Moreover, clearing the DSI interrupt while the display controller
is not ready yet, may result in an interrupt storm.

In order to prevent this from happening, request the interrupt
with IRQF_NO_AUTOEN, and enable it only when binding DSI to its
display controller component master.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 drivers/gpu/drm/mediatek/mtk_dsi.c | 22 +++++++++++++++-------
 1 file changed, 15 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index 3e22553ca990..284dae372c7c 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -225,6 +225,7 @@ struct mtk_dsi {
 	int refcount;
 	bool enabled;
 	bool lanes_ready;
+	int irq;
 	u32 irq_data;
 	wait_queue_head_t irq_wait_queue;
 	const struct mtk_dsi_driver_data *driver_data;
@@ -1052,7 +1053,13 @@ static int mtk_dsi_bind(struct device *dev, struct device *master, void *data)
 	if (ret)
 		return ret;
 
-	return device_reset_optional(dev);
+	ret = device_reset_optional(dev);
+	if (ret)
+		return ret;
+
+	enable_irq(dsi->irq);
+
+	return 0;
 }
 
 static void mtk_dsi_unbind(struct device *dev, struct device *master,
@@ -1060,6 +1067,8 @@ static void mtk_dsi_unbind(struct device *dev, struct device *master,
 {
 	struct mtk_dsi *dsi = dev_get_drvdata(dev);
 
+	disable_irq(dsi->irq);
+
 	drm_encoder_cleanup(&dsi->encoder);
 }
 
@@ -1290,7 +1299,6 @@ static int mtk_dsi_probe(struct platform_device *pdev)
 {
 	struct mtk_dsi *dsi;
 	struct device *dev = &pdev->dev;
-	int irq_num;
 	int ret;
 
 	dsi = devm_drm_bridge_alloc(dev, struct mtk_dsi, bridge,
@@ -1323,9 +1331,9 @@ static int mtk_dsi_probe(struct platform_device *pdev)
 	if (IS_ERR(dsi->phy))
 		return dev_err_probe(dev, PTR_ERR(dsi->phy), "Failed to get MIPI-DPHY\n");
 
-	irq_num = platform_get_irq(pdev, 0);
-	if (irq_num < 0)
-		return irq_num;
+	dsi->irq = platform_get_irq(pdev, 0);
+	if (dsi->irq < 0)
+		return dsi->irq;
 
 	dsi->host.ops = &mtk_dsi_ops;
 	dsi->host.dev = dev;
@@ -1338,8 +1346,8 @@ static int mtk_dsi_probe(struct platform_device *pdev)
 	if (ret < 0)
 		return dev_err_probe(dev, ret, "Failed to register DSI host\n");
 
-	ret = devm_request_irq(&pdev->dev, irq_num, mtk_dsi_irq,
-			       IRQF_TRIGGER_NONE, dev_name(&pdev->dev), dsi);
+	ret = devm_request_irq(&pdev->dev, dsi->irq, mtk_dsi_irq,
+			       IRQF_NO_AUTOEN, dev_name(&pdev->dev), dsi);
 	if (ret) {
 		mipi_dsi_host_unregister(&dsi->host);
 		return dev_err_probe(&pdev->dev, ret, "Failed to request DSI irq\n");
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 05/10] drm/mediatek: mtk_dsi: Transfer register offsets to per-SoC const
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
                   ` (3 preceding siblings ...)
  2026-07-13  9:58 ` [PATCH v2 04/10] drm/mediatek: mtk_dsi: Enable interrupt at component bind time AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13 10:07   ` sashiko-bot
  2026-07-13  9:58 ` [PATCH v2 06/10] drm/mediatek: mtk_dsi: Add support for MT8189 AngeloGioacchino Del Regno
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin

As of now, the all of the supported SoCs have small differences in
the register offsets for their version of the DSI IP, at least for
the VM_CMD_CON, SHADOW_DEBUG and CMDQ offsets.

As a preparation for introducing support for newer generation DSI
IPs, having even more differences in the register offsets (but not
in the layout of their fields, nor in the actual programming), as
found on Dimensity 9400 MT6991, Kompanio Ultra MT8196 and Genio
Pro 5100 MT8894, transfer all the register offsets to two const
arrays, splitting the DSI IP version specific registers from the
SoC specific ones (as those depend on interfacing with CMDQ and
other IPs external to DSI, but internal to the SoC, and embedded
in DSI).

This change brings no functional difference, as it only changes
how the register offsets are retrieved and nothing else.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 drivers/gpu/drm/mediatek/mtk_dsi.c | 378 +++++++++++++++++++----------
 1 file changed, 253 insertions(+), 125 deletions(-)

diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index 284dae372c7c..6a77ce8eea91 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
  * Copyright (c) 2015 MediaTek Inc.
+ * Copyright (c) 2026 Collabora Ltd.
+ *                    AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
  */
 
 #include <linux/bitfield.h>
@@ -34,11 +36,11 @@
 #include "mtk_disp_drv.h"
 #include "mtk_drm_drv.h"
 
-#define DSI_START		0x00
+/* DSI_START */
 
-#define DSI_INTEN		0x08
+/* DSI_INTEN */
 
-#define DSI_INTSTA		0x0c
+/* DSI_INTSTA */
 #define LPRX_RD_RDY_INT_FLAG		BIT(0)
 #define CMD_DONE_INT_FLAG		BIT(1)
 #define TE_RDY_INT_FLAG			BIT(2)
@@ -46,12 +48,13 @@
 #define EXT_TE_RDY_INT_FLAG		BIT(4)
 #define DSI_BUSY			BIT(31)
 
-#define DSI_CON_CTRL		0x10
+/* DSI_CON_CTRL */
 #define DSI_RESET			BIT(0)
 #define DSI_EN				BIT(1)
 #define DPHY_RESET			BIT(2)
+#define CMDMODE_WAIT_DATA_EVERY_LINE_EN	BIT(24)
 
-#define DSI_MODE_CTRL		0x14
+/* DSI_MODE_CTRL */
 #define MODE				(3)
 #define CMD_MODE			0
 #define SYNC_PULSE_MODE			1
@@ -60,7 +63,7 @@
 #define FRM_MODE			BIT(16)
 #define MIX_MODE			BIT(17)
 
-#define DSI_TXRX_CTRL		0x18
+/* DSI_TXRX_CTRL */
 #define VC_NUM				BIT(1)
 #define LANE_NUM			GENMASK(5, 2)
 #define DIS_EOT				BIT(6)
@@ -71,7 +74,7 @@
 #define MAX_RTN_SIZE			GENMASK(15, 12)
 #define HSTX_CKLP_EN			BIT(16)
 
-#define DSI_PSCTRL		0x1c
+/* DSI_PSCTRL */
 #define DSI_PS_WC			GENMASK(13, 0)
 #define DSI_PS_SEL			GENMASK(19, 16)
 #define PACKED_PS_16BIT_RGB565		0
@@ -80,64 +83,64 @@
 #define PACKED_PS_24BIT_RGB888		3
 #define COMPRESSED_PS_DSC		5
 
-#define DSI_VSA_NL		0x20
-#define DSI_VBP_NL		0x24
-#define DSI_VFP_NL		0x28
-#define DSI_VACT_NL		0x2C
+/* DSI_VSA_NL */
+/* DSI_VBP_NL */
+/* DSI_VFP_NL */
+/* DSI_VACT_NL */
 #define VACT_NL				GENMASK(14, 0)
-#define DSI_SIZE_CON		0x38
-#define DSI_HEIGHT				GENMASK(30, 16)
-#define DSI_WIDTH				GENMASK(14, 0)
-#define DSI_HSA_WC		0x50
-#define DSI_HBP_WC		0x54
-#define DSI_HFP_WC		0x58
-#define HFP_HS_VB_PS_WC		GENMASK(30, 16)
+/* DSI_SIZE_CON */
+#define DSI_HEIGHT			GENMASK(30, 16)
+#define DSI_WIDTH			GENMASK(14, 0)
+/* DSI_HSA_WC */
+/* DSI_HBP_WC */
+/* DSI_HFP_WC */
+#define HFP_HS_VB_PS_WC			GENMASK(30, 16)
 #define HFP_HS_EN			BIT(31)
 
-#define DSI_CMDQ_SIZE		0x60
+/* DSI_CMDQ_SIZE */
 #define CMDQ_SIZE			0x3f
-#define CMDQ_SIZE_SEL		BIT(15)
+#define CMDQ_SIZE_SEL			BIT(15)
 
-#define DSI_HSTX_CKL_WC		0x64
+/* DSI_HSTX_CKL_WC */
 #define HSTX_CKL_WC			GENMASK(15, 2)
 
-#define DSI_RX_DATA0		0x74
-#define DSI_RX_DATA1		0x78
-#define DSI_RX_DATA2		0x7c
-#define DSI_RX_DATA3		0x80
+/* DSI_RX_DATA0 */
+/* DSI_RX_DATA1 */
+/* DSI_RX_DATA2 */
+/* DSI_RX_DATA3 */
 
-#define DSI_RACK		0x84
+/* DSI_RACK */
 #define RACK				BIT(0)
 
-#define DSI_PHY_LCCON		0x104
+/* DSI_PHY_LCCON */
 #define LC_HS_TX_EN			BIT(0)
 #define LC_ULPM_EN			BIT(1)
 #define LC_WAKEUP_EN			BIT(2)
 
-#define DSI_PHY_LD0CON		0x108
+/* DSI_PHY_LD0CON */
 #define LD0_HS_TX_EN			BIT(0)
 #define LD0_ULPM_EN			BIT(1)
 #define LD0_WAKEUP_EN			BIT(2)
 
-#define DSI_PHY_TIMECON0	0x110
+/* DSI_PHY_TIMECON0 */
 #define LPX				GENMASK(7, 0)
 #define HS_PREP				GENMASK(15, 8)
 #define HS_ZERO				GENMASK(23, 16)
 #define HS_TRAIL			GENMASK(31, 24)
 
-#define DSI_PHY_TIMECON1	0x114
+/* DSI_PHY_TIMECON1 */
 #define TA_GO				GENMASK(7, 0)
 #define TA_SURE				GENMASK(15, 8)
 #define TA_GET				GENMASK(23, 16)
 #define DA_HS_EXIT			GENMASK(31, 24)
 
-#define DSI_PHY_TIMECON2	0x118
+/* DSI_PHY_TIMECON2 */
 #define CONT_DET			GENMASK(7, 0)
 #define DA_HS_SYNC			GENMASK(15, 8)
 #define CLK_ZERO			GENMASK(23, 16)
 #define CLK_TRAIL			GENMASK(31, 24)
 
-#define DSI_PHY_TIMECON3	0x11c
+/* DSI_PHY_TIMECON3 */
 #define CLK_HS_PREP			GENMASK(7, 0)
 #define CLK_HS_POST			GENMASK(15, 8)
 #define CLK_HS_EXIT			GENMASK(23, 16)
@@ -168,6 +171,45 @@
 	(type == MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM) || \
 	(type == MIPI_DSI_DCS_READ))
 
+enum mtk_dsi_main_regidx {
+	DSI_START,
+	DSI_INTEN,
+	DSI_INTSTA,
+	DSI_CON_CTRL,
+	DSI_MODE_CTRL,
+	DSI_TXRX_CTRL,
+	DSI_PSCTRL,
+	DSI_VSA_NL,
+	DSI_VBP_NL,
+	DSI_VFP_NL,
+	DSI_VACT_NL,
+	DSI_SIZE_CON,
+	DSI_HSA_WC,
+	DSI_HBP_WC,
+	DSI_HFP_WC,
+	DSI_CMDQ_SIZE,
+	DSI_HSTX_CKL_WC,
+	DSI_RX_DATA0,
+	DSI_RX_DATA1,
+	DSI_RX_DATA2,
+	DSI_RX_DATA3,
+	DSI_RACK,
+	DSI_PHY_LCCON,
+	DSI_PHY_LD0CON,
+	DSI_PHY_TIMECON0,
+	DSI_PHY_TIMECON1,
+	DSI_PHY_TIMECON2,
+	DSI_PHY_TIMECON3,
+	DSI_MAIN_REG_MAX
+};
+
+enum mtk_dsi_adv_regidx {
+	DSI_VM_CMD_CON,
+	DSI_SHADOW_DEBUG,
+	DSI_CMDQ,
+	DSI_ADV_REG_MAX
+};
+
 struct mtk_phy_timing {
 	u32 lpx;
 	u32 da_hs_prepare;
@@ -190,10 +232,9 @@ struct mtk_phy_timing {
 struct phy;
 
 struct mtk_dsi_driver_data {
-	const u32 reg_cmdq_off;
-	const u32 reg_vm_cmd_off;
-	const u32 reg_shadow_dbg_off;
-	bool has_shadow_ctl;
+	const u16 *reg_main;
+	const u16 *reg_adv;
+
 	bool has_size_ctl;
 	bool cmdq_long_packet_ctl;
 	bool support_per_frame_lp;
@@ -231,6 +272,61 @@ struct mtk_dsi {
 	const struct mtk_dsi_driver_data *driver_data;
 };
 
+static const u16 mtk_dsi_regs_main_v1[DSI_MAIN_REG_MAX] = {
+	[DSI_START] = 0x00,
+	[DSI_INTEN] = 0x08,
+	[DSI_INTSTA] = 0x0c,
+	[DSI_CON_CTRL] = 0x10,
+	[DSI_MODE_CTRL] = 0x14,
+	[DSI_TXRX_CTRL] = 0x18,
+	[DSI_PSCTRL] = 0x1c,
+	[DSI_VSA_NL] = 0x20,
+	[DSI_VBP_NL] = 0x24,
+	[DSI_VFP_NL] = 0x28,
+	[DSI_VACT_NL] = 0x2c,
+	[DSI_SIZE_CON] = 0x38,
+	[DSI_HSA_WC] = 0x50,
+	[DSI_HBP_WC] = 0x54,
+	[DSI_HFP_WC] = 0x58,
+	[DSI_CMDQ_SIZE] = 0x60,
+	[DSI_HSTX_CKL_WC] = 0x64,
+	[DSI_RX_DATA0] = 0x74,
+	[DSI_RX_DATA1] = 0x78,
+	[DSI_RX_DATA2] = 0x7c,
+	[DSI_RX_DATA3] = 0x80,
+	[DSI_RACK] = 0x84,
+	[DSI_PHY_LCCON] = 0x104,
+	[DSI_PHY_LD0CON] = 0x108,
+	[DSI_PHY_TIMECON0] = 0x110,
+	[DSI_PHY_TIMECON1] = 0x114,
+	[DSI_PHY_TIMECON2] = 0x118,
+	[DSI_PHY_TIMECON3] = 0x11c,
+};
+
+static const u16 mtk_dsi_regs_mt2701[DSI_ADV_REG_MAX] = {
+	[DSI_VM_CMD_CON] = 0x130,
+	[DSI_SHADOW_DEBUG] = 0,
+	[DSI_CMDQ] = 0x180,
+};
+
+static const u16 mtk_dsi_regs_mt8173[DSI_ADV_REG_MAX] = {
+	[DSI_VM_CMD_CON] = 0x130,
+	[DSI_SHADOW_DEBUG] = 0,
+	[DSI_CMDQ] = 0x200,
+};
+
+static const u16 mtk_dsi_regs_mt8183[DSI_ADV_REG_MAX] = {
+	[DSI_VM_CMD_CON] = 0x130,
+	[DSI_SHADOW_DEBUG] = 0x190,
+	[DSI_CMDQ] = 0x200,
+};
+
+static const u16 mtk_dsi_regs_mt8186[DSI_ADV_REG_MAX] = {
+	[DSI_VM_CMD_CON] = 0x200,
+	[DSI_SHADOW_DEBUG] = 0xc00,
+	[DSI_CMDQ] = 0xd00,
+};
+
 static inline struct mtk_dsi *bridge_to_dsi(struct drm_bridge *b)
 {
 	return container_of(b, struct mtk_dsi, bridge);
@@ -253,6 +349,7 @@ static void mtk_dsi_phy_timconfig(struct mtk_dsi *dsi)
 	u32 timcon0, timcon1, timcon2, timcon3;
 	u32 data_rate_mhz = DIV_ROUND_UP(dsi->data_rate, HZ_PER_MHZ);
 	struct mtk_phy_timing *timing = &dsi->phy_timing;
+	const u16 *reg_main = dsi->driver_data->reg_main;
 
 	timing->lpx = (60 * data_rate_mhz / (8 * 1000)) + 1;
 	timing->da_hs_prepare = (80 * data_rate_mhz + 4 * 1000) / 8000;
@@ -289,76 +386,97 @@ static void mtk_dsi_phy_timconfig(struct mtk_dsi *dsi)
 		  FIELD_PREP(CLK_HS_POST, timing->clk_hs_post) |
 		  FIELD_PREP(CLK_HS_EXIT, timing->clk_hs_exit);
 
-	writel(timcon0, dsi->regs + DSI_PHY_TIMECON0);
-	writel(timcon1, dsi->regs + DSI_PHY_TIMECON1);
-	writel(timcon2, dsi->regs + DSI_PHY_TIMECON2);
-	writel(timcon3, dsi->regs + DSI_PHY_TIMECON3);
+	writel(timcon0, dsi->regs + reg_main[DSI_PHY_TIMECON0]);
+	writel(timcon1, dsi->regs + reg_main[DSI_PHY_TIMECON1]);
+	writel(timcon2, dsi->regs + reg_main[DSI_PHY_TIMECON2]);
+	writel(timcon3, dsi->regs + reg_main[DSI_PHY_TIMECON3]);
 }
 
 static void mtk_dsi_enable(struct mtk_dsi *dsi)
 {
-	mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, DSI_EN);
+	const u16 *reg_main = dsi->driver_data->reg_main;
+
+	mtk_dsi_mask(dsi, reg_main[DSI_CON_CTRL], DSI_EN, DSI_EN);
 }
 
 static void mtk_dsi_disable(struct mtk_dsi *dsi)
 {
-	mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, 0);
+	const u16 *reg_main = dsi->driver_data->reg_main;
+
+	mtk_dsi_mask(dsi, reg_main[DSI_CON_CTRL], DSI_EN, 0);
 }
 
 static void mtk_dsi_reset_engine(struct mtk_dsi *dsi)
 {
-	mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, DSI_RESET);
-	mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, 0);
+	const u16 *reg_main = dsi->driver_data->reg_main;
+
+	mtk_dsi_mask(dsi, reg_main[DSI_CON_CTRL], DSI_RESET, DSI_RESET);
+	mtk_dsi_mask(dsi, reg_main[DSI_CON_CTRL], DSI_RESET, 0);
 }
 
 static void mtk_dsi_reset_dphy(struct mtk_dsi *dsi)
 {
-	mtk_dsi_mask(dsi, DSI_CON_CTRL, DPHY_RESET, DPHY_RESET);
-	mtk_dsi_mask(dsi, DSI_CON_CTRL, DPHY_RESET, 0);
+	const u16 *reg_main = dsi->driver_data->reg_main;
+
+	mtk_dsi_mask(dsi, reg_main[DSI_CON_CTRL], DPHY_RESET, DPHY_RESET);
+	mtk_dsi_mask(dsi, reg_main[DSI_CON_CTRL], DPHY_RESET, 0);
 }
 
 static void mtk_dsi_clk_ulp_mode_enter(struct mtk_dsi *dsi)
 {
-	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0);
-	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0);
+	const u16 *reg_main = dsi->driver_data->reg_main;
+
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LCCON], LC_HS_TX_EN, 0);
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LCCON], LC_ULPM_EN, 0);
 }
 
 static void mtk_dsi_clk_ulp_mode_leave(struct mtk_dsi *dsi)
 {
-	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0);
-	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, LC_WAKEUP_EN);
-	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, 0);
+	const u16 *reg_main = dsi->driver_data->reg_main;
+
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LCCON], LC_ULPM_EN, 0);
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LCCON], LC_WAKEUP_EN, LC_WAKEUP_EN);
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LCCON], LC_WAKEUP_EN, 0);
 }
 
 static void mtk_dsi_lane0_ulp_mode_enter(struct mtk_dsi *dsi)
 {
-	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_HS_TX_EN, 0);
-	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0);
+	const u16 *reg_main = dsi->driver_data->reg_main;
+
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LD0CON], LD0_HS_TX_EN, 0);
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LD0CON], LD0_ULPM_EN, 0);
 }
 
 static void mtk_dsi_lane0_ulp_mode_leave(struct mtk_dsi *dsi)
 {
-	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0);
-	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, LD0_WAKEUP_EN);
-	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, 0);
+	const u16 *reg_main = dsi->driver_data->reg_main;
+
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LD0CON], LD0_ULPM_EN, 0);
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LD0CON], LD0_WAKEUP_EN, LD0_WAKEUP_EN);
+	mtk_dsi_mask(dsi, reg_main[DSI_PHY_LD0CON], LD0_WAKEUP_EN, 0);
 }
 
 static bool mtk_dsi_clk_hs_state(struct mtk_dsi *dsi)
 {
-	return readl(dsi->regs + DSI_PHY_LCCON) & LC_HS_TX_EN;
+	const u16 *regoff = dsi->driver_data->reg_main;
+
+	return readl(dsi->regs + regoff[DSI_PHY_LCCON]) & LC_HS_TX_EN;
 }
 
 static void mtk_dsi_clk_hs_mode(struct mtk_dsi *dsi, bool enter)
 {
+	const u16 *reg_main = dsi->driver_data->reg_main;
+
 	if (enter && !mtk_dsi_clk_hs_state(dsi))
-		mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, LC_HS_TX_EN);
+		mtk_dsi_mask(dsi, reg_main[DSI_PHY_LCCON], LC_HS_TX_EN, LC_HS_TX_EN);
 	else if (!enter && mtk_dsi_clk_hs_state(dsi))
-		mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0);
+		mtk_dsi_mask(dsi, reg_main[DSI_PHY_LCCON], LC_HS_TX_EN, 0);
 }
 
 static void mtk_dsi_set_mode(struct mtk_dsi *dsi)
 {
-	u32 vid_mode = CMD_MODE;
+	const u16 *reg_main = dsi->driver_data->reg_main;
+	u32 vid_mode;
 
 	if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
 		if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
@@ -367,19 +485,24 @@ static void mtk_dsi_set_mode(struct mtk_dsi *dsi)
 			vid_mode = SYNC_PULSE_MODE;
 		else
 			vid_mode = SYNC_EVENT_MODE;
+	} else {
+		vid_mode = CMD_MODE;
 	}
 
-	writel(vid_mode, dsi->regs + DSI_MODE_CTRL);
+	writel(vid_mode, dsi->regs + reg_main[DSI_MODE_CTRL]);
 }
 
 static void mtk_dsi_set_vm_cmd(struct mtk_dsi *dsi)
 {
-	mtk_dsi_mask(dsi, dsi->driver_data->reg_vm_cmd_off, VM_CMD_EN, VM_CMD_EN);
-	mtk_dsi_mask(dsi, dsi->driver_data->reg_vm_cmd_off, TS_VFP_EN, TS_VFP_EN);
+	const u16 *reg_adv = dsi->driver_data->reg_adv;
+
+	mtk_dsi_mask(dsi, reg_adv[DSI_VM_CMD_CON], VM_CMD_EN, VM_CMD_EN);
+	mtk_dsi_mask(dsi, reg_adv[DSI_VM_CMD_CON], TS_VFP_EN, TS_VFP_EN);
 }
 
 static void mtk_dsi_rxtx_control(struct mtk_dsi *dsi)
 {
+	const u16 *reg_main = dsi->driver_data->reg_main;
 	u32 regval, tmp_reg = 0;
 	u8 i;
 
@@ -395,11 +518,13 @@ static void mtk_dsi_rxtx_control(struct mtk_dsi *dsi)
 	if (dsi->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
 		regval |= DIS_EOT;
 
-	writel(regval, dsi->regs + DSI_TXRX_CTRL);
+	writel(regval, dsi->regs + reg_main[DSI_TXRX_CTRL]);
 }
 
 static void mtk_dsi_ps_control(struct mtk_dsi *dsi, bool config_vact)
 {
+	const struct mtk_dsi_driver_data *data = dsi->driver_data;
+	const u16 *reg_main = dsi->driver_data->reg_main;
 	u32 dsi_buf_bpp, ps_val, ps_wc, size_val, vact_nl;
 
 	if (dsi->format == MIPI_DSI_FMT_RGB565)
@@ -437,12 +562,12 @@ static void mtk_dsi_ps_control(struct mtk_dsi *dsi, bool config_vact)
 
 	if (config_vact) {
 		vact_nl = FIELD_PREP(VACT_NL, dsi->vm.vactive);
-		writel(vact_nl, dsi->regs + DSI_VACT_NL);
-		writel(ps_wc, dsi->regs + DSI_HSTX_CKL_WC);
+		writel(vact_nl, dsi->regs + reg_main[DSI_VACT_NL]);
+		writel(ps_wc, dsi->regs + reg_main[DSI_HSTX_CKL_WC]);
 	}
-	writel(ps_val, dsi->regs + DSI_PSCTRL);
+	writel(ps_val, dsi->regs + reg_main[DSI_PSCTRL]);
 
-	if (dsi->driver_data->has_size_ctl) {
+	if (data->has_size_ctl) {
 		size_val = FIELD_PREP(DSI_HEIGHT, dsi->vm.vactive);
 		if (dsi->dsc)
 			size_val |= FIELD_PREP(DSI_WIDTH,
@@ -451,12 +576,13 @@ static void mtk_dsi_ps_control(struct mtk_dsi *dsi, bool config_vact)
 		else
 			size_val |= FIELD_PREP(DSI_WIDTH, dsi->vm.hactive);
 
-		writel(size_val, dsi->regs + DSI_SIZE_CON);
+		writel(size_val, dsi->regs + reg_main[DSI_SIZE_CON]);
 	}
 }
 
 static void mtk_dsi_config_vdo_timing_per_frame_lp(struct mtk_dsi *dsi)
 {
+	const u16 *reg_main = dsi->driver_data->reg_main;
 	u32 horizontal_sync_active_byte;
 	u32 horizontal_backporch_byte;
 	u32 horizontal_frontporch_byte;
@@ -511,20 +637,21 @@ static void mtk_dsi_config_vdo_timing_per_frame_lp(struct mtk_dsi *dsi)
 			   ps_wc), dsi->lanes) + da_hs_trail + 1) * dsi->lanes / 6 - 1;
 
 	hstx_cklp_wc = FIELD_PREP(HSTX_CKL_WC, (hstx_cklp_wc_min + hstx_cklp_wc_max) / 2);
-	writel(hstx_cklp_wc, dsi->regs + DSI_HSTX_CKL_WC);
+	writel(hstx_cklp_wc, dsi->regs + reg_main[DSI_HSTX_CKL_WC]);
 
 	hs_vb_ps_wc = ps_wc - (dsi->phy_timing.lpx + dsi->phy_timing.da_hs_exit +
 		      dsi->phy_timing.da_hs_prepare + dsi->phy_timing.da_hs_zero + 2) * dsi->lanes;
 	horizontal_frontporch_byte |= FIELD_PREP(HFP_HS_EN, 1) |
 				      FIELD_PREP(HFP_HS_VB_PS_WC, hs_vb_ps_wc);
 
-	writel(horizontal_sync_active_byte, dsi->regs + DSI_HSA_WC);
-	writel(horizontal_backporch_byte, dsi->regs + DSI_HBP_WC);
-	writel(horizontal_frontporch_byte, dsi->regs + DSI_HFP_WC);
+	writel(horizontal_sync_active_byte, dsi->regs + reg_main[DSI_HSA_WC]);
+	writel(horizontal_backporch_byte, dsi->regs + reg_main[DSI_HBP_WC]);
+	writel(horizontal_frontporch_byte, dsi->regs + reg_main[DSI_HFP_WC]);
 }
 
 static void mtk_dsi_config_vdo_timing_per_line_lp(struct mtk_dsi *dsi)
 {
+	const u16 *reg_main = dsi->driver_data->reg_main;
 	u32 horizontal_sync_active_byte;
 	u32 horizontal_backporch_byte;
 	u32 horizontal_frontporch_byte;
@@ -583,9 +710,9 @@ static void mtk_dsi_config_vdo_timing_per_line_lp(struct mtk_dsi *dsi)
 			(vm->hactive * dsi_tmp_buf_bpp + 2) % dsi->lanes;
 	}
 
-	writel(horizontal_sync_active_byte, dsi->regs + DSI_HSA_WC);
-	writel(horizontal_backporch_byte, dsi->regs + DSI_HBP_WC);
-	writel(horizontal_frontporch_byte, dsi->regs + DSI_HFP_WC);
+	writel(horizontal_sync_active_byte, dsi->regs + reg_main[DSI_HSA_WC]);
+	writel(horizontal_backporch_byte, dsi->regs + reg_main[DSI_HBP_WC]);
+	writel(horizontal_frontporch_byte, dsi->regs + reg_main[DSI_HFP_WC]);
 }
 
 static int mtk_dsi_set_dsc_params(struct mtk_dsi *dsi)
@@ -626,14 +753,16 @@ static int mtk_dsi_set_dsc_params(struct mtk_dsi *dsi)
 
 static int mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
 {
+	const struct mtk_dsi_driver_data *data = dsi->driver_data;
+	const u16 *reg_main = data->reg_main;
 	struct videomode *vm = &dsi->vm;
 
-	writel(vm->vsync_len, dsi->regs + DSI_VSA_NL);
-	writel(vm->vback_porch, dsi->regs + DSI_VBP_NL);
-	writel(vm->vfront_porch, dsi->regs + DSI_VFP_NL);
-	writel(vm->vactive, dsi->regs + DSI_VACT_NL);
+	writel(vm->vsync_len, dsi->regs + reg_main[DSI_VSA_NL]);
+	writel(vm->vback_porch, dsi->regs + reg_main[DSI_VBP_NL]);
+	writel(vm->vfront_porch, dsi->regs + reg_main[DSI_VFP_NL]);
+	writel(vm->vactive, dsi->regs + reg_main[DSI_VACT_NL]);
 
-	if (dsi->driver_data->support_per_frame_lp)
+	if (data->support_per_frame_lp)
 		mtk_dsi_config_vdo_timing_per_frame_lp(dsi);
 	else
 		mtk_dsi_config_vdo_timing_per_line_lp(dsi);
@@ -648,25 +777,25 @@ static int mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
 
 static void mtk_dsi_start(struct mtk_dsi *dsi)
 {
-	writel(0, dsi->regs + DSI_START);
-	writel(1, dsi->regs + DSI_START);
+	writel(0, dsi->regs + dsi->driver_data->reg_main[DSI_START]);
+	writel(1, dsi->regs + dsi->driver_data->reg_main[DSI_START]);
 }
 
 static void mtk_dsi_stop(struct mtk_dsi *dsi)
 {
-	writel(0, dsi->regs + DSI_START);
+	writel(0, dsi->regs + dsi->driver_data->reg_main[DSI_START]);
 }
 
 static void mtk_dsi_set_cmd_mode(struct mtk_dsi *dsi)
 {
-	writel(CMD_MODE, dsi->regs + DSI_MODE_CTRL);
+	writel(CMD_MODE, dsi->regs + dsi->driver_data->reg_main[DSI_MODE_CTRL]);
 }
 
 static void mtk_dsi_set_interrupt_enable(struct mtk_dsi *dsi)
 {
 	u32 inten = LPRX_RD_RDY_INT_FLAG | CMD_DONE_INT_FLAG | VM_DONE_INT_FLAG;
 
-	writel(inten, dsi->regs + DSI_INTEN);
+	writel(inten, dsi->regs + dsi->driver_data->reg_main[DSI_INTEN]);
 }
 
 static void mtk_dsi_irq_data_set(struct mtk_dsi *dsi, u32 irq_bit)
@@ -701,19 +830,20 @@ static s32 mtk_dsi_wait_for_irq_done(struct mtk_dsi *dsi, u32 irq_flag,
 
 static irqreturn_t mtk_dsi_irq(int irq, void *dev_id)
 {
-	struct mtk_dsi *dsi = dev_id;
 	u32 status, tmp;
-	u32 flag = LPRX_RD_RDY_INT_FLAG | CMD_DONE_INT_FLAG | VM_DONE_INT_FLAG;
+	struct mtk_dsi *dsi = dev_id;
+	const u16 *reg_main = dsi->driver_data->reg_main;
+	const u32 flag = LPRX_RD_RDY_INT_FLAG | CMD_DONE_INT_FLAG | VM_DONE_INT_FLAG;
 
-	status = readl(dsi->regs + DSI_INTSTA) & flag;
+	status = readl(dsi->regs + dsi->driver_data->reg_main[DSI_INTSTA]) & flag;
 
 	if (status) {
 		do {
-			mtk_dsi_mask(dsi, DSI_RACK, RACK, RACK);
-			tmp = readl(dsi->regs + DSI_INTSTA);
+			mtk_dsi_mask(dsi, reg_main[DSI_RACK], RACK, RACK);
+			tmp = readl(dsi->regs + reg_main[DSI_INTSTA]);
 		} while (tmp & DSI_BUSY);
 
-		mtk_dsi_mask(dsi, DSI_INTSTA, status, 0);
+		mtk_dsi_mask(dsi, reg_main[DSI_INTSTA], status, 0);
 		mtk_dsi_irq_data_set(dsi, status);
 		wake_up_interruptible(&dsi->irq_wait_queue);
 	}
@@ -752,9 +882,10 @@ static void mtk_dsi_lane_ready(struct mtk_dsi *dsi)
 
 static int mtk_dsi_poweron(struct mtk_dsi *dsi)
 {
+	const struct mtk_dsi_driver_data *data = dsi->driver_data;
 	struct device *dev = dsi->host.dev;
-	int ret;
 	u32 bit_per_pixel;
+	int ret;
 
 	if (++dsi->refcount != 1)
 		return 0;
@@ -791,9 +922,10 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi)
 
 	mtk_dsi_enable(dsi);
 
-	if (dsi->driver_data->has_shadow_ctl)
+	/* Bypass shadow and force commit only if the register is present */
+	if (data->reg_adv[DSI_SHADOW_DEBUG])
 		writel(FORCE_COMMIT | BYPASS_SHADOW,
-		       dsi->regs + dsi->driver_data->reg_shadow_dbg_off);
+		       dsi->regs + data->reg_adv[DSI_SHADOW_DEBUG]);
 
 	mtk_dsi_reset_engine(dsi);
 	mtk_dsi_phy_timconfig(dsi);
@@ -842,7 +974,7 @@ static void mtk_dsi_poweroff(struct mtk_dsi *dsi)
 	mtk_dsi_lane0_ulp_mode_enter(dsi);
 	mtk_dsi_clk_ulp_mode_enter(dsi);
 	/* set the lane number as 0 to pull down mipi */
-	writel(0, dsi->regs + DSI_TXRX_CTRL);
+	writel(0, dsi->regs + dsi->driver_data->reg_main[DSI_TXRX_CTRL]);
 
 	mtk_dsi_disable(dsi);
 
@@ -1130,9 +1262,10 @@ static void mtk_dsi_wait_for_idle(struct mtk_dsi *dsi)
 	int ret;
 	u32 val;
 	struct drm_device *drm = dsi->bridge.dev;
+	const struct mtk_dsi_driver_data *data = dsi->driver_data;
 
-	ret = readl_poll_timeout(dsi->regs + DSI_INTSTA, val, !(val & DSI_BUSY),
-				 4, 2000000);
+	ret = readl_poll_timeout(dsi->regs + data->reg_main[DSI_INTSTA],
+				 val, !(val & DSI_BUSY), 4, 2000000);
 	if (ret) {
 		drm_warn(drm, "polling dsi wait not busy timeout!\n");
 
@@ -1166,10 +1299,12 @@ static u32 mtk_dsi_recv_cnt(u8 type, u8 *read_data)
 
 static void mtk_dsi_cmdq(struct mtk_dsi *dsi, const struct mipi_dsi_msg *msg)
 {
+	const struct mtk_dsi_driver_data *data = dsi->driver_data;
 	const char *tx_buf = msg->tx_buf;
-	u8 config, cmdq_size, cmdq_off, type = msg->type;
+	const u8 type = msg->type;
 	u32 reg_val, cmdq_mask, i;
-	u32 reg_cmdq_off = dsi->driver_data->reg_cmdq_off;
+	u8 cmdq_size, cmdq_off;
+	u8 config;
 
 	if (MTK_DSI_HOST_IS_READ(type))
 		config = BTA;
@@ -1192,15 +1327,15 @@ static void mtk_dsi_cmdq(struct mtk_dsi *dsi, const struct mipi_dsi_msg *msg)
 	}
 
 	for (i = 0; i < msg->tx_len; i++)
-		mtk_dsi_mask(dsi, (reg_cmdq_off + cmdq_off + i) & (~0x3U),
+		mtk_dsi_mask(dsi, (data->reg_adv[DSI_CMDQ] + cmdq_off + i) & (~0x3U),
 			     (0xffUL << (((i + cmdq_off) & 3U) * 8U)),
 			     tx_buf[i] << (((i + cmdq_off) & 3U) * 8U));
 
-	mtk_dsi_mask(dsi, reg_cmdq_off, cmdq_mask, reg_val);
-	mtk_dsi_mask(dsi, DSI_CMDQ_SIZE, CMDQ_SIZE, cmdq_size);
-	if (dsi->driver_data->cmdq_long_packet_ctl) {
+	mtk_dsi_mask(dsi, data->reg_adv[DSI_CMDQ], cmdq_mask, reg_val);
+	mtk_dsi_mask(dsi, data->reg_main[DSI_CMDQ_SIZE], CMDQ_SIZE, cmdq_size);
+	if (data->cmdq_long_packet_ctl) {
 		/* Disable setting cmdq_size automatically for long packets */
-		mtk_dsi_mask(dsi, DSI_CMDQ_SIZE, CMDQ_SIZE_SEL, CMDQ_SIZE_SEL);
+		mtk_dsi_mask(dsi, data->reg_main[DSI_CMDQ_SIZE], CMDQ_SIZE_SEL, CMDQ_SIZE_SEL);
 	}
 }
 
@@ -1230,7 +1365,7 @@ static ssize_t mtk_dsi_host_transfer(struct mipi_dsi_host *host,
 	u32 dsi_mode;
 	int ret, i;
 
-	dsi_mode = readl(dsi->regs + DSI_MODE_CTRL);
+	dsi_mode = readl(dsi->regs + dsi->driver_data->reg_main[DSI_MODE_CTRL]);
 	if (dsi_mode & MODE) {
 		mtk_dsi_stop(dsi);
 		ret = mtk_dsi_switch_to_cmd_mode(dsi, VM_DONE_INT_FLAG, 500);
@@ -1259,7 +1394,8 @@ static ssize_t mtk_dsi_host_transfer(struct mipi_dsi_host *host,
 	}
 
 	for (i = 0; i < 16; i++)
-		*(read_data + i) = readb(dsi->regs + DSI_RX_DATA0 + i);
+		*(read_data + i) = readb(dsi->regs +
+					 dsi->driver_data->reg_main[DSI_RX_DATA0 + i]);
 
 	recv_cnt = mtk_dsi_recv_cnt(read_data[0], read_data);
 
@@ -1368,38 +1504,30 @@ static void mtk_dsi_remove(struct platform_device *pdev)
 }
 
 static const struct mtk_dsi_driver_data mt8173_dsi_driver_data = {
-	.reg_cmdq_off = 0x200,
-	.reg_vm_cmd_off = 0x130,
-	.reg_shadow_dbg_off = 0x190
+	.reg_main = mtk_dsi_regs_main_v1,
+	.reg_adv = mtk_dsi_regs_mt8173,
 };
 
 static const struct mtk_dsi_driver_data mt2701_dsi_driver_data = {
-	.reg_cmdq_off = 0x180,
-	.reg_vm_cmd_off = 0x130,
-	.reg_shadow_dbg_off = 0x190
+	.reg_main = mtk_dsi_regs_main_v1,
+	.reg_adv = mtk_dsi_regs_mt2701,
 };
 
 static const struct mtk_dsi_driver_data mt8183_dsi_driver_data = {
-	.reg_cmdq_off = 0x200,
-	.reg_vm_cmd_off = 0x130,
-	.reg_shadow_dbg_off = 0x190,
-	.has_shadow_ctl = true,
+	.reg_main = mtk_dsi_regs_main_v1,
+	.reg_adv = mtk_dsi_regs_mt8183,
 	.has_size_ctl = true,
 };
 
 static const struct mtk_dsi_driver_data mt8186_dsi_driver_data = {
-	.reg_cmdq_off = 0xd00,
-	.reg_vm_cmd_off = 0x200,
-	.reg_shadow_dbg_off = 0xc00,
-	.has_shadow_ctl = true,
+	.reg_main = mtk_dsi_regs_main_v1,
+	.reg_adv = mtk_dsi_regs_mt8186,
 	.has_size_ctl = true,
 };
 
 static const struct mtk_dsi_driver_data mt8188_dsi_driver_data = {
-	.reg_cmdq_off = 0xd00,
-	.reg_vm_cmd_off = 0x200,
-	.reg_shadow_dbg_off = 0xc00,
-	.has_shadow_ctl = true,
+	.reg_main = mtk_dsi_regs_main_v1,
+	.reg_adv = mtk_dsi_regs_mt8186,
 	.has_size_ctl = true,
 	.cmdq_long_packet_ctl = true,
 	.support_per_frame_lp = true,
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 06/10] drm/mediatek: mtk_dsi: Add support for MT8189
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
                   ` (4 preceding siblings ...)
  2026-07-13  9:58 ` [PATCH v2 05/10] drm/mediatek: mtk_dsi: Transfer register offsets to per-SoC const AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13 10:13   ` sashiko-bot
  2026-07-13  9:58 ` [PATCH v2 07/10] drm/mediatek: mtk_dsi: Add support for MT8196 AngeloGioacchino Del Regno
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin

Add support for the DSI IP found in the MT8189 SoC: this one is
similar to the IPs found in the most recent already supported SoCs
with the only difference being that it supports a higher link rate
of 2.5Gbps instead of 1.5Gbps.

To support the higher rate, add a new "max_link_rate_mbps" member
to the mtk_dsi_driver_data, assign the correct one to all of the
supported SoCs, other than to the newly introduced MT8189, and
use it in the .mode_valid() callback in place of the hardcoded
1.5Gbps value.

Since .mode_valid() is supposed to run just for resolution changes
the link rate was expressed in Mbps and gets multiplied on the fly
to save some bits.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 drivers/gpu/drm/mediatek/mtk_dsi.c | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index 6a77ce8eea91..2df2a9d5ec03 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -235,6 +235,8 @@ struct mtk_dsi_driver_data {
 	const u16 *reg_main;
 	const u16 *reg_adv;
 
+	const u16 max_link_rate_mbps;
+
 	bool has_size_ctl;
 	bool cmdq_long_packet_ctl;
 	bool support_per_frame_lp;
@@ -1070,13 +1072,20 @@ mtk_dsi_bridge_mode_valid(struct drm_bridge *bridge,
 			  const struct drm_display_mode *mode)
 {
 	struct mtk_dsi *dsi = bridge_to_dsi(bridge);
+	const struct mtk_dsi_driver_data *data = dsi->driver_data;
+	u64 wanted_link_rate, max_link_rate;
 	int bpp;
 
 	bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
 	if (bpp < 0)
 		return MODE_ERROR;
 
-	if (mode->clock * bpp / dsi->lanes > 1500000)
+	wanted_link_rate = mode->clock * bpp;
+	max_link_rate = data->max_link_rate_mbps;
+	max_link_rate *= dsi->lanes;
+	max_link_rate *= KILO;
+
+	if (wanted_link_rate > max_link_rate)
 		return MODE_CLOCK_HIGH;
 
 	if (dsi->dsc) {
@@ -1506,28 +1515,42 @@ static void mtk_dsi_remove(struct platform_device *pdev)
 static const struct mtk_dsi_driver_data mt8173_dsi_driver_data = {
 	.reg_main = mtk_dsi_regs_main_v1,
 	.reg_adv = mtk_dsi_regs_mt8173,
+	.max_link_rate_mbps = 1500,
 };
 
 static const struct mtk_dsi_driver_data mt2701_dsi_driver_data = {
 	.reg_main = mtk_dsi_regs_main_v1,
 	.reg_adv = mtk_dsi_regs_mt2701,
+	.max_link_rate_mbps = 1500,
 };
 
 static const struct mtk_dsi_driver_data mt8183_dsi_driver_data = {
 	.reg_main = mtk_dsi_regs_main_v1,
 	.reg_adv = mtk_dsi_regs_mt8183,
+	.max_link_rate_mbps = 1500,
 	.has_size_ctl = true,
 };
 
 static const struct mtk_dsi_driver_data mt8186_dsi_driver_data = {
 	.reg_main = mtk_dsi_regs_main_v1,
 	.reg_adv = mtk_dsi_regs_mt8186,
+	.max_link_rate_mbps = 1500,
 	.has_size_ctl = true,
 };
 
 static const struct mtk_dsi_driver_data mt8188_dsi_driver_data = {
 	.reg_main = mtk_dsi_regs_main_v1,
 	.reg_adv = mtk_dsi_regs_mt8186,
+	.max_link_rate_mbps = 1500,
+	.has_size_ctl = true,
+	.cmdq_long_packet_ctl = true,
+	.support_per_frame_lp = true,
+};
+
+static const struct mtk_dsi_driver_data mt8189_dsi_driver_data = {
+	.reg_main = mtk_dsi_regs_main_v1,
+	.reg_adv = mtk_dsi_regs_mt8186,
+	.max_link_rate_mbps = 2500,
 	.has_size_ctl = true,
 	.cmdq_long_packet_ctl = true,
 	.support_per_frame_lp = true,
@@ -1540,6 +1563,7 @@ static const struct of_device_id mtk_dsi_of_match[] = {
 	{ .compatible = "mediatek,mt8183-dsi", .data = &mt8183_dsi_driver_data },
 	{ .compatible = "mediatek,mt8186-dsi", .data = &mt8186_dsi_driver_data },
 	{ .compatible = "mediatek,mt8188-dsi", .data = &mt8188_dsi_driver_data },
+	{ .compatible = "mediatek,mt8189-dsi", .data = &mt8189_dsi_driver_data },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, mtk_dsi_of_match);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 07/10] drm/mediatek: mtk_dsi: Add support for MT8196
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
                   ` (5 preceding siblings ...)
  2026-07-13  9:58 ` [PATCH v2 06/10] drm/mediatek: mtk_dsi: Add support for MT8189 AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13 10:13   ` sashiko-bot
  2026-07-13  9:58 ` [PATCH v2 08/10] drm/mediatek: mtk_dsi: Enable PM Runtime on probe AngeloGioacchino Del Regno
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin

Add support for the new DSI IP found in the Kompanio Ultra MT8196
SoC and its Dimensity and Genio variants.

Differently from the older DSI IPs, the one from MT8196 requires
the initialization of all of the QoS parameters and can make use
of a DSI SRAM reserved buffer (present also on older SoCs but not
mandatory on those).

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 drivers/gpu/drm/mediatek/mtk_drm_drv.c |   2 +
 drivers/gpu/drm/mediatek/mtk_dsi.c     | 197 +++++++++++++++++++++++++
 2 files changed, 199 insertions(+)

diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
index bd2d17017bd2..9a4c59849c4b 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
@@ -826,6 +826,8 @@ static const struct of_device_id mtk_ddp_comp_dt_ids[] = {
 	  .data = (void *)MTK_DSI },
 	{ .compatible = "mediatek,mt8188-dsi",
 	  .data = (void *)MTK_DSI },
+	{ .compatible = "mediatek,mt8196-dsi",
+	  .data = (void *)MTK_DSI },
 	{ }
 };
 
diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index 2df2a9d5ec03..5af44c13bd5a 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -153,6 +153,18 @@
 #define FORCE_COMMIT			BIT(0)
 #define BYPASS_SHADOW			BIT(1)
 
+/* DSI_VDE */
+#define VDE_BLOCK_ULTRA			BIT(29)
+
+/* DSI_BUF_CON0 */
+#define DSI_QOS_BUF_EN			BIT(0)
+
+/* DSI_BUF_CON1 */
+#define BUF_OUT_VALID_THRESH		GENMASK(14, 0)
+
+/* DSI_BUF_SODI_HIGH, SODI_LOW and other BUF registers */
+#define BUF_THRESHOLD_PARAM		GENMASK(19, 0)
+
 /* CMDQ related bits */
 #define CONFIG				GENMASK(7, 0)
 #define SHORT_PACKET			0
@@ -165,6 +177,16 @@
 
 #define NS_TO_CYCLE(n, c)    ((n) / (c) + (((n) % (c)) ? 1 : 0))
 
+/* HW QoS and Anti-Latency Buffer related bits */
+#define MTK_DSI_MAX_FIFO_BYTES			1554
+#define MTK_DSI_DEFAULT_QOS_VALID_FIFO_US	25
+#define MTK_DSI_DEFAULT_QOS_PREULTRA_HI_US	36
+#define MTK_DSI_DEFAULT_QOS_PREULTRA_LO_US	35
+#define MTK_DSI_DEFAULT_QOS_ULTRA_HI_US		26
+#define MTK_DSI_DEFAULT_QOS_ULTRA_LO_US		25
+#define MTK_DSI_DEFAULT_QOS_URGENT_LO_US	11
+#define MTK_DSI_DEFAULT_QOS_URGENT_HI_US	12
+
 #define MTK_DSI_HOST_IS_READ(type) \
 	((type == MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM) || \
 	(type == MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM) || \
@@ -207,9 +229,26 @@ enum mtk_dsi_adv_regidx {
 	DSI_VM_CMD_CON,
 	DSI_SHADOW_DEBUG,
 	DSI_CMDQ,
+	DSI_VDE,
 	DSI_ADV_REG_MAX
 };
 
+enum mtk_dsi_qos_regidx {
+	DSI_QOS_BUF_CON0,
+	DSI_QOS_BUF_CON1,
+	DSI_QOS_TX_BUF_RW_TIMES,
+	DSI_QOS_SODI_HIGH,
+	DSI_QOS_SODI_LOW,
+	DSI_QOS_PREULTRA_HIGH,
+	DSI_QOS_PREULTRA_LOW,
+	DSI_QOS_ULTRA_HIGH,
+	DSI_QOS_ULTRA_LOW,
+	DSI_QOS_URGENT_HIGH,
+	DSI_QOS_URGENT_LOW,
+	DSI_QOS_PREURGENT_HIGH,
+	DSI_QOS_REG_MAX
+};
+
 struct mtk_phy_timing {
 	u32 lpx;
 	u32 da_hs_prepare;
@@ -234,8 +273,12 @@ struct phy;
 struct mtk_dsi_driver_data {
 	const u16 *reg_main;
 	const u16 *reg_adv;
+	const u16 *reg_qos;
 
 	const u16 max_link_rate_mbps;
+	const u8 dsi_sram_bytes;
+	const u8 pixels_per_iter;
+	const u8 num_burst_lines;
 
 	bool has_size_ctl;
 	bool cmdq_long_packet_ctl;
@@ -329,6 +372,59 @@ static const u16 mtk_dsi_regs_mt8186[DSI_ADV_REG_MAX] = {
 	[DSI_CMDQ] = 0xd00,
 };
 
+static const u16 mtk_dsi_regs_main_v2[DSI_MAIN_REG_MAX] = {
+	[DSI_START] = 0x00,
+	[DSI_INTEN] = 0x08,
+	[DSI_INTSTA] = 0x0c,
+	[DSI_CON_CTRL] = 0x30,
+	[DSI_MODE_CTRL] = 0x34,
+	[DSI_TXRX_CTRL] = 0x38,
+	[DSI_PSCTRL] = 0x3c,
+	[DSI_VSA_NL] = 0x60,
+	[DSI_VBP_NL] = 0x64,
+	[DSI_VFP_NL] = 0x68,
+	[DSI_VACT_NL] = 0x6c,
+	[DSI_SIZE_CON] = 0x2c,
+	[DSI_HSA_WC] = 0x80,
+	[DSI_HBP_WC] = 0x84,
+	[DSI_HFP_WC] = 0x88,
+	[DSI_CMDQ_SIZE] = 0x44,
+	[DSI_HSTX_CKL_WC] = 0x100,
+	[DSI_RX_DATA0] = 0xa4,
+	[DSI_RX_DATA1] = 0xa8,
+	[DSI_RX_DATA2] = 0xac,
+	[DSI_RX_DATA3] = 0xb0,
+	[DSI_RACK] = 0xb4,
+	[DSI_PHY_LCCON] = 0x7d0,
+	[DSI_PHY_LD0CON] = 0x7d4,
+	[DSI_PHY_TIMECON0] = 0x600,
+	[DSI_PHY_TIMECON1] = 0x604,
+	[DSI_PHY_TIMECON2] = 0x608,
+	[DSI_PHY_TIMECON3] = 0x60c,
+};
+
+static const u16 mtk_dsi_regs_qos_v2[DSI_QOS_REG_MAX] = {
+	[DSI_QOS_BUF_CON0] = 0x300,
+	[DSI_QOS_BUF_CON1] = 0x304,
+	[DSI_QOS_TX_BUF_RW_TIMES] = 0x310,
+	[DSI_QOS_SODI_HIGH] = 0x314,
+	[DSI_QOS_SODI_LOW] = 0x318,
+	[DSI_QOS_PREULTRA_HIGH] = 0x324,
+	[DSI_QOS_PREULTRA_LOW] = 0x328,
+	[DSI_QOS_ULTRA_HIGH] = 0x32c,
+	[DSI_QOS_ULTRA_LOW] = 0x330,
+	[DSI_QOS_URGENT_HIGH] = 0x334,
+	[DSI_QOS_URGENT_LOW] = 0x338,
+	[DSI_QOS_PREURGENT_HIGH] = 0x33c
+};
+
+static const u16 mtk_dsi_regs_mt8196[DSI_ADV_REG_MAX] = {
+	[DSI_VM_CMD_CON] = 0x110,
+	[DSI_SHADOW_DEBUG] = 0xd0,
+	[DSI_CMDQ] = 0x400,
+	[DSI_VDE] = 0x3f8,
+};
+
 static inline struct mtk_dsi *bridge_to_dsi(struct drm_bridge *b)
 {
 	return container_of(b, struct mtk_dsi, bridge);
@@ -753,6 +849,89 @@ static int mtk_dsi_set_dsc_params(struct mtk_dsi *dsi)
 	return drm_dsc_compute_rc_parameters(dsc);
 }
 
+static void mtk_dsi_config_hw_buffers(struct mtk_dsi *dsi)
+{
+	const struct mtk_dsi_driver_data *data = dsi->driver_data;
+	const u16 *reg_qos = data->reg_qos;
+	u32 buffer_unit, sram_unit, num_hw_buffers;
+	u32 preultra_hi, preultra_lo;
+	u32 urgent_hi, urgent_lo;
+	u32 ultra_hi, ultra_lo;
+	u32 sodi_hi, sodi_lo;
+	u32 data_rate_per_buf;
+	u32 out_valid_thresh;
+	u32 dsi_buf_bpp;
+	u32 fill_rate;
+	u32 pclk_mhz;
+	u32 rw_times;
+	u32 val;
+
+	/*
+	 * At the time of writing, only MT8196 is implemented and, for this SoC,
+	 * the buffer unit is equal to the SRAM bytes.
+	 *
+	 * There are other SoCs already out in the wild that do support the HW
+	 * buffers and that have different sizes, so keep the calculation as-is!
+	 */
+	buffer_unit = data->dsi_sram_bytes;
+	sram_unit = data->dsi_sram_bytes;
+	num_hw_buffers = sram_unit / buffer_unit;
+
+	if (data->support_per_frame_lp)
+		val = CMDMODE_WAIT_DATA_EVERY_LINE_EN;
+	else
+		val = 0;
+
+	mtk_dsi_mask(dsi, data->reg_main[DSI_CON_CTRL],
+		     CMDMODE_WAIT_DATA_EVERY_LINE_EN, val);
+
+	data_rate_per_buf = dsi->data_rate * dsi->lanes / 8 / buffer_unit;
+
+	/* Calculate valid threshold and avoid exceeding FIFO size */
+	out_valid_thresh = MTK_DSI_DEFAULT_QOS_VALID_FIFO_US * data_rate_per_buf;
+	out_valid_thresh = min(out_valid_thresh, MTK_DSI_MAX_FIFO_BYTES - 1);
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_BUF_CON1], BUF_OUT_VALID_THRESH, out_valid_thresh);
+
+	/* Enable ULTRA signal trigger between SOF and VACT */
+	mtk_dsi_mask(dsi, data->reg_adv[DSI_VDE], VDE_BLOCK_ULTRA, 0);
+
+	/* Calculate fill rate with line counter mode for DSI Video Mode */
+	if (dsi->format == MIPI_DSI_FMT_RGB565)
+		dsi_buf_bpp = 2;
+	else
+		dsi_buf_bpp = 3;
+
+	pclk_mhz = dsi->vm.pixelclock / HZ_PER_MHZ;
+	fill_rate = pclk_mhz * data->pixels_per_iter * dsi_buf_bpp / buffer_unit;
+
+	/* Calculate QoS Anti-Latency parameters */
+	sodi_hi = MTK_DSI_MAX_FIFO_BYTES * num_hw_buffers;
+	sodi_hi -= (fill_rate - data_rate_per_buf) * 12 / 10;
+	sodi_lo = (23 + 5) * data_rate_per_buf;
+	preultra_hi = MTK_DSI_DEFAULT_QOS_PREULTRA_HI_US * data_rate_per_buf;
+	preultra_lo = MTK_DSI_DEFAULT_QOS_PREULTRA_LO_US * data_rate_per_buf;
+	ultra_hi = MTK_DSI_DEFAULT_QOS_ULTRA_HI_US * data_rate_per_buf;
+	ultra_lo = MTK_DSI_DEFAULT_QOS_ULTRA_LO_US * data_rate_per_buf;
+	urgent_hi = MTK_DSI_DEFAULT_QOS_URGENT_HI_US * data_rate_per_buf;
+	urgent_lo = MTK_DSI_DEFAULT_QOS_URGENT_LO_US * data_rate_per_buf;
+	rw_times = dsi->vm.vactive * dsi_buf_bpp;
+	rw_times /= data->num_burst_lines * data->pixels_per_iter;
+
+	/* Write all QoS parameters: Screen On Deep Idle, (pre)Ultra, Urgent, RW times */
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_SODI_HIGH], BUF_THRESHOLD_PARAM, sodi_hi);
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_SODI_LOW], BUF_THRESHOLD_PARAM, sodi_lo);
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_PREULTRA_HIGH], BUF_THRESHOLD_PARAM, preultra_hi);
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_PREULTRA_LOW], BUF_THRESHOLD_PARAM, preultra_lo);
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_ULTRA_HIGH], BUF_THRESHOLD_PARAM, ultra_hi);
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_ULTRA_LOW], BUF_THRESHOLD_PARAM, ultra_lo);
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_URGENT_HIGH], BUF_THRESHOLD_PARAM, urgent_hi);
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_URGENT_LOW], BUF_THRESHOLD_PARAM, urgent_lo);
+	writel(rw_times, dsi->regs + reg_qos[DSI_QOS_TX_BUF_RW_TIMES]);
+
+	/* Finally, activate internal line-buffering */
+	mtk_dsi_mask(dsi, reg_qos[DSI_QOS_BUF_CON0], DSI_QOS_BUF_EN, DSI_QOS_BUF_EN);
+}
+
 static int mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
 {
 	const struct mtk_dsi_driver_data *data = dsi->driver_data;
@@ -932,6 +1111,10 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi)
 	mtk_dsi_reset_engine(dsi);
 	mtk_dsi_phy_timconfig(dsi);
 
+	/* Setup HW FIFO if DSI supports QoS Anti-Latency buffers */
+	if (data->dsi_sram_bytes && data->reg_qos)
+		mtk_dsi_config_hw_buffers(dsi);
+
 	mtk_dsi_ps_control(dsi, true);
 	mtk_dsi_set_vm_cmd(dsi);
 	ret = mtk_dsi_config_vdo_timing(dsi);
@@ -1556,6 +1739,19 @@ static const struct mtk_dsi_driver_data mt8189_dsi_driver_data = {
 	.support_per_frame_lp = true,
 };
 
+static const struct mtk_dsi_driver_data mt8196_dsi_driver_data = {
+	.reg_main = mtk_dsi_regs_main_v2,
+	.reg_qos = mtk_dsi_regs_qos_v2,
+	.reg_adv = mtk_dsi_regs_mt8196,
+	.max_link_rate_mbps = 2000,
+	.dsi_sram_bytes = 32,
+	.pixels_per_iter = 2,
+	.num_burst_lines = 8,
+	.has_size_ctl = true,
+	.cmdq_long_packet_ctl = true,
+	.support_per_frame_lp = true,
+};
+
 static const struct of_device_id mtk_dsi_of_match[] = {
 	{ .compatible = "mediatek,mt2701-dsi", .data = &mt2701_dsi_driver_data },
 	{ .compatible = "mediatek,mt8167-dsi", .data = &mt2701_dsi_driver_data },
@@ -1564,6 +1760,7 @@ static const struct of_device_id mtk_dsi_of_match[] = {
 	{ .compatible = "mediatek,mt8186-dsi", .data = &mt8186_dsi_driver_data },
 	{ .compatible = "mediatek,mt8188-dsi", .data = &mt8188_dsi_driver_data },
 	{ .compatible = "mediatek,mt8189-dsi", .data = &mt8189_dsi_driver_data },
+	{ .compatible = "mediatek,mt8196-dsi", .data = &mt8196_dsi_driver_data },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, mtk_dsi_of_match);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 08/10] drm/mediatek: mtk_dsi: Enable PM Runtime on probe
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
                   ` (6 preceding siblings ...)
  2026-07-13  9:58 ` [PATCH v2 07/10] drm/mediatek: mtk_dsi: Add support for MT8196 AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13 10:23   ` sashiko-bot
  2026-07-13  9:58 ` [PATCH v2 09/10] dt-bindings: display: mediatek: wdma: Add compatibles for more SoCs AngeloGioacchino Del Regno
  2026-07-13  9:58 ` [PATCH v2 10/10] drm/mediatek: Add Write DMA (WDMA) Engine for Writeback support AngeloGioacchino Del Regno
  9 siblings, 1 reply; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin

Enable PM Runtime at the end of mtk_dsi_probe() so that the
mediatek-drm mtk_ddp_comp_power_on() function is able to do
pm_runtime_resume_and_get() and, analogously, the function
mtk_ddp_comp_power_off() to pm_runtime_put() for the DSI
device.

This makes sure that the power domains for the DSI IP are
turned on and off when necessary.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 drivers/gpu/drm/mediatek/mtk_dsi.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index 5af44c13bd5a..5fbaa7d580d3 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -1684,6 +1684,10 @@ static int mtk_dsi_probe(struct platform_device *pdev)
 	dsi->bridge.of_node = dev->of_node;
 	dsi->bridge.type = DRM_MODE_CONNECTOR_DSI;
 
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 09/10] dt-bindings: display: mediatek: wdma: Add compatibles for more SoCs
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
                   ` (7 preceding siblings ...)
  2026-07-13  9:58 ` [PATCH v2 08/10] drm/mediatek: mtk_dsi: Enable PM Runtime on probe AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13 10:23   ` sashiko-bot
  2026-07-13  9:58 ` [PATCH v2 10/10] drm/mediatek: Add Write DMA (WDMA) Engine for Writeback support AngeloGioacchino Del Regno
  9 siblings, 1 reply; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin,
	Krzysztof Kozlowski

Multiple MediaTek SoCs have the Write DMA hardware in their Display
Controller: add the missing compatibles for Dimensity 1200 (MT6893)
and for Kompanio 500/520/820/1200 (MT8183/MT8186/MT8192/MT8195).

Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 .../devicetree/bindings/display/mediatek/mediatek,wdma.yaml  | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml
index 97d052b0fb61..c3ed867d058d 100644
--- a/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml
+++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml
@@ -22,11 +22,16 @@ properties:
   compatible:
     oneOf:
       - enum:
+          - mediatek,mt6893-disp-wdma
           - mediatek,mt8173-disp-wdma
       - items:
           - enum:
               - mediatek,mt6795-disp-wdma
               - mediatek,mt8167-disp-wdma
+              - mediatek,mt8183-disp-wdma
+              - mediatek,mt8186-disp-wdma
+              - mediatek,mt8192-disp-wdma
+              - mediatek,mt8195-disp-wdma
           - const: mediatek,mt8173-disp-wdma
 
   reg:
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 10/10] drm/mediatek: Add Write DMA (WDMA) Engine for Writeback support
  2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
                   ` (8 preceding siblings ...)
  2026-07-13  9:58 ` [PATCH v2 09/10] dt-bindings: display: mediatek: wdma: Add compatibles for more SoCs AngeloGioacchino Del Regno
@ 2026-07-13  9:58 ` AngeloGioacchino Del Regno
  2026-07-13 10:26   ` sashiko-bot
  9 siblings, 1 reply; 19+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-13  9:58 UTC (permalink / raw)
  To: chunkuang.hu
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, matthias.bgg, angelogioacchino.delregno,
	jitao.shi, dri-devel, linux-mediatek, devicetree, linux-kernel,
	linux-arm-kernel, kernel, justin.yeh, jason-jh.lin

Add a basic driver for the Write DMA Engine and initial compatible
for the MediaTek MT8173 and MediaTek Dimensity 1200 MT6893, and
hook it up to the mtk_ddp_comp and mtk_drm_drv in order for it to
probe.

This display controller component is used to enable the writeback
engine that can be used for faster display image capturing in the
userspace (for example, screenshots and screen recording).

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 drivers/gpu/drm/mediatek/Makefile        |   1 +
 drivers/gpu/drm/mediatek/mtk_ddp_comp.c  |  21 +-
 drivers/gpu/drm/mediatek/mtk_disp_drv.h  |  20 +
 drivers/gpu/drm/mediatek/mtk_disp_wdma.c | 611 +++++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_drv.c   |   4 +
 drivers/gpu/drm/mediatek/mtk_drm_drv.h   |   1 +
 6 files changed, 656 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/mediatek/mtk_disp_wdma.c

diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile
index 03b3470ea5b5..8079962597c8 100644
--- a/drivers/gpu/drm/mediatek/Makefile
+++ b/drivers/gpu/drm/mediatek/Makefile
@@ -11,6 +11,7 @@ mediatek-drm-y := mtk_crtc.o \
 		  mtk_disp_ovl.o \
 		  mtk_disp_ovl_adaptor.o \
 		  mtk_disp_rdma.o \
+		  mtk_disp_wdma.o \
 		  mtk_drm_drv.o \
 		  mtk_dsi.o \
 		  mtk_dpi.o \
diff --git a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c
index 13aaf12ecbe5..94b356da6de7 100644
--- a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c
+++ b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c
@@ -357,6 +357,22 @@ static const struct mtk_ddp_comp_funcs ddp_rdma = {
 	.get_num_formats = mtk_rdma_get_num_formats,
 };
 
+static const struct mtk_ddp_comp_funcs ddp_wdma = {
+	.clk_enable = mtk_wdma_clk_enable,
+	.clk_disable = mtk_wdma_clk_disable,
+	.config = mtk_wdma_config,
+	.start = mtk_wdma_start,
+	.stop = mtk_wdma_stop,
+	.register_vblank_cb = mtk_wdma_register_vblank_cb,
+	.unregister_vblank_cb = mtk_wdma_unregister_vblank_cb,
+	.enable_vblank = mtk_wdma_enable_vblank,
+	.disable_vblank = mtk_wdma_disable_vblank,
+	.layer_nr = mtk_wdma_layer_nr,
+	.layer_config = mtk_wdma_layer_config,
+	.get_formats = mtk_wdma_get_formats,
+	.get_num_formats = mtk_wdma_get_num_formats,
+};
+
 static const struct mtk_ddp_comp_funcs ddp_ufoe = {
 	.clk_enable = mtk_ddp_clk_enable,
 	.clk_disable = mtk_ddp_clk_disable,
@@ -460,8 +476,8 @@ static const struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_DRM_ID_MAX]
 	[DDP_COMPONENT_RDMA2]		= { MTK_DISP_RDMA,		2, &ddp_rdma },
 	[DDP_COMPONENT_RDMA4]		= { MTK_DISP_RDMA,		4, &ddp_rdma },
 	[DDP_COMPONENT_UFOE]		= { MTK_DISP_UFOE,		0, &ddp_ufoe },
-	[DDP_COMPONENT_WDMA0]		= { MTK_DISP_WDMA,		0, NULL },
-	[DDP_COMPONENT_WDMA1]		= { MTK_DISP_WDMA,		1, NULL },
+	[DDP_COMPONENT_WDMA0]		= { MTK_DISP_WDMA,		0, &ddp_wdma },
+	[DDP_COMPONENT_WDMA1]		= { MTK_DISP_WDMA,		1, &ddp_wdma },
 };
 
 static bool mtk_ddp_comp_find(struct device *dev,
@@ -642,6 +658,7 @@ int mtk_ddp_comp_init(struct device *dev, struct device_node *node, struct mtk_d
 	    type == MTK_DISP_OVL_2L ||
 	    type == MTK_DISP_PWM ||
 	    type == MTK_DISP_RDMA ||
+	    type == MTK_DISP_WDMA ||
 	    type == MTK_DPI ||
 	    type == MTK_DP_INTF ||
 	    type == MTK_DSI)
diff --git a/drivers/gpu/drm/mediatek/mtk_disp_drv.h b/drivers/gpu/drm/mediatek/mtk_disp_drv.h
index 5e2d8748120a..e0c30c6c7cc8 100644
--- a/drivers/gpu/drm/mediatek/mtk_disp_drv.h
+++ b/drivers/gpu/drm/mediatek/mtk_disp_drv.h
@@ -181,6 +181,26 @@ void mtk_mdp_rdma_config(struct device *dev, struct mtk_mdp_rdma_cfg *cfg,
 const u32 *mtk_mdp_rdma_get_formats(struct device *dev);
 size_t mtk_mdp_rdma_get_num_formats(struct device *dev);
 
+int mtk_wdma_clk_enable(struct device *dev);
+void mtk_wdma_clk_disable(struct device *dev);
+void mtk_wdma_config(struct device *dev, unsigned int width,
+		     unsigned int height, unsigned int vrefresh,
+		     unsigned int bpc, struct cmdq_pkt *cmdq_pkt);
+unsigned int mtk_wdma_layer_nr(struct device *dev);
+void mtk_wdma_layer_config(struct device *dev, unsigned int idx,
+			   struct mtk_plane_state *state,
+			   struct cmdq_pkt *cmdq_pkt);
+void mtk_wdma_start(struct device *dev);
+void mtk_wdma_stop(struct device *dev);
+void mtk_wdma_register_vblank_cb(struct device *dev,
+				 void (*vblank_cb)(void *),
+				 void *vblank_cb_data);
+void mtk_wdma_unregister_vblank_cb(struct device *dev);
+void mtk_wdma_enable_vblank(struct device *dev);
+void mtk_wdma_disable_vblank(struct device *dev);
+const u32 *mtk_wdma_get_formats(struct device *dev);
+size_t mtk_wdma_get_num_formats(struct device *dev);
+
 int mtk_padding_clk_enable(struct device *dev);
 void mtk_padding_clk_disable(struct device *dev);
 void mtk_padding_start(struct device *dev);
diff --git a/drivers/gpu/drm/mediatek/mtk_disp_wdma.c b/drivers/gpu/drm/mediatek/mtk_disp_wdma.c
new file mode 100644
index 000000000000..a093d6d57f1a
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_disp_wdma.c
@@ -0,0 +1,611 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021 MediaTek Inc.
+ * Copyright (c) 2025 Collabora Ltd
+ *                    AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_writeback.h>
+
+#include <linux/align.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/soc/mediatek/mtk-cmdq.h>
+#include <linux/wordpart.h>
+
+#include "mtk_crtc.h"
+#include "mtk_ddp_comp.h"
+#include "mtk_disp_drv.h"
+#include "mtk_drm_drv.h"
+
+#define DISP_REG_WDMA_INT_ENABLE		0x000
+ #define WDMA_FRAME_COMPLETE_INT		BIT(0)
+#define DISP_REG_WDMA_INT_STATUS		0x004
+#define DISP_REG_WDMA_EN			0x008
+ #define WDMA_ENGINE_EN				BIT(0)
+#define DISP_REG_WDMA_CFG			0x014
+ #define WDMA_CFG_OUT_FMT			GENMASK(7, 4)
+  #define WDMA_OUT_FMT_RGB565			0
+  #define WDMA_OUT_FMT_RGB888			1
+  #define WDMA_OUT_FMT_RGBA8888			2
+  #define WDMA_OUT_FMT_ARGB8888			3
+  #define WDMA_OUT_FMT_UYVY			4
+  #define WDMA_OUT_FMT_YUY2			5
+  #define WDMA_OUT_FMT_P010			6
+  #define WDMA_OUT_FMT_Y_ONLY			7
+  #define WDMA_OUT_FMT_I420			8
+  #define WDMA_OUT_FMT_ARGB2101010		11
+  #define WDMA_OUT_FMT_NV12			12
+ #define WDMA_CT_EN				BIT(11)
+ #define WDMA_CFG_SWAP				BIT(16)
+ #define WDMA_UFO_DCP_ENABLE			BIT(17)
+ #define WDMA_INT_MTX_SEL			GENMASK(27, 23)
+  #define WDMA_CT_COEF_RGB_TO_JPEG		0
+  #define WDMA_CT_COEF_JPEG_TO_RGB		4
+#define DISP_REG_WDMA_SRC_SIZE			0x018
+#define DISP_REG_WDMA_CLIP_SIZE			0x01c
+ #define WDMA_HEIGHT_PX				GENMASK(29, 16)
+ #define WDMA_WIDTH_PX				GENMASK(13, 0)
+#define DISP_REG_WDMA_CLIP_COORD		0x020
+ #define WDMA_CLIP_Y_COORD			GENMASK(29, 16)
+ #define WDMA_CLIP_X_COORD			GENMASK(13, 0)
+#define DISP_REG_WDMA_SHADOW_CTRL		0x024
+ #define WDMA_FORCE_COMMIT			BIT(0)
+ #define WDMA_BYPASS_SHADOW			BIT(1)
+#define DISP_REG_WDMA_DST_W_IN_BYTE		0x028
+#define DISP_REG_WDMA_DST_UV_PITCH		0x078
+ #define WDMA_UV_DST_W_IN_BYTE			GENMASK(15, 0)
+#define DISP_REG_WDMA_DST_ADDR_LSB		0xf00
+#define DISP_REG_WDMA_DST_ADDR_MSB_MT6893	0xf20
+#define DISP_REG_WDMA_DST_ADDRX(r, x)		(r + (x * 0x4))
+
+static const u32 mtk_wdma_wb_output_formats[] = {
+	DRM_FORMAT_RGB888
+};
+
+static const u32 mt6893_formats[] = {
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_BGRX8888,
+	DRM_FORMAT_BGRA8888,
+	DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_RGB888,
+	DRM_FORMAT_BGR888,
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_YUV420,
+	DRM_FORMAT_YVU420,
+	DRM_FORMAT_UYVY,
+	DRM_FORMAT_YUYV,
+};
+
+struct mtk_disp_wdma_data {
+	u32 reg_wdma_dst_addr0_msb;
+	const u32 *formats;
+	size_t num_formats;
+};
+
+struct mtk_disp_wdma {
+	struct device			*dev;
+	struct clk			*clk;
+	void __iomem			*regs;
+	struct cmdq_client_reg		cmdq_reg;
+	const struct mtk_disp_wdma_data	*data;
+	void				(*vblank_cb)(void *data);
+	void				*vblank_cb_data;
+	int				irq;
+	struct drm_writeback_connector	wb_connector;
+	bool				wb_pending;
+};
+
+static inline struct mtk_disp_wdma *connector_to_wdma(struct drm_connector *connector)
+{
+	return container_of(connector, struct mtk_disp_wdma, wb_connector.base);
+}
+
+static irqreturn_t mtk_disp_wdma_irq_handler(int irq, void *dev_id)
+{
+	struct mtk_disp_wdma *wdma = dev_id;
+
+	/* Clear frame completion interrupt */
+	writel(0x0, wdma->regs + DISP_REG_WDMA_INT_STATUS);
+
+	if (!wdma->vblank_cb)
+		return IRQ_NONE;
+
+	wdma->vblank_cb(wdma->vblank_cb_data);
+
+	/* TODO: Move completion signaling to CMDQ interrupt callback */
+	if (wdma->wb_pending) {
+		drm_writeback_signal_completion(&wdma->wb_connector, 0);
+		wdma->wb_pending = false;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void wdma_update_bits(struct device *dev, unsigned int reg,
+			     unsigned int mask, unsigned int val)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+	unsigned int tmp = readl(wdma->regs + reg);
+
+	tmp = (tmp & ~mask) | (val & mask);
+	writel(tmp, wdma->regs + reg);
+}
+
+void mtk_wdma_register_vblank_cb(struct device *dev,
+				 void (*vblank_cb)(void *),
+				 void *vblank_cb_data)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+
+	wdma->vblank_cb = vblank_cb;
+	wdma->vblank_cb_data = vblank_cb_data;
+}
+
+void mtk_wdma_unregister_vblank_cb(struct device *dev)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+
+	wdma->vblank_cb = NULL;
+	wdma->vblank_cb_data = NULL;
+}
+
+void mtk_wdma_enable_vblank(struct device *dev)
+{
+	wdma_update_bits(dev, DISP_REG_WDMA_INT_ENABLE, WDMA_FRAME_COMPLETE_INT,
+			 WDMA_FRAME_COMPLETE_INT);
+}
+
+void mtk_wdma_disable_vblank(struct device *dev)
+{
+	wdma_update_bits(dev, DISP_REG_WDMA_INT_ENABLE, WDMA_FRAME_COMPLETE_INT, 0);
+}
+
+const u32 *mtk_wdma_get_formats(struct device *dev)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+
+	return wdma->data->formats;
+}
+
+size_t mtk_wdma_get_num_formats(struct device *dev)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+
+	return wdma->data->num_formats;
+}
+
+int mtk_wdma_clk_enable(struct device *dev)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+
+	return clk_prepare_enable(wdma->clk);
+}
+
+void mtk_wdma_clk_disable(struct device *dev)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(wdma->clk);
+}
+
+void mtk_wdma_start(struct device *dev)
+{
+	wdma_update_bits(dev, DISP_REG_WDMA_EN, WDMA_ENGINE_EN,
+			 WDMA_ENGINE_EN);
+}
+
+void mtk_wdma_stop(struct device *dev)
+{
+	wdma_update_bits(dev, DISP_REG_WDMA_EN, WDMA_ENGINE_EN, 0);
+}
+
+void mtk_wdma_config(struct device *dev, unsigned int width,
+		     unsigned int height, unsigned int vrefresh,
+		     unsigned int bpc, struct cmdq_pkt *cmdq_pkt)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+
+	writel(WDMA_FORCE_COMMIT | WDMA_BYPASS_SHADOW,
+	       wdma->regs + DISP_REG_WDMA_SHADOW_CTRL);
+}
+
+static u32 wdma_fmt_convert(unsigned int fmt)
+{
+	switch (fmt) {
+	default:
+	case DRM_FORMAT_RGB565:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_RGB565);
+	case DRM_FORMAT_BGR565:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_RGB565) | WDMA_CFG_SWAP;
+	case DRM_FORMAT_RGB888:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_RGB888);
+	case DRM_FORMAT_BGR888:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_RGB888) | WDMA_CFG_SWAP;
+	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_RGBA8888:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_RGBA8888);
+	case DRM_FORMAT_BGRX8888:
+	case DRM_FORMAT_BGRA8888:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_RGBA8888) | WDMA_CFG_SWAP;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_ARGB8888);
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_ARGB8888) | WDMA_CFG_SWAP;
+	case DRM_FORMAT_UYVY:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_UYVY);
+	case DRM_FORMAT_YUYV:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_YUY2);
+	case DRM_FORMAT_YUV420:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_I420);
+	case DRM_FORMAT_YVU420:
+		return FIELD_PREP(WDMA_CFG_OUT_FMT, WDMA_OUT_FMT_I420) | WDMA_CFG_SWAP;
+	}
+}
+
+unsigned int mtk_wdma_layer_nr(struct device *dev)
+{
+	return 1;
+}
+
+static void mtk_wdma_ddp_write_dst_addr(struct cmdq_pkt *cmdq_pkt, u64 val,
+					u8 reg_id, struct mtk_disp_wdma *wdma)
+{
+	mtk_ddp_write(cmdq_pkt, lower_32_bits(val), &wdma->cmdq_reg, wdma->regs,
+		      DISP_REG_WDMA_DST_ADDRX(DISP_REG_WDMA_DST_ADDR_LSB, 1));
+
+	if (wdma->data->reg_wdma_dst_addr0_msb == 0)
+		return;
+
+	mtk_ddp_write(cmdq_pkt, upper_32_bits(val), &wdma->cmdq_reg, wdma->regs,
+		      DISP_REG_WDMA_DST_ADDRX(wdma->data->reg_wdma_dst_addr0_msb, 1));
+}
+
+static void mtk_wdma_format_config(struct mtk_disp_wdma *wdma,
+				   struct mtk_plane_pending_state *pending,
+				   const struct drm_format_info *fmt_info,
+				   struct cmdq_pkt *cmdq_pkt)
+{
+	unsigned int u_off, u_stride, u_size, v_off;
+	u32 val;
+
+	/*
+	 * For RGB formats, this sets the image destination address;
+	 * For YUV formats, this sets the Y component destination address.
+	 */
+	mtk_wdma_ddp_write_dst_addr(cmdq_pkt, pending->addr, 0, wdma);
+
+	if (!fmt_info->is_yuv) {
+		/* Disable color transform matrix and data compression */
+		mtk_ddp_write_mask(cmdq_pkt, 0, &wdma->cmdq_reg, wdma->regs,
+				   DISP_REG_WDMA_CFG,
+				   WDMA_UFO_DCP_ENABLE | WDMA_CT_EN);
+		return;
+	}
+
+	/* Additional format config required only for 420 sampling */
+	if (!drm_format_info_is_yuv_sampling_420(fmt_info))
+		return;
+
+	u_off = pending->pitch * pending->height;
+	u_stride = pending->pitch / 2;
+
+	if (drm_format_info_is_yuv_planar(fmt_info)) {
+		/* YUV420 or YVU420 */
+		u_stride = ALIGN(u_stride, 16);
+		u_size = u_stride * pending->height / 2;
+		v_off = u_off + u_size;
+	} else {
+		/* NV12 or NV21 */
+		u_size = u_stride * pending->height / 2;
+		v_off = 0;
+	}
+
+	/* Set U and V components destination addresses */
+	mtk_wdma_ddp_write_dst_addr(cmdq_pkt, pending->addr + u_off, 1, wdma);
+	mtk_wdma_ddp_write_dst_addr(cmdq_pkt, pending->addr + v_off, 2, wdma);
+
+	mtk_ddp_write(cmdq_pkt, FIELD_PREP(WDMA_UV_DST_W_IN_BYTE, u_stride),
+		      &wdma->cmdq_reg, wdma->regs, DISP_REG_WDMA_DST_UV_PITCH);
+
+	/* Color transform coefficient selection */
+	val = FIELD_PREP_CONST(WDMA_INT_MTX_SEL, WDMA_CT_COEF_JPEG_TO_RGB);
+	mtk_ddp_write_mask(cmdq_pkt, val, &wdma->cmdq_reg, wdma->regs,
+			   DISP_REG_WDMA_CFG, WDMA_INT_MTX_SEL);
+
+	/* Enable color transform matrix, disable data compression */
+	mtk_ddp_write_mask(cmdq_pkt, WDMA_CT_EN, &wdma->cmdq_reg, wdma->regs,
+			   DISP_REG_WDMA_CFG, WDMA_UFO_DCP_ENABLE | WDMA_CT_EN);
+}
+
+void mtk_wdma_layer_config(struct device *dev, unsigned int idx,
+			   struct mtk_plane_state *state,
+			   struct cmdq_pkt *cmdq_pkt)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+	struct mtk_plane_pending_state *pending = &state->pending;
+	unsigned int pitch = pending->pitch & 0xffff;
+	unsigned int fmt = pending->format;
+	unsigned int con = wdma_fmt_convert(fmt);
+	const struct drm_format_info *fmt_info = drm_format_info(fmt);
+	u16 clip_sz_h = pending->height;
+	u16 clip_sz_w = pending->width;
+	u32 val;
+
+	val = FIELD_PREP(WDMA_HEIGHT_PX, pending->height);
+	val |= FIELD_PREP(WDMA_WIDTH_PX, pending->width);
+	mtk_ddp_write(cmdq_pkt, val, &wdma->cmdq_reg, wdma->regs,
+		      DISP_REG_WDMA_SRC_SIZE);
+
+	val = FIELD_PREP(WDMA_HEIGHT_PX, pending->y);
+	val |= FIELD_PREP(WDMA_WIDTH_PX, pending->x);
+	mtk_ddp_write(cmdq_pkt, val, &wdma->cmdq_reg, wdma->regs,
+		      DISP_REG_WDMA_CLIP_COORD);
+
+	if (fmt_info->is_yuv) {
+		if ((pending->y + pending->height) % 2)
+			clip_sz_h--;
+
+		if ((pending->x + pending->width) % 2)
+			clip_sz_w--;
+	}
+	val = FIELD_PREP(WDMA_HEIGHT_PX, clip_sz_h);
+	val |= FIELD_PREP(WDMA_WIDTH_PX, clip_sz_w);
+	mtk_ddp_write(cmdq_pkt, val, &wdma->cmdq_reg, wdma->regs,
+		      DISP_REG_WDMA_CLIP_SIZE);
+
+	mtk_ddp_write(cmdq_pkt, con, &wdma->cmdq_reg, wdma->regs,
+		      DISP_REG_WDMA_CFG);
+	mtk_ddp_write(cmdq_pkt, pitch, &wdma->cmdq_reg, wdma->regs,
+		      DISP_REG_WDMA_DST_W_IN_BYTE);
+
+	mtk_wdma_format_config(wdma, pending, fmt_info, cmdq_pkt);
+
+	drm_writeback_queue_job(&wdma->wb_connector, wdma->wb_connector.base.state);
+}
+
+static enum drm_connector_status
+mtk_wdma_wb_connector_detect(struct drm_connector *connector, bool force)
+{
+	return connector_status_connected;
+}
+
+static const struct drm_connector_funcs mtk_wdma_wb_connector_funcs = {
+	.detect = mtk_wdma_wb_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int mtk_wdma_wb_atomic_check(struct drm_encoder *encoder,
+				    struct drm_crtc_state *crtc_state,
+				    struct drm_connector_state *conn_state)
+{
+	const struct drm_display_mode *mode = &crtc_state->mode;
+	struct drm_framebuffer *fb;
+	int i;
+
+	if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
+		return 0;
+
+	fb = conn_state->writeback_job->fb;
+	if (fb->width != mode->hdisplay || fb->height != mode->vdisplay)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(mtk_wdma_wb_output_formats); i++) {
+		if (fb->format->format == mtk_wdma_wb_output_formats[i])
+			return 0;
+	}
+
+	return -EINVAL;
+}
+
+static const struct drm_encoder_helper_funcs mtk_wdma_wb_encoder_helper_funcs = {
+	.atomic_check = mtk_wdma_wb_atomic_check,
+};
+
+static int mtk_wdma_wb_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+
+	return drm_add_modes_noedid(connector, dev->mode_config.max_width,
+				    dev->mode_config.max_height);
+}
+
+static enum drm_mode_status
+mtk_wdma_wb_connector_mode_valid(struct drm_connector *connector,
+				 const struct drm_display_mode *mode)
+{
+	struct drm_device *dev = connector->dev;
+	struct drm_mode_config *mode_config = &dev->mode_config;
+	int w = mode->hdisplay, h = mode->vdisplay;
+
+	if (w < mode_config->min_width || w > mode_config->max_width)
+		return MODE_BAD_HVALUE;
+
+	if (h < mode_config->min_height || h > mode_config->max_height)
+		return MODE_BAD_VVALUE;
+
+	return MODE_OK;
+}
+
+static void mtk_wdma_wb_connector_atomic_commit(struct drm_connector *connector,
+						struct drm_atomic_commit *state)
+{
+	struct drm_connector_state *conn_state =
+		drm_atomic_get_new_connector_state(state, connector);
+	struct mtk_disp_wdma *wdma = connector_to_wdma(connector);
+	struct drm_framebuffer *fb;
+	struct drm_gem_object *gem;
+	struct drm_gem_dma_object *dma_obj;
+	dma_addr_t addr;
+
+	if (WARN_ON(!conn_state->writeback_job))
+		return;
+
+	fb = conn_state->writeback_job->fb;
+	gem = fb->obj[0];
+	dma_obj = to_drm_gem_dma_obj(gem);
+	addr = dma_obj->dma_addr;
+
+	/* Store writeback pending state before queuing the job */
+	wdma->wb_pending = true;
+
+	mtk_wdma_ddp_write_dst_addr(NULL, addr, 0, wdma);
+	drm_writeback_queue_job(&wdma->wb_connector, conn_state);
+}
+
+static const struct drm_connector_helper_funcs mtk_wdma_wb_connector_helper_funcs = {
+	.get_modes = mtk_wdma_wb_connector_get_modes,
+	.mode_valid = mtk_wdma_wb_connector_mode_valid,
+	.atomic_commit = mtk_wdma_wb_connector_atomic_commit,
+};
+
+static int mtk_disp_wdma_bind(struct device *dev, struct device *master,
+			      void *data)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+	struct drm_device *drm_dev = data;
+	int crtcs, ret;
+
+	crtcs = mtk_find_possible_crtcs(drm_dev, wdma->dev);
+	if (crtcs < 0)
+		return crtcs;
+
+	drm_connector_helper_add(&wdma->wb_connector.base,
+				 &mtk_wdma_wb_connector_helper_funcs);
+
+	ret = drm_writeback_connector_init(drm_dev, &wdma->wb_connector,
+					   &mtk_wdma_wb_connector_funcs,
+					   &mtk_wdma_wb_encoder_helper_funcs,
+					   mtk_wdma_wb_output_formats,
+					   ARRAY_SIZE(mtk_wdma_wb_output_formats),
+					   crtcs);
+	if (ret)
+		return ret;
+
+	/* Disable and clear pending interrupts */
+	writel(0x0, wdma->regs + DISP_REG_WDMA_INT_ENABLE);
+	writel(0x0, wdma->regs + DISP_REG_WDMA_INT_STATUS);
+
+	enable_irq(wdma->irq);
+	return 0;
+}
+
+static void mtk_disp_wdma_unbind(struct device *dev, struct device *master,
+				 void *data)
+{
+	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
+
+	disable_irq(wdma->irq);
+}
+
+static const struct component_ops mtk_disp_wdma_component_ops = {
+	.bind	= mtk_disp_wdma_bind,
+	.unbind = mtk_disp_wdma_unbind,
+};
+
+static int mtk_disp_wdma_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_disp_wdma *priv;
+	struct resource *res;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->dev = dev;
+
+	priv->irq = platform_get_irq(pdev, 0);
+	if (priv->irq < 0)
+		return priv->irq;
+
+	priv->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(priv->clk))
+		return dev_err_probe(dev, PTR_ERR(priv->clk),
+				     "failed to get wdma clk\n");
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	priv->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(priv->regs))
+		return dev_err_probe(dev, PTR_ERR(priv->regs),
+				     "failed to ioremap wdma\n");
+#if IS_REACHABLE(CONFIG_MTK_CMDQ)
+	ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0);
+	if (ret)
+		dev_dbg(dev, "get mediatek,gce-client-reg fail!\n");
+#endif
+
+	ret = devm_request_irq(dev, priv->irq, mtk_disp_wdma_irq_handler,
+			       IRQF_NO_AUTOEN, dev_name(dev), priv);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to request irq\n");
+
+	priv->data = of_device_get_match_data(dev);
+
+	platform_set_drvdata(pdev, priv);
+
+	pm_runtime_enable(dev);
+
+	ret = component_add(dev, &mtk_disp_wdma_component_ops);
+	if (ret) {
+		pm_runtime_disable(dev);
+		return dev_err_probe(dev, ret, "Failed to add component\n");
+	}
+
+	return 0;
+}
+
+static void mtk_disp_wdma_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &mtk_disp_wdma_component_ops);
+
+	pm_runtime_disable(&pdev->dev);
+}
+
+static const struct mtk_disp_wdma_data mt6893_wdma_driver_data = {
+	.reg_wdma_dst_addr0_msb = DISP_REG_WDMA_DST_ADDR_MSB_MT6893,
+	.formats = mt6893_formats,
+	.num_formats = ARRAY_SIZE(mt6893_formats),
+};
+
+static const struct mtk_disp_wdma_data mt8173_wdma_driver_data = {
+	.formats = mt6893_formats,
+	.num_formats = ARRAY_SIZE(mt6893_formats),
+};
+
+static const struct of_device_id mtk_disp_wdma_driver_dt_match[] = {
+	{ .compatible = "mediatek,mt6893-disp-wdma", .data = &mt6893_wdma_driver_data },
+	{ .compatible = "mediatek,mt8173-disp-wdma", .data = &mt8173_wdma_driver_data },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mtk_disp_wdma_driver_dt_match);
+
+struct platform_driver mtk_disp_wdma_driver = {
+	.probe		= mtk_disp_wdma_probe,
+	.remove		= mtk_disp_wdma_remove,
+	.driver		= {
+		.name	= "mediatek-disp-wdma",
+		.of_match_table = mtk_disp_wdma_driver_dt_match,
+	},
+};
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
index 9a4c59849c4b..e54f0654f2f9 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
@@ -796,6 +796,8 @@ static const struct of_device_id mtk_ddp_comp_dt_ids[] = {
 	  .data = (void *)MTK_DISP_RDMA },
 	{ .compatible = "mediatek,mt8173-disp-ufoe",
 	  .data = (void *)MTK_DISP_UFOE },
+	{ .compatible = "mediatek,mt6893-disp-wdma",
+	  .data = (void *)MTK_DISP_WDMA },
 	{ .compatible = "mediatek,mt8173-disp-wdma",
 	  .data = (void *)MTK_DISP_WDMA },
 	{ .compatible = "mediatek,mt2701-dpi",
@@ -1173,6 +1175,7 @@ static int mtk_drm_probe(struct platform_device *pdev)
 		    comp_type == MTK_DISP_OVL_2L ||
 		    comp_type == MTK_DISP_OVL_ADAPTOR ||
 		    comp_type == MTK_DISP_RDMA ||
+		    comp_type == MTK_DISP_WDMA ||
 		    comp_type == MTK_DP_INTF ||
 		    comp_type == MTK_DPI ||
 		    comp_type == MTK_DSI) {
@@ -1281,6 +1284,7 @@ static struct platform_driver * const mtk_drm_drivers[] = {
 	&mtk_disp_ovl_adaptor_driver,
 	&mtk_disp_ovl_driver,
 	&mtk_disp_rdma_driver,
+	&mtk_disp_wdma_driver,
 	&mtk_dpi_driver,
 	&mtk_drm_platform_driver,
 	&mtk_dsi_driver,
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
index 43aac2d956e7..8b5c51d93f72 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.h
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
@@ -77,6 +77,7 @@ extern struct platform_driver mtk_disp_merge_driver;
 extern struct platform_driver mtk_disp_ovl_adaptor_driver;
 extern struct platform_driver mtk_disp_ovl_driver;
 extern struct platform_driver mtk_disp_rdma_driver;
+extern struct platform_driver mtk_disp_wdma_driver;
 extern struct platform_driver mtk_dpi_driver;
 extern struct platform_driver mtk_dsi_driver;
 extern struct platform_driver mtk_ethdr_driver;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 05/10] drm/mediatek: mtk_dsi: Transfer register offsets to per-SoC const
  2026-07-13  9:58 ` [PATCH v2 05/10] drm/mediatek: mtk_dsi: Transfer register offsets to per-SoC const AngeloGioacchino Del Regno
@ 2026-07-13 10:07   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-13 10:07 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno; +Cc: robh, dri-devel, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The loop reading DSI RX data in `mtk_dsi_host_transfer` incorrectly uses the loop counter to offset the `reg_main` array index instead of the MMIO base offset, causing out-of-bounds array reads and accessing incorrect hardware registers.
--

--- Patch [5]: [PATCH v2 05/10] drm/mediatek: mtk_dsi: Transfer register offsets to per-SoC const ---
commit 43b878ef0a8fe56d80e1c26ea5be280083d3ccaf
Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>

drm/mediatek: mtk_dsi: Transfer register offsets to per-SoC const

This patch refactors the MediaTek DSI driver by moving register offsets
into per-SoC constant arrays. This separates the main DSI registers from the
advanced and SoC-specific ones, preparing the driver for newer hardware
generations with different register layouts.

> diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
> index 284dae372c7c2..6a77ce8eea91f 100644
> --- a/drivers/gpu/drm/mediatek/mtk_dsi.c
> +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
[ ... ]
> @@ -1259,7 +1394,8 @@ static ssize_t mtk_dsi_host_transfer(struct mipi_dsi_host *host,
>  	}
>  
>  	for (i = 0; i < 16; i++)
> -		*(read_data + i) = readb(dsi->regs + DSI_RX_DATA0 + i);
> +		*(read_data + i) = readb(dsi->regs +
> +					 dsi->driver_data->reg_main[DSI_RX_DATA0 + i]);

[Severity: High]
Does this code read out of bounds on the reg_main array?

The original code added i to the DSI_RX_DATA0 offset to read consecutive bytes.
In the new code, DSI_RX_DATA0 is an enum value (17). When adding i (which goes
up to 15) to DSI_RX_DATA0 inside the index, this accesses reg_main up to index
32, but DSI_MAIN_REG_MAX is only 28.

This means for i >= 4, the code will fetch unrelated register offsets (such as
DSI_RACK or DSI_PHY_LCCON) instead of the expected RX data bytes. For i >= 11,
it reads past the end of the reg_main array, which could cause MMIO reads at
unexpected addresses.

Should the loop counter i be added after the array lookup instead, such as
dsi->driver_data->reg_main[DSI_RX_DATA0] + i?

>  
>  	recv_cnt = mtk_dsi_recv_cnt(read_data[0], read_data);
>  
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713095837.16590-1-angelogioacchino.delregno@collabora.com?part=5

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 04/10] drm/mediatek: mtk_dsi: Enable interrupt at component bind time
  2026-07-13  9:58 ` [PATCH v2 04/10] drm/mediatek: mtk_dsi: Enable interrupt at component bind time AngeloGioacchino Del Regno
@ 2026-07-13 10:11   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-13 10:11 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno; +Cc: conor+dt, devicetree, dri-devel, robh

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Calling `enable_irq()` in `mtk_dsi_bind()` executes before the IRQ is requested, leaving the interrupt permanently disabled.

Pre-existing issues:
- [High] Data race on `dsi->irq_data` between process and hardirq context leads to dropped interrupts.
- [High] `mtk_dsi_host_send_cmd()` incorrectly interprets `-ERESTARTSYS` as success, causing the driver to process stale hardware memory.
- [High] Unbounded busy-wait loop in `mtk_dsi_irq()` hard-locks the CPU if hardware stalls.
--

--- Patch [4]: [PATCH v2 04/10] drm/mediatek: mtk_dsi: Enable interrupt at component bind time ---
commit ab0ed7e073fb408fbb00443f437ebcc970005f69
Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>

drm/mediatek: mtk_dsi: Enable interrupt at component bind time

This patch changes the DSI interrupt to be requested with IRQF_NO_AUTOEN
and enables it later during the component bind phase. This aims to prevent
interrupt storms and artifacts caused by interrupts arriving before the
display controller is fully ready.

> diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
> index 3e22553ca990f..284dae372c7c2 100644
> --- a/drivers/gpu/drm/mediatek/mtk_dsi.c
> +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c

[ ... ]

> @@ -1052,7 +1053,13 @@ static int mtk_dsi_bind(struct device *dev, struct device *master, void *data)
>  	if (ret)
>  		return ret;
>  
> -	return device_reset_optional(dev);
> +	ret = device_reset_optional(dev);
> +	if (ret)
> +		return ret;
> +
> +	enable_irq(dsi->irq);
> +
> +	return 0;
>  }
>  
>  static void mtk_dsi_unbind(struct device *dev, struct device *master,
> @@ -1060,6 +1067,8 @@ static void mtk_dsi_unbind(struct device *dev, struct device *master,
>  {
>  	struct mtk_dsi *dsi = dev_get_drvdata(dev);
>  
> +	disable_irq(dsi->irq);
> +
>  	drm_encoder_cleanup(&dsi->encoder);
>  }

[ ... ]

> @@ -1338,8 +1346,8 @@ static int mtk_dsi_probe(struct platform_device *pdev)
>  	if (ret < 0)
>  		return dev_err_probe(dev, ret, "Failed to register DSI host\n");
>  
> -	ret = devm_request_irq(&pdev->dev, irq_num, mtk_dsi_irq,
> -			       IRQF_TRIGGER_NONE, dev_name(&pdev->dev), dsi);
> +	ret = devm_request_irq(&pdev->dev, dsi->irq, mtk_dsi_irq,
> +			       IRQF_NO_AUTOEN, dev_name(&pdev->dev), dsi);

[Severity: High]
Does mipi_dsi_host_register() trigger component binding synchronously before
the IRQ is actually requested here?

If mtk_dsi_probe() invokes mipi_dsi_host_register() before devm_request_irq():
1. mipi_dsi_host_register() synchronously probes child panel devices
2. The child devices invoke component_add() for the DSI component
3. If the aggregate device is ready, this synchronously triggers mtk_dsi_bind()
4. mtk_dsi_bind() then calls enable_irq(dsi->irq)

Since devm_request_irq() hasn't run yet, will enable_irq() encounter an
unrequested IRQ descriptor and abort without unmasking the hardware? When
devm_request_irq() subsequently runs with IRQF_NO_AUTOEN, it seems the IRQ
might remain disabled forever, completely breaking display operation.

[Severity: High]
This is a pre-existing issue, but can this code drop interrupts due to a data
race on dsi->irq_data?

Process context uses a load-modify-store to clear flags:

mtk_dsi_irq_data_clear()
    dsi->irq_data &= ~irq_bit;

While hardirq context uses a non-atomic operation to set flags:

mtk_dsi_irq_data_set()
    dsi->irq_data |= irq_bit;

If an interrupt fires between the load and store in process context, the
interrupt handler will set its status flag, but the process context's store
will overwrite dsi->irq_data with its stale value, destroying the newly set
flag and potentially causing wait_event polling timeouts.

[Severity: High]
This is a pre-existing issue, but does mtk_dsi_host_send_cmd() incorrectly
interpret -ERESTARTSYS as success?

mtk_dsi_wait_for_irq_done() returns -ERESTARTSYS if interrupted by a signal:

mtk_dsi_wait_for_irq_done()
    ret = wait_event_interruptible_timeout(...);

However, mtk_dsi_host_send_cmd() evaluates the return value as a boolean:

mtk_dsi_host_send_cmd()
    if (!mtk_dsi_wait_for_irq_done(dsi, flag, 2000))
        return -ETIME;
    else
        return 0;

Since -ERESTARTSYS is non-zero, its negation evaluates to false, causing
execution to bypass the error path and return 0. The caller then assumes the
hardware operation completed successfully and might proceed to read stale
memory from the hardware RX registers.

[Severity: High]
This is a pre-existing issue, but can the busy-wait loop in mtk_dsi_irq()
cause a hard lockup?

The mtk_dsi_irq() interrupt handler runs in hardirq context and contains
a polling loop without a timeout:

mtk_dsi_irq()
    do {
        mtk_dsi_mask(dsi, DSI_RACK, RACK, RACK);
        tmp = readl(dsi->regs + DSI_INTSTA);
    } while (tmp & DSI_BUSY);

If the DSI hardware encounters an error, loses its clock, or gets stuck in a
busy state, the DSI_BUSY bit will never clear and the CPU will spin
indefinitely.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713095837.16590-1-angelogioacchino.delregno@collabora.com?part=4

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 06/10] drm/mediatek: mtk_dsi: Add support for MT8189
  2026-07-13  9:58 ` [PATCH v2 06/10] drm/mediatek: mtk_dsi: Add support for MT8189 AngeloGioacchino Del Regno
@ 2026-07-13 10:13   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-13 10:13 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno; +Cc: conor+dt, robh, devicetree, dri-devel

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [High] Inaccurate link rate overestimation falsely rejects valid modes and incorrectly configures the PHY clock when DSC is enabled.

Pre-existing issues:
- [High] Silent 32-bit integer overflow when calculating DSI data rate for high-resolution panels on 32-bit architectures.
- [Medium] 32-bit signed integer overflow in `wanted_link_rate` calculation allows maliciously crafted modes to bypass the maximum link rate security check.
--

--- Patch [6]: [PATCH v2 06/10] drm/mediatek: mtk_dsi: Add support for MT8189 ---
commit 06bb023e18f2fab2efd045626f608227b5c0522f
Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Subject: drm/mediatek: mtk_dsi: Add support for MT8189

This commit adds support for the DSI IP in the MT8189 SoC, which supports a
higher link rate of 2.5Gbps. It introduces a max_link_rate_mbps member to
parameterize the maximum link rate per SoC and updates the mode validation
callback to use this new parameter with 64-bit math.

> diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
> index 6a77ce8eea91f..2df2a9d5ec038 100644
> --- a/drivers/gpu/drm/mediatek/mtk_dsi.c
> +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
> @@ -1070,13 +1072,20 @@ mtk_dsi_bridge_mode_valid(struct drm_bridge *bridge,
>  			  const struct drm_display_mode *mode)
>  {
>  	struct mtk_dsi *dsi = bridge_to_dsi(bridge);
> +	const struct mtk_dsi_driver_data *data = dsi->driver_data;
> +	u64 wanted_link_rate, max_link_rate;
>  	int bpp;
>  
>  	bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
>  	if (bpp < 0)
>  		return MODE_ERROR;
>  
> -	if (mode->clock * bpp / dsi->lanes > 1500000)
> +	wanted_link_rate = mode->clock * bpp;

[Severity: Medium]
This is a pre-existing issue, but does this calculation allow a 32-bit signed
integer overflow? Since mode->clock and bpp are both evaluated as 32-bit
signed integers, an abnormally high clock value could wrap around to a small
positive value before being zero-extended and assigned to the 64-bit
wanted_link_rate. Could this allow crafted modes to bypass the maximum link
rate check?

[Severity: High]
Does this calculation also overestimate the link rate when Display Stream
Compression (DSC) is enabled? By using the raw uncompressed bpp instead of
the compressed bit rate (dsi->dsc->bits_per_pixel), high-resolution
DSC-enabled displays might be falsely rejected as MODE_CLOCK_HIGH.

[Severity: High]
This is a pre-existing issue, but does a similar 32-bit integer overflow occur
in mtk_dsi_poweron() on 32-bit architectures? 

drivers/gpu/drm/mediatek/mtk_dsi.c:mtk_dsi_poweron() {
    ...
    dsi->data_rate = DIV_ROUND_UP_ULL(dsi->vm.pixelclock * bit_per_pixel,
                                      dsi->lanes);
    ...
}

On 32-bit systems, pixelclock is a 32-bit unsigned long. For high-resolution
panels, the multiplication could overflow the 32-bit limit before the macro
casts the result to unsigned long long, resulting in a corrupted frequency
being sent to clk_set_rate().

Additionally, does mtk_dsi_poweron() configure the hardware DSI HS clock
using the uncompressed data rate rather than the DSC compressed data rate?
If so, could this overclock the hardware and cause it to fail when DSC is
active?

> +	max_link_rate = data->max_link_rate_mbps;
> +	max_link_rate *= dsi->lanes;
> +	max_link_rate *= KILO;
> +
> +	if (wanted_link_rate > max_link_rate)
>  		return MODE_CLOCK_HIGH;

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713095837.16590-1-angelogioacchino.delregno@collabora.com?part=6

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 07/10] drm/mediatek: mtk_dsi: Add support for MT8196
  2026-07-13  9:58 ` [PATCH v2 07/10] drm/mediatek: mtk_dsi: Add support for MT8196 AngeloGioacchino Del Regno
@ 2026-07-13 10:13   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-13 10:13 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno; +Cc: robh, conor+dt, devicetree, dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Integer overflow and time-domain unit mismatch (bps vs Mbps) in QoS HW buffer calculations corrupts DSI thresholds.
- [High] Space domain unit mismatch (Bytes vs Buffer Units) in QoS threshold calculations bypasses physical FIFO limits.
--

--- Patch [7]: [PATCH v2 07/10] drm/mediatek: mtk_dsi: Add support for MT8196 ---
commit b12ec6677a476992917258eedb82bde7663b944c
Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Subject: drm/mediatek: mtk_dsi: Add support for MT8196

Add support for the new DSI IP found in the Kompanio Ultra MT8196 SoC.
This introduces the initialization of QoS parameters and can make use
of a DSI SRAM reserved buffer.

> diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
> index 2df2a9d5ec038..5af44c13bd5ac 100644
> --- a/drivers/gpu/drm/mediatek/mtk_dsi.c
> +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
[ ... ]
> @@ -753,6 +849,89 @@ static int mtk_dsi_set_dsc_params(struct mtk_dsi *dsi)
> +static void mtk_dsi_config_hw_buffers(struct mtk_dsi *dsi)
> +{
[ ... ]
> +	data_rate_per_buf = dsi->data_rate * dsi->lanes / 8 / buffer_unit;

[Severity: High]
Will this calculation overflow 32-bit arithmetic for typical displays?
Since dsi->data_rate is in Hz, a bandwidth over 1 Gbps combined with 4 lanes
will exceed the u32 limit before the division.

Also, is it intentional that data_rate_per_buf is calculated in units per
second instead of units per microsecond? Later in mtk_dsi_config_hw_buffers(),
it is mixed with microsecond constants and fill_rate.

> +
> +	/* Calculate valid threshold and avoid exceeding FIFO size */
> +	out_valid_thresh = MTK_DSI_DEFAULT_QOS_VALID_FIFO_US * data_rate_per_buf;
> +	out_valid_thresh = min(out_valid_thresh, MTK_DSI_MAX_FIFO_BYTES - 1);

[Severity: High]
Does this min() correctly clamp the threshold? out_valid_thresh appears to be
calculated in buffer units, but it is being clamped against a limit specified
in bytes (MTK_DSI_MAX_FIFO_BYTES - 1, which is 1553). For an MT8196 where a
buffer unit is 32 bytes, this would allow up to 1553 buffer units (49,696
bytes), bypassing the intended physical FIFO limits.

[ ... ]
> +	pclk_mhz = dsi->vm.pixelclock / HZ_PER_MHZ;
> +	fill_rate = pclk_mhz * data->pixels_per_iter * dsi_buf_bpp / buffer_unit;
> +
> +	/* Calculate QoS Anti-Latency parameters */
> +	sodi_hi = MTK_DSI_MAX_FIFO_BYTES * num_hw_buffers;

[Severity: High]
Is this mixing bytes and buffer units? MTK_DSI_MAX_FIFO_BYTES is in bytes,
but it seems to be multiplied by the number of hardware buffers here instead
of a byte-based metric.

> +	sodi_hi -= (fill_rate - data_rate_per_buf) * 12 / 10;

[Severity: High]
Could this subtraction cause an integer underflow? fill_rate is calculated
using pclk_mhz so it is a per-microsecond rate, while data_rate_per_buf is
a per-second rate. Subtracting a large per-second rate from a small
per-microsecond rate will wrap around.

> +	sodi_lo = (23 + 5) * data_rate_per_buf;
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713095837.16590-1-angelogioacchino.delregno@collabora.com?part=7

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 02/10] drm/mediatek: Implement Display Stream Compression support
  2026-07-13  9:58 ` [PATCH v2 02/10] drm/mediatek: Implement Display Stream Compression support AngeloGioacchino Del Regno
@ 2026-07-13 10:13   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-13 10:13 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno; +Cc: dri-devel, conor+dt, robh, devicetree

Thank you for your contribution! Sashiko AI review found 16 potential issue(s) to consider:

New issues:
- [High] DSC setup overwrites the DSC control register, disabling the IP after it was started.
- [High] Uninitialized `slice_chunk_size` is used to configure hardware registers.
- [High] Integer truncation on `range_data` zeroes out the second set of RC range parameters.
- [High] Off-by-one loop boundary misses PPS 19 and the final RC range parameter.
- [High] Incorrect bitmask validation erroneously rejects valid integer BPP configurations.
- [High] State leakage of `dsc_config_done` causes bypass to be skipped when switching out of DSC modes.
- [High] Loop skips the final pipeline component, causing DSI and DSC to not be found and DSC configuration to be ignored.
- [High] Integer truncation on `dsc_pre_pad_sz` zeroes out the `DSC_PIC_PREPAD_WIDTH` configuration.
- [High] The DSI pixel stream word count assumes exactly 2 DSC slices per line, violating panel variability.
- [High] DSI link timings use uncompressed byte counts instead of compressed payloads when DSC is active.
- [High] Dangling pointer to `drm_dsc_config` leads to Use-After-Free if the DSI panel is unbound.
- [High] Unscaled RC buffer thresholds are packed into PPS registers, causing bitwise overflows.
- [Medium] Missing hardware cleanup in `mtk_dsi_poweron()` error path leaves DSI enabled but unclocked.
- [Medium] `COMPRESSED_PS_DSC` stream type is conditionally set only when the format is `MIPI_DSI_FMT_RGB888`.
- [Medium] Missing zero validation for `slice_width` and `slice_height` risks division by zero panics.

Pre-existing issues:
- [High] Caller ignores `mtk_dsi_poweron()` errors, causing unprotected writes to unclocked DSI MMIO registers.
--

--- Patch [2]: [PATCH v2 02/10] drm/mediatek: Implement Display Stream Compression support ---
commit 1aa5619e9968680600a369e5067b3b6f174817c1
Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Subject: drm/mediatek: Implement Display Stream Compression support

Add a real driver for the Display Stream Compression (DSC) Display
Controller IP, implementing support for DSC v1.1 to v1.2.

> diff --git a/drivers/gpu/drm/mediatek/mtk_crtc.c b/drivers/gpu/drm/mediatek/mtk_crtc.c
> index 8e552cdc3b53b..d4be40b2574a1 100644
> --- a/drivers/gpu/drm/mediatek/mtk_crtc.c
> +++ b/drivers/gpu/drm/mediatek/mtk_crtc.c
[ ... ]
> @@ -398,6 +401,17 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc)
>  		if (!mtk_ddp_comp_add(mtk_crtc->ddp_comp[i], mtk_crtc->mutex))
>  			mtk_mutex_add_comp(mtk_crtc->mutex,
>  					   mtk_crtc->ddp_comp[i]->id);
> +
> +		/* For now, only single DSI is supported */
> +		if (mtk_crtc->ddp_comp[i]->id >= DDP_COMPONENT_DSI0 &&
> +		    mtk_crtc->ddp_comp[i]->id <= DDP_COMPONENT_DSI3)
> +			if (!comp_dsi)
> +				comp_dsi = mtk_crtc->ddp_comp[i];
> +
> +		if (mtk_crtc->ddp_comp[i]->id == DDP_COMPONENT_DSC0 ||
> +		    mtk_crtc->ddp_comp[i]->id == DDP_COMPONENT_DSC1)
> +			if (!comp_dsc)
> +				comp_dsc = mtk_crtc->ddp_comp[i];
>  	}

[Severity: High]
Does this loop skip the final pipeline component? Because it iterates only to
mtk_crtc->ddp_comp_nr - 1, the final component (which is often DSI) won't be
checked, leaving comp_dsi as NULL and silently bypassing DSC configuration.

>  	if (!mtk_ddp_comp_add(mtk_crtc->ddp_comp[i], mtk_crtc->mutex))
>  		mtk_mutex_add_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id);
> @@ -413,6 +427,13 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc)
>  		mtk_ddp_comp_start(comp);
>  	}
>  
> +	/* Setup the DSC if present, with the config coming from DSI */
> +	if (comp_dsc && comp_dsi) {
> +		dsc_cfg = mtk_dsi_get_dsc_config(comp_dsi->dev);
> +		if (dsc_cfg)
> +			mtk_ddp_comp_dsc_setup(comp_dsc, dsc_cfg);
> +	}
> +

[Severity: High]
Can calling mtk_ddp_comp_dsc_setup() after mtk_ddp_comp_start() disable the
DSC IP? Looking at mtk_dsc_setup(), it performs a direct writel() to
DISP_REG_DSC_CON which clears the DSC_EN bit that was just set during the
start phase.

>  	/* Initially configure all planes */
>  	for (i = 0; i < mtk_crtc->layer_nr; i++) {
>  		struct drm_plane *plane = &mtk_crtc->planes[i];
> diff --git a/drivers/gpu/drm/mediatek/mtk_disp_dsc.c b/drivers/gpu/drm/mediatek/mtk_disp_dsc.c
> new file mode 100644
> index 0000000000000..79b66bd7e5f3c
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_disp_dsc.c
[ ... ]
> +static void mtk_dsc_pps_setup(struct mtk_dsc *disp_dsc, struct drm_dsc_config *dsc_cfg)
> +{
[ ... ]
> +	/* PPS 8..11 - Each register holds 4 RC buffer thresholds (PPS 11 has two) */
> +	for (i = 0; i < 4; i++) {
> +		u8 block_num = i * 4;
> +		data = 0;
> +
> +		for (j = 0; j < 4; j++) {
> +			u8 buf_index = block_num + j;
> +			u8 data_shift = j * 8;
> +
> +			/* rc_buf_thresh holds 14 elements in total */
> +			if (buf_index > 13)
> +				break;
> +
> +			data |= dsc_cfg->rc_buf_thresh[buf_index] << data_shift;

[Severity: High]
Are the RC buffer thresholds missing scaling here? The rc_buf_thresh values
can be up to 14 bits wide, but they are packed into 8-bit fields without being
right-shifted by 6, which could cause bitwise overflows into adjacent fields.

> +		}
> +		writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(8 + i));
> +	}
> +
> +	/* PPS 12..19 - Each register holds two sets of RC range parameters (19 has one) */
> +	for (i = 0; i < 7; i++) {

[Severity: High]
Does this loop terminate before configuring PPS 19? The condition i < 7
means it iterates through i = 6, which covers up to PPS 18 (12 + 6). The 15th
RC range parameter in PPS 19 appears to be skipped.

> +		struct drm_dsc_rc_range_parameters *rcrp = dsc_cfg->rc_range_params;
> +		u8 block_num = i * 2;
> +		data = 0;
> +
> +		for (j = 0; j < 2; j++) {
> +			u8 buf_index = block_num + j;
> +			u8 data_shift = j * 16;
> +			u16 range_data;
> +
> +			range_data = FIELD_PREP(DSC_P12_RC_RANGE_MIN_QP,
> +						 rcrp[buf_index].range_min_qp);
> +			range_data |= FIELD_PREP(DSC_P12_RC_RANGE_MAX_QP,
> +						 rcrp[buf_index].range_max_qp);
> +			range_data |= FIELD_PREP(DSC_P12_RC_RANGE_BPG_OFFSET,
> +						 rcrp[buf_index].range_bpg_offset);
> +
> +			range_data <<= data_shift;

[Severity: High]
Could this shift cause integer truncation? The range_data variable is declared
as u16. When j=1, data_shift is 16, shifting all bits out of the u16 and
truncating it to 0, which would lose the configuration for the odd-indexed RC
range parameters.

> +			data |= range_data;
> +
> +			/* rc_range_params holds 15 elements in total */
> +			if (buf_index == 14)
> +				break;
> +		}
> +		writel(data, disp_dsc->reg + DISP_REG_DSC_PPS(12 + i));
> +	}
> +}
> +
> +void mtk_dsc_setup(struct device *dev, struct drm_dsc_config *dsc_cfg)
> +{
> +	struct mtk_dsc *disp_dsc = dev_get_drvdata(dev);
> +	u32 dsc_slice_w, dsc_slice_h, dsc_mode, dsc_cfg_rval, dsc_shadow;
> +	u32 dsc_dbg_con, dsc_con, dsc_enc_width, dsc_pic_w, dsc_pic_h;
> +	u16 pic_group_width, pic_height_ext_num, slice_group_width;
> +	u16 chunk_size, dsc_pad_num, dsc_pre_pad_sz;
[ ... ]
> +	dsc_slice_w = FIELD_PREP(DSC_SLICE_GROUP_WIDTH_M1, slice_group_width - 1);
> +	dsc_slice_w |= FIELD_PREP(DSC_SLICE_WIDTH, dsc_cfg->slice_width);
> +	dsc_slice_h = FIELD_PREP(DSC_SLICE_WIDTH_MOD3, dsc_cfg->slice_width % 3);
> +	dsc_slice_h |= FIELD_PREP(DSC_SLICE_NUM_M1,
> +				  (pic_height_ext_num / dsc_cfg->slice_height) - 1);

[Severity: Medium]
Is it possible for dsc_cfg->slice_height to be zero here, leading to a
division by zero panic? A validation check for zero slice height might be
needed.

> +	dsc_slice_h |= FIELD_PREP(DSC_SLICE_HEIGHT_M1, dsc_cfg->slice_height - 1);
> +
> +	dsc_pad_num = 3 - ((chunk_size * 2) % 3) % 3;
> +	dsc_pad_num = FIELD_PREP(DSC_PAD_NUMBER, dsc_pad_num);
> +
> +	dsc_pre_pad_sz = FIELD_PREP(DSC_PIC_PREPAD_HEIGHT, dsc_cfg->pic_height);
> +	dsc_pre_pad_sz |= FIELD_PREP(DSC_PIC_PREPAD_WIDTH, dsc_cfg->pic_width);

[Severity: High]
Will this truncate the DSC_PIC_PREPAD_WIDTH value? The variable dsc_pre_pad_sz
is declared as a u16, but DSC_PIC_PREPAD_WIDTH is defined as GENMASK(31, 16),
which means the OR operation will overflow the u16 and lose the width data.

> +
> +	dsc_mode = FIELD_PREP(DSC_INIT_DELAY_HEIGHT, 4);
[ ... ]
> +void mtk_dsc_start(struct device *dev)
> +{
> +	struct mtk_dsc *disp_dsc = dev_get_drvdata(dev);
> +
> +	/* If no DSC or config not done, set bypass mode */
> +	if (!disp_dsc->dsc_config_done) {

[Severity: High]
Does this handle transitioning from DSC to non-DSC modes correctly? Because
disp_dsc->dsc_config_done is never cleared after being set in mtk_dsc_setup(),
it looks like setting bypass mode will be skipped on subsequent starts in
non-DSC configurations.

> +		mtk_ddp_write_mask(NULL, DSC_BYPASS, &disp_dsc->cmdq_reg,
> +				   disp_dsc->reg, DISP_REG_DSC_CON, DSC_BYPASS);
> +		mtk_ddp_write_mask(NULL, DSC_UFOE_SEL, &disp_dsc->cmdq_reg,
> +				   disp_dsc->reg, DISP_REG_DSC_CON, DSC_UFOE_SEL);
> +		mtk_ddp_write_mask(NULL, DSC_DUAL_INOUT, &disp_dsc->cmdq_reg,
> +				   disp_dsc->reg, DISP_REG_DSC_CON, DSC_DUAL_INOUT);
> +	}
> +
> +	mtk_ddp_write_mask(NULL, DSC_EN, &disp_dsc->cmdq_reg,
> +			   disp_dsc->reg, DISP_REG_DSC_CON, DSC_EN);
> +}
> +
> +void mtk_dsc_stop(struct device *dev)
> +{
> +	struct mtk_dsc *disp_dsc = dev_get_drvdata(dev);
> +
> +	writel(0, disp_dsc->reg + DISP_REG_DSC_CON);
> +}
> diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
> index 3f3f56eed3f92..3e22553ca990f 100644
> --- a/drivers/gpu/drm/mediatek/mtk_dsi.c
> +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
[ ... ]
> @@ -395,7 +399,7 @@ static void mtk_dsi_rxtx_control(struct mtk_dsi *dsi)
>  
>  static void mtk_dsi_ps_control(struct mtk_dsi *dsi, bool config_vact)
>  {
> -	u32 dsi_buf_bpp, ps_val, ps_wc, vact_nl;
> +	u32 dsi_buf_bpp, ps_val, ps_wc, size_val, vact_nl;
>  
>  	if (dsi->format == MIPI_DSI_FMT_RGB565)
>  		dsi_buf_bpp = 2;
>  	else
>  		dsi_buf_bpp = 3;
>  
>  	/* Word count */
> -	ps_wc = FIELD_PREP(DSI_PS_WC, dsi->vm.hactive * dsi_buf_bpp);
> +	if (dsi->dsc)
> +		ps_wc = FIELD_PREP(DSI_PS_WC, 2 * dsi->dsc->slice_chunk_size);

[Severity: High]
Is it safe to assume exactly 2 DSC slices per line here? If a panel requires
1, 4, or another slice count, hardcoding the multiplier to 2 could result in
improperly sized MIPI DSI packets.

> +	else
> +		ps_wc = FIELD_PREP(DSI_PS_WC, dsi->vm.hactive * dsi_buf_bpp);
>  	ps_val = ps_wc;
>  
>  	/* Pixel Stream type */
> @@ -411,7 +418,10 @@ static void mtk_dsi_ps_control(struct mtk_dsi *dsi, bool config_vact)
>  	default:
>  		fallthrough;
>  	case MIPI_DSI_FMT_RGB888:
> -		ps_val |= FIELD_PREP(DSI_PS_SEL, PACKED_PS_24BIT_RGB888);
> +		if (dsi->dsc)
> +			ps_val |= FIELD_PREP(DSI_PS_SEL, COMPRESSED_PS_DSC);
> +		else
> +			ps_val |= FIELD_PREP(DSI_PS_SEL, PACKED_PS_24BIT_RGB888);
>  		break;

[Severity: Medium]
Will this prevent compressed streams for formats other than RGB888? Since the
COMPRESSED_PS_DSC assignment is fully nested inside the MIPI_DSI_FMT_RGB888
case, using DSC with RGB666 would mistakenly signal PACKED_PS_18BIT_RGB666
over the bus.

>  	case MIPI_DSI_FMT_RGB666:
>  		ps_val |= FIELD_PREP(DSI_PS_SEL, LOOSELY_PS_24BIT_RGB666);
> @@ -565,7 +587,43 @@ static void mtk_dsi_config_vdo_timing_per_line_lp(struct mtk_dsi *dsi)
>  	writel(horizontal_frontporch_byte, dsi->regs + DSI_HFP_WC);
>  }
>  
> -static void mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
> +static int mtk_dsi_set_dsc_params(struct mtk_dsi *dsi)
> +{
> +	struct drm_dsc_config *dsc = dsi->dsc;
> +	struct device *dev = dsi->host.dev;
> +	int ret;
> +
> +	if (dsc->bits_per_pixel & GENMASK(7, 0)) {
> +		dev_err(dev, "Fractional bits_per_pixel not supported\n");
> +		return -EINVAL;
> +	}

[Severity: High]
Could this bitmask erroneously reject valid integer bits-per-pixel values? The
bits_per_pixel value is shifted by 4 (to allow 4 fractional bits).
GENMASK(7, 0) covers the lower 8 bits, which includes 4 of the integer bits.
This would fail for a valid 8 BPP configuration.

> +
> +	if (dsc->bits_per_component != 8) {
> +		dev_err(dev, "%u bits per component is not supported\n",
> +			dsc->bits_per_component);
[ ... ]
> +static int mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
>  {
>  	struct videomode *vm = &dsi->vm;
>  
> @@ -574,17 +632,17 @@ static void mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
>  	writel(vm->vfront_porch, dsi->regs + DSI_VFP_NL);
>  	writel(vm->vactive, dsi->regs + DSI_VACT_NL);
>  
> -	if (dsi->driver_data->has_size_ctl)
> -		writel(FIELD_PREP(DSI_HEIGHT, vm->vactive) |
> -			FIELD_PREP(DSI_WIDTH, vm->hactive),
> -			dsi->regs + DSI_SIZE_CON);
> -
>  	if (dsi->driver_data->support_per_frame_lp)
>  		mtk_dsi_config_vdo_timing_per_frame_lp(dsi);
>  	else
>  		mtk_dsi_config_vdo_timing_per_line_lp(dsi);
>  
>  	mtk_dsi_ps_control(dsi, false);
> +
> +	if (dsi->dsc)
> +		return mtk_dsi_set_dsc_params(dsi);
> +
> +	return 0;
>  }

[Severity: High]
Is there an ordering issue here with uninitialized parameters? 
mtk_dsi_ps_control() reads dsi->dsc->slice_chunk_size, but it hasn't been
computed yet because mtk_dsi_set_dsc_params() is called afterwards.

Additionally, do the DSI timing calculations use uncompressed byte counts when
DSC is active? The functions mtk_dsi_config_vdo_timing_per_frame_lp() and
per_line_lp() calculate ps_wc using vm->hactive * dsi_tmp_buf_bpp, which
ignores the reduced compressed payload size, potentially miscomputing the
front/back porch and roundup metrics.

> @@ -741,12 +799,17 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi)
>  
>  	mtk_dsi_ps_control(dsi, true);
>  	mtk_dsi_set_vm_cmd(dsi);
> -	mtk_dsi_config_vdo_timing(dsi);
> +	ret = mtk_dsi_config_vdo_timing(dsi);
> +	if (ret)
> +		goto err_disable_digital_clk;
> +
>  	mtk_dsi_set_interrupt_enable(dsi);
>  	mtk_dsi_lane_ready(dsi);
>  	mtk_dsi_clk_hs_mode(dsi, 1);
>  
>  	return 0;
> +err_disable_digital_clk:
> +	clk_disable_unprepare(dsi->digital_clk);
>  err_disable_engine_clk:
>  	clk_disable_unprepare(dsi->engine_clk);
>  err_phy_power_off:

[Severity: Medium]
Does this error path miss cleaning up the DSI hardware state? If
mtk_dsi_config_vdo_timing() fails, jumping to err_disable_digital_clk skips
calling mtk_dsi_disable(dsi), which was enabled earlier in mtk_dsi_poweron(),
leaving it enabled but unclocked.

[Severity: High]
This is a pre-existing issue, but does mtk_dsi_ddp_start() ignore
mtk_dsi_poweron() errors? Since mtk_dsi_poweron() can now return errors (like
when DSC parameter setup fails), mtk_dsi_ddp_start() might proceed to call
mtk_dsi_start() on unclocked hardware.

> @@ -883,6 +946,21 @@ mtk_dsi_bridge_mode_valid(struct drm_bridge *bridge,
>  	if (mode->clock * bpp / dsi->lanes > 1500000)
>  		return MODE_CLOCK_HIGH;
>  
> +	if (dsi->dsc) {
> +		if (mode->hdisplay % dsi->dsc->slice_width) {

[Severity: Medium]
Is there a risk of a division by zero panic here? If an external panel driver
supplies a zero slice_width, this modulo operation will crash.

> +			dev_err(dsi->host.dev,
> +				"DSC: hdisplay %u is not a multiple of slice width %u\n",
> +				dsi->dsc->slice_width, mode->hdisplay);
> +			return MODE_H_ILLEGAL;
> +		}
> +		if (mode->vdisplay % dsi->dsc->slice_height) {
> +			dev_err(dsi->host.dev,
> +				"DSC: vdisplay %u is not a multiple of slice height %u\n",
> +				dsi->dsc->slice_height, mode->vdisplay);
> +			return MODE_V_ILLEGAL;
> +		}
> +	}
> +
>  	return MODE_OK;
>  }
[ ... ]
> @@ -1006,6 +1091,9 @@ static int mtk_dsi_host_attach(struct mipi_dsi_host *host,
>  			return PTR_ERR(dsi->next_bridge);
>  	}
>  
> +	if (device->dsc)
> +		dsi->dsc = device->dsc;
> +

[Severity: High]
Does this create a dangling pointer if the DSI panel is unbound? A pointer to
device->dsc is captured here, but there appears to be no corresponding code in
mtk_dsi_host_detach() to set it back to NULL, which could lead to a
use-after-free.

>  	drm_bridge_add(&dsi->bridge);
>  
>  	ret = component_add(host->dev, &mtk_dsi_component_ops);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713095837.16590-1-angelogioacchino.delregno@collabora.com?part=2

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 09/10] dt-bindings: display: mediatek: wdma: Add compatibles for more SoCs
  2026-07-13  9:58 ` [PATCH v2 09/10] dt-bindings: display: mediatek: wdma: Add compatibles for more SoCs AngeloGioacchino Del Regno
@ 2026-07-13 10:23   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-13 10:23 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno; +Cc: dri-devel, robh, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The mediatek,mt6893-disp-wdma compatible string is incorrectly placed in the standalone enum block, preventing it from using the mediatek,mt8173-disp-wdma fallback.
--

--- Patch [9]: [PATCH v2 09/10] dt-bindings: display: mediatek: wdma: Add compatibles for more SoCs ---
commit f57f0fa27efb2b4a343645baf41ff18e654a1717
Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>

dt-bindings: display: mediatek: wdma: Add compatibles for more SoCs

This commit adds missing device tree compatible strings for the Write DMA
hardware on several MediaTek SoCs. It specifically targets the Dimensity 1200
and the Kompanio 500/520/820/1200 series.

> diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml
> index 97d052b0fb614..c3ed867d058d9 100644
> --- a/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml
> +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml
> @@ -22,11 +22,16 @@ properties:
>    compatible:
>      oneOf:
>        - enum:
> +          - mediatek,mt6893-disp-wdma

[Severity: Medium]
Is mediatek,mt6893-disp-wdma incorrectly placed in this standalone enum block?

Placing it here prevents it from using the mediatek,mt8173-disp-wdma
fallback string. Because the MediaTek DRM driver only matches
mediatek,mt8173-disp-wdma, Device Trees for the MT6893 will either break
driver probing if they follow this schema strictly, or fail validation
if they include the fallback string to allow the driver to probe.

Should this compatible string be placed in the items block below with the
other SoCs instead?

>            - mediatek,mt8173-disp-wdma
>        - items:
>            - enum:
>                - mediatek,mt6795-disp-wdma
>                - mediatek,mt8167-disp-wdma
> +              - mediatek,mt8183-disp-wdma
> +              - mediatek,mt8186-disp-wdma
> +              - mediatek,mt8192-disp-wdma
> +              - mediatek,mt8195-disp-wdma
>            - const: mediatek,mt8173-disp-wdma

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713095837.16590-1-angelogioacchino.delregno@collabora.com?part=9

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 08/10] drm/mediatek: mtk_dsi: Enable PM Runtime on probe
  2026-07-13  9:58 ` [PATCH v2 08/10] drm/mediatek: mtk_dsi: Enable PM Runtime on probe AngeloGioacchino Del Regno
@ 2026-07-13 10:23   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-13 10:23 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno; +Cc: devicetree, conor+dt, dri-devel, robh

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Missing cleanup of DSI host registration on devm_pm_runtime_enable() failure.
- [High] The DSI device is never runtime-resumed by mtk_ddp_comp_power_on(), leaving its power domain off and causing hardware access faults.
- [High] devm_pm_runtime_enable() is called too late in the probe sequence, creating a race condition with DRM device binding.
--

--- Patch [8]: [PATCH v2 08/10] drm/mediatek: mtk_dsi: Enable PM Runtime on probe ---
commit a3706ccca44411c23c1950cb6ce7edfa10269c16
Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>

drm/mediatek: mtk_dsi: Enable PM Runtime on probe
    
Enable PM Runtime at the end of mtk_dsi_probe() so that the
mediatek-drm mtk_ddp_comp_power_on() function is able to do
pm_runtime_resume_and_get() and, analogously, the function
mtk_ddp_comp_power_off() to pm_runtime_put() for the DSI
device.
    
This makes sure that the power domains for the DSI IP are
turned on and off when necessary.
    
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>

> Enable PM Runtime at the end of mtk_dsi_probe() so that the
> mediatek-drm mtk_ddp_comp_power_on() function is able to do
> pm_runtime_resume_and_get() and, analogously, the function
> mtk_ddp_comp_power_off() to pm_runtime_put() for the DSI
> device.

[Severity: High]
Does mtk_ddp_comp_power_on() actually get called for the DSI device?

Looking at mtk_crtc_atomic_enable(), it only powers on the first component in
the pipeline:

drivers/gpu/drm/mediatek/mtk_crtc.c:mtk_crtc_atomic_enable() {
    ...
    struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[0];
    ...
    ret = mtk_ddp_comp_power_on(comp);
    ...
}

Since the DSI device is an encoder/bridge at the end of the pipeline (e.g.,
ddp_comp[6]), it won't be resumed here, leaving it in RPM_SUSPENDED state.
Will this cause hardware access faults when DSI registers are subsequently
accessed?

> diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
> index 5af44c13bd5ac..5fbaa7d580d35 100644
> --- a/drivers/gpu/drm/mediatek/mtk_dsi.c
> +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
> @@ -1684,6 +1684,10 @@ static int mtk_dsi_probe(struct platform_device *pdev)
>  	dsi->bridge.of_node = dev->of_node;
>  	dsi->bridge.type = DRM_MODE_CONNECTOR_DSI;
>  
> +	ret = devm_pm_runtime_enable(dev);
> +	if (ret)
> +		return ret;

[Severity: High]
If this returns early, will it leak the DSI host registration?

Earlier in mtk_dsi_probe(), mipi_dsi_host_register(&dsi->host) adds the host
to a global list. Since there is no mipi_dsi_host_unregister() call on this
error path, and the devres-managed mtk_dsi structure will be freed, does this
leave a dangling pointer in the MIPI DSI host list that could lead to a
use-after-free?

[Severity: High]
Is it safe to enable PM runtime after registering the DSI host?

By calling mipi_dsi_host_register() earlier in the function, the DSI host
becomes visible to dependent panel drivers that may probe synchronously:

mtk_dsi_probe()
  mipi_dsi_host_register()
    -> panel probe triggers
      mipi_dsi_attach()
        mtk_dsi_host_attach()
          component_add()
            -> DRM master bind triggers if all components are ready

Since devm_pm_runtime_enable() hasn't executed yet, PM runtime isn't ready
when the display pipeline starts initializing. Would it be better to move
devm_pm_runtime_enable() before mipi_dsi_host_register() to avoid this race?

> +
>  	return 0;
>  }
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713095837.16590-1-angelogioacchino.delregno@collabora.com?part=8

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 10/10] drm/mediatek: Add Write DMA (WDMA) Engine for Writeback support
  2026-07-13  9:58 ` [PATCH v2 10/10] drm/mediatek: Add Write DMA (WDMA) Engine for Writeback support AngeloGioacchino Del Regno
@ 2026-07-13 10:26   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-13 10:26 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno; +Cc: dri-devel, devicetree, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [Critical] Architectural Flaw: Writeback configuration is improperly placed in the input plane callback `layer_config`, causing dead code, list corruption, and potential memory corruption.
- [High] Hardcoded register offset in `mtk_wdma_ddp_write_dst_addr` overwrites the same register for all planes.
- [High] Accessing WDMA registers in `mtk_disp_wdma_bind` without enabling clocks or power domains causes a bus hang.
- [High] Writeback completion signaling is erroneously tied to CRTC vblank enablement, and the interrupt handler incorrectly returns `IRQ_NONE`.
- [High] Pending writeback jobs are permanently leaked if the CRTC is disabled or the component is unbound.
- [High] Race condition in writeback job queuing and signaling due to unprotected `wb_pending` flag.
- [High] Direct MMIO write of the destination address during `atomic_commit` corrupts ongoing DMA writeback operations.
- [Medium] Multi-planar YUV formats incorrectly assume contiguous memory allocations for U and V planes.
- [Medium] Lockless access to `wdma->vblank_cb` in the IRQ handler can lead to a NULL pointer dereference (TOCTOU).
--

--- Patch [10]: [PATCH v2 10/10] drm/mediatek: Add Write DMA (WDMA) Engine for Writeback support ---
commit bcba28b1b84d2c188344cc47c55787ee3fddf6b4
Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Subject: drm/mediatek: Add Write DMA (WDMA) Engine for Writeback support

This commit adds a driver for the MediaTek Write DMA (WDMA) engine, 
hooking it up to the display component framework. This provides initial 
writeback support for capturing display images on MT8173 and MT6893 
devices.

> diff --git a/drivers/gpu/drm/mediatek/mtk_disp_wdma.c b/drivers/gpu/drm/mediatek/mtk_disp_wdma.c
> new file mode 100644
> index 0000000000000..a093d6d57f1a0
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_disp_wdma.c
> @@ -0,0 +1,611 @@
[ ... ]
> +static irqreturn_t mtk_disp_wdma_irq_handler(int irq, void *dev_id)
> +{
> +	struct mtk_disp_wdma *wdma = dev_id;
> +
> +	/* Clear frame completion interrupt */
> +	writel(0x0, wdma->regs + DISP_REG_WDMA_INT_STATUS);
> +
> +	if (!wdma->vblank_cb)
> +		return IRQ_NONE;

[Severity: High]
Does this tie writeback completion signaling to vblank enablement? If
userspace queues a writeback job without requesting vblank events, the
vblank callback might be NULL. Returning IRQ_NONE here skips the subsequent
drm_writeback_signal_completion() call, which would strand the writeback
job indefinitely.

> +
> +	wdma->vblank_cb(wdma->vblank_cb_data);

[Severity: Medium]
Is there a TOCTOU race with the lockless access to the vblank callback?
Since mtk_wdma_unregister_vblank_cb() can clear wdma->vblank_cb without
synchronization, the compiler might fetch the pointer twice. If it gets
nullified between the check and the execution, could this cause a NULL
pointer dereference?

[ ... ]
> +void mtk_wdma_disable_vblank(struct device *dev)
> +{
> +	wdma_update_bits(dev, DISP_REG_WDMA_INT_ENABLE, WDMA_FRAME_COMPLETE_INT, 0);
> +}

[Severity: High]
Does disabling vblank also incorrectly disable the writeback completion
interrupt? If a writeback job is queued while vblank is disabled, the
hardware interrupt won't fire, leaving the writeback job permanently
hanging.

[ ... ]
> +void mtk_wdma_stop(struct device *dev)
> +{
> +	wdma_update_bits(dev, DISP_REG_WDMA_EN, WDMA_ENGINE_EN, 0);
> +}

[Severity: High]
When the hardware is stopped here, are pending writeback jobs properly
drained? If the display pipeline is stopped before a hardware frame
completes, the interrupt will never fire, which would permanently leak the
drm_writeback_job and its framebuffer reference.

[ ... ]
> +static void mtk_wdma_ddp_write_dst_addr(struct cmdq_pkt *cmdq_pkt, u64 val,
> +					u8 reg_id, struct mtk_disp_wdma *wdma)
> +{
> +	mtk_ddp_write(cmdq_pkt, lower_32_bits(val), &wdma->cmdq_reg, wdma->regs,
> +		      DISP_REG_WDMA_DST_ADDRX(DISP_REG_WDMA_DST_ADDR_LSB, 1));

[Severity: High]
Does the hardcoded 1 here ignore the reg_id parameter? By passing 1
instead of reg_id to DISP_REG_WDMA_DST_ADDRX, the base address register
for the primary plane might be left uninitialized, and Y/U/V plane
addresses might overwrite each other.

[ ... ]
> +	/* Set U and V components destination addresses */
> +	mtk_wdma_ddp_write_dst_addr(cmdq_pkt, pending->addr + u_off, 1, wdma);
> +	mtk_wdma_ddp_write_dst_addr(cmdq_pkt, pending->addr + v_off, 2, wdma);

[Severity: Medium]
Does this calculation assume multi-planar YUV formats are always
contiguous in memory? The DRM subsystem allows multi-planar framebuffers to
have independent memory offsets or separate GEM objects per plane. Adding
an offset to the base address instead of using the framebuffer offsets
might lead to out-of-bounds writes if a disjoint framebuffer is provided.

[ ... ]
> +void mtk_wdma_layer_config(struct device *dev, unsigned int idx,
> +			   struct mtk_plane_state *state,
> +			   struct cmdq_pkt *cmdq_pkt)
> +{
[ ... ]
> +	mtk_wdma_format_config(wdma, pending, fmt_info, cmdq_pkt);
> +
> +	drm_writeback_queue_job(&wdma->wb_connector, wdma->wb_connector.base.state);
> +}

[Severity: Critical]
Is mtk_wdma_layer_config the correct place to configure writeback? This
callback is designed for input planes, but mtk_crtc_num_comp_planes
assigns 0 planes to components without bgclr_in_on at index >= 1. Since
WDMA is a writeback output, it receives 0 planes, making this function
dead code.

If this function were somehow called, would using pending->addr as the
write destination corrupt user memory, since pending->addr is an input read
address? Furthermore, since the job is already queued during the atomic
commit phase, calling drm_writeback_queue_job() again here with a NULL
conn_state->writeback_job would cause a list corruption NULL pointer
dereference.

[ ... ]
> +static void mtk_wdma_wb_connector_atomic_commit(struct drm_connector *connector,
> +						struct drm_atomic_commit *state)
> +{
[ ... ]
> +	/* Store writeback pending state before queuing the job */
> +	wdma->wb_pending = true;

[Severity: High]
Can setting wb_pending to true before placing the job into the queue
create a race condition with the interrupt handler? If a frame completion
interrupt fires in this narrow window, the handler might see wb_pending as
true, attempt to signal an empty queue, and clear the flag. The subsequent
job completion would then be ignored.

> +
> +	mtk_wdma_ddp_write_dst_addr(NULL, addr, 0, wdma);

[Severity: High]
Does writing the new destination address directly to hardware via MMIO
here corrupt ongoing DMA operations? Because mtk_wdma_config sets
WDMA_BYPASS_SHADOW, this unsynchronized address change takes effect
immediately. If this occurs mid-frame while the hardware is processing the
previous frame, could it scatter pixel data into the new buffer?

[ ... ]
> +static int mtk_disp_wdma_bind(struct device *dev, struct device *master,
> +			      void *data)
> +{
[ ... ]
> +	/* Disable and clear pending interrupts */
> +	writel(0x0, wdma->regs + DISP_REG_WDMA_INT_ENABLE);
> +	writel(0x0, wdma->regs + DISP_REG_WDMA_INT_STATUS);

[Severity: High]
Are the device clocks and power domains enabled before accessing these MMIO
registers? During the component bind phase, pm_runtime_get_sync() has not
been called and the component clocks are disabled. Writing to
DISP_REG_WDMA_INT_ENABLE while the peripheral is gated could instantly hang
the bus.

[ ... ]
> +static void mtk_disp_wdma_unbind(struct device *dev, struct device *master,
> +				 void *data)
> +{
> +	struct mtk_disp_wdma *wdma = dev_get_drvdata(dev);
> +
> +	disable_irq(wdma->irq);
> +}

[Severity: High]
Similar to mtk_wdma_stop(), are pending writeback jobs properly drained
during unbind? Disabling the IRQ while jobs may still be pending in the
queue prevents them from ever completing, causing tasks waiting on the
out-fence to hang indefinitely.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713095837.16590-1-angelogioacchino.delregno@collabora.com?part=10

^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2026-07-13 10:27 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-13  9:58 [PATCH v2 00/10] drm/mediatek: Add DSC, WDMA, MT8189/96 DSI support AngeloGioacchino Del Regno
2026-07-13  9:58 ` [PATCH v2 01/10] dt-bindings: display: mediatek: dsc: Add MT8196 compatible AngeloGioacchino Del Regno
2026-07-13  9:58 ` [PATCH v2 02/10] drm/mediatek: Implement Display Stream Compression support AngeloGioacchino Del Regno
2026-07-13 10:13   ` sashiko-bot
2026-07-13  9:58 ` [PATCH v2 03/10] dt-bindings: display: mediatek: dsi: Document MT8189 and MT8196 AngeloGioacchino Del Regno
2026-07-13  9:58 ` [PATCH v2 04/10] drm/mediatek: mtk_dsi: Enable interrupt at component bind time AngeloGioacchino Del Regno
2026-07-13 10:11   ` sashiko-bot
2026-07-13  9:58 ` [PATCH v2 05/10] drm/mediatek: mtk_dsi: Transfer register offsets to per-SoC const AngeloGioacchino Del Regno
2026-07-13 10:07   ` sashiko-bot
2026-07-13  9:58 ` [PATCH v2 06/10] drm/mediatek: mtk_dsi: Add support for MT8189 AngeloGioacchino Del Regno
2026-07-13 10:13   ` sashiko-bot
2026-07-13  9:58 ` [PATCH v2 07/10] drm/mediatek: mtk_dsi: Add support for MT8196 AngeloGioacchino Del Regno
2026-07-13 10:13   ` sashiko-bot
2026-07-13  9:58 ` [PATCH v2 08/10] drm/mediatek: mtk_dsi: Enable PM Runtime on probe AngeloGioacchino Del Regno
2026-07-13 10:23   ` sashiko-bot
2026-07-13  9:58 ` [PATCH v2 09/10] dt-bindings: display: mediatek: wdma: Add compatibles for more SoCs AngeloGioacchino Del Regno
2026-07-13 10:23   ` sashiko-bot
2026-07-13  9:58 ` [PATCH v2 10/10] drm/mediatek: Add Write DMA (WDMA) Engine for Writeback support AngeloGioacchino Del Regno
2026-07-13 10:26   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox