Skip to contentSkip to conversation

Add Google Tensor SoC USB controller support

PATCH Superseded Roy Luo 4 participants 17 messages Oct 17, 2025 – Nov 7, 2025

Subsystems (5)

v4 of 10 Superseded Latest is v10 →

lore
Share & tools
lore

Roy Luo

author
#0 lore raw

v4 cover Add Google Tensor SoC USB controller support

This series introduces USB controller support for the Google Tensor G5 SoC (codename: Laguna), a new generation of Google silicon first launched with Pixel 10 devices.

The Tensor G5 represents a significant architectural overhaul compared to previous Tensor generations (e.g., gs101), which were based on Samsung Exynos IP. Although the G5 still utilizes Synopsys IP for the USB components, the custom top-level integration introduces a completely new design for clock, reset scheme, register interfaces and programming sequence, necessitating new drivers and device tree bindings.

The USB subsystem on Tensor G5 integrates a Synopsys DWC3 USB 3.1 DRD-Single Port controller with hibernation support, and a custom PHY block comprising Synopsys eUSB2 and USB 3.2/DP combo PHYs. The PHY support is sent as a separate patch series.

---
Changes in v4:
- Separate controller and phy changes into two distinct patch series.
- Rename dwc3 core interrupt as "core".
- Remove u2phy_apb clk/reset (moved to PHY)
- Configure usb2only mode when usb3 phy is not present.
- Adopt pm_ptr PM macros to fix build warnings.
Link to v3: https://lore.kernel.org/linux-usb/[email protected]

Changes in v3:
- Align binding file name with the compatible string
- Simplify the compatible property in binding to a single const value.
- Add descriptive comments and use item list in binding.
- Rename binding entries for clarity and brevity.
Link to v2: https://lore.kernel.org/linux-usb/[email protected]

Changes in v2:
- Reorder patches to present bindings first.
- Update dt binding compatible strings to be SoC-specific (google,gs5-*).
- Better describe the hardware in dt binding commit messages and descriptions.
- Adjust PHY driver commit subjects to use correct prefixes ("phy:").
- Move PHY driver from a subdirectory to drivers/phy/.
Link to v1: https://lore.kernel.org/linux-usb/[email protected]/
---
Roy Luo (2):
dt-bindings: usb: dwc3: Add Google Tensor G5 DWC3
usb: dwc3: Add Google Tensor SoC DWC3 glue driver

 .../bindings/usb/google,gs5-dwc3.yaml         | 135 ++++
 drivers/usb/dwc3/Kconfig                      |  10 +
 drivers/usb/dwc3/Makefile                     |   1 +
 drivers/usb/dwc3/dwc3-google.c                | 608 ++++++++++++++++++
 4 files changed, 754 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/google,gs5-dwc3.yaml
 create mode 100644 drivers/usb/dwc3/dwc3-google.c

base-commit: e5f0a698b34ed76002dc5cff3804a61c80233a7a

Signature · 1 line
2.51.0.858.gf9c4a03a3a-goog
author
#1 lore raw

PATCH v4 1/2 dt-bindings: usb: dwc3: Add Google Tensor G5 DWC3

Document the device tree bindings for the DWC3 USB controller found in Google Tensor SoCs, starting with the G5 generation.

The Tensor G5 silicon represents a complete architectural departure from previous generations (like gs101), including entirely new clock/reset schemes, top-level wrapper and register interface. Consequently, existing Samsung/Exynos DWC3 USB bindings are incompatible, necessitating this new device tree binding.

The USB controller on Tensor G5 is based on Synopsys DWC3 IP and features Dual-Role Device single port with hibernation support.

---
 .../bindings/usb/google,gs5-dwc3.yaml         | 135 ++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/google,gs5-dwc3.yaml
Documentation/devicetree/bindings/usb/google,gs5-dwc3.yaml new file +135 -0
new file mode 100644
index 000000000000..09756bf6fd3c
@@ -0,0 +1,135 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright (c) 2025, Google LLC
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/google,gs5-dwc3.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Google Tensor Series (G5+) DWC3 USB SoC Controller
+
+maintainers:
+  - Roy Luo <[email protected]>
+
+description:
+  Describes the DWC3 USB controller block implemented on Google Tensor SoCs,
+  starting with the G5 generation. Based on Synopsys DWC3 IP, the controller
+  features Dual-Role Device single port with hibernation add-on.
+
+properties:
+  compatible:
+    const: google,gs5-dwc3
+
+  reg:
+    items:
+      - description: Core DWC3 IP registers.
+      - description: USB host controller configuration registers.
+      - description: USB custom interrrupts control registers.
+
+  reg-names:
+    items:
+      - const: dwc3_core
+      - const: host_cfg
+      - const: usbint_cfg
+
+  interrupts:
+    items:
+      - description: Core DWC3 interrupt.
+      - description: High speed power management event for remote wakeup.
+      - description: Super speed power management event for remote wakeup.
+
+  interrupt-names:
+    items:
+      - const: core
+      - const: hs_pme
+      - const: ss_pme
+
+  clocks:
+    items:
+      - description: Non-sticky module clock.
+      - description: Sticky module clock.
+
+  clock-names:
+    items:
+      - const: non_sticky
+      - const: sticky
+
+  resets:
+    items:
+      - description: Non-sticky module reset.
+      - description: Sticky module reset.
+      - description: DRD bus reset.
+      - description: Top-level reset.
+
+  reset-names:
+    items:
+      - const: non_sticky
+      - const: sticky
+      - const: drd_bus
+      - const: top
+
+  power-domains:
+    items:
+      - description: Power switchable domain, the child of top domain.
+          Turning it on puts the controller into full power state,
+          turning it off puts the controller into power gated state.
+      - description: Top domain, the parent of power switchable domain.
+          Turning it on puts the controller into power gated state,
+          turning it off completely shuts off the controller.
+
+  power-domain-names:
+    items:
+      - const: psw
+      - const: top
+
+  iommus:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - interrupts
+  - interrupt-names
+  - clocks
+  - clock-names
+  - resets
+  - reset-names
+  - power-domains
+  - power-domain-names
+
+allOf:
+  - $ref: snps,dwc3-common.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        usb@c400000 {
+            compatible = "google,gs5-dwc3";
+            reg = <0 0x0c400000  0 0xd060>, <0 0x0c450000 0 0x14>, <0 0x0c450020 0 0x43>;
+            reg-names = "dwc3_core", "host_cfg", "usbint_cfg";
+            interrupts = <GIC_SPI 580 IRQ_TYPE_LEVEL_HIGH 0>,
+                         <GIC_SPI 597 IRQ_TYPE_LEVEL_HIGH 0>,
+                         <GIC_SPI 598 IRQ_TYPE_LEVEL_HIGH 0>;
+            interrupt-names = "core", "hs_pme", "ss_pme";
+            clocks = <&hsion_usbc_non_sticky_clk>,  <&hsion_usbc_sticky_clk>;
+            clock-names = "non_sticky", "sticky";
+            resets = <&hsion_resets_usbc_non_sticky>, <&hsion_resets_usbc_sticky>,
+                     <&hsion_resets_usb_drd_bus>, <&hsion_resets_usb_top>;
+            reset-names = "non_sticky", "sticky", "drd_bus", "top";
+            power-domains = <&hsio_n_usb_psw>, <&hsio_n_usb>;
+            power-domain-names = "psw", "top";
+            phys = <&usb_phy 0>;
+            phy-names = "usb2-phy";
+            snps,quirk-frame-length-adjustment = <0x20>;
+            snps,gfladj-refclk-lpm-sel-quirk;
+            snps,incr-burst-type-adjustment = <4>;
+        };
+    };
+...
Signature · 1 line
2.51.0.858.gf9c4a03a3a-goog
maintainer Reviewed-by on v4 1/2
#2 lore raw

Re: [PATCH v4 1/2] dt-bindings: usb: dwc3: Add Google Tensor G5 DWC3

Quoted 9 lines · Roy Luo

Document the device tree bindings for the DWC3 USB controller found in Google Tensor SoCs, starting with the G5 generation.

The Tensor G5 silicon represents a complete architectural departure from previous generations (like gs101), including entirely new clock/reset schemes, top-level wrapper and register interface. Consequently, existing Samsung/Exynos DWC3 USB bindings are incompatible, necessitating this new device tree binding.

Quoted 8 lines

The USB controller on Tensor G5 is based on Synopsys DWC3 IP and features Dual-Role Device single port with hibernation support.

---
 .../bindings/usb/google,gs5-dwc3.yaml         | 135 ++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/google,gs5-dwc3.yaml

Best regards,
Krzysztof

author
#3 lore raw

PATCH v4 2/2 usb: dwc3: Add Google Tensor SoC DWC3 glue driver

In reply to Roy Luo

Add support for the DWC3 USB controller found on Google Tensor G5. The controller features dual-role functionality and hibernation.

The primary focus is implementing hibernation support in host mode, enabling the controller to enter a low-power state (D3). This is particularly relevant during system power state transition and runtime power management for power efficiency. Highlights:
- Align suspend callback with dwc3_suspend_common() for deciding between a full teardown and hibernation in host mode.
- Integration with psw (power switchable) and top power domains, managing their states and device links to support hibernation.
- A notifier callback dwc3_google_usb_psw_pd_notifier() for psw power domain events to manage controller state transitions to/from D3.
- Coordination of the non_sticky reset during power state transitions, asserting it on D3 entry and deasserting on D0 entry in hibernation scenario.
- Handling of high-speed and super-speed PME interrupts that are generated by remote wakeup during hibernation.

---
 drivers/usb/dwc3/Kconfig       |  10 +
 drivers/usb/dwc3/Makefile      |   1 +
 drivers/usb/dwc3/dwc3-google.c | 608 +++++++++++++++++++++++++++++++++
 3 files changed, 619 insertions(+)
 create mode 100644 drivers/usb/dwc3/dwc3-google.c
drivers/usb/dwc3/Kconfig +10 -0
index 310d182e10b5..467515d5f937 100644
@@ -189,4 +189,14 @@ config USB_DWC3_RTK
 	  or dual-role mode.
 	  Say 'Y' or 'M' if you have such device.
 
+config USB_DWC3_GOOGLE
+	tristate "Google Platform"
+	depends on OF && COMMON_CLK && RESET_CONTROLLER
+	default n
+	help
+	  Support the DesignWare Core USB3 IP found on Google Tensor
+	  SoCs, starting with the G5 generation. This driver includes
+	  support for hibernation in host mode.
+	  Say 'Y' or 'M' if you have one such device.
+
 endif
drivers/usb/dwc3/Makefile +1 -0
index 830e6c9e5fe0..a94982630657 100644
@@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_IMX8MP)		+= dwc3-imx8mp.o
 obj-$(CONFIG_USB_DWC3_XILINX)		+= dwc3-xilinx.o
 obj-$(CONFIG_USB_DWC3_OCTEON)		+= dwc3-octeon.o
 obj-$(CONFIG_USB_DWC3_RTK)		+= dwc3-rtk.o
+obj-$(CONFIG_USB_DWC3_GOOGLE)		+= dwc3-google.o
drivers/usb/dwc3/dwc3-google.c new file +608 -0

608 lines not on this page · raw message

Signature · 1 line
2.51.0.858.gf9c4a03a3a-goog
maintainer
#4 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 26 lines · Roy Luo

Add support for the DWC3 USB controller found on Google Tensor G5. The controller features dual-role functionality and hibernation.

The primary focus is implementing hibernation support in host mode, enabling the controller to enter a low-power state (D3). This is particularly relevant during system power state transition and runtime power management for power efficiency. Highlights:
- Align suspend callback with dwc3_suspend_common() for deciding between a full teardown and hibernation in host mode.
- Integration with psw (power switchable) and top power domains, managing their states and device links to support hibernation.
- A notifier callback dwc3_google_usb_psw_pd_notifier() for psw power domain events to manage controller state transitions to/from D3.
- Coordination of the non_sticky reset during power state transitions, asserting it on D3 entry and deasserting on D0 entry in hibernation scenario.
- Handling of high-speed and super-speed PME interrupts that are generated by remote wakeup during hibernation.

Quoted 7 lines
---
 drivers/usb/dwc3/Kconfig       |  10 +
 drivers/usb/dwc3/Makefile      |   1 +
 drivers/usb/dwc3/dwc3-google.c | 608 +++++++++++++++++++++++++++++++++
 3 files changed, 619 insertions(+)
 create mode 100644 drivers/usb/dwc3/dwc3-google.c

Sorry, I've been tied up with some internal projects and haven't reviewed this in detail yet. I think this change deserve more time and attention, and thus the delay.

One of the things that stood out is that you're assuming the host suspend is always hibernation. While it's true that xhci suspend would go through the xhci hibernation flow, however, that needs to communicate to the glue driver here. For example, if the xhci driver is not bound, and the device goes into suspend, we may go through this hibernation flow when we should not. But maybe that's already handle? I need to check.

In any case, there are multiple players (xhci, xhci-plat, dwc3, glue) here, and I need more time to review. Appologies if this will take longer than usual.

Thanks,
Thinh

author
#5 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 36 lines · Thinh Nguyen
Quoted 26 lines · Roy Luo

Add support for the DWC3 USB controller found on Google Tensor G5. The controller features dual-role functionality and hibernation.

The primary focus is implementing hibernation support in host mode, enabling the controller to enter a low-power state (D3). This is particularly relevant during system power state transition and runtime power management for power efficiency. Highlights:
- Align suspend callback with dwc3_suspend_common() for deciding between a full teardown and hibernation in host mode.
- Integration with psw (power switchable) and top power domains, managing their states and device links to support hibernation.
- A notifier callback dwc3_google_usb_psw_pd_notifier() for psw power domain events to manage controller state transitions to/from D3.
- Coordination of the non_sticky reset during power state transitions, asserting it on D3 entry and deasserting on D0 entry in hibernation scenario.
- Handling of high-speed and super-speed PME interrupts that are generated by remote wakeup during hibernation.

Quoted 7 lines
---
 drivers/usb/dwc3/Kconfig       |  10 +
 drivers/usb/dwc3/Makefile      |   1 +
 drivers/usb/dwc3/dwc3-google.c | 608 +++++++++++++++++++++++++++++++++
 3 files changed, 619 insertions(+)
 create mode 100644 drivers/usb/dwc3/dwc3-google.c
Quoted 4 lines

Sorry, I've been tied up with some internal projects and haven't reviewed this in detail yet. I think this change deserve more time and attention, and thus the delay.

No worry, thanks for paying attention to this patch!

Quoted 7 lines

One of the things that stood out is that you're assuming the host suspend is always hibernation. While it's true that xhci suspend would go through the xhci hibernation flow, however, that needs to communicate to the glue driver here. For example, if the xhci driver is not bound, and the device goes into suspend, we may go through this hibernation flow when we should not. But maybe that's already handle? I need to check.

Actually the host suspend doesn't always go into hibernation. In dwc3_google_suspend(), this driver follows the logic in dwc3_suspend_common() in determining whether to do a full tear down or enter hibernation.

| static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
| ...
| case DWC3_GCTL_PRTCAP_HOST:
|   if (!PMSG_IS_AUTO(msg) && !device_may_wakeup(dwc->dev)) {
|     dwc3_core_exit(dwc);
|     break;
|   }

The glue and the dwc3 core have to be aligned here as there's no way to enter hibernation if the core has been completely torn down. As for xhci, I don't see any logic that's conditional on hibernation so I didn't pay much attention to it, please correct me if I'm wrong.

Quoted 7 lines

In any case, there are multiple players (xhci, xhci-plat, dwc3, glue) here, and I need more time to review. Appologies if this will take longer than usual.

Thanks,
Thinh

Looking forward to working with you to land this patch.

Thanks,
Roy Luo

maintainer
#6 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 52 lines · Roy Luo
Quoted 36 lines · Thinh Nguyen
Quoted 26 lines · Roy Luo

Add support for the DWC3 USB controller found on Google Tensor G5. The controller features dual-role functionality and hibernation.

The primary focus is implementing hibernation support in host mode, enabling the controller to enter a low-power state (D3). This is particularly relevant during system power state transition and runtime power management for power efficiency. Highlights:
- Align suspend callback with dwc3_suspend_common() for deciding between a full teardown and hibernation in host mode.
- Integration with psw (power switchable) and top power domains, managing their states and device links to support hibernation.
- A notifier callback dwc3_google_usb_psw_pd_notifier() for psw power domain events to manage controller state transitions to/from D3.
- Coordination of the non_sticky reset during power state transitions, asserting it on D3 entry and deasserting on D0 entry in hibernation scenario.
- Handling of high-speed and super-speed PME interrupts that are generated by remote wakeup during hibernation.

Quoted 7 lines
---
 drivers/usb/dwc3/Kconfig       |  10 +
 drivers/usb/dwc3/Makefile      |   1 +
 drivers/usb/dwc3/dwc3-google.c | 608 +++++++++++++++++++++++++++++++++
 3 files changed, 619 insertions(+)
 create mode 100644 drivers/usb/dwc3/dwc3-google.c
Quoted 4 lines

Sorry, I've been tied up with some internal projects and haven't reviewed this in detail yet. I think this change deserve more time and attention, and thus the delay.

No worry, thanks for paying attention to this patch!

Quoted 7 lines

One of the things that stood out is that you're assuming the host suspend is always hibernation. While it's true that xhci suspend would go through the xhci hibernation flow, however, that needs to communicate to the glue driver here. For example, if the xhci driver is not bound, and the device goes into suspend, we may go through this hibernation flow when we should not. But maybe that's already handle? I need to check.

Quoted 4 lines

Actually the host suspend doesn't always go into hibernation. In dwc3_google_suspend(), this driver follows the logic in dwc3_suspend_common() in determining whether to do a full tear down or enter hibernation.

If we go through the xhci_suspend() and xhci_resume(), then there will be saving and restoring of xHC states. And what you said is true. if we go through the dwc3_core_exit, the states may be lost because of the phy teardown. Maybe I should not mix the term hibernation for xhci save/restore states.

Quoted 10 lines
| static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
| ...
| case DWC3_GCTL_PRTCAP_HOST:
|   if (!PMSG_IS_AUTO(msg) && !device_may_wakeup(dwc->dev)) {
|     dwc3_core_exit(dwc);
|     break;
|   }
Quoted 4 lines

The glue and the dwc3 core have to be aligned here as there's no way to enter hibernation if the core has been completely torn down. As for xhci, I don't see any logic that's conditional on hibernation so I didn't pay much attention to it, please correct me if I'm wrong.

There's no conditional save states for xhci. It always saves states on suspend. But to pass the power to the PMU and to the save and restore of the sticky states when entering D3, I think the logic you noted to check that is fine.

BR,
Thinh

Quoted 13 lines
Quoted 8 lines

In any case, there are multiple players (xhci, xhci-plat, dwc3, glue) here, and I need more time to review. Appologies if this will take longer than usual.

Thanks,
Thinh

Looking forward to working with you to land this patch.

Thanks,
Roy Luo

maintainer
#7 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

In reply to Roy Luo
Quoted 249 lines · Roy Luo

Add support for the DWC3 USB controller found on Google Tensor G5. The controller features dual-role functionality and hibernation.

The primary focus is implementing hibernation support in host mode, enabling the controller to enter a low-power state (D3). This is particularly relevant during system power state transition and runtime power management for power efficiency. Highlights:
- Align suspend callback with dwc3_suspend_common() for deciding between a full teardown and hibernation in host mode.
- Integration with psw (power switchable) and top power domains, managing their states and device links to support hibernation.
- A notifier callback dwc3_google_usb_psw_pd_notifier() for psw power domain events to manage controller state transitions to/from D3.
- Coordination of the non_sticky reset during power state transitions, asserting it on D3 entry and deasserting on D0 entry in hibernation scenario.
- Handling of high-speed and super-speed PME interrupts that are generated by remote wakeup during hibernation.

---
 drivers/usb/dwc3/Kconfig       |  10 +
 drivers/usb/dwc3/Makefile      |   1 +
 drivers/usb/dwc3/dwc3-google.c | 608 +++++++++++++++++++++++++++++++++
 3 files changed, 619 insertions(+)
 create mode 100644 drivers/usb/dwc3/dwc3-google.c
drivers/usb/dwc3/Kconfig +10 -0
index 310d182e10b5..467515d5f937 100644
@@ -189,4 +189,14 @@ config USB_DWC3_RTK
 	  or dual-role mode.
 	  Say 'Y' or 'M' if you have such device.
 
+config USB_DWC3_GOOGLE
+	tristate "Google Platform"
+	depends on OF && COMMON_CLK && RESET_CONTROLLER
+	default n
+	help
+	  Support the DesignWare Core USB3 IP found on Google Tensor
+	  SoCs, starting with the G5 generation. This driver includes
+	  support for hibernation in host mode.
+	  Say 'Y' or 'M' if you have one such device.
+
 endif
drivers/usb/dwc3/Makefile +1 -0
index 830e6c9e5fe0..a94982630657 100644
@@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_IMX8MP)		+= dwc3-imx8mp.o
 obj-$(CONFIG_USB_DWC3_XILINX)		+= dwc3-xilinx.o
 obj-$(CONFIG_USB_DWC3_OCTEON)		+= dwc3-octeon.o
 obj-$(CONFIG_USB_DWC3_RTK)		+= dwc3-rtk.o
+obj-$(CONFIG_USB_DWC3_GOOGLE)		+= dwc3-google.o
drivers/usb/dwc3/dwc3-google.c new file +182 -0
new file mode 100644
index 000000000000..d3fec6fb4dcc
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwc3-google.c - Google DWC3 Specific Glue Layer
+ *
+ * Copyright (c) 2025, Google LLC
+ * Author: Roy Luo <[email protected]>
+ */
+
+#include <linux/of.h>
+#include <linux/bitfield.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/pm_domain.h>
+#include <linux/iopoll.h>
+#include "core.h"
+#include "glue.h"
+
+/* HOST CFG registers */
+#define HC_STATUS_OFFSET 0x0
+#define HC_STATUS_CURRENT_POWER_STATE_U2PMU GENMASK(1, 0)
+#define HC_STATUS_CURRENT_POWER_STATE_U3PMU GENMASK(4, 3)
+
+#define HOST_CFG1_OFFSET 0x4
+#define HOST_CFG1_PME_EN BIT(3)
+#define HOST_CFG1_PM_POWER_STATE_REQUEST GENMASK(5, 4)
+#define HOST_CFG1_PM_POWER_STATE_D0 0x0
+#define HOST_CFG1_PM_POWER_STATE_D3 0x3
+
+/* USBINT registers */
+#define USBINT_CFG1_OFFSET 0x0
+#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK BIT(2)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK BIT(3)
+#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN BIT(8)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN BIT(9)
+#define USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR BIT(14)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR BIT(15)
+
+#define USBINT_STATUS_OFFSET 0x4
+#define USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW BIT(2)
+#define USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW BIT(3)
+
+#define USBCS_TOP_CTRL_CFG1_OFFSET 0xc
+#define USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE BIT(5)
+
+#define DWC3_GOOGLE_MAX_RESETS	4
+
+struct dwc3_google {
+	struct device		*dev;
+	struct dwc3		dwc;
+	struct clk_bulk_data	*clks;
+	int			num_clks;
+	struct reset_control_bulk_data rsts[DWC3_GOOGLE_MAX_RESETS];
+	int			num_rsts;
+	struct reset_control	*non_sticky_rst;
+	struct device		*usb_psw_pd;
+	struct device_link	*usb_psw_pd_dl;
+	struct notifier_block	usb_psw_pd_nb;
+	struct device		*usb_top_pd;
+	struct device_link	*usb_top_pd_dl;
+	void __iomem		*host_cfg_base;
+	void __iomem		*usbint_cfg_base;
+	int			hs_pme_irq;
+	int			ss_pme_irq;
+	bool			is_usb2only;
+	bool			is_hibernation;
+};
+
+#define to_dwc3_google(d) container_of((d), struct dwc3_google, dwc)
+
+static int dwc3_google_rst_init(struct dwc3_google *google)
+{
+	int ret;
+
+	google->num_rsts = 4;
+	google->rsts[0].id = "non_sticky";
+	google->rsts[1].id = "sticky";
+	google->rsts[2].id = "drd_bus";
+	google->rsts[3].id = "top";
+
+	ret = devm_reset_control_bulk_get_exclusive(google->dev,
+						    google->num_rsts,
+						    google->rsts);
+
+	if (ret < 0)
+		return ret;
+
+	google->non_sticky_rst = google->rsts[0].rstc;
+
+	return 0;
+}
+
+static int dwc3_google_set_pmu_state(struct dwc3_google *google, int state)
+{
+	u32 reg;
+	int ret;
+
+	reg = readl(google->host_cfg_base + HOST_CFG1_OFFSET);
+	reg &= ~HOST_CFG1_PM_POWER_STATE_REQUEST;
+	reg |= (FIELD_PREP(HOST_CFG1_PM_POWER_STATE_REQUEST, state) |
+		HOST_CFG1_PME_EN);
+	writel(reg, google->host_cfg_base + HOST_CFG1_OFFSET);
+
+	ret = readl_poll_timeout(google->host_cfg_base + HC_STATUS_OFFSET, reg,
+				 (FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U2PMU, reg) == state &&
+				  FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U3PMU, reg) == state),
+				 10, 10000);
+
+	if (ret)
+		dev_err(google->dev, "failed to set PMU state %d\n", state);
+
+	return ret;
+}
+
+/*
+ * Clear pme interrupts and report their status.
+ * The hardware requires write-1 then write-0 sequence to clear the interrupt bits.
+ */
+static u32 dwc3_google_clear_pme_irqs(struct dwc3_google *google)
+{
+	u32 irq_status, reg_set, reg_clear;
+
+	irq_status = readl(google->usbint_cfg_base + USBINT_STATUS_OFFSET);
+	irq_status &= (USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW |
+		       USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW);
+	if (!irq_status)
+		return irq_status;
+
+	reg_set = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+	reg_clear = reg_set;
+	if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW) {
+		reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
+		reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
+	}
+	if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW) {
+		reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
+		reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
+	}
+
+	writel(reg_set, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+	writel(reg_clear, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+	return irq_status;
+}
+
+static void dwc3_google_enable_pme_irq(struct dwc3_google *google)
+{
+	u32 reg;
+
+	reg = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+	reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
+		 USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
+	reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
+		USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
+	writel(reg, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+	enable_irq(google->hs_pme_irq);
+	enable_irq(google->ss_pme_irq);
+	enable_irq_wake(google->hs_pme_irq);
+	enable_irq_wake(google->ss_pme_irq);
+}
+
+static void dwc3_google_disable_pme_irq(struct dwc3_google *google)
+{
+	u32 reg;
+
+	reg = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+	reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
+		 USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
+	reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
+		USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
+	writel(reg, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+	disable_irq_wake(google->hs_pme_irq);
+	disable_irq_wake(google->ss_pme_irq);
+	disable_irq_nosync(google->hs_pme_irq);
+	disable_irq_nosync(google->ss_pme_irq);
+}
+

drivers/usb/dwc3/dwc3-google.c line 192

Quoted 10 lines
drivers/usb/dwc3/dwc3-google.c new file +10 -0
+static irqreturn_t dwc3_google_resume_irq(int irq, void *data)
+{
+	struct dwc3_google      *google = data;
+	struct dwc3             *dwc = &google->dwc;
+	u32 irq_status, dr_role;
+
+	irq_status = dwc3_google_clear_pme_irqs(google);
+	dr_role = dwc->current_dr_role;
+
+	if (!irq_status || !google->is_hibernation ||

Any handling of race condition with is_hibernation?

drivers/usb/dwc3/dwc3-google.c line 195

Quoted 3 lines
drivers/usb/dwc3/dwc3-google.c new file +3 -0
+	    dr_role != DWC3_GCTL_PRTCAP_HOST) {
+		dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
+			 irq, google->is_hibernation, dr_role);

Should we limit this print and do we need this to be dev_warn? It may be noisy wouldn't it.

Quoted 35 lines
drivers/usb/dwc3/dwc3-google.c new file +35 -0
+		return IRQ_HANDLED;
+	}
+
+	if (dwc->xhci)
+		pm_runtime_resume(&dwc->xhci->dev);
+
+	return IRQ_HANDLED;
+}
+
+static int dwc3_google_request_irq(struct dwc3_google *google, struct platform_device *pdev,
+				   const char *irq_name, const char *req_name)
+{
+	int ret;
+	int irq;
+
+	irq = platform_get_irq_byname(pdev, irq_name);
+	if (irq < 0) {
+		dev_err(google->dev, "invalid irq name %s\n", irq_name);
+		return irq;
+	}
+
+	irq_set_status_flags(irq, IRQ_NOAUTOEN);
+	ret = devm_request_threaded_irq(google->dev, irq, NULL,
+					dwc3_google_resume_irq,
+					IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+					req_name, google);
+	if (ret < 0) {
+		dev_err(google->dev, "failed to request irq %s\n", req_name);
+		return ret;
+	}
+
+	return irq;
+}
+
+static int dwc3_google_usb_psw_pd_notifier(struct notifier_block *nb, unsigned long action, void *d)

drivers/usb/dwc3/dwc3-google.c line 240

Quoted 10 lines
drivers/usb/dwc3/dwc3-google.c new file +10 -0
+{
+	struct dwc3_google *google = container_of(nb, struct dwc3_google, usb_psw_pd_nb);
+	int ret;
+
+	if (!google->is_hibernation)
+		return NOTIFY_OK;
+
+	if (action == GENPD_NOTIFY_OFF) {
+		dev_dbg(google->dev, "enter D3 power state\n");
+		dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D3);

What happen when we failed to set the PMU state?

Quoted 61 lines
drivers/usb/dwc3/dwc3-google.c new file +61 -0
+		ret = reset_control_assert(google->non_sticky_rst);
+		if (ret)
+			dev_err(google->dev, "non sticky reset assert failed: %d\n", ret);
+	} else if (action == GENPD_NOTIFY_ON) {
+		dev_dbg(google->dev, "enter D0 power state\n");
+		dwc3_google_clear_pme_irqs(google);
+		ret = reset_control_deassert(google->non_sticky_rst);
+		if (ret)
+			dev_err(google->dev, "non sticky reset deassert failed: %d\n", ret);
+		dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D0);
+	}
+
+	return NOTIFY_OK;
+}
+
+static void dwc3_google_pm_domain_deinit(struct dwc3_google *google)
+{
+	if (google->usb_top_pd_dl)
+		device_link_del(google->usb_top_pd_dl);
+
+	if (!IS_ERR_OR_NULL(google->usb_top_pd)) {
+		device_set_wakeup_capable(google->usb_top_pd, false);
+		dev_pm_domain_detach(google->usb_top_pd, true);
+	}
+
+	if (google->usb_psw_pd_dl)
+		device_link_del(google->usb_psw_pd_dl);
+
+	if (!IS_ERR_OR_NULL(google->usb_psw_pd)) {
+		dev_pm_genpd_remove_notifier(google->usb_psw_pd);
+		dev_pm_domain_detach(google->usb_psw_pd, true);
+	}
+}
+
+static int dwc3_google_pm_domain_init(struct dwc3_google *google)
+{
+	int ret;
+
+	/*
+	 * Establish PM RUNTIME link between dwc dev and its power domain usb_psw_pd,
+	 * register notifier block to handle hibernation.
+	 */
+	google->usb_psw_pd = dev_pm_domain_attach_by_name(google->dev, "psw");
+	if (IS_ERR_OR_NULL(google->usb_psw_pd)) {
+		dev_err(google->dev, "failed to get psw pd");
+		ret = google->usb_psw_pd ? PTR_ERR(google->usb_psw_pd) : -ENODATA;
+		return ret;
+	}
+
+	google->usb_psw_pd_nb.notifier_call = dwc3_google_usb_psw_pd_notifier;
+	ret = dev_pm_genpd_add_notifier(google->usb_psw_pd, &google->usb_psw_pd_nb);
+	if (ret) {
+		dev_err(google->dev, "failed to add psw pd notifier");
+		goto err;
+	}
+
+	google->usb_psw_pd_dl = device_link_add(google->dev, google->usb_psw_pd,
+						DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
+						DL_FLAG_RPM_ACTIVE);
+	if (!google->usb_psw_pd_dl) {
+		dev_err(google->usb_psw_pd, "failed to add device link");

drivers/usb/dwc3/dwc3-google.c line 311

Quoted 10 lines
drivers/usb/dwc3/dwc3-google.c new file +10 -0
+		ret = -ENODEV;
+		goto err;
+	}
+
+	/*
+	 * usb_top_pd is the parent power domain of usb_psw_pd. Keeping usb_top_pd on
+	 * while usb_psw_pd is off places the controller in a power-gated state,
+	 * essential for hibernation. Acquire a handle to usb_top_pd and sets it as
+	 * wakeup-capable to allow the domain to be left on during system suspend.
+	 */

I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and the google->dev are working together in the glue here, particularly why usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this glued? Do you do anything except setting wakeup-capable?

BR,
Thinh

Quoted 300 lines
drivers/usb/dwc3/dwc3-google.c new file +297 -0
+	google->usb_top_pd = dev_pm_domain_attach_by_name(google->dev, "top");
+	if (IS_ERR_OR_NULL(google->usb_top_pd)) {
+		dev_err(google->dev, "failed to get top pd");
+		ret = google->usb_top_pd ? PTR_ERR(google->usb_top_pd) : -ENODATA;
+		goto err;
+	}
+	device_set_wakeup_capable(google->usb_top_pd, true);
+
+	google->usb_top_pd_dl = device_link_add(google->dev, google->usb_top_pd,
+						DL_FLAG_STATELESS);
+	if (!google->usb_top_pd_dl) {
+		dev_err(google->usb_top_pd, "failed to add device link");
+		ret = -ENODEV;
+		goto err;
+	}
+
+	return 0;
+
+err:
+	dwc3_google_pm_domain_deinit(google);
+
+	return ret;
+}
+
+static void dwc3_google_program_usb2only(struct dwc3_google *google)
+{
+	u32 reg;
+
+	reg = readl(google->usbint_cfg_base + USBCS_TOP_CTRL_CFG1_OFFSET);
+	reg |= USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE;
+	writel(reg, google->usbint_cfg_base + USBCS_TOP_CTRL_CFG1_OFFSET);
+}
+
+static int dwc3_google_probe(struct platform_device *pdev)
+{
+	struct dwc3_probe_data	probe_data = {};
+	struct device		*dev = &pdev->dev;
+	struct dwc3_google	*google;
+	struct resource		*res;
+	int			ret;
+
+	google = devm_kzalloc(&pdev->dev, sizeof(*google), GFP_KERNEL);
+	if (!google)
+		return -ENOMEM;
+
+	google->dev = &pdev->dev;
+
+	ret = dwc3_google_pm_domain_init(google);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "failed to init pdom\n");
+
+	google->host_cfg_base =
+		devm_platform_ioremap_resource_byname(pdev, "host_cfg");
+	if (IS_ERR(google->host_cfg_base)) {
+		return dev_err_probe(dev, PTR_ERR(google->host_cfg_base),
+				     "invalid host cfg\n");
+	}
+
+	google->usbint_cfg_base =
+		devm_platform_ioremap_resource_byname(pdev, "usbint_cfg");
+	if (IS_ERR(google->usbint_cfg_base)) {
+		return dev_err_probe(dev, PTR_ERR(google->usbint_cfg_base),
+				     "invalid usbint cfg\n");
+	}
+
+	if (device_property_match_string(dev, "phy-names", "usb3-phy") < 0) {
+		google->is_usb2only = true;
+		dwc3_google_program_usb2only(google);
+	}
+
+	ret = devm_clk_bulk_get_all_enabled(dev, &google->clks);
+	if (ret < 0) {
+		ret = dev_err_probe(dev, ret, "failed to get and enable clks\n");
+		goto err_deinit_pdom;
+	}
+	google->num_clks = ret;
+
+	ret = dwc3_google_rst_init(google);
+	if (ret) {
+		ret = dev_err_probe(dev, ret, "failed to get resets\n");
+		goto err_deinit_pdom;
+	}
+
+	ret = reset_control_bulk_deassert(google->num_rsts, google->rsts);
+	if (ret) {
+		ret = dev_err_probe(dev, ret, "failed to deassert rsts\n");
+		goto err_deinit_pdom;
+	}
+
+	ret = dwc3_google_request_irq(google, pdev, "hs_pme", "USB HS wakeup");
+	if (ret < 0) {
+		ret = dev_err_probe(dev, ret, "failed to request hs pme irq");
+		goto err_reset_assert;
+	}
+	google->hs_pme_irq = ret;
+
+	ret = dwc3_google_request_irq(google, pdev, "ss_pme", "USB SS wakeup");
+	if (ret < 0) {
+		ret = dev_err_probe(dev, ret, "failed to request ss pme irq");
+		goto err_reset_assert;
+	}
+	google->ss_pme_irq = ret;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dwc3_core");
+	if (!res) {
+		ret = dev_err_probe(dev, -ENODEV, "invalid dwc3 core memory\n");
+		goto err_reset_assert;
+	}
+
+	device_init_wakeup(dev, true);
+
+	google->dwc.dev = dev;
+	probe_data.dwc = &google->dwc;
+	probe_data.res = res;
+	probe_data.ignore_clocks_and_resets = true;
+	ret = dwc3_core_probe(&probe_data);
+	if (ret)  {
+		ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n");
+		goto err_reset_assert;
+	}
+
+	return 0;
+
+err_reset_assert:
+	reset_control_bulk_assert(google->num_rsts, google->rsts);
+
+err_deinit_pdom:
+	dwc3_google_pm_domain_deinit(google);
+
+	return ret;
+}
+
+static void dwc3_google_remove(struct platform_device *pdev)
+{
+	struct dwc3 *dwc = platform_get_drvdata(pdev);
+	struct dwc3_google *google = to_dwc3_google(dwc);
+
+	dwc3_core_remove(&google->dwc);
+
+	reset_control_bulk_assert(google->num_rsts, google->rsts);
+
+	dwc3_google_pm_domain_deinit(google);
+}
+
+static int dwc3_google_suspend(struct dwc3_google *google, pm_message_t msg)
+{
+	if (pm_runtime_suspended(google->dev))
+		return 0;
+
+	if (google->dwc.current_dr_role == DWC3_GCTL_PRTCAP_HOST) {
+		/*
+		 * Follow dwc3_suspend_common() guidelines for deciding between
+		 * a full teardown and hibernation.
+		 */
+		if (PMSG_IS_AUTO(msg) || device_may_wakeup(google->dev)) {
+			dev_dbg(google->dev, "enter hibernation");
+			pm_runtime_get_sync(google->usb_top_pd);
+			device_wakeup_enable(google->usb_top_pd);
+			dwc3_google_enable_pme_irq(google);
+			google->is_hibernation = true;
+			return 0;
+		}
+	}
+
+	reset_control_bulk_assert(google->num_rsts, google->rsts);
+	clk_bulk_disable_unprepare(google->num_clks, google->clks);
+
+	return 0;
+}
+
+static int dwc3_google_resume(struct dwc3_google *google, pm_message_t msg)
+{
+	int ret;
+
+	if (google->is_hibernation) {
+		dev_dbg(google->dev, "exit hibernation");
+		dwc3_google_disable_pme_irq(google);
+		device_wakeup_disable(google->usb_top_pd);
+		pm_runtime_put_sync(google->usb_top_pd);
+		google->is_hibernation = false;
+		return 0;
+	}
+
+	if (google->is_usb2only)
+		dwc3_google_program_usb2only(google);
+
+	ret = clk_bulk_prepare_enable(google->num_clks, google->clks);
+	if (ret)
+		return ret;
+
+	ret = reset_control_bulk_deassert(google->num_rsts, google->rsts);
+	if (ret) {
+		clk_bulk_disable_unprepare(google->num_clks, google->clks);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int dwc3_google_pm_suspend(struct device *dev)
+{
+	struct dwc3 *dwc = dev_get_drvdata(dev);
+	struct dwc3_google *google = to_dwc3_google(dwc);
+	int ret;
+
+	ret = dwc3_pm_suspend(&google->dwc);
+	if (ret)
+		return ret;
+
+	return dwc3_google_suspend(google, PMSG_SUSPEND);
+}
+
+static int dwc3_google_pm_resume(struct device *dev)
+{
+	struct dwc3 *dwc = dev_get_drvdata(dev);
+	struct dwc3_google *google = to_dwc3_google(dwc);
+	int ret;
+
+	ret = dwc3_google_resume(google, PMSG_RESUME);
+	if (ret)
+		return ret;
+
+	return dwc3_pm_resume(&google->dwc);
+}
+
+static void dwc3_google_complete(struct device *dev)
+{
+	struct dwc3 *dwc = dev_get_drvdata(dev);
+
+	dwc3_pm_complete(dwc);
+}
+
+static int dwc3_google_prepare(struct device *dev)
+{
+	struct dwc3 *dwc = dev_get_drvdata(dev);
+
+	return dwc3_pm_prepare(dwc);
+}
+
+static int dwc3_google_runtime_suspend(struct device *dev)
+{
+	struct dwc3 *dwc = dev_get_drvdata(dev);
+	struct dwc3_google *google = to_dwc3_google(dwc);
+	int ret;
+
+	ret = dwc3_runtime_suspend(&google->dwc);
+	if (ret)
+		return ret;
+
+	return dwc3_google_suspend(google, PMSG_AUTO_SUSPEND);
+}
+
+static int dwc3_google_runtime_resume(struct device *dev)
+{
+	struct dwc3 *dwc = dev_get_drvdata(dev);
+	struct dwc3_google *google = to_dwc3_google(dwc);
+	int ret;
+
+	ret = dwc3_google_resume(google, PMSG_AUTO_RESUME);
+	if (ret)
+		return ret;
+
+	return dwc3_runtime_resume(&google->dwc);
+}
+
+static int dwc3_google_runtime_idle(struct device *dev)
+{
+	return dwc3_runtime_idle(dev_get_drvdata(dev));
+}
+
+static const struct dev_pm_ops dwc3_google_dev_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(dwc3_google_pm_suspend, dwc3_google_pm_resume)
+	RUNTIME_PM_OPS(dwc3_google_runtime_suspend, dwc3_google_runtime_resume,
+		       dwc3_google_runtime_idle)
+	.complete = pm_sleep_ptr(dwc3_google_complete),
+	.prepare = pm_sleep_ptr(dwc3_google_prepare),
+};
+
+static const struct of_device_id dwc3_google_of_match[] = {
+	{ .compatible = "google,gs5-dwc3" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, dwc3_google_of_match);
+
+static struct platform_driver dwc3_google_driver = {
+	.probe		= dwc3_google_probe,
+	.remove		= dwc3_google_remove,
+	.driver		= {
+		.name	= "google-dwc3",
+		.pm	= pm_ptr(&dwc3_google_dev_pm_ops),
+		.of_match_table	= dwc3_google_of_match,
+	},
+};
+
+module_platform_driver(dwc3_google_driver);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DesignWare DWC3 Google Glue Driver");
Signature · 1 line
2.51.0.858.gf9c4a03a3a-goog
author
#8 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 255 lines · Thinh Nguyen
Quoted 253 lines · Roy Luo

Add support for the DWC3 USB controller found on Google Tensor G5. The controller features dual-role functionality and hibernation.

The primary focus is implementing hibernation support in host mode, enabling the controller to enter a low-power state (D3). This is particularly relevant during system power state transition and runtime power management for power efficiency. Highlights:
- Align suspend callback with dwc3_suspend_common() for deciding between a full teardown and hibernation in host mode.
- Integration with psw (power switchable) and top power domains, managing their states and device links to support hibernation.
- A notifier callback dwc3_google_usb_psw_pd_notifier() for psw power domain events to manage controller state transitions to/from D3.
- Coordination of the non_sticky reset during power state transitions, asserting it on D3 entry and deasserting on D0 entry in hibernation scenario.
- Handling of high-speed and super-speed PME interrupts that are generated by remote wakeup during hibernation.

---
 drivers/usb/dwc3/Kconfig       |  10 +
 drivers/usb/dwc3/Makefile      |   1 +
 drivers/usb/dwc3/dwc3-google.c | 608 +++++++++++++++++++++++++++++++++
 3 files changed, 619 insertions(+)
 create mode 100644 drivers/usb/dwc3/dwc3-google.c
drivers/usb/dwc3/Kconfig +10 -0
index 310d182e10b5..467515d5f937 100644
@@ -189,4 +189,14 @@ config USB_DWC3_RTK
        or dual-role mode.
        Say 'Y' or 'M' if you have such device.
 
+config USB_DWC3_GOOGLE
+     tristate "Google Platform"
+     depends on OF && COMMON_CLK && RESET_CONTROLLER
+     default n
+     help
+       Support the DesignWare Core USB3 IP found on Google Tensor
+       SoCs, starting with the G5 generation. This driver includes
+       support for hibernation in host mode.
+       Say 'Y' or 'M' if you have one such device.
+
 endif
drivers/usb/dwc3/Makefile +1 -0
index 830e6c9e5fe0..a94982630657 100644
@@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_IMX8MP)               += dwc3-imx8mp.o
 obj-$(CONFIG_USB_DWC3_XILINX)                += dwc3-xilinx.o
 obj-$(CONFIG_USB_DWC3_OCTEON)                += dwc3-octeon.o
 obj-$(CONFIG_USB_DWC3_RTK)           += dwc3-rtk.o
+obj-$(CONFIG_USB_DWC3_GOOGLE)                += dwc3-google.o
drivers/usb/dwc3/dwc3-google.c new file +186 -0
new file mode 100644
index 000000000000..d3fec6fb4dcc
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwc3-google.c - Google DWC3 Specific Glue Layer
+ *
+ * Copyright (c) 2025, Google LLC
+ * Author: Roy Luo <[email protected]>
+ */
+
+#include <linux/of.h>
+#include <linux/bitfield.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/pm_domain.h>
+#include <linux/iopoll.h>
+#include "core.h"
+#include "glue.h"
+
+/* HOST CFG registers */
+#define HC_STATUS_OFFSET 0x0
+#define HC_STATUS_CURRENT_POWER_STATE_U2PMU GENMASK(1, 0)
+#define HC_STATUS_CURRENT_POWER_STATE_U3PMU GENMASK(4, 3)
+
+#define HOST_CFG1_OFFSET 0x4
+#define HOST_CFG1_PME_EN BIT(3)
+#define HOST_CFG1_PM_POWER_STATE_REQUEST GENMASK(5, 4)
+#define HOST_CFG1_PM_POWER_STATE_D0 0x0
+#define HOST_CFG1_PM_POWER_STATE_D3 0x3
+
+/* USBINT registers */
+#define USBINT_CFG1_OFFSET 0x0
+#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK BIT(2)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK BIT(3)
+#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN BIT(8)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN BIT(9)
+#define USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR BIT(14)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR BIT(15)
+
+#define USBINT_STATUS_OFFSET 0x4
+#define USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW BIT(2)
+#define USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW BIT(3)
+
+#define USBCS_TOP_CTRL_CFG1_OFFSET 0xc
+#define USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE BIT(5)
+
+#define DWC3_GOOGLE_MAX_RESETS       4
+
+struct dwc3_google {
+     struct device           *dev;
+     struct dwc3             dwc;
+     struct clk_bulk_data    *clks;
+     int                     num_clks;
+     struct reset_control_bulk_data rsts[DWC3_GOOGLE_MAX_RESETS];
+     int                     num_rsts;
+     struct reset_control    *non_sticky_rst;
+     struct device           *usb_psw_pd;
+     struct device_link      *usb_psw_pd_dl;
+     struct notifier_block   usb_psw_pd_nb;
+     struct device           *usb_top_pd;
+     struct device_link      *usb_top_pd_dl;
+     void __iomem            *host_cfg_base;
+     void __iomem            *usbint_cfg_base;
+     int                     hs_pme_irq;
+     int                     ss_pme_irq;
+     bool                    is_usb2only;
+     bool                    is_hibernation;
+};
+
+#define to_dwc3_google(d) container_of((d), struct dwc3_google, dwc)
+
+static int dwc3_google_rst_init(struct dwc3_google *google)
+{
+     int ret;
+
+     google->num_rsts = 4;
+     google->rsts[0].id = "non_sticky";
+     google->rsts[1].id = "sticky";
+     google->rsts[2].id = "drd_bus";
+     google->rsts[3].id = "top";
+
+     ret = devm_reset_control_bulk_get_exclusive(google->dev,
+                                                 google->num_rsts,
+                                                 google->rsts);
+
+     if (ret < 0)
+             return ret;
+
+     google->non_sticky_rst = google->rsts[0].rstc;
+
+     return 0;
+}
+
+static int dwc3_google_set_pmu_state(struct dwc3_google *google, int state)
+{
+     u32 reg;
+     int ret;
+
+     reg = readl(google->host_cfg_base + HOST_CFG1_OFFSET);
+     reg &= ~HOST_CFG1_PM_POWER_STATE_REQUEST;
+     reg |= (FIELD_PREP(HOST_CFG1_PM_POWER_STATE_REQUEST, state) |
+             HOST_CFG1_PME_EN);
+     writel(reg, google->host_cfg_base + HOST_CFG1_OFFSET);
+
+     ret = readl_poll_timeout(google->host_cfg_base + HC_STATUS_OFFSET, reg,
+                              (FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U2PMU, reg) == state &&
+                               FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U3PMU, reg) == state),
+                              10, 10000);
+
+     if (ret)
+             dev_err(google->dev, "failed to set PMU state %d\n", state);
+
+     return ret;
+}
+
+/*
+ * Clear pme interrupts and report their status.
+ * The hardware requires write-1 then write-0 sequence to clear the interrupt bits.
+ */
+static u32 dwc3_google_clear_pme_irqs(struct dwc3_google *google)
+{
+     u32 irq_status, reg_set, reg_clear;
+
+     irq_status = readl(google->usbint_cfg_base + USBINT_STATUS_OFFSET);
+     irq_status &= (USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW |
+                    USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW);
+     if (!irq_status)
+             return irq_status;
+
+     reg_set = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     reg_clear = reg_set;
+     if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW) {
+             reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
+             reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
+     }
+     if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW) {
+             reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
+             reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
+     }
+
+     writel(reg_set, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     writel(reg_clear, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+     return irq_status;
+}
+
+static void dwc3_google_enable_pme_irq(struct dwc3_google *google)
+{
+     u32 reg;
+
+     reg = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
+              USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
+     reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
+             USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
+     writel(reg, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+     enable_irq(google->hs_pme_irq);
+     enable_irq(google->ss_pme_irq);
+     enable_irq_wake(google->hs_pme_irq);
+     enable_irq_wake(google->ss_pme_irq);
+}
+
+static void dwc3_google_disable_pme_irq(struct dwc3_google *google)
+{
+     u32 reg;
+
+     reg = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
+              USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
+     reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
+             USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
+     writel(reg, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+     disable_irq_wake(google->hs_pme_irq);
+     disable_irq_wake(google->ss_pme_irq);
+     disable_irq_nosync(google->hs_pme_irq);
+     disable_irq_nosync(google->ss_pme_irq);
+}
+
+static irqreturn_t dwc3_google_resume_irq(int irq, void *data)
+{
+     struct dwc3_google      *google = data;
+     struct dwc3             *dwc = &google->dwc;
Quoted 8 lines

drivers/usb/dwc3/dwc3-google.c line 192

Quoted 6 lines
drivers/usb/dwc3/dwc3-google.c new file +6 -0
+     u32 irq_status, dr_role;
+
+     irq_status = dwc3_google_clear_pme_irqs(google);
+     dr_role = dwc->current_dr_role;
+
+     if (!irq_status || !google->is_hibernation ||

Any handling of race condition with is_hibernation?

IIUC, you're referring to the race between
- pme irq handler: dwc3_google_resume_irq() reads is_hibernation
- suspend/resume work: sets and unsets is_hibernation

Possible scenarios:
1. Suspend into hibernation: everything is serialized.

        dwc3_google_suspend() sets is_hibernation
    -> usb psw pdom is turned off
    -> controller enters D3
    -> pme irq could be triggered
    -> pme irq handler
2.1 Resume from hibernation: irq handler wins the race.
           pme irq handler (is_hibernation = true) triggers resume
       -> usb psw pdom is turned on
       -> dwc3_google_resume() unset is_hibernation
2.2 Resume from hibernation: resume work wins the race.
           usb psw pdom is turned on
       -> dwc3_google_resume() unset is_hibernation
       -> pme irq handler (is_hibernation = false) does nothing

So far I don't see any issues in any of the scenarios, please let me know if otherwise.

Quoted 7 lines

drivers/usb/dwc3/dwc3-google.c line 195

Quoted 4 lines
drivers/usb/dwc3/dwc3-google.c new file +3 -0
+         dr_role != DWC3_GCTL_PRTCAP_HOST) {
+             dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
+                      irq, google->is_hibernation, dr_role);

Should we limit this print and do we need this to be dev_warn? It may be noisy wouldn't it.

Ack, will make it WARN_ONCE in the next version.

Quoted 40 lines
Quoted 40 lines
drivers/usb/dwc3/dwc3-google.c new file +39 -0
+             return IRQ_HANDLED;
+     }
+
+     if (dwc->xhci)
+             pm_runtime_resume(&dwc->xhci->dev);
+
+     return IRQ_HANDLED;
+}
+
+static int dwc3_google_request_irq(struct dwc3_google *google, struct platform_device *pdev,
+                                const char *irq_name, const char *req_name)
+{
+     int ret;
+     int irq;
+
+     irq = platform_get_irq_byname(pdev, irq_name);
+     if (irq < 0) {
+             dev_err(google->dev, "invalid irq name %s\n", irq_name);
+             return irq;
+     }
+
+     irq_set_status_flags(irq, IRQ_NOAUTOEN);
+     ret = devm_request_threaded_irq(google->dev, irq, NULL,
+                                     dwc3_google_resume_irq,
+                                     IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+                                     req_name, google);
+     if (ret < 0) {
+             dev_err(google->dev, "failed to request irq %s\n", req_name);
+             return ret;
+     }
+
+     return irq;
+}
+
+static int dwc3_google_usb_psw_pd_notifier(struct notifier_block *nb, unsigned long action, void *d)
+{
+     struct dwc3_google *google = container_of(nb, struct dwc3_google, usb_psw_pd_nb);
+     int ret;
+
Quoted 8 lines

drivers/usb/dwc3/dwc3-google.c line 240

Quoted 6 lines
drivers/usb/dwc3/dwc3-google.c new file +6 -0
+     if (!google->is_hibernation)
+             return NOTIFY_OK;
+
+     if (action == GENPD_NOTIFY_OFF) {
+             dev_dbg(google->dev, "enter D3 power state\n");
+             dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D3);

What happen when we failed to set the PMU state?

Apparently there must be something wrong with the hardware. Currently we only throw some error logs as no reliable recovery mechanism is known.

Quoted 78 lines
Quoted 62 lines
drivers/usb/dwc3/dwc3-google.c new file +61 -0
+             ret = reset_control_assert(google->non_sticky_rst);
+             if (ret)
+                     dev_err(google->dev, "non sticky reset assert failed: %d\n", ret);
+     } else if (action == GENPD_NOTIFY_ON) {
+             dev_dbg(google->dev, "enter D0 power state\n");
+             dwc3_google_clear_pme_irqs(google);
+             ret = reset_control_deassert(google->non_sticky_rst);
+             if (ret)
+                     dev_err(google->dev, "non sticky reset deassert failed: %d\n", ret);
+             dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D0);
+     }
+
+     return NOTIFY_OK;
+}
+
+static void dwc3_google_pm_domain_deinit(struct dwc3_google *google)
+{
+     if (google->usb_top_pd_dl)
+             device_link_del(google->usb_top_pd_dl);
+
+     if (!IS_ERR_OR_NULL(google->usb_top_pd)) {
+             device_set_wakeup_capable(google->usb_top_pd, false);
+             dev_pm_domain_detach(google->usb_top_pd, true);
+     }
+
+     if (google->usb_psw_pd_dl)
+             device_link_del(google->usb_psw_pd_dl);
+
+     if (!IS_ERR_OR_NULL(google->usb_psw_pd)) {
+             dev_pm_genpd_remove_notifier(google->usb_psw_pd);
+             dev_pm_domain_detach(google->usb_psw_pd, true);
+     }
+}
+
+static int dwc3_google_pm_domain_init(struct dwc3_google *google)
+{
+     int ret;
+
+     /*
+      * Establish PM RUNTIME link between dwc dev and its power domain usb_psw_pd,
+      * register notifier block to handle hibernation.
+      */
+     google->usb_psw_pd = dev_pm_domain_attach_by_name(google->dev, "psw");
+     if (IS_ERR_OR_NULL(google->usb_psw_pd)) {
+             dev_err(google->dev, "failed to get psw pd");
+             ret = google->usb_psw_pd ? PTR_ERR(google->usb_psw_pd) : -ENODATA;
+             return ret;
+     }
+
+     google->usb_psw_pd_nb.notifier_call = dwc3_google_usb_psw_pd_notifier;
+     ret = dev_pm_genpd_add_notifier(google->usb_psw_pd, &google->usb_psw_pd_nb);
+     if (ret) {
+             dev_err(google->dev, "failed to add psw pd notifier");
+             goto err;
+     }
+
+     google->usb_psw_pd_dl = device_link_add(google->dev, google->usb_psw_pd,
+                                             DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
+                                             DL_FLAG_RPM_ACTIVE);
+     if (!google->usb_psw_pd_dl) {
+             dev_err(google->usb_psw_pd, "failed to add device link");

drivers/usb/dwc3/dwc3-google.c line 311

Quoted 10 lines
drivers/usb/dwc3/dwc3-google.c new file +10 -0
+             ret = -ENODEV;
+             goto err;
+     }
+
+     /*
+      * usb_top_pd is the parent power domain of usb_psw_pd. Keeping usb_top_pd on
+      * while usb_psw_pd is off places the controller in a power-gated state,
+      * essential for hibernation. Acquire a handle to usb_top_pd and sets it as
+      * wakeup-capable to allow the domain to be left on during system suspend.
+      */

I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and the google->dev are working together in the glue here, particularly why usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this glued? Do you do anything except setting wakeup-capable?

Quoted 2 lines

BR,
Thinh

To provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd are the logical power domains to represent the 3 power states.
- Full Power: usb_psw_pd ON, usb_top_p ON.
- Power Gated: usb_psw_pd OFF, usb_top_p ON.
- Off: usb_psw_pd OFF, usb_top_p OFF.

To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to usb_top_pd and would cast a vote to keep it ON when attempting to enter hibernation. In addition, the usb_psw_pd runtime PM is directly tied to google->dev so that usb_psw_pd would be OFF when google->dev suspends. Together, the usb power domain would reach Power Gated state when device suspends.

I hope this information helps.

Thanks,
Roy Luo

Quoted 301 lines
Quoted 301 lines
drivers/usb/dwc3/dwc3-google.c new file +297 -1
+     google->usb_top_pd = dev_pm_domain_attach_by_name(google->dev, "top");
+     if (IS_ERR_OR_NULL(google->usb_top_pd)) {
+             dev_err(google->dev, "failed to get top pd");
+             ret = google->usb_top_pd ? PTR_ERR(google->usb_top_pd) : -ENODATA;
+             goto err;
+     }
+     device_set_wakeup_capable(google->usb_top_pd, true);
+
+     google->usb_top_pd_dl = device_link_add(google->dev, google->usb_top_pd,
+                                             DL_FLAG_STATELESS);
+     if (!google->usb_top_pd_dl) {
+             dev_err(google->usb_top_pd, "failed to add device link");
+             ret = -ENODEV;
+             goto err;
+     }
+
+     return 0;
+
+err:
+     dwc3_google_pm_domain_deinit(google);
+
+     return ret;
+}
+
+static void dwc3_google_program_usb2only(struct dwc3_google *google)
+{
+     u32 reg;
+
+     reg = readl(google->usbint_cfg_base + USBCS_TOP_CTRL_CFG1_OFFSET);
+     reg |= USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE;
+     writel(reg, google->usbint_cfg_base + USBCS_TOP_CTRL_CFG1_OFFSET);
+}
+
+static int dwc3_google_probe(struct platform_device *pdev)
+{
+     struct dwc3_probe_data  probe_data = {};
+     struct device           *dev = &pdev->dev;
+     struct dwc3_google      *google;
+     struct resource         *res;
+     int                     ret;
+
+     google = devm_kzalloc(&pdev->dev, sizeof(*google), GFP_KERNEL);
+     if (!google)
+             return -ENOMEM;
+
+     google->dev = &pdev->dev;
+
+     ret = dwc3_google_pm_domain_init(google);
+     if (ret < 0)
+             return dev_err_probe(dev, ret, "failed to init pdom\n");
+
+     google->host_cfg_base =
+             devm_platform_ioremap_resource_byname(pdev, "host_cfg");
+     if (IS_ERR(google->host_cfg_base)) {
+             return dev_err_probe(dev, PTR_ERR(google->host_cfg_base),
+                                  "invalid host cfg\n");
+     }
+
+     google->usbint_cfg_base =
+             devm_platform_ioremap_resource_byname(pdev, "usbint_cfg");
+     if (IS_ERR(google->usbint_cfg_base)) {
+             return dev_err_probe(dev, PTR_ERR(google->usbint_cfg_base),
+                                  "invalid usbint cfg\n");
+     }
+
+     if (device_property_match_string(dev, "phy-names", "usb3-phy") < 0) {
+             google->is_usb2only = true;
+             dwc3_google_program_usb2only(google);
+     }
+
+     ret = devm_clk_bulk_get_all_enabled(dev, &google->clks);
+     if (ret < 0) {
+             ret = dev_err_probe(dev, ret, "failed to get and enable clks\n");
+             goto err_deinit_pdom;
+     }
+     google->num_clks = ret;
+
+     ret = dwc3_google_rst_init(google);
+     if (ret) {
+             ret = dev_err_probe(dev, ret, "failed to get resets\n");
+             goto err_deinit_pdom;
+     }
+
+     ret = reset_control_bulk_deassert(google->num_rsts, google->rsts);
+     if (ret) {
+             ret = dev_err_probe(dev, ret, "failed to deassert rsts\n");
+             goto err_deinit_pdom;
+     }
+
+     ret = dwc3_google_request_irq(google, pdev, "hs_pme", "USB HS wakeup");
+     if (ret < 0) {
+             ret = dev_err_probe(dev, ret, "failed to request hs pme irq");
+             goto err_reset_assert;
+     }
+     google->hs_pme_irq = ret;
+
+     ret = dwc3_google_request_irq(google, pdev, "ss_pme", "USB SS wakeup");
+     if (ret < 0) {
+             ret = dev_err_probe(dev, ret, "failed to request ss pme irq");
+             goto err_reset_assert;
+     }
+     google->ss_pme_irq = ret;
+
+     res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dwc3_core");
+     if (!res) {
+             ret = dev_err_probe(dev, -ENODEV, "invalid dwc3 core memory\n");
+             goto err_reset_assert;
+     }
+
+     device_init_wakeup(dev, true);
+
+     google->dwc.dev = dev;
+     probe_data.dwc = &google->dwc;
+     probe_data.res = res;
+     probe_data.ignore_clocks_and_resets = true;
+     ret = dwc3_core_probe(&probe_data);
+     if (ret)  {
+             ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n");
+             goto err_reset_assert;
+     }
+
+     return 0;
+
+err_reset_assert:
+     reset_control_bulk_assert(google->num_rsts, google->rsts);
+
+err_deinit_pdom:
+     dwc3_google_pm_domain_deinit(google);
+
+     return ret;
+}
+
+static void dwc3_google_remove(struct platform_device *pdev)
+{
+     struct dwc3 *dwc = platform_get_drvdata(pdev);
+     struct dwc3_google *google = to_dwc3_google(dwc);
+
+     dwc3_core_remove(&google->dwc);
+
+     reset_control_bulk_assert(google->num_rsts, google->rsts);
+
+     dwc3_google_pm_domain_deinit(google);
+}
+
+static int dwc3_google_suspend(struct dwc3_google *google, pm_message_t msg)
+{
+     if (pm_runtime_suspended(google->dev))
+             return 0;
+
+     if (google->dwc.current_dr_role == DWC3_GCTL_PRTCAP_HOST) {
+             /*
+              * Follow dwc3_suspend_common() guidelines for deciding between
+              * a full teardown and hibernation.
+              */
+             if (PMSG_IS_AUTO(msg) || device_may_wakeup(google->dev)) {
+                     dev_dbg(google->dev, "enter hibernation");
+                     pm_runtime_get_sync(google->usb_top_pd);
+                     device_wakeup_enable(google->usb_top_pd);
+                     dwc3_google_enable_pme_irq(google);
+                     google->is_hibernation = true;
+                     return 0;
+             }
+     }
+
+     reset_control_bulk_assert(google->num_rsts, google->rsts);
+     clk_bulk_disable_unprepare(google->num_clks, google->clks);
+
+     return 0;
+}
+
+static int dwc3_google_resume(struct dwc3_google *google, pm_message_t msg)
+{
+     int ret;
+
+     if (google->is_hibernation) {
+             dev_dbg(google->dev, "exit hibernation");
+             dwc3_google_disable_pme_irq(google);
+             device_wakeup_disable(google->usb_top_pd);
+             pm_runtime_put_sync(google->usb_top_pd);
+             google->is_hibernation = false;
+             return 0;
+     }
+
+     if (google->is_usb2only)
+             dwc3_google_program_usb2only(google);
+
+     ret = clk_bulk_prepare_enable(google->num_clks, google->clks);
+     if (ret)
+             return ret;
+
+     ret = reset_control_bulk_deassert(google->num_rsts, google->rsts);
+     if (ret) {
+             clk_bulk_disable_unprepare(google->num_clks, google->clks);
+             return ret;
+     }
+
+     return 0;
+}
+
+static int dwc3_google_pm_suspend(struct device *dev)
+{
+     struct dwc3 *dwc = dev_get_drvdata(dev);
+     struct dwc3_google *google = to_dwc3_google(dwc);
+     int ret;
+
+     ret = dwc3_pm_suspend(&google->dwc);
+     if (ret)
+             return ret;
+
+     return dwc3_google_suspend(google, PMSG_SUSPEND);
+}
+
+static int dwc3_google_pm_resume(struct device *dev)
+{
+     struct dwc3 *dwc = dev_get_drvdata(dev);
+     struct dwc3_google *google = to_dwc3_google(dwc);
+     int ret;
+
+     ret = dwc3_google_resume(google, PMSG_RESUME);
+     if (ret)
+             return ret;
+
+     return dwc3_pm_resume(&google->dwc);
+}
+
+static void dwc3_google_complete(struct device *dev)
+{
+     struct dwc3 *dwc = dev_get_drvdata(dev);
+
+     dwc3_pm_complete(dwc);
+}
+
+static int dwc3_google_prepare(struct device *dev)
+{
+     struct dwc3 *dwc = dev_get_drvdata(dev);
+
+     return dwc3_pm_prepare(dwc);
+}
+
+static int dwc3_google_runtime_suspend(struct device *dev)
+{
+     struct dwc3 *dwc = dev_get_drvdata(dev);
+     struct dwc3_google *google = to_dwc3_google(dwc);
+     int ret;
+
+     ret = dwc3_runtime_suspend(&google->dwc);
+     if (ret)
+             return ret;
+
+     return dwc3_google_suspend(google, PMSG_AUTO_SUSPEND);
+}
+
+static int dwc3_google_runtime_resume(struct device *dev)
+{
+     struct dwc3 *dwc = dev_get_drvdata(dev);
+     struct dwc3_google *google = to_dwc3_google(dwc);
+     int ret;
+
+     ret = dwc3_google_resume(google, PMSG_AUTO_RESUME);
+     if (ret)
+             return ret;
+
+     return dwc3_runtime_resume(&google->dwc);
+}
+
+static int dwc3_google_runtime_idle(struct device *dev)
+{
+     return dwc3_runtime_idle(dev_get_drvdata(dev));
+}
+
+static const struct dev_pm_ops dwc3_google_dev_pm_ops = {
+     SYSTEM_SLEEP_PM_OPS(dwc3_google_pm_suspend, dwc3_google_pm_resume)
+     RUNTIME_PM_OPS(dwc3_google_runtime_suspend, dwc3_google_runtime_resume,
+                    dwc3_google_runtime_idle)
+     .complete = pm_sleep_ptr(dwc3_google_complete),
+     .prepare = pm_sleep_ptr(dwc3_google_prepare),
+};
+
+static const struct of_device_id dwc3_google_of_match[] = {
+     { .compatible = "google,gs5-dwc3" },
+     { }
+};
+MODULE_DEVICE_TABLE(of, dwc3_google_of_match);
+
+static struct platform_driver dwc3_google_driver = {
+     .probe          = dwc3_google_probe,
+     .remove         = dwc3_google_remove,
+     .driver         = {
+             .name   = "google-dwc3",
+             .pm     = pm_ptr(&dwc3_google_dev_pm_ops),
+             .of_match_table = dwc3_google_of_match,
+     },
+};
+
+module_platform_driver(dwc3_google_driver);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DesignWare DWC3 Google Glue Driver");
--

2.51.0.858.gf9c4a03a3a-goog

#9 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 8 lines · Roy Luo
Quoted 6 lines

drivers/usb/dwc3/dwc3-google.c line 195

Quoted 3 lines
drivers/usb/dwc3/dwc3-google.c new file +3 -0
+         dr_role != DWC3_GCTL_PRTCAP_HOST) {
+             dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
+                      irq, google->is_hibernation, dr_role);

Should we limit this print and do we need this to be dev_warn? It may be noisy wouldn't it.

Ack, will make it WARN_ONCE in the next version.

So you really want to panic your system if this happens (remember, the HUGE majority of Linux systems run with panic-on-warn enabled)?

Please do not, handle the issue, dump a message to the log if you really need to, and move on, don't crash.

thanks,

greg k-h

author
#10 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 14 lines · Greg Kroah-Hartman
Quoted 8 lines · Roy Luo
Quoted 6 lines

drivers/usb/dwc3/dwc3-google.c line 195

Quoted 3 lines
drivers/usb/dwc3/dwc3-google.c new file +3 -0
+         dr_role != DWC3_GCTL_PRTCAP_HOST) {
+             dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
+                      irq, google->is_hibernation, dr_role);

Should we limit this print and do we need this to be dev_warn? It may be noisy wouldn't it.

Ack, will make it WARN_ONCE in the next version.

So you really want to panic your system if this happens (remember, the HUGE majority of Linux systems run with panic-on-warn enabled)?

Quoted 6 lines

Please do not, handle the issue, dump a message to the log if you really need to, and move on, don't crash.

thanks,

greg k-h

Thanks for pointing this out, I overlooked the panic-on-warn scenario. This case is actually very rare, I haven't seen this on the field and I feel it does no harm to the system even it it were to happen, having a debug log should still be useful if we were to debug anything specific to this interrupt. I will make it dev_dbg.

Thanks,
Roy Luo

maintainer
#11 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

In reply to Roy Luo
Quoted 269 lines · Roy Luo
Quoted 255 lines · Thinh Nguyen
Quoted 253 lines · Roy Luo

Add support for the DWC3 USB controller found on Google Tensor G5. The controller features dual-role functionality and hibernation.

The primary focus is implementing hibernation support in host mode, enabling the controller to enter a low-power state (D3). This is particularly relevant during system power state transition and runtime power management for power efficiency. Highlights:
- Align suspend callback with dwc3_suspend_common() for deciding between a full teardown and hibernation in host mode.
- Integration with psw (power switchable) and top power domains, managing their states and device links to support hibernation.
- A notifier callback dwc3_google_usb_psw_pd_notifier() for psw power domain events to manage controller state transitions to/from D3.
- Coordination of the non_sticky reset during power state transitions, asserting it on D3 entry and deasserting on D0 entry in hibernation scenario.
- Handling of high-speed and super-speed PME interrupts that are generated by remote wakeup during hibernation.

---
 drivers/usb/dwc3/Kconfig       |  10 +
 drivers/usb/dwc3/Makefile      |   1 +
 drivers/usb/dwc3/dwc3-google.c | 608 +++++++++++++++++++++++++++++++++
 3 files changed, 619 insertions(+)
 create mode 100644 drivers/usb/dwc3/dwc3-google.c
drivers/usb/dwc3/Kconfig +10 -0
index 310d182e10b5..467515d5f937 100644
@@ -189,4 +189,14 @@ config USB_DWC3_RTK
        or dual-role mode.
        Say 'Y' or 'M' if you have such device.
 
+config USB_DWC3_GOOGLE
+     tristate "Google Platform"
+     depends on OF && COMMON_CLK && RESET_CONTROLLER
+     default n
+     help
+       Support the DesignWare Core USB3 IP found on Google Tensor
+       SoCs, starting with the G5 generation. This driver includes
+       support for hibernation in host mode.
+       Say 'Y' or 'M' if you have one such device.
+
 endif
drivers/usb/dwc3/Makefile +1 -0
index 830e6c9e5fe0..a94982630657 100644
@@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_IMX8MP)               += dwc3-imx8mp.o
 obj-$(CONFIG_USB_DWC3_XILINX)                += dwc3-xilinx.o
 obj-$(CONFIG_USB_DWC3_OCTEON)                += dwc3-octeon.o
 obj-$(CONFIG_USB_DWC3_RTK)           += dwc3-rtk.o
+obj-$(CONFIG_USB_DWC3_GOOGLE)                += dwc3-google.o
drivers/usb/dwc3/dwc3-google.c new file +186 -0
new file mode 100644
index 000000000000..d3fec6fb4dcc
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwc3-google.c - Google DWC3 Specific Glue Layer
+ *
+ * Copyright (c) 2025, Google LLC
+ * Author: Roy Luo <[email protected]>
+ */
+
+#include <linux/of.h>
+#include <linux/bitfield.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/pm_domain.h>
+#include <linux/iopoll.h>
+#include "core.h"
+#include "glue.h"
+
+/* HOST CFG registers */
+#define HC_STATUS_OFFSET 0x0
+#define HC_STATUS_CURRENT_POWER_STATE_U2PMU GENMASK(1, 0)
+#define HC_STATUS_CURRENT_POWER_STATE_U3PMU GENMASK(4, 3)
+
+#define HOST_CFG1_OFFSET 0x4
+#define HOST_CFG1_PME_EN BIT(3)
+#define HOST_CFG1_PM_POWER_STATE_REQUEST GENMASK(5, 4)
+#define HOST_CFG1_PM_POWER_STATE_D0 0x0
+#define HOST_CFG1_PM_POWER_STATE_D3 0x3
+
+/* USBINT registers */
+#define USBINT_CFG1_OFFSET 0x0
+#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK BIT(2)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK BIT(3)
+#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN BIT(8)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN BIT(9)
+#define USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR BIT(14)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR BIT(15)
+
+#define USBINT_STATUS_OFFSET 0x4
+#define USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW BIT(2)
+#define USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW BIT(3)
+
+#define USBCS_TOP_CTRL_CFG1_OFFSET 0xc
+#define USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE BIT(5)
+
+#define DWC3_GOOGLE_MAX_RESETS       4
+
+struct dwc3_google {
+     struct device           *dev;
+     struct dwc3             dwc;
+     struct clk_bulk_data    *clks;
+     int                     num_clks;
+     struct reset_control_bulk_data rsts[DWC3_GOOGLE_MAX_RESETS];
+     int                     num_rsts;
+     struct reset_control    *non_sticky_rst;
+     struct device           *usb_psw_pd;
+     struct device_link      *usb_psw_pd_dl;
+     struct notifier_block   usb_psw_pd_nb;
+     struct device           *usb_top_pd;
+     struct device_link      *usb_top_pd_dl;
+     void __iomem            *host_cfg_base;
+     void __iomem            *usbint_cfg_base;
+     int                     hs_pme_irq;
+     int                     ss_pme_irq;
+     bool                    is_usb2only;
+     bool                    is_hibernation;
+};
+
+#define to_dwc3_google(d) container_of((d), struct dwc3_google, dwc)
+
+static int dwc3_google_rst_init(struct dwc3_google *google)
+{
+     int ret;
+
+     google->num_rsts = 4;
+     google->rsts[0].id = "non_sticky";
+     google->rsts[1].id = "sticky";
+     google->rsts[2].id = "drd_bus";
+     google->rsts[3].id = "top";
+
+     ret = devm_reset_control_bulk_get_exclusive(google->dev,
+                                                 google->num_rsts,
+                                                 google->rsts);
+
+     if (ret < 0)
+             return ret;
+
+     google->non_sticky_rst = google->rsts[0].rstc;
+
+     return 0;
+}
+
+static int dwc3_google_set_pmu_state(struct dwc3_google *google, int state)
+{
+     u32 reg;
+     int ret;
+
+     reg = readl(google->host_cfg_base + HOST_CFG1_OFFSET);
+     reg &= ~HOST_CFG1_PM_POWER_STATE_REQUEST;
+     reg |= (FIELD_PREP(HOST_CFG1_PM_POWER_STATE_REQUEST, state) |
+             HOST_CFG1_PME_EN);
+     writel(reg, google->host_cfg_base + HOST_CFG1_OFFSET);
+
+     ret = readl_poll_timeout(google->host_cfg_base + HC_STATUS_OFFSET, reg,
+                              (FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U2PMU, reg) == state &&
+                               FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U3PMU, reg) == state),
+                              10, 10000);
+
+     if (ret)
+             dev_err(google->dev, "failed to set PMU state %d\n", state);
+
+     return ret;
+}
+
+/*
+ * Clear pme interrupts and report their status.
+ * The hardware requires write-1 then write-0 sequence to clear the interrupt bits.
+ */
+static u32 dwc3_google_clear_pme_irqs(struct dwc3_google *google)
+{
+     u32 irq_status, reg_set, reg_clear;
+
+     irq_status = readl(google->usbint_cfg_base + USBINT_STATUS_OFFSET);
+     irq_status &= (USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW |
+                    USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW);
+     if (!irq_status)
+             return irq_status;
+
+     reg_set = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     reg_clear = reg_set;
+     if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW) {
+             reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
+             reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
+     }
+     if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW) {
+             reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
+             reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
+     }
+
+     writel(reg_set, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     writel(reg_clear, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+     return irq_status;
+}
+
+static void dwc3_google_enable_pme_irq(struct dwc3_google *google)
+{
+     u32 reg;
+
+     reg = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
+              USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
+     reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
+             USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
+     writel(reg, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+     enable_irq(google->hs_pme_irq);
+     enable_irq(google->ss_pme_irq);
+     enable_irq_wake(google->hs_pme_irq);
+     enable_irq_wake(google->ss_pme_irq);
+}
+
+static void dwc3_google_disable_pme_irq(struct dwc3_google *google)
+{
+     u32 reg;
+
+     reg = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
+              USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
+     reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
+             USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
+     writel(reg, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+     disable_irq_wake(google->hs_pme_irq);
+     disable_irq_wake(google->ss_pme_irq);
+     disable_irq_nosync(google->hs_pme_irq);
+     disable_irq_nosync(google->ss_pme_irq);
+}
+
+static irqreturn_t dwc3_google_resume_irq(int irq, void *data)
+{
+     struct dwc3_google      *google = data;
+     struct dwc3             *dwc = &google->dwc;
Quoted 8 lines

drivers/usb/dwc3/dwc3-google.c line 192

Quoted 6 lines
drivers/usb/dwc3/dwc3-google.c new file +6 -0
+     u32 irq_status, dr_role;
+
+     irq_status = dwc3_google_clear_pme_irqs(google);
+     dr_role = dwc->current_dr_role;
+
+     if (!irq_status || !google->is_hibernation ||

Any handling of race condition with is_hibernation?

IIUC, you're referring to the race between
- pme irq handler: dwc3_google_resume_irq() reads is_hibernation
- suspend/resume work: sets and unsets is_hibernation

Quoted 7 lines

Possible scenarios:
1. Suspend into hibernation: everything is serialized.

        dwc3_google_suspend() sets is_hibernation
    -> usb psw pdom is turned off
    -> controller enters D3
    -> pme irq could be triggered
    -> pme irq handler

In dwc3_google_suspend(), looks like is_hibernation is set after you enable pme irq, probably very unlikely, but can the interrupt be asserted then? If so, will there be another interrupt asserted? Otherwise the current logic may think it was spurious interrupt a miss an event.

Quoted 13 lines

2.1 Resume from hibernation: irq handler wins the race.

           pme irq handler (is_hibernation = true) triggers resume
       -> usb psw pdom is turned on
       -> dwc3_google_resume() unset is_hibernation
2.2 Resume from hibernation: resume work wins the race.
           usb psw pdom is turned on
       -> dwc3_google_resume() unset is_hibernation
       -> pme irq handler (is_hibernation = false) does nothing

So far I don't see any issues in any of the scenarios, please let me know if otherwise.

Quoted 1 line
Quoted 8 lines
Quoted 6 lines

drivers/usb/dwc3/dwc3-google.c line 195

Quoted 3 lines
drivers/usb/dwc3/dwc3-google.c new file +3 -0
+         dr_role != DWC3_GCTL_PRTCAP_HOST) {
+             dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
+                      irq, google->is_hibernation, dr_role);

Should we limit this print and do we need this to be dev_warn? It may be noisy wouldn't it.

Ack, will make it WARN_ONCE in the next version.

As Greg noted, don't use WARN_ONCE. I don't know how often spurious come, if there are many, perhaps use dev_dbg_ratelimited? If not, just stick to dev_dbg(). The regular user doesn't need to know about this right?

Quoted 50 lines
Quoted 41 lines
Quoted 41 lines
drivers/usb/dwc3/dwc3-google.c new file +39 -0
+             return IRQ_HANDLED;
+     }
+
+     if (dwc->xhci)
+             pm_runtime_resume(&dwc->xhci->dev);
+
+     return IRQ_HANDLED;
+}
+
+static int dwc3_google_request_irq(struct dwc3_google *google, struct platform_device *pdev,
+                                const char *irq_name, const char *req_name)
+{
+     int ret;
+     int irq;
+
+     irq = platform_get_irq_byname(pdev, irq_name);
+     if (irq < 0) {
+             dev_err(google->dev, "invalid irq name %s\n", irq_name);
+             return irq;
+     }
+
+     irq_set_status_flags(irq, IRQ_NOAUTOEN);
+     ret = devm_request_threaded_irq(google->dev, irq, NULL,
+                                     dwc3_google_resume_irq,
+                                     IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+                                     req_name, google);
+     if (ret < 0) {
+             dev_err(google->dev, "failed to request irq %s\n", req_name);
+             return ret;
+     }
+
+     return irq;
+}
+
+static int dwc3_google_usb_psw_pd_notifier(struct notifier_block *nb, unsigned long action, void *d)
+{
+     struct dwc3_google *google = container_of(nb, struct dwc3_google, usb_psw_pd_nb);
+     int ret;
+
Quoted 8 lines

drivers/usb/dwc3/dwc3-google.c line 240

Quoted 6 lines
drivers/usb/dwc3/dwc3-google.c new file +6 -0
+     if (!google->is_hibernation)
+             return NOTIFY_OK;
+
+     if (action == GENPD_NOTIFY_OFF) {
+             dev_dbg(google->dev, "enter D3 power state\n");
+             dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D3);

What happen when we failed to set the PMU state?

Quoted 3 lines

Apparently there must be something wrong with the hardware. Currently we only throw some error logs as no reliable recovery mechanism is known.

Just checking here because you did not return early and error out. If it's already in a bad state and no known recovery mechanism, either way is fine.

Quoted 97 lines
Quoted 79 lines
Quoted 63 lines
drivers/usb/dwc3/dwc3-google.c new file +61 -0
+             ret = reset_control_assert(google->non_sticky_rst);
+             if (ret)
+                     dev_err(google->dev, "non sticky reset assert failed: %d\n", ret);
+     } else if (action == GENPD_NOTIFY_ON) {
+             dev_dbg(google->dev, "enter D0 power state\n");
+             dwc3_google_clear_pme_irqs(google);
+             ret = reset_control_deassert(google->non_sticky_rst);
+             if (ret)
+                     dev_err(google->dev, "non sticky reset deassert failed: %d\n", ret);
+             dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D0);
+     }
+
+     return NOTIFY_OK;
+}
+
+static void dwc3_google_pm_domain_deinit(struct dwc3_google *google)
+{
+     if (google->usb_top_pd_dl)
+             device_link_del(google->usb_top_pd_dl);
+
+     if (!IS_ERR_OR_NULL(google->usb_top_pd)) {
+             device_set_wakeup_capable(google->usb_top_pd, false);
+             dev_pm_domain_detach(google->usb_top_pd, true);
+     }
+
+     if (google->usb_psw_pd_dl)
+             device_link_del(google->usb_psw_pd_dl);
+
+     if (!IS_ERR_OR_NULL(google->usb_psw_pd)) {
+             dev_pm_genpd_remove_notifier(google->usb_psw_pd);
+             dev_pm_domain_detach(google->usb_psw_pd, true);
+     }
+}
+
+static int dwc3_google_pm_domain_init(struct dwc3_google *google)
+{
+     int ret;
+
+     /*
+      * Establish PM RUNTIME link between dwc dev and its power domain usb_psw_pd,
+      * register notifier block to handle hibernation.
+      */
+     google->usb_psw_pd = dev_pm_domain_attach_by_name(google->dev, "psw");
+     if (IS_ERR_OR_NULL(google->usb_psw_pd)) {
+             dev_err(google->dev, "failed to get psw pd");
+             ret = google->usb_psw_pd ? PTR_ERR(google->usb_psw_pd) : -ENODATA;
+             return ret;
+     }
+
+     google->usb_psw_pd_nb.notifier_call = dwc3_google_usb_psw_pd_notifier;
+     ret = dev_pm_genpd_add_notifier(google->usb_psw_pd, &google->usb_psw_pd_nb);
+     if (ret) {
+             dev_err(google->dev, "failed to add psw pd notifier");
+             goto err;
+     }
+
+     google->usb_psw_pd_dl = device_link_add(google->dev, google->usb_psw_pd,
+                                             DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
+                                             DL_FLAG_RPM_ACTIVE);
+     if (!google->usb_psw_pd_dl) {
+             dev_err(google->usb_psw_pd, "failed to add device link");

drivers/usb/dwc3/dwc3-google.c line 311

Quoted 10 lines
drivers/usb/dwc3/dwc3-google.c new file +10 -0
+             ret = -ENODEV;
+             goto err;
+     }
+
+     /*
+      * usb_top_pd is the parent power domain of usb_psw_pd. Keeping usb_top_pd on
+      * while usb_psw_pd is off places the controller in a power-gated state,
+      * essential for hibernation. Acquire a handle to usb_top_pd and sets it as
+      * wakeup-capable to allow the domain to be left on during system suspend.
+      */

I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and the google->dev are working together in the glue here, particularly why usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this glued? Do you do anything except setting wakeup-capable?

Quoted 2 lines

BR,
Thinh

To provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd are the logical power domains to represent the 3 power states.
- Full Power: usb_psw_pd ON, usb_top_p ON.
- Power Gated: usb_psw_pd OFF, usb_top_p ON.
- Off: usb_psw_pd OFF, usb_top_p OFF.

To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to usb_top_pd and would cast a vote to keep it ON when attempting to enter hibernation. In addition, the usb_psw_pd runtime PM is directly tied to google->dev so that usb_psw_pd would be OFF when google->dev suspends. Together, the usb power domain would reach Power Gated state when device suspends.

Quoted 2 lines

I hope this information helps.

Yes. This is very helpful.

So, while the glue driver is bound, usb_top_pd is always ON? Even when xhci driver is not bound or when in device mode?

BR,
Thinh

author
#12 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 279 lines · Thinh Nguyen
Quoted 269 lines · Roy Luo
Quoted 255 lines · Thinh Nguyen
Quoted 253 lines · Roy Luo

Add support for the DWC3 USB controller found on Google Tensor G5. The controller features dual-role functionality and hibernation.

The primary focus is implementing hibernation support in host mode, enabling the controller to enter a low-power state (D3). This is particularly relevant during system power state transition and runtime power management for power efficiency. Highlights:
- Align suspend callback with dwc3_suspend_common() for deciding between a full teardown and hibernation in host mode.
- Integration with psw (power switchable) and top power domains, managing their states and device links to support hibernation.
- A notifier callback dwc3_google_usb_psw_pd_notifier() for psw power domain events to manage controller state transitions to/from D3.
- Coordination of the non_sticky reset during power state transitions, asserting it on D3 entry and deasserting on D0 entry in hibernation scenario.
- Handling of high-speed and super-speed PME interrupts that are generated by remote wakeup during hibernation.

---
 drivers/usb/dwc3/Kconfig       |  10 +
 drivers/usb/dwc3/Makefile      |   1 +
 drivers/usb/dwc3/dwc3-google.c | 608 +++++++++++++++++++++++++++++++++
 3 files changed, 619 insertions(+)
 create mode 100644 drivers/usb/dwc3/dwc3-google.c
drivers/usb/dwc3/Kconfig +10 -0
index 310d182e10b5..467515d5f937 100644
@@ -189,4 +189,14 @@ config USB_DWC3_RTK
        or dual-role mode.
        Say 'Y' or 'M' if you have such device.
 
+config USB_DWC3_GOOGLE
+     tristate "Google Platform"
+     depends on OF && COMMON_CLK && RESET_CONTROLLER
+     default n
+     help
+       Support the DesignWare Core USB3 IP found on Google Tensor
+       SoCs, starting with the G5 generation. This driver includes
+       support for hibernation in host mode.
+       Say 'Y' or 'M' if you have one such device.
+
 endif
drivers/usb/dwc3/Makefile +1 -0
index 830e6c9e5fe0..a94982630657 100644
@@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_IMX8MP)               += dwc3-imx8mp.o
 obj-$(CONFIG_USB_DWC3_XILINX)                += dwc3-xilinx.o
 obj-$(CONFIG_USB_DWC3_OCTEON)                += dwc3-octeon.o
 obj-$(CONFIG_USB_DWC3_RTK)           += dwc3-rtk.o
+obj-$(CONFIG_USB_DWC3_GOOGLE)                += dwc3-google.o
drivers/usb/dwc3/dwc3-google.c new file +186 -0
new file mode 100644
index 000000000000..d3fec6fb4dcc
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwc3-google.c - Google DWC3 Specific Glue Layer
+ *
+ * Copyright (c) 2025, Google LLC
+ * Author: Roy Luo <[email protected]>
+ */
+
+#include <linux/of.h>
+#include <linux/bitfield.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/pm_domain.h>
+#include <linux/iopoll.h>
+#include "core.h"
+#include "glue.h"
+
+/* HOST CFG registers */
+#define HC_STATUS_OFFSET 0x0
+#define HC_STATUS_CURRENT_POWER_STATE_U2PMU GENMASK(1, 0)
+#define HC_STATUS_CURRENT_POWER_STATE_U3PMU GENMASK(4, 3)
+
+#define HOST_CFG1_OFFSET 0x4
+#define HOST_CFG1_PME_EN BIT(3)
+#define HOST_CFG1_PM_POWER_STATE_REQUEST GENMASK(5, 4)
+#define HOST_CFG1_PM_POWER_STATE_D0 0x0
+#define HOST_CFG1_PM_POWER_STATE_D3 0x3
+
+/* USBINT registers */
+#define USBINT_CFG1_OFFSET 0x0
+#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK BIT(2)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK BIT(3)
+#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN BIT(8)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN BIT(9)
+#define USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR BIT(14)
+#define USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR BIT(15)
+
+#define USBINT_STATUS_OFFSET 0x4
+#define USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW BIT(2)
+#define USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW BIT(3)
+
+#define USBCS_TOP_CTRL_CFG1_OFFSET 0xc
+#define USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE BIT(5)
+
+#define DWC3_GOOGLE_MAX_RESETS       4
+
+struct dwc3_google {
+     struct device           *dev;
+     struct dwc3             dwc;
+     struct clk_bulk_data    *clks;
+     int                     num_clks;
+     struct reset_control_bulk_data rsts[DWC3_GOOGLE_MAX_RESETS];
+     int                     num_rsts;
+     struct reset_control    *non_sticky_rst;
+     struct device           *usb_psw_pd;
+     struct device_link      *usb_psw_pd_dl;
+     struct notifier_block   usb_psw_pd_nb;
+     struct device           *usb_top_pd;
+     struct device_link      *usb_top_pd_dl;
+     void __iomem            *host_cfg_base;
+     void __iomem            *usbint_cfg_base;
+     int                     hs_pme_irq;
+     int                     ss_pme_irq;
+     bool                    is_usb2only;
+     bool                    is_hibernation;
+};
+
+#define to_dwc3_google(d) container_of((d), struct dwc3_google, dwc)
+
+static int dwc3_google_rst_init(struct dwc3_google *google)
+{
+     int ret;
+
+     google->num_rsts = 4;
+     google->rsts[0].id = "non_sticky";
+     google->rsts[1].id = "sticky";
+     google->rsts[2].id = "drd_bus";
+     google->rsts[3].id = "top";
+
+     ret = devm_reset_control_bulk_get_exclusive(google->dev,
+                                                 google->num_rsts,
+                                                 google->rsts);
+
+     if (ret < 0)
+             return ret;
+
+     google->non_sticky_rst = google->rsts[0].rstc;
+
+     return 0;
+}
+
+static int dwc3_google_set_pmu_state(struct dwc3_google *google, int state)
+{
+     u32 reg;
+     int ret;
+
+     reg = readl(google->host_cfg_base + HOST_CFG1_OFFSET);
+     reg &= ~HOST_CFG1_PM_POWER_STATE_REQUEST;
+     reg |= (FIELD_PREP(HOST_CFG1_PM_POWER_STATE_REQUEST, state) |
+             HOST_CFG1_PME_EN);
+     writel(reg, google->host_cfg_base + HOST_CFG1_OFFSET);
+
+     ret = readl_poll_timeout(google->host_cfg_base + HC_STATUS_OFFSET, reg,
+                              (FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U2PMU, reg) == state &&
+                               FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U3PMU, reg) == state),
+                              10, 10000);
+
+     if (ret)
+             dev_err(google->dev, "failed to set PMU state %d\n", state);
+
+     return ret;
+}
+
+/*
+ * Clear pme interrupts and report their status.
+ * The hardware requires write-1 then write-0 sequence to clear the interrupt bits.
+ */
+static u32 dwc3_google_clear_pme_irqs(struct dwc3_google *google)
+{
+     u32 irq_status, reg_set, reg_clear;
+
+     irq_status = readl(google->usbint_cfg_base + USBINT_STATUS_OFFSET);
+     irq_status &= (USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW |
+                    USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW);
+     if (!irq_status)
+             return irq_status;
+
+     reg_set = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     reg_clear = reg_set;
+     if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW) {
+             reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
+             reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
+     }
+     if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW) {
+             reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
+             reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
+     }
+
+     writel(reg_set, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     writel(reg_clear, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+     return irq_status;
+}
+
+static void dwc3_google_enable_pme_irq(struct dwc3_google *google)
+{
+     u32 reg;
+
+     reg = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
+              USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
+     reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
+             USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
+     writel(reg, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+     enable_irq(google->hs_pme_irq);
+     enable_irq(google->ss_pme_irq);
+     enable_irq_wake(google->hs_pme_irq);
+     enable_irq_wake(google->ss_pme_irq);
+}
+
+static void dwc3_google_disable_pme_irq(struct dwc3_google *google)
+{
+     u32 reg;
+
+     reg = readl(google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+     reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
+              USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
+     reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
+             USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
+     writel(reg, google->usbint_cfg_base + USBINT_CFG1_OFFSET);
+
+     disable_irq_wake(google->hs_pme_irq);
+     disable_irq_wake(google->ss_pme_irq);
+     disable_irq_nosync(google->hs_pme_irq);
+     disable_irq_nosync(google->ss_pme_irq);
+}
+
+static irqreturn_t dwc3_google_resume_irq(int irq, void *data)
+{
+     struct dwc3_google      *google = data;
+     struct dwc3             *dwc = &google->dwc;
Quoted 8 lines

drivers/usb/dwc3/dwc3-google.c line 192

Quoted 6 lines
drivers/usb/dwc3/dwc3-google.c new file +6 -0
+     u32 irq_status, dr_role;
+
+     irq_status = dwc3_google_clear_pme_irqs(google);
+     dr_role = dwc->current_dr_role;
+
+     if (!irq_status || !google->is_hibernation ||

Any handling of race condition with is_hibernation?

IIUC, you're referring to the race between
- pme irq handler: dwc3_google_resume_irq() reads is_hibernation
- suspend/resume work: sets and unsets is_hibernation

Quoted 7 lines

Possible scenarios:
1. Suspend into hibernation: everything is serialized.

        dwc3_google_suspend() sets is_hibernation
    -> usb psw pdom is turned off
    -> controller enters D3
    -> pme irq could be triggered
    -> pme irq handler
Quoted 5 lines

In dwc3_google_suspend(), looks like is_hibernation is set after you enable pme irq, probably very unlikely, but can the interrupt be asserted then? If so, will there be another interrupt asserted? Otherwise the current logic may think it was spurious interrupt a miss an event.

The pme interrupt can only be asserted after controller is in hibernation, that is, after the usb psw dom is turned off and the dwc3_google_usb_psw_pd_notifier() callback is completed. So no, the interrupt won't fire before is_hibernation is set.

Quoted 21 lines
Quoted 21 lines

2.1 Resume from hibernation: irq handler wins the race.

           pme irq handler (is_hibernation = true) triggers resume
       -> usb psw pdom is turned on
       -> dwc3_google_resume() unset is_hibernation
2.2 Resume from hibernation: resume work wins the race.
           usb psw pdom is turned on
       -> dwc3_google_resume() unset is_hibernation
       -> pme irq handler (is_hibernation = false) does nothing

So far I don't see any issues in any of the scenarios, please let me know if otherwise.

Quoted 7 lines

drivers/usb/dwc3/dwc3-google.c line 195

Quoted 4 lines
drivers/usb/dwc3/dwc3-google.c new file +3 -0
+         dr_role != DWC3_GCTL_PRTCAP_HOST) {
+             dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
+                      irq, google->is_hibernation, dr_role);

Should we limit this print and do we need this to be dev_warn? It may be noisy wouldn't it.

Quoted 6 lines
Quoted 1 line

Ack, will make it WARN_ONCE in the next version.

As Greg noted, don't use WARN_ONCE. I don't know how often spurious come, if there are many, perhaps use dev_dbg_ratelimited? If not, just stick to dev_dbg(). The regular user doesn't need to know about this right?

This case should be very very rare, I haven't seen it on the field. And yes, this log isn't for regular end users.
I will change it to dev_dbg() as suggested in the next version.

Quoted 51 lines
Quoted 51 lines
Quoted 50 lines
Quoted 38 lines
drivers/usb/dwc3/dwc3-google.c new file +35 -0
+             return IRQ_HANDLED;
+     }
+
+     if (dwc->xhci)
+             pm_runtime_resume(&dwc->xhci->dev);
+
+     return IRQ_HANDLED;
+}
+
+static int dwc3_google_request_irq(struct dwc3_google *google, struct platform_device *pdev,
+                                const char *irq_name, const char *req_name)
+{
+     int ret;
+     int irq;
+
+     irq = platform_get_irq_byname(pdev, irq_name);
+     if (irq < 0) {
+             dev_err(google->dev, "invalid irq name %s\n", irq_name);
+             return irq;
+     }
+
+     irq_set_status_flags(irq, IRQ_NOAUTOEN);
+     ret = devm_request_threaded_irq(google->dev, irq, NULL,
+                                     dwc3_google_resume_irq,
+                                     IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+                                     req_name, google);
+     if (ret < 0) {
+             dev_err(google->dev, "failed to request irq %s\n", req_name);
+             return ret;
+     }
+
+     return irq;
+}
+
+static int dwc3_google_usb_psw_pd_notifier(struct notifier_block *nb, unsigned long action, void *d)

drivers/usb/dwc3/dwc3-google.c line 240

Quoted 10 lines
drivers/usb/dwc3/dwc3-google.c new file +10 -0
+{
+     struct dwc3_google *google = container_of(nb, struct dwc3_google, usb_psw_pd_nb);
+     int ret;
+
+     if (!google->is_hibernation)
+             return NOTIFY_OK;
+
+     if (action == GENPD_NOTIFY_OFF) {
+             dev_dbg(google->dev, "enter D3 power state\n");
+             dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D3);

What happen when we failed to set the PMU state?

Quoted 7 lines
Quoted 3 lines

Apparently there must be something wrong with the hardware. Currently we only throw some error logs as no reliable recovery mechanism is known.

Just checking here because you did not return early and error out. If it's already in a bad state and no known recovery mechanism, either way is fine.

Ack.

Quoted 98 lines
Quoted 98 lines
Quoted 80 lines
Quoted 64 lines
drivers/usb/dwc3/dwc3-google.c new file +61 -0
+             ret = reset_control_assert(google->non_sticky_rst);
+             if (ret)
+                     dev_err(google->dev, "non sticky reset assert failed: %d\n", ret);
+     } else if (action == GENPD_NOTIFY_ON) {
+             dev_dbg(google->dev, "enter D0 power state\n");
+             dwc3_google_clear_pme_irqs(google);
+             ret = reset_control_deassert(google->non_sticky_rst);
+             if (ret)
+                     dev_err(google->dev, "non sticky reset deassert failed: %d\n", ret);
+             dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D0);
+     }
+
+     return NOTIFY_OK;
+}
+
+static void dwc3_google_pm_domain_deinit(struct dwc3_google *google)
+{
+     if (google->usb_top_pd_dl)
+             device_link_del(google->usb_top_pd_dl);
+
+     if (!IS_ERR_OR_NULL(google->usb_top_pd)) {
+             device_set_wakeup_capable(google->usb_top_pd, false);
+             dev_pm_domain_detach(google->usb_top_pd, true);
+     }
+
+     if (google->usb_psw_pd_dl)
+             device_link_del(google->usb_psw_pd_dl);
+
+     if (!IS_ERR_OR_NULL(google->usb_psw_pd)) {
+             dev_pm_genpd_remove_notifier(google->usb_psw_pd);
+             dev_pm_domain_detach(google->usb_psw_pd, true);
+     }
+}
+
+static int dwc3_google_pm_domain_init(struct dwc3_google *google)
+{
+     int ret;
+
+     /*
+      * Establish PM RUNTIME link between dwc dev and its power domain usb_psw_pd,
+      * register notifier block to handle hibernation.
+      */
+     google->usb_psw_pd = dev_pm_domain_attach_by_name(google->dev, "psw");
+     if (IS_ERR_OR_NULL(google->usb_psw_pd)) {
+             dev_err(google->dev, "failed to get psw pd");
+             ret = google->usb_psw_pd ? PTR_ERR(google->usb_psw_pd) : -ENODATA;
+             return ret;
+     }
+
+     google->usb_psw_pd_nb.notifier_call = dwc3_google_usb_psw_pd_notifier;
+     ret = dev_pm_genpd_add_notifier(google->usb_psw_pd, &google->usb_psw_pd_nb);
+     if (ret) {
+             dev_err(google->dev, "failed to add psw pd notifier");
+             goto err;
+     }
+
+     google->usb_psw_pd_dl = device_link_add(google->dev, google->usb_psw_pd,
+                                             DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
+                                             DL_FLAG_RPM_ACTIVE);
+     if (!google->usb_psw_pd_dl) {
+             dev_err(google->usb_psw_pd, "failed to add device link");

drivers/usb/dwc3/dwc3-google.c line 311

Quoted 10 lines
drivers/usb/dwc3/dwc3-google.c new file +10 -0
+             ret = -ENODEV;
+             goto err;
+     }
+
+     /*
+      * usb_top_pd is the parent power domain of usb_psw_pd. Keeping usb_top_pd on
+      * while usb_psw_pd is off places the controller in a power-gated state,
+      * essential for hibernation. Acquire a handle to usb_top_pd and sets it as
+      * wakeup-capable to allow the domain to be left on during system suspend.
+      */

I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and the google->dev are working together in the glue here, particularly why usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this glued? Do you do anything except setting wakeup-capable?

Quoted 2 lines

BR,
Thinh

To provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd are the logical power domains to represent the 3 power states.
- Full Power: usb_psw_pd ON, usb_top_p ON.
- Power Gated: usb_psw_pd OFF, usb_top_p ON.
- Off: usb_psw_pd OFF, usb_top_p OFF.

To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to usb_top_pd and would cast a vote to keep it ON when attempting to enter hibernation. In addition, the usb_psw_pd runtime PM is directly tied to google->dev so that usb_psw_pd would be OFF when google->dev suspends. Together, the usb power domain would reach Power Gated state when device suspends.

Quoted 7 lines
Quoted 2 lines

I hope this information helps.

Yes. This is very helpful.

So, while the glue driver is bound, usb_top_pd is always ON? Even when xhci driver is not bound or when in device mode?

Since usb_top_pd is the parent power domain of usb_psw_pd, and usb_psw_pd RPM is directly tied to glue device, usb_top_pd would be ON when glue device is active (because usb_psw_pd is ON) and would be OFF when glue device suspends in non-hibernation scenarios (because usb_psw_pd is OFF). In hibernation scenario, a vote is casted for usb_top_pd to keep it on even when the glue device is suspended and usb_psw_pd is OFF.

To your question, usb_top_pd is not always ON because it would be turned off when the glue device suspends in non-hibernation scenario. When in device mode and provided dwc3 dev is active, usb_top_pd would be ON because its child usb_psw_pd is ON.

Regards,
Roy Luo

Quoted 3 lines

BR,
Thinh

maintainer
#13 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 11 lines · Roy Luo
Quoted 9 lines · Thinh Nguyen
Quoted 1 line · Roy Luo

On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <[email protected]> wrote:

In dwc3_google_suspend(), looks like is_hibernation is set after you enable pme irq, probably very unlikely, but can the interrupt be asserted then? If so, will there be another interrupt asserted? Otherwise the current logic may think it was spurious interrupt a miss an event.

Quoted 5 lines

The pme interrupt can only be asserted after controller is in hibernation, that is, after the usb psw dom is turned off and the dwc3_google_usb_psw_pd_notifier() callback is completed. So no, the interrupt won't fire before is_hibernation is set.

Thanks for the confirmation.

<snip>

Quoted 40 lines
Quoted 24 lines
Quoted 24 lines
Quoted 8 lines

I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and the google->dev are working together in the glue here, particularly why usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this glued? Do you do anything except setting wakeup-capable?

BR,
Thinh

To provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd are the logical power domains to represent the 3 power states.
- Full Power: usb_psw_pd ON, usb_top_p ON.
- Power Gated: usb_psw_pd OFF, usb_top_p ON.
- Off: usb_psw_pd OFF, usb_top_p OFF.

To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to usb_top_pd and would cast a vote to keep it ON when attempting to enter hibernation. In addition, the usb_psw_pd runtime PM is directly tied to google->dev so that usb_psw_pd would be OFF when google->dev suspends. Together, the usb power domain would reach Power Gated state when device suspends.

Quoted 7 lines
Quoted 2 lines

I hope this information helps.

Yes. This is very helpful.

So, while the glue driver is bound, usb_top_pd is always ON? Even when xhci driver is not bound or when in device mode?

Since usb_top_pd is the parent power domain of usb_psw_pd, and usb_psw_pd RPM is directly tied to glue device, usb_top_pd would be ON when glue device is active (because usb_psw_pd is ON) and would be OFF when glue device suspends in non-hibernation scenarios (because usb_psw_pd is OFF). In hibernation scenario, a vote is casted for usb_top_pd to keep it on even when the glue device is suspended and usb_psw_pd is OFF.

Quoted 5 lines

To your question, usb_top_pd is not always ON because it would be turned off when the glue device suspends in non-hibernation scenario. When in device mode and provided dwc3 dev is active, usb_top_pd would be ON because its child usb_psw_pd is ON.

Thanks for the clarification and bearing with my questions.

If there's no device connected, do you role-switch back to default mode? Often I see that the role-switch is defaulted to peripheral and switch to default mode if there's no connection.

I want to check the case where the device may wakeup by connection but cannot because it is not in host mode. Do you have a separate TCPC/connector that can wakeup the system on attachment?

BR,
Thinh

author
#14 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 71 lines · Thinh Nguyen
Quoted 11 lines · Roy Luo
Quoted 9 lines · Thinh Nguyen
Quoted 1 line · Roy Luo

On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <[email protected]> wrote:

In dwc3_google_suspend(), looks like is_hibernation is set after you enable pme irq, probably very unlikely, but can the interrupt be asserted then? If so, will there be another interrupt asserted? Otherwise the current logic may think it was spurious interrupt a miss an event.

Quoted 5 lines

The pme interrupt can only be asserted after controller is in hibernation, that is, after the usb psw dom is turned off and the dwc3_google_usb_psw_pd_notifier() callback is completed. So no, the interrupt won't fire before is_hibernation is set.

Thanks for the confirmation.

<snip>

Quoted 40 lines
Quoted 24 lines
Quoted 24 lines
Quoted 8 lines

I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and the google->dev are working together in the glue here, particularly why usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this glued? Do you do anything except setting wakeup-capable?

BR,
Thinh

To provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd are the logical power domains to represent the 3 power states.
- Full Power: usb_psw_pd ON, usb_top_p ON.
- Power Gated: usb_psw_pd OFF, usb_top_p ON.
- Off: usb_psw_pd OFF, usb_top_p OFF.

To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to usb_top_pd and would cast a vote to keep it ON when attempting to enter hibernation. In addition, the usb_psw_pd runtime PM is directly tied to google->dev so that usb_psw_pd would be OFF when google->dev suspends. Together, the usb power domain would reach Power Gated state when device suspends.

Quoted 7 lines
Quoted 2 lines

I hope this information helps.

Yes. This is very helpful.

So, while the glue driver is bound, usb_top_pd is always ON? Even when xhci driver is not bound or when in device mode?

Since usb_top_pd is the parent power domain of usb_psw_pd, and usb_psw_pd RPM is directly tied to glue device, usb_top_pd would be ON when glue device is active (because usb_psw_pd is ON) and would be OFF when glue device suspends in non-hibernation scenarios (because usb_psw_pd is OFF). In hibernation scenario, a vote is casted for usb_top_pd to keep it on even when the glue device is suspended and usb_psw_pd is OFF.

Quoted 5 lines

To your question, usb_top_pd is not always ON because it would be turned off when the glue device suspends in non-hibernation scenario. When in device mode and provided dwc3 dev is active, usb_top_pd would be ON because its child usb_psw_pd is ON.

Quoted 5 lines

Thanks for the clarification and bearing with my questions.

If there's no device connected, do you role-switch back to default mode? Often I see that the role-switch is defaulted to peripheral and switch to default mode if there's no connection.

Yes, the default mode would be peripheral and it would switch to peripheral mode if there's no connection.

Quoted 4 lines

I want to check the case where the device may wakeup by connection but cannot because it is not in host mode. Do you have a separate TCPC/connector that can wakeup the system on attachment?

Yes, there's a separate TCPC/connector to trigger a role switch when there's an incoming connection.

Regards,
Roy Luo

Quoted 3 lines

BR,
Thinh

maintainer Acked-by
#15 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 82 lines · Roy Luo
Quoted 71 lines · Thinh Nguyen
Quoted 11 lines · Roy Luo
Quoted 9 lines · Thinh Nguyen
Quoted 1 line · Roy Luo

On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <[email protected]> wrote:

In dwc3_google_suspend(), looks like is_hibernation is set after you enable pme irq, probably very unlikely, but can the interrupt be asserted then? If so, will there be another interrupt asserted? Otherwise the current logic may think it was spurious interrupt a miss an event.

Quoted 5 lines

The pme interrupt can only be asserted after controller is in hibernation, that is, after the usb psw dom is turned off and the dwc3_google_usb_psw_pd_notifier() callback is completed. So no, the interrupt won't fire before is_hibernation is set.

Thanks for the confirmation.

<snip>

Quoted 40 lines
Quoted 24 lines
Quoted 24 lines
Quoted 8 lines

I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and the google->dev are working together in the glue here, particularly why usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this glued? Do you do anything except setting wakeup-capable?

BR,
Thinh

To provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd are the logical power domains to represent the 3 power states.
- Full Power: usb_psw_pd ON, usb_top_p ON.
- Power Gated: usb_psw_pd OFF, usb_top_p ON.
- Off: usb_psw_pd OFF, usb_top_p OFF.

To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to usb_top_pd and would cast a vote to keep it ON when attempting to enter hibernation. In addition, the usb_psw_pd runtime PM is directly tied to google->dev so that usb_psw_pd would be OFF when google->dev suspends. Together, the usb power domain would reach Power Gated state when device suspends.

Quoted 7 lines
Quoted 2 lines

I hope this information helps.

Yes. This is very helpful.

So, while the glue driver is bound, usb_top_pd is always ON? Even when xhci driver is not bound or when in device mode?

Since usb_top_pd is the parent power domain of usb_psw_pd, and usb_psw_pd RPM is directly tied to glue device, usb_top_pd would be ON when glue device is active (because usb_psw_pd is ON) and would be OFF when glue device suspends in non-hibernation scenarios (because usb_psw_pd is OFF). In hibernation scenario, a vote is casted for usb_top_pd to keep it on even when the glue device is suspended and usb_psw_pd is OFF.

Quoted 5 lines

To your question, usb_top_pd is not always ON because it would be turned off when the glue device suspends in non-hibernation scenario. When in device mode and provided dwc3 dev is active, usb_top_pd would be ON because its child usb_psw_pd is ON.

Quoted 5 lines

Thanks for the clarification and bearing with my questions.

If there's no device connected, do you role-switch back to default mode? Often I see that the role-switch is defaulted to peripheral and switch to default mode if there's no connection.

Yes, the default mode would be peripheral and it would switch to peripheral mode if there's no connection.

Quoted 1 line
Quoted 7 lines
Quoted 3 lines

I want to check the case where the device may wakeup by connection but cannot because it is not in host mode. Do you have a separate TCPC/connector that can wakeup the system on attachment?

Yes, there's a separate TCPC/connector to trigger a role switch when there's an incoming connection.

This addressed my concerns. My other comments are minor nits.

You can include this on your next submission:

Thanks,
Thinh

author
#16 lore raw

Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver

Quoted 92 lines · Thinh Nguyen
Quoted 82 lines · Roy Luo
Quoted 71 lines · Thinh Nguyen
Quoted 11 lines · Roy Luo
Quoted 9 lines · Thinh Nguyen
Quoted 1 line · Roy Luo

On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <[email protected]> wrote:

In dwc3_google_suspend(), looks like is_hibernation is set after you enable pme irq, probably very unlikely, but can the interrupt be asserted then? If so, will there be another interrupt asserted? Otherwise the current logic may think it was spurious interrupt a miss an event.

Quoted 5 lines

The pme interrupt can only be asserted after controller is in hibernation, that is, after the usb psw dom is turned off and the dwc3_google_usb_psw_pd_notifier() callback is completed. So no, the interrupt won't fire before is_hibernation is set.

Thanks for the confirmation.

<snip>

Quoted 40 lines
Quoted 24 lines
Quoted 24 lines
Quoted 8 lines

I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and the google->dev are working together in the glue here, particularly why usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this glued? Do you do anything except setting wakeup-capable?

BR,
Thinh

To provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd are the logical power domains to represent the 3 power states.
- Full Power: usb_psw_pd ON, usb_top_p ON.
- Power Gated: usb_psw_pd OFF, usb_top_p ON.
- Off: usb_psw_pd OFF, usb_top_p OFF.

To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to usb_top_pd and would cast a vote to keep it ON when attempting to enter hibernation. In addition, the usb_psw_pd runtime PM is directly tied to google->dev so that usb_psw_pd would be OFF when google->dev suspends. Together, the usb power domain would reach Power Gated state when device suspends.

Quoted 7 lines
Quoted 2 lines

I hope this information helps.

Yes. This is very helpful.

So, while the glue driver is bound, usb_top_pd is always ON? Even when xhci driver is not bound or when in device mode?

Since usb_top_pd is the parent power domain of usb_psw_pd, and usb_psw_pd RPM is directly tied to glue device, usb_top_pd would be ON when glue device is active (because usb_psw_pd is ON) and would be OFF when glue device suspends in non-hibernation scenarios (because usb_psw_pd is OFF). In hibernation scenario, a vote is casted for usb_top_pd to keep it on even when the glue device is suspended and usb_psw_pd is OFF.

Quoted 5 lines

To your question, usb_top_pd is not always ON because it would be turned off when the glue device suspends in non-hibernation scenario. When in device mode and provided dwc3 dev is active, usb_top_pd would be ON because its child usb_psw_pd is ON.

Quoted 5 lines

Thanks for the clarification and bearing with my questions.

If there's no device connected, do you role-switch back to default mode? Often I see that the role-switch is defaulted to peripheral and switch to default mode if there's no connection.

Yes, the default mode would be peripheral and it would switch to peripheral mode if there's no connection.

Quoted 1 line
Quoted 7 lines
Quoted 3 lines

I want to check the case where the device may wakeup by connection but cannot because it is not in host mode. Do you have a separate TCPC/connector that can wakeup the system on attachment?

Yes, there's a separate TCPC/connector to trigger a role switch when there's an incoming connection.

Quoted 8 lines

This addressed my concerns. My other comments are minor nits.

You can include this on your next submission:

Thanks,
Thinh

Thinh,

Thanks for the review, appreciate it!
I'd like to give you a heads up on a change I'm going to make in the next version. Per Krzysztof's suggestion in [1], I'm making a register region that's shared between the controller and the PHY a syscon node. The impact to this patch is that mmio space "host_cfg" and "usbint_cfg" would be accessed through syscon API instead, but there won't be any functional change.

[1] https://lore.kernel.org/linux-phy/[email protected]/

Regards,
Roy Luo