From nobody Sat Feb 7 23:10:53 2026 Received: from mail-pg1-f180.google.com (mail-pg1-f180.google.com [209.85.215.180]) (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 30DD9313E31 for ; Mon, 26 Jan 2026 08:59:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769417960; cv=none; b=uAXYeATOqA9AyeWVCJAsvTMCDwaOhlz/TOiRhyTkyBOmhKgPQpXKPibDZshExxnbr07TD/eNtCWjD/FMYDyfp2Pc52DPVLgubddQ4zuDKcUvM1E9xY00xYUAX2DRUrVrDKqyC0HdymtlGB+00OPak3YEH1Qeg3qMJGkGnrtQWUI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769417960; c=relaxed/simple; bh=rHXdkELnmXPgmfL/hpVHtqe/40spio7lZ9ACAwM5880=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ML5LdF6yPaj48lJuo67JYYyte10BPeCJJT5KJPrTct2oNTZOptXr07G/z1QxpieuTsk0M21ej6xxzV4r9IxstNLb19moCeNY/U6/QA/r7nLCI8IllJjRyI4axBmr0pTPxbQszvqQarLY9g+I98wqa7e2eHCvHKiAZTZbhN0bdCM= 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=PUnMPd9y; arc=none smtp.client-ip=209.85.215.180 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="PUnMPd9y" Received: by mail-pg1-f180.google.com with SMTP id 41be03b00d2f7-c5513f598c0so1484530a12.0 for ; Mon, 26 Jan 2026 00:59:18 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769417958; x=1770022758; 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=xhETkR8BTNErY7rf9frMZHrNNt0dwrD/s7H9/3Vpz5E=; b=PUnMPd9yYuUupoSKOgQs+H6BHt1JnvZ/Wr7RP/ebbx1GrKKu+5ngJOdgyw9pV6co1Q 2w2e1rNsFRl33PeICxehPHtJ5wZalfhiLBX/tB/aZq2x90TpX6zOXgRQIxc1UOZi3/gE R/5S7xhRlUgy1F85AbYoa1IrvWbuRKyKcGP6bR3Fya75F6OvkGDphWgpoaIntVoGYXnI KOT/25zyBY/niSoPNCBB3UpxXzEXug4EpWh7S/0HC4n+KInzIm5q1OIFCk+TaqCkUjT4 wwcJeMDYHo/tNUhlQjn5V/IKla79ZsIjlyQ0dP41BX9TLG/HPnv5EKxsToqHjflKxNFD rOkw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769417958; x=1770022758; 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=xhETkR8BTNErY7rf9frMZHrNNt0dwrD/s7H9/3Vpz5E=; b=oCGkB8TLKiLkJiKca823P5AW8hs0ZWlPbugQ8AFeb0kc/c0nmm+IKTk6raVrWqMgjh +6j+5tmdKkE9kXNJX9s59sIllX1LeoNU8jayLFu5ByacFuVCy17aRzbsyIUePJsUs1/m iyIavha0cKZFUVX6EE1pzhYWotgsxjl7Iu+Acpcom3DuLcD0Azv/5W1BDGm62KkaSNEH 9spFOEpils6SI+CO5y0Kddr1Dbe1z2k18WV9opRhvXaex7YcZvaOXfN96vBaFElA4frc wNX1+10MdrG5yMJdytG43hOcaTuZXfgbNx8fdRGZCJI65qVaOr4xK85M4kQaq3ccN/KL gFrw== X-Forwarded-Encrypted: i=1; AJvYcCXmlQ1ekhQFTIZri2ajQe+lCjcRHmjcmx1K4Z4qjsSahJybqmJP6P0Qb7q+fnBboeGFNz34I9v2+tQZloo=@vger.kernel.org X-Gm-Message-State: AOJu0YxRBLovlYglKQfyt2P1ZPtJmdKCp8rYYoEcTwq3jyBHU5xon7+j J41H5XPYGpuNKVfEdRq8TCJwXSrZR8awgWBxaiaQlwgS9Vwf71EM5nlv X-Gm-Gg: AZuq6aIWgQet/6PJ413ulES2Fvvd9dA/RUxv6DFLeqVhLEfF2RSoBy1nGbTd0+A0Vjo wZNP3uYH7p47abpGXbYI1AVp4kECWGriw2o9fR3xFYWawR4pedU8srK/iAdTO7GPjROua4/ww27 xcM9EsfYSeArMs3Jlu+lebtfFUbezkJJ3aAQQlCNPNn4AC+0lahO+U4scI3X32245n4fQW72T+V 38cTveer9SGRlsxgoHO/PVMjIlmZRB2hs7BUIWNylFDmT2xcK/012OzsyiLJN6FGEZMfpOwtBrS RDluKetF7J/yclbbGywTZtmftUi9XOZdFfX4aMFCk4mVnHuuF9ltgP84uLeiZ0vEf+7FYiyRB2g yKXRupMfWn/+o+I4FkOHrFx1Qta0FlXpBsCPzfIMuNkp1twX39aG6BF5fD/cWUhA1Aq2APtlhJd iJa9OW2xOwS1xoJHNYlXiPV/wPxILo+WXsC3vuPRzEAMQ9YLtTx80jGCypXeT24NHRdkJw2hb1 X-Received: by 2002:a17:90b:2cc6:b0:352:e3d1:8d69 with SMTP id 98e67ed59e1d1-353c40b9e8bmr2572049a91.1.1769417958446; Mon, 26 Jan 2026 00:59:18 -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 98e67ed59e1d1-3536dc3e0ecsm8251798a91.10.2026.01.26.00.59.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Jan 2026 00:59:18 -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 1/3] dt-bindings: display: nuvoton: add MA35D1 DCU binding Date: Mon, 26 Jan 2026 16:57:25 +0800 Message-ID: <20260126085727.2568958-2-a0987203069@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260126085727.2568958-1-a0987203069@gmail.com> References: <20260126085727.2568958-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 | 74 +++++++++++++++++++ 1 file changed, 74 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..e3b79b5b7dbd --- /dev/null +++ b/Documentation/devicetree/bindings/display/nuvoton,ma35d1-dcu.yaml @@ -0,0 +1,74 @@ +# 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 + - port + +additionalProperties: false + +examples: + - | + #include + #include + #include + + display@40260000 { + compatible =3D "nuvoton,ma35d1-drm"; + 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 23:10:53 2026 Received: from mail-pj1-f49.google.com (mail-pj1-f49.google.com [209.85.216.49]) (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 C30CB31196A for ; Mon, 26 Jan 2026 08:59:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769417964; cv=none; b=myDODK8wIzyvucPr7OW4R5kMW14v9oX19Dco4TRuE9qMpqBA3pqaUKecxfBFU5j4vRYgkZyTur87OFrK/X/MUkk5da80mip9jvf+pPDJEDv3ddsBdmcPH6H65Pfdbj11Rm+YE2qH8PGi1WrxvoNbbHEuOyDhi7hgHWVlvIV4kx4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769417964; c=relaxed/simple; bh=mIIGP8ADQDXAifGwYCbfQhje9ZzDDTFLQayrat1NL24=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Hf0tSnv90yN5E6F7mrmRxUUcuh0t5534OApjvh74CY/IKR3ho2aj/h/NPNJmPF4EHDXh//92IjinM2wIsuUDhOgjQwnkguVuNoiwJQ19IeIAdq9KU3bfGE/cLqSaWPM+LT4cU+h+uroG0xrCUN3F+/K3B6zIOm1AElpFYWTivew= 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=HSWMWF66; arc=none smtp.client-ip=209.85.216.49 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="HSWMWF66" Received: by mail-pj1-f49.google.com with SMTP id 98e67ed59e1d1-34f634dbfd6so3493016a91.2 for ; Mon, 26 Jan 2026 00:59:22 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769417962; x=1770022762; 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=15BA5riHYfgjJikydQ+3AqLkz5CGwlAFj0WC+7ODSWs=; b=HSWMWF66vq8XvBX4b3TJxl0FlARVMcdKf7ZNL450NAQurTddc25D5adanlMzzC+WD0 raq3n24eN8MOtC451rDYNi5YfDajH+siL8PvweVLBll4+KeRSWFI5cbnkeOh5elyH7kR EKfVemLzak0ywx0qRreRFwyIHBboK5ixx2Sx6LUvcvQsa5mGZQmYaEfJXA31Uj+n2PST WmqWGbnOJ6+vWmNq7mTnsfeRukdMuDEaTsTB4TAEhJDS9VYlCIgcMqoxxgEv136OwQVA lijEV1+SBnalTWiW0STA+2hFeWc1//YgCsAXT1GV2YPmt6Q6Cz1Teh7BR3mfjzfSZt9n xXHw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769417962; x=1770022762; 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=15BA5riHYfgjJikydQ+3AqLkz5CGwlAFj0WC+7ODSWs=; b=Sp5OzcLWScdvDLv/Egh2IWsGLE/W1rNuihuYrwJmmT599ElZje1CytxXJq4fMphEAI naON5G/WGCPetqub8/jyIEJVqrBma3zxcsI9jv95gf6DT04p3dddYXSsoEtOAMweh88S XDarENf8f8UlKxKZTpATLrOzcsB3R18k0/TuKctjy3qxLXDbJ19XrF4pt7azYOH6cxtI Gqz6GHQih8CaEXFpq3YwWBZN36UpAM+2K6Om6dqq29XYIUbPP/Btw71ZvChhzUjAh/t3 xbvZcs3ixQtscQmGzYpWaLU7Fo0EJCTJb4692xehrW3+ihGW5DphvD0eaH3igHHUomXq gD1A== X-Forwarded-Encrypted: i=1; AJvYcCUr4gGAFO4fLl0nl0w2m+6xiwxqEr8OrI9/t7FJ0UHY1dWydxbFuOgdsP4MoOLqA+6Z3eWAvaN5xU3fmDM=@vger.kernel.org X-Gm-Message-State: AOJu0Yz60Bj59ARMk7L3O8jgZDfHPNV/JqTesPDPm/Iy3deHtSWqs82e 9HZtB927J+RUJEEvHp82Udlv8Vq8GKaZb7CcDHVI8HQ8y1WOcslKoMt4 X-Gm-Gg: AZuq6aIPdI5+mSEA0rHS8yUwdXhlw7qXGTrf5iwVZO90LBP2uxO8NcTSxcKSXCKXMaQ gaKERg+1RZxDTYDeesiXEJmR4+fCIow8j1BfatT08MSAwsS4DqmdbfDXS+HczyUkp2bQCOFYCwr 2EDctmKw+zA1jLW35yhF9y/erqxIvBNFdFIYm6Pe7DsPUAv3gRWvbR/w80T9SJkxOByb8PkugRR aG7853IHfUuJE3tXycVvAhs6BGOU2xLpjVW4Qa+Cq58WCu+pBMjtHhts+sXM6D+Auu9xE6kiHzd nQNrQgU3258/IZLkkPIcvukGlNvz1tdzxrSZ39RyLYVBXMRxVEgWrJcjofg0s1f+K/DIPDNdiZc 5ZwGyp8XgV1J3Xr/Ei6CFgjlctQoHzqZ5wE/IH+Y6vQK+aWSSe77Cz0qXzifz7jvTSFoTjL3rVL GniiydfSWIB3VML4KzXPtY8Df9obNqPw0n8wfYCGmLaCiLq5mNzTOzbam4dbpqmCrpOewXL+hO X-Received: by 2002:a17:90b:248e:b0:34c:cb3c:f536 with SMTP id 98e67ed59e1d1-353c41d1ec5mr2498102a91.36.1769417962171; Mon, 26 Jan 2026 00:59:22 -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 98e67ed59e1d1-3536dc3e0ecsm8251798a91.10.2026.01.26.00.59.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Jan 2026 00:59:21 -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 2/3] arm64: dts: nuvoton: ma35d1: add display controller support Date: Mon, 26 Jan 2026 16:57:26 +0800 Message-ID: <20260126085727.2568958-3-a0987203069@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260126085727.2568958-1-a0987203069@gmail.com> References: <20260126085727.2568958-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 | 42 +++++++++++++++++++ arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 26 ++++++++++++ 2 files changed, 68 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..406dd7998324 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,9 @@ &uart16 { pinctrl-0 =3D <&pinctrl_uart16>; status =3D "okay"; }; + +&display { + pinctrl-names =3D "default"; + pinctrl-0 =3D <&pinctrl_display>; + status =3D "okay"; +}; diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/= nuvoton/ma35d1.dtsi index e51b98f5bdce..7293b5eff046 100644 --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi @@ -379,5 +379,31 @@ uart16: serial@40880000 { clocks =3D <&clk UART16_GATE>; status =3D "disabled"; }; + + panel: panel { + compatible =3D "panel-dpi"; + + port { + panel_in: endpoint@0 { + remote-endpoint =3D <&dpi_out>; + }; + }; + }; + + 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"; + + port { + dpi_out: endpoint@0 { + remote-endpoint =3D <&panel_in>; + }; + }; + }; }; }; --=20 2.43.0 From nobody Sat Feb 7 23:10:53 2026 Received: from mail-pf1-f182.google.com (mail-pf1-f182.google.com [209.85.210.182]) (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 B32453093AD for ; Mon, 26 Jan 2026 08:59:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769417971; cv=none; b=CisJgGo91x9K6iFp/mCKVHKtZMN0xc2NEGXXccEueShtBOsqYgfit7THqXlCcW4b7p2J5L+ivf8EfBBlcAP0piqO8MJdf+8G0NninO+JCmuggkFl69+972Dgm79iLxneZe806k0C0zlA0rTTzN+2bRj1SIhrHNVWHVUz5oLkJyM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769417971; c=relaxed/simple; bh=vPFIRhUKDHL/FESoQAtLioWVWE5MfyxYx2a0xOPGtg8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=DrWb5Hc05ooyzEJ+zjrTjBwv2sARM9+zYrQEaVbsJieyDoQdBDggurXhoqc2ZG6w/KS+kU7Sr70f4zLLcOtIyR7m7Y5yZODwcP48pxXch0nL0d6/wmdrc2qZMj2uJZo1MviEms4R5m/5aSHb/owe3uL6NZ1uhUyNjqjpFvXK0+g= 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=K1HkAWtJ; arc=none smtp.client-ip=209.85.210.182 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="K1HkAWtJ" Received: by mail-pf1-f182.google.com with SMTP id d2e1a72fcca58-81f47610542so2146359b3a.0 for ; Mon, 26 Jan 2026 00:59:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769417966; x=1770022766; 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=VXDjBlJc4nC9tIg8M6rHwWYuOcDKS6IrCHK8L8lQUno=; b=K1HkAWtJdk4jdylaKvjAQLGcxScT96ZlV2tIcxIuCmF6GglpZOF6/6oaISQ4jPypDD w/66w656lCgLhx3tU0ERfLJLSknGdwSIwMXF6qI7WxjnCwKwyGSNGoFLLimDMqLAqMuf gJuzuj4S7gGRUDhUCXcLDUgaAbAedyxTOPb4U27YwYua+X0b7cEHjthXoehibHxQmwFv A3nl3LZYPnnpHiSH9k3ESylAPsOETflyiC43LXQWbeh0RANoMuftWiWvv1P8GUPO14hA KolRYAeTFCMavzsJ28teofMZjFk6ddL000ULF/RacAY0O7ufnIsDkRfCZT4uJYD5bUI/ YnnA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769417966; x=1770022766; 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=VXDjBlJc4nC9tIg8M6rHwWYuOcDKS6IrCHK8L8lQUno=; b=L3k3fgnfcZOnvrSu93Dm172cTaBt0e+gmHqtuPyYoWWi/M2T8gOGfe/6B94tB3/8BX DuAwLPUHgIYAD85D2pRYxi1LC2hEXybeydK44Stcsmg0bewe855ZvM127QohtZb4Ln1B hUM4cdA49fuRy1hCSguM5JShjEP2OysZJgVW5yQd24/7FdH8Dwyxflh5fQ7SQxTc43WU T0XuBzQuJ1zvkZWIZDp3yqNyjycmRDdJrkiP5TqiiyXjLvvnBV4Fb8RmWk+qmVITKo1W G3HHioU8KZ1MDIL4j+52Duv1I6a2JX48bD6qjMg3iMeFaKtZ8FYoAHvZR8S/1IIbFh8Q Vg2g== X-Forwarded-Encrypted: i=1; AJvYcCVd7sdM4KTY3t+p+a1zz7WDMVch9R1NQRU2aCvV5nnmbbx+rYLs9ev0UBQXppXdJzWqwpBBaB/387N1QbQ=@vger.kernel.org X-Gm-Message-State: AOJu0YyJdAO+IEF58oc03dRTQzf0volhWmM74WZQ+bOEXpfhJdrjugei 9IYpZ6i+J+WkbrmgYm2erCSvYEcDEOKtd8ABJzR/VgtTjkOJmzuXsee4 X-Gm-Gg: AZuq6aLLbEhgb/60D0Fdg+2qsy7Cg8kRRuTHL6GD3BMEJcX0vZjQmGntBZsMeMUCl2U ecn9YvgW3d+y2bMVwoBoSaovnQJkdDTIM8Zsc+VcSmnaOvFnU9p9/U/giZ/pjpFhe4mISfN0W77 IvlRwP0cndJxzuGJeKxMx5WFfXZdicIsudRRi8CPYTgMKtbMrfrTv2vBuBA3tmnJzrSXXjuy+C7 0Hg3VLsFES8vXOG+qYPNhHxEtWJyR65YF6CEZV1vVPgzt+7m6AR38hjbIGYYBt+m7TP89eXh07W 9nuDKiVEEVEwhcf1Sn+xySVREUtoV6P2QjLul7a84CFfAQqRSDTlYbj6BiwgTCXPuOmz7B1izNS PXwzLYScKZ0hdccC8oh0oWCDsOpg/Yw02sQG9THklS2VKPGd8qKZpRTJEizv08MhZU7hYpOql4K koUwoA26tti4TGHJiwSzQ54jJ0jeGi0/T51mujZEiZ9bgO/9Y+rAFsyi3XOlViy1d+PUSvRMeq X-Received: by 2002:a17:90b:4c8b:b0:353:100:2f20 with SMTP id 98e67ed59e1d1-353c40e6493mr2979848a91.10.1769417965652; Mon, 26 Jan 2026 00:59:25 -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 98e67ed59e1d1-3536dc3e0ecsm8251798a91.10.2026.01.26.00.59.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Jan 2026 00:59:24 -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 3/3] drm/nuvoton: add MA35D1 display controller driver Date: Mon, 26 Jan 2026 16:57:27 +0800 Message-ID: <20260126085727.2568958-4-a0987203069@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260126085727.2568958-1-a0987203069@gmail.com> References: <20260126085727.2568958-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 | 445 +++++++++++ drivers/gpu/drm/nuvoton/ma35_crtc.h | 78 ++ drivers/gpu/drm/nuvoton/ma35_drm.c | 389 ++++++++++ drivers/gpu/drm/nuvoton/ma35_drm.h | 48 ++ drivers/gpu/drm/nuvoton/ma35_interface.c | 192 +++++ drivers/gpu/drm/nuvoton/ma35_interface.h | 30 + drivers/gpu/drm/nuvoton/ma35_plane.c | 904 +++++++++++++++++++++++ drivers/gpu/drm/nuvoton/ma35_plane.h | 226 ++++++ drivers/gpu/drm/nuvoton/ma35_regs.h | 88 +++ 13 files changed, 2430 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..d168351dcdbe --- /dev/null +++ b/drivers/gpu/drm/nuvoton/ma35_crtc.c @@ -0,0 +1,445 @@ +// 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_crtc(c) \ + container_of(c, struct ma35_crtc, drm_crtc) + +static const struct drm_prop_enum_list ma35_dpi_format[] =3D { + { MA35_DPI_D16CFG1, "D16CFG1" }, + { MA35_DPI_D16CFG2, "D16CFG2" }, + { MA35_DPI_D16CFG3, "D16CFG3" }, + { MA35_DPI_D18CFG1, "D18CFG1" }, + { MA35_DPI_D18CFG2, "D18CFG2" }, + { MA35_DPI_D24, "D24" }, +}; + +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 int ma35_crtc_atomic_set_property(struct drm_crtc *drm_crtc, + struct drm_crtc_state *state, + struct drm_property *property, + uint64_t value) +{ + struct ma35_crtc *crtc =3D ma35_crtc(drm_crtc); + + if (property =3D=3D crtc->dpi_format_prop) + crtc->dpi_format =3D value; + else if (property =3D=3D crtc->dither_enable_prop) + crtc->dither_enable =3D value; + else if (property =3D=3D crtc->dither_depth_prop) + crtc->dither_depth =3D value; + else + return -EINVAL; + + return 0; +} + +static int ma35_crtc_atomic_get_property(struct drm_crtc *drm_crtc, + const struct drm_crtc_state *state, + struct drm_property *property, + uint64_t *value) +{ + struct ma35_crtc *crtc =3D ma35_crtc(drm_crtc); + + if (property =3D=3D crtc->dpi_format_prop) + *value =3D crtc->dpi_format; + else if (property =3D=3D crtc->dither_enable_prop) + *value =3D crtc->dither_enable; + else if (property =3D=3D crtc->dither_depth_prop) + *value =3D crtc->dither_depth; + else + return -EINVAL; + + 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, + .atomic_set_property =3D ma35_crtc_atomic_set_property, + .atomic_get_property =3D ma35_crtc_atomic_get_property, +}; + +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 drm_device *drm_dev =3D &priv->drm_dev; + struct ma35_crtc *crtc =3D priv->crtc; + struct drm_crtc *drm_crtc =3D &crtc->drm_crtc; + + crtc->dpi_format_prop =3D drm_property_create_enum(drm_dev, 0, + "dpi-format", + ma35_dpi_format, + ARRAY_SIZE(ma35_dpi_format)); + if (!crtc->dpi_format_prop) { + drm_err(drm_dev, "Failed to create dpi format property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_crtc->base, crtc->dpi_format_prop, MA35_D= PI_D24); + crtc->dpi_format =3D MA35_DPI_D24; + + crtc->dither_enable_prop =3D drm_property_create_bool(drm_dev, 0, "dither= -enable"); + if (!crtc->dither_enable_prop) { + drm_err(drm_dev, "Failed to create dither enable property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_crtc->base, crtc->dither_enable_prop, fal= se); + crtc->dither_enable =3D false; + + crtc->dither_depth_prop =3D drm_property_create_range(drm_dev, 0, "dither= -depth", + 0, 0xf); + if (!crtc->dither_depth_prop) { + drm_err(drm_dev, "Failed to create dither depth property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_crtc->base, crtc->dither_depth_prop, 0); + crtc->dither_depth =3D 0; + + return 0; +} + +int ma35_crtc_init(struct ma35_drm *priv) +{ + struct drm_device *drm_dev =3D &priv->drm_dev; + struct device *dev =3D drm_dev->dev; + struct ma35_crtc *crtc; + struct ma35_layer *layer_primary, *layer_cursor; + struct drm_plane *cursor_plane =3D NULL; + int ret; + + crtc =3D devm_kzalloc(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; + 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); + + 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..71a41da21df7 --- /dev/null +++ b/drivers/gpu/drm/nuvoton/ma35_crtc.h @@ -0,0 +1,78 @@ +/* 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; + +enum ma35_dpi_format_enum { + MA35_DPI_D16CFG1, + MA35_DPI_D16CFG2, + MA35_DPI_D16CFG3, + MA35_DPI_D18CFG1, + MA35_DPI_D18CFG2, + MA35_DPI_D24, +}; + +#define MA35_DPI_FORMAT_MASK GENMASK(2, 0) + +struct ma35_crtc { + struct drm_crtc drm_crtc; + struct drm_property *dpi_format_prop; + struct drm_property *dither_depth_prop; + struct drm_property *dither_enable_prop; + 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..b675f6d346b1 --- /dev/null +++ b/drivers/gpu/drm/nuvoton/ma35_drm.c @@ -0,0 +1,389 @@ +// 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_node *of_node =3D pdev->dev.of_node; + struct device *dev =3D &pdev->dev; + struct device_node *mem_node; + struct resource res; + struct ma35_drm *priv; + struct drm_device *drm_dev; + void __iomem *base; + struct regmap *regmap =3D NULL; + int irq; + int ret; + + /* Check for reserved memory. Fallback to dynamic allocation if undefined= */ + mem_node =3D of_parse_phandle(of_node, "memory-region", 0); + if (mem_node) { + ret =3D of_address_to_resource(mem_node, 0, &res); + if (ret) { + dev_err(dev, "Failed to parse reserved memory resource: %d\n", ret); + of_node_put(mem_node); + return ret; + } + of_node_put(mem_node); + dev_info(dev, "registering reserved memory %pR\n", &res); + + ret =3D of_reserved_mem_device_init(dev); + if (ret && ret !=3D -ENODEV) { + dev_err(dev, "Failed to init memory region\n"); + goto error_early; + } + } + + 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); + +error_early: + 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..48d1535ace2f --- /dev/null +++ b/drivers/gpu/drm/nuvoton/ma35_interface.c @@ -0,0 +1,192 @@ +// 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; + + /* 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 drm_connector_init(drm_dev, &interface->drm_connector, + &ma35_connector_funcs, + DRM_MODE_CONNECTOR_DPI); + 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..3449cd33059a --- /dev/null +++ b/drivers/gpu/drm/nuvoton/ma35_plane.c @@ -0,0 +1,904 @@ +// 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 "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, + .swizzle =3D true, + .colorkey =3D true, // ARGB only, replaced with primary + .background =3D false, + .foreground =3D false, + }, + { /* 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, + .swizzle =3D true, + .colorkey =3D true, // ARGB only, replaced with background + .background =3D true, + .foreground =3D false, + }, + { /* cursor */ + .alpha =3D false, + .swizzle =3D false, + .colorkey =3D false, + .background =3D true, + .foreground =3D true, + }, +}; + +static const struct drm_prop_enum_list ma35_blend_modes[] =3D { + { MA35_ALPHA_CLEAR, "CLEAR" }, + { MA35_ALPHA_SRC, "SRC" }, + { MA35_ALPHA_DST, "DST" }, + { MA35_ALPHA_SRC_OVER, "SRC_OVER" }, + { MA35_ALPHA_DST_OVER, "DST_OVER" }, + { MA35_ALPHA_SRC_IN, "SRC_IN" }, + { MA35_ALPHA_DST_IN, "DST_IN" }, + { MA35_ALPHA_SRC_OUT, "SRC_OUT" }, + { MA35_ALPHA_DST_OUT, "DST_OUT" }, + { MA35_ALPHA_SRC_ATOP, "SRC_ATOP" }, + { MA35_ALPHA_DST_ATOP, "DST_ATOP" }, + { MA35_ALPHA_XOR, "XOR" }, +}; + +static const struct drm_prop_enum_list ma35_alpha_modes[] =3D { + { MA35_ALPHA_MODE_NONE, "None" }, + { MA35_ALPHA_MODE_GLOBAL, "Coverage" }, +}; + +static const struct drm_prop_enum_list ma35_swizzles[] =3D { + { MA35_SWIZZLE_ARGB, "ARGB" }, + { MA35_SWIZZLE_RGBA, "RGBA" }, + { MA35_SWIZZLE_ABGR, "ABGR" }, + { MA35_SWIZZLE_BGRA, "BGRA" }, + { MA35_SWIZZLE_UV, "UV" }, +}; + +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_layer_blend_mode_select(u32 mode, u32 *reg) +{ + u32 ret =3D 0; + + switch (mode) { + case MA35_ALPHA_CLEAR: + *reg =3D MA35_BLEND_MODE_CLEAR; + break; + case MA35_ALPHA_SRC: + *reg =3D MA35_BLEND_MODE_SRC; + break; + case MA35_ALPHA_DST: + *reg =3D MA35_BLEND_MODE_DST; + break; + case MA35_ALPHA_SRC_OVER: + *reg =3D MA35_BLEND_MODE_SRC_OVER; + break; + case MA35_ALPHA_DST_OVER: + *reg =3D MA35_BLEND_MODE_DST_OVER; + break; + case MA35_ALPHA_SRC_IN: + *reg =3D MA35_BLEND_MODE_SRC_IN; + break; + case MA35_ALPHA_DST_IN: + *reg =3D MA35_BLEND_MODE_DST_IN; + break; + case MA35_ALPHA_SRC_OUT: + *reg =3D MA35_BLEND_MODE_SRC_OUT; + break; + case MA35_ALPHA_DST_OUT: + *reg =3D MA35_BLEND_MODE_DST_OUT; + break; + case MA35_ALPHA_SRC_ATOP: + *reg =3D MA35_BLEND_MODE_SRC_ATOP; + break; + case MA35_ALPHA_DST_ATOP: + *reg =3D MA35_BLEND_MODE_DST_ATOP; + break; + case MA35_ALPHA_XOR: + *reg =3D MA35_BLEND_MODE_XOR; + break; + default: + ret =3D -EINVAL; + } + + return ret; +} + +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 ma35_layer *layer =3D ma35_layer(drm_plane); + 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_new_crtc_state(state, crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + 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 (layer->config.blend_mode > MA35_ALPHA_XOR) { + drm_err(drm_dev, "Invalid blend mode\n"); + return -EINVAL; + } + + if (layer->config.swizzle > MA35_SWIZZLE_UV) { + drm_err(drm_dev, "Invalid swizzle mode\n"); + return -EINVAL; + } + + if ((layer->config.swizzle & MA35_SWIZZLE_ARGB_MASK) && + fb->format->is_yuv) { + drm_err(drm_dev, "Invalid swizzle mode for RGB format\n"); + return -EINVAL; + } + + if ((layer->config.swizzle & MA35_SWIZZLE_UV_MASK) && + !fb->format->is_yuv) { + drm_err(drm_dev, "Invalid swizzle mode for YUV format\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_new_crtc_state(state, crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + 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) | + FIELD_PREP(MA35_PRIMARY_SWIZZLE_MASK, layer->config.swizzle) | + MA35_PRIMARY_RESET | MA35_PRIMARY_ENABLE; + if (layer->config.colorkeylo || layer->config.colorkeyup) + reg |=3D FIELD_PREP(MA35_PRIMARY_TRANSPARENCY_MASK, MA35_COLORKEY_ENABL= E); + else + reg |=3D FIELD_PREP(MA35_PRIMARY_TRANSPARENCY_MASK, MA35_COLORKEY_DISAB= LE); + + 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); + + /* background */ + regmap_write(priv->regmap, MA35_FRAMEBUFFER_BGCOLOR, layer->config.backg= round); + + /* clear value */ + regmap_write(priv->regmap, MA35_FRAMEBUFFER_CLEARVALUE, 0); + + /* colorkey */ + regmap_write(priv->regmap, MA35_FRAMEBUFFER_COLORKEY, layer->config.colo= rkeylo); + regmap_write(priv->regmap, MA35_FRAMEBUFFER_COLORHIGHKEY, layer->config.= colorkeyup); + } else if (drm_plane->type =3D=3D DRM_PLANE_TYPE_OVERLAY) { + reg =3D FIELD_PREP(MA35_OVERLAY_FORMAT_MASK, format) | + FIELD_PREP(MA35_OVERLAY_SWIZZLE_MASK, layer->config.swizzle) | + MA35_OVERLAY_ENABLE; + if (layer->config.colorkeylo || layer->config.colorkeyup) + reg |=3D FIELD_PREP(MA35_OVERLAY_TRANSPARENCY_MASK, MA35_COLORKEY_ENABL= E); + else + reg |=3D FIELD_PREP(MA35_OVERLAY_TRANSPARENCY_MASK, MA35_COLORKEY_DISAB= LE); + + 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) { + ma35_layer_blend_mode_select(layer->config.blend_mode, ®); + reg |=3D FIELD_PREP(MA35_SRC_ALPHA_MODE, (u32)layer->config.alpha_mode[= 0]) | + FIELD_PREP(MA35_DST_ALPHA_MODE, (u32)layer->config.alpha_mode[1]); + regmap_write(priv->regmap, MA35_OVERLAY_ALPHA_BLEND_CONFIG, reg); + + regmap_write(priv->regmap, MA35_OVERLAY_SRC_GLOBAL_COLOR, + layer->config.src_color); + regmap_write(priv->regmap, MA35_OVERLAY_DST_GLOBAL_COLOR, + layer->config.dst_color); + } 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); + + /* colorkey */ + regmap_write(priv->regmap, MA35_OVERLAY_COLOR_KEY, layer->config.colorke= ylo); + regmap_write(priv->regmap, MA35_OVERLAY_COLOR_KEY_HIGH, layer->config.co= lorkeyup); + } + + /* 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_write(priv->regmap, MA35_CURSOR_BACKGROUND, layer->config.backgro= und); + regmap_write(priv->regmap, MA35_CURSOR_FOREGROUND, layer->config.foregro= und); + 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_write(priv->regmap, MA35_CURSOR_BACKGROUND, layer->config.backgro= und); + regmap_write(priv->regmap, MA35_CURSOR_FOREGROUND, layer->config.foregro= und); + 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) +{ + struct ma35_layer *layer =3D ma35_layer(drm_plane); + + if (property =3D=3D layer->blend_mode_prop) + layer->config.blend_mode =3D val; + else if (property =3D=3D layer->src_alpha_mode_prop) + layer->config.alpha_mode[0] =3D val; + else if (property =3D=3D layer->dst_alpha_mode_prop) + layer->config.alpha_mode[1] =3D val; + else if (property =3D=3D layer->src_color_prop) + layer->config.src_color =3D val; + else if (property =3D=3D layer->dst_color_prop) + layer->config.dst_color =3D val; + else if (property =3D=3D layer->swizzle_prop) + layer->config.swizzle =3D val; + else if (property =3D=3D layer->colorkeylo_prop) + layer->config.colorkeylo =3D val; + else if (property =3D=3D layer->colorkeyup_prop) + layer->config.colorkeyup =3D val; + else if (property =3D=3D layer->background_prop) + layer->config.background =3D val; + else if (property =3D=3D layer->foreground_prop) + layer->config.foreground =3D val; + else 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) +{ + struct ma35_layer *layer =3D ma35_layer(drm_plane); + + if (property =3D=3D layer->blend_mode_prop) + *val =3D layer->config.blend_mode; + else if (property =3D=3D layer->src_alpha_mode_prop) + *val =3D layer->config.alpha_mode[0]; + else if (property =3D=3D layer->dst_alpha_mode_prop) + *val =3D layer->config.alpha_mode[1]; + else if (property =3D=3D layer->src_color_prop) + *val =3D layer->config.src_color; + else if (property =3D=3D layer->dst_color_prop) + *val =3D layer->config.dst_color; + else if (property =3D=3D layer->swizzle_prop) + *val =3D layer->config.swizzle; + else if (property =3D=3D layer->colorkeylo_prop) + *val =3D layer->config.colorkeylo; + else if (property =3D=3D layer->colorkeyup_prop) + *val =3D layer->config.colorkeyup; + else if (property =3D=3D layer->background_prop) + *val =3D layer->config.background; + else if (property =3D=3D layer->foreground_prop) + *val =3D layer->config.foreground; + else 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_device *drm_dev =3D &priv->drm_dev; + struct drm_plane *drm_plane =3D &layer->drm_plane; + int ret =3D 0; + + if (ma35_plane_properties[drm_plane->type].alpha) { + layer->blend_mode_prop =3D drm_property_create_enum(drm_dev, 0, + "porter-duff-blend-mode", + ma35_blend_modes, + ARRAY_SIZE(ma35_blend_modes)); + if (!layer->blend_mode_prop) { + drm_err(drm_dev, "Failed to create blend mode property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->blend_mode_prop, + MA35_ALPHA_SRC_OVER); + layer->config.blend_mode =3D MA35_ALPHA_SRC_OVER; + + layer->src_alpha_mode_prop =3D drm_property_create_enum(drm_dev, 0, + "source-alpha-mode", + ma35_alpha_modes, + ARRAY_SIZE(ma35_alpha_modes)); + if (!layer->src_alpha_mode_prop) { + drm_err(drm_dev, "Failed to create source alpha property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->src_alpha_mode_prop, + MA35_ALPHA_MODE_NONE); + layer->config.alpha_mode[0] =3D MA35_ALPHA_MODE_NONE; + + layer->dst_alpha_mode_prop =3D drm_property_create_enum(drm_dev, 0, + "destination-alpha-mode", + ma35_alpha_modes, + ARRAY_SIZE(ma35_alpha_modes)); + if (!layer->dst_alpha_mode_prop) { + drm_err(drm_dev, "Failed to create destination alpha property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->dst_alpha_mode_prop, + MA35_ALPHA_MODE_NONE); + layer->config.alpha_mode[1] =3D MA35_ALPHA_MODE_NONE; + + layer->src_color_prop =3D drm_property_create_range(drm_dev, 0, + "source-global-color", + 0, 0xffffffff); + if (!layer->src_color_prop) { + drm_err(drm_dev, "Failed to create source color property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->src_color_prop, 0); + layer->config.src_color =3D 0; + + layer->dst_color_prop =3D drm_property_create_range(drm_dev, 0, + "destination-global-color", + 0, 0xffffffff); + if (!layer->dst_color_prop) { + drm_err(drm_dev, "Failed to create destination color property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->dst_color_prop, 0); + layer->config.dst_color =3D 0; + } + + if (ma35_plane_properties[drm_plane->type].swizzle) { + layer->swizzle_prop =3D drm_property_create_enum(drm_dev, 0, + "swizzle", + ma35_swizzles, + ARRAY_SIZE(ma35_swizzles)); + if (!layer->swizzle_prop) { + drm_err(drm_dev, "Failed to create swizzle property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->swizzle_prop, + MA35_SWIZZLE_ARGB); + layer->config.swizzle =3D MA35_SWIZZLE_ARGB; + } + + if (ma35_plane_properties[drm_plane->type].colorkey) { + layer->colorkeylo_prop =3D drm_property_create_range(drm_dev, 0, + "colorkey-lower-bound", + 0, 0xffffffff); + if (!layer->colorkeylo_prop) { + drm_err(drm_dev, "Failed to create colorkey property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->colorkeylo_prop, 0); + layer->config.colorkeylo =3D 0; + + layer->colorkeyup_prop =3D drm_property_create_range(drm_dev, 0, + "colorkey-upper-bound", + 0, 0xffffffff); + if (!layer->colorkeyup_prop) { + drm_err(drm_dev, "Failed to create colorkey property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->colorkeyup_prop, 0); + layer->config.colorkeyup =3D 0; + } + + if (ma35_plane_properties[drm_plane->type].background) { + layer->background_prop =3D drm_property_create_range(drm_dev, 0, + "background", + 0, 0xffffffff); + if (!layer->background_prop) { + drm_err(drm_dev, "Failed to create background property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->background_prop, 0); + layer->config.background =3D 0; + } + + if (ma35_plane_properties[drm_plane->type].foreground) { + layer->foreground_prop =3D drm_property_create_range(drm_dev, 0, + "foreground", + 0, 0xffffffff); + if (!layer->foreground_prop) { + drm_err(drm_dev, "Failed to create foreground property\n"); + return -ENOMEM; + } + drm_object_attach_property(&drm_plane->base, layer->foreground_prop, + 0xffffff); + layer->config.foreground =3D 0xffffff; + } + + 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 devm_kzalloc(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_err(drm_dev, "Failed to parse config for layer #%d\n", + index); + goto error; + } + + 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..b975590e03a5 --- /dev/null +++ b/drivers/gpu/drm/nuvoton/ma35_plane.h @@ -0,0 +1,226 @@ +/* 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 + +#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; + bool swizzle; + bool colorkey; + bool background; + bool foreground; +}; + +struct ma35_layer_config { + u32 blend_mode; + u16 alpha_mode[2]; + u32 src_color; + u32 dst_color; + u32 swizzle; + u32 colorkeylo; + u32 colorkeyup; + u32 background; + u32 foreground; + int32_t hotspot_x; + int32_t hotspot_y; +}; + +struct ma35_layer { + struct drm_plane drm_plane; + struct list_head list; + struct device_node *of_node; + struct ma35_layer_config config; + phys_addr_t fb_base[MA35_MAX_PLANES]; + + struct drm_property *blend_mode_prop; + struct drm_property *src_alpha_mode_prop; + struct drm_property *dst_alpha_mode_prop; + struct drm_property *src_color_prop; + struct drm_property *dst_color_prop; + struct drm_property *swizzle_prop; + struct drm_property *colorkeylo_prop; + struct drm_property *colorkeyup_prop; + struct drm_property *background_prop; + struct drm_property *foreground_prop; +}; + +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, +}; + +/* output =3D src * a + dst * b */ +enum ma35_blend_mode_enum { // (a, b) + MA35_ALPHA_CLEAR, // (0, 0) + MA35_ALPHA_SRC, // (1, 0) + MA35_ALPHA_DST, // (0, 1) + MA35_ALPHA_SRC_OVER, // (1, 1-alpha_s) + MA35_ALPHA_DST_OVER, // (1-alpha_d, 1) + MA35_ALPHA_SRC_IN, // (alpha_d, 0) + MA35_ALPHA_DST_IN, // (0, alpha_s) + MA35_ALPHA_SRC_OUT, // (1-alpha_d, 0) + MA35_ALPHA_DST_OUT, // (0, 1-alpha_s) + MA35_ALPHA_SRC_ATOP, // (alpha_d, 1-alpha_s) + MA35_ALPHA_DST_ATOP, // (1-alpha_d, alpha_s) + MA35_ALPHA_XOR, // (1-alpha_d, 1-alpha_s) +}; + +#define MA35_ALPHA_BLEND_DISABLE BIT(1) + +#define MA35_SRC_ALPHA_MODE_INVERSED BIT(0) +#define MA35_DST_ALPHA_MODE_INVERSED BIT(9) + +enum ma35_alpha_mode_enum { + MA35_ALPHA_MODE_NONE, // pass-through + MA35_ALPHA_MODE_GLOBAL, // substitute by global color +}; +#define MA35_SRC_ALPHA_MODE GENMASK(4, 3) +#define MA35_DST_ALPHA_MODE GENMASK(11, 10) + +enum ma35_alpha_blend_enum { + MA35_ALPHA_BLEND_ZERO, + MA35_ALPHA_BLEND_ONE, + MA35_ALPHA_BLEND_NORMAL, + MA35_ALPHA_BLEND_INVERSED, + MA35_ALPHA_BLEND_COLOR, + MA35_ALPHA_BLEND_COLOR_INVERSED, + MA35_ALPHA_BLEND_SATURATED_ALPHA, + MA35_ALPHA_BLEND_SATURATED_DEST_ALPHA, +}; +#define MA35_SRC_BLENDING_MODE GENMASK(7, 5) +#define MA35_DST_BLENDING_MODE GENMASK(14, 12) + +#define MA35_SRC_ALPHA_FACTOR_EN BIT(8) +#define MA35_DST_ALPHA_FACTOR_EN BIT(15) + +/* configs for blend modes */ +#define MA35_BLEND_MODE_CLEAR 0 +#define MA35_BLEND_MODE_SRC \ + FIELD_PREP(MA35_SRC_BLENDING_MODE, MA35_ALPHA_BLEND_ONE) +#define MA35_BLEND_MODE_DST \ + FIELD_PREP(MA35_DST_BLENDING_MODE, MA35_ALPHA_BLEND_ONE) +#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_BLEND_MODE_DST_OVER \ + (FIELD_PREP(MA35_SRC_BLENDING_MODE, MA35_ALPHA_BLEND_INVERSED) | \ + FIELD_PREP(MA35_DST_BLENDING_MODE, MA35_ALPHA_BLEND_ONE)) +#define MA35_BLEND_MODE_SRC_IN \ + FIELD_PREP(MA35_SRC_BLENDING_MODE, MA35_ALPHA_BLEND_NORMAL) +#define MA35_BLEND_MODE_DST_IN \ + FIELD_PREP(MA35_DST_BLENDING_MODE, MA35_ALPHA_BLEND_NORMAL) +#define MA35_BLEND_MODE_SRC_OUT \ + FIELD_PREP(MA35_SRC_BLENDING_MODE, MA35_ALPHA_BLEND_INVERSED) +#define MA35_BLEND_MODE_DST_OUT \ + FIELD_PREP(MA35_DST_BLENDING_MODE, MA35_ALPHA_BLEND_INVERSED) +#define MA35_BLEND_MODE_SRC_ATOP \ + (FIELD_PREP(MA35_SRC_BLENDING_MODE, MA35_ALPHA_BLEND_NORMAL) | \ + FIELD_PREP(MA35_DST_BLENDING_MODE, MA35_ALPHA_BLEND_INVERSED)) +#define MA35_BLEND_MODE_DST_ATOP \ + (FIELD_PREP(MA35_SRC_BLENDING_MODE, MA35_ALPHA_BLEND_INVERSED) | \ + FIELD_PREP(MA35_DST_BLENDING_MODE, MA35_ALPHA_BLEND_NORMAL)) +#define MA35_BLEND_MODE_XOR \ + (FIELD_PREP(MA35_SRC_BLENDING_MODE, MA35_ALPHA_BLEND_INVERSED) | \ + FIELD_PREP(MA35_DST_BLENDING_MODE, MA35_ALPHA_BLEND_INVERSED)) + +/* colorkey */ +#define MA35_COLORKEY_ENABLE 2 +#define MA35_COLORKEY_DISABLE 0 + +#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_TRANSPARENCY_MASK GENMASK(10, 9) +#define MA35_PRIMARY_SWIZZLE_MASK GENMASK(25, 23) +#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_OVERLAY_SWIZZLE_MASK GENMASK(15, 13) +#define MA35_OVERLAY_TRANSPARENCY_MASK GENMASK(1, 0) + +#define MA35_SWIZZLE_ARGB_MASK GENMASK(1, 0) +#define MA35_SWIZZLE_UV_MASK BIT(2) + +enum ma35_swizzles_enum { + MA35_SWIZZLE_ARGB, + MA35_SWIZZLE_RGBA, + MA35_SWIZZLE_ABGR, + MA35_SWIZZLE_BGRA, + MA35_SWIZZLE_UV, +}; + +#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