Add Google Tensor SoC USB controller support
Subsystems (5)
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
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
@@ -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
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.
Signed-off-by Roy Luo <[email protected]>--- .../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
PATCH v4 2/2 usb: dwc3: Add Google Tensor SoC DWC3 glue driver
In reply to Roy LuoAdd 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
@@ -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
@@ -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
Thinh Nguyen
maintainerRe: [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 withdwc3_suspend_common()for deciding between a full teardown and hibernation in host mode.
- Integration withpsw(power switchable) andtoppower domains, managing their states and device links to support hibernation.
- A notifier callbackdwc3_google_usb_psw_pd_notifier()forpswpower domain events to manage controller state transitions to/from D3.
- Coordination of thenon_stickyreset 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.Co-developed-by Joy Chakraborty <[email protected]>Signed-off-by Joy Chakraborty <[email protected]>Co-developed-by Naveen Kumar <[email protected]>Signed-off-by Naveen Kumar <[email protected]>Signed-off-by Roy Luo <[email protected]>
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
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 withdwc3_suspend_common()for deciding between a full teardown and hibernation in host mode.
- Integration withpsw(power switchable) andtoppower domains, managing their states and device links to support hibernation.
- A notifier callbackdwc3_google_usb_psw_pd_notifier()forpswpower domain events to manage controller state transitions to/from D3.
- Coordination of thenon_stickyreset 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.Co-developed-by Joy Chakraborty <[email protected]>Signed-off-by Joy Chakraborty <[email protected]>Co-developed-by Naveen Kumar <[email protected]>Signed-off-by Naveen Kumar <[email protected]>Signed-off-by Roy Luo <[email protected]>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
Thinh Nguyen
maintainerRe: [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 withdwc3_suspend_common()for deciding between a full teardown and hibernation in host mode.
- Integration withpsw(power switchable) andtoppower domains, managing their states and device links to support hibernation.
- A notifier callbackdwc3_google_usb_psw_pd_notifier()forpswpower domain events to manage controller state transitions to/from D3.
- Coordination of thenon_stickyreset 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.Co-developed-by Joy Chakraborty <[email protected]>Signed-off-by Joy Chakraborty <[email protected]>Co-developed-by Naveen Kumar <[email protected]>Signed-off-by Naveen Kumar <[email protected]>Signed-off-by Roy Luo <[email protected]>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.cQuoted 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 indwc3_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,
ThinhLooking forward to working with you to land this patch.
Thanks,
Roy Luo
Thinh Nguyen
maintainerRe: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
In reply to Roy LuoQuoted 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 withdwc3_suspend_common()for deciding between a full teardown and hibernation in host mode.
- Integration withpsw(power switchable) andtoppower domains, managing their states and device links to support hibernation.
- A notifier callbackdwc3_google_usb_psw_pd_notifier()forpswpower domain events to manage controller state transitions to/from D3.
- Coordination of thenon_stickyreset 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.Co-developed-by Joy Chakraborty <[email protected]>Signed-off-by Joy Chakraborty <[email protected]>Co-developed-by Naveen Kumar <[email protected]>Signed-off-by Naveen Kumar <[email protected]>Signed-off-by Roy Luo <[email protected]>--- 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.cdrivers/usb/dwc3/Kconfig +10
-0@@ -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. + endifdrivers/usb/dwc3/Makefile +1
-0@@ -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.odrivers/usb/dwc3/dwc3-google.c new file +182
-0@@ -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
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 withdwc3_suspend_common()for deciding between a full teardown and hibernation in host mode.
- Integration withpsw(power switchable) andtoppower domains, managing their states and device links to support hibernation.
- A notifier callbackdwc3_google_usb_psw_pd_notifier()forpswpower domain events to manage controller state transitions to/from D3.
- Coordination of thenon_stickyreset 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.Co-developed-by Joy Chakraborty <[email protected]>Signed-off-by Joy Chakraborty <[email protected]>Co-developed-by Naveen Kumar <[email protected]>Signed-off-by Naveen Kumar <[email protected]>Signed-off-by Roy Luo <[email protected]>--- 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.cdrivers/usb/dwc3/Kconfig +10
-0@@ -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. + endifdrivers/usb/dwc3/Makefile +1
-0@@ -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.odrivers/usb/dwc3/dwc3-google.c new file +186
-0@@ -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 nothingSo 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 thegoogle->devare working together in the glue here, particularly whyusb_top_pdis needed. It seemsusb_top_pdshouldn'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
Greg Kroah-Hartman
maintainerRe: [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_ONCEin 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
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_ONCEin 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
Thinh Nguyen
maintainerRe: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
In reply to Roy LuoQuoted 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 withdwc3_suspend_common()for deciding between a full teardown and hibernation in host mode.
- Integration withpsw(power switchable) andtoppower domains, managing their states and device links to support hibernation.
- A notifier callbackdwc3_google_usb_psw_pd_notifier()forpswpower domain events to manage controller state transitions to/from D3.
- Coordination of thenon_stickyreset 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.Co-developed-by Joy Chakraborty <[email protected]>Signed-off-by Joy Chakraborty <[email protected]>Co-developed-by Naveen Kumar <[email protected]>Signed-off-by Naveen Kumar <[email protected]>Signed-off-by Roy Luo <[email protected]>--- 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.cdrivers/usb/dwc3/Kconfig +10
-0@@ -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. + endifdrivers/usb/dwc3/Makefile +1
-0@@ -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.odrivers/usb/dwc3/dwc3-google.c new file +186
-0@@ -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()readsis_hibernation
- suspend/resume work: sets and unsetsis_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 nothingSo 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_ONCEin 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 thegoogle->devare working together in the glue here, particularly whyusb_top_pdis needed. It seemsusb_top_pdshouldn't be handled by this glued? Do you do anything except setting wakeup-capable?Quoted 2 lines
BR,
ThinhTo provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. Theusb_top_pdandusb_psw_pdare the logical power domains to represent the 3 power states.
- Full Power:usb_psw_pdON,usb_top_pON.
- Power Gated:usb_psw_pdOFF,usb_top_pON.
- Off:usb_psw_pdOFF,usb_top_pOFF.To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to
usb_top_pdand would cast a vote to keep it ON when attempting to enter hibernation. In addition, theusb_psw_pdruntime PM is directly tied togoogle->devso thatusb_psw_pdwould be OFF whengoogle->devsuspends. 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
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 withdwc3_suspend_common()for deciding between a full teardown and hibernation in host mode.
- Integration withpsw(power switchable) andtoppower domains, managing their states and device links to support hibernation.
- A notifier callbackdwc3_google_usb_psw_pd_notifier()forpswpower domain events to manage controller state transitions to/from D3.
- Coordination of thenon_stickyreset 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.Co-developed-by Joy Chakraborty <[email protected]>Signed-off-by Joy Chakraborty <[email protected]>Co-developed-by Naveen Kumar <[email protected]>Signed-off-by Naveen Kumar <[email protected]>Signed-off-by Roy Luo <[email protected]>--- 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.cdrivers/usb/dwc3/Kconfig +10
-0@@ -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. + endifdrivers/usb/dwc3/Makefile +1
-0@@ -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.odrivers/usb/dwc3/dwc3-google.c new file +186
-0@@ -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()readsis_hibernation
- suspend/resume work: sets and unsetsis_hibernationQuoted 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 likeis_hibernationis 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 nothingSo 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_ONCEin the next version.As Greg noted, don't use
WARN_ONCE. I don't know how often spurious come, if there are many, perhaps usedev_dbg_ratelimited? If not, just stick todev_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 thegoogle->devare working together in the glue here, particularly whyusb_top_pdis needed. It seemsusb_top_pdshouldn't be handled by this glued? Do you do anything except setting wakeup-capable?Quoted 2 lines
BR,
ThinhTo provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. Theusb_top_pdandusb_psw_pdare the logical power domains to represent the 3 power states.
- Full Power:usb_psw_pdON,usb_top_pON.
- Power Gated:usb_psw_pdOFF,usb_top_pON.
- Off:usb_psw_pdOFF,usb_top_pOFF.To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to
usb_top_pdand would cast a vote to keep it ON when attempting to enter hibernation. In addition, theusb_psw_pdruntime PM is directly tied togoogle->devso thatusb_psw_pdwould be OFF whengoogle->devsuspends. 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_pdis 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
Thinh Nguyen
maintainerRe: [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 likeis_hibernationis 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 beforeis_hibernationis 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 thegoogle->devare working together in the glue here, particularly whyusb_top_pdis needed. It seemsusb_top_pdshouldn't be handled by this glued? Do you do anything except setting wakeup-capable?BR,
ThinhTo provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. Theusb_top_pdandusb_psw_pdare the logical power domains to represent the 3 power states.
- Full Power:usb_psw_pdON,usb_top_pON.
- Power Gated:usb_psw_pdOFF,usb_top_pON.
- Off:usb_psw_pdOFF,usb_top_pOFF.To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to
usb_top_pdand would cast a vote to keep it ON when attempting to enter hibernation. In addition, theusb_psw_pdruntime PM is directly tied togoogle->devso thatusb_psw_pdwould be OFF whengoogle->devsuspends. 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_pdis always ON? Even when xhci driver is not bound or when in device mode?Since
usb_top_pdis the parent power domain ofusb_psw_pd, andusb_psw_pdRPM is directly tied to glue device,usb_top_pdwould be ON when glue device is active (becauseusb_psw_pdis ON) and would be OFF when glue device suspends in non-hibernation scenarios (becauseusb_psw_pdis OFF). In hibernation scenario, a vote is casted forusb_top_pdto keep it on even when the glue device is suspended andusb_psw_pdis OFF.
Quoted 5 lines
To your question,
usb_top_pdis 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_pdwould be ON because its childusb_psw_pdis 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
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 likeis_hibernationis 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 beforeis_hibernationis 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 thegoogle->devare working together in the glue here, particularly whyusb_top_pdis needed. It seemsusb_top_pdshouldn't be handled by this glued? Do you do anything except setting wakeup-capable?BR,
ThinhTo provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. Theusb_top_pdandusb_psw_pdare the logical power domains to represent the 3 power states.
- Full Power:usb_psw_pdON,usb_top_pON.
- Power Gated:usb_psw_pdOFF,usb_top_pON.
- Off:usb_psw_pdOFF,usb_top_pOFF.To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to
usb_top_pdand would cast a vote to keep it ON when attempting to enter hibernation. In addition, theusb_psw_pdruntime PM is directly tied togoogle->devso thatusb_psw_pdwould be OFF whengoogle->devsuspends. 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_pdis always ON? Even when xhci driver is not bound or when in device mode?Since
usb_top_pdis the parent power domain ofusb_psw_pd, andusb_psw_pdRPM is directly tied to glue device,usb_top_pdwould be ON when glue device is active (becauseusb_psw_pdis ON) and would be OFF when glue device suspends in non-hibernation scenarios (becauseusb_psw_pdis OFF). In hibernation scenario, a vote is casted forusb_top_pdto keep it on even when the glue device is suspended andusb_psw_pdis OFF.Quoted 5 lines
To your question,
usb_top_pdis 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_pdwould be ON because its childusb_psw_pdis 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
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 likeis_hibernationis 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 beforeis_hibernationis 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 thegoogle->devare working together in the glue here, particularly whyusb_top_pdis needed. It seemsusb_top_pdshouldn't be handled by this glued? Do you do anything except setting wakeup-capable?BR,
ThinhTo provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. Theusb_top_pdandusb_psw_pdare the logical power domains to represent the 3 power states.
- Full Power:usb_psw_pdON,usb_top_pON.
- Power Gated:usb_psw_pdOFF,usb_top_pON.
- Off:usb_psw_pdOFF,usb_top_pOFF.To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to
usb_top_pdand would cast a vote to keep it ON when attempting to enter hibernation. In addition, theusb_psw_pdruntime PM is directly tied togoogle->devso thatusb_psw_pdwould be OFF whengoogle->devsuspends. 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_pdis always ON? Even when xhci driver is not bound or when in device mode?Since
usb_top_pdis the parent power domain ofusb_psw_pd, andusb_psw_pdRPM is directly tied to glue device,usb_top_pdwould be ON when glue device is active (becauseusb_psw_pdis ON) and would be OFF when glue device suspends in non-hibernation scenarios (becauseusb_psw_pdis OFF). In hibernation scenario, a vote is casted forusb_top_pdto keep it on even when the glue device is suspended andusb_psw_pdis OFF.Quoted 5 lines
To your question,
usb_top_pdis 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_pdwould be ON because its childusb_psw_pdis 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
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 likeis_hibernationis 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 beforeis_hibernationis 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 thegoogle->devare working together in the glue here, particularly whyusb_top_pdis needed. It seemsusb_top_pdshouldn't be handled by this glued? Do you do anything except setting wakeup-capable?BR,
ThinhTo provide more context, the underlying usb power domain has 3 power
states: Full Power, Power Gated, Off. Theusb_top_pdandusb_psw_pdare the logical power domains to represent the 3 power states.
- Full Power:usb_psw_pdON,usb_top_pON.
- Power Gated:usb_psw_pdOFF,usb_top_pON.
- Off:usb_psw_pdOFF,usb_top_pOFF.To enter hibernation, the usb power domain must enter Power Gated state. To achieve this, this glue driver holds a handle to
usb_top_pdand would cast a vote to keep it ON when attempting to enter hibernation. In addition, theusb_psw_pdruntime PM is directly tied togoogle->devso thatusb_psw_pdwould be OFF whengoogle->devsuspends. 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_pdis always ON? Even when xhci driver is not bound or when in device mode?Since
usb_top_pdis the parent power domain ofusb_psw_pd, andusb_psw_pdRPM is directly tied to glue device,usb_top_pdwould be ON when glue device is active (becauseusb_psw_pdis ON) and would be OFF when glue device suspends in non-hibernation scenarios (becauseusb_psw_pdis OFF). In hibernation scenario, a vote is casted forusb_top_pdto keep it on even when the glue device is suspended andusb_psw_pdis OFF.Quoted 5 lines
To your question,
usb_top_pdis 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_pdwould be ON because its childusb_psw_pdis 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:
Acked-by Thinh Nguyen <[email protected]>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