public inbox for devicetree@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/3] drm: nuvoton: Add MA35D1 display controller support
@ 2026-01-29  4:05 Joey Lu
  2026-01-29  4:05 ` [PATCH v2 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding Joey Lu
                   ` (2 more replies)
  0 siblings, 3 replies; 17+ messages in thread
From: Joey Lu @ 2026-01-29  4:05 UTC (permalink / raw)
  To: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt
  Cc: ychuang3, schung, yclu4, a0987203069, linux-arm-kernel, dri-devel,
	devicetree, linux-kernel

This is v2 of the Nuvoton MA35D1 DCU DRM driver series.

v2:
  Driver changes:
  - Drop custom CRTC and plane properties and switch to core-supported ones
  - Switch to memory-safe DRM-managed allocation helpers
  - Fix several minor logic
  - Fix W=1 build failure on non-ARM architectures

  Device tree / bindings:
  - Fix dt_binding_check errors in the YAML schema
  - Update DTS/DTSI files to follow proper hierarchy and port modeling

Thanks for the review.

Best regards,
Joey Lu

Joey Lu (3):
  dt-bindings: display: nuvoton: add MA35D1 DCU binding
  arm64: dts: nuvoton: ma35d1: add display controller support
  drm/nuvoton: add MA35D1 display controller driver

 .../bindings/display/nuvoton,ma35d1-dcu.yaml  |  73 +++
 .../boot/dts/nuvoton/ma35d1-som-256m.dts      |  56 ++
 arch/arm64/boot/dts/nuvoton/ma35d1.dtsi       |  14 +
 drivers/gpu/drm/Kconfig                       |   1 +
 drivers/gpu/drm/Makefile                      |   1 +
 drivers/gpu/drm/nuvoton/Kconfig               |  21 +
 drivers/gpu/drm/nuvoton/Makefile              |   7 +
 drivers/gpu/drm/nuvoton/ma35_crtc.c           | 372 +++++++++++
 drivers/gpu/drm/nuvoton/ma35_crtc.h           |  67 ++
 drivers/gpu/drm/nuvoton/ma35_drm.c            | 371 +++++++++++
 drivers/gpu/drm/nuvoton/ma35_drm.h            |  48 ++
 drivers/gpu/drm/nuvoton/ma35_interface.c      | 193 ++++++
 drivers/gpu/drm/nuvoton/ma35_interface.h      |  30 +
 drivers/gpu/drm/nuvoton/ma35_plane.c          | 603 ++++++++++++++++++
 drivers/gpu/drm/nuvoton/ma35_plane.h          | 115 ++++
 drivers/gpu/drm/nuvoton/ma35_regs.h           |  88 +++
 16 files changed, 2060 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/nuvoton,ma35d1-dcu.yaml
 create mode 100644 drivers/gpu/drm/nuvoton/Kconfig
 create mode 100644 drivers/gpu/drm/nuvoton/Makefile
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_crtc.c
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_crtc.h
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_drm.c
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_drm.h
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_interface.c
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_interface.h
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_plane.c
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_plane.h
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_regs.h

-- 
2.43.0


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

* [PATCH v2 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding
  2026-01-29  4:05 [PATCH v2 0/3] drm: nuvoton: Add MA35D1 display controller support Joey Lu
@ 2026-01-29  4:05 ` Joey Lu
  2026-02-05 13:18   ` Krzysztof Kozlowski
  2026-01-29  4:05 ` [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support Joey Lu
  2026-01-29  4:05 ` [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver Joey Lu
  2 siblings, 1 reply; 17+ messages in thread
From: Joey Lu @ 2026-01-29  4:05 UTC (permalink / raw)
  To: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt
  Cc: ychuang3, schung, yclu4, a0987203069, linux-arm-kernel, dri-devel,
	devicetree, linux-kernel

Add Device Tree binding documentation for the Display Control
Unit (DCU) found in Nuvoton MA35D1 SoCs.

The DCU is a DPI-based display controller supporting RGB output
with optional external bridges or panels.

Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
 .../bindings/display/nuvoton,ma35d1-dcu.yaml  | 73 +++++++++++++++++++
 1 file changed, 73 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/nuvoton,ma35d1-dcu.yaml

diff --git a/Documentation/devicetree/bindings/display/nuvoton,ma35d1-dcu.yaml b/Documentation/devicetree/bindings/display/nuvoton,ma35d1-dcu.yaml
new file mode 100644
index 000000000000..adfc20117eb7
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/nuvoton,ma35d1-dcu.yaml
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/nuvoton,ma35d1-dcu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton MA35D1 Display Controller Unit (DCU)
+
+maintainers:
+  - Joey Lu <a0987203069@gmail.com>
+
+description:
+  The Nuvoton MA35D1 Display Controller Unit (DCU) supports multiple
+  layers of composition, blending, and output to parallel RGB (DPI)
+  interfaces.
+
+properties:
+  compatible:
+    const: nuvoton,ma35d1-dcu
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: DCU Gate clock for register access
+      - description: DCU Pixel clock for display timing
+
+  clock-names:
+    items:
+      - const: dcu_gate
+      - const: dcup_div
+
+  resets:
+    maxItems: 1
+
+  port:
+    $ref: /schemas/graph.yaml#/properties/port
+    description: Video output port
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - clock-names
+  - resets
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+    #include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
+
+    display@40260000 {
+        compatible = "nuvoton,ma35d1-dcu";
+        reg = <0x40260000 0x2000>;
+        interrupts = <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&clk DCU_GATE>, <&clk DCUP_DIV>;
+        clock-names = "dcu_gate", "dcup_div";
+        resets = <&sys MA35D1_RESET_DISP>;
+
+        port {
+            dpi_out: endpoint {
+                remote-endpoint = <&panel_in>;
+            };
+        };
+    };
-- 
2.43.0


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

* [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support
  2026-01-29  4:05 [PATCH v2 0/3] drm: nuvoton: Add MA35D1 display controller support Joey Lu
  2026-01-29  4:05 ` [PATCH v2 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding Joey Lu
@ 2026-01-29  4:05 ` Joey Lu
  2026-02-05 13:23   ` Krzysztof Kozlowski
  2026-01-29  4:05 ` [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver Joey Lu
  2 siblings, 1 reply; 17+ messages in thread
From: Joey Lu @ 2026-01-29  4:05 UTC (permalink / raw)
  To: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt
  Cc: ychuang3, schung, yclu4, a0987203069, linux-arm-kernel, dri-devel,
	devicetree, linux-kernel

Enable the Display Control Unit (DCU) for the Nuvoton MA35D1 SoC.

This patch adds the DCU node to the SoC dtsi and enables it on
the MA35D1 SOM board.

Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
 .../boot/dts/nuvoton/ma35d1-som-256m.dts      | 56 +++++++++++++++++++
 arch/arm64/boot/dts/nuvoton/ma35d1.dtsi       | 14 +++++
 2 files changed, 70 insertions(+)

diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
index f6f20a17e501..e596bb452cc9 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
@@ -98,6 +98,42 @@ pinctrl_uart16: uart16-pins {
 			power-source = <1>;
 		};
 	};
+
+	dcu {
+		pinctrl_display: display-pins {
+			nuvoton,pins =
+					   <6 8 6>,  /* VSYNC */
+					   <6 9 6>,  /* HSYNC */
+					   <6 10 6>, /* CLK */
+					   <10 4 6>, /* DE */
+					   <8 8 6>,  /* D0 - D23 */
+					   <8 9 6>,
+					   <8 10 6>,
+					   <8 11 6>,
+					   <8 12 6>,
+					   <8 13 6>,
+					   <8 14 6>,
+					   <8 15 6>,
+					   <7 0 6>,
+					   <7 1 6>,
+					   <7 2 6>,
+					   <7 3 6>,
+					   <7 4 6>,
+					   <7 5 6>,
+					   <7 6 6>,
+					   <7 7 6>,
+					   <2 12 6>,
+					   <2 13 6>,
+					   <2 14 6>,
+					   <2 15 6>,
+					   <7 12 6>,
+					   <7 13 6>,
+					   <7 14 6>,
+					   <7 15 6>;
+			bias-disable;
+			power-source = <1>;
+		};
+	};
 };
 
 &uart0 {
@@ -129,3 +165,23 @@ &uart16 {
 	pinctrl-0 = <&pinctrl_uart16>;
 	status = "okay";
 };
+
+&panel {
+	port {
+		panel_in: endpoint@0 {
+			remote-endpoint = <&dpi_out>;
+		};
+	};
+};
+
+&display {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_display>;
+	status = "okay";
+
+	port {
+		dpi_out: endpoint@0 {
+			remote-endpoint = <&panel_in>;
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
index e51b98f5bdce..7d9d077f12b2 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
@@ -379,5 +379,19 @@ uart16: serial@40880000 {
 			clocks = <&clk UART16_GATE>;
 			status = "disabled";
 		};
+
+		panel: panel {
+			compatible = "panel-dpi";
+		};
+
+		display: display@40260000 {
+			compatible = "nuvoton,ma35d1-dcu";
+			reg = <0x0 0x40260000 0x0 0x2000>;
+			interrupts = <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk DCU_GATE>, <&clk DCUP_DIV>;
+			clock-names = "dcu_gate", "dcup_div";
+			resets = <&sys MA35D1_RESET_DISP>;
+			status = "disabled";
+		};
 	};
 };
-- 
2.43.0


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

* [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver
  2026-01-29  4:05 [PATCH v2 0/3] drm: nuvoton: Add MA35D1 display controller support Joey Lu
  2026-01-29  4:05 ` [PATCH v2 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding Joey Lu
  2026-01-29  4:05 ` [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support Joey Lu
@ 2026-01-29  4:05 ` Joey Lu
  2026-02-05 13:22   ` Krzysztof Kozlowski
  2026-02-06 15:09   ` Icenowy Zheng
  2 siblings, 2 replies; 17+ messages in thread
From: Joey Lu @ 2026-01-29  4:05 UTC (permalink / raw)
  To: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt
  Cc: ychuang3, schung, yclu4, a0987203069, linux-arm-kernel, dri-devel,
	devicetree, linux-kernel

Add DRM driver support for the Display Control Unit (DCU)
found in Nuvoton MA35D1 SoCs.

Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
 drivers/gpu/drm/Kconfig                  |   1 +
 drivers/gpu/drm/Makefile                 |   1 +
 drivers/gpu/drm/nuvoton/Kconfig          |  21 +
 drivers/gpu/drm/nuvoton/Makefile         |   7 +
 drivers/gpu/drm/nuvoton/ma35_crtc.c      | 372 ++++++++++++++
 drivers/gpu/drm/nuvoton/ma35_crtc.h      |  67 +++
 drivers/gpu/drm/nuvoton/ma35_drm.c       | 371 ++++++++++++++
 drivers/gpu/drm/nuvoton/ma35_drm.h       |  48 ++
 drivers/gpu/drm/nuvoton/ma35_interface.c | 193 ++++++++
 drivers/gpu/drm/nuvoton/ma35_interface.h |  30 ++
 drivers/gpu/drm/nuvoton/ma35_plane.c     | 603 +++++++++++++++++++++++
 drivers/gpu/drm/nuvoton/ma35_plane.h     | 115 +++++
 drivers/gpu/drm/nuvoton/ma35_regs.h      |  88 ++++
 13 files changed, 1917 insertions(+)
 create mode 100644 drivers/gpu/drm/nuvoton/Kconfig
 create mode 100644 drivers/gpu/drm/nuvoton/Makefile
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_crtc.c
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_crtc.h
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_drm.c
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_drm.h
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_interface.c
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_interface.h
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_plane.c
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_plane.h
 create mode 100644 drivers/gpu/drm/nuvoton/ma35_regs.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index a33b90251530..3645255bc458 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -309,6 +309,7 @@ source "drivers/gpu/drm/msm/Kconfig"
 source "drivers/gpu/drm/mxsfb/Kconfig"
 source "drivers/gpu/drm/nouveau/Kconfig"
 source "drivers/gpu/drm/nova/Kconfig"
+source "drivers/gpu/drm/nuvoton/Kconfig"
 source "drivers/gpu/drm/omapdrm/Kconfig"
 source "drivers/gpu/drm/panel/Kconfig"
 source "drivers/gpu/drm/panfrost/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0e1c668b46d2..4ded9547d7ff 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -235,6 +235,7 @@ obj-y			+= solomon/
 obj-$(CONFIG_DRM_SPRD) += sprd/
 obj-$(CONFIG_DRM_LOONGSON) += loongson/
 obj-$(CONFIG_DRM_POWERVR) += imagination/
+obj-$(CONFIG_DRM_MA35) += nuvoton/
 
 # Ensure drm headers are self-contained and pass kernel-doc
 hdrtest-files := \
diff --git a/drivers/gpu/drm/nuvoton/Kconfig b/drivers/gpu/drm/nuvoton/Kconfig
new file mode 100644
index 000000000000..6bb970b9890c
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/Kconfig
@@ -0,0 +1,21 @@
+config DRM_MA35
+	tristate "Nuvoton MA35D1 LCD Display Controller"
+	default ARCH_MA35
+	depends on DRM
+	depends on OF && (ARCH_MA35 || COMPILE_TEST)
+	select DRM_KMS_HELPER
+	select DRM_KMS_DMA_HELPER
+	select DRM_GEM_DMA_HELPER
+	select DRM_BRIDGE
+	select DRM_PANEL_BRIDGE
+	select VIDEOMODE_HELPERS
+	select REGMAP_MMIO
+	help
+	  Choose this option to enable support for the Display Controller Unit (DCU)
+	  found in Nuvoton MA35D1 SoCs.
+
+	  This driver supports the DRM/KMS API for the MA35 display subsystem,
+	  handling display output via hardware composition layers.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called ma35-drm.
\ No newline at end of file
diff --git a/drivers/gpu/drm/nuvoton/Makefile b/drivers/gpu/drm/nuvoton/Makefile
new file mode 100644
index 000000000000..aac4113106b2
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/Makefile
@@ -0,0 +1,7 @@
+ma35-drm-y += \
+	ma35_drm.o \
+	ma35_plane.o \
+	ma35_crtc.o \
+	ma35_interface.o
+
+obj-$(CONFIG_DRM_MA35)	+= ma35-drm.o
diff --git a/drivers/gpu/drm/nuvoton/ma35_crtc.c b/drivers/gpu/drm/nuvoton/ma35_crtc.c
new file mode 100644
index 000000000000..790fdba21c3a
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/ma35_crtc.c
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Nuvoton DRM driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+
+#include "ma35_drm.h"
+
+#define ma35_crtc(c) \
+	container_of(c, struct ma35_crtc, drm_crtc)
+
+static enum drm_mode_status
+ma35_crtc_mode_valid(struct drm_crtc *drm_crtc,
+			const struct drm_display_mode *mode)
+{
+	struct drm_device *drm_dev = drm_crtc->dev;
+	struct drm_mode_config *mode_config = &drm_dev->mode_config;
+
+	/* check drm_mode_status for some limitations */
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		return MODE_NO_INTERLACE;
+
+	if (mode->hdisplay > mode_config->max_width || mode->hdisplay < mode_config->min_width)
+		return MODE_BAD_HVALUE;
+
+	if (mode->vdisplay > mode_config->max_height || mode->vdisplay < mode_config->min_height)
+		return MODE_BAD_VVALUE;
+
+	if (mode->clock > MA35_MAX_PIXEL_CLK)
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static int ma35_crtc_atomic_check(struct drm_crtc *drm_crtc,
+					 struct drm_atomic_state *state)
+{
+	struct ma35_drm *priv = ma35_drm(drm_crtc->dev);
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, drm_crtc);
+	struct drm_display_mode *mode = &crtc_state->mode;
+	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+	int clk_rate;
+
+	if (mode->clock > MA35_MAX_PIXEL_CLK)
+		return MODE_CLOCK_HIGH;
+
+	/* check rounded pixel clock */
+	clk_rate = clk_round_rate(priv->dcupclk, mode->clock * 1000);
+	if (clk_rate <= 0)
+		return MODE_CLOCK_RANGE;
+
+	adjusted_mode->clock = DIV_ROUND_UP(clk_rate, 1000);
+
+	return 0;
+}
+
+static void ma35_crtc_atomic_enable(struct drm_crtc *drm_crtc,
+				       struct drm_atomic_state *state)
+{
+	struct ma35_crtc *crtc = ma35_crtc(drm_crtc);
+	struct ma35_drm *priv = ma35_drm(drm_crtc->dev);
+	struct drm_crtc_state *new_state =
+		drm_atomic_get_new_crtc_state(state, drm_crtc);
+	struct drm_display_mode *mode = &new_state->adjusted_mode;
+	struct ma35_interface *interface = priv->interface;
+	struct drm_color_lut *lut;
+	int i, size;
+	u32 reg;
+
+	/* Timings */
+	reg = FIELD_PREP(MA35_DISPLAY_TOTAL_MASK, mode->htotal) |
+		  FIELD_PREP(MA35_DISPLAY_ACTIVE_MASK, mode->hdisplay);
+	regmap_write(priv->regmap, MA35_HDISPLAY, reg);
+
+	reg = MA35_SYNC_PULSE_ENABLE |
+		  FIELD_PREP(MA35_SYNC_START_MASK, mode->hsync_start) |
+		  FIELD_PREP(MA35_SYNC_END_MASK, mode->hsync_end);
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		reg |= MA35_SYNC_POLARITY_BIT;
+	regmap_write(priv->regmap, MA35_HSYNC, reg);
+
+	reg = FIELD_PREP(MA35_DISPLAY_TOTAL_MASK, mode->vtotal) |
+		  FIELD_PREP(MA35_DISPLAY_ACTIVE_MASK, mode->vdisplay);
+	regmap_write(priv->regmap, MA35_VDISPLAY, reg);
+
+	reg = MA35_SYNC_PULSE_ENABLE |
+		  FIELD_PREP(MA35_SYNC_START_MASK, mode->vsync_start) |
+		  FIELD_PREP(MA35_SYNC_END_MASK, mode->vsync_end);
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		reg |= MA35_SYNC_POLARITY_BIT;
+	regmap_write(priv->regmap, MA35_VSYNC, reg);
+
+	/* Signals */
+	reg = MA35_PANEL_DATA_ENABLE_ENABLE | MA35_PANEL_DATA_ENABLE |
+		  MA35_PANEL_DATA_CLOCK_ENABLE;
+	if (interface->bus_flags & DRM_BUS_FLAG_DE_LOW)
+		reg |= MA35_PANEL_DATA_ENABLE_POLARITY;
+
+	if (interface->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+		reg |= MA35_PANEL_DATA_POLARITY;
+	regmap_write(priv->regmap, MA35_PANEL_CONFIG, reg);
+
+	/* Gamma */
+	if (new_state->gamma_lut) {
+		if (new_state->color_mgmt_changed) {
+			lut = new_state->gamma_lut->data;
+			size = new_state->gamma_lut->length / sizeof(struct drm_color_lut);
+
+			for (i = 0; i < size; i++) {
+				regmap_write(priv->regmap, MA35_GAMMA_INDEX, i);
+				/* shift DRM gamma 16-bit values to 10-bit */
+				reg = FIELD_PREP(MA35_GAMMA_RED_MASK, lut[i].red >> 6) |
+					  FIELD_PREP(MA35_GAMMA_GREEN_MASK, lut[i].green >> 6) |
+					  FIELD_PREP(MA35_GAMMA_BLUE_MASK, lut[i].blue >> 6);
+				regmap_write(priv->regmap, MA35_GAMMA_DATA, reg);
+			}
+		}
+		/* Enable gamma */
+		regmap_update_bits(priv->regmap, MA35_FRAMEBUFFER_CONFIG,
+						   MA35_PRIMARY_GAMMA, MA35_PRIMARY_GAMMA);
+	} else {
+		/* Disable gamma */
+		regmap_update_bits(priv->regmap, MA35_FRAMEBUFFER_CONFIG,
+						   MA35_PRIMARY_GAMMA, 0);
+	}
+
+	/* DPI format */
+	reg = FIELD_PREP(MA35_DPI_FORMAT_MASK, crtc->dpi_format);
+	regmap_write(priv->regmap, MA35_DPI_CONFIG, reg);
+
+	/* Dither */
+	if (crtc->dither_enable) {
+		for (i = 0, reg = 0; i < MA35_DITHER_TABLE_ENTRY / 2; i++)
+			reg |= (crtc->dither_depth & MA35_DITHER_TABLE_MASK) << (i * 4);
+
+		regmap_write(priv->regmap, MA35_DISPLAY_DITHER_TABLE_LOW, reg);
+		regmap_write(priv->regmap, MA35_DISPLAY_DITHER_TABLE_HIGH, reg);
+		regmap_write(priv->regmap, MA35_DISPLAY_DITHER_CONFIG, MA35_DITHER_ENABLE);
+	} else {
+		regmap_write(priv->regmap, MA35_DISPLAY_DITHER_CONFIG, 0);
+	}
+
+	drm_crtc_vblank_on(drm_crtc);
+}
+
+static void ma35_crtc_atomic_disable(struct drm_crtc *drm_crtc,
+					struct drm_atomic_state *state)
+{
+	struct ma35_drm *priv = ma35_drm(drm_crtc->dev);
+	struct drm_device *drm_dev = drm_crtc->dev;
+
+	drm_crtc_vblank_off(drm_crtc);
+
+	/* Disable and clear CRTC bits. */
+	regmap_update_bits(priv->regmap, MA35_PANEL_CONFIG,
+					   MA35_PANEL_DATA_ENABLE_ENABLE, 0);
+	regmap_update_bits(priv->regmap, MA35_FRAMEBUFFER_CONFIG,
+					   MA35_PRIMARY_GAMMA, 0);
+	regmap_write(priv->regmap, MA35_DISPLAY_DITHER_CONFIG, 0);
+
+	/* Consume any leftover event since vblank is now disabled. */
+	if (drm_crtc->state->event && !drm_crtc->state->active) {
+		spin_lock_irq(&drm_dev->event_lock);
+
+		drm_crtc_send_vblank_event(drm_crtc, drm_crtc->state->event);
+		drm_crtc->state->event = NULL;
+		spin_unlock_irq(&drm_dev->event_lock);
+	}
+}
+
+static void ma35_crtc_atomic_flush(struct drm_crtc *drm_crtc,
+	struct drm_atomic_state *state)
+{
+	spin_lock_irq(&drm_crtc->dev->event_lock);
+	if (drm_crtc->state->event) {
+		if (drm_crtc_vblank_get(drm_crtc) == 0)
+			drm_crtc_arm_vblank_event(drm_crtc, drm_crtc->state->event);
+		else
+			drm_crtc_send_vblank_event(drm_crtc, drm_crtc->state->event);
+
+		drm_crtc->state->event = NULL;
+	}
+	spin_unlock_irq(&drm_crtc->dev->event_lock);
+}
+
+static bool ma35_crtc_get_scanout_position(struct drm_crtc *drm_crtc,
+					   bool in_vblank_irq,
+					   int *vpos,
+					   int *hpos,
+					   ktime_t *stime,
+					   ktime_t *etime,
+					   const struct drm_display_mode *mode)
+{
+	struct ma35_drm *priv = ma35_drm(drm_crtc->dev);
+	u32 reg;
+
+	if (stime)
+		*stime = ktime_get();
+
+	regmap_read(priv->regmap, MA35_DISPLAY_CURRENT_LOCATION, &reg);
+
+	*hpos = FIELD_GET(MA35_DISPLAY_CURRENT_X, reg);
+	*vpos = FIELD_GET(MA35_DISPLAY_CURRENT_Y, reg);
+
+	if (etime)
+		*etime = ktime_get();
+
+	return true;
+}
+
+static const struct drm_crtc_helper_funcs ma35_crtc_helper_funcs = {
+	.mode_valid		= ma35_crtc_mode_valid,
+	.atomic_check		= ma35_crtc_atomic_check,
+	.atomic_enable		= ma35_crtc_atomic_enable,
+	.atomic_disable		= ma35_crtc_atomic_disable,
+	.atomic_flush		= ma35_crtc_atomic_flush,
+	.get_scanout_position	= ma35_crtc_get_scanout_position,
+};
+
+static int ma35_crtc_enable_vblank(struct drm_crtc *drm_crtc)
+{
+	struct ma35_drm *priv = ma35_drm(drm_crtc->dev);
+
+	regmap_write(priv->regmap, MA35_DISPLAY_INTRENABLE,
+			  MA35_CRTC_VBLANK);
+
+	return 0;
+}
+
+static void ma35_crtc_disable_vblank(struct drm_crtc *drm_crtc)
+{
+	struct ma35_drm *priv = ma35_drm(drm_crtc->dev);
+
+	regmap_write(priv->regmap, MA35_DISPLAY_INTRENABLE, 0);
+}
+
+static u32 ma35_crtc_get_vblank_counter(struct drm_crtc *drm_crtc)
+{
+	struct ma35_drm *priv = ma35_drm(drm_crtc->dev);
+
+	return atomic_read(&priv->crtc->vblank_counter);
+}
+
+static int ma35_crtc_gamma_set(struct drm_crtc *drm_crtc,
+		  u16 *r, u16 *g, u16 *b, uint32_t size,
+		  struct drm_modeset_acquire_ctx *ctx)
+{
+	struct ma35_drm *priv = ma35_drm(drm_crtc->dev);
+	u32 reg;
+	int i;
+
+	if (size != MA35_GAMMA_TABLE_SIZE)
+		return -EINVAL;
+
+	regmap_write(priv->regmap, MA35_GAMMA_INDEX, 0); // auto increment
+
+	for (i = 0; i < size; i++) {
+		reg = FIELD_PREP(MA35_GAMMA_RED_MASK, r[i]) |
+			  FIELD_PREP(MA35_GAMMA_GREEN_MASK, g[i]) |
+			  FIELD_PREP(MA35_GAMMA_BLUE_MASK, b[i]);
+		regmap_write(priv->regmap, MA35_GAMMA_DATA, reg);
+	}
+
+	return 0;
+}
+
+static const struct drm_crtc_funcs ma35_crtc_funcs = {
+	.reset			= drm_atomic_helper_crtc_reset,
+	.destroy		= drm_crtc_cleanup,
+	.set_config		= drm_atomic_helper_set_config,
+	.page_flip		= drm_atomic_helper_page_flip,
+	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank		= ma35_crtc_enable_vblank,
+	.disable_vblank		= ma35_crtc_disable_vblank,
+	.get_vblank_counter = ma35_crtc_get_vblank_counter,
+	.gamma_set      = ma35_crtc_gamma_set,
+};
+
+void ma35_crtc_vblank_handler(struct ma35_drm *priv)
+{
+	struct ma35_crtc *crtc = priv->crtc;
+
+	if (!crtc)
+		return;
+
+	atomic_inc(&crtc->vblank_counter);
+
+	drm_crtc_handle_vblank(&crtc->drm_crtc);
+}
+
+static int ma35_crtc_create_properties(struct ma35_drm *priv)
+{
+	struct ma35_crtc *crtc = priv->crtc;
+	int ret;
+
+	crtc->dpi_format = MA35_DPI_D24;
+	crtc->dither_enable = true;
+	crtc->dither_depth = 8;
+
+	ret = drm_mode_crtc_set_gamma_size(&crtc->drm_crtc, MA35_GAMMA_TABLE_SIZE);
+	if (ret)
+		return ret;
+	drm_crtc_enable_color_mgmt(&crtc->drm_crtc, 0, false, MA35_GAMMA_TABLE_SIZE);
+
+	return 0;
+}
+
+int ma35_crtc_init(struct ma35_drm *priv)
+{
+	struct drm_device *drm_dev = &priv->drm_dev;
+	struct ma35_crtc *crtc;
+	struct ma35_layer *layer_primary, *layer_cursor;
+	struct drm_plane *cursor_plane = NULL;
+	int ret;
+
+	crtc = drmm_kzalloc(drm_dev, sizeof(*crtc), GFP_KERNEL);
+	if (!crtc)
+		return -ENOMEM;
+
+	priv->crtc = crtc;
+	atomic_set(&crtc->vblank_counter, 0);
+
+	layer_primary = ma35_layer_get_from_type(priv, DRM_PLANE_TYPE_PRIMARY);
+	if (!layer_primary) {
+		drm_err(drm_dev, "Failed to get primary layer\n");
+		return -EINVAL;
+	}
+
+	layer_cursor = ma35_layer_get_from_type(priv, DRM_PLANE_TYPE_CURSOR);
+	if (layer_cursor)
+		cursor_plane = &layer_cursor->drm_plane;
+
+	/* attach primary and cursor */
+	ret = drm_crtc_init_with_planes(drm_dev, &crtc->drm_crtc,
+					&layer_primary->drm_plane, cursor_plane,
+					&ma35_crtc_funcs, NULL);
+	if (ret) {
+		drm_err(drm_dev, "Failed to initialize CRTC\n");
+		return ret;
+	}
+
+	/* attach overlay */
+	ma35_overlay_attach_crtc(priv);
+
+	/* dither & gamma */
+	ret = ma35_crtc_create_properties(priv);
+	if (ret)
+		return ret;
+
+	drm_crtc_helper_add(&crtc->drm_crtc, &ma35_crtc_helper_funcs);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/nuvoton/ma35_crtc.h b/drivers/gpu/drm/nuvoton/ma35_crtc.h
new file mode 100644
index 000000000000..c5d592fca87f
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/ma35_crtc.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Nuvoton DRM driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#ifndef _MA35_CRTC_H_
+#define _MA35_CRTC_H_
+
+#include <drm/drm_crtc.h>
+
+struct drm_pending_vblank_event;
+struct ma35_drm;
+
+#define MA35_DPI_D24	5
+#define MA35_DPI_FORMAT_MASK GENMASK(2, 0)
+
+struct ma35_crtc {
+	struct drm_crtc drm_crtc;
+	atomic_t vblank_counter;
+	u32 dpi_format;
+	u16 dither_depth;
+	bool dither_enable;
+};
+
+#define MA35_DEFAULT_CRTC_ID	0
+
+#define MA35_MAX_PIXEL_CLK		150000
+
+#define MA35_GAMMA_TABLE_SIZE	256
+#define MA35_GAMMA_RED_MASK		GENMASK(29, 20)
+#define MA35_GAMMA_GREEN_MASK	GENMASK(19, 10)
+#define MA35_GAMMA_BLUE_MASK	GENMASK(9, 0)
+
+#define MA35_DITHER_TABLE_ENTRY	16
+#define MA35_DITHER_ENABLE		BIT(31)
+#define MA35_DITHER_TABLE_MASK	GENMASK(3, 0)
+
+#define MA35_CRTC_VBLANK		BIT(0)
+
+#define MA35_DEBUG_COUNTER_MASK		GENMASK(31, 0)
+
+#define MA35_PANEL_DATA_ENABLE_ENABLE	BIT(0)
+#define MA35_PANEL_DATA_ENABLE_POLARITY	BIT(1)
+#define MA35_PANEL_DATA_ENABLE			BIT(4)
+#define MA35_PANEL_DATA_POLARITY		BIT(5)
+#define MA35_PANEL_DATA_CLOCK_ENABLE	BIT(8)
+#define MA35_PANEL_DATA_CLOCK_POLARITY	BIT(9)
+
+#define MA35_DISPLAY_TOTAL_MASK		GENMASK(30, 16)
+#define MA35_DISPLAY_ACTIVE_MASK	GENMASK(14, 0)
+
+#define MA35_SYNC_POLARITY_BIT	BIT(31)
+#define MA35_SYNC_PULSE_ENABLE	BIT(30)
+#define MA35_SYNC_END_MASK		GENMASK(29, 15)
+#define MA35_SYNC_START_MASK	GENMASK(14, 0)
+
+#define MA35_DISPLAY_CURRENT_X	GENMASK(15, 0)
+#define MA35_DISPLAY_CURRENT_Y	GENMASK(31, 16)
+
+void ma35_crtc_vblank_handler(struct ma35_drm *priv);
+int ma35_crtc_init(struct ma35_drm *priv);
+
+#endif
diff --git a/drivers/gpu/drm/nuvoton/ma35_drm.c b/drivers/gpu/drm/nuvoton/ma35_drm.c
new file mode 100644
index 000000000000..71fe3ccfb9dc
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/ma35_drm.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Nuvoton DRM driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <drm/clients/drm_client_setup.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "ma35_drm.h"
+
+DEFINE_DRM_GEM_DMA_FOPS(ma35_drm_fops);
+
+static int ma35_drm_gem_dma_dumb_create(struct drm_file *file_priv,
+					   struct drm_device *drm_dev,
+					   struct drm_mode_create_dumb *args)
+{
+	struct drm_mode_config *mode_config = &drm_dev->mode_config;
+	u32 pixel_align;
+
+	if (args->width < mode_config->min_width ||
+		args->height < mode_config->min_height)
+		return -EINVAL;
+
+	/* check for alignment */
+	pixel_align = MA35_DISPLAY_ALIGN_PIXELS * args->bpp / 8;
+	args->pitch = ALIGN(args->width * args->bpp / 8, pixel_align);
+
+	return drm_gem_dma_dumb_create_internal(file_priv, drm_dev, args);
+}
+
+static struct drm_driver ma35_drm_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC |
+						  DRIVER_CURSOR_HOTSPOT,
+
+	.fops				= &ma35_drm_fops,
+	.name				= "ma35-drm",
+	.desc				= "Nuvoton MA35 series DRM driver",
+	.major				= DRIVER_MAJOR,
+	.minor				= DRIVER_MINOR,
+
+	DRM_GEM_DMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(ma35_drm_gem_dma_dumb_create),
+};
+
+static const struct regmap_config ma35_drm_regmap_config = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= 0x2000,
+	.name		= "ma35-drm",
+};
+
+static irqreturn_t ma35_drm_irq_handler(int irq, void *data)
+{
+	struct ma35_drm *priv = data;
+	irqreturn_t ret = IRQ_NONE;
+	u32 stat = 0;
+
+	/* Get pending interrupt sources (RO) */
+	regmap_read(priv->regmap, MA35_INT_STATE, &stat);
+
+	if (stat & MA35_INT_STATE_DISP0) {
+		ma35_crtc_vblank_handler(priv);
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+static const struct drm_mode_config_funcs ma35_mode_config_funcs = {
+	.fb_create		= drm_gem_fb_create,
+	.atomic_check		= drm_atomic_helper_check,
+	.atomic_commit		= drm_atomic_helper_commit,
+};
+
+static const struct drm_mode_config_helper_funcs ma35_mode_config_helper_funcs = {
+	.atomic_commit_tail = drm_atomic_helper_commit_tail,
+};
+
+static int ma35_mode_init(struct ma35_drm *priv)
+{
+	struct drm_device *drm_dev = &priv->drm_dev;
+	struct drm_mode_config *mode_config = &drm_dev->mode_config;
+	int ret;
+
+	ret = drmm_mode_config_init(drm_dev);
+	if (ret) {
+		drm_err(drm_dev, "Failed to init mode config\n");
+		return -EINVAL;
+	}
+
+	drm_dev->max_vblank_count = MA35_DEBUG_COUNTER_MASK;
+	ret = drm_vblank_init(drm_dev, 1);
+	if (ret) {
+		drm_err(drm_dev, "Failed to initialize vblank\n");
+		return ret;
+	}
+
+	mode_config->min_width = 32;
+	mode_config->max_width = 1920;
+	mode_config->min_height = 1;
+	mode_config->max_height = 1080;
+	mode_config->preferred_depth = 24;
+	mode_config->cursor_width = MA35_CURSOR_WIDTH;
+	mode_config->cursor_height = MA35_CURSOR_HEIGHT;
+	mode_config->funcs = &ma35_mode_config_funcs;
+	mode_config->helper_private = &ma35_mode_config_helper_funcs;
+
+	return 0;
+}
+
+static void ma35_mode_fini(struct ma35_drm *priv)
+{
+	struct drm_device *drm_dev = &priv->drm_dev;
+
+	drm_kms_helper_poll_fini(drm_dev);
+}
+
+static int ma35_clocks_prepare(struct ma35_drm *priv)
+{
+	struct drm_device *drm_dev = &priv->drm_dev;
+	struct device *dev = drm_dev->dev;
+	int ret;
+
+	priv->dcuclk = devm_clk_get(dev, "dcu_gate");
+	if (IS_ERR(priv->dcuclk)) {
+		dev_err(dev, "Failed to get display core clock\n");
+		return PTR_ERR(priv->dcuclk);
+	}
+
+	ret = clk_prepare_enable(priv->dcuclk);
+	if (ret) {
+		dev_err(dev, "Failed to enable display core clock\n");
+		return ret;
+	}
+
+	priv->dcupclk = devm_clk_get(dev, "dcup_div");
+	if (IS_ERR(priv->dcupclk)) {
+		dev_err(dev, "Failed to get display pixel clock\n");
+		return PTR_ERR(priv->dcupclk);
+	}
+
+	ret = clk_prepare_enable(priv->dcupclk);
+	if (ret) {
+		dev_err(dev, "Failed to enable display pixel clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ma35_clocks_unprepare(struct ma35_drm *priv)
+{
+	struct clk **clocks[] = {
+		&priv->dcuclk,
+		&priv->dcupclk,
+	};
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(clocks); i++) {
+		if (!*clocks[i])
+			continue;
+
+		clk_disable_unprepare(*clocks[i]);
+		*clocks[i] = NULL;
+	}
+
+	return 0;
+}
+
+static int ma35_drm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ma35_drm *priv;
+	struct drm_device *drm_dev;
+	void __iomem *base;
+	struct regmap *regmap = NULL;
+	int irq;
+	int ret;
+
+	ret = of_reserved_mem_device_init(dev);
+	if (ret && ret != -ENODEV) {
+		dev_err(dev, "Failed to get optional reserved memory: %d\n", ret);
+		return ret;
+	}
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base)) {
+		dev_err(dev, "Failed to map I/O base\n");
+		ret = PTR_ERR(base);
+		goto error_reserved_mem;
+	}
+	regmap = devm_regmap_init_mmio(dev, base, &ma35_drm_regmap_config);
+	if (IS_ERR(regmap)) {
+		dev_err(dev, "Failed to create regmap for I/O\n");
+		ret = PTR_ERR(regmap);
+		goto error_reserved_mem;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		ret = -ENODEV;
+		goto error_reserved_mem;
+	}
+
+	priv = devm_drm_dev_alloc(dev, &ma35_drm_driver,
+				     struct ma35_drm, drm_dev);
+	if (IS_ERR(priv)) {
+		ret = PTR_ERR(priv);
+		goto error_reserved_mem;
+	}
+
+	platform_set_drvdata(pdev, priv);
+	drm_dev = &priv->drm_dev;
+	priv->regmap = regmap;
+	INIT_LIST_HEAD(&priv->layers_list);
+
+	ret = ma35_clocks_prepare(priv);
+	if (ret) {
+		drm_err(drm_dev, "Failed to prepare clocks\n");
+		goto error_reserved_mem;
+	}
+
+	ret = devm_request_irq(dev, irq, ma35_drm_irq_handler, 0,
+			       dev_name(dev), priv);
+	if (ret) {
+		drm_err(drm_dev, "Failed to request IRQ\n");
+		goto error_clocks;
+	}
+
+	/* modeset */
+	ret = ma35_mode_init(priv);
+	if (ret) {
+		drm_err(drm_dev, "Failed to initialize KMS\n");
+		goto error_clocks;
+	}
+
+	/* plane */
+	ret = ma35_plane_init(priv);
+	if (ret) {
+		drm_err(drm_dev, "Failed to initialize layers\n");
+		goto error_clocks;
+	}
+
+	/* crtc */
+	ret = ma35_crtc_init(priv);
+	if (ret) {
+		drm_err(drm_dev, "Failed to initialize CRTC\n");
+		goto error_clocks;
+	}
+
+	/* interface */
+	ret = ma35_interface_init(priv);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			drm_err(drm_dev, "Failed to initialize interface\n");
+
+		goto error_clocks;
+	}
+
+	drm_mode_config_reset(drm_dev);
+
+	ret = drm_dev_register(drm_dev, 0);
+	if (ret) {
+		drm_err(drm_dev, "Failed to register DRM device\n");
+		goto error_mode;
+	}
+
+	drm_client_setup(drm_dev, NULL);
+
+	return 0;
+
+error_mode:
+	ma35_mode_fini(priv);
+
+error_clocks:
+	ma35_clocks_unprepare(priv);
+
+error_reserved_mem:
+	of_reserved_mem_device_release(dev);
+	return ret;
+}
+
+static void ma35_drm_remove(struct platform_device *pdev)
+{
+	struct ma35_drm *priv = platform_get_drvdata(pdev);
+	struct device *dev = &pdev->dev;
+	struct drm_device *drm_dev = &priv->drm_dev;
+
+	drm_dev_unregister(drm_dev);
+	drm_atomic_helper_shutdown(drm_dev);
+
+	ma35_mode_fini(priv);
+
+	ma35_clocks_unprepare(priv);
+
+	of_reserved_mem_device_release(dev);
+}
+
+static void ma35_drm_shutdown(struct platform_device *pdev)
+{
+	struct ma35_drm *priv = platform_get_drvdata(pdev);
+	struct drm_device *drm_dev = &priv->drm_dev;
+
+	drm_atomic_helper_shutdown(drm_dev);
+}
+
+static __maybe_unused int ma35_drm_suspend(struct device *dev)
+{
+	struct ma35_drm *priv = dev_get_drvdata(dev);
+	struct drm_device *drm_dev = &priv->drm_dev;
+
+	return drm_mode_config_helper_suspend(drm_dev);
+}
+
+static __maybe_unused int ma35_drm_resume(struct device *dev)
+{
+	struct ma35_drm *priv = dev_get_drvdata(dev);
+	struct drm_device *drm_dev = &priv->drm_dev;
+
+	return drm_mode_config_helper_resume(drm_dev);
+}
+
+static const struct of_device_id ma35_drm_of_table[] = {
+	{ .compatible = "nuvoton,ma35d1-dcu" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ma35_drm_of_table);
+
+static const struct dev_pm_ops ma35_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(ma35_drm_suspend, ma35_drm_resume)
+};
+
+static struct platform_driver ma35_drm_platform_driver = {
+	.probe		= ma35_drm_probe,
+	.remove		= ma35_drm_remove,
+	.shutdown	= ma35_drm_shutdown,
+	.driver		= {
+		.name		= "ma35-drm",
+		.of_match_table	= ma35_drm_of_table,
+		.pm = &ma35_pm_ops,
+	},
+};
+
+module_platform_driver(ma35_drm_platform_driver);
+
+MODULE_AUTHOR("Joey Lu <a0987203069@gmail.com>");
+MODULE_DESCRIPTION("Nuvoton MA35 series DRM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/nuvoton/ma35_drm.h b/drivers/gpu/drm/nuvoton/ma35_drm.h
new file mode 100644
index 000000000000..68da6b11a323
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/ma35_drm.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Nuvoton DRM driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#ifndef _MA35_DRM_H_
+#define _MA35_DRM_H_
+
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <drm/drm_device.h>
+
+#include "ma35_regs.h"
+#include "ma35_plane.h"
+#include "ma35_crtc.h"
+#include "ma35_interface.h"
+
+#define DRIVER_MAJOR	1
+#define DRIVER_MINOR	0
+
+#define MA35_INT_STATE_DISP0	BIT(0)
+
+#define MA35_DISPLAY_ALIGN_PIXELS	32
+#define MA35_DISPLAY_PREFER_DEPTH	32
+
+#define MA35_CURSOR_WIDTH	32
+#define MA35_CURSOR_HEIGHT	32
+
+#define MA35_DISPLAY_MAX_ZPOS	3
+
+#define ma35_drm(d) \
+	container_of(d, struct ma35_drm, drm_dev)
+
+struct ma35_drm {
+	struct drm_device drm_dev;
+	struct regmap *regmap;
+	struct list_head layers_list;
+	struct ma35_crtc *crtc;
+	struct ma35_interface *interface;
+	struct clk *dcuclk;
+	struct clk *dcupclk;
+};
+
+#endif
diff --git a/drivers/gpu/drm/nuvoton/ma35_interface.c b/drivers/gpu/drm/nuvoton/ma35_interface.c
new file mode 100644
index 000000000000..ceb37bbabf6c
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/ma35_interface.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Nuvoton DRM driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#include <linux/types.h>
+#include <linux/clk.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "ma35_drm.h"
+
+#define ma35_encoder(e) \
+	container_of(e, struct ma35_interface, drm_encoder)
+#define ma35_connector(c) \
+	container_of(c, struct ma35_interface, drm_connector)
+
+static void ma35_encoder_mode_set(struct drm_encoder *encoder,
+	struct drm_crtc_state *crtc_state,
+	struct drm_connector_state *conn_state)
+{
+	struct drm_device *drm_dev = encoder->dev;
+	struct ma35_drm *priv = ma35_drm(drm_dev);
+	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+	int result;
+
+	clk_set_rate(priv->dcupclk, adjusted_mode->clock * 1000);
+	result = DIV_ROUND_UP(clk_get_rate(priv->dcupclk), 1000);
+	drm_dbg(drm_dev, "Pixel clock: %d kHz; request : %d kHz\n", result, adjusted_mode->clock);
+}
+
+static int ma35_encoder_atomic_check(struct drm_encoder *encoder,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state)
+{
+	struct ma35_interface *interface = ma35_encoder(encoder);
+	struct drm_display_info *display_info = &conn_state->connector->display_info;
+
+	interface->bus_flags = display_info->bus_flags;
+
+	return 0;
+}
+
+static const struct drm_encoder_helper_funcs ma35_encoder_helper_funcs = {
+	.atomic_mode_set	= ma35_encoder_mode_set,
+	.atomic_check		= ma35_encoder_atomic_check,
+};
+
+static const struct drm_connector_funcs ma35_connector_funcs = {
+	.reset			= drm_atomic_helper_connector_reset,
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.destroy		= drm_connector_cleanup,
+	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
+};
+
+static int ma35_connector_get_modes(struct drm_connector *drm_connector)
+{
+	struct ma35_drm *priv = ma35_drm(drm_connector->dev);
+	struct drm_device *drm_dev = &priv->drm_dev;
+	struct drm_mode_config *mode_config = &drm_dev->mode_config;
+	struct ma35_interface *interface = ma35_connector(drm_connector);
+	int count;
+
+	if (!interface->drm_panel) {
+		/* Use the default modes */
+		count = drm_add_modes_noedid(drm_connector,
+				mode_config->max_width, mode_config->max_height);
+		drm_set_preferred_mode(drm_connector,
+				mode_config->max_width, mode_config->max_height);
+
+		return count;
+	} else {
+		return drm_panel_get_modes(interface->drm_panel, drm_connector);
+	}
+}
+
+static const struct drm_connector_helper_funcs ma35_connector_helper_funcs = {
+	.get_modes		= ma35_connector_get_modes,
+};
+
+static void ma35_encoder_attach_crtc(struct ma35_drm *priv)
+{
+	uint32_t possible_crtcs = drm_crtc_mask(&priv->crtc->drm_crtc);
+
+	priv->interface->drm_encoder.possible_crtcs = possible_crtcs;
+}
+
+static int ma35_bridge_try_attach(struct ma35_drm *priv, struct ma35_interface *interface)
+{
+	struct drm_device *drm_dev = &priv->drm_dev;
+	struct device *dev = drm_dev->dev;
+	struct device_node *of_node = dev->of_node;
+	struct drm_bridge *bridge;
+	struct drm_panel *panel;
+	int ret;
+
+	ret = drm_of_find_panel_or_bridge(of_node, 0, 0, &panel, &bridge);
+
+	if (ret) {
+		drm_info(drm_dev, "No panel or bridge found\n");
+		return ret;
+	}
+
+	if (panel) {
+		bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DPI);
+		if (IS_ERR(bridge))
+			return PTR_ERR(bridge);
+	}
+
+	interface->drm_panel = panel;
+	interface->drm_bridge = bridge;
+
+	ret = drm_bridge_attach(&interface->drm_encoder, bridge,
+				NULL, 0);
+	if (ret) {
+		drm_err(drm_dev, "Failed to attach bridge to encoder\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+int ma35_interface_init(struct ma35_drm *priv)
+{
+	struct ma35_interface *interface;
+	struct drm_device *drm_dev = &priv->drm_dev;
+	struct drm_encoder *drm_encoder;
+	int ret = 0;
+
+	/* encoder */
+	interface = drmm_simple_encoder_alloc(drm_dev,
+					struct ma35_interface, drm_encoder, DRM_MODE_ENCODER_DPI);
+	if (!interface) {
+		drm_err(drm_dev, "Failed to initialize encoder\n");
+		goto error_early;
+	}
+	priv->interface = interface;
+	drm_encoder = &interface->drm_encoder;
+	drm_encoder_helper_add(drm_encoder,
+			       &ma35_encoder_helper_funcs);
+
+	/* attach encoder to crtc */
+	ma35_encoder_attach_crtc(priv);
+
+	/* attach bridge to encoder if found one in device tree */
+	ret = ma35_bridge_try_attach(priv, interface);
+	if (!ret)
+		return 0;
+
+	/* fallback to raw dpi connector */
+	ret = drmm_connector_init(drm_dev, &interface->drm_connector,
+					&ma35_connector_funcs,
+					DRM_MODE_CONNECTOR_DPI,
+					NULL);
+	if (ret) {
+		drm_err(drm_dev, "Failed to initialize connector\n");
+		goto error_encoder;
+	}
+	drm_connector_helper_add(&interface->drm_connector,
+						&ma35_connector_helper_funcs);
+	ret = drm_connector_attach_encoder(&interface->drm_connector,
+						drm_encoder);
+	if (ret) {
+		drm_err(drm_dev,
+			"Failed to attach connector to encoder\n");
+		goto error_encoder;
+	}
+
+	return ret;
+
+error_encoder:
+	drm_encoder_cleanup(drm_encoder);
+
+error_early:
+	return ret;
+}
diff --git a/drivers/gpu/drm/nuvoton/ma35_interface.h b/drivers/gpu/drm/nuvoton/ma35_interface.h
new file mode 100644
index 000000000000..db7ed41bee45
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/ma35_interface.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Nuvoton DRM driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#ifndef _MA35_INTERFACE_H_
+#define _MA35_INTERFACE_H_
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+
+struct ma35_drm;
+
+struct ma35_interface {
+	struct drm_encoder drm_encoder;
+	struct drm_connector drm_connector;
+	struct drm_panel *drm_panel;
+	struct drm_bridge *drm_bridge;
+
+	u32 bus_flags;
+};
+
+int ma35_interface_init(struct ma35_drm *priv);
+
+#endif
diff --git a/drivers/gpu/drm/nuvoton/ma35_plane.c b/drivers/gpu/drm/nuvoton/ma35_plane.c
new file mode 100644
index 000000000000..a810883abbe1
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/ma35_plane.c
@@ -0,0 +1,603 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Nuvoton DRM driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#include <linux/of.h>
+#include <linux/types.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include "ma35_drm.h"
+
+#define ma35_layer(p) \
+	container_of(p, struct ma35_layer, drm_plane)
+
+static uint32_t ma35_layer_formats[] = {
+	/* rgb32 */
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_ARGB2101010,
+	/* rgb16 */
+	DRM_FORMAT_XRGB4444,
+	DRM_FORMAT_ARGB4444,
+	DRM_FORMAT_XRGB1555,
+	DRM_FORMAT_ARGB1555,
+	DRM_FORMAT_RGB565,
+	/* yuv */
+	DRM_FORMAT_YUYV,
+	DRM_FORMAT_UYVY,
+	DRM_FORMAT_YVU420,
+	DRM_FORMAT_NV12,
+	DRM_FORMAT_NV16,
+	DRM_FORMAT_P010,
+};
+
+static uint32_t ma35_cursor_formats[] = {
+	DRM_FORMAT_XRGB8888,
+};
+
+static struct ma35_plane_property ma35_plane_properties[] = {
+	{ /* overlay */
+		.fb_addr    = { MA35_OVERLAY_ADDRESS,
+						MA35_OVERLAY_UPLANAR_ADDRESS,
+						MA35_OVERLAY_VPLANAR_ADDRESS },
+		.fb_stride  = { MA35_OVERLAY_STRIDE,
+						MA35_OVERLAY_USTRIDE,
+						MA35_OVERLAY_VSTRIDE },
+		.alpha      = true,
+	},
+	{ /* primary */
+		.fb_addr    = { MA35_FRAMEBUFFER_ADDRESS,
+						MA35_FRAMEBUFFER_UPLANAR_ADDRESS,
+						MA35_FRAMEBUFFER_VPLANAR_ADDRESS },
+		.fb_stride  = { MA35_FRAMEBUFFER_STRIDE,
+						MA35_FRAMEBUFFER_USTRIDE,
+						MA35_FRAMEBUFFER_VSTRIDE },
+		.alpha      = false,
+	},
+};
+
+static int ma35_layer_format_validate(u32 fourcc, u32 *format)
+{
+	switch (fourcc) {
+	case DRM_FORMAT_XRGB4444:
+		*format = MA35_FORMAT_X4R4G4B4;
+		break;
+	case DRM_FORMAT_ARGB4444:
+		*format = MA35_FORMAT_A4R4G4B4;
+		break;
+	case DRM_FORMAT_XRGB1555:
+		*format = MA35_FORMAT_X1R5G5B5;
+		break;
+	case DRM_FORMAT_ARGB1555:
+		*format = MA35_FORMAT_A1R5G5B5;
+		break;
+	case DRM_FORMAT_RGB565:
+		*format = MA35_FORMAT_R5G6B5;
+		break;
+	case DRM_FORMAT_XRGB8888:
+		*format = MA35_FORMAT_X8R8G8B8;
+		break;
+	case DRM_FORMAT_ARGB8888:
+		*format = MA35_FORMAT_A8R8G8B8;
+		break;
+	case DRM_FORMAT_ARGB2101010:
+		*format = MA35_FORMAT_A2R10G10B10;
+		break;
+	case DRM_FORMAT_YUYV:
+		*format = MA35_FORMAT_YUY2;
+		break;
+	case DRM_FORMAT_UYVY:
+		*format = MA35_FORMAT_UYVY;
+		break;
+	case DRM_FORMAT_YVU420:
+		*format = MA35_FORMAT_YV12;
+		break;
+	case DRM_FORMAT_NV12:
+		*format = MA35_FORMAT_NV12;
+		break;
+	case DRM_FORMAT_NV16:
+		*format = MA35_FORMAT_NV16;
+		break;
+	case DRM_FORMAT_P010:
+		*format = MA35_FORMAT_P010;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ma35_plane_atomic_check(struct drm_plane *drm_plane,
+				      struct drm_atomic_state *state)
+{
+	struct drm_device *drm_dev = drm_plane->dev;
+	struct drm_plane_state *new_state =
+		drm_atomic_get_new_plane_state(state, drm_plane);
+	struct drm_crtc *crtc = new_state->crtc;
+	struct drm_framebuffer *fb = new_state->fb;
+	struct drm_crtc_state *crtc_state;
+	bool can_position;
+	u32 format;
+
+	if (!crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (IS_ERR(crtc_state))
+		return PTR_ERR(crtc_state);
+
+	if (new_state->crtc_x < 0 || new_state->crtc_y < 0) {
+		drm_err(drm_dev,
+			"Negative on-CRTC positions are not supported.\n");
+		return -EINVAL;
+	}
+
+	if (ma35_layer_format_validate(fb->format->format, &format) < 0) {
+		drm_err(drm_dev, "Unsupported format\n");
+		return -EINVAL;
+	}
+
+	can_position = (drm_plane->type != DRM_PLANE_TYPE_PRIMARY);
+	return drm_atomic_helper_check_plane_state(new_state, crtc_state,
+						  DRM_PLANE_NO_SCALING, DRM_PLANE_NO_SCALING,
+						  can_position, true);
+}
+
+static int ma35_cursor_plane_atomic_check(struct drm_plane *drm_plane,
+				      struct drm_atomic_state *state)
+{
+	struct drm_device *drm_dev = drm_plane->dev;
+	struct drm_plane_state *new_state =
+		drm_atomic_get_new_plane_state(state, drm_plane);
+	struct drm_framebuffer *fb = new_state->fb;
+	struct drm_crtc *crtc = new_state->crtc;
+	struct drm_crtc_state *crtc_state;
+
+	if (!fb)
+		return 0;
+
+	if (!crtc)
+		return -EINVAL;
+
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (IS_ERR(crtc_state))
+		return PTR_ERR(crtc_state);
+
+	if (fb->format->format != DRM_FORMAT_XRGB8888) {
+		drm_err(drm_dev, "Invalid cursor format\n");
+		return -EINVAL;
+	}
+
+	if (new_state->crtc_w != MA35_CURSOR_SIZE || new_state->crtc_h != MA35_CURSOR_SIZE) {
+		drm_err(drm_dev, "Unsupported cursor size: %ux%u\n",
+				new_state->crtc_w, new_state->crtc_h);
+		return -EINVAL;
+	}
+
+	if (new_state->hotspot_x >= 32 || new_state->hotspot_x < 0 ||
+		new_state->hotspot_y >= 32 || new_state->hotspot_y < 0) {
+		drm_err(drm_dev, "Invalid cursor hotspot offset\n");
+		return -EINVAL;
+	}
+
+	return drm_atomic_helper_check_plane_state(new_state, crtc_state,
+						  DRM_PLANE_NO_SCALING, DRM_PLANE_NO_SCALING,
+						  true, true);
+}
+
+static int ma35_cursor_plane_atomic_async_check(struct drm_plane *drm_plane,
+				      struct drm_atomic_state *state, bool flip)
+{
+	return ma35_cursor_plane_atomic_check(drm_plane, state);
+}
+
+static void ma35_overlay_position_update(struct ma35_drm *priv,
+						int x, int y, uint32_t w, uint32_t h)
+{
+	u32 reg;
+	int right, bottom;
+
+	right = x + w;
+	bottom = y + h;
+
+	x = (x < 0) ? 0 : x;
+	y = (y < 0) ? 0 : y;
+	right = (right < 0) ? 0 : right;
+	bottom = (bottom < 0) ? 0 : bottom;
+
+	reg = FIELD_PREP(MA35_OVERLAY_POSITION_X_MASK, x) |
+		  FIELD_PREP(MA35_OVERLAY_POSITION_Y_MASK, y);
+	regmap_write(priv->regmap, MA35_OVERLAY_TL, reg);
+
+	reg = FIELD_PREP(MA35_OVERLAY_POSITION_X_MASK, right) |
+		  FIELD_PREP(MA35_OVERLAY_POSITION_Y_MASK, bottom);
+	regmap_write(priv->regmap, MA35_OVERLAY_BR, reg);
+}
+
+static void ma35_plane_atomic_update(struct drm_plane *drm_plane,
+					struct drm_atomic_state *state)
+{
+	struct ma35_layer *layer = ma35_layer(drm_plane);
+	struct ma35_drm *priv = ma35_drm(drm_plane->dev);
+	struct drm_plane_state *new_state =
+		drm_atomic_get_new_plane_state(state, drm_plane);
+	struct drm_framebuffer *fb = new_state->fb;
+	u32 format, reg;
+	u32 *preg;
+
+	ma35_layer_format_validate(fb->format->format, &format);
+
+	if (drm_plane->type == DRM_PLANE_TYPE_PRIMARY) {
+		reg = FIELD_PREP(MA35_PRIMARY_FORMAT_MASK, format) |
+			  MA35_PRIMARY_RESET | MA35_PRIMARY_ENABLE;
+		regmap_write(priv->regmap, MA35_FRAMEBUFFER_CONFIG, reg);
+
+		reg = FIELD_PREP(MA35_LAYER_FB_HEIGHT, fb->height) |
+			  FIELD_PREP(MA35_LAYER_FB_WIDTH, fb->width);
+		regmap_write(priv->regmap, MA35_FRAMEBUFFER_SIZE, reg);
+
+		/* clear value */
+		regmap_write(priv->regmap, MA35_FRAMEBUFFER_CLEARVALUE, 0);
+	} else if (drm_plane->type == DRM_PLANE_TYPE_OVERLAY) {
+		reg = FIELD_PREP(MA35_OVERLAY_FORMAT_MASK, format) |
+			  MA35_OVERLAY_ENABLE;
+		regmap_write(priv->regmap, MA35_OVERLAY_CONFIG, reg);
+
+		reg = FIELD_PREP(MA35_LAYER_FB_HEIGHT, fb->height) |
+			FIELD_PREP(MA35_LAYER_FB_WIDTH, fb->width);
+		regmap_write(priv->regmap, MA35_OVERLAY_SIZE, reg);
+
+		/* can_position */
+		ma35_overlay_position_update(priv, new_state->crtc_x, new_state->crtc_y,
+			new_state->crtc_w, new_state->crtc_h);
+
+		/* alpha blending */
+		if (fb->format->format == DRM_FORMAT_ARGB8888) {
+			if (new_state->pixel_blend_mode &
+				(DRM_MODE_BLEND_PREMULTI | DRM_MODE_BLEND_COVERAGE))
+				reg = MA35_BLEND_MODE_SRC_OVER;
+			if (new_state->pixel_blend_mode & DRM_MODE_BLEND_COVERAGE)
+				reg |= MA35_SRC_ALPHA_FACTOR_EN;
+			regmap_write(priv->regmap, MA35_OVERLAY_ALPHA_BLEND_CONFIG, reg);
+		} else {
+			regmap_update_bits(priv->regmap, MA35_OVERLAY_ALPHA_BLEND_CONFIG,
+				MA35_ALPHA_BLEND_DISABLE, MA35_ALPHA_BLEND_DISABLE);
+		}
+
+		/* clear value */
+		regmap_write(priv->regmap, MA35_OVERLAY_CLEAR_VALUE, 0);
+	}
+
+	/* retrieves DMA address set by userspace */
+	for (int i = 0; i < fb->format->num_planes; i++) {
+		layer->fb_base[i] = drm_fb_dma_get_gem_addr(fb, new_state, i);
+		preg = ma35_plane_properties[drm_plane->type].fb_addr;
+		regmap_write(priv->regmap, preg[i], layer->fb_base[i]);
+		preg = ma35_plane_properties[drm_plane->type].fb_stride;
+		regmap_write(priv->regmap, preg[i], fb->pitches[i]);
+	}
+}
+
+static void ma35_cursor_position_update(struct ma35_drm *priv, int x, int y)
+{
+	u32 reg;
+
+	x = (x < 0) ? 0 : x;
+	y = (y < 0) ? 0 : y;
+
+	reg = FIELD_PREP(MA35_CURSOR_X_MASK, x) |
+		  FIELD_PREP(MA35_CURSOR_Y_MASK, y);
+	regmap_write(priv->regmap, MA35_CURSOR_LOCATION, reg);
+}
+
+static void ma35_cursor_plane_atomic_update(struct drm_plane *drm_plane,
+					struct drm_atomic_state *state)
+{
+	struct ma35_layer *layer = ma35_layer(drm_plane);
+	struct ma35_drm *priv = ma35_drm(drm_plane->dev);
+	struct drm_plane_state *old_state =
+		drm_atomic_get_old_plane_state(state, drm_plane);
+	struct drm_plane_state *new_state =
+		drm_atomic_get_new_plane_state(state, drm_plane);
+	struct drm_framebuffer *old_fb = old_state->fb;
+	struct drm_framebuffer *new_fb = new_state->fb;
+	u32 reg;
+
+	if (!new_state->visible) {
+		regmap_update_bits(priv->regmap, MA35_CURSOR_CONFIG,
+			MA35_CURSOR_FORMAT_MASK, MA35_CURSOR_FORMAT_DISABLE);
+		return;
+	}
+
+	/* update position */
+	ma35_cursor_position_update(priv, new_state->crtc_x, new_state->crtc_y);
+
+	/* check new_state is different from old_state for dimensions or format changed */
+	if (!old_fb || old_fb != new_fb) {
+		layer->fb_base[0] = drm_fb_dma_get_gem_addr(new_fb, new_state, 0);
+		regmap_write(priv->regmap, MA35_CURSOR_ADDRESS, layer->fb_base[0]);
+		regmap_update_bits(priv->regmap, MA35_CURSOR_CONFIG,
+			MA35_CURSOR_FORMAT_MASK, MA35_CURSOR_FORMAT_A8R8G8B8);
+	}
+
+	/* update hotspot offset & format */
+	if (old_state->hotspot_x != new_state->hotspot_x ||
+		old_state->hotspot_y != new_state->hotspot_y) {
+		reg = MA35_CURSOR_FORMAT_A8R8G8B8 |
+			FIELD_PREP(MA35_CURSOR_HOTSPOT_X_MASK, new_state->hotspot_x) |
+			FIELD_PREP(MA35_CURSOR_HOTSPOT_Y_MASK, new_state->hotspot_y);
+		regmap_write(priv->regmap, MA35_CURSOR_CONFIG, reg);
+	}
+}
+
+static void ma35_cursor_plane_atomic_async_update(struct drm_plane *drm_plane,
+					struct drm_atomic_state *state)
+{
+	struct ma35_layer *layer = ma35_layer(drm_plane);
+	struct ma35_drm *priv = ma35_drm(drm_plane->dev);
+	struct drm_plane_state *old_state = drm_plane->state;
+	struct drm_plane_state *new_state =
+		drm_atomic_get_new_plane_state(state, drm_plane);
+	struct drm_framebuffer *old_fb = old_state->fb;
+	struct drm_framebuffer *new_fb = new_state->fb;
+	u32 reg;
+
+	/* update the current one with the new plane state */
+	old_state->crtc_x = new_state->crtc_x;
+	old_state->crtc_y = new_state->crtc_y;
+	old_state->crtc_h = new_state->crtc_h;
+	old_state->crtc_w = new_state->crtc_w;
+	old_state->src_x = new_state->src_x;
+	old_state->src_y = new_state->src_y;
+	old_state->src_h = new_state->src_h;
+	old_state->src_w = new_state->src_w;
+	/* swap current and new framebuffers */
+	swap(old_fb, new_fb);
+
+	if (!new_state->visible) {
+		regmap_update_bits(priv->regmap, MA35_CURSOR_CONFIG,
+			MA35_CURSOR_FORMAT_MASK, MA35_CURSOR_FORMAT_DISABLE);
+		return;
+	}
+
+	/* update position */
+	ma35_cursor_position_update(priv, new_state->crtc_x, new_state->crtc_y);
+
+	/* check new_state is different from old_state for dimensions or format changed */
+	if (!old_fb || old_fb != new_fb) {
+		layer->fb_base[0] = drm_fb_dma_get_gem_addr(new_fb, new_state, 0);
+		regmap_write(priv->regmap, MA35_CURSOR_ADDRESS, layer->fb_base[0]);
+		regmap_update_bits(priv->regmap, MA35_CURSOR_CONFIG,
+			MA35_CURSOR_FORMAT_MASK, MA35_CURSOR_FORMAT_A8R8G8B8);
+	}
+
+	/* update hotspot offset & format */
+	if (old_state->hotspot_x != new_state->hotspot_x ||
+		old_state->hotspot_y != new_state->hotspot_y) {
+		reg = MA35_CURSOR_FORMAT_A8R8G8B8 |
+			FIELD_PREP(MA35_CURSOR_HOTSPOT_X_MASK, new_state->hotspot_x) |
+			FIELD_PREP(MA35_CURSOR_HOTSPOT_Y_MASK, new_state->hotspot_y);
+		regmap_write(priv->regmap, MA35_CURSOR_CONFIG, reg);
+		old_state->hotspot_x = new_state->hotspot_x;
+		old_state->hotspot_y = new_state->hotspot_y;
+	}
+}
+
+static void ma35_plane_atomic_disable(struct drm_plane *drm_plane,
+					 struct drm_atomic_state *state)
+{
+	struct ma35_drm *priv = ma35_drm(drm_plane->dev);
+
+	regmap_update_bits(priv->regmap, MA35_FRAMEBUFFER_CONFIG,
+		MA35_PRIMARY_ENABLE, 0);
+}
+
+static void ma35_cursor_plane_atomic_disable(struct drm_plane *drm_plane,
+					 struct drm_atomic_state *state)
+{
+	struct ma35_drm *priv = ma35_drm(drm_plane->dev);
+
+	regmap_update_bits(priv->regmap, MA35_CURSOR_CONFIG,
+		MA35_CURSOR_FORMAT_MASK, MA35_CURSOR_FORMAT_DISABLE);
+}
+
+static struct drm_plane_helper_funcs ma35_plane_helper_funcs = {
+	.atomic_check		= ma35_plane_atomic_check,
+	.atomic_update		= ma35_plane_atomic_update,
+	.atomic_disable		= ma35_plane_atomic_disable,
+};
+
+static struct drm_plane_helper_funcs ma35_cursor_plane_helper_funcs = {
+	.atomic_check		= ma35_cursor_plane_atomic_check,
+	.atomic_update		= ma35_cursor_plane_atomic_update,
+	.atomic_disable		= ma35_cursor_plane_atomic_disable,
+	.atomic_async_check		= ma35_cursor_plane_atomic_async_check,
+	.atomic_async_update	= ma35_cursor_plane_atomic_async_update,
+};
+
+static int ma35_plane_set_property(struct drm_plane *drm_plane,
+	struct drm_plane_state *state, struct drm_property *property,
+	uint64_t val)
+{
+	if (property == drm_plane->hotspot_x_property)
+		state->hotspot_x = val;
+	else if (property == drm_plane->hotspot_y_property)
+		state->hotspot_y = val;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static int ma35_plane_get_property(struct drm_plane *drm_plane,
+	const struct drm_plane_state *state, struct drm_property *property,
+	uint64_t *val)
+{
+	if (property == drm_plane->hotspot_x_property)
+		*val = state->hotspot_x;
+	else if (property == drm_plane->hotspot_y_property)
+		*val = state->hotspot_y;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct drm_plane_funcs ma35_plane_funcs = {
+	.update_plane		= drm_atomic_helper_update_plane,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.destroy		= drm_plane_cleanup,
+	.reset			= drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+	.atomic_set_property = ma35_plane_set_property,
+	.atomic_get_property = ma35_plane_get_property,
+};
+
+static int ma35_layer_create_properties(struct ma35_drm *priv,
+				      struct ma35_layer *layer)
+{
+	struct drm_plane *drm_plane = &layer->drm_plane;
+	int ret = 0;
+
+	if (ma35_plane_properties[drm_plane->type].alpha) {
+		drm_plane_create_alpha_property(drm_plane);
+		ret = drm_plane_create_blend_mode_property(drm_plane, BIT(DRM_MODE_BLEND_PREMULTI) |
+							 BIT(DRM_MODE_BLEND_COVERAGE));
+		if (ret)
+			return ret;
+	}
+
+	return ret;
+}
+
+struct ma35_layer *ma35_layer_get_from_type(struct ma35_drm *priv, enum drm_plane_type type)
+{
+	struct ma35_layer *layer;
+	struct drm_plane *drm_plane;
+
+	list_for_each_entry(layer, &priv->layers_list, list) {
+		drm_plane = &layer->drm_plane;
+		if (drm_plane->type == type)
+			return layer;
+	}
+
+	return NULL;
+}
+
+static int ma35_layer_create(struct ma35_drm *priv,
+			      struct device_node *of_node, u32 index,
+			      enum drm_plane_type type)
+{
+	struct drm_device *drm_dev = &priv->drm_dev;
+	struct device *dev = drm_dev->dev;
+	struct ma35_layer *layer;
+	int ret;
+
+	layer = drmm_kzalloc(drm_dev, sizeof(*layer), GFP_KERNEL);
+	if (!layer) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	layer->of_node = of_node;
+
+	if (type == DRM_PLANE_TYPE_CURSOR) {
+		ret = drm_universal_plane_init(drm_dev, &layer->drm_plane,
+			1 << MA35_DEFAULT_CRTC_ID,
+			&ma35_plane_funcs, ma35_cursor_formats,
+			ARRAY_SIZE(ma35_cursor_formats), NULL, type, NULL);
+		if (ret) {
+			drm_err(drm_dev, "Failed to initialize layer plane\n");
+			return ret;
+		}
+
+		drm_plane_helper_add(&layer->drm_plane, &ma35_cursor_plane_helper_funcs);
+	} else {
+		ret = drm_universal_plane_init(drm_dev, &layer->drm_plane,
+			1 << MA35_DEFAULT_CRTC_ID,
+			&ma35_plane_funcs, ma35_layer_formats,
+			ARRAY_SIZE(ma35_layer_formats), NULL, type, NULL);
+		if (ret) {
+			drm_err(drm_dev, "Failed to initialize layer plane\n");
+			return ret;
+		}
+
+		drm_plane_helper_add(&layer->drm_plane, &ma35_plane_helper_funcs);
+	}
+
+	if (ma35_layer_create_properties(priv, layer))
+		drm_warn(drm_dev, "Failed to create properties for layer #%d\n",
+			index);
+
+	drm_plane_create_zpos_immutable_property(&layer->drm_plane, index);
+
+	list_add_tail(&layer->list, &priv->layers_list);
+
+	return 0;
+
+error:
+	if (layer) {
+		list_del(&layer->list);
+		devm_kfree(dev, layer);
+	}
+
+	return ret;
+}
+
+void ma35_overlay_attach_crtc(struct ma35_drm *priv)
+{
+	uint32_t possible_crtcs = drm_crtc_mask(&priv->crtc->drm_crtc);
+	struct ma35_layer *layer;
+	struct drm_plane *drm_plane;
+
+	list_for_each_entry(layer, &priv->layers_list, list) {
+		drm_plane = &layer->drm_plane;
+		if (drm_plane->type != DRM_PLANE_TYPE_OVERLAY)
+			continue;
+
+		drm_plane->possible_crtcs = possible_crtcs;
+	}
+}
+
+int ma35_plane_init(struct ma35_drm *priv)
+{
+	struct drm_device *drm_dev = &priv->drm_dev;
+	int ret;
+
+	ret = ma35_layer_create(priv, NULL, 0, DRM_PLANE_TYPE_PRIMARY);
+	if (ret) {
+		drm_err(drm_dev, "Failed to create primary layer\n");
+		return ret;
+	}
+
+	ret = ma35_layer_create(priv, NULL, 1, DRM_PLANE_TYPE_OVERLAY);
+	if (ret) {
+		drm_err(drm_dev, "Failed to create overlay layer\n");
+		return ret;
+	}
+
+	ret = ma35_layer_create(priv, NULL, 2, DRM_PLANE_TYPE_CURSOR);
+	if (ret) {
+		drm_err(drm_dev, "Failed to create cursor layer\n");
+		return ret;
+	}
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/nuvoton/ma35_plane.h b/drivers/gpu/drm/nuvoton/ma35_plane.h
new file mode 100644
index 000000000000..0f80e348fb7b
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/ma35_plane.h
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Nuvoton DRM driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#ifndef _MA35_LAYER_H_
+#define _MA35_LAYER_H_
+
+#include <linux/bitfield.h>
+#include <linux/of.h>
+#include <linux/types.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_property.h>
+
+#define MA35_MAX_PLANES	3
+
+struct ma35_drm;
+
+struct ma35_plane_property {
+	u32 fb_addr[MA35_MAX_PLANES];
+	u32 fb_stride[MA35_MAX_PLANES];
+	bool alpha;
+};
+
+struct ma35_layer {
+	struct drm_plane drm_plane;
+	struct list_head list;
+	struct device_node *of_node;
+	phys_addr_t fb_base[MA35_MAX_PLANES];
+};
+
+enum ma35_format_enum {
+	MA35_FORMAT_X4R4G4B4,	// DRM_FORMAT_XRGB4444
+	MA35_FORMAT_A4R4G4B4,	// DRM_FORMAT_ARGB4444
+	MA35_FORMAT_X1R5G5B5,	// DRM_FORMAT_XRGB1555
+	MA35_FORMAT_A1R5G5B5,	// DRM_FORMAT_ARGB1555
+	MA35_FORMAT_R5G6B5,		// DRM_FORMAT_RGB565
+	MA35_FORMAT_X8R8G8B8,	// DRM_FORMAT_XRGB8888
+	MA35_FORMAT_A8R8G8B8,	// DRM_FORMAT_ARGB8888
+	MA35_FORMAT_YUY2,		// YUV422, DRM_FORMAT_YUYV
+	MA35_FORMAT_UYVY,		// YUV422, DRM_FORMAT_UYVY
+	MA35_FORMAT_INDEX8,
+	MA35_FORMAT_MONOCHROME,
+	MA35_FORMAT_YV12,		// YUV420, DRM_FORMAT_YVU420
+	MA35_FORMAT_A8,
+	MA35_FORMAT_NV12,		// YUV420, DRM_FORMAT_NV12
+	MA35_FORMAT_NV16,		// YUV422, DRM_FORMAT_NV16
+	MA35_FORMAT_RG16,
+	MA35_FORMAT_R8,
+	MA35_FORMAT_NV12_10BIT,
+	MA35_FORMAT_A2R10G10B10, // DRM_FORMAT_ARGB2101010
+	MA35_FORMAT_NV16_10BIT,
+	MA35_FORMAT_INDEX1,
+	MA35_FORMAT_INDEX2,
+	MA35_FORMAT_INDEX4,
+	MA35_FORMAT_P010,		// YUV420, DRM_FORMAT_P010
+	MA35_FORMAT_NV12_10BIT_L1,
+	MA35_FORMAT_NV16_10BIT_L1,
+};
+
+#define MA35_ALPHA_BLEND_DISABLE		BIT(1)
+
+#define MA35_ALPHA_BLEND_ONE		1
+#define MA35_ALPHA_BLEND_INVERSED	3
+#define MA35_SRC_BLENDING_MODE		GENMASK(7, 5)
+#define MA35_DST_BLENDING_MODE		GENMASK(14, 12)
+#define MA35_BLEND_MODE_SRC_OVER \
+	(FIELD_PREP(MA35_SRC_BLENDING_MODE, MA35_ALPHA_BLEND_ONE) | \
+	FIELD_PREP(MA35_DST_BLENDING_MODE, MA35_ALPHA_BLEND_INVERSED))
+#define MA35_SRC_ALPHA_FACTOR_EN		BIT(8)
+
+#define MA35_CURSOR_SIZE		32
+#define MA35_CURSOR_DEPTH		24
+
+enum ma35_cursor_formats_enum {
+	MA35_CURSOR_FORMAT_DISABLE,
+	MA35_CURSOR_FORMAT_MASKED,
+	MA35_CURSOR_FORMAT_A8R8G8B8,
+};
+
+#define MA35_CURSOR_FORMAT_MASK		GENMASK(1, 0)
+
+#define MA35_CURSOR_HOTSPOT_X_MASK	GENMASK(20, 16)
+#define MA35_CURSOR_HOTSPOT_Y_MASK	GENMASK(12, 8)
+#define MA35_CURSOR_FORMAT_MASK		GENMASK(1, 0)
+#define MA35_CURSOR_OWNER_MASK		BIT(4)
+#define MA35_CURSOR_X_MASK		GENMASK(14, 0)
+#define MA35_CURSOR_Y_MASK		GENMASK(30, 16)
+
+#define MA35_PRIMARY_ENABLE		BIT(0)
+#define MA35_PRIMARY_GAMMA		BIT(2)
+#define MA35_PRIMARY_RESET		BIT(4)
+#define MA35_PRIMARY_CLEAR		BIT(8)
+#define MA35_PRIMARY_FORMAT_MASK		GENMASK(31, 26)
+
+#define MA35_OVERLAY_ENABLE		BIT(24)
+#define MA35_OVERLAY_CLEAR		BIT(25)
+#define MA35_OVERLAY_FORMAT_MASK	GENMASK(21, 16)
+
+#define MA35_LAYER_FB_HEIGHT	GENMASK(29, 15)
+#define MA35_LAYER_FB_WIDTH		GENMASK(14, 0)
+
+#define MA35_OVERLAY_POSITION_Y_MASK	MA35_LAYER_FB_HEIGHT
+#define MA35_OVERLAY_POSITION_X_MASK	MA35_LAYER_FB_WIDTH
+
+struct ma35_layer *ma35_layer_get_from_type(struct ma35_drm *priv,
+							enum drm_plane_type type);
+void ma35_overlay_attach_crtc(struct ma35_drm *priv);
+int ma35_plane_init(struct ma35_drm *priv);
+
+#endif
diff --git a/drivers/gpu/drm/nuvoton/ma35_regs.h b/drivers/gpu/drm/nuvoton/ma35_regs.h
new file mode 100644
index 000000000000..0f4a7a13e7d8
--- /dev/null
+++ b/drivers/gpu/drm/nuvoton/ma35_regs.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Nuvoton DRM driver
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#ifndef _MA35_REGS_H_
+#define _MA35_REGS_H_
+
+#define MA35_FRAMEBUFFER_CONFIG                   0x1518
+#define MA35_FRAMEBUFFER_ADDRESS                  0x1400
+#define MA35_FRAMEBUFFER_STRIDE                   0x1408
+#define MA35_HDISPLAY                             0x1430
+#define MA35_HSYNC                                0x1438
+#define MA35_VDISPLAY                             0x1440
+#define MA35_VSYNC                                0x1448
+#define MA35_PANEL_CONFIG                         0x1418
+#define MA35_DPI_CONFIG                           0x14B8
+#define MA35_CURSOR_ADDRESS                       0x146C
+#define MA35_CURSOR_CONFIG                        0x1468
+#define MA35_CURSOR_LOCATION                      0x1470
+#define MA35_CURSOR_BACKGROUND                    0x1474
+#define MA35_CURSOR_FOREGROUND                    0x1478
+#define MA35_FRAMEBUFFER_UPLANAR_ADDRESS          0x1530
+#define MA35_FRAMEBUFFER_VPLANAR_ADDRESS          0x1538
+#define MA35_FRAMEBUFFER_USTRIDE                  0x1800
+#define MA35_FRAMEBUFFER_VSTRIDE                  0x1808
+#define MA35_INDEXCOLOR_TABLEINDEX                0x1818
+#define MA35_INDEXCOLOR_TABLEDATA                 0x1820
+#define MA35_FRAMEBUFFER_SIZE                     0x1810
+#define MA35_FRAMEBUFFER_SCALEFACTORX             0x1828
+#define MA35_FRAMEBUFFER_SCALEFACTORY             0x1830
+#define MA35_FRAMEBUFFER_SCALEFCONFIG             0x1520
+#define MA35_HORIFILTER_KERNELINDEX               0x1838
+#define MA35_HORIFILTER_KERNEL                    0x1A00
+#define MA35_VERTIFILTER_KERNELINDEX              0x1A08
+#define MA35_VERTIFILTER_KERNEL                   0x1A10
+#define MA35_FRAMEBUFFER_INITIALOFFSET            0x1A20
+#define MA35_FRAMEBUFFER_COLORKEY                 0x1508
+#define MA35_FRAMEBUFFER_COLORHIGHKEY             0x1510
+#define MA35_FRAMEBUFFER_BGCOLOR                  0x1528
+#define MA35_FRAMEBUFFER_CLEARVALUE               0x1A18
+#define MA35_DISPLAY_INTRENABLE                   0x1480
+#define MA35_INT_STATE                            0x147C
+#define MA35_PANEL_DEST_ADDRESS                   0x14F0
+#define MA35_MEM_DEST_ADDRESS                     0x14E8
+#define MA35_DEST_CONFIG                          0x14F8
+#define MA35_DEST_STRIDE                          0x1500
+#define MA35_DBI_CONFIG                           0x1488
+#define MA35_AQHICLOCKCONTROL                     0x0000
+#define MA35_OVERLAY_CONFIG                       0x1540
+#define MA35_OVERLAY_STRIDE                       0x1600
+#define MA35_OVERLAY_USTRIDE                      0x18C0
+#define MA35_OVERLAY_VSTRIDE                      0x1900
+#define MA35_OVERLAY_TL                           0x1640
+#define MA35_OVERLAY_BR                           0x1680
+#define MA35_OVERLAY_ALPHA_BLEND_CONFIG           0x1580
+#define MA35_OVERLAY_SRC_GLOBAL_COLOR             0x16C0
+#define MA35_OVERLAY_DST_GLOBAL_COLOR             0x1700
+#define MA35_OVERLAY_CLEAR_VALUE                  0x1940
+#define MA35_OVERLAY_SIZE                         0x17C0
+#define MA35_OVERLAY_COLOR_KEY                    0x1740
+#define MA35_OVERLAY_COLOR_KEY_HIGH               0x1780
+#define MA35_OVERLAY_ADDRESS                      0x15C0
+#define MA35_OVERLAY_UPLANAR_ADDRESS              0x1840
+#define MA35_OVERLAY_VPLANAR_ADDRESS              0x1880
+#define MA35_OVERLAY_SCALE_CONFIG                 0x1C00
+#define MA35_OVERLAY_SCALE_FACTOR_X               0x1A40
+#define MA35_OVERLAY_SCALE_FACTOR_Y               0x1A80
+#define MA35_OVERLAY_HORI_FILTER_KERNEL_INDEX     0x1AC0
+#define MA35_OVERLAY_HORI_FILTER_KERNEL           0x1B00
+#define MA35_OVERLAY_VERTI_FILTER_KERNEL_INDEX    0x1B40
+#define MA35_OVERLAY_VERTI_FILTER_KERNEL          0x1B80
+#define MA35_OVERLAY_INITIAL_OFFSET               0x1BC0
+#define MA35_GAMMA_EX_INDEX                       0x1CF0
+#define MA35_GAMMA_EX_DATA                        0x1CF8
+#define MA35_GAMMA_EX_ONE_DATA                    0x1D80
+#define MA35_GAMMA_INDEX                          0x1458
+#define MA35_GAMMA_DATA                           0x1460
+#define MA35_DISPLAY_DITHER_TABLE_LOW             0x1420
+#define MA35_DISPLAY_DITHER_TABLE_HIGH            0x1428
+#define MA35_DISPLAY_DITHER_CONFIG                0x1410
+#define MA35_DISPLAY_CURRENT_LOCATION             0x1450
+
+#endif
-- 
2.43.0


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

* Re: [PATCH v2 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding
  2026-01-29  4:05 ` [PATCH v2 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding Joey Lu
@ 2026-02-05 13:18   ` Krzysztof Kozlowski
  2026-02-06  7:05     ` Joey Lu
  0 siblings, 1 reply; 17+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-05 13:18 UTC (permalink / raw)
  To: Joey Lu
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, ychuang3, schung, yclu4, linux-arm-kernel,
	dri-devel, devicetree, linux-kernel

On Thu, Jan 29, 2026 at 12:05:30PM +0800, Joey Lu wrote:
> +
> +  clocks:
> +    items:
> +      - description: DCU Gate clock for register access
> +      - description: DCU Pixel clock for display timing
> +
> +  clock-names:
> +    items:
> +      - const: dcu_gate

That's bus or apb

> +      - const: dcup_div

That's pixel

Use descriptive names of functions here. Not your clock controller. It
is completely irrelevant whether you provide here gate or div or mux or
whatever else. If by any chance in new design you put here mux, not div,
then completely new binding?


Best regards,
Krzysztof


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

* Re: [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver
  2026-01-29  4:05 ` [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver Joey Lu
@ 2026-02-05 13:22   ` Krzysztof Kozlowski
  2026-02-06  7:23     ` Joey Lu
  2026-02-06 15:09   ` Icenowy Zheng
  1 sibling, 1 reply; 17+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-05 13:22 UTC (permalink / raw)
  To: Joey Lu
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, ychuang3, schung, yclu4, linux-arm-kernel,
	dri-devel, devicetree, linux-kernel

On Thu, Jan 29, 2026 at 12:05:32PM +0800, Joey Lu wrote:
> Add DRM driver support for the Display Control Unit (DCU)
> found in Nuvoton MA35D1 SoCs.
> 
> Signed-off-by: Joey Lu <a0987203069@gmail.com>
> ---
>  drivers/gpu/drm/Kconfig                  |   1 +
>  drivers/gpu/drm/Makefile                 |   1 +
>  drivers/gpu/drm/nuvoton/Kconfig          |  21 +
>  drivers/gpu/drm/nuvoton/Makefile         |   7 +
>  drivers/gpu/drm/nuvoton/ma35_crtc.c      | 372 ++++++++++++++
>  drivers/gpu/drm/nuvoton/ma35_crtc.h      |  67 +++
>  drivers/gpu/drm/nuvoton/ma35_drm.c       | 371 ++++++++++++++
>  drivers/gpu/drm/nuvoton/ma35_drm.h       |  48 ++
>  drivers/gpu/drm/nuvoton/ma35_interface.c | 193 ++++++++
>  drivers/gpu/drm/nuvoton/ma35_interface.h |  30 ++
>  drivers/gpu/drm/nuvoton/ma35_plane.c     | 603 +++++++++++++++++++++++
>  drivers/gpu/drm/nuvoton/ma35_plane.h     | 115 +++++
>  drivers/gpu/drm/nuvoton/ma35_regs.h      |  88 ++++

No maintainers? Why would we want to take unmaintained code?

> +static void ma35_mode_fini(struct ma35_drm *priv)
> +{
> +	struct drm_device *drm_dev = &priv->drm_dev;
> +
> +	drm_kms_helper_poll_fini(drm_dev);
> +}
> +
> +static int ma35_clocks_prepare(struct ma35_drm *priv)
> +{
> +	struct drm_device *drm_dev = &priv->drm_dev;
> +	struct device *dev = drm_dev->dev;
> +	int ret;
> +
> +	priv->dcuclk = devm_clk_get(dev, "dcu_gate");
> +	if (IS_ERR(priv->dcuclk)) {
> +		dev_err(dev, "Failed to get display core clock\n");

Don't spam logs on defers. Syntax is in entire probe path: return
dev_err_probe

> +		return PTR_ERR(priv->dcuclk);
> +	}
> +
> +	ret = clk_prepare_enable(priv->dcuclk);

Why this cannot be devm_clk_get_enabled?

> +	if (ret) {
> +		dev_err(dev, "Failed to enable display core clock\n");
> +		return ret;
> +	}
> +
> +	priv->dcupclk = devm_clk_get(dev, "dcup_div");
> +	if (IS_ERR(priv->dcupclk)) {
> +		dev_err(dev, "Failed to get display pixel clock\n");
> +		return PTR_ERR(priv->dcupclk);
> +	}
> +
> +	ret = clk_prepare_enable(priv->dcupclk);
> +	if (ret) {
> +		dev_err(dev, "Failed to enable display pixel clock\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ma35_clocks_unprepare(struct ma35_drm *priv)
> +{
> +	struct clk **clocks[] = {
> +		&priv->dcuclk,
> +		&priv->dcupclk,
> +	};
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(clocks); i++) {
> +		if (!*clocks[i])
> +			continue;
> +
> +		clk_disable_unprepare(*clocks[i]);
> +		*clocks[i] = NULL;

Huh, pretty complicated and pointless code. This should be devm and bulk
API...

> +	}
> +
> +	return 0;
> +}
> +
> +static int ma35_drm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct ma35_drm *priv;
> +	struct drm_device *drm_dev;
> +	void __iomem *base;
> +	struct regmap *regmap = NULL;
> +	int irq;
> +	int ret;
> +
> +	ret = of_reserved_mem_device_init(dev);
> +	if (ret && ret != -ENODEV) {
> +		dev_err(dev, "Failed to get optional reserved memory: %d\n", ret);
> +		return ret;
> +	}
> +
> +	base = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(base)) {
> +		dev_err(dev, "Failed to map I/O base\n");

Why aren't you using dev_err_probe?

> +		ret = PTR_ERR(base);
> +		goto error_reserved_mem;
> +	}
> +	regmap = devm_regmap_init_mmio(dev, base, &ma35_drm_regmap_config);
> +	if (IS_ERR(regmap)) {
> +		dev_err(dev, "Failed to create regmap for I/O\n");
> +		ret = PTR_ERR(regmap);
> +		goto error_reserved_mem;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		ret = -ENODEV;
> +		goto error_reserved_mem;
> +	}
> +
> +	priv = devm_drm_dev_alloc(dev, &ma35_drm_driver,
> +				     struct ma35_drm, drm_dev);
> +	if (IS_ERR(priv)) {
> +		ret = PTR_ERR(priv);
> +		goto error_reserved_mem;
> +	}
> +
> +	platform_set_drvdata(pdev, priv);
> +	drm_dev = &priv->drm_dev;
> +	priv->regmap = regmap;
> +	INIT_LIST_HEAD(&priv->layers_list);
> +
> +	ret = ma35_clocks_prepare(priv);
> +	if (ret) {
> +		drm_err(drm_dev, "Failed to prepare clocks\n");

Why do you print error twice? Once in the function, second time here?

> +		goto error_reserved_mem;
> +	}
> +
> +	ret = devm_request_irq(dev, irq, ma35_drm_irq_handler, 0,
> +			       dev_name(dev), priv);
> +	if (ret) {
> +		drm_err(drm_dev, "Failed to request IRQ\n");
> +		goto error_clocks;
> +	}
> +
> +	/* modeset */
> +	ret = ma35_mode_init(priv);
> +	if (ret) {
> +		drm_err(drm_dev, "Failed to initialize KMS\n");
> +		goto error_clocks;
> +	}
> +
> +	/* plane */
> +	ret = ma35_plane_init(priv);
> +	if (ret) {
> +		drm_err(drm_dev, "Failed to initialize layers\n");
> +		goto error_clocks;
> +	}
> +
> +	/* crtc */
> +	ret = ma35_crtc_init(priv);
> +	if (ret) {
> +		drm_err(drm_dev, "Failed to initialize CRTC\n");
> +		goto error_clocks;
> +	}
> +
> +	/* interface */
> +	ret = ma35_interface_init(priv);
> +	if (ret) {
> +		if (ret != -EPROBE_DEFER)
> +			drm_err(drm_dev, "Failed to initialize interface\n");
> +
> +		goto error_clocks;
> +	}
> +
> +	drm_mode_config_reset(drm_dev);
> +
> +	ret = drm_dev_register(drm_dev, 0);
> +	if (ret) {
> +		drm_err(drm_dev, "Failed to register DRM device\n");
> +		goto error_mode;
> +	}
> +
> +	drm_client_setup(drm_dev, NULL);

Best regards,
Krzysztof


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

* Re: [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support
  2026-01-29  4:05 ` [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support Joey Lu
@ 2026-02-05 13:23   ` Krzysztof Kozlowski
  2026-02-06  7:12     ` Joey Lu
  0 siblings, 1 reply; 17+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-05 13:23 UTC (permalink / raw)
  To: Joey Lu
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, ychuang3, schung, yclu4, linux-arm-kernel,
	dri-devel, devicetree, linux-kernel

On Thu, Jan 29, 2026 at 12:05:31PM +0800, Joey Lu wrote:
>  &uart0 {
> @@ -129,3 +165,23 @@ &uart16 {
>  	pinctrl-0 = <&pinctrl_uart16>;
>  	status = "okay";
>  };
> +
> +&panel {
> +	port {
> +		panel_in: endpoint@0 {
> +			remote-endpoint = <&dpi_out>;
> +		};
> +	};
> +};
> +
> +&display {

What sort of ordering rule is followed in Nuvoton? Why is it different
than DTS coding style? Why do you choose other style?

> +	pinctrl-names = "default";
> +	pinctrl-0 = <&pinctrl_display>;
> +	status = "okay";
> +
> +	port {
> +		dpi_out: endpoint@0 {
> +			remote-endpoint = <&panel_in>;
> +		};
> +	};
> +};
> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> index e51b98f5bdce..7d9d077f12b2 100644
> --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> @@ -379,5 +379,19 @@ uart16: serial@40880000 {
>  			clocks = <&clk UART16_GATE>;
>  			status = "disabled";
>  		};
> +
> +		panel: panel {

No, there is no way your SoC has a panel.

Don't add fake stuff to your DTS.

Best regards,
Krzysztof


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

* Re: [PATCH v2 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding
  2026-02-05 13:18   ` Krzysztof Kozlowski
@ 2026-02-06  7:05     ` Joey Lu
  0 siblings, 0 replies; 17+ messages in thread
From: Joey Lu @ 2026-02-06  7:05 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, ychuang3, schung, yclu4, linux-arm-kernel,
	dri-devel, devicetree, linux-kernel


On 2/5/2026 9:18 PM, Krzysztof Kozlowski wrote:
> On Thu, Jan 29, 2026 at 12:05:30PM +0800, Joey Lu wrote:
>> +
>> +  clocks:
>> +    items:
>> +      - description: DCU Gate clock for register access
>> +      - description: DCU Pixel clock for display timing
>> +
>> +  clock-names:
>> +    items:
>> +      - const: dcu_gate
> That's bus or apb
>
>> +      - const: dcup_div
> That's pixel
>
> Use descriptive names of functions here. Not your clock controller. It
> is completely irrelevant whether you provide here gate or div or mux or
> whatever else. If by any chance in new design you put here mux, not div,
> then completely new binding?
>
>
> Best regards,
> Krzysztof

I'll use functional clock names bus & pixel instead.

Joey


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

* Re: [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support
  2026-02-05 13:23   ` Krzysztof Kozlowski
@ 2026-02-06  7:12     ` Joey Lu
  2026-02-06  8:56       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 17+ messages in thread
From: Joey Lu @ 2026-02-06  7:12 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, ychuang3, schung, yclu4, linux-arm-kernel,
	dri-devel, devicetree, linux-kernel


On 2/5/2026 9:23 PM, Krzysztof Kozlowski wrote:
> On Thu, Jan 29, 2026 at 12:05:31PM +0800, Joey Lu wrote:
>>   &uart0 {
>> @@ -129,3 +165,23 @@ &uart16 {
>>   	pinctrl-0 = <&pinctrl_uart16>;
>>   	status = "okay";
>>   };
>> +
>> +&panel {
>> +	port {
>> +		panel_in: endpoint@0 {
>> +			remote-endpoint = <&dpi_out>;
>> +		};
>> +	};
>> +};
>> +
>> +&display {
> What sort of ordering rule is followed in Nuvoton? Why is it different
> than DTS coding style? Why do you choose other style?
>
>> +	pinctrl-names = "default";
>> +	pinctrl-0 = <&pinctrl_display>;
>> +	status = "okay";
>> +
>> +	port {
>> +		dpi_out: endpoint@0 {
>> +			remote-endpoint = <&panel_in>;
>> +		};
>> +	};
>> +};
>> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>> index e51b98f5bdce..7d9d077f12b2 100644
>> --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>> @@ -379,5 +379,19 @@ uart16: serial@40880000 {
>>   			clocks = <&clk UART16_GATE>;
>>   			status = "disabled";
>>   		};
>> +
>> +		panel: panel {
> No, there is no way your SoC has a panel.
>
> Don't add fake stuff to your DTS.
>
> Best regards,
> Krzysztof

I'll move panel nodes out of dtsi into board dts.

Joey


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

* Re: [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver
  2026-02-05 13:22   ` Krzysztof Kozlowski
@ 2026-02-06  7:23     ` Joey Lu
  0 siblings, 0 replies; 17+ messages in thread
From: Joey Lu @ 2026-02-06  7:23 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, ychuang3, schung, yclu4, linux-arm-kernel,
	dri-devel, devicetree, linux-kernel


On 2/5/2026 9:22 PM, Krzysztof Kozlowski wrote:
> On Thu, Jan 29, 2026 at 12:05:32PM +0800, Joey Lu wrote:
>> Add DRM driver support for the Display Control Unit (DCU)
>> found in Nuvoton MA35D1 SoCs.
>>
>> Signed-off-by: Joey Lu <a0987203069@gmail.com>
>> ---
>>   drivers/gpu/drm/Kconfig                  |   1 +
>>   drivers/gpu/drm/Makefile                 |   1 +
>>   drivers/gpu/drm/nuvoton/Kconfig          |  21 +
>>   drivers/gpu/drm/nuvoton/Makefile         |   7 +
>>   drivers/gpu/drm/nuvoton/ma35_crtc.c      | 372 ++++++++++++++
>>   drivers/gpu/drm/nuvoton/ma35_crtc.h      |  67 +++
>>   drivers/gpu/drm/nuvoton/ma35_drm.c       | 371 ++++++++++++++
>>   drivers/gpu/drm/nuvoton/ma35_drm.h       |  48 ++
>>   drivers/gpu/drm/nuvoton/ma35_interface.c | 193 ++++++++
>>   drivers/gpu/drm/nuvoton/ma35_interface.h |  30 ++
>>   drivers/gpu/drm/nuvoton/ma35_plane.c     | 603 +++++++++++++++++++++++
>>   drivers/gpu/drm/nuvoton/ma35_plane.h     | 115 +++++
>>   drivers/gpu/drm/nuvoton/ma35_regs.h      |  88 ++++
> No maintainers? Why would we want to take unmaintained code?
I'll add an entry in MAINTAINERS file.
>
>> +static void ma35_mode_fini(struct ma35_drm *priv)
>> +{
>> +	struct drm_device *drm_dev = &priv->drm_dev;
>> +
>> +	drm_kms_helper_poll_fini(drm_dev);
>> +}
>> +
>> +static int ma35_clocks_prepare(struct ma35_drm *priv)
>> +{
>> +	struct drm_device *drm_dev = &priv->drm_dev;
>> +	struct device *dev = drm_dev->dev;
>> +	int ret;
>> +
>> +	priv->dcuclk = devm_clk_get(dev, "dcu_gate");
>> +	if (IS_ERR(priv->dcuclk)) {
>> +		dev_err(dev, "Failed to get display core clock\n");
> Don't spam logs on defers. Syntax is in entire probe path: return
> dev_err_probe
>
>> +		return PTR_ERR(priv->dcuclk);
>> +	}
>> +
>> +	ret = clk_prepare_enable(priv->dcuclk);
> Why this cannot be devm_clk_get_enabled?
I'll fix it.
>> +	if (ret) {
>> +		dev_err(dev, "Failed to enable display core clock\n");
>> +		return ret;
>> +	}
>> +
>> +	priv->dcupclk = devm_clk_get(dev, "dcup_div");
>> +	if (IS_ERR(priv->dcupclk)) {
>> +		dev_err(dev, "Failed to get display pixel clock\n");
>> +		return PTR_ERR(priv->dcupclk);
>> +	}
>> +
>> +	ret = clk_prepare_enable(priv->dcupclk);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to enable display pixel clock\n");
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int ma35_clocks_unprepare(struct ma35_drm *priv)
>> +{
>> +	struct clk **clocks[] = {
>> +		&priv->dcuclk,
>> +		&priv->dcupclk,
>> +	};
>> +	unsigned int i;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(clocks); i++) {
>> +		if (!*clocks[i])
>> +			continue;
>> +
>> +		clk_disable_unprepare(*clocks[i]);
>> +		*clocks[i] = NULL;
> Huh, pretty complicated and pointless code. This should be devm and bulk
> API...
I'll use memory safe helpers instead.
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int ma35_drm_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct ma35_drm *priv;
>> +	struct drm_device *drm_dev;
>> +	void __iomem *base;
>> +	struct regmap *regmap = NULL;
>> +	int irq;
>> +	int ret;
>> +
>> +	ret = of_reserved_mem_device_init(dev);
>> +	if (ret && ret != -ENODEV) {
>> +		dev_err(dev, "Failed to get optional reserved memory: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	base = devm_platform_ioremap_resource(pdev, 0);
>> +	if (IS_ERR(base)) {
>> +		dev_err(dev, "Failed to map I/O base\n");
> Why aren't you using dev_err_probe?
>
>> +		ret = PTR_ERR(base);
>> +		goto error_reserved_mem;
>> +	}
>> +	regmap = devm_regmap_init_mmio(dev, base, &ma35_drm_regmap_config);
>> +	if (IS_ERR(regmap)) {
>> +		dev_err(dev, "Failed to create regmap for I/O\n");
>> +		ret = PTR_ERR(regmap);
>> +		goto error_reserved_mem;
>> +	}
>> +
>> +	irq = platform_get_irq(pdev, 0);
>> +	if (irq < 0) {
>> +		ret = -ENODEV;
>> +		goto error_reserved_mem;
>> +	}
>> +
>> +	priv = devm_drm_dev_alloc(dev, &ma35_drm_driver,
>> +				     struct ma35_drm, drm_dev);
>> +	if (IS_ERR(priv)) {
>> +		ret = PTR_ERR(priv);
>> +		goto error_reserved_mem;
>> +	}
>> +
>> +	platform_set_drvdata(pdev, priv);
>> +	drm_dev = &priv->drm_dev;
>> +	priv->regmap = regmap;
>> +	INIT_LIST_HEAD(&priv->layers_list);
>> +
>> +	ret = ma35_clocks_prepare(priv);
>> +	if (ret) {
>> +		drm_err(drm_dev, "Failed to prepare clocks\n");
> Why do you print error twice? Once in the function, second time here?
>
>> +		goto error_reserved_mem;
>> +	}
>> +
>> +	ret = devm_request_irq(dev, irq, ma35_drm_irq_handler, 0,
>> +			       dev_name(dev), priv);
>> +	if (ret) {
>> +		drm_err(drm_dev, "Failed to request IRQ\n");
>> +		goto error_clocks;
>> +	}
>> +
>> +	/* modeset */
>> +	ret = ma35_mode_init(priv);
>> +	if (ret) {
>> +		drm_err(drm_dev, "Failed to initialize KMS\n");
>> +		goto error_clocks;
>> +	}
>> +
>> +	/* plane */
>> +	ret = ma35_plane_init(priv);
>> +	if (ret) {
>> +		drm_err(drm_dev, "Failed to initialize layers\n");
>> +		goto error_clocks;
>> +	}
>> +
>> +	/* crtc */
>> +	ret = ma35_crtc_init(priv);
>> +	if (ret) {
>> +		drm_err(drm_dev, "Failed to initialize CRTC\n");
>> +		goto error_clocks;
>> +	}
>> +
>> +	/* interface */
>> +	ret = ma35_interface_init(priv);
>> +	if (ret) {
>> +		if (ret != -EPROBE_DEFER)
>> +			drm_err(drm_dev, "Failed to initialize interface\n");
>> +
>> +		goto error_clocks;
>> +	}
>> +
>> +	drm_mode_config_reset(drm_dev);
>> +
>> +	ret = drm_dev_register(drm_dev, 0);
>> +	if (ret) {
>> +		drm_err(drm_dev, "Failed to register DRM device\n");
>> +		goto error_mode;
>> +	}
>> +
>> +	drm_client_setup(drm_dev, NULL);
> Best regards,
> Krzysztof

I'll  return raw error codes and let probe wrap them with dev_err_probe().

Thanks for the review.

Best regards,

Joey


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

* Re: [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support
  2026-02-06  7:12     ` Joey Lu
@ 2026-02-06  8:56       ` Krzysztof Kozlowski
  2026-02-06  9:21         ` Joey Lu
  0 siblings, 1 reply; 17+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-06  8:56 UTC (permalink / raw)
  To: Joey Lu
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, ychuang3, schung, yclu4, linux-arm-kernel,
	dri-devel, devicetree, linux-kernel

On 06/02/2026 08:12, Joey Lu wrote:
> 
> On 2/5/2026 9:23 PM, Krzysztof Kozlowski wrote:
>> On Thu, Jan 29, 2026 at 12:05:31PM +0800, Joey Lu wrote:
>>>   &uart0 {
>>> @@ -129,3 +165,23 @@ &uart16 {
>>>   	pinctrl-0 = <&pinctrl_uart16>;
>>>   	status = "okay";
>>>   };
>>> +
>>> +&panel {
>>> +	port {
>>> +		panel_in: endpoint@0 {
>>> +			remote-endpoint = <&dpi_out>;
>>> +		};
>>> +	};
>>> +};
>>> +
>>> +&display {
>> What sort of ordering rule is followed in Nuvoton? Why is it different
>> than DTS coding style? Why do you choose other style?
>>
>>> +	pinctrl-names = "default";
>>> +	pinctrl-0 = <&pinctrl_display>;
>>> +	status = "okay";
>>> +
>>> +	port {
>>> +		dpi_out: endpoint@0 {
>>> +			remote-endpoint = <&panel_in>;
>>> +		};
>>> +	};
>>> +};
>>> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>>> index e51b98f5bdce..7d9d077f12b2 100644
>>> --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>>> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>>> @@ -379,5 +379,19 @@ uart16: serial@40880000 {
>>>   			clocks = <&clk UART16_GATE>;
>>>   			status = "disabled";
>>>   		};
>>> +
>>> +		panel: panel {
>> No, there is no way your SoC has a panel.
>>
>> Don't add fake stuff to your DTS.
>>
>> Best regards,
>> Krzysztof
> 
> I'll move panel nodes out of dtsi into board dts.

You did not respond to several comments in total, it's fine, but if you
just ignored them, then it would not be fine.


Best regards,
Krzysztof

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

* Re: [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support
  2026-02-06  8:56       ` Krzysztof Kozlowski
@ 2026-02-06  9:21         ` Joey Lu
  0 siblings, 0 replies; 17+ messages in thread
From: Joey Lu @ 2026-02-06  9:21 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, ychuang3, schung, yclu4, linux-arm-kernel,
	dri-devel, devicetree, linux-kernel


On 2/6/2026 4:56 PM, Krzysztof Kozlowski wrote:
> On 06/02/2026 08:12, Joey Lu wrote:
>> On 2/5/2026 9:23 PM, Krzysztof Kozlowski wrote:
>>> On Thu, Jan 29, 2026 at 12:05:31PM +0800, Joey Lu wrote:
>>>>    &uart0 {
>>>> @@ -129,3 +165,23 @@ &uart16 {
>>>>    	pinctrl-0 = <&pinctrl_uart16>;
>>>>    	status = "okay";
>>>>    };
>>>> +
>>>> +&panel {
>>>> +	port {
>>>> +		panel_in: endpoint@0 {
>>>> +			remote-endpoint = <&dpi_out>;
>>>> +		};
>>>> +	};
>>>> +};
>>>> +
>>>> +&display {
>>> What sort of ordering rule is followed in Nuvoton? Why is it different
>>> than DTS coding style? Why do you choose other style?
>>>
>>>> +	pinctrl-names = "default";
>>>> +	pinctrl-0 = <&pinctrl_display>;
>>>> +	status = "okay";
>>>> +
>>>> +	port {
>>>> +		dpi_out: endpoint@0 {
>>>> +			remote-endpoint = <&panel_in>;
>>>> +		};
>>>> +	};
>>>> +};
>>>> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>>>> index e51b98f5bdce..7d9d077f12b2 100644
>>>> --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>>>> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>>>> @@ -379,5 +379,19 @@ uart16: serial@40880000 {
>>>>    			clocks = <&clk UART16_GATE>;
>>>>    			status = "disabled";
>>>>    		};
>>>> +
>>>> +		panel: panel {
>>> No, there is no way your SoC has a panel.
>>>
>>> Don't add fake stuff to your DTS.
>>>
>>> Best regards,
>>> Krzysztof
>> I'll move panel nodes out of dtsi into board dts.
> You did not respond to several comments in total, it's fine, but if you
> just ignored them, then it would not be fine.
>
>
> Best regards,
> Krzysztof
Thanks for the review. I've gone through all comments carefully.

Some points were addressed directly in the next revision,so I grouped a 
few replies together since the original code will no longer appear after 
the update.

I will also restructure the DTS so the panel is defined under the root 
node to follow the standard hierarchy and ordering conventions.

For remaining items that need discussion, I'll reply inline one by one.

Joey


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

* Re: [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver
  2026-01-29  4:05 ` [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver Joey Lu
  2026-02-05 13:22   ` Krzysztof Kozlowski
@ 2026-02-06 15:09   ` Icenowy Zheng
  2026-02-09  8:45     ` Joey Lu
  1 sibling, 1 reply; 17+ messages in thread
From: Icenowy Zheng @ 2026-02-06 15:09 UTC (permalink / raw)
  To: Joey Lu, airlied, simona, maarten.lankhorst, mripard, tzimmermann,
	robh, krzk+dt, conor+dt
  Cc: ychuang3, schung, yclu4, linux-arm-kernel, dri-devel, devicetree,
	linux-kernel

在 2026-01-29星期四的 12:05 +0800,Joey Lu写道:
> ========== 8< ============
> +#endif
> diff --git a/drivers/gpu/drm/nuvoton/ma35_regs.h
> b/drivers/gpu/drm/nuvoton/ma35_regs.h
> new file mode 100644
> index 000000000000..0f4a7a13e7d8
> --- /dev/null
> +++ b/drivers/gpu/drm/nuvoton/ma35_regs.h
> @@ -0,0 +1,88 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Nuvoton DRM driver
> + *
> + * Copyright (C) 2026 Nuvoton Technology Corp.
> + *
> + * Author: Joey Lu <a0987203069@gmail.com>
> + */
> +
> +#ifndef _MA35_REGS_H_
> +#define _MA35_REGS_H_
> +
> +#define MA35_FRAMEBUFFER_CONFIG                   0x1518

Please check my Verisilicon DC8200 driver, which is already part of
drm-misc-next now.

The display controller here seems to be a earlier one from
Verisilicon.it looks like a DC8000, or maybe a more earlier one?

> +#define MA35_FRAMEBUFFER_ADDRESS                  0x1400
> +#define MA35_FRAMEBUFFER_STRIDE                   0x1408
> +#define MA35_HDISPLAY                             0x1430
> +#define MA35_HSYNC                                0x1438
> +#define MA35_VDISPLAY                             0x1440
> +#define MA35_VSYNC                                0x1448
> +#define MA35_PANEL_CONFIG                         0x1418
> +#define MA35_DPI_CONFIG                           0x14B8
> +#define MA35_CURSOR_ADDRESS                       0x146C
> +#define MA35_CURSOR_CONFIG                        0x1468
> +#define MA35_CURSOR_LOCATION                      0x1470
> +#define MA35_CURSOR_BACKGROUND                    0x1474
> +#define MA35_CURSOR_FOREGROUND                    0x1478
> +#define MA35_FRAMEBUFFER_UPLANAR_ADDRESS          0x1530
> +#define MA35_FRAMEBUFFER_VPLANAR_ADDRESS          0x1538
> +#define MA35_FRAMEBUFFER_USTRIDE                  0x1800
> +#define MA35_FRAMEBUFFER_VSTRIDE                  0x1808
> +#define MA35_INDEXCOLOR_TABLEINDEX                0x1818
> +#define MA35_INDEXCOLOR_TABLEDATA                 0x1820
> +#define MA35_FRAMEBUFFER_SIZE                     0x1810
> +#define MA35_FRAMEBUFFER_SCALEFACTORX             0x1828
> +#define MA35_FRAMEBUFFER_SCALEFACTORY             0x1830
> +#define MA35_FRAMEBUFFER_SCALEFCONFIG             0x1520
> +#define MA35_HORIFILTER_KERNELINDEX               0x1838
> +#define MA35_HORIFILTER_KERNEL                    0x1A00
> +#define MA35_VERTIFILTER_KERNELINDEX              0x1A08
> +#define MA35_VERTIFILTER_KERNEL                   0x1A10
> +#define MA35_FRAMEBUFFER_INITIALOFFSET            0x1A20
> +#define MA35_FRAMEBUFFER_COLORKEY                 0x1508
> +#define MA35_FRAMEBUFFER_COLORHIGHKEY             0x1510
> +#define MA35_FRAMEBUFFER_BGCOLOR                  0x1528
> +#define MA35_FRAMEBUFFER_CLEARVALUE               0x1A18
> +#define MA35_DISPLAY_INTRENABLE                   0x1480
> +#define MA35_INT_STATE                            0x147C
> +#define MA35_PANEL_DEST_ADDRESS                   0x14F0
> +#define MA35_MEM_DEST_ADDRESS                     0x14E8
> +#define MA35_DEST_CONFIG                          0x14F8
> +#define MA35_DEST_STRIDE                          0x1500
> +#define MA35_DBI_CONFIG                           0x1488
> +#define MA35_AQHICLOCKCONTROL                     0x0000
> +#define MA35_OVERLAY_CONFIG                       0x1540
> +#define MA35_OVERLAY_STRIDE                       0x1600
> +#define MA35_OVERLAY_USTRIDE                      0x18C0
> +#define MA35_OVERLAY_VSTRIDE                      0x1900
> +#define MA35_OVERLAY_TL                           0x1640
> +#define MA35_OVERLAY_BR                           0x1680
> +#define MA35_OVERLAY_ALPHA_BLEND_CONFIG           0x1580
> +#define MA35_OVERLAY_SRC_GLOBAL_COLOR             0x16C0
> +#define MA35_OVERLAY_DST_GLOBAL_COLOR             0x1700
> +#define MA35_OVERLAY_CLEAR_VALUE                  0x1940
> +#define MA35_OVERLAY_SIZE                         0x17C0
> +#define MA35_OVERLAY_COLOR_KEY                    0x1740
> +#define MA35_OVERLAY_COLOR_KEY_HIGH               0x1780
> +#define MA35_OVERLAY_ADDRESS                      0x15C0
> +#define MA35_OVERLAY_UPLANAR_ADDRESS              0x1840
> +#define MA35_OVERLAY_VPLANAR_ADDRESS              0x1880
> +#define MA35_OVERLAY_SCALE_CONFIG                 0x1C00
> +#define MA35_OVERLAY_SCALE_FACTOR_X               0x1A40
> +#define MA35_OVERLAY_SCALE_FACTOR_Y               0x1A80
> +#define MA35_OVERLAY_HORI_FILTER_KERNEL_INDEX     0x1AC0
> +#define MA35_OVERLAY_HORI_FILTER_KERNEL           0x1B00
> +#define MA35_OVERLAY_VERTI_FILTER_KERNEL_INDEX    0x1B40
> +#define MA35_OVERLAY_VERTI_FILTER_KERNEL          0x1B80
> +#define MA35_OVERLAY_INITIAL_OFFSET               0x1BC0
> +#define MA35_GAMMA_EX_INDEX                       0x1CF0
> +#define MA35_GAMMA_EX_DATA                        0x1CF8
> +#define MA35_GAMMA_EX_ONE_DATA                    0x1D80
> +#define MA35_GAMMA_INDEX                          0x1458
> +#define MA35_GAMMA_DATA                           0x1460
> +#define MA35_DISPLAY_DITHER_TABLE_LOW             0x1420
> +#define MA35_DISPLAY_DITHER_TABLE_HIGH            0x1428
> +#define MA35_DISPLAY_DITHER_CONFIG                0x1410
> +#define MA35_DISPLAY_CURRENT_LOCATION             0x1450
> +
> +#endif


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

* Re: [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver
  2026-02-06 15:09   ` Icenowy Zheng
@ 2026-02-09  8:45     ` Joey Lu
  2026-02-10 12:02       ` Icenowy Zheng
  0 siblings, 1 reply; 17+ messages in thread
From: Joey Lu @ 2026-02-09  8:45 UTC (permalink / raw)
  To: Icenowy Zheng, airlied, simona, maarten.lankhorst, mripard,
	tzimmermann, robh, krzk+dt, conor+dt
  Cc: ychuang3, schung, yclu4, linux-arm-kernel, dri-devel, devicetree,
	linux-kernel


On 2/6/2026 11:09 PM, Icenowy Zheng wrote:
> 在 2026-01-29星期四的 12:05 +0800,Joey Lu写道:
>> ========== 8< ============
>> +#endif
>> diff --git a/drivers/gpu/drm/nuvoton/ma35_regs.h
>> b/drivers/gpu/drm/nuvoton/ma35_regs.h
>> new file mode 100644
>> index 000000000000..0f4a7a13e7d8
>> --- /dev/null
>> +++ b/drivers/gpu/drm/nuvoton/ma35_regs.h
>> @@ -0,0 +1,88 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Nuvoton DRM driver
>> + *
>> + * Copyright (C) 2026 Nuvoton Technology Corp.
>> + *
>> + * Author: Joey Lu <a0987203069@gmail.com>
>> + */
>> +
>> +#ifndef _MA35_REGS_H_
>> +#define _MA35_REGS_H_
>> +
>> +#define MA35_FRAMEBUFFER_CONFIG                   0x1518
> Please check my Verisilicon DC8200 driver, which is already part of
> drm-misc-next now.
>
> The display controller here seems to be a earlier one from
> Verisilicon.it looks like a DC8000, or maybe a more earlier one?

The DCU is a Vivante DCUltra IP rather than a DC8000 series.

It's an earlier generation display controller and was customized for 
Nuvoton, so it doesn't have a public model ID.

Because of that lineage, parts of the register layout and functionality 
remain similar to older DC IPs.

Please refer to MA35D1 datasheet for more details.

>> +#define MA35_FRAMEBUFFER_ADDRESS                  0x1400
>> +#define MA35_FRAMEBUFFER_STRIDE                   0x1408
>> +#define MA35_HDISPLAY                             0x1430
>> +#define MA35_HSYNC                                0x1438
>> +#define MA35_VDISPLAY                             0x1440
>> +#define MA35_VSYNC                                0x1448
>> +#define MA35_PANEL_CONFIG                         0x1418
>> +#define MA35_DPI_CONFIG                           0x14B8
>> +#define MA35_CURSOR_ADDRESS                       0x146C
>> +#define MA35_CURSOR_CONFIG                        0x1468
>> +#define MA35_CURSOR_LOCATION                      0x1470
>> +#define MA35_CURSOR_BACKGROUND                    0x1474
>> +#define MA35_CURSOR_FOREGROUND                    0x1478
>> +#define MA35_FRAMEBUFFER_UPLANAR_ADDRESS          0x1530
>> +#define MA35_FRAMEBUFFER_VPLANAR_ADDRESS          0x1538
>> +#define MA35_FRAMEBUFFER_USTRIDE                  0x1800
>> +#define MA35_FRAMEBUFFER_VSTRIDE                  0x1808
>> +#define MA35_INDEXCOLOR_TABLEINDEX                0x1818
>> +#define MA35_INDEXCOLOR_TABLEDATA                 0x1820
>> +#define MA35_FRAMEBUFFER_SIZE                     0x1810
>> +#define MA35_FRAMEBUFFER_SCALEFACTORX             0x1828
>> +#define MA35_FRAMEBUFFER_SCALEFACTORY             0x1830
>> +#define MA35_FRAMEBUFFER_SCALEFCONFIG             0x1520
>> +#define MA35_HORIFILTER_KERNELINDEX               0x1838
>> +#define MA35_HORIFILTER_KERNEL                    0x1A00
>> +#define MA35_VERTIFILTER_KERNELINDEX              0x1A08
>> +#define MA35_VERTIFILTER_KERNEL                   0x1A10
>> +#define MA35_FRAMEBUFFER_INITIALOFFSET            0x1A20
>> +#define MA35_FRAMEBUFFER_COLORKEY                 0x1508
>> +#define MA35_FRAMEBUFFER_COLORHIGHKEY             0x1510
>> +#define MA35_FRAMEBUFFER_BGCOLOR                  0x1528
>> +#define MA35_FRAMEBUFFER_CLEARVALUE               0x1A18
>> +#define MA35_DISPLAY_INTRENABLE                   0x1480
>> +#define MA35_INT_STATE                            0x147C
>> +#define MA35_PANEL_DEST_ADDRESS                   0x14F0
>> +#define MA35_MEM_DEST_ADDRESS                     0x14E8
>> +#define MA35_DEST_CONFIG                          0x14F8
>> +#define MA35_DEST_STRIDE                          0x1500
>> +#define MA35_DBI_CONFIG                           0x1488
>> +#define MA35_AQHICLOCKCONTROL                     0x0000
>> +#define MA35_OVERLAY_CONFIG                       0x1540
>> +#define MA35_OVERLAY_STRIDE                       0x1600
>> +#define MA35_OVERLAY_USTRIDE                      0x18C0
>> +#define MA35_OVERLAY_VSTRIDE                      0x1900
>> +#define MA35_OVERLAY_TL                           0x1640
>> +#define MA35_OVERLAY_BR                           0x1680
>> +#define MA35_OVERLAY_ALPHA_BLEND_CONFIG           0x1580
>> +#define MA35_OVERLAY_SRC_GLOBAL_COLOR             0x16C0
>> +#define MA35_OVERLAY_DST_GLOBAL_COLOR             0x1700
>> +#define MA35_OVERLAY_CLEAR_VALUE                  0x1940
>> +#define MA35_OVERLAY_SIZE                         0x17C0
>> +#define MA35_OVERLAY_COLOR_KEY                    0x1740
>> +#define MA35_OVERLAY_COLOR_KEY_HIGH               0x1780
>> +#define MA35_OVERLAY_ADDRESS                      0x15C0
>> +#define MA35_OVERLAY_UPLANAR_ADDRESS              0x1840
>> +#define MA35_OVERLAY_VPLANAR_ADDRESS              0x1880
>> +#define MA35_OVERLAY_SCALE_CONFIG                 0x1C00
>> +#define MA35_OVERLAY_SCALE_FACTOR_X               0x1A40
>> +#define MA35_OVERLAY_SCALE_FACTOR_Y               0x1A80
>> +#define MA35_OVERLAY_HORI_FILTER_KERNEL_INDEX     0x1AC0
>> +#define MA35_OVERLAY_HORI_FILTER_KERNEL           0x1B00
>> +#define MA35_OVERLAY_VERTI_FILTER_KERNEL_INDEX    0x1B40
>> +#define MA35_OVERLAY_VERTI_FILTER_KERNEL          0x1B80
>> +#define MA35_OVERLAY_INITIAL_OFFSET               0x1BC0
>> +#define MA35_GAMMA_EX_INDEX                       0x1CF0
>> +#define MA35_GAMMA_EX_DATA                        0x1CF8
>> +#define MA35_GAMMA_EX_ONE_DATA                    0x1D80
>> +#define MA35_GAMMA_INDEX                          0x1458
>> +#define MA35_GAMMA_DATA                           0x1460
>> +#define MA35_DISPLAY_DITHER_TABLE_LOW             0x1420
>> +#define MA35_DISPLAY_DITHER_TABLE_HIGH            0x1428
>> +#define MA35_DISPLAY_DITHER_CONFIG                0x1410
>> +#define MA35_DISPLAY_CURRENT_LOCATION             0x1450
>> +
>> +#endif

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

* Re: [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver
  2026-02-09  8:45     ` Joey Lu
@ 2026-02-10 12:02       ` Icenowy Zheng
  2026-03-03  8:15         ` Joey Lu
  0 siblings, 1 reply; 17+ messages in thread
From: Icenowy Zheng @ 2026-02-10 12:02 UTC (permalink / raw)
  To: Joey Lu, airlied, simona, maarten.lankhorst, mripard, tzimmermann,
	robh, krzk+dt, conor+dt
  Cc: ychuang3, schung, yclu4, linux-arm-kernel, dri-devel, devicetree,
	linux-kernel

在 2026-02-09星期一的 16:45 +0800,Joey Lu写道:
> 
> On 2/6/2026 11:09 PM, Icenowy Zheng wrote:
> > 在 2026-01-29星期四的 12:05 +0800,Joey Lu写道:
> > > ========== 8< ============
> > > +#endif
> > > diff --git a/drivers/gpu/drm/nuvoton/ma35_regs.h
> > > b/drivers/gpu/drm/nuvoton/ma35_regs.h
> > > new file mode 100644
> > > index 000000000000..0f4a7a13e7d8
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/nuvoton/ma35_regs.h
> > > @@ -0,0 +1,88 @@
> > > +/* SPDX-License-Identifier: GPL-2.0+ */
> > > +/*
> > > + * Nuvoton DRM driver
> > > + *
> > > + * Copyright (C) 2026 Nuvoton Technology Corp.
> > > + *
> > > + * Author: Joey Lu <a0987203069@gmail.com>
> > > + */
> > > +
> > > +#ifndef _MA35_REGS_H_
> > > +#define _MA35_REGS_H_
> > > +
> > > +#define MA35_FRAMEBUFFER_CONFIG                   0x1518
> > Please check my Verisilicon DC8200 driver, which is already part of
> > drm-misc-next now.
> > 
> > The display controller here seems to be a earlier one from
> > Verisilicon.it looks like a DC8000, or maybe a more earlier one?
> 
> The DCU is a Vivante DCUltra IP rather than a DC8000 series.
> 
> It's an earlier generation display controller and was customized for 
> Nuvoton, so it doesn't have a public model ID.
> 
> Because of that lineage, parts of the register layout and
> functionality 
> remain similar to older DC IPs.

The kernel seems to dislike different drivers for similar IPs.

> 
> Please refer to MA35D1 datasheet for more details.

I checked it, and the register definitions looks compatible with DC8000
registers at [1]. However no identification information is shown in the
manual.

I may get a MA35D1 board after the Lunar New Year (although it looks a
little expensive), is it easy to bring up mainline kernal on arbitary
MA35D1 boards?

[1]
https://github.com/milkv-megrez/rockos-u-boot/blob/c9221cf2fa77d39c0b241ab4b030c708e7ebe279/drivers/video/eswin/eswin_dc_reg.h

> 
> > > +#define MA35_FRAMEBUFFER_ADDRESS                  0x1400
> > > +#define MA35_FRAMEBUFFER_STRIDE                   0x1408
> > > +#define MA35_HDISPLAY                             0x1430
> > > +#define MA35_HSYNC                                0x1438
> > > +#define MA35_VDISPLAY                             0x1440
> > > +#define MA35_VSYNC                                0x1448
> > > +#define MA35_PANEL_CONFIG                         0x1418
> > > +#define MA35_DPI_CONFIG                           0x14B8
> > > +#define MA35_CURSOR_ADDRESS                       0x146C
> > > +#define MA35_CURSOR_CONFIG                        0x1468
> > > +#define MA35_CURSOR_LOCATION                      0x1470
> > > +#define MA35_CURSOR_BACKGROUND                    0x1474
> > > +#define MA35_CURSOR_FOREGROUND                    0x1478
> > > +#define MA35_FRAMEBUFFER_UPLANAR_ADDRESS          0x1530
> > > +#define MA35_FRAMEBUFFER_VPLANAR_ADDRESS          0x1538
> > > +#define MA35_FRAMEBUFFER_USTRIDE                  0x1800
> > > +#define MA35_FRAMEBUFFER_VSTRIDE                  0x1808
> > > +#define MA35_INDEXCOLOR_TABLEINDEX                0x1818
> > > +#define MA35_INDEXCOLOR_TABLEDATA                 0x1820
> > > +#define MA35_FRAMEBUFFER_SIZE                     0x1810
> > > +#define MA35_FRAMEBUFFER_SCALEFACTORX             0x1828
> > > +#define MA35_FRAMEBUFFER_SCALEFACTORY             0x1830
> > > +#define MA35_FRAMEBUFFER_SCALEFCONFIG             0x1520
> > > +#define MA35_HORIFILTER_KERNELINDEX               0x1838
> > > +#define MA35_HORIFILTER_KERNEL                    0x1A00
> > > +#define MA35_VERTIFILTER_KERNELINDEX              0x1A08
> > > +#define MA35_VERTIFILTER_KERNEL                   0x1A10
> > > +#define MA35_FRAMEBUFFER_INITIALOFFSET            0x1A20
> > > +#define MA35_FRAMEBUFFER_COLORKEY                 0x1508
> > > +#define MA35_FRAMEBUFFER_COLORHIGHKEY             0x1510
> > > +#define MA35_FRAMEBUFFER_BGCOLOR                  0x1528
> > > +#define MA35_FRAMEBUFFER_CLEARVALUE               0x1A18
> > > +#define MA35_DISPLAY_INTRENABLE                   0x1480
> > > +#define MA35_INT_STATE                            0x147C
> > > +#define MA35_PANEL_DEST_ADDRESS                   0x14F0
> > > +#define MA35_MEM_DEST_ADDRESS                     0x14E8
> > > +#define MA35_DEST_CONFIG                          0x14F8
> > > +#define MA35_DEST_STRIDE                          0x1500
> > > +#define MA35_DBI_CONFIG                           0x1488
> > > +#define MA35_AQHICLOCKCONTROL                     0x0000
> > > +#define MA35_OVERLAY_CONFIG                       0x1540
> > > +#define MA35_OVERLAY_STRIDE                       0x1600
> > > +#define MA35_OVERLAY_USTRIDE                      0x18C0
> > > +#define MA35_OVERLAY_VSTRIDE                      0x1900
> > > +#define MA35_OVERLAY_TL                           0x1640
> > > +#define MA35_OVERLAY_BR                           0x1680
> > > +#define MA35_OVERLAY_ALPHA_BLEND_CONFIG           0x1580
> > > +#define MA35_OVERLAY_SRC_GLOBAL_COLOR             0x16C0
> > > +#define MA35_OVERLAY_DST_GLOBAL_COLOR             0x1700
> > > +#define MA35_OVERLAY_CLEAR_VALUE                  0x1940
> > > +#define MA35_OVERLAY_SIZE                         0x17C0
> > > +#define MA35_OVERLAY_COLOR_KEY                    0x1740
> > > +#define MA35_OVERLAY_COLOR_KEY_HIGH               0x1780
> > > +#define MA35_OVERLAY_ADDRESS                      0x15C0
> > > +#define MA35_OVERLAY_UPLANAR_ADDRESS              0x1840
> > > +#define MA35_OVERLAY_VPLANAR_ADDRESS              0x1880
> > > +#define MA35_OVERLAY_SCALE_CONFIG                 0x1C00
> > > +#define MA35_OVERLAY_SCALE_FACTOR_X               0x1A40
> > > +#define MA35_OVERLAY_SCALE_FACTOR_Y               0x1A80
> > > +#define MA35_OVERLAY_HORI_FILTER_KERNEL_INDEX     0x1AC0
> > > +#define MA35_OVERLAY_HORI_FILTER_KERNEL           0x1B00
> > > +#define MA35_OVERLAY_VERTI_FILTER_KERNEL_INDEX    0x1B40
> > > +#define MA35_OVERLAY_VERTI_FILTER_KERNEL          0x1B80
> > > +#define MA35_OVERLAY_INITIAL_OFFSET               0x1BC0
> > > +#define MA35_GAMMA_EX_INDEX                       0x1CF0
> > > +#define MA35_GAMMA_EX_DATA                        0x1CF8
> > > +#define MA35_GAMMA_EX_ONE_DATA                    0x1D80
> > > +#define MA35_GAMMA_INDEX                          0x1458
> > > +#define MA35_GAMMA_DATA                           0x1460
> > > +#define MA35_DISPLAY_DITHER_TABLE_LOW             0x1420
> > > +#define MA35_DISPLAY_DITHER_TABLE_HIGH            0x1428
> > > +#define MA35_DISPLAY_DITHER_CONFIG                0x1410
> > > +#define MA35_DISPLAY_CURRENT_LOCATION             0x1450
> > > +
> > > +#endif


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

* Re: [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver
  2026-02-10 12:02       ` Icenowy Zheng
@ 2026-03-03  8:15         ` Joey Lu
  2026-03-08 15:20           ` Icenowy Zheng
  0 siblings, 1 reply; 17+ messages in thread
From: Joey Lu @ 2026-03-03  8:15 UTC (permalink / raw)
  To: Icenowy Zheng, airlied, simona, maarten.lankhorst, mripard,
	tzimmermann, robh, krzk+dt, conor+dt
  Cc: ychuang3, schung, yclu4, linux-arm-kernel, dri-devel, devicetree,
	linux-kernel


On 2/10/2026 8:02 PM, Icenowy Zheng wrote:
> 在 2026-02-09星期一的 16:45 +0800,Joey Lu写道:
>> On 2/6/2026 11:09 PM, Icenowy Zheng wrote:
>>> 在 2026-01-29星期四的 12:05 +0800,Joey Lu写道:
>>>> ========== 8< ============
>>>> +#endif
>>>> diff --git a/drivers/gpu/drm/nuvoton/ma35_regs.h
>>>> b/drivers/gpu/drm/nuvoton/ma35_regs.h
>>>> new file mode 100644
>>>> index 000000000000..0f4a7a13e7d8
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/nuvoton/ma35_regs.h
>>>> @@ -0,0 +1,88 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0+ */
>>>> +/*
>>>> + * Nuvoton DRM driver
>>>> + *
>>>> + * Copyright (C) 2026 Nuvoton Technology Corp.
>>>> + *
>>>> + * Author: Joey Lu <a0987203069@gmail.com>
>>>> + */
>>>> +
>>>> +#ifndef _MA35_REGS_H_
>>>> +#define _MA35_REGS_H_
>>>> +
>>>> +#define MA35_FRAMEBUFFER_CONFIG                   0x1518
>>> Please check my Verisilicon DC8200 driver, which is already part of
>>> drm-misc-next now.
>>>
>>> The display controller here seems to be a earlier one from
>>> Verisilicon.it looks like a DC8000, or maybe a more earlier one?
>> The DCU is a Vivante DCUltra IP rather than a DC8000 series.
>>
>> It's an earlier generation display controller and was customized for
>> Nuvoton, so it doesn't have a public model ID.
>>
>> Because of that lineage, parts of the register layout and
>> functionality
>> remain similar to older DC IPs.
> The kernel seems to dislike different drivers for similar IPs.
>
>> Please refer to MA35D1 datasheet for more details.
> I checked it, and the register definitions looks compatible with DC8000
> registers at [1]. However no identification information is shown in the
> manual.
>
> I may get a MA35D1 board after the Lunar New Year (although it looks a
> little expensive), is it easy to bring up mainline kernal on arbitary
> MA35D1 boards?
>
> [1]
> https://github.com/milkv-megrez/rockos-u-boot/blob/c9221cf2fa77d39c0b241ab4b030c708e7ebe279/drivers/video/eswin/eswin_dc_reg.h
Our team is actively working on upstreaming support for MA35D1. It is 
possible to bring up the mainline kernel on arbitrary MA35D1 boards, but 
for the best experience I strongly recommend using our Buildroot[1] or 
Yocto[2] tooling available on the official GitHub. These provide 
board-specific configurations and greatly simplify the process.

If you decide to get an MA35D1 board yourself, our Buildroot/Yocto setup 
will help you get started quickly and ensure you have the right patches 
and configurations in place.

Currently, the mainline kernel version is 6.6, which is sufficient for 
checking registers and basic functionality. For more complete feature 
support, our Buildroot/Yocto tooling remains the recommended path.

[1] https://github.com/OpenNuvoton/buildroot_2024

[2] https://github.com/OpenNuvoton/MA35D1_yocto-v5.0

Best regards,

Joey


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

* Re: [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver
  2026-03-03  8:15         ` Joey Lu
@ 2026-03-08 15:20           ` Icenowy Zheng
  0 siblings, 0 replies; 17+ messages in thread
From: Icenowy Zheng @ 2026-03-08 15:20 UTC (permalink / raw)
  To: Joey Lu, airlied, simona, maarten.lankhorst, mripard, tzimmermann,
	robh, krzk+dt, conor+dt
  Cc: ychuang3, schung, yclu4, linux-arm-kernel, dri-devel, devicetree,
	linux-kernel

在 2026-03-03二的 16:15 +0800,Joey Lu写道:
> 
> On 2/10/2026 8:02 PM, Icenowy Zheng wrote:
> > 在 2026-02-09星期一的 16:45 +0800,Joey Lu写道:
> > > On 2/6/2026 11:09 PM, Icenowy Zheng wrote:
> > > > 在 2026-01-29星期四的 12:05 +0800,Joey Lu写道:
> > > > > ========== 8< ============
> > > > > +#endif
> > > > > diff --git a/drivers/gpu/drm/nuvoton/ma35_regs.h
> > > > > b/drivers/gpu/drm/nuvoton/ma35_regs.h
> > > > > new file mode 100644
> > > > > index 000000000000..0f4a7a13e7d8
> > > > > --- /dev/null
> > > > > +++ b/drivers/gpu/drm/nuvoton/ma35_regs.h
> > > > > @@ -0,0 +1,88 @@
> > > > > +/* SPDX-License-Identifier: GPL-2.0+ */
> > > > > +/*
> > > > > + * Nuvoton DRM driver
> > > > > + *
> > > > > + * Copyright (C) 2026 Nuvoton Technology Corp.
> > > > > + *
> > > > > + * Author: Joey Lu <a0987203069@gmail.com>
> > > > > + */
> > > > > +
> > > > > +#ifndef _MA35_REGS_H_
> > > > > +#define _MA35_REGS_H_
> > > > > +
> > > > > +#define MA35_FRAMEBUFFER_CONFIG                   0x1518
> > > > Please check my Verisilicon DC8200 driver, which is already
> > > > part of
> > > > drm-misc-next now.
> > > > 
> > > > The display controller here seems to be a earlier one from
> > > > Verisilicon.it looks like a DC8000, or maybe a more earlier
> > > > one?
> > > The DCU is a Vivante DCUltra IP rather than a DC8000 series.
> > > 
> > > It's an earlier generation display controller and was customized
> > > for
> > > Nuvoton, so it doesn't have a public model ID.
> > > 
> > > Because of that lineage, parts of the register layout and
> > > functionality
> > > remain similar to older DC IPs.
> > The kernel seems to dislike different drivers for similar IPs.
> > 
> > > Please refer to MA35D1 datasheet for more details.
> > I checked it, and the register definitions looks compatible with
> > DC8000
> > registers at [1]. However no identification information is shown in
> > the
> > manual.

I brought up my MYIR MYD-LMA35 (with its stock OS yet), and I got the
following register value readings:

```
root@myd-lma35-emmc:~# ./busybox-armv7l devmem 0x40260020
0x00000000 # chip id
root@myd-lma35-emmc:~# ./busybox-armv7l devmem 0x40260024
0x00005560 # chip rev
root@myd-lma35-emmc:~# ./busybox-armv7l devmem 0x40260028
0x20200807 # date code
root@myd-lma35-emmc:~# ./busybox-armv7l devmem 0x40260030
0x00000305 # customer id
root@myd-lma35-emmc:~# ./busybox-armv7l devmem 0x40260098
0x0000000C # patch rev
root@myd-lma35-emmc:~# ./busybox-armv7l devmem 0x402600a4
0x00211110 # info
root@myd-lma35-emmc:~# ./busybox-armv7l devmem 0x402600a8
0x02000002 # product id
root@myd-lma35-emmc:~# ./busybox-armv7l devmem 0x402600e8
0x00000000
```

Most of these values look okay, although the chip id register reads out
0x0 (although this is weird, this matches how this IP is called --
DCUltraLite, without any number).

Decoding 0xA4 with the the document from Eswin results:
- DC is included (of course ;-) )
- Single AXI
- AHB interface
- SingleGPU configuration
- AXI bus width 128bit

Decoding 0xA8 (which seems to be a more new and detailed IP name
related register) results:
- Extra letter is "L-Lite" (well a little odd, because it should be  
"UL-UltraLite"?)
- Product number is 0
- Product type is DC

Looks like the only bad thing here is the IP's numerical name is 0, but
matching revision 5560 customer 305 should be okay for this IP?

(BTW the revision number is between two recognized numbers in DC8000
driver from Eswin [1] (5551/5701), and the two numbers in DC8000 driver
are strictly smaller than the two numbers in DC8200 driver from T-Head
[2] (5720/5721), indicates that it should be something similar to
DC8000, which matches my previous thoughts; note that these two drivers
both only match the 2 known numbers, so this IP is surely neither
DC8000 nor DC8200)

[1]
https://github.com/rockos-riscv/rockos-kernel/blob/rockos-v6.6.y/drivers/gpu/drm/eswin/es_dc_hw.c
[2]
https://github.com/revyos/th1520-linux-kernel/blob/th1520-lts/drivers/gpu/drm/verisilicon/vs_dc_hw.c

> > 
> > I may get a MA35D1 board after the Lunar New Year (although it
> > looks a
> > little expensive), is it easy to bring up mainline kernal on
> > arbitary
> > MA35D1 boards?
> > 
> > [1]
> > https://github.com/milkv-megrez/rockos-u-boot/blob/c9221cf2fa77d39c0b241ab4b030c708e7ebe279/drivers/video/eswin/eswin_dc_reg.h
> Our team is actively working on upstreaming support for MA35D1. It is
> possible to bring up the mainline kernel on arbitrary MA35D1 boards,
> but 
> for the best experience I strongly recommend using our Buildroot[1]
> or 
> Yocto[2] tooling available on the official GitHub. These provide 
> board-specific configurations and greatly simplify the process.
> 
> If you decide to get an MA35D1 board yourself, our Buildroot/Yocto
> setup 
> will help you get started quickly and ensure you have the right
> patches 
> and configurations in place.
> 
> Currently, the mainline kernel version is 6.6, which is sufficient
> for 
> checking registers and basic functionality. For more complete feature
> support, our Buildroot/Yocto tooling remains the recommended path.

Ah I mean upstream kernel here. But it looks like it has at least
decent support? (Well I think otherwise the display driver won't be
sent to mailing list either)

> 
> [1] https://github.com/OpenNuvoton/buildroot_2024
> 
> [2] https://github.com/OpenNuvoton/MA35D1_yocto-v5.0
> 
> Best regards,
> 
> Joey

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

end of thread, other threads:[~2026-03-08 15:21 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-29  4:05 [PATCH v2 0/3] drm: nuvoton: Add MA35D1 display controller support Joey Lu
2026-01-29  4:05 ` [PATCH v2 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding Joey Lu
2026-02-05 13:18   ` Krzysztof Kozlowski
2026-02-06  7:05     ` Joey Lu
2026-01-29  4:05 ` [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support Joey Lu
2026-02-05 13:23   ` Krzysztof Kozlowski
2026-02-06  7:12     ` Joey Lu
2026-02-06  8:56       ` Krzysztof Kozlowski
2026-02-06  9:21         ` Joey Lu
2026-01-29  4:05 ` [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver Joey Lu
2026-02-05 13:22   ` Krzysztof Kozlowski
2026-02-06  7:23     ` Joey Lu
2026-02-06 15:09   ` Icenowy Zheng
2026-02-09  8:45     ` Joey Lu
2026-02-10 12:02       ` Icenowy Zheng
2026-03-03  8:15         ` Joey Lu
2026-03-08 15:20           ` Icenowy Zheng

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