From nobody Mon Jun 8 09:48:36 2026 Received: from mail-pj1-f53.google.com (mail-pj1-f53.google.com [209.85.216.53]) (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 D30EF3D891E for ; Thu, 4 Jun 2026 10:12:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780567953; cv=none; b=JsN/rjx/M0TPtbbLrq6l7ZqTtijXBsqxpXUd2U3iU75r190V7YYIEUyeJpS4o12ccOa7TIBUgnbC7dpZwcQ1VmqEI96/utxAIEaosQqfC2CdfCdn3h9cq75iMM/oefwBwG2fEfyfnOndJOQo473Pzt6Qw3N/EN8Ytpac/YXqEfY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780567953; c=relaxed/simple; bh=KZD8Zz+ug3sS6SUPS7FyviMXeaSNXK4TH+nbzfl418o=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KFgiKqlR8nVo5IzNFBx3omZDFXPqzNTO6nL/Zm/yp89HXqn/adIpezcGBkXDVxEXd1ON4dGG7/3NIbfvPerwiP2hcVXMUZZeA/lWTMQnWC7CkHhhnWIEns7oQ58i2YFEiIlexX9eMZa75PNBlaw5YIJ3IyoDReM6aHhOQTHcxN4= 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=KBeFTVU6; arc=none smtp.client-ip=209.85.216.53 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="KBeFTVU6" Received: by mail-pj1-f53.google.com with SMTP id 98e67ed59e1d1-36b7b7b7a80so886939a91.1 for ; Thu, 04 Jun 2026 03:12:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780567951; x=1781172751; 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=du3ehlF1Uyll9z9fTITCQGImnBetFueZCw24wEt4ZU8=; b=KBeFTVU64PobICdETdrFrnBEwYxbBjuBFXGN0+0QlzuxUNNVK1cBW80DZEfKyG3NQ7 3p0lG8pbnF9Ko11jIZEObtceUcUg0Mo2jXFkX3kCOKrQ0fLNYKjoWuoco0ES+KYhYSSp fxuB4W0uJ8rYf64zG4rTHFFKYFdWI4Yrj3VBbMTkZErRnD6zH9rKWXyNBMOnaofeD3Ip LuDDBRe2b66LR+WQw+V9GOoYO76Q4MvgA4Fb+Z2WIXnMz8V9yftErGf6C9XR1rNp8sP8 vRD21UCr9X9dhg+Mw43xF/BWQ3IYHysUNYEoY0XReqWsNHOnDn6DH3ntpzSDpbtxn/MS Hujw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780567951; x=1781172751; 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=du3ehlF1Uyll9z9fTITCQGImnBetFueZCw24wEt4ZU8=; b=dbyzzJS0iVdaQjwXEZCodTXPfDdK+1wXUBxa2ZBrzrk9fCT0Nf3vzWxDSg60P8rqYE v8FP+YQbTfbMU51+E3FlvEyLfeoS7VzsUv9NQwbnWt+hcn9AWbef5NMKTwDwrjSiUo02 xhKRk/LaDDh+cbaAa4t/7eSljHfNdT6PUcw/dSQZ06vBYRPn2he9mp8x/Uzz98MFNwJE K24iO+FNvVZjf1/lSSF32/VsfHepdTjF7t2pruWRq9HVsdk2OHljy+y1RfgLN7DbMqhA y+ZqDzp0+sl7Wb8jVEkd/7HhbDBbPbglUMibYZnVsAGzdo/jZa6mr7t2ew1HOzEtZarg Tv5g== X-Forwarded-Encrypted: i=1; AFNElJ/+aBsaOatILB86pzC84C+LCF4LfG4k2wcji1aKvjBuyZ64PZ5URMnkBqAv32mLvGTsD1MVL4KC+PfOCRY=@vger.kernel.org X-Gm-Message-State: AOJu0YxRM7rvA60PICs18ozdK3qYA1y0TCw3wlKh91Jd+09BJwX9t/gW jnh0TxajhfdTX37rbBqiUnveEdGzdecbmDUyaPN95rEXy9jxQoVqB/mU X-Gm-Gg: Acq92OHYy155EavF1UahEnuX1rrxvAQVKZFE2brQOTkXFW+Cq07kz2k0zS18RcIjMTZ beDUBSNQheAL407Ox9kK9txMaHN5wWJkY9r7IOgbkCz/051UTGIemeADTGrovJzHXXurbI7UxJy 3huHAmpGTlrbkuOVyZs7PlMspaRG3vqGJrwqFEQNO3vH3+jgldK0aqnhIX8SrPyIqsTo1D1ylgf AZa+vWKQwHs0DYoGRkmBylX4uXu2BjUoU9IMZJLV5dTwgOB6hW/bUXS/BrhkAzowZEjrLnYtyyJ WLEpA0ScQtWI/9h1Zn2fZ3bmUkKcoKPyQbVQegLhpg/5l7sMRLcka23uOPtQHSdnmDDA5O+66dp El3/kwONGiAGoDai9dmbpBt2I2IrVJ2HR9oRfMuUW7KTFczyaZcIlfpg5Pnps974LHSjg1qe1/O 3PnqQdPSLoI6tP4xx4LtoShlWXrlRYXnnVuuLHr7gbgVIu4nPA08IPpk7lE3VeX9S2MLrPCOUc2 WgzWdyNWLhWDqTQYToK4QA= X-Received: by 2002:a17:90b:2c8c:b0:368:65d1:893 with SMTP id 98e67ed59e1d1-36f75f9c22bmr2777074a91.5.1780567951044; Thu, 04 Jun 2026 03:12:31 -0700 (PDT) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-36f70a29cd6sm2483385a91.11.2026.06.04.03.12.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 04 Jun 2026 03:12:30 -0700 (PDT) From: Joey Lu To: Vinod Koul , Neil Armstrong Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Jacky Huang , Shan-Chun Hung , linux-phy@lists.infradead.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Joey Lu Subject: [PATCH 1/2] dt-bindings: phy: nuvoton: Add MA35D1 USB2 OTG PHY binding Date: Thu, 4 Jun 2026 18:12:19 +0800 Message-ID: <20260604101220.1092822-2-a0987203069@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260604101220.1092822-1-a0987203069@gmail.com> References: <20260604101220.1092822-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 Nuvoton MA35D1 USB 2.0 OTG PHY driver (nuvoton,ma35d1-usb2-phy-otg). PHY index 0 (USB0) is an OTG port whose signals are routed by a hardware mux to either the DWC2 device controller or the EHCI0/OHCI0 host controllers depending on the USB ID pin state. PHY index 1 (USB1) is a dedicated host-only port. Optional properties allow board-specific resistor calibration trim (nuvoton,rcalcode) and over-current detect polarity configuration (nuvoton,oc-active-high). Signed-off-by: Joey Lu --- .../phy/nuvoton,ma35d1-usb2-phy-otg.yaml | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/nuvoton,ma35d1-us= b2-phy-otg.yaml diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy-= otg.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy-ot= g.yaml new file mode 100644 index 000000000000..19f074565cc6 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy-otg.yaml @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/nuvoton,ma35d1-usb2-phy-otg.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 USB 2.0 host PHY + +maintainers: + - Joey Lu + +description: + USB 2.0 PHY driver for the Nuvoton MA35D1 SoC, used by the EHCI and + OHCI host controllers. + + USB0 (PHY index 0) is an OTG port whose physical signals are routed to + either the DWC2 device controller or the EHCI0/OHCI0 host controller by + a hardware mux that follows the USB ID pin. + + USB1 (PHY index 1) is a dedicated host port with no OTG capability. + +properties: + compatible: + const: nuvoton,ma35d1-usb2-phy-otg + + clocks: + maxItems: 1 + + nuvoton,sys: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: phandle to the system management syscon. + - description: PHY instance index. + enum: + - 0 # USB0, OTG port (shared with DWC2 gadget controller) + - 1 # USB1, host-only port + description: + A phandle to the syscon node covering the SYS register block, with + one argument selecting the PHY instance. Index 0 selects the OTG + port PHY (USB0) and index 1 selects the host-only PHY (USB1). + + "#phy-cells": + const: 0 + + nuvoton,rcalcode: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 15 + description: + Resistor calibration trim code written to the RCALCODE field in + USBPMISCR. The 4-bit value adjusts the PHY's internal termination + resistance. When absent the hardware reset default is used. + + nuvoton,oc-active-high: + type: boolean + description: + When present, the over-current detect input from the VBUS power + switch is treated as active-high. The default (property absent) is + active-low. This setting is shared by both USB host ports. + +required: + - compatible + - clocks + - nuvoton,sys + - "#phy-cells" + +additionalProperties: false + +examples: + - | + #include + + usb_hphy0: usb-host-phy { + compatible =3D "nuvoton,ma35d1-usb2-phy-otg"; + clocks =3D <&clk HUSBH0_GATE>; + nuvoton,sys =3D <&sys 0>; + #phy-cells =3D <0>; + }; --=20 2.43.0 From nobody Mon Jun 8 09:48:36 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 8ADEC3E1D16 for ; Thu, 4 Jun 2026 10:12:34 +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=1780567956; cv=none; b=oywKdeX8EaQSzEHvynwCPKK8eCOV1xROO2AFJb4vGXA2Tx5aBAPvn7Q+roa1gjEmGZXA/Q/mJ+zpKuchRd9G+00Oj7+uWjuZZ0Svwk3l2UGrjTQYOZvg2HrJXKigF6VzwGXwx1k72UuM40FtSnkROx0EDIoIyLU9N0RqEJ2Exm4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780567956; c=relaxed/simple; bh=P74ofiRfGsghjpSUERmpaOaj+GQ5IknRdHaPhINrkZw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BBitnumhVje5WiqTHfVJvyzxfMfYakuKq3LRgoNZW178XeRpsozRB08CmRYQhdAkGbi8Dn1jOdHom3enDxLygAXVaWF+mj6TZ4ix/vhdvmm90/QHLl9md+GZH4gJkFgb8f9Tp4jVnBkpufJx20hkDoHeECU4LmZtWtJ7Hu3QKQQ= 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=qs2WSZph; 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="qs2WSZph" Received: by mail-pj1-f49.google.com with SMTP id 98e67ed59e1d1-36d98c9b596so332633a91.3 for ; Thu, 04 Jun 2026 03:12:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780567954; x=1781172754; 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=KNgQ3uIDHmbUKLYb1HRZcPl4cxTs8/vnDz7ZCuCW2h8=; b=qs2WSZphGGtXmuPoWWJyXfOzaEFDiWrgGTwpIbxOEonagdbThlXFSqISb/ZMQe+mu2 20buh6qojcFdKvv+z3G3SPBjR8AV+8RDzG1m1rO8NlD6EryYYoUOBSepQGDLO2fay+mc +Sj9xK4ZSje9Qhq8Oy6UY86qmQRK7joS/UZmo8gy4djNGX4ElQzvsjaqKw/OiTxxz9kc KRZWiOulwiyTFRdsARC1sIwlBbPpU1AXHPV7I7+P+k7eLa26D8Ul6BCJNGGgCGk6ldhb PTWQNbp4NcVqVfdW2Zd/pQrfXWO+Pgxqmgmxx8tAZjjlxC+Bnn3GD3nC74AW6+UqNfVq 7gww== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780567954; x=1781172754; 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=KNgQ3uIDHmbUKLYb1HRZcPl4cxTs8/vnDz7ZCuCW2h8=; b=ozzekFB3glM9sgx97oE2Rkz59Pqu2IizIz1EC5gqzEyGhZEuSJrqEDGBku6xJ31ZSd NjVZlQirDmbNUzpnadncjCrl9587vN+VQBLZdy2QVahrl1OTEZA+Pdfhtdg9jhRBCdHn GJ4C1o1rvYntpyJRdTC2XHKk5QXiH11gaZAk7r8TmOw9eP9Mv5+JfUYWRtk0SXPIWlI4 6MJbItuQ2rnjbjRAt5/GF3/Q0ONR4/Q+iwJ74c/5GZVfxhDpt1WD3v4fmhEaZFiwZszC qxFFCQRSeoIEObriLJvWW3uJpsTrOTBVlksziB7rv1MnUIq8TsOpptiuXs7kSwr54I49 0Tjg== X-Forwarded-Encrypted: i=1; AFNElJ/0xncquDC7frxS7weoL6xw4NF1xWp0T6hDuVqgEtjSfFK4IvNc78X9jZKz0len0XJdBrMzGtxZ5cNGpFs=@vger.kernel.org X-Gm-Message-State: AOJu0YxBbM3PoSTVRhnElJ9Q0mNsB4fOfKBMEkq74aNUVVp0ICkdO1Mx 87/gksQRbJX7rS31skIk9lyvkvA8WVGUaHkYHAalgUWIzWRrVF2xZ/8C X-Gm-Gg: Acq92OEyvCNQywNG/oUCrFxZUAeumOX8bFFjOktluIdKLhYv4A/zD/fq1t+0dv9e/25 o3QofR/0PGIUzlTQlNfUUPyuXkuQfrZjxB8mAJw8ctPsJzUya1Bd37KWK8+uYBitXg9ewk723fk j1n5sweqnbhT4e4BZs9qouSrRlarIw1XmNcWvp4qNdeIm1BIXKyyoOsH3me7WyzYTk4BWJ7Lm2a vdlBWnNZQxqS1s8BrRmWueoxhtv4cwqjAQiH31HChwMWUbUHp93WfVcKUn/tibdy3A6PFxH/ZxX zhOsDJF2660v3Q0fIMO4gazr4EFi4QYkIBZ7M4AG65nIwDtrUmqfnf5Khen07UN3zRzt3/9HxSf mduIUOpdZO0JdjdnJM83ck61YO0olQ1IYa2ST7PGUNYD2gMfH8BpwcrQqjl0UWJjCxvk+kh3WzZ hokmAki/TR7anTyggjbk+Pc4zAVwkPVIBFK0KL77ZkCZ+gso7i3UAB7GHW3xaO6IezPYnGFjZpK LNar+6+ypmp7k0jLnAzytMTNfIUFuT5cg== X-Received: by 2002:a17:90b:48cf:b0:36d:633a:e8aa with SMTP id 98e67ed59e1d1-36e30a2855amr7656208a91.13.1780567953772; Thu, 04 Jun 2026 03:12:33 -0700 (PDT) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-36f70a29cd6sm2483385a91.11.2026.06.04.03.12.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 04 Jun 2026 03:12:33 -0700 (PDT) From: Joey Lu To: Vinod Koul , Neil Armstrong Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Jacky Huang , Shan-Chun Hung , linux-phy@lists.infradead.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Joey Lu Subject: [PATCH 2/2] phy: nuvoton: Add MA35D1 USB2 OTG PHY driver Date: Thu, 4 Jun 2026 18:12:20 +0800 Message-ID: <20260604101220.1092822-3-a0987203069@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260604101220.1092822-1-a0987203069@gmail.com> References: <20260604101220.1092822-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 a PHY driver for the USB 2.0 PHYs in the Nuvoton MA35D1 SoC, intended for use with the EHCI and OHCI host controllers. The MA35D1 SoC has two USB ports: - USB0: an OTG port shared between a DWC2 gadget controller and EHCI0/OHCI0 host controllers. A hardware mux automatically routes the physical USB0 signals to the appropriate controller based on the USB ID pin state. The DWC2 IP is device-only in hardware, so host-mode operation on USB0 is handled entirely by EHCI0/OHCI0. - USB1: a dedicated host-only port served by EHCI1/OHCI1. The driver implements: - Power-On Reset sequence with a guard that skips re-initialization if the PHY is already operational. This protects PHY0 when the DWC2 gadget driver has already run its own init before EHCI0 probes. - Optional resistor calibration trim via nuvoton,rcalcode. - Optional over-current detect polarity via nuvoton,oc-active-high. - For PHY0 only: a USB role switch that exposes the hardware ID pin state (PWRONOTP[16]). Signed-off-by: Joey Lu --- drivers/phy/nuvoton/Kconfig | 15 ++ drivers/phy/nuvoton/Makefile | 1 + drivers/phy/nuvoton/phy-ma35d1-otg.c | 264 +++++++++++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 drivers/phy/nuvoton/phy-ma35d1-otg.c diff --git a/drivers/phy/nuvoton/Kconfig b/drivers/phy/nuvoton/Kconfig index d02cae2db315..5fdd13f841e7 100644 --- a/drivers/phy/nuvoton/Kconfig +++ b/drivers/phy/nuvoton/Kconfig @@ -10,3 +10,18 @@ config PHY_MA35_USB help Enable this to support the USB2.0 PHY on the Nuvoton MA35 series SoCs. + +config PHY_MA35_USB_OTG + tristate "Nuvoton MA35 USB2.0 OTG PHY driver" + depends on ARCH_MA35 || COMPILE_TEST + depends on OF + select GENERIC_PHY + select MFD_SYSCON + select USB_ROLE_SWITCH + help + Enable this to support the USB2.0 OTG PHY on the Nuvoton MA35 + series SoCs. This driver handles PHY initialization for the + EHCI/OHCI host controllers, including per-PHY power-on reset, + resistor calibration trim, and over-current polarity + configuration. For the OTG port (PHY0), it also monitors the + USB ID pin and registers a USB role switch. diff --git a/drivers/phy/nuvoton/Makefile b/drivers/phy/nuvoton/Makefile index 2937e3921898..3ecd76f35d7c 100644 --- a/drivers/phy/nuvoton/Makefile +++ b/drivers/phy/nuvoton/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 =20 obj-$(CONFIG_PHY_MA35_USB) +=3D phy-ma35d1-usb2.o +obj-$(CONFIG_PHY_MA35_USB_OTG) +=3D phy-ma35d1-otg.o diff --git a/drivers/phy/nuvoton/phy-ma35d1-otg.c b/drivers/phy/nuvoton/phy= -ma35d1-otg.c new file mode 100644 index 000000000000..53bc6ddf755e --- /dev/null +++ b/drivers/phy/nuvoton/phy-ma35d1-otg.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton MA35D1 USB 2.0 OTG PHY driver + * + * PHY0 (USB0) is shared between DWC2 gadget and EHCI0/OHCI0 host + * controllers. The hardware mux switches automatically via the USB + * ID pin. PHY1 (USB1) is host-only. + * + * Copyright (C) 2026 Nuvoton Technology Corp. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MA35_SYS_PWRONOTP 0x04 +#define PWRONOTP_USBP0ID BIT(16) + +#define MA35_SYS_USBPMISCR 0x60 +#define USBPMISCR_PHY_POR(n) BIT(0 + (n) * 16) +#define USBPMISCR_PHY_SUSPEND(n) BIT(1 + (n) * 16) +#define USBPMISCR_PHY_COMN(n) BIT(2 + (n) * 16) +#define USBPMISCR_PHY_HSTCKSTB(n) BIT(8 + (n) * 16) +#define USBPMISCR_PHY_CK12MSTB(n) BIT(9 + (n) * 16) +/* Mask for control bits (POR, SUSPEND, COMN) of one PHY */ +#define USBPMISCR_PHY_CTL_MASK(n) (0x7 << ((n) * 16)) +/* Host-mode ready: SUSPEND + HSTCKSTB + CK12MSTB */ +#define USBPMISCR_PHY_HOST_READY(n) (USBPMISCR_PHY_SUSPEND(n) | \ + USBPMISCR_PHY_HSTCKSTB(n) | \ + USBPMISCR_PHY_CK12MSTB(n)) +/* RCALCODE: 4-bit resistor trim at bits [15:12] (PHY0) or [31:28] (PHY1) = */ +#define USBPMISCR_RCAL_SHIFT(n) (12 + (n) * 16) +#define USBPMISCR_RCAL_MASK(n) GENMASK(USBPMISCR_RCAL_SHIFT(n) + 3, \ + USBPMISCR_RCAL_SHIFT(n)) + +#define MA35_SYS_MISCFCR0 0x70 +/* MISCFCR0[12]: USB host over-current detect polarity (shared, both ports= ) */ +#define MISCFCR0_UHOVRCURH BIT(12) + +struct ma35_otg_phy { + struct clk *clk; + struct device *dev; + struct regmap *sysreg; + unsigned int phy_idx; + struct usb_role_switch *role_sw; + enum usb_role cur_role; +}; + +static int ma35_otg_phy_init(struct phy *phy) +{ + struct ma35_otg_phy *p =3D phy_get_drvdata(phy); + unsigned int n =3D p->phy_idx; + u32 ready_mask =3D USBPMISCR_PHY_HOST_READY(n); + unsigned int val; + int ret; + + regmap_read(p->sysreg, MA35_SYS_USBPMISCR, &val); + if ((val & ready_mask) =3D=3D ready_mask) + return 0; + + regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR, + USBPMISCR_PHY_CTL_MASK(n), + USBPMISCR_PHY_POR(n) | USBPMISCR_PHY_SUSPEND(n)); + msleep(20); + + regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR, + USBPMISCR_PHY_CTL_MASK(n), + USBPMISCR_PHY_SUSPEND(n)); + + ret =3D regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR, val, + (val & ready_mask) =3D=3D ready_mask, + 10, 1000); + if (ret) { + dev_err(p->dev, "USB PHY%u clock not stable (USBPMISCR=3D0x%08x)\n", + n, val); + return ret; + } + + return 0; +} + +static int ma35_otg_phy_power_on(struct phy *phy) +{ + struct ma35_otg_phy *p =3D phy_get_drvdata(phy); + + return clk_prepare_enable(p->clk); +} + +static int ma35_otg_phy_power_off(struct phy *phy) +{ + struct ma35_otg_phy *p =3D phy_get_drvdata(phy); + + clk_disable_unprepare(p->clk); + return 0; +} + +static const struct phy_ops ma35_otg_phy_ops =3D { + .init =3D ma35_otg_phy_init, + .power_on =3D ma35_otg_phy_power_on, + .power_off =3D ma35_otg_phy_power_off, + .owner =3D THIS_MODULE, +}; + +static enum usb_role ma35_otg_read_id(struct ma35_otg_phy *p) +{ + unsigned int val; + + regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val); + return (val & PWRONOTP_USBP0ID) ? USB_ROLE_HOST : USB_ROLE_DEVICE; +} + +static int ma35_otg_role_sw_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct ma35_otg_phy *p =3D usb_role_switch_get_drvdata(sw); + + p->cur_role =3D role; + + return 0; +} + +static enum usb_role ma35_otg_role_sw_get(struct usb_role_switch *sw) +{ + struct ma35_otg_phy *p =3D usb_role_switch_get_drvdata(sw); + + return ma35_otg_read_id(p); +} + +static int ma35_otg_role_switch_init(struct platform_device *pdev, + struct ma35_otg_phy *p) +{ + struct usb_role_switch_desc sw_desc =3D { }; + + p->cur_role =3D ma35_otg_read_id(p); + + sw_desc.set =3D ma35_otg_role_sw_set; + sw_desc.get =3D ma35_otg_role_sw_get; + sw_desc.allow_userspace_control =3D true; + sw_desc.driver_data =3D p; + sw_desc.fwnode =3D dev_fwnode(&pdev->dev); + + p->role_sw =3D usb_role_switch_register(&pdev->dev, &sw_desc); + if (IS_ERR(p->role_sw)) + return dev_err_probe(&pdev->dev, PTR_ERR(p->role_sw), + "failed to register role switch\n"); + + return 0; +} + +static void ma35_otg_role_switch_exit(struct ma35_otg_phy *p) +{ + if (!p->role_sw) + return; + + usb_role_switch_unregister(p->role_sw); + p->role_sw =3D NULL; +} + +static int ma35_otg_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *provider; + struct ma35_otg_phy *p; + unsigned int sys_args[1]; + struct phy *phy; + u32 rcalcode; + int ret; + + p =3D devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + p->dev =3D &pdev->dev; + platform_set_drvdata(pdev, p); + + p->sysreg =3D syscon_regmap_lookup_by_phandle_args(pdev->dev.of_node, + "nuvoton,sys", + 1, sys_args); + if (IS_ERR(p->sysreg)) + return dev_err_probe(&pdev->dev, PTR_ERR(p->sysreg), + "Failed to get SYS regmap\n"); + + p->phy_idx =3D sys_args[0]; + + if (p->phy_idx > 1) + return dev_err_probe(&pdev->dev, -EINVAL, + "invalid PHY index %u (must be 0 or 1)\n", + p->phy_idx); + + p->clk =3D devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(p->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(p->clk), + "failed to get PHY clock\n"); + + if (!of_property_read_u32(pdev->dev.of_node, "nuvoton,rcalcode", + &rcalcode)) { + if (rcalcode > 15) + return dev_err_probe(&pdev->dev, -EINVAL, + "rcalcode %u out of range (0-15)\n", + rcalcode); + regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR, + USBPMISCR_RCAL_MASK(p->phy_idx), + rcalcode << USBPMISCR_RCAL_SHIFT(p->phy_idx)); + } + + if (of_property_read_bool(pdev->dev.of_node, "nuvoton,oc-active-high")) + regmap_update_bits(p->sysreg, MA35_SYS_MISCFCR0, + MISCFCR0_UHOVRCURH, MISCFCR0_UHOVRCURH); + + phy =3D devm_phy_create(&pdev->dev, pdev->dev.of_node, &ma35_otg_phy_ops); + if (IS_ERR(phy)) + return dev_err_probe(&pdev->dev, PTR_ERR(phy), + "Failed to create PHY\n"); + + phy_set_drvdata(phy, p); + + provider =3D devm_of_phy_provider_register(&pdev->dev, + of_phy_simple_xlate); + if (IS_ERR(provider)) + return dev_err_probe(&pdev->dev, PTR_ERR(provider), + "Failed to register PHY provider\n"); + + if (p->phy_idx =3D=3D 0) { + ret =3D ma35_otg_role_switch_init(pdev, p); + if (ret) + return ret; + } + + return 0; +} + +static void ma35_otg_phy_remove(struct platform_device *pdev) +{ + struct ma35_otg_phy *p =3D platform_get_drvdata(pdev); + + ma35_otg_role_switch_exit(p); +} + +static const struct of_device_id ma35_otg_phy_of_match[] =3D { + { .compatible =3D "nuvoton,ma35d1-usb2-phy-otg" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ma35_otg_phy_of_match); + +static struct platform_driver ma35_otg_phy_driver =3D { + .probe =3D ma35_otg_phy_probe, + .remove =3D ma35_otg_phy_remove, + .driver =3D { + .name =3D "ma35d1-usb2-phy-otg", + .of_match_table =3D ma35_otg_phy_of_match, + }, +}; +module_platform_driver(ma35_otg_phy_driver); + +MODULE_DESCRIPTION("Nuvoton MA35D1 USB 2.0 OTG PHY driver"); +MODULE_AUTHOR("Joey Lu "); +MODULE_LICENSE("GPL"); --=20 2.43.0