From nobody Sat Feb 7 18:21:00 2026 Received: from mail-pf1-f177.google.com (mail-pf1-f177.google.com [209.85.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C430C28CF4A for ; Thu, 29 Jan 2026 04:05:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769659543; cv=none; b=B8BYagogIwGw52xdvhSNMQp9/ZM4VLf9XHj1kkyddFAPbMFvGOQ2jhXHfCcvXCJ1bmtkqx2zRtJZ+rbIXGobjAymZaGE5dYet4W+2+9EKZgti4HqwlWYApz7vv0SQpsnBl6rWrntcnkBNO1rcPOTctipzh7YYKqe56cOtB1QFT4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769659543; c=relaxed/simple; bh=+vwNPlWwkjH6h++Xv+jKnOVO1xWHDFJPGzNnoOzcQYE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=iy5BcIGXydGfDvCM0v4HfG9kyb193xk33GILiPZqm6AE/za4K/GSMFvX2Mzrs1jJlI7v7xsgXmWjoJT+13El3zblMSqUlVwZtbRsuquiKvNIVr0se0cRNIt6tb0OcccV2rEM2vTyKMLFdD1M2WXzf9yVct0xz8r+UlQWIr8SnQ0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ZOXq4ZrX; arc=none smtp.client-ip=209.85.210.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ZOXq4ZrX" Received: by mail-pf1-f177.google.com with SMTP id d2e1a72fcca58-82361bcbd8fso298272b3a.0 for ; Wed, 28 Jan 2026 20:05:41 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769659541; x=1770264341; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=E68djXKo/Qb6+8kiZNd4FljhUOa17e5P51Qp4W2uY20=; b=ZOXq4ZrXkeD1qPHwhKpkPKMYhm4M+b1RyVLLk/rwl3O31xqEcyBzhVFNddDp/dcqlF wLjGRUvpPOkWTGOPOH1j9OQejdt+z23fEoyMaMO+HPlfHnISZZldNLwi6TkJ2t1IoPO4 p8aOWQkqt1qmxg3WLrBTUAigjptGhiA6+atP1t/JhobFqxOZx1df1LWZ7KBa69/pIGQ2 TtuvMez7pSN1SvnI5qanvH/nZS9d2t/Gg9ZaMy3VHHTl/vnlvFPM6hA3atyZjlu5lqmM DRA57s+M0X3VYqbaPq/u5K6NrEAlofsFyMxi5uuckfwxQZ7l7TdIHBwuzAOspQP6iuiM e2aQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769659541; x=1770264341; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=E68djXKo/Qb6+8kiZNd4FljhUOa17e5P51Qp4W2uY20=; b=syFm16wlvgzTVOaEhMppXY9xa2soJs6YJFhmI1LiLw08Hfk5AbGxnUZdQOVju3H/lc 91nUUmD08VLKfFVOprHffnDhZHprVI0H0yxPAPS8yeOvl7bcP06F4pDWl4wCMrZneMWg CANWn2kWlA5aqkP3menSwAqC2GbWkOTCojKu60fGFVmKgYXhknFVo3WjFMKdq1zdxN1c 4wWfNHCoWIJBspVhorL7uVBRGgK09OrjMJrN2WJVG/kSl4gbQ6/PR2ZYXQKKAw10+uce FYWrqPlBlLN+nT2ht1whomKzvczCccfRrSWY7S/YAKk0xdvJ0cEAx4BDP/2s88pcOOLO 8f6w== X-Forwarded-Encrypted: i=1; AJvYcCW+imySX7r06e4jXVEMGMJcQLjI5q7TrtgxKieAtmn3XjAbzw348oDb6mjaV6A36VtaaMIvZNjYxykPf/U=@vger.kernel.org X-Gm-Message-State: AOJu0YzIRlNwsxt6Yj28lm4OHJ+K2mJZ1BZcLtMMp8zjoUkKmj+kP5zr qY0k7duD/rLQ7UgMvHyaymcY5NgG+f0OX331KTv6bT9wTZj0gGJAsKLq X-Gm-Gg: AZuq6aKDFUqCLjEwRd47Xv3yLUN4oDcXkUpVQoZrwiBV1ZFo34gy/3FoXDNf+3d+C37 xcREX7/sTdiTlPJQqcRnmXBQ5AFDbAvjhR8MCxwIrE9xVDMLefr2zAx6aQv5bMknrGKHOzKb5uV AtFOSceNFjb765Uj9boEYps1a3t7aJN1PX0KQv5YoV3Vu5Vi2LPjhS61rYanABormd9912uN1zp 2a+REwKPX+Turza2XZiDAfNAtEBuN2ng/Ad0IN3e8ULo+ch3nNJNUNoGNg4VzpMX2kNOIO/qDNO ViVOQ9budFyWxPs95+ZZC9kYwXJGTRp1VQP6MOlZkXDj5LdeaVy48foVM/1izf24l5Z3s4O3jRC 2Y5vlweWjMKqBbuzw0g66XmgYwAzSDIB7w4IWzJAzbQX9mWNrHZce+0qpl1SxsAhnX0XE40/lYg TOkmlwFw7x27i/0JnHDixDbDW9AWflImnG5upk8Ob1zNFBg/hAic9b98TUgILc8B6xRvUlSY86 X-Received: by 2002:a05:6a00:94f5:b0:81f:3bcb:af2a with SMTP id d2e1a72fcca58-8236917be3fmr6772085b3a.26.1769659541008; Wed, 28 Jan 2026 20:05:41 -0800 (PST) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-82379c22672sm3857721b3a.51.2026.01.28.20.05.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Jan 2026 20:05:40 -0800 (PST) From: Joey Lu To: airlied@gmail.com, simona@ffwll.ch, maarten.lankhorst@linux.intel.com, mripard@kernel.org, tzimmermann@suse.de, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: ychuang3@nuvoton.com, schung@nuvoton.com, yclu4@nuvoton.com, a0987203069@gmail.com, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding Date: Thu, 29 Jan 2026 12:05:30 +0800 Message-ID: <20260129040532.382693-2-a0987203069@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260129040532.382693-1-a0987203069@gmail.com> References: <20260129040532.382693-1-a0987203069@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- .../bindings/display/nuvoton,ma35d1-dcu.yaml | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/nuvoton,ma35d= 1-dcu.yaml diff --git a/Documentation/devicetree/bindings/display/nuvoton,ma35d1-dcu.y= aml 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 + +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 + #include + #include + + display@40260000 { + compatible =3D "nuvoton,ma35d1-dcu"; + reg =3D <0x40260000 0x2000>; + interrupts =3D ; + clocks =3D <&clk DCU_GATE>, <&clk DCUP_DIV>; + clock-names =3D "dcu_gate", "dcup_div"; + resets =3D <&sys MA35D1_RESET_DISP>; + + port { + dpi_out: endpoint { + remote-endpoint =3D <&panel_in>; + }; + }; + }; --=20 2.43.0 From nobody Sat Feb 7 18:21:00 2026 Received: from mail-pf1-f173.google.com (mail-pf1-f173.google.com [209.85.210.173]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B06871A267 for ; Thu, 29 Jan 2026 04:05:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769659545; cv=none; b=kKjKnqB1XBt766n0Yg2qewQPFitYtQXVLMqpCjXfPuelQwLgVKEvICp23xTBukNSJ3DGL2FzQCEF6LZXYz0JU3pYvJG5KbSGSVdLJvPT4bGQ/rXNmfjyRh0y55CyRWD0VaESbE4MdgrsXQNVw7zHkkL6HRvrueuX864M7NdysPM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769659545; c=relaxed/simple; bh=wj4RosRxpeVyy3B37y+KwEEJeJ6jJ20W7R8CR4cuAmc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BWeHzFKcT6yUhNZnlhPLQ5IXPCsn2rA9nTKQ5YrIt7rUvQHH1hTbF8f6UPDaQzQg9aYMokUjHfIRWtcKq7ES1+JUlGdfFqzf35K2NLE0nmDjxBh7WCBGXDxfTimXpkDdPCbMhHKJvYyvLLQ/CMjcKeQpR+VT9TF/BufcWl24p1I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=aRVvjuDv; arc=none smtp.client-ip=209.85.210.173 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="aRVvjuDv" Received: by mail-pf1-f173.google.com with SMTP id d2e1a72fcca58-81e8a9d521dso251596b3a.2 for ; Wed, 28 Jan 2026 20:05:44 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769659544; x=1770264344; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qs+TGYRSq+syNgtmamomzVV57rWTcldKeZsL7QkXhHc=; b=aRVvjuDvnQd2PgQ54H2yiFf6lplYibMKaPlqa05tq/imU0fo9yLoFuqHUeKNCl2To3 dHCrdgwrTECUQotYUlhWWhMZ8etmKREIiVXcUIeMXe1wSFGAbjiQslfzcSd2xyhDlIkC w/Y+MhjvCDSBV+XLoRmgh9m6FONTcK0v+meRG0HWD35kdZaW7lcHHBZVVkBkU/nbkBsF 3+WFaM85z99++n2kx1dIyoCJ7s9bhHVuoJ8uR8LxgNSAZ/zHF/m4xKK8gKsJmH2Yrofe 3ACak2427g/AgC9k5rBtKKsBePN28pEfL4fRDh2dymAuFEkYdTKfB+FuOgTEnvG2L3iJ 8n4w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769659544; x=1770264344; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=qs+TGYRSq+syNgtmamomzVV57rWTcldKeZsL7QkXhHc=; b=OyYSfQ9LMfK9e0w2M09PIxEPfsYkQdmIiWnH++ve5DCKc1SBGXLC34Db04ttYc28Fn UPsScSFCSBrcMklWQpeIsq0PiagpW53W40+DEFUszOzkFWWsoWyl2UUVUw6Fc+7rs3G6 oRJgQtGoV10L79p0+S3a8TTR7qct6elUn9ew+z3zdRiOTZn53i2BDw8Sj0Pl23WgEF7g Z8ynVnyw/F5epZCgWSSAyKzfAFxrFJXAffIs0z7ZU9D9wIHrG9YEi9oHGJfKOGq/ni8l gKGICPmVn//51u/6lefUzXrv0OvYhoQyzfcOXPznqIQcm5ErHPgbPQ23RTd633UvpHtu xlew== X-Forwarded-Encrypted: i=1; AJvYcCUeOxXA12QgOG8qtTR9Squ3yUjQY3t/jPRLUQ8+XxZydb4CKDwUm8xEcDqHC1YGo+MHyYtrWcBYJxPEBd4=@vger.kernel.org X-Gm-Message-State: AOJu0YxG+TCwye9ir5fL+WNemEry7PJm+Twd7WNZoa2Xwr9vmodNK4dP r/syiHgnRvaTbgy4yXwkvTC37BSwxXX5nNaniCdNv3UA9/Rr6QvamdW7 X-Gm-Gg: AZuq6aJA/KX0nnK+uu1BONN6BJz1Gu6ALPT01WZOJjp/KEfJnCxzf34eIo32Q1F5Dw8 PZycdydju4UanCsDMoGUrW/FvLHKapwuEG+hOXkeg1jC3b0cfnON8sRfNVVo2fg0PU3mY+3DHet s1gcbx2+krtUgZg+HssVpjSnX3hHuLL+gWeHi0Twma0KvkYWk5GPdip6OGrp5TWtTfOpFVfR0H8 XXCWr1IoCp4dZWJ6CAQes9M0qLfpOh2FeY6ltKoYVSwEOwouM/Zf3uBmtl5hR6ff+kBt06m0Zry ctDPaIPyo0eNXuTCKkRglHRwgCB1DnKA6UhVm1m/yEWXF3PFLSZQz9sJwA8aTaJgWsM6pgNGKWJ x8LfvlrVEPF5p8zU/O9tZigC3rtEOPiKcscJRYwbKJNLoY6CY/fKQc30L6e7+wXfNd1+1V5uwPm /I7EB7e9l4RG0gCksk4rJx40sopa/+OYPh0n6tsYCa7tbmb0h7US/4GiMsylANMORr5IfOyBHY X-Received: by 2002:a05:6a00:14d3:b0:823:1cae:10bb with SMTP id d2e1a72fcca58-823692c322cmr7240139b3a.51.1769659543769; Wed, 28 Jan 2026 20:05:43 -0800 (PST) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-82379c22672sm3857721b3a.51.2026.01.28.20.05.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Jan 2026 20:05:43 -0800 (PST) From: Joey Lu To: airlied@gmail.com, simona@ffwll.ch, maarten.lankhorst@linux.intel.com, mripard@kernel.org, tzimmermann@suse.de, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: ychuang3@nuvoton.com, schung@nuvoton.com, yclu4@nuvoton.com, a0987203069@gmail.com, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 2/3] arm64: dts: nuvoton: ma35d1: add display controller support Date: Thu, 29 Jan 2026 12:05:31 +0800 Message-ID: <20260129040532.382693-3-a0987203069@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260129040532.382693-1-a0987203069@gmail.com> References: <20260129040532.382693-1-a0987203069@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- .../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/b= oot/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 =3D <1>; }; }; + + dcu { + pinctrl_display: display-pins { + nuvoton,pins =3D + <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 =3D <1>; + }; + }; }; =20 &uart0 { @@ -129,3 +165,23 @@ &uart16 { pinctrl-0 =3D <&pinctrl_uart16>; status =3D "okay"; }; + +&panel { + port { + panel_in: endpoint@0 { + remote-endpoint =3D <&dpi_out>; + }; + }; +}; + +&display { + pinctrl-names =3D "default"; + pinctrl-0 =3D <&pinctrl_display>; + status =3D "okay"; + + port { + dpi_out: endpoint@0 { + remote-endpoint =3D <&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 =3D <&clk UART16_GATE>; status =3D "disabled"; }; + + panel: panel { + compatible =3D "panel-dpi"; + }; + + display: display@40260000 { + compatible =3D "nuvoton,ma35d1-dcu"; + reg =3D <0x0 0x40260000 0x0 0x2000>; + interrupts =3D ; + clocks =3D <&clk DCU_GATE>, <&clk DCUP_DIV>; + clock-names =3D "dcu_gate", "dcup_div"; + resets =3D <&sys MA35D1_RESET_DISP>; + status =3D "disabled"; + }; }; }; --=20 2.43.0 From nobody Sat Feb 7 18:21:00 2026 Received: from mail-pf1-f171.google.com (mail-pf1-f171.google.com [209.85.210.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B55AC25F7A9 for ; Thu, 29 Jan 2026 04:05:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769659550; cv=none; b=Yk2QZLLoFn0KCveeA35OJnf5GN21Jom7YVcefglNdOcjBtgpUN6kpV1lX+9AB9g5FRnSJYRzLvEJYum24r8Rb76NqTgr8Y/b3gvIkEc7louXQxepXi+ZEeT8dJDnWmICZCvm0773LdFviebY9iW8XxEO2Ck7zpZZ5sKQ47n15lA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769659550; c=relaxed/simple; bh=/zSl3R6EdgcIRVMDln5mxLQGxBtiMt+UEZRi5GBKjO0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nYyteYXa7tmJK/qoZ0iN2MJJ39jF/etBRcBtrsmRmfiTAIOdH7e6jADuaQtxOYLwdW1JV9XukZRwlumi96uWSQ1MZ5oohvcWCh/Z7WqKnu7Pnwk81bDTWkfVu7Va7OiGiCO2K049SpZ9hR7MfxZT6PGJg+gw2EIBhe47JSIYo58= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=BcyJp4fB; arc=none smtp.client-ip=209.85.210.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="BcyJp4fB" Received: by mail-pf1-f171.google.com with SMTP id d2e1a72fcca58-82307c6902eso246618b3a.3 for ; Wed, 28 Jan 2026 20:05:47 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769659547; x=1770264347; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=2DNX5ATY40Y0jrFMGkeAQFy0L1LfkQYVsQiguwPlCVg=; b=BcyJp4fBscVV6oqV7pX7BZZefVgl70myqAOlvBlKizoinfPNP2Vrzq5KZlMvG5bb/S l7Qhz8nlQn0UWzwPeczPliLnowu4Ooikw8oePbbdIUA5YeZgooVYQ6fT3fKv+5lKlK7S PS/pCcwIG2CSWmIW7qy5yu48sFgEFGd+tqHYAhuK4M9Hoo1mzDHQzZhCU3S6dloLGToj X9xzalcT0lBRXSjj0qEF2HOseFy5hn7MTT8YpCyBeeTCq5PUifhNGoQW2pHxI3X7T3he f63fBfoOfkAa/oXfZzImNTmWRvb7GGiVnKIpi1TTLUGPnsufg95hySQ86Txa5xY+/G60 rO+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769659547; x=1770264347; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=2DNX5ATY40Y0jrFMGkeAQFy0L1LfkQYVsQiguwPlCVg=; b=ixMBS6kPKGMgFefjjvdMR3HQbGqi9VkpflKwOs5cmRkJD//Zy5pmDIUWqjTmUPDc1L OGHrCxX5C6m0+d/bH5HUijJ6W858dmP6FgRsdnFsVBLfWa4HGgmOSDA6+koQQ7oxETHw DYdEdl+/GL+JvwjtkNblvNbIDUfM7dxWBkzHcVMS2m1QQwFldJyLryaeBntQyEWnetwN VD2pA4lYb9zrCaM+ddQ96FaG8EJp0JpDHuj1VF892fsrfgMW5EGjp6kPhzBhZj7JfujO wy1qxMfs8A+xFYaXWRyPhsEa2X/+EwJF3FNZ9yF9BqPbM4SQtMrE9RtFMx1SoZ/lVcGf yuJQ== X-Forwarded-Encrypted: i=1; AJvYcCU/packyIKUt0+OkffL9RhWrx8FodhiTw9KxTFoLRrN28O6S6KLB1RXUvPu0Qj2N8GoHN+Pna8kXvC20Xw=@vger.kernel.org X-Gm-Message-State: AOJu0YzPbxQ4xZoSi4A9gy63NLM51l0rr/ZUgtibo+BMe2GuvwyG8czO YV/xoUlqWVAxuzSIzpoaurCKcIMjiHDgl95ZjDG1ukwUD302JGTxPCMu X-Gm-Gg: AZuq6aJSIemgqXDj5qwQYheILOOO/lbgENy4sNgUa2T5P3G3WFCtwdg/mdNRW6Xib/d RDSPyQGTnAMDFx1To0cWPpSRletW7HvQWM2RZren8ApvtiB/Dp9q2Z/6qHDV7WT+JK63/+nktBu 5dCrZXswfKElt10tOtc8azw+3sO9fD9hQxBqbIBWH0a7Uv6UYS8IKyi6V0YVHE7VJNeV+/dgIgF 2sDKfUOABFWROh3zdfRvTFlFWxYj+KF5TVff5ta83PyB4Pzk/vnJGp6R6fQ/D5NT1PfLFMVbkAB l4sIRhYfAcjFOULNPkFfckBv1SOVPHNAmtihw7JQmRqMx2McX1DYHVD2zQrt1spUz8sfH40IwWW U9h7NoZqmRzUl3wy4BhiFruKEihrvAQ8uoBZueVpZ3UfG0NXb3du1bMkbCurYTTK0qRnJh9AJPr SS7RMCpySWydYrC14nE1iy1v2E915KAAdwyBvBgs8b4kXYQwSIsQo/BvM0nQsDbY/pViLXu5Ax X-Received: by 2002:a05:6a00:a88c:b0:821:8ea4:4809 with SMTP id d2e1a72fcca58-8236928eb13mr6598057b3a.41.1769659546770; Wed, 28 Jan 2026 20:05:46 -0800 (PST) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-82379c22672sm3857721b3a.51.2026.01.28.20.05.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Jan 2026 20:05:46 -0800 (PST) From: Joey Lu To: airlied@gmail.com, simona@ffwll.ch, maarten.lankhorst@linux.intel.com, mripard@kernel.org, tzimmermann@suse.de, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: ychuang3@nuvoton.com, schung@nuvoton.com, yclu4@nuvoton.com, a0987203069@gmail.com, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 3/3] drm/nuvoton: add MA35D1 display controller driver Date: Thu, 29 Jan 2026 12:05:32 +0800 Message-ID: <20260129040532.382693-4-a0987203069@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260129040532.382693-1-a0987203069@gmail.com> References: <20260129040532.382693-1-a0987203069@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add DRM driver support for the Display Control Unit (DCU) found in Nuvoton MA35D1 SoCs. Signed-off-by: Joey Lu --- 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 +=3D solomon/ obj-$(CONFIG_DRM_SPRD) +=3D sprd/ obj-$(CONFIG_DRM_LOONGSON) +=3D loongson/ obj-$(CONFIG_DRM_POWERVR) +=3D imagination/ +obj-$(CONFIG_DRM_MA35) +=3D nuvoton/ =20 # Ensure drm headers are self-contained and pass kernel-doc hdrtest-files :=3D \ diff --git a/drivers/gpu/drm/nuvoton/Kconfig b/drivers/gpu/drm/nuvoton/Kcon= fig 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 (D= CU) + 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/Mak= efile new file mode 100644 index 000000000000..aac4113106b2 --- /dev/null +++ b/drivers/gpu/drm/nuvoton/Makefile @@ -0,0 +1,7 @@ +ma35-drm-y +=3D \ + ma35_drm.o \ + ma35_plane.o \ + ma35_crtc.o \ + ma35_interface.o + +obj-$(CONFIG_DRM_MA35) +=3D 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 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 =3D drm_crtc->dev; + struct drm_mode_config *mode_config =3D &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_conf= ig->min_width) + return MODE_BAD_HVALUE; + + if (mode->vdisplay > mode_config->max_height || mode->vdisplay < mode_con= fig->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 =3D ma35_drm(drm_crtc->dev); + struct drm_crtc_state *crtc_state =3D drm_atomic_get_new_crtc_state(state= , drm_crtc); + struct drm_display_mode *mode =3D &crtc_state->mode; + struct drm_display_mode *adjusted_mode =3D &crtc_state->adjusted_mode; + int clk_rate; + + if (mode->clock > MA35_MAX_PIXEL_CLK) + return MODE_CLOCK_HIGH; + + /* check rounded pixel clock */ + clk_rate =3D clk_round_rate(priv->dcupclk, mode->clock * 1000); + if (clk_rate <=3D 0) + return MODE_CLOCK_RANGE; + + adjusted_mode->clock =3D 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 =3D ma35_crtc(drm_crtc); + struct ma35_drm *priv =3D ma35_drm(drm_crtc->dev); + struct drm_crtc_state *new_state =3D + drm_atomic_get_new_crtc_state(state, drm_crtc); + struct drm_display_mode *mode =3D &new_state->adjusted_mode; + struct ma35_interface *interface =3D priv->interface; + struct drm_color_lut *lut; + int i, size; + u32 reg; + + /* Timings */ + reg =3D FIELD_PREP(MA35_DISPLAY_TOTAL_MASK, mode->htotal) | + FIELD_PREP(MA35_DISPLAY_ACTIVE_MASK, mode->hdisplay); + regmap_write(priv->regmap, MA35_HDISPLAY, reg); + + reg =3D 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 |=3D MA35_SYNC_POLARITY_BIT; + regmap_write(priv->regmap, MA35_HSYNC, reg); + + reg =3D FIELD_PREP(MA35_DISPLAY_TOTAL_MASK, mode->vtotal) | + FIELD_PREP(MA35_DISPLAY_ACTIVE_MASK, mode->vdisplay); + regmap_write(priv->regmap, MA35_VDISPLAY, reg); + + reg =3D 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 |=3D MA35_SYNC_POLARITY_BIT; + regmap_write(priv->regmap, MA35_VSYNC, reg); + + /* Signals */ + reg =3D MA35_PANEL_DATA_ENABLE_ENABLE | MA35_PANEL_DATA_ENABLE | + MA35_PANEL_DATA_CLOCK_ENABLE; + if (interface->bus_flags & DRM_BUS_FLAG_DE_LOW) + reg |=3D MA35_PANEL_DATA_ENABLE_POLARITY; + + if (interface->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) + reg |=3D 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 =3D new_state->gamma_lut->data; + size =3D new_state->gamma_lut->length / sizeof(struct drm_color_lut); + + for (i =3D 0; i < size; i++) { + regmap_write(priv->regmap, MA35_GAMMA_INDEX, i); + /* shift DRM gamma 16-bit values to 10-bit */ + reg =3D 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 =3D FIELD_PREP(MA35_DPI_FORMAT_MASK, crtc->dpi_format); + regmap_write(priv->regmap, MA35_DPI_CONFIG, reg); + + /* Dither */ + if (crtc->dither_enable) { + for (i =3D 0, reg =3D 0; i < MA35_DITHER_TABLE_ENTRY / 2; i++) + reg |=3D (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_ENABL= E); + } 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 =3D ma35_drm(drm_crtc->dev); + struct drm_device *drm_dev =3D 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 =3D 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) =3D=3D 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 =3D 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 =3D ma35_drm(drm_crtc->dev); + u32 reg; + + if (stime) + *stime =3D ktime_get(); + + regmap_read(priv->regmap, MA35_DISPLAY_CURRENT_LOCATION, ®); + + *hpos =3D FIELD_GET(MA35_DISPLAY_CURRENT_X, reg); + *vpos =3D FIELD_GET(MA35_DISPLAY_CURRENT_Y, reg); + + if (etime) + *etime =3D ktime_get(); + + return true; +} + +static const struct drm_crtc_helper_funcs ma35_crtc_helper_funcs =3D { + .mode_valid =3D ma35_crtc_mode_valid, + .atomic_check =3D ma35_crtc_atomic_check, + .atomic_enable =3D ma35_crtc_atomic_enable, + .atomic_disable =3D ma35_crtc_atomic_disable, + .atomic_flush =3D ma35_crtc_atomic_flush, + .get_scanout_position =3D ma35_crtc_get_scanout_position, +}; + +static int ma35_crtc_enable_vblank(struct drm_crtc *drm_crtc) +{ + struct ma35_drm *priv =3D 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 =3D 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 =3D 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 =3D ma35_drm(drm_crtc->dev); + u32 reg; + int i; + + if (size !=3D MA35_GAMMA_TABLE_SIZE) + return -EINVAL; + + regmap_write(priv->regmap, MA35_GAMMA_INDEX, 0); // auto increment + + for (i =3D 0; i < size; i++) { + reg =3D 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 =3D { + .reset =3D drm_atomic_helper_crtc_reset, + .destroy =3D drm_crtc_cleanup, + .set_config =3D drm_atomic_helper_set_config, + .page_flip =3D drm_atomic_helper_page_flip, + .atomic_duplicate_state =3D drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state =3D drm_atomic_helper_crtc_destroy_state, + .enable_vblank =3D ma35_crtc_enable_vblank, + .disable_vblank =3D ma35_crtc_disable_vblank, + .get_vblank_counter =3D ma35_crtc_get_vblank_counter, + .gamma_set =3D ma35_crtc_gamma_set, +}; + +void ma35_crtc_vblank_handler(struct ma35_drm *priv) +{ + struct ma35_crtc *crtc =3D 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 =3D priv->crtc; + int ret; + + crtc->dpi_format =3D MA35_DPI_D24; + crtc->dither_enable =3D true; + crtc->dither_depth =3D 8; + + ret =3D drm_mode_crtc_set_gamma_size(&crtc->drm_crtc, MA35_GAMMA_TABLE_SI= ZE); + if (ret) + return ret; + drm_crtc_enable_color_mgmt(&crtc->drm_crtc, 0, false, MA35_GAMMA_TABLE_SI= ZE); + + return 0; +} + +int ma35_crtc_init(struct ma35_drm *priv) +{ + struct drm_device *drm_dev =3D &priv->drm_dev; + struct ma35_crtc *crtc; + struct ma35_layer *layer_primary, *layer_cursor; + struct drm_plane *cursor_plane =3D NULL; + int ret; + + crtc =3D drmm_kzalloc(drm_dev, sizeof(*crtc), GFP_KERNEL); + if (!crtc) + return -ENOMEM; + + priv->crtc =3D crtc; + atomic_set(&crtc->vblank_counter, 0); + + layer_primary =3D 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 =3D ma35_layer_get_from_type(priv, DRM_PLANE_TYPE_CURSOR); + if (layer_cursor) + cursor_plane =3D &layer_cursor->drm_plane; + + /* attach primary and cursor */ + ret =3D 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 =3D 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 + */ + +#ifndef _MA35_CRTC_H_ +#define _MA35_CRTC_H_ + +#include + +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/m= a35_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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 =3D &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 =3D MA35_DISPLAY_ALIGN_PIXELS * args->bpp / 8; + args->pitch =3D 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 =3D { + .driver_features =3D DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC | + DRIVER_CURSOR_HOTSPOT, + + .fops =3D &ma35_drm_fops, + .name =3D "ma35-drm", + .desc =3D "Nuvoton MA35 series DRM driver", + .major =3D DRIVER_MAJOR, + .minor =3D 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 =3D { + .reg_bits =3D 32, + .val_bits =3D 32, + .reg_stride =3D 4, + .max_register =3D 0x2000, + .name =3D "ma35-drm", +}; + +static irqreturn_t ma35_drm_irq_handler(int irq, void *data) +{ + struct ma35_drm *priv =3D data; + irqreturn_t ret =3D IRQ_NONE; + u32 stat =3D 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 =3D IRQ_HANDLED; + } + + return ret; +} + +static const struct drm_mode_config_funcs ma35_mode_config_funcs =3D { + .fb_create =3D drm_gem_fb_create, + .atomic_check =3D drm_atomic_helper_check, + .atomic_commit =3D drm_atomic_helper_commit, +}; + +static const struct drm_mode_config_helper_funcs ma35_mode_config_helper_f= uncs =3D { + .atomic_commit_tail =3D drm_atomic_helper_commit_tail, +}; + +static int ma35_mode_init(struct ma35_drm *priv) +{ + struct drm_device *drm_dev =3D &priv->drm_dev; + struct drm_mode_config *mode_config =3D &drm_dev->mode_config; + int ret; + + ret =3D 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 =3D MA35_DEBUG_COUNTER_MASK; + ret =3D drm_vblank_init(drm_dev, 1); + if (ret) { + drm_err(drm_dev, "Failed to initialize vblank\n"); + return ret; + } + + mode_config->min_width =3D 32; + mode_config->max_width =3D 1920; + mode_config->min_height =3D 1; + mode_config->max_height =3D 1080; + mode_config->preferred_depth =3D 24; + mode_config->cursor_width =3D MA35_CURSOR_WIDTH; + mode_config->cursor_height =3D MA35_CURSOR_HEIGHT; + mode_config->funcs =3D &ma35_mode_config_funcs; + mode_config->helper_private =3D &ma35_mode_config_helper_funcs; + + return 0; +} + +static void ma35_mode_fini(struct ma35_drm *priv) +{ + struct drm_device *drm_dev =3D &priv->drm_dev; + + drm_kms_helper_poll_fini(drm_dev); +} + +static int ma35_clocks_prepare(struct ma35_drm *priv) +{ + struct drm_device *drm_dev =3D &priv->drm_dev; + struct device *dev =3D drm_dev->dev; + int ret; + + priv->dcuclk =3D 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 =3D clk_prepare_enable(priv->dcuclk); + if (ret) { + dev_err(dev, "Failed to enable display core clock\n"); + return ret; + } + + priv->dcupclk =3D 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 =3D 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[] =3D { + &priv->dcuclk, + &priv->dcupclk, + }; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(clocks); i++) { + if (!*clocks[i]) + continue; + + clk_disable_unprepare(*clocks[i]); + *clocks[i] =3D NULL; + } + + return 0; +} + +static int ma35_drm_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct ma35_drm *priv; + struct drm_device *drm_dev; + void __iomem *base; + struct regmap *regmap =3D NULL; + int irq; + int ret; + + ret =3D of_reserved_mem_device_init(dev); + if (ret && ret !=3D -ENODEV) { + dev_err(dev, "Failed to get optional reserved memory: %d\n", ret); + return ret; + } + + base =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) { + dev_err(dev, "Failed to map I/O base\n"); + ret =3D PTR_ERR(base); + goto error_reserved_mem; + } + regmap =3D 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 =3D PTR_ERR(regmap); + goto error_reserved_mem; + } + + irq =3D platform_get_irq(pdev, 0); + if (irq < 0) { + ret =3D -ENODEV; + goto error_reserved_mem; + } + + priv =3D devm_drm_dev_alloc(dev, &ma35_drm_driver, + struct ma35_drm, drm_dev); + if (IS_ERR(priv)) { + ret =3D PTR_ERR(priv); + goto error_reserved_mem; + } + + platform_set_drvdata(pdev, priv); + drm_dev =3D &priv->drm_dev; + priv->regmap =3D regmap; + INIT_LIST_HEAD(&priv->layers_list); + + ret =3D ma35_clocks_prepare(priv); + if (ret) { + drm_err(drm_dev, "Failed to prepare clocks\n"); + goto error_reserved_mem; + } + + ret =3D 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 =3D ma35_mode_init(priv); + if (ret) { + drm_err(drm_dev, "Failed to initialize KMS\n"); + goto error_clocks; + } + + /* plane */ + ret =3D ma35_plane_init(priv); + if (ret) { + drm_err(drm_dev, "Failed to initialize layers\n"); + goto error_clocks; + } + + /* crtc */ + ret =3D ma35_crtc_init(priv); + if (ret) { + drm_err(drm_dev, "Failed to initialize CRTC\n"); + goto error_clocks; + } + + /* interface */ + ret =3D ma35_interface_init(priv); + if (ret) { + if (ret !=3D -EPROBE_DEFER) + drm_err(drm_dev, "Failed to initialize interface\n"); + + goto error_clocks; + } + + drm_mode_config_reset(drm_dev); + + ret =3D 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 =3D platform_get_drvdata(pdev); + struct device *dev =3D &pdev->dev; + struct drm_device *drm_dev =3D &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 =3D platform_get_drvdata(pdev); + struct drm_device *drm_dev =3D &priv->drm_dev; + + drm_atomic_helper_shutdown(drm_dev); +} + +static __maybe_unused int ma35_drm_suspend(struct device *dev) +{ + struct ma35_drm *priv =3D dev_get_drvdata(dev); + struct drm_device *drm_dev =3D &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 =3D dev_get_drvdata(dev); + struct drm_device *drm_dev =3D &priv->drm_dev; + + return drm_mode_config_helper_resume(drm_dev); +} + +static const struct of_device_id ma35_drm_of_table[] =3D { + { .compatible =3D "nuvoton,ma35d1-dcu" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ma35_drm_of_table); + +static const struct dev_pm_ops ma35_pm_ops =3D { + SET_SYSTEM_SLEEP_PM_OPS(ma35_drm_suspend, ma35_drm_resume) +}; + +static struct platform_driver ma35_drm_platform_driver =3D { + .probe =3D ma35_drm_probe, + .remove =3D ma35_drm_remove, + .shutdown =3D ma35_drm_shutdown, + .driver =3D { + .name =3D "ma35-drm", + .of_match_table =3D ma35_drm_of_table, + .pm =3D &ma35_pm_ops, + }, +}; + +module_platform_driver(ma35_drm_platform_driver); + +MODULE_AUTHOR("Joey Lu "); +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/m= a35_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 + */ + +#ifndef _MA35_DRM_H_ +#define _MA35_DRM_H_ + +#include +#include +#include + +#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/nuv= oton/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 + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 =3D encoder->dev; + struct ma35_drm *priv =3D ma35_drm(drm_dev); + struct drm_display_mode *adjusted_mode =3D &crtc_state->adjusted_mode; + int result; + + clk_set_rate(priv->dcupclk, adjusted_mode->clock * 1000); + result =3D DIV_ROUND_UP(clk_get_rate(priv->dcupclk), 1000); + drm_dbg(drm_dev, "Pixel clock: %d kHz; request : %d kHz\n", result, adjus= ted_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 =3D ma35_encoder(encoder); + struct drm_display_info *display_info =3D &conn_state->connector->display= _info; + + interface->bus_flags =3D display_info->bus_flags; + + return 0; +} + +static const struct drm_encoder_helper_funcs ma35_encoder_helper_funcs =3D= { + .atomic_mode_set =3D ma35_encoder_mode_set, + .atomic_check =3D ma35_encoder_atomic_check, +}; + +static const struct drm_connector_funcs ma35_connector_funcs =3D { + .reset =3D drm_atomic_helper_connector_reset, + .fill_modes =3D drm_helper_probe_single_connector_modes, + .destroy =3D drm_connector_cleanup, + .atomic_duplicate_state =3D drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state =3D drm_atomic_helper_connector_destroy_state, +}; + +static int ma35_connector_get_modes(struct drm_connector *drm_connector) +{ + struct ma35_drm *priv =3D ma35_drm(drm_connector->dev); + struct drm_device *drm_dev =3D &priv->drm_dev; + struct drm_mode_config *mode_config =3D &drm_dev->mode_config; + struct ma35_interface *interface =3D ma35_connector(drm_connector); + int count; + + if (!interface->drm_panel) { + /* Use the default modes */ + count =3D 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= =3D { + .get_modes =3D ma35_connector_get_modes, +}; + +static void ma35_encoder_attach_crtc(struct ma35_drm *priv) +{ + uint32_t possible_crtcs =3D drm_crtc_mask(&priv->crtc->drm_crtc); + + priv->interface->drm_encoder.possible_crtcs =3D possible_crtcs; +} + +static int ma35_bridge_try_attach(struct ma35_drm *priv, struct ma35_inter= face *interface) +{ + struct drm_device *drm_dev =3D &priv->drm_dev; + struct device *dev =3D drm_dev->dev; + struct device_node *of_node =3D dev->of_node; + struct drm_bridge *bridge; + struct drm_panel *panel; + int ret; + + ret =3D 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 =3D drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + } + + interface->drm_panel =3D panel; + interface->drm_bridge =3D bridge; + + ret =3D 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 =3D &priv->drm_dev; + struct drm_encoder *drm_encoder; + int ret =3D 0; + + /* encoder */ + interface =3D 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 =3D interface; + drm_encoder =3D &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 =3D ma35_bridge_try_attach(priv, interface); + if (!ret) + return 0; + + /* fallback to raw dpi connector */ + ret =3D 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 =3D 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/nuv= oton/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 + */ + +#ifndef _MA35_INTERFACE_H_ +#define _MA35_INTERFACE_H_ + +#include +#include +#include + +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 + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ma35_drm.h" + +#define ma35_layer(p) \ + container_of(p, struct ma35_layer, drm_plane) + +static uint32_t ma35_layer_formats[] =3D { + /* 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[] =3D { + DRM_FORMAT_XRGB8888, +}; + +static struct ma35_plane_property ma35_plane_properties[] =3D { + { /* overlay */ + .fb_addr =3D { MA35_OVERLAY_ADDRESS, + MA35_OVERLAY_UPLANAR_ADDRESS, + MA35_OVERLAY_VPLANAR_ADDRESS }, + .fb_stride =3D { MA35_OVERLAY_STRIDE, + MA35_OVERLAY_USTRIDE, + MA35_OVERLAY_VSTRIDE }, + .alpha =3D true, + }, + { /* primary */ + .fb_addr =3D { MA35_FRAMEBUFFER_ADDRESS, + MA35_FRAMEBUFFER_UPLANAR_ADDRESS, + MA35_FRAMEBUFFER_VPLANAR_ADDRESS }, + .fb_stride =3D { MA35_FRAMEBUFFER_STRIDE, + MA35_FRAMEBUFFER_USTRIDE, + MA35_FRAMEBUFFER_VSTRIDE }, + .alpha =3D false, + }, +}; + +static int ma35_layer_format_validate(u32 fourcc, u32 *format) +{ + switch (fourcc) { + case DRM_FORMAT_XRGB4444: + *format =3D MA35_FORMAT_X4R4G4B4; + break; + case DRM_FORMAT_ARGB4444: + *format =3D MA35_FORMAT_A4R4G4B4; + break; + case DRM_FORMAT_XRGB1555: + *format =3D MA35_FORMAT_X1R5G5B5; + break; + case DRM_FORMAT_ARGB1555: + *format =3D MA35_FORMAT_A1R5G5B5; + break; + case DRM_FORMAT_RGB565: + *format =3D MA35_FORMAT_R5G6B5; + break; + case DRM_FORMAT_XRGB8888: + *format =3D MA35_FORMAT_X8R8G8B8; + break; + case DRM_FORMAT_ARGB8888: + *format =3D MA35_FORMAT_A8R8G8B8; + break; + case DRM_FORMAT_ARGB2101010: + *format =3D MA35_FORMAT_A2R10G10B10; + break; + case DRM_FORMAT_YUYV: + *format =3D MA35_FORMAT_YUY2; + break; + case DRM_FORMAT_UYVY: + *format =3D MA35_FORMAT_UYVY; + break; + case DRM_FORMAT_YVU420: + *format =3D MA35_FORMAT_YV12; + break; + case DRM_FORMAT_NV12: + *format =3D MA35_FORMAT_NV12; + break; + case DRM_FORMAT_NV16: + *format =3D MA35_FORMAT_NV16; + break; + case DRM_FORMAT_P010: + *format =3D 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 =3D drm_plane->dev; + struct drm_plane_state *new_state =3D + drm_atomic_get_new_plane_state(state, drm_plane); + struct drm_crtc *crtc =3D new_state->crtc; + struct drm_framebuffer *fb =3D new_state->fb; + struct drm_crtc_state *crtc_state; + bool can_position; + u32 format; + + if (!crtc) + return 0; + + crtc_state =3D 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 =3D (drm_plane->type !=3D 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 =3D drm_plane->dev; + struct drm_plane_state *new_state =3D + drm_atomic_get_new_plane_state(state, drm_plane); + struct drm_framebuffer *fb =3D new_state->fb; + struct drm_crtc *crtc =3D new_state->crtc; + struct drm_crtc_state *crtc_state; + + if (!fb) + return 0; + + if (!crtc) + return -EINVAL; + + crtc_state =3D drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (fb->format->format !=3D DRM_FORMAT_XRGB8888) { + drm_err(drm_dev, "Invalid cursor format\n"); + return -EINVAL; + } + + if (new_state->crtc_w !=3D MA35_CURSOR_SIZE || new_state->crtc_h !=3D MA3= 5_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 >=3D 32 || new_state->hotspot_x < 0 || + new_state->hotspot_y >=3D 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_plan= e, + 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 =3D x + w; + bottom =3D y + h; + + x =3D (x < 0) ? 0 : x; + y =3D (y < 0) ? 0 : y; + right =3D (right < 0) ? 0 : right; + bottom =3D (bottom < 0) ? 0 : bottom; + + reg =3D 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 =3D 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 =3D ma35_layer(drm_plane); + struct ma35_drm *priv =3D ma35_drm(drm_plane->dev); + struct drm_plane_state *new_state =3D + drm_atomic_get_new_plane_state(state, drm_plane); + struct drm_framebuffer *fb =3D new_state->fb; + u32 format, reg; + u32 *preg; + + ma35_layer_format_validate(fb->format->format, &format); + + if (drm_plane->type =3D=3D DRM_PLANE_TYPE_PRIMARY) { + reg =3D FIELD_PREP(MA35_PRIMARY_FORMAT_MASK, format) | + MA35_PRIMARY_RESET | MA35_PRIMARY_ENABLE; + regmap_write(priv->regmap, MA35_FRAMEBUFFER_CONFIG, reg); + + reg =3D 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 =3D=3D DRM_PLANE_TYPE_OVERLAY) { + reg =3D FIELD_PREP(MA35_OVERLAY_FORMAT_MASK, format) | + MA35_OVERLAY_ENABLE; + regmap_write(priv->regmap, MA35_OVERLAY_CONFIG, reg); + + reg =3D 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 =3D=3D DRM_FORMAT_ARGB8888) { + if (new_state->pixel_blend_mode & + (DRM_MODE_BLEND_PREMULTI | DRM_MODE_BLEND_COVERAGE)) + reg =3D MA35_BLEND_MODE_SRC_OVER; + if (new_state->pixel_blend_mode & DRM_MODE_BLEND_COVERAGE) + reg |=3D 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 =3D 0; i < fb->format->num_planes; i++) { + layer->fb_base[i] =3D drm_fb_dma_get_gem_addr(fb, new_state, i); + preg =3D ma35_plane_properties[drm_plane->type].fb_addr; + regmap_write(priv->regmap, preg[i], layer->fb_base[i]); + preg =3D 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 =3D (x < 0) ? 0 : x; + y =3D (y < 0) ? 0 : y; + + reg =3D 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 =3D ma35_layer(drm_plane); + struct ma35_drm *priv =3D ma35_drm(drm_plane->dev); + struct drm_plane_state *old_state =3D + drm_atomic_get_old_plane_state(state, drm_plane); + struct drm_plane_state *new_state =3D + drm_atomic_get_new_plane_state(state, drm_plane); + struct drm_framebuffer *old_fb =3D old_state->fb; + struct drm_framebuffer *new_fb =3D 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 c= hanged */ + if (!old_fb || old_fb !=3D new_fb) { + layer->fb_base[0] =3D 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 !=3D new_state->hotspot_x || + old_state->hotspot_y !=3D new_state->hotspot_y) { + reg =3D 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_pl= ane, + struct drm_atomic_state *state) +{ + struct ma35_layer *layer =3D ma35_layer(drm_plane); + struct ma35_drm *priv =3D ma35_drm(drm_plane->dev); + struct drm_plane_state *old_state =3D drm_plane->state; + struct drm_plane_state *new_state =3D + drm_atomic_get_new_plane_state(state, drm_plane); + struct drm_framebuffer *old_fb =3D old_state->fb; + struct drm_framebuffer *new_fb =3D new_state->fb; + u32 reg; + + /* update the current one with the new plane state */ + old_state->crtc_x =3D new_state->crtc_x; + old_state->crtc_y =3D new_state->crtc_y; + old_state->crtc_h =3D new_state->crtc_h; + old_state->crtc_w =3D new_state->crtc_w; + old_state->src_x =3D new_state->src_x; + old_state->src_y =3D new_state->src_y; + old_state->src_h =3D new_state->src_h; + old_state->src_w =3D 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 c= hanged */ + if (!old_fb || old_fb !=3D new_fb) { + layer->fb_base[0] =3D 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 !=3D new_state->hotspot_x || + old_state->hotspot_y !=3D new_state->hotspot_y) { + reg =3D 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 =3D new_state->hotspot_x; + old_state->hotspot_y =3D new_state->hotspot_y; + } +} + +static void ma35_plane_atomic_disable(struct drm_plane *drm_plane, + struct drm_atomic_state *state) +{ + struct ma35_drm *priv =3D 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 =3D 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 =3D { + .atomic_check =3D ma35_plane_atomic_check, + .atomic_update =3D ma35_plane_atomic_update, + .atomic_disable =3D ma35_plane_atomic_disable, +}; + +static struct drm_plane_helper_funcs ma35_cursor_plane_helper_funcs =3D { + .atomic_check =3D ma35_cursor_plane_atomic_check, + .atomic_update =3D ma35_cursor_plane_atomic_update, + .atomic_disable =3D ma35_cursor_plane_atomic_disable, + .atomic_async_check =3D ma35_cursor_plane_atomic_async_check, + .atomic_async_update =3D 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 =3D=3D drm_plane->hotspot_x_property) + state->hotspot_x =3D val; + else if (property =3D=3D drm_plane->hotspot_y_property) + state->hotspot_y =3D 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 =3D=3D drm_plane->hotspot_x_property) + *val =3D state->hotspot_x; + else if (property =3D=3D drm_plane->hotspot_y_property) + *val =3D state->hotspot_y; + else + return -EINVAL; + + return 0; +} + +static const struct drm_plane_funcs ma35_plane_funcs =3D { + .update_plane =3D drm_atomic_helper_update_plane, + .disable_plane =3D drm_atomic_helper_disable_plane, + .destroy =3D drm_plane_cleanup, + .reset =3D drm_atomic_helper_plane_reset, + .atomic_duplicate_state =3D drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state =3D drm_atomic_helper_plane_destroy_state, + .atomic_set_property =3D ma35_plane_set_property, + .atomic_get_property =3D ma35_plane_get_property, +}; + +static int ma35_layer_create_properties(struct ma35_drm *priv, + struct ma35_layer *layer) +{ + struct drm_plane *drm_plane =3D &layer->drm_plane; + int ret =3D 0; + + if (ma35_plane_properties[drm_plane->type].alpha) { + drm_plane_create_alpha_property(drm_plane); + ret =3D drm_plane_create_blend_mode_property(drm_plane, BIT(DRM_MODE_BLE= ND_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 dr= m_plane_type type) +{ + struct ma35_layer *layer; + struct drm_plane *drm_plane; + + list_for_each_entry(layer, &priv->layers_list, list) { + drm_plane =3D &layer->drm_plane; + if (drm_plane->type =3D=3D 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 =3D &priv->drm_dev; + struct device *dev =3D drm_dev->dev; + struct ma35_layer *layer; + int ret; + + layer =3D drmm_kzalloc(drm_dev, sizeof(*layer), GFP_KERNEL); + if (!layer) { + ret =3D -ENOMEM; + goto error; + } + + layer->of_node =3D of_node; + + if (type =3D=3D DRM_PLANE_TYPE_CURSOR) { + ret =3D 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 =3D 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 =3D 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 =3D &layer->drm_plane; + if (drm_plane->type !=3D DRM_PLANE_TYPE_OVERLAY) + continue; + + drm_plane->possible_crtcs =3D possible_crtcs; + } +} + +int ma35_plane_init(struct ma35_drm *priv) +{ + struct drm_device *drm_dev =3D &priv->drm_dev; + int ret; + + ret =3D 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 =3D 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 =3D 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 + */ + +#ifndef _MA35_LAYER_H_ +#define _MA35_LAYER_H_ + +#include +#include +#include +#include +#include + +#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 + */ + +#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 --=20 2.43.0