public inbox for linux-arm-kernel@lists.infradead.org 
 help / color / mirror / Atom feed
* [PATCH 0/2] phy: nuvoton: Add MA35D1 USB2 OTG PHY driver
@ 2026-06-04 10:12 Joey Lu
  2026-06-04 10:12 ` [PATCH 1/2] dt-bindings: phy: nuvoton: Add MA35D1 USB2 OTG PHY binding Joey Lu
  2026-06-04 10:12 ` [PATCH 2/2] phy: nuvoton: Add MA35D1 USB2 OTG PHY driver Joey Lu
  0 siblings, 2 replies; 3+ messages in thread
From: Joey Lu @ 2026-06-04 10:12 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jacky Huang,
	Shan-Chun Hung, linux-phy, devicetree, linux-arm-kernel,
	linux-kernel, Joey Lu

The Nuvoton MA35D1 SoC has two USB 2.0 ports:

  USB0 is an OTG-capable port.  Its physical signals are routed by a
  hardware mux to either a DWC2 gadget controller or the EHCI0/OHCI0
  host controllers, depending on the USB ID pin state.  The DWC2 IP is
  device-only in hardware, so all host-mode operation on USB0 is
  handled by EHCI0/OHCI0.

  USB1 is a dedicated host-only port served by EHCI1/OHCI1.

About this driver:

  - Runs the PHY Power-On Reset sequence, with a guard that skips
    re-initialization if the PHY is already operational.

  - Supports optional resistor calibration trim (nuvoton,rcalcode) and
    over-current detect polarity configuration (nuvoton,oc-active-high).

  - For PHY0 (USB0) only: registers a USB role switch that reads the
    hardware ID pin state from PWRONOTP[16] on every query.

Joey Lu (2):
  dt-bindings: phy: nuvoton: Add MA35D1 USB2 OTG PHY  binding
  phy: nuvoton: Add MA35D1 USB2 OTG PHY driver

 .../phy/nuvoton,ma35d1-usb2-phy-otg.yaml      |  79 ++++++
 drivers/phy/nuvoton/Kconfig                   |  15 +
 drivers/phy/nuvoton/Makefile                  |   1 +
 drivers/phy/nuvoton/phy-ma35d1-otg.c          | 264 ++++++++++++++++++
 4 files changed, 359 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy-otg.yaml
 create mode 100644 drivers/phy/nuvoton/phy-ma35d1-otg.c

-- 
2.43.0



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

* [PATCH 1/2] dt-bindings: phy: nuvoton: Add MA35D1 USB2 OTG PHY  binding
  2026-06-04 10:12 [PATCH 0/2] phy: nuvoton: Add MA35D1 USB2 OTG PHY driver Joey Lu
@ 2026-06-04 10:12 ` Joey Lu
  2026-06-04 10:12 ` [PATCH 2/2] phy: nuvoton: Add MA35D1 USB2 OTG PHY driver Joey Lu
  1 sibling, 0 replies; 3+ messages in thread
From: Joey Lu @ 2026-06-04 10:12 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jacky Huang,
	Shan-Chun Hung, linux-phy, devicetree, linux-arm-kernel,
	linux-kernel, Joey Lu

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 <a0987203069@gmail•com>
---
 .../phy/nuvoton,ma35d1-usb2-phy-otg.yaml      | 79 +++++++++++++++++++
 1 file changed, 79 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-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-otg.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 <yclu4@nuvoton•com>
+
+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 <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+
+    usb_hphy0: usb-host-phy {
+        compatible = "nuvoton,ma35d1-usb2-phy-otg";
+        clocks = <&clk HUSBH0_GATE>;
+        nuvoton,sys = <&sys 0>;
+        #phy-cells = <0>;
+    };
-- 
2.43.0



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

* [PATCH 2/2] phy: nuvoton: Add MA35D1 USB2 OTG PHY driver
  2026-06-04 10:12 [PATCH 0/2] phy: nuvoton: Add MA35D1 USB2 OTG PHY driver Joey Lu
  2026-06-04 10:12 ` [PATCH 1/2] dt-bindings: phy: nuvoton: Add MA35D1 USB2 OTG PHY binding Joey Lu
@ 2026-06-04 10:12 ` Joey Lu
  1 sibling, 0 replies; 3+ messages in thread
From: Joey Lu @ 2026-06-04 10:12 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jacky Huang,
	Shan-Chun Hung, linux-phy, devicetree, linux-arm-kernel,
	linux-kernel, Joey Lu

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 <a0987203069@gmail•com>
---
 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
 
 obj-$(CONFIG_PHY_MA35_USB)		+= phy-ma35d1-usb2.o
+obj-$(CONFIG_PHY_MA35_USB_OTG)		+= 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 <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/usb/role.h>
+
+#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 = phy_get_drvdata(phy);
+	unsigned int n = p->phy_idx;
+	u32 ready_mask = USBPMISCR_PHY_HOST_READY(n);
+	unsigned int val;
+	int ret;
+
+	regmap_read(p->sysreg, MA35_SYS_USBPMISCR, &val);
+	if ((val & ready_mask) == 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 = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR, val,
+				       (val & ready_mask) == ready_mask,
+				       10, 1000);
+	if (ret) {
+		dev_err(p->dev, "USB PHY%u clock not stable (USBPMISCR=0x%08x)\n",
+			n, val);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ma35_otg_phy_power_on(struct phy *phy)
+{
+	struct ma35_otg_phy *p = 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 = phy_get_drvdata(phy);
+
+	clk_disable_unprepare(p->clk);
+	return 0;
+}
+
+static const struct phy_ops ma35_otg_phy_ops = {
+	.init = ma35_otg_phy_init,
+	.power_on = ma35_otg_phy_power_on,
+	.power_off = ma35_otg_phy_power_off,
+	.owner = 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 = usb_role_switch_get_drvdata(sw);
+
+	p->cur_role = role;
+
+	return 0;
+}
+
+static enum usb_role ma35_otg_role_sw_get(struct usb_role_switch *sw)
+{
+	struct ma35_otg_phy *p = 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 = { };
+
+	p->cur_role = ma35_otg_read_id(p);
+
+	sw_desc.set = ma35_otg_role_sw_set;
+	sw_desc.get = ma35_otg_role_sw_get;
+	sw_desc.allow_userspace_control = true;
+	sw_desc.driver_data = p;
+	sw_desc.fwnode = dev_fwnode(&pdev->dev);
+
+	p->role_sw = 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 = 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 = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
+	if (!p)
+		return -ENOMEM;
+
+	p->dev = &pdev->dev;
+	platform_set_drvdata(pdev, p);
+
+	p->sysreg = 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 = 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 = 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 = 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 = 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 == 0) {
+		ret = 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 = platform_get_drvdata(pdev);
+
+	ma35_otg_role_switch_exit(p);
+}
+
+static const struct of_device_id ma35_otg_phy_of_match[] = {
+	{ .compatible = "nuvoton,ma35d1-usb2-phy-otg" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ma35_otg_phy_of_match);
+
+static struct platform_driver ma35_otg_phy_driver = {
+	.probe	= ma35_otg_phy_probe,
+	.remove	= ma35_otg_phy_remove,
+	.driver	= {
+		.name		= "ma35d1-usb2-phy-otg",
+		.of_match_table	= 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 <a0987203069@gmail•com>");
+MODULE_LICENSE("GPL");
-- 
2.43.0



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

end of thread, other threads:[~2026-06-04 10:12 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-04 10:12 [PATCH 0/2] phy: nuvoton: Add MA35D1 USB2 OTG PHY driver Joey Lu
2026-06-04 10:12 ` [PATCH 1/2] dt-bindings: phy: nuvoton: Add MA35D1 USB2 OTG PHY binding Joey Lu
2026-06-04 10:12 ` [PATCH 2/2] phy: nuvoton: Add MA35D1 USB2 OTG PHY driver Joey Lu

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