* [PATCH v3 0/3] phy: nuvoton: extend MA35D1 USB2 PHY driver for dual-port OTG support
@ 2026-07-08 10:36 ` Joey Lu
0 siblings, 0 replies; 18+ messages in thread
From: Joey Lu @ 2026-07-08 10:36 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
The MA35D1 SoC has two USB PHY ports managed by a shared hardware block:
- PHY0 (USB0): OTG port shared between the DWC2 gadget controller and
the EHCI0/OHCI0 host controllers. A hardware mux automatically routes
USB0 signals to the correct controller based on the USB ID pin state.
- PHY1 (USB1): dedicated host-only port for EHCI1/OHCI1.
This series extends the existing phy-ma35d1-usb2.c driver and its binding
to cover both ports and add OTG role-switch support, while keeping full
backward compatibility with existing device trees.
Changes since v2:
- Dropped patch 1 (nuvoton,ma35d1-reset: add simple-mfd and child node
support) entirely. That approach embedded usb-phy@60 as a child of the
syscon node with a reg property, which broke the established ABI for
out-of-tree MA35D1 board files.
- The PHY remains a standalone top-level node as in the mainline binding.
The existing required properties (clocks, nuvoton,sys, #phy-cells) are
retained. No reg property is added.
- '#phy-cells' is now enum: [0, 1] instead of const: 1. Boards using
'#phy-cells = <0>' continue to validate and work unchanged.
- The DTS usb_phy node is a sibling of sys (not its child), retaining
clocks and nuvoton,sys, matching the original binding structure.
Joey Lu (3):
dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and
OTG
arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY
node
phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support
.../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml | 45 ++-
.../boot/dts/nuvoton/ma35d1-iot-512m.dts | 36 +++
.../boot/dts/nuvoton/ma35d1-som-256m.dts | 36 +++
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 65 ++++
drivers/phy/nuvoton/phy-ma35d1-usb2.c | 279 +++++++++++++-----
5 files changed, 384 insertions(+), 77 deletions(-)
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
--
2.43.0
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH v3 0/3] phy: nuvoton: extend MA35D1 USB2 PHY driver for dual-port OTG support @ 2026-07-08 10:36 ` Joey Lu 0 siblings, 0 replies; 18+ messages in thread From: Joey Lu @ 2026-07-08 10:36 UTC (permalink / raw) To: Vinod Koul, Neil Armstrong Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel, Joey Lu The MA35D1 SoC has two USB PHY ports managed by a shared hardware block: - PHY0 (USB0): OTG port shared between the DWC2 gadget controller and the EHCI0/OHCI0 host controllers. A hardware mux automatically routes USB0 signals to the correct controller based on the USB ID pin state. - PHY1 (USB1): dedicated host-only port for EHCI1/OHCI1. This series extends the existing phy-ma35d1-usb2.c driver and its binding to cover both ports and add OTG role-switch support, while keeping full backward compatibility with existing device trees. Changes since v2: - Dropped patch 1 (nuvoton,ma35d1-reset: add simple-mfd and child node support) entirely. That approach embedded usb-phy@60 as a child of the syscon node with a reg property, which broke the established ABI for out-of-tree MA35D1 board files. - The PHY remains a standalone top-level node as in the mainline binding. The existing required properties (clocks, nuvoton,sys, #phy-cells) are retained. No reg property is added. - '#phy-cells' is now enum: [0, 1] instead of const: 1. Boards using '#phy-cells = <0>' continue to validate and work unchanged. - The DTS usb_phy node is a sibling of sys (not its child), retaining clocks and nuvoton,sys, matching the original binding structure. Joey Lu (3): dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support .../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml | 45 ++- .../boot/dts/nuvoton/ma35d1-iot-512m.dts | 36 +++ .../boot/dts/nuvoton/ma35d1-som-256m.dts | 36 +++ arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 65 ++++ drivers/phy/nuvoton/phy-ma35d1-usb2.c | 279 +++++++++++++----- 5 files changed, 384 insertions(+), 77 deletions(-) base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482 -- 2.43.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG 2026-07-08 10:36 ` Joey Lu @ 2026-07-08 10:36 ` Joey Lu -1 siblings, 0 replies; 18+ messages in thread From: Joey Lu @ 2026-07-08 10:36 UTC (permalink / raw) To: Vinod Koul, Neil Armstrong Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel, Joey Lu The MA35D1 exposes two USB PHY ports (PHY0 and PHY1) managed by the same hardware block (USBPMISCR register in the system-management syscon). PHY0 is shared between the DWC2 gadget controller and EHCI0/OHCI0 through an automatic hardware mux that follows the USB ID pin; PHY1 is dedicated to EHCI1/OHCI1. Because both ports share the same register topology a single binding and driver should cover both. Changing '#phy-cells' from const: 0 to enum: [0, 1] lets consumers name the port they need while preserving backward compatibility: boards already using '#phy-cells = <0>' continue to validate and function unchanged. The two new optional properties: - nuvoton,rcalcode: the resistor calibration trim code is determined at board design time to match the PCB trace impedance. - nuvoton,oc-active-high: the over-current detect polarity depends on the choice of VBUS power switch. Signed-off-by: Joey Lu <a0987203069@gmail.com> --- .../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml index fff858c909a0..191d1b2272ec 100644 --- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml +++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml @@ -8,6 +8,18 @@ title: Nuvoton MA35D1 USB2 phy maintainers: - Hui-Ping Chen <hpchen0nvt@gmail.com> + - Joey Lu <yclu4@nuvoton.com> + +description: + USB 2.0 PHY for the Nuvoton MA35D1 SoC. The PHY node is a standalone + platform device that accesses the USB PHY control registers inside the + system-management syscon block via the nuvoton,sys phandle. + + PHY0 is the OTG port whose signals are routed to either the DWC2 gadget + controller or the EHCI0/OHCI0 host controller by a hardware mux that + follows the USB ID pin automatically. + + PHY1 is a dedicated host-only port used by EHCI1/OHCI1. properties: compatible: @@ -15,7 +27,12 @@ properties: - nuvoton,ma35d1-usb2-phy "#phy-cells": - const: 0 + enum: [0, 1] + description: + When 0, the node exposes PHY0 only and consumers reference it + with no cell argument. When 1, the single cell selects the port, + with 0 for the OTG port (USB0, shared with DWC2 gadget controller) + and 1 for the host-only port (USB1). clocks: maxItems: 1 @@ -23,7 +40,29 @@ properties: nuvoton,sys: $ref: /schemas/types.yaml#/definitions/phandle description: - phandle to syscon for checking the PHY clock status. + Phandle to the system-management syscon node providing access to the + USB PHY control registers. + + nuvoton,rcalcode: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + maxItems: 2 + items: + minimum: 0 + maximum: 15 + description: + Resistor calibration trim codes for PHY0 and PHY1 respectively. + Each 4-bit value is written to the RCALCODE field in USBPMISCR and + adjusts the PHY's internal termination resistance. Both entries must + be supplied when this property is present; when absent the hardware + reset default is used for each port. + + 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 @@ -39,7 +78,7 @@ examples: usb_phy: usb-phy { compatible = "nuvoton,ma35d1-usb2-phy"; - clocks = <&clk USBD_GATE>; + clocks = <&clk HUSBH0_GATE>; nuvoton,sys = <&sys>; #phy-cells = <0>; }; -- 2.43.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG @ 2026-07-08 10:36 ` Joey Lu 0 siblings, 0 replies; 18+ messages in thread From: Joey Lu @ 2026-07-08 10:36 UTC (permalink / raw) To: Vinod Koul, Neil Armstrong Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel, Joey Lu The MA35D1 exposes two USB PHY ports (PHY0 and PHY1) managed by the same hardware block (USBPMISCR register in the system-management syscon). PHY0 is shared between the DWC2 gadget controller and EHCI0/OHCI0 through an automatic hardware mux that follows the USB ID pin; PHY1 is dedicated to EHCI1/OHCI1. Because both ports share the same register topology a single binding and driver should cover both. Changing '#phy-cells' from const: 0 to enum: [0, 1] lets consumers name the port they need while preserving backward compatibility: boards already using '#phy-cells = <0>' continue to validate and function unchanged. The two new optional properties: - nuvoton,rcalcode: the resistor calibration trim code is determined at board design time to match the PCB trace impedance. - nuvoton,oc-active-high: the over-current detect polarity depends on the choice of VBUS power switch. Signed-off-by: Joey Lu <a0987203069@gmail.com> --- .../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml index fff858c909a0..191d1b2272ec 100644 --- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml +++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml @@ -8,6 +8,18 @@ title: Nuvoton MA35D1 USB2 phy maintainers: - Hui-Ping Chen <hpchen0nvt@gmail.com> + - Joey Lu <yclu4@nuvoton.com> + +description: + USB 2.0 PHY for the Nuvoton MA35D1 SoC. The PHY node is a standalone + platform device that accesses the USB PHY control registers inside the + system-management syscon block via the nuvoton,sys phandle. + + PHY0 is the OTG port whose signals are routed to either the DWC2 gadget + controller or the EHCI0/OHCI0 host controller by a hardware mux that + follows the USB ID pin automatically. + + PHY1 is a dedicated host-only port used by EHCI1/OHCI1. properties: compatible: @@ -15,7 +27,12 @@ properties: - nuvoton,ma35d1-usb2-phy "#phy-cells": - const: 0 + enum: [0, 1] + description: + When 0, the node exposes PHY0 only and consumers reference it + with no cell argument. When 1, the single cell selects the port, + with 0 for the OTG port (USB0, shared with DWC2 gadget controller) + and 1 for the host-only port (USB1). clocks: maxItems: 1 @@ -23,7 +40,29 @@ properties: nuvoton,sys: $ref: /schemas/types.yaml#/definitions/phandle description: - phandle to syscon for checking the PHY clock status. + Phandle to the system-management syscon node providing access to the + USB PHY control registers. + + nuvoton,rcalcode: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + maxItems: 2 + items: + minimum: 0 + maximum: 15 + description: + Resistor calibration trim codes for PHY0 and PHY1 respectively. + Each 4-bit value is written to the RCALCODE field in USBPMISCR and + adjusts the PHY's internal termination resistance. Both entries must + be supplied when this property is present; when absent the hardware + reset default is used for each port. + + 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 @@ -39,7 +78,7 @@ examples: usb_phy: usb-phy { compatible = "nuvoton,ma35d1-usb2-phy"; - clocks = <&clk USBD_GATE>; + clocks = <&clk HUSBH0_GATE>; nuvoton,sys = <&sys>; #phy-cells = <0>; }; -- 2.43.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG 2026-07-08 10:36 ` Joey Lu @ 2026-07-08 10:49 ` sashiko-bot -1 siblings, 0 replies; 18+ messages in thread From: sashiko-bot @ 2026-07-08 10:49 UTC (permalink / raw) To: Joey Lu Cc: neil.armstrong, devicetree, olteanv, linux-phy, vkoul, robh, conor+dt Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] The DT binding introduces multi-port support, but the corresponding driver lacks implementation, leading to silent hardware malfunction. - [Medium] The `#phy-cells` property uses an enum constraint instead of a strict const constraint, violating subsystem guidelines. -- --- Patch [1]: [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG --- commit 7f72a8cc4b64e1dfcf4efba1cd67bf9eafd31d72 Author: Joey Lu <a0987203069@gmail.com> dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG This commit updates the nuvoton,ma35d1-usb2-phy device tree binding to support dual USB PHY ports (PHY0 and PHY1). It relaxes #phy-cells from const: 0 to enum: [0, 1] to allow port selection while preserving backward compatibility. It also adds new optional properties for resistor calibration and over-current polarity. > diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml > index fff858c909a06..191d1b2272ec5 100644 > --- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml > +++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml [ ... ] > @@ -15,7 +27,12 @@ properties: > - nuvoton,ma35d1-usb2-phy > > "#phy-cells": > - const: 0 > + enum: [0, 1] [Severity: High] Does the driver currently support this new #phy-cells = <1> configuration? Looking at ma35_usb_phy_probe() in drivers/phy/nuvoton/phy-ma35d1-usb2.c, the provider registers using of_phy_simple_xlate: provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); Because of_phy_simple_xlate ignores cell arguments, any valid DT request for PHY1 will silently map to the single initialized PHY. Furthermore, ma35_usb_phy_power_on() strictly hardcodes registers for PHY0: regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND)); Will this cause a consumer attempting to control PHY1 to inadvertently reset and power on PHY0 instead? [Severity: Medium] Is it acceptable to use an enum constraint for a cell-count property? According to the subsystem/dt-bindings.md global review guidelines, each cell-count property must have a strict const constraint matching the hardware. Since the hardware physically has two ports, shouldn't #phy-cells be defined as const: 1 to accurately model the hardware's capabilities? > + description: > + When 0, the node exposes PHY0 only and consumers reference it > + with no cell argument. When 1, the single cell selects the port, > + with 0 for the OTG port (USB0, shared with DWC2 gadget controller) > + and 1 for the host-only port (USB1). [ ... ] > @@ -39,7 +78,7 @@ examples: > > usb_phy: usb-phy { > compatible = "nuvoton,ma35d1-usb2-phy"; > - clocks = <&clk USBD_GATE>; > + clocks = <&clk HUSBH0_GATE>; > nuvoton,sys = <&sys>; > #phy-cells = <0>; [Severity: Medium] With the hardware supporting multiple ports, should the example be updated to demonstrate the correct multi-port configuration (#phy-cells = <1>;) instead of propagating the legacy configuration? > }; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260708103606.1462960-1-a0987203069@gmail.com?part=1 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG @ 2026-07-08 10:49 ` sashiko-bot 0 siblings, 0 replies; 18+ messages in thread From: sashiko-bot @ 2026-07-08 10:49 UTC (permalink / raw) To: Joey Lu Cc: neil.armstrong, devicetree, olteanv, linux-phy, vkoul, robh, conor+dt Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] The DT binding introduces multi-port support, but the corresponding driver lacks implementation, leading to silent hardware malfunction. - [Medium] The `#phy-cells` property uses an enum constraint instead of a strict const constraint, violating subsystem guidelines. -- --- Patch [1]: [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG --- commit 7f72a8cc4b64e1dfcf4efba1cd67bf9eafd31d72 Author: Joey Lu <a0987203069@gmail.com> dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG This commit updates the nuvoton,ma35d1-usb2-phy device tree binding to support dual USB PHY ports (PHY0 and PHY1). It relaxes #phy-cells from const: 0 to enum: [0, 1] to allow port selection while preserving backward compatibility. It also adds new optional properties for resistor calibration and over-current polarity. > diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml > index fff858c909a06..191d1b2272ec5 100644 > --- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml > +++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml [ ... ] > @@ -15,7 +27,12 @@ properties: > - nuvoton,ma35d1-usb2-phy > > "#phy-cells": > - const: 0 > + enum: [0, 1] [Severity: High] Does the driver currently support this new #phy-cells = <1> configuration? Looking at ma35_usb_phy_probe() in drivers/phy/nuvoton/phy-ma35d1-usb2.c, the provider registers using of_phy_simple_xlate: provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); Because of_phy_simple_xlate ignores cell arguments, any valid DT request for PHY1 will silently map to the single initialized PHY. Furthermore, ma35_usb_phy_power_on() strictly hardcodes registers for PHY0: regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND)); Will this cause a consumer attempting to control PHY1 to inadvertently reset and power on PHY0 instead? [Severity: Medium] Is it acceptable to use an enum constraint for a cell-count property? According to the subsystem/dt-bindings.md global review guidelines, each cell-count property must have a strict const constraint matching the hardware. Since the hardware physically has two ports, shouldn't #phy-cells be defined as const: 1 to accurately model the hardware's capabilities? > + description: > + When 0, the node exposes PHY0 only and consumers reference it > + with no cell argument. When 1, the single cell selects the port, > + with 0 for the OTG port (USB0, shared with DWC2 gadget controller) > + and 1 for the host-only port (USB1). [ ... ] > @@ -39,7 +78,7 @@ examples: > > usb_phy: usb-phy { > compatible = "nuvoton,ma35d1-usb2-phy"; > - clocks = <&clk USBD_GATE>; > + clocks = <&clk HUSBH0_GATE>; > nuvoton,sys = <&sys>; > #phy-cells = <0>; [Severity: Medium] With the hardware supporting multiple ports, should the example be updated to demonstrate the correct multi-port configuration (#phy-cells = <1>;) instead of propagating the legacy configuration? > }; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260708103606.1462960-1-a0987203069@gmail.com?part=1 ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG 2026-07-08 10:36 ` Joey Lu @ 2026-07-10 8:54 ` Krzysztof Kozlowski -1 siblings, 0 replies; 18+ messages in thread From: Krzysztof Kozlowski @ 2026-07-10 8:54 UTC (permalink / raw) To: Joey Lu Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel On Wed, Jul 08, 2026 at 06:36:04PM +0800, Joey Lu wrote: > + 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 > @@ -39,7 +78,7 @@ examples: > > usb_phy: usb-phy { > compatible = "nuvoton,ma35d1-usb2-phy"; > - clocks = <&clk USBD_GATE>; > + clocks = <&clk HUSBH0_GATE>; This change is really redundant. Instead, add optional properties like nuvoton,rcalcode and nuvoton,oc-active-high. The rest looks good, so with above change: Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> Best regards, Krzysztof ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG @ 2026-07-10 8:54 ` Krzysztof Kozlowski 0 siblings, 0 replies; 18+ messages in thread From: Krzysztof Kozlowski @ 2026-07-10 8:54 UTC (permalink / raw) To: Joey Lu Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel On Wed, Jul 08, 2026 at 06:36:04PM +0800, Joey Lu wrote: > + 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 > @@ -39,7 +78,7 @@ examples: > > usb_phy: usb-phy { > compatible = "nuvoton,ma35d1-usb2-phy"; > - clocks = <&clk USBD_GATE>; > + clocks = <&clk HUSBH0_GATE>; This change is really redundant. Instead, add optional properties like nuvoton,rcalcode and nuvoton,oc-active-high. The rest looks good, so with above change: Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> Best regards, Krzysztof -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG 2026-07-10 8:54 ` Krzysztof Kozlowski @ 2026-07-14 1:26 ` Joey Lu -1 siblings, 0 replies; 18+ messages in thread From: Joey Lu @ 2026-07-14 1:26 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel On 7/10/2026 4:54 PM, Krzysztof Kozlowski wrote: > On Wed, Jul 08, 2026 at 06:36:04PM +0800, Joey Lu wrote: >> + 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 >> @@ -39,7 +78,7 @@ examples: >> >> usb_phy: usb-phy { >> compatible = "nuvoton,ma35d1-usb2-phy"; >> - clocks = <&clk USBD_GATE>; >> + clocks = <&clk HUSBH0_GATE>; > This change is really redundant. Instead, add optional properties like > nuvoton,rcalcode and nuvoton,oc-active-high. > > The rest looks good, so with above change: > > Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> > > Best regards, > Krzysztof Thank you for the review. Agreed on both points: the clock swap in the example is redundant and I will revert it. I will add nuvoton,rcalcode to the example to highlight the optional properties. For nuvoton,oc-active-high, since active-low is the default, I will leave it out of the example to keep it representative of a typical board. I will address this in the next patch series. BR, Joey ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG @ 2026-07-14 1:26 ` Joey Lu 0 siblings, 0 replies; 18+ messages in thread From: Joey Lu @ 2026-07-14 1:26 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel On 7/10/2026 4:54 PM, Krzysztof Kozlowski wrote: > On Wed, Jul 08, 2026 at 06:36:04PM +0800, Joey Lu wrote: >> + 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 >> @@ -39,7 +78,7 @@ examples: >> >> usb_phy: usb-phy { >> compatible = "nuvoton,ma35d1-usb2-phy"; >> - clocks = <&clk USBD_GATE>; >> + clocks = <&clk HUSBH0_GATE>; > This change is really redundant. Instead, add optional properties like > nuvoton,rcalcode and nuvoton,oc-active-high. > > The rest looks good, so with above change: > > Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> > > Best regards, > Krzysztof Thank you for the review. Agreed on both points: the clock swap in the example is redundant and I will revert it. I will add nuvoton,rcalcode to the example to highlight the optional properties. For nuvoton,oc-active-high, since active-low is the default, I will leave it out of the example to keep it representative of a typical board. I will address this in the next patch series. BR, Joey -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v3 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node 2026-07-08 10:36 ` Joey Lu @ 2026-07-08 10:36 ` Joey Lu -1 siblings, 0 replies; 18+ messages in thread From: Joey Lu @ 2026-07-08 10:36 UTC (permalink / raw) To: Vinod Koul, Neil Armstrong Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel, Joey Lu PHY0 is referenced by both the DWC2 gadget and EHCI0/OHCI0 nodes because the USB0 signal lines are physically wired to all three controllers simultaneously; the on-chip hardware mux, driven by the USB ID pin, selects which controller is active. The PHY framework handles this shared reference correctly. OHCI companion nodes are required alongside their EHCI counterparts so that the EHCI driver can hand off full-speed and low-speed devices that cannot operate at high speed; without them those device classes fail to enumerate. The board files add pinctrl entries for the HSUSB signals (VBUSVLD, PWREN, OVC) because these lines are routed through multiplexed pads and must be explicitly configured for USB function. Signed-off-by: Joey Lu <a0987203069@gmail.com> --- .../boot/dts/nuvoton/ma35d1-iot-512m.dts | 36 ++++++++++ .../boot/dts/nuvoton/ma35d1-som-256m.dts | 36 ++++++++++ arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 65 +++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts index 78534d613486..0a068abda6e0 100644 --- a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts +++ b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts @@ -97,6 +97,16 @@ pinctrl_uart14: uart14-pins { power-source = <1>; }; }; + + hsusb { + pinctrl_hsusb: hsusb-pins { + nuvoton,pins = <5 15 1>, /* VBUSVLD */ + <11 12 9>, /* PWREN */ + <11 13 9>; /* OVC */ + bias-disable; + power-source = <1>; + }; + }; }; &uart0 { @@ -151,3 +161,29 @@ eth_phy1: ethernet-phy@1 { reg = <1>; }; }; + +&usb_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_hsusb>; + status = "okay"; +}; + +&usb { + status = "okay"; +}; + +&ehci0 { + status = "okay"; +}; + +&ehci1 { + status = "okay"; +}; + +&ohci0 { + status = "okay"; +}; + +&ohci1 { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts index a029b660e8dc..7f1e6d2a1fc2 100644 --- a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts +++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts @@ -100,6 +100,16 @@ pinctrl_uart16: uart16-pins { power-source = <1>; }; }; + + hsusb { + pinctrl_hsusb: hsusb-pins { + nuvoton,pins = <5 15 1>, /* VBUSVLD */ + <11 12 9>, /* PWREN */ + <11 13 9>; /* OVC */ + bias-disable; + power-source = <1>; + }; + }; }; &uart0 { @@ -153,3 +163,29 @@ eth_phy1: ethernet-phy@1 { reg = <1>; }; }; + +&usb_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_hsusb>; + status = "okay"; +}; + +&usb { + status = "okay"; +}; + +&ehci0 { + status = "okay"; +}; + +&ehci1 { + status = "okay"; +}; + +&ohci0 { + status = "okay"; +}; + +&ohci1 { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi index 7228ad4735b5..5a7a39b1b514 100644 --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi @@ -88,6 +88,14 @@ sys: system-management@40460000 { #reset-cells = <1>; }; + usb_phy: usb-phy { + compatible = "nuvoton,ma35d1-usb2-phy"; + clocks = <&clk HUSBH0_GATE>; + nuvoton,sys = <&sys>; + #phy-cells = <1>; + status = "disabled"; + }; + clk: clock-controller@40460200 { compatible = "nuvoton,ma35d1-clk"; reg = <0x00000000 0x40460200 0x0 0x100>; @@ -431,5 +439,62 @@ mdio1: mdio { #size-cells = <0>; }; }; + + usb: usb@40200000 { + compatible = "snps,dwc2"; + reg = <0x0 0x40200000 0x0 0x1000>; + interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk USBD_GATE>; + clock-names = "otg"; + phys = <&usb_phy 0>; + phy-names = "usb2-phy"; + dr_mode = "peripheral"; + g-np-tx-fifo-size = <16>; + g-rx-fifo-size = <0x100>; + g-tx-fifo-size = <256 256 64 64 64 32 32 32>; + status = "disabled"; + }; + + ehci0: usb@40140000 { + compatible = "generic-ehci"; + reg = <0x0 0x40140000 0x0 0x1000>; + interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk HUSBH0_GATE>; + phys = <&usb_phy 0>; + phy-names = "usb"; + companion = <&ohci0>; + status = "disabled"; + }; + + ehci1: usb@401c0000 { + compatible = "generic-ehci"; + reg = <0x0 0x401c0000 0x0 0x1000>; + interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk HUSBH1_GATE>; + phys = <&usb_phy 1>; + phy-names = "usb"; + companion = <&ohci1>; + status = "disabled"; + }; + + ohci0: usb@40150000 { + compatible = "generic-ohci"; + reg = <0x0 0x40150000 0x0 0x1000>; + interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk HUSBH0_GATE>; + phys = <&usb_phy 0>; + phy-names = "usb"; + status = "disabled"; + }; + + ohci1: usb@401d0000 { + compatible = "generic-ohci"; + reg = <0x0 0x401d0000 0x0 0x1000>; + interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk HUSBH1_GATE>; + phys = <&usb_phy 1>; + phy-names = "usb"; + status = "disabled"; + }; }; }; -- 2.43.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v3 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node @ 2026-07-08 10:36 ` Joey Lu 0 siblings, 0 replies; 18+ messages in thread From: Joey Lu @ 2026-07-08 10:36 UTC (permalink / raw) To: Vinod Koul, Neil Armstrong Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel, Joey Lu PHY0 is referenced by both the DWC2 gadget and EHCI0/OHCI0 nodes because the USB0 signal lines are physically wired to all three controllers simultaneously; the on-chip hardware mux, driven by the USB ID pin, selects which controller is active. The PHY framework handles this shared reference correctly. OHCI companion nodes are required alongside their EHCI counterparts so that the EHCI driver can hand off full-speed and low-speed devices that cannot operate at high speed; without them those device classes fail to enumerate. The board files add pinctrl entries for the HSUSB signals (VBUSVLD, PWREN, OVC) because these lines are routed through multiplexed pads and must be explicitly configured for USB function. Signed-off-by: Joey Lu <a0987203069@gmail.com> --- .../boot/dts/nuvoton/ma35d1-iot-512m.dts | 36 ++++++++++ .../boot/dts/nuvoton/ma35d1-som-256m.dts | 36 ++++++++++ arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 65 +++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts index 78534d613486..0a068abda6e0 100644 --- a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts +++ b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts @@ -97,6 +97,16 @@ pinctrl_uart14: uart14-pins { power-source = <1>; }; }; + + hsusb { + pinctrl_hsusb: hsusb-pins { + nuvoton,pins = <5 15 1>, /* VBUSVLD */ + <11 12 9>, /* PWREN */ + <11 13 9>; /* OVC */ + bias-disable; + power-source = <1>; + }; + }; }; &uart0 { @@ -151,3 +161,29 @@ eth_phy1: ethernet-phy@1 { reg = <1>; }; }; + +&usb_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_hsusb>; + status = "okay"; +}; + +&usb { + status = "okay"; +}; + +&ehci0 { + status = "okay"; +}; + +&ehci1 { + status = "okay"; +}; + +&ohci0 { + status = "okay"; +}; + +&ohci1 { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts index a029b660e8dc..7f1e6d2a1fc2 100644 --- a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts +++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts @@ -100,6 +100,16 @@ pinctrl_uart16: uart16-pins { power-source = <1>; }; }; + + hsusb { + pinctrl_hsusb: hsusb-pins { + nuvoton,pins = <5 15 1>, /* VBUSVLD */ + <11 12 9>, /* PWREN */ + <11 13 9>; /* OVC */ + bias-disable; + power-source = <1>; + }; + }; }; &uart0 { @@ -153,3 +163,29 @@ eth_phy1: ethernet-phy@1 { reg = <1>; }; }; + +&usb_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_hsusb>; + status = "okay"; +}; + +&usb { + status = "okay"; +}; + +&ehci0 { + status = "okay"; +}; + +&ehci1 { + status = "okay"; +}; + +&ohci0 { + status = "okay"; +}; + +&ohci1 { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi index 7228ad4735b5..5a7a39b1b514 100644 --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi @@ -88,6 +88,14 @@ sys: system-management@40460000 { #reset-cells = <1>; }; + usb_phy: usb-phy { + compatible = "nuvoton,ma35d1-usb2-phy"; + clocks = <&clk HUSBH0_GATE>; + nuvoton,sys = <&sys>; + #phy-cells = <1>; + status = "disabled"; + }; + clk: clock-controller@40460200 { compatible = "nuvoton,ma35d1-clk"; reg = <0x00000000 0x40460200 0x0 0x100>; @@ -431,5 +439,62 @@ mdio1: mdio { #size-cells = <0>; }; }; + + usb: usb@40200000 { + compatible = "snps,dwc2"; + reg = <0x0 0x40200000 0x0 0x1000>; + interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk USBD_GATE>; + clock-names = "otg"; + phys = <&usb_phy 0>; + phy-names = "usb2-phy"; + dr_mode = "peripheral"; + g-np-tx-fifo-size = <16>; + g-rx-fifo-size = <0x100>; + g-tx-fifo-size = <256 256 64 64 64 32 32 32>; + status = "disabled"; + }; + + ehci0: usb@40140000 { + compatible = "generic-ehci"; + reg = <0x0 0x40140000 0x0 0x1000>; + interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk HUSBH0_GATE>; + phys = <&usb_phy 0>; + phy-names = "usb"; + companion = <&ohci0>; + status = "disabled"; + }; + + ehci1: usb@401c0000 { + compatible = "generic-ehci"; + reg = <0x0 0x401c0000 0x0 0x1000>; + interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk HUSBH1_GATE>; + phys = <&usb_phy 1>; + phy-names = "usb"; + companion = <&ohci1>; + status = "disabled"; + }; + + ohci0: usb@40150000 { + compatible = "generic-ohci"; + reg = <0x0 0x40150000 0x0 0x1000>; + interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk HUSBH0_GATE>; + phys = <&usb_phy 0>; + phy-names = "usb"; + status = "disabled"; + }; + + ohci1: usb@401d0000 { + compatible = "generic-ohci"; + reg = <0x0 0x401d0000 0x0 0x1000>; + interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk HUSBH1_GATE>; + phys = <&usb_phy 1>; + phy-names = "usb"; + status = "disabled"; + }; }; }; -- 2.43.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v3 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node 2026-07-08 10:36 ` Joey Lu @ 2026-07-08 10:48 ` sashiko-bot -1 siblings, 0 replies; 18+ messages in thread From: sashiko-bot @ 2026-07-08 10:48 UTC (permalink / raw) To: Joey Lu Cc: devicetree, olteanv, neil.armstrong, robh, linux-phy, conor+dt, vkoul Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] The DT changes add dual-port USB PHY support but the underlying PHY driver only supports a single port, breaking USB port 1. -- --- Patch [2]: [PATCH v3 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node --- commit ae86165665a697c5181c72ed4e3f8f74bcf0beed Author: Joey Lu <a0987203069@gmail.com> arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node This commit adds USB controller (DWC2, EHCI, OHCI) and dual-port PHY nodes to the ma35d1 device tree. It also adds pin control configurations for the HSUSB signals to the ma35d1-iot-512m and ma35d1-som-256m board files. > diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi > --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi > +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi [ ... ] > @@ -88,6 +88,14 @@ sys: system-management@40460000 { > #reset-cells = <1>; > }; > > + usb_phy: usb-phy { > + compatible = "nuvoton,ma35d1-usb2-phy"; > + clocks = <&clk HUSBH0_GATE>; > + nuvoton,sys = <&sys>; > + #phy-cells = <1>; > + status = "disabled"; > + }; > + [ ... ] > + ehci1: usb@401c0000 { > + compatible = "generic-ehci"; > + reg = <0x0 0x401c0000 0x0 0x1000>; > + interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>; > + clocks = <&clk HUSBH1_GATE>; > + phys = <&usb_phy 1>; [Severity: High] Is the ma35d1 USB PHY driver fully capable of supporting dual ports? Looking at ma35_usb_phy_probe() in drivers/phy/nuvoton/phy-ma35d1-usb2.c, it registers the provider with of_phy_simple_xlate, which ignores the index in `<&usb_phy 1>` and unconditionally returns the first PHY instance. Furthermore, ma35_usb_phy_power_on() hardcodes the power-on routine to PHY0: drivers/phy/nuvoton/phy-ma35d1-usb2.c:ma35_usb_phy_power_on() { ... regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND)); ... } Will this cause port 1 to fail to initialize since the driver will incorrectly share PHY0's refcount and leave PHY1 completely uninitialized? > + phy-names = "usb"; > + companion = <&ohci1>; > + status = "disabled"; > + }; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260708103606.1462960-1-a0987203069@gmail.com?part=2 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node @ 2026-07-08 10:48 ` sashiko-bot 0 siblings, 0 replies; 18+ messages in thread From: sashiko-bot @ 2026-07-08 10:48 UTC (permalink / raw) To: Joey Lu Cc: devicetree, olteanv, neil.armstrong, robh, linux-phy, conor+dt, vkoul Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] The DT changes add dual-port USB PHY support but the underlying PHY driver only supports a single port, breaking USB port 1. -- --- Patch [2]: [PATCH v3 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node --- commit ae86165665a697c5181c72ed4e3f8f74bcf0beed Author: Joey Lu <a0987203069@gmail.com> arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node This commit adds USB controller (DWC2, EHCI, OHCI) and dual-port PHY nodes to the ma35d1 device tree. It also adds pin control configurations for the HSUSB signals to the ma35d1-iot-512m and ma35d1-som-256m board files. > diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi > --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi > +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi [ ... ] > @@ -88,6 +88,14 @@ sys: system-management@40460000 { > #reset-cells = <1>; > }; > > + usb_phy: usb-phy { > + compatible = "nuvoton,ma35d1-usb2-phy"; > + clocks = <&clk HUSBH0_GATE>; > + nuvoton,sys = <&sys>; > + #phy-cells = <1>; > + status = "disabled"; > + }; > + [ ... ] > + ehci1: usb@401c0000 { > + compatible = "generic-ehci"; > + reg = <0x0 0x401c0000 0x0 0x1000>; > + interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>; > + clocks = <&clk HUSBH1_GATE>; > + phys = <&usb_phy 1>; [Severity: High] Is the ma35d1 USB PHY driver fully capable of supporting dual ports? Looking at ma35_usb_phy_probe() in drivers/phy/nuvoton/phy-ma35d1-usb2.c, it registers the provider with of_phy_simple_xlate, which ignores the index in `<&usb_phy 1>` and unconditionally returns the first PHY instance. Furthermore, ma35_usb_phy_power_on() hardcodes the power-on routine to PHY0: drivers/phy/nuvoton/phy-ma35d1-usb2.c:ma35_usb_phy_power_on() { ... regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND)); ... } Will this cause port 1 to fail to initialize since the driver will incorrectly share PHY0's refcount and leave PHY1 completely uninitialized? > + phy-names = "usb"; > + companion = <&ohci1>; > + status = "disabled"; > + }; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260708103606.1462960-1-a0987203069@gmail.com?part=2 ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v3 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support 2026-07-08 10:36 ` Joey Lu @ 2026-07-08 10:36 ` Joey Lu -1 siblings, 0 replies; 18+ messages in thread From: Joey Lu @ 2026-07-08 10:36 UTC (permalink / raw) To: Vinod Koul, Neil Armstrong Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel, Joey Lu PHY0 and PHY1 use the same power-on/reset sequence in USBPMISCR, with PHY1 control bits shifted 16 positions relative to PHY0. A separate driver for PHY1 would duplicate this logic, so the existing driver is extended to manage both ports. The original driver polled only DEVCKSTB after releasing PHY0 from reset. When USB0 operates in host mode (USB ID pin floating or tied high) only HSTCKSTB and CK12MSTB assert; DEVCKSTB never sets. Polling exclusively for DEVCKSTB in host mode causes a 1 ms timeout on every phy_init() call from the EHCI driver. The init callback is changed to accept either host-mode or device-mode clock stability, whichever asserts first. The power_on and power_off callbacks are replaced by a single init callback that handles PHY reset and clock-stable polling, because there is no PHY-specific clock gate on MA35D1; the PHY analog block derives its reference from the HXT crystal. A read-only USB role switch is registered for PHY0 to expose the USB0 role to userspace via the standard role-switch sysfs interface. The .set callback returns -EOPNOTSUPP because the hardware mux is fully automatic with no software override path. Signed-off-by: Joey Lu <a0987203069@gmail.com> --- drivers/phy/nuvoton/phy-ma35d1-usb2.c | 279 +++++++++++++++++++------- 1 file changed, 205 insertions(+), 74 deletions(-) diff --git a/drivers/phy/nuvoton/phy-ma35d1-usb2.c b/drivers/phy/nuvoton/phy-ma35d1-usb2.c index 9a459b700ed4..e2eac4f8c5bf 100644 --- a/drivers/phy/nuvoton/phy-ma35d1-usb2.c +++ b/drivers/phy/nuvoton/phy-ma35d1-usb2.c @@ -1,11 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (C) 2024 Nuvoton Technology Corp. + * Nuvoton MA35D1 USB 2.0 PHY driver + * + * Supports PHY0 (USB0 OTG port, shared between DWC2 gadget and EHCI0/OHCI0) + * and PHY1 (USB1 host-only port, used by EHCI1/OHCI1). The hardware mux on + * PHY0 switches automatically via the USB ID pin. + * + * 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> @@ -13,131 +18,257 @@ #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/regmap.h> +#include <linux/usb/role.h> -/* USB PHY Miscellaneous Control Register */ -#define MA35_SYS_REG_USBPMISCR 0x60 -#define PHY0POR BIT(0) /* PHY Power-On Reset Control Bit */ -#define PHY0SUSPEND BIT(1) /* PHY Suspend; 0: suspend, 1: operaion */ -#define PHY0COMN BIT(2) /* PHY Common Block Power-Down Control */ -#define PHY0DEVCKSTB BIT(10) /* PHY 60 MHz UTMI clock stable bit */ +#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) +#define USBPMISCR_PHY_DEVCKSTB(n) BIT(10 + (n) * 16) +/* Mask for control bits (POR, SUSPEND, COMN) */ +#define USBPMISCR_PHY_CTL_MASK(n) (0x7u << ((n) * 16)) +/* Host-mode ready */ +#define USBPMISCR_PHY_HOST_READY(n) (USBPMISCR_PHY_SUSPEND(n) | \ + USBPMISCR_PHY_HSTCKSTB(n) | \ + USBPMISCR_PHY_CK12MSTB(n)) +/* Device-mode ready */ +#define USBPMISCR_PHY_DEV_READY(n) (USBPMISCR_PHY_SUSPEND(n) | \ + USBPMISCR_PHY_DEVCKSTB(n)) +/* RCALCODE: 4-bit resistor trim */ +#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 +/* USB host over-current detect polarity (shared, both ports) */ +#define MISCFCR0_UHOVRCURH BIT(12) + +#define MA35_PHY_NUM 2 + +struct ma35_phy_port { + struct phy *phy; + unsigned int idx; +}; struct ma35_usb_phy { - struct clk *clk; struct device *dev; struct regmap *sysreg; + struct ma35_phy_port port[MA35_PHY_NUM]; + struct usb_role_switch *role_sw; }; -static int ma35_usb_phy_power_on(struct phy *phy) +static int ma35_usb_phy_init(struct phy *phy) { - struct ma35_usb_phy *p_phy = phy_get_drvdata(phy); + struct ma35_phy_port *port = phy_get_drvdata(phy); + struct ma35_usb_phy *p = container_of(port - port->idx, + struct ma35_usb_phy, port[0]); + unsigned int n = port->idx; unsigned int val; int ret; - ret = clk_prepare_enable(p_phy->clk); - if (ret < 0) { - dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret); - return ret; - } + regmap_read(p->sysreg, MA35_SYS_USBPMISCR, &val); - regmap_read(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, &val); - if (val & PHY0SUSPEND) { - /* - * USB PHY0 is in operation mode already - * make sure USB PHY 60 MHz UTMI Interface Clock ready - */ - ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val, - val & PHY0DEVCKSTB, 10, 1000); - if (ret == 0) - return 0; - } + if (val & USBPMISCR_PHY_SUSPEND(n)) + return 0; - /* - * reset USB PHY0. - * wait until USB PHY0 60 MHz UTMI Interface Clock ready - */ - regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND)); + regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR, + USBPMISCR_PHY_CTL_MASK(n), + USBPMISCR_PHY_POR(n) | USBPMISCR_PHY_SUSPEND(n)); udelay(20); - /* make USB PHY0 enter operation mode */ - regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, PHY0SUSPEND); + regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR, + USBPMISCR_PHY_CTL_MASK(n), + USBPMISCR_PHY_SUSPEND(n)); - /* make sure USB PHY 60 MHz UTMI Interface Clock ready */ - ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val, - val & PHY0DEVCKSTB, 10, 1000); - if (ret == -ETIMEDOUT) { - dev_err(p_phy->dev, "Check PHY clock, Timeout: %d\n", ret); - clk_disable_unprepare(p_phy->clk); + if (n == 0) { + ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR, + val, + ((val & USBPMISCR_PHY_HOST_READY(0)) == + USBPMISCR_PHY_HOST_READY(0)) || + ((val & USBPMISCR_PHY_DEV_READY(0)) == + USBPMISCR_PHY_DEV_READY(0)), + 10, 1000); + } else { + ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR, + val, + (val & USBPMISCR_PHY_HOST_READY(n)) == + USBPMISCR_PHY_HOST_READY(n), + 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_usb_phy_power_off(struct phy *phy) +static const struct phy_ops ma35_usb_phy_ops = { + .init = ma35_usb_phy_init, + .owner = THIS_MODULE, +}; + +static int ma35_role_sw_set(struct usb_role_switch *sw, enum usb_role role) +{ + return -EOPNOTSUPP; +} + +static enum usb_role ma35_role_sw_get(struct usb_role_switch *sw) { - struct ma35_usb_phy *p_phy = phy_get_drvdata(phy); + struct ma35_usb_phy *p = usb_role_switch_get_drvdata(sw); + u32 val; + + regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val); + + return (val & PWRONOTP_USBP0ID) ? USB_ROLE_HOST : USB_ROLE_DEVICE; +} + +static int ma35_role_switch_init(struct platform_device *pdev, + struct ma35_usb_phy *p) +{ + struct usb_role_switch_desc sw_desc = {0}; + + sw_desc.set = ma35_role_sw_set; + sw_desc.get = ma35_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"); - clk_disable_unprepare(p_phy->clk); return 0; } -static const struct phy_ops ma35_usb_phy_ops = { - .power_on = ma35_usb_phy_power_on, - .power_off = ma35_usb_phy_power_off, - .owner = THIS_MODULE, -}; +static void ma35_role_switch_exit(struct ma35_usb_phy *p) +{ + if (p->role_sw) { + usb_role_switch_unregister(p->role_sw); + p->role_sw = NULL; + } +} + +static struct phy *ma35_usb_phy_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct ma35_usb_phy *p = dev_get_drvdata(dev); + unsigned int idx; + + if (args->args_count == 0) + idx = 0; + else + idx = args->args[0]; + + if (idx >= MA35_PHY_NUM) + return ERR_PTR(-EINVAL); + + return p->port[idx].phy; +} static int ma35_usb_phy_probe(struct platform_device *pdev) { struct phy_provider *provider; - struct ma35_usb_phy *p_phy; - struct phy *phy; + struct ma35_usb_phy *p; + struct clk *clk; + int n, ret; + u32 code; - p_phy = devm_kzalloc(&pdev->dev, sizeof(*p_phy), GFP_KERNEL); - if (!p_phy) + p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) return -ENOMEM; - p_phy->dev = &pdev->dev; - platform_set_drvdata(pdev, p_phy); + p->dev = &pdev->dev; + platform_set_drvdata(pdev, p); + + p->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "nuvoton,sys"); + if (IS_ERR(p->sysreg)) + return dev_err_probe(&pdev->dev, PTR_ERR(p->sysreg), + "failed to get SYS regmap\n"); + + clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(clk), + "failed to get clock\n"); - p_phy->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "nuvoton,sys"); - if (IS_ERR(p_phy->sysreg)) - return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->sysreg), - "Failed to get SYS registers\n"); + for (n = 0; n < MA35_PHY_NUM; n++) { + if (of_property_read_u32_index(pdev->dev.of_node, + "nuvoton,rcalcode", n, &code)) + continue; - p_phy->clk = of_clk_get(pdev->dev.of_node, 0); - if (IS_ERR(p_phy->clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->clk), - "failed to find usb_phy clock\n"); + if (code > 15) + return dev_err_probe(&pdev->dev, -EINVAL, + "rcalcode[%d] %u out of range (0-15)\n", + n, code); - phy = devm_phy_create(&pdev->dev, NULL, &ma35_usb_phy_ops); - if (IS_ERR(phy)) - return dev_err_probe(&pdev->dev, PTR_ERR(phy), "Failed to create PHY\n"); + regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR, + USBPMISCR_RCAL_MASK(n), + code << USBPMISCR_RCAL_SHIFT(n)); + } + + 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); + + for (n = 0; n < MA35_PHY_NUM; n++) { + p->port[n].idx = n; + + p->port[n].phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, + &ma35_usb_phy_ops); + if (IS_ERR(p->port[n].phy)) + return dev_err_probe(&pdev->dev, PTR_ERR(p->port[n].phy), + "failed to create PHY%d\n", n); - phy_set_drvdata(phy, p_phy); + phy_set_drvdata(p->port[n].phy, &p->port[n]); + } + + ret = ma35_role_switch_init(pdev, p); + if (ret) + return ret; - provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); - if (IS_ERR(provider)) + provider = devm_of_phy_provider_register(&pdev->dev, ma35_usb_phy_xlate); + if (IS_ERR(provider)) { + ma35_role_switch_exit(p); return dev_err_probe(&pdev->dev, PTR_ERR(provider), - "Failed to register PHY provider\n"); + "failed to register PHY provider\n"); + } + return 0; } +static void ma35_usb_phy_remove(struct platform_device *pdev) +{ + struct ma35_usb_phy *p = platform_get_drvdata(pdev); + + ma35_role_switch_exit(p); +} + static const struct of_device_id ma35_usb_phy_of_match[] = { - { .compatible = "nuvoton,ma35d1-usb2-phy", }, - { }, + { .compatible = "nuvoton,ma35d1-usb2-phy" }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ma35_usb_phy_of_match); static struct platform_driver ma35_usb_phy_driver = { .probe = ma35_usb_phy_probe, - .driver = { - .name = "ma35d1-usb2-phy", - .of_match_table = ma35_usb_phy_of_match, + .remove = ma35_usb_phy_remove, + .driver = { + .name = "ma35d1-usb2-phy", + .of_match_table = ma35_usb_phy_of_match, }, }; module_platform_driver(ma35_usb_phy_driver); MODULE_DESCRIPTION("Nuvoton ma35d1 USB2.0 PHY driver"); MODULE_AUTHOR("Hui-Ping Chen <hpchen0nvt@gmail.com>"); +MODULE_AUTHOR("Joey Lu <a0987203069@gmail.com>"); MODULE_LICENSE("GPL"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v3 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support @ 2026-07-08 10:36 ` Joey Lu 0 siblings, 0 replies; 18+ messages in thread From: Joey Lu @ 2026-07-08 10:36 UTC (permalink / raw) To: Vinod Koul, Neil Armstrong Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann, Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen, Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel, Joey Lu PHY0 and PHY1 use the same power-on/reset sequence in USBPMISCR, with PHY1 control bits shifted 16 positions relative to PHY0. A separate driver for PHY1 would duplicate this logic, so the existing driver is extended to manage both ports. The original driver polled only DEVCKSTB after releasing PHY0 from reset. When USB0 operates in host mode (USB ID pin floating or tied high) only HSTCKSTB and CK12MSTB assert; DEVCKSTB never sets. Polling exclusively for DEVCKSTB in host mode causes a 1 ms timeout on every phy_init() call from the EHCI driver. The init callback is changed to accept either host-mode or device-mode clock stability, whichever asserts first. The power_on and power_off callbacks are replaced by a single init callback that handles PHY reset and clock-stable polling, because there is no PHY-specific clock gate on MA35D1; the PHY analog block derives its reference from the HXT crystal. A read-only USB role switch is registered for PHY0 to expose the USB0 role to userspace via the standard role-switch sysfs interface. The .set callback returns -EOPNOTSUPP because the hardware mux is fully automatic with no software override path. Signed-off-by: Joey Lu <a0987203069@gmail.com> --- drivers/phy/nuvoton/phy-ma35d1-usb2.c | 279 +++++++++++++++++++------- 1 file changed, 205 insertions(+), 74 deletions(-) diff --git a/drivers/phy/nuvoton/phy-ma35d1-usb2.c b/drivers/phy/nuvoton/phy-ma35d1-usb2.c index 9a459b700ed4..e2eac4f8c5bf 100644 --- a/drivers/phy/nuvoton/phy-ma35d1-usb2.c +++ b/drivers/phy/nuvoton/phy-ma35d1-usb2.c @@ -1,11 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (C) 2024 Nuvoton Technology Corp. + * Nuvoton MA35D1 USB 2.0 PHY driver + * + * Supports PHY0 (USB0 OTG port, shared between DWC2 gadget and EHCI0/OHCI0) + * and PHY1 (USB1 host-only port, used by EHCI1/OHCI1). The hardware mux on + * PHY0 switches automatically via the USB ID pin. + * + * 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> @@ -13,131 +18,257 @@ #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/regmap.h> +#include <linux/usb/role.h> -/* USB PHY Miscellaneous Control Register */ -#define MA35_SYS_REG_USBPMISCR 0x60 -#define PHY0POR BIT(0) /* PHY Power-On Reset Control Bit */ -#define PHY0SUSPEND BIT(1) /* PHY Suspend; 0: suspend, 1: operaion */ -#define PHY0COMN BIT(2) /* PHY Common Block Power-Down Control */ -#define PHY0DEVCKSTB BIT(10) /* PHY 60 MHz UTMI clock stable bit */ +#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) +#define USBPMISCR_PHY_DEVCKSTB(n) BIT(10 + (n) * 16) +/* Mask for control bits (POR, SUSPEND, COMN) */ +#define USBPMISCR_PHY_CTL_MASK(n) (0x7u << ((n) * 16)) +/* Host-mode ready */ +#define USBPMISCR_PHY_HOST_READY(n) (USBPMISCR_PHY_SUSPEND(n) | \ + USBPMISCR_PHY_HSTCKSTB(n) | \ + USBPMISCR_PHY_CK12MSTB(n)) +/* Device-mode ready */ +#define USBPMISCR_PHY_DEV_READY(n) (USBPMISCR_PHY_SUSPEND(n) | \ + USBPMISCR_PHY_DEVCKSTB(n)) +/* RCALCODE: 4-bit resistor trim */ +#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 +/* USB host over-current detect polarity (shared, both ports) */ +#define MISCFCR0_UHOVRCURH BIT(12) + +#define MA35_PHY_NUM 2 + +struct ma35_phy_port { + struct phy *phy; + unsigned int idx; +}; struct ma35_usb_phy { - struct clk *clk; struct device *dev; struct regmap *sysreg; + struct ma35_phy_port port[MA35_PHY_NUM]; + struct usb_role_switch *role_sw; }; -static int ma35_usb_phy_power_on(struct phy *phy) +static int ma35_usb_phy_init(struct phy *phy) { - struct ma35_usb_phy *p_phy = phy_get_drvdata(phy); + struct ma35_phy_port *port = phy_get_drvdata(phy); + struct ma35_usb_phy *p = container_of(port - port->idx, + struct ma35_usb_phy, port[0]); + unsigned int n = port->idx; unsigned int val; int ret; - ret = clk_prepare_enable(p_phy->clk); - if (ret < 0) { - dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret); - return ret; - } + regmap_read(p->sysreg, MA35_SYS_USBPMISCR, &val); - regmap_read(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, &val); - if (val & PHY0SUSPEND) { - /* - * USB PHY0 is in operation mode already - * make sure USB PHY 60 MHz UTMI Interface Clock ready - */ - ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val, - val & PHY0DEVCKSTB, 10, 1000); - if (ret == 0) - return 0; - } + if (val & USBPMISCR_PHY_SUSPEND(n)) + return 0; - /* - * reset USB PHY0. - * wait until USB PHY0 60 MHz UTMI Interface Clock ready - */ - regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND)); + regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR, + USBPMISCR_PHY_CTL_MASK(n), + USBPMISCR_PHY_POR(n) | USBPMISCR_PHY_SUSPEND(n)); udelay(20); - /* make USB PHY0 enter operation mode */ - regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, PHY0SUSPEND); + regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR, + USBPMISCR_PHY_CTL_MASK(n), + USBPMISCR_PHY_SUSPEND(n)); - /* make sure USB PHY 60 MHz UTMI Interface Clock ready */ - ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val, - val & PHY0DEVCKSTB, 10, 1000); - if (ret == -ETIMEDOUT) { - dev_err(p_phy->dev, "Check PHY clock, Timeout: %d\n", ret); - clk_disable_unprepare(p_phy->clk); + if (n == 0) { + ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR, + val, + ((val & USBPMISCR_PHY_HOST_READY(0)) == + USBPMISCR_PHY_HOST_READY(0)) || + ((val & USBPMISCR_PHY_DEV_READY(0)) == + USBPMISCR_PHY_DEV_READY(0)), + 10, 1000); + } else { + ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR, + val, + (val & USBPMISCR_PHY_HOST_READY(n)) == + USBPMISCR_PHY_HOST_READY(n), + 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_usb_phy_power_off(struct phy *phy) +static const struct phy_ops ma35_usb_phy_ops = { + .init = ma35_usb_phy_init, + .owner = THIS_MODULE, +}; + +static int ma35_role_sw_set(struct usb_role_switch *sw, enum usb_role role) +{ + return -EOPNOTSUPP; +} + +static enum usb_role ma35_role_sw_get(struct usb_role_switch *sw) { - struct ma35_usb_phy *p_phy = phy_get_drvdata(phy); + struct ma35_usb_phy *p = usb_role_switch_get_drvdata(sw); + u32 val; + + regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val); + + return (val & PWRONOTP_USBP0ID) ? USB_ROLE_HOST : USB_ROLE_DEVICE; +} + +static int ma35_role_switch_init(struct platform_device *pdev, + struct ma35_usb_phy *p) +{ + struct usb_role_switch_desc sw_desc = {0}; + + sw_desc.set = ma35_role_sw_set; + sw_desc.get = ma35_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"); - clk_disable_unprepare(p_phy->clk); return 0; } -static const struct phy_ops ma35_usb_phy_ops = { - .power_on = ma35_usb_phy_power_on, - .power_off = ma35_usb_phy_power_off, - .owner = THIS_MODULE, -}; +static void ma35_role_switch_exit(struct ma35_usb_phy *p) +{ + if (p->role_sw) { + usb_role_switch_unregister(p->role_sw); + p->role_sw = NULL; + } +} + +static struct phy *ma35_usb_phy_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct ma35_usb_phy *p = dev_get_drvdata(dev); + unsigned int idx; + + if (args->args_count == 0) + idx = 0; + else + idx = args->args[0]; + + if (idx >= MA35_PHY_NUM) + return ERR_PTR(-EINVAL); + + return p->port[idx].phy; +} static int ma35_usb_phy_probe(struct platform_device *pdev) { struct phy_provider *provider; - struct ma35_usb_phy *p_phy; - struct phy *phy; + struct ma35_usb_phy *p; + struct clk *clk; + int n, ret; + u32 code; - p_phy = devm_kzalloc(&pdev->dev, sizeof(*p_phy), GFP_KERNEL); - if (!p_phy) + p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) return -ENOMEM; - p_phy->dev = &pdev->dev; - platform_set_drvdata(pdev, p_phy); + p->dev = &pdev->dev; + platform_set_drvdata(pdev, p); + + p->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "nuvoton,sys"); + if (IS_ERR(p->sysreg)) + return dev_err_probe(&pdev->dev, PTR_ERR(p->sysreg), + "failed to get SYS regmap\n"); + + clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(clk), + "failed to get clock\n"); - p_phy->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "nuvoton,sys"); - if (IS_ERR(p_phy->sysreg)) - return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->sysreg), - "Failed to get SYS registers\n"); + for (n = 0; n < MA35_PHY_NUM; n++) { + if (of_property_read_u32_index(pdev->dev.of_node, + "nuvoton,rcalcode", n, &code)) + continue; - p_phy->clk = of_clk_get(pdev->dev.of_node, 0); - if (IS_ERR(p_phy->clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->clk), - "failed to find usb_phy clock\n"); + if (code > 15) + return dev_err_probe(&pdev->dev, -EINVAL, + "rcalcode[%d] %u out of range (0-15)\n", + n, code); - phy = devm_phy_create(&pdev->dev, NULL, &ma35_usb_phy_ops); - if (IS_ERR(phy)) - return dev_err_probe(&pdev->dev, PTR_ERR(phy), "Failed to create PHY\n"); + regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR, + USBPMISCR_RCAL_MASK(n), + code << USBPMISCR_RCAL_SHIFT(n)); + } + + 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); + + for (n = 0; n < MA35_PHY_NUM; n++) { + p->port[n].idx = n; + + p->port[n].phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, + &ma35_usb_phy_ops); + if (IS_ERR(p->port[n].phy)) + return dev_err_probe(&pdev->dev, PTR_ERR(p->port[n].phy), + "failed to create PHY%d\n", n); - phy_set_drvdata(phy, p_phy); + phy_set_drvdata(p->port[n].phy, &p->port[n]); + } + + ret = ma35_role_switch_init(pdev, p); + if (ret) + return ret; - provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); - if (IS_ERR(provider)) + provider = devm_of_phy_provider_register(&pdev->dev, ma35_usb_phy_xlate); + if (IS_ERR(provider)) { + ma35_role_switch_exit(p); return dev_err_probe(&pdev->dev, PTR_ERR(provider), - "Failed to register PHY provider\n"); + "failed to register PHY provider\n"); + } + return 0; } +static void ma35_usb_phy_remove(struct platform_device *pdev) +{ + struct ma35_usb_phy *p = platform_get_drvdata(pdev); + + ma35_role_switch_exit(p); +} + static const struct of_device_id ma35_usb_phy_of_match[] = { - { .compatible = "nuvoton,ma35d1-usb2-phy", }, - { }, + { .compatible = "nuvoton,ma35d1-usb2-phy" }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ma35_usb_phy_of_match); static struct platform_driver ma35_usb_phy_driver = { .probe = ma35_usb_phy_probe, - .driver = { - .name = "ma35d1-usb2-phy", - .of_match_table = ma35_usb_phy_of_match, + .remove = ma35_usb_phy_remove, + .driver = { + .name = "ma35d1-usb2-phy", + .of_match_table = ma35_usb_phy_of_match, }, }; module_platform_driver(ma35_usb_phy_driver); MODULE_DESCRIPTION("Nuvoton ma35d1 USB2.0 PHY driver"); MODULE_AUTHOR("Hui-Ping Chen <hpchen0nvt@gmail.com>"); +MODULE_AUTHOR("Joey Lu <a0987203069@gmail.com>"); MODULE_LICENSE("GPL"); -- 2.43.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v3 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support 2026-07-08 10:36 ` Joey Lu @ 2026-07-08 10:49 ` sashiko-bot -1 siblings, 0 replies; 18+ messages in thread From: sashiko-bot @ 2026-07-08 10:49 UTC (permalink / raw) To: Joey Lu Cc: conor+dt, linux-phy, vkoul, devicetree, neil.armstrong, olteanv, robh Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] Kconfig Dependency / Build and Probe Regression - [High] Missing Wait for Clock Stability on Already-Operating PHY (Suspend/Resume Regression) - [Medium] Uninitialized variable usage due to ignored regmap_read return value - [Low] Undocumented Functional Changes in Hardware Configuration -- --- Patch [3]: [PATCH v3 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support --- Note: The format of this report is altered due to recitation restrictions. I am providing a free-form summary of the findings without quoting the original patch code. commit 0f7c9811782335bab86ecc4c08f7f6a17839e8c3 Author: Joey Lu <a0987203069@gmail.com> Subject: phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support Summary: This commit extends the existing MA35D1 USB 2.0 PHY driver to manage both PHY0 and PHY1. It updates the initialization logic to support host-mode clock stability polling and registers a read-only USB role switch for PHY0. [Severity: High] In ma35_role_switch_init(), the driver fails the probe if the role switch registration returns an error. Does this break the driver when the role switch framework is disabled? If CONFIG_USB_ROLE_SWITCH is not enabled, the register function returns -ENODEV, causing ma35_role_switch_init() to treat it as a fatal error and unconditionally fail the PHY probe. Furthermore, shouldn't the driver declare a Kconfig dependency on USB_ROLE_SWITCH or handle the -ENODEV return case gracefully? [Severity: High] In ma35_usb_phy_init(), the driver returns early if the suspend bit is already set. Does this skip waiting for clock stability during system resume? On system suspend, the suspend bit remains set. During resume, the PHY crystal might have been stopped and requires time to restabilize. Returning early skips the host/device clock stability polling. The original code explicitly handled this by checking for clock stability even when the suspend bit was already set. Could this lead to controller hangs when the controller proceeds before the PHY clocks are stable? [Severity: Medium] In ma35_role_sw_get(), the return value of regmap_read() is ignored before evaluating the val variable. Can this expose uninitialized kernel stack memory? If the syscon read fails, val will contain uninitialized memory, which is then evaluated and returned to userspace via the role sysfs attribute. This is a pre-existing issue, but the same pattern of ignoring the regmap_read() return value also appears in ma35_usb_phy_init(). Should the return values be checked in both locations? [Severity: Low] In ma35_usb_phy_probe(), new logic is introduced to parse nuvoton,rcalcode and nuvoton,oc-active-high from the device tree and apply them to the resistor trim and over-current polarity registers. Could the commit message be updated to document these functional hardware configuration changes? They are currently completely omitted from the commit history. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260708103606.1462960-1-a0987203069@gmail.com?part=3 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support @ 2026-07-08 10:49 ` sashiko-bot 0 siblings, 0 replies; 18+ messages in thread From: sashiko-bot @ 2026-07-08 10:49 UTC (permalink / raw) To: Joey Lu Cc: conor+dt, linux-phy, vkoul, devicetree, neil.armstrong, olteanv, robh Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] Kconfig Dependency / Build and Probe Regression - [High] Missing Wait for Clock Stability on Already-Operating PHY (Suspend/Resume Regression) - [Medium] Uninitialized variable usage due to ignored regmap_read return value - [Low] Undocumented Functional Changes in Hardware Configuration -- --- Patch [3]: [PATCH v3 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support --- Note: The format of this report is altered due to recitation restrictions. I am providing a free-form summary of the findings without quoting the original patch code. commit 0f7c9811782335bab86ecc4c08f7f6a17839e8c3 Author: Joey Lu <a0987203069@gmail.com> Subject: phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support Summary: This commit extends the existing MA35D1 USB 2.0 PHY driver to manage both PHY0 and PHY1. It updates the initialization logic to support host-mode clock stability polling and registers a read-only USB role switch for PHY0. [Severity: High] In ma35_role_switch_init(), the driver fails the probe if the role switch registration returns an error. Does this break the driver when the role switch framework is disabled? If CONFIG_USB_ROLE_SWITCH is not enabled, the register function returns -ENODEV, causing ma35_role_switch_init() to treat it as a fatal error and unconditionally fail the PHY probe. Furthermore, shouldn't the driver declare a Kconfig dependency on USB_ROLE_SWITCH or handle the -ENODEV return case gracefully? [Severity: High] In ma35_usb_phy_init(), the driver returns early if the suspend bit is already set. Does this skip waiting for clock stability during system resume? On system suspend, the suspend bit remains set. During resume, the PHY crystal might have been stopped and requires time to restabilize. Returning early skips the host/device clock stability polling. The original code explicitly handled this by checking for clock stability even when the suspend bit was already set. Could this lead to controller hangs when the controller proceeds before the PHY clocks are stable? [Severity: Medium] In ma35_role_sw_get(), the return value of regmap_read() is ignored before evaluating the val variable. Can this expose uninitialized kernel stack memory? If the syscon read fails, val will contain uninitialized memory, which is then evaluated and returned to userspace via the role sysfs attribute. This is a pre-existing issue, but the same pattern of ignoring the regmap_read() return value also appears in ma35_usb_phy_init(). Should the return values be checked in both locations? [Severity: Low] In ma35_usb_phy_probe(), new logic is introduced to parse nuvoton,rcalcode and nuvoton,oc-active-high from the device tree and apply them to the resistor trim and over-current polarity registers. Could the commit message be updated to document these functional hardware configuration changes? They are currently completely omitted from the commit history. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260708103606.1462960-1-a0987203069@gmail.com?part=3 ^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-07-14 1:26 UTC | newest] Thread overview: 18+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-08 10:36 [PATCH v3 0/3] phy: nuvoton: extend MA35D1 USB2 PHY driver for dual-port OTG support Joey Lu 2026-07-08 10:36 ` Joey Lu 2026-07-08 10:36 ` [PATCH v3 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port and OTG Joey Lu 2026-07-08 10:36 ` Joey Lu 2026-07-08 10:49 ` sashiko-bot 2026-07-08 10:49 ` sashiko-bot 2026-07-10 8:54 ` Krzysztof Kozlowski 2026-07-10 8:54 ` Krzysztof Kozlowski 2026-07-14 1:26 ` Joey Lu 2026-07-14 1:26 ` Joey Lu 2026-07-08 10:36 ` [PATCH v3 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node Joey Lu 2026-07-08 10:36 ` Joey Lu 2026-07-08 10:48 ` sashiko-bot 2026-07-08 10:48 ` sashiko-bot 2026-07-08 10:36 ` [PATCH v3 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support Joey Lu 2026-07-08 10:36 ` Joey Lu 2026-07-08 10:49 ` sashiko-bot 2026-07-08 10:49 ` sashiko-bot
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.