* [PATCH v4 0/2] Add Google Tensor SoC USB controller support
@ 2025-10-17 23:34 Roy Luo
2025-10-17 23:34 ` [PATCH v4 1/2] dt-bindings: usb: dwc3: Add Google Tensor G5 DWC3 Roy Luo
2025-10-17 23:34 ` [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver Roy Luo
0 siblings, 2 replies; 17+ messages in thread
From: Roy Luo @ 2025-10-17 23:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Thinh Nguyen, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus
Cc: Joy Chakraborty, Naveen Kumar, Roy Luo, Badhri Jagan Sridharan,
devicetree, linux-kernel, linux-usb, linux-arm-kernel,
linux-samsung-soc
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.
Co-developed-by: Joy Chakraborty <joychakr@google.com>
Signed-off-by: Joy Chakraborty <joychakr@google.com>
Co-developed-by: Naveen Kumar <mnkumar@google.com>
Signed-off-by: Naveen Kumar <mnkumar@google.com>
Signed-off-by: Roy Luo <royluo@google.com>
---
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/20251010201607.1190967-1-royluo@google.com
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/20251008060000.3136021-1-royluo@google.com
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/20251006232125.1833979-1-royluo@google.com/
---
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
--
2.51.0.858.gf9c4a03a3a-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v4 1/2] dt-bindings: usb: dwc3: Add Google Tensor G5 DWC3
2025-10-17 23:34 [PATCH v4 0/2] Add Google Tensor SoC USB controller support Roy Luo
@ 2025-10-17 23:34 ` Roy Luo
2025-10-22 6:30 ` Krzysztof Kozlowski
2025-10-17 23:34 ` [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver Roy Luo
1 sibling, 1 reply; 17+ messages in thread
From: Roy Luo @ 2025-10-17 23:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Thinh Nguyen, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus
Cc: Joy Chakraborty, Naveen Kumar, Roy Luo, Badhri Jagan Sridharan,
devicetree, linux-kernel, linux-usb, linux-arm-kernel,
linux-samsung-soc
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.
Signed-off-by: Roy Luo <royluo@google.com>
---
.../bindings/usb/google,gs5-dwc3.yaml | 135 ++++++++++++++++++
1 file changed, 135 insertions(+)
create mode 100644 Documentation/devicetree/bindings/usb/google,gs5-dwc3.yaml
diff --git a/Documentation/devicetree/bindings/usb/google,gs5-dwc3.yaml b/Documentation/devicetree/bindings/usb/google,gs5-dwc3.yaml
new file mode 100644
index 000000000000..09756bf6fd3c
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/google,gs5-dwc3.yaml
@@ -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 <royluo@google.com>
+
+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>;
+ };
+ };
+...
--
2.51.0.858.gf9c4a03a3a-goog
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-10-17 23:34 [PATCH v4 0/2] Add Google Tensor SoC USB controller support Roy Luo
2025-10-17 23:34 ` [PATCH v4 1/2] dt-bindings: usb: dwc3: Add Google Tensor G5 DWC3 Roy Luo
@ 2025-10-17 23:34 ` Roy Luo
2025-10-23 22:43 ` Thinh Nguyen
2025-10-30 1:35 ` Thinh Nguyen
1 sibling, 2 replies; 17+ messages in thread
From: Roy Luo @ 2025-10-17 23:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Thinh Nguyen, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus
Cc: Joy Chakraborty, Naveen Kumar, Roy Luo, Badhri Jagan Sridharan,
devicetree, linux-kernel, linux-usb, linux-arm-kernel,
linux-samsung-soc
Add support for the DWC3 USB controller found on Google Tensor G5.
The controller features dual-role functionality and hibernation.
The primary focus is implementing hibernation support in host mode,
enabling the controller to enter a low-power state (D3). This is
particularly relevant during system power state transition and
runtime power management for power efficiency.
Highlights:
- Align suspend callback with dwc3_suspend_common() for deciding
between a full teardown and hibernation in host mode.
- Integration with `psw` (power switchable) and `top` power domains,
managing their states and device links to support hibernation.
- A notifier callback dwc3_google_usb_psw_pd_notifier() for
`psw` power domain events to manage controller state
transitions to/from D3.
- Coordination of the `non_sticky` reset during power state
transitions, asserting it on D3 entry and deasserting on D0 entry
in hibernation scenario.
- Handling of high-speed and super-speed PME interrupts
that are generated by remote wakeup during hibernation.
Co-developed-by: Joy Chakraborty <joychakr@google.com>
Signed-off-by: Joy Chakraborty <joychakr@google.com>
Co-developed-by: Naveen Kumar <mnkumar@google.com>
Signed-off-by: Naveen Kumar <mnkumar@google.com>
Signed-off-by: Roy Luo <royluo@google.com>
---
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
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 310d182e10b5..467515d5f937 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -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
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index 830e6c9e5fe0..a94982630657 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -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
diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c
new file mode 100644
index 000000000000..d3fec6fb4dcc
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-google.c
@@ -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 <royluo@google.com>
+ */
+
+#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;
+ 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 ||
+ 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);
+ 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;
+
+ 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);
+ 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");
+ 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.
+ */
+ 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
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: usb: dwc3: Add Google Tensor G5 DWC3
2025-10-17 23:34 ` [PATCH v4 1/2] dt-bindings: usb: dwc3: Add Google Tensor G5 DWC3 Roy Luo
@ 2025-10-22 6:30 ` Krzysztof Kozlowski
0 siblings, 0 replies; 17+ messages in thread
From: Krzysztof Kozlowski @ 2025-10-22 6:30 UTC (permalink / raw)
To: Roy Luo
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Thinh Nguyen, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree, linux-kernel, linux-usb,
linux-arm-kernel, linux-samsung-soc
On Fri, Oct 17, 2025 at 11:34:58PM +0000, Roy Luo wrote:
> 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.
>
> Signed-off-by: Roy Luo <royluo@google.com>
> ---
> .../bindings/usb/google,gs5-dwc3.yaml | 135 ++++++++++++++++++
> 1 file changed, 135 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/usb/google,gs5-dwc3.yaml
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-10-17 23:34 ` [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver Roy Luo
@ 2025-10-23 22:43 ` Thinh Nguyen
2025-10-25 0:25 ` Roy Luo
2025-10-30 1:35 ` Thinh Nguyen
1 sibling, 1 reply; 17+ messages in thread
From: Thinh Nguyen @ 2025-10-23 22:43 UTC (permalink / raw)
To: Roy Luo
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Thinh Nguyen, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Fri, Oct 17, 2025, Roy Luo wrote:
> Add support for the DWC3 USB controller found on Google Tensor G5.
> The controller features dual-role functionality and hibernation.
>
> The primary focus is implementing hibernation support in host mode,
> enabling the controller to enter a low-power state (D3). This is
> particularly relevant during system power state transition and
> runtime power management for power efficiency.
> Highlights:
> - Align suspend callback with dwc3_suspend_common() for deciding
> between a full teardown and hibernation in host mode.
> - Integration with `psw` (power switchable) and `top` power domains,
> managing their states and device links to support hibernation.
> - A notifier callback dwc3_google_usb_psw_pd_notifier() for
> `psw` power domain events to manage controller state
> transitions to/from D3.
> - Coordination of the `non_sticky` reset during power state
> transitions, asserting it on D3 entry and deasserting on D0 entry
> in hibernation scenario.
> - Handling of high-speed and super-speed PME interrupts
> that are generated by remote wakeup during hibernation.
>
> Co-developed-by: Joy Chakraborty <joychakr@google.com>
> Signed-off-by: Joy Chakraborty <joychakr@google.com>
> Co-developed-by: Naveen Kumar <mnkumar@google.com>
> Signed-off-by: Naveen Kumar <mnkumar@google.com>
> Signed-off-by: Roy Luo <royluo@google.com>
> ---
> 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
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-10-23 22:43 ` Thinh Nguyen
@ 2025-10-25 0:25 ` Roy Luo
2025-10-30 1:02 ` Thinh Nguyen
0 siblings, 1 reply; 17+ messages in thread
From: Roy Luo @ 2025-10-25 0:25 UTC (permalink / raw)
To: Thinh Nguyen
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Thu, Oct 23, 2025 at 3:43 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
>
> On Fri, Oct 17, 2025, Roy Luo wrote:
> > Add support for the DWC3 USB controller found on Google Tensor G5.
> > The controller features dual-role functionality and hibernation.
> >
> > The primary focus is implementing hibernation support in host mode,
> > enabling the controller to enter a low-power state (D3). This is
> > particularly relevant during system power state transition and
> > runtime power management for power efficiency.
> > Highlights:
> > - Align suspend callback with dwc3_suspend_common() for deciding
> > between a full teardown and hibernation in host mode.
> > - Integration with `psw` (power switchable) and `top` power domains,
> > managing their states and device links to support hibernation.
> > - A notifier callback dwc3_google_usb_psw_pd_notifier() for
> > `psw` power domain events to manage controller state
> > transitions to/from D3.
> > - Coordination of the `non_sticky` reset during power state
> > transitions, asserting it on D3 entry and deasserting on D0 entry
> > in hibernation scenario.
> > - Handling of high-speed and super-speed PME interrupts
> > that are generated by remote wakeup during hibernation.
> >
> > Co-developed-by: Joy Chakraborty <joychakr@google.com>
> > Signed-off-by: Joy Chakraborty <joychakr@google.com>
> > Co-developed-by: Naveen Kumar <mnkumar@google.com>
> > Signed-off-by: Naveen Kumar <mnkumar@google.com>
> > Signed-off-by: Roy Luo <royluo@google.com>
> > ---
> > 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.
>
No worry, thanks for paying attention to this patch!
> 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.
>
> 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
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-10-25 0:25 ` Roy Luo
@ 2025-10-30 1:02 ` Thinh Nguyen
0 siblings, 0 replies; 17+ messages in thread
From: Thinh Nguyen @ 2025-10-30 1:02 UTC (permalink / raw)
To: Roy Luo
Cc: Thinh Nguyen, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Fri, Oct 24, 2025, Roy Luo wrote:
> On Thu, Oct 23, 2025 at 3:43 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> >
> > On Fri, Oct 17, 2025, Roy Luo wrote:
> > > Add support for the DWC3 USB controller found on Google Tensor G5.
> > > The controller features dual-role functionality and hibernation.
> > >
> > > The primary focus is implementing hibernation support in host mode,
> > > enabling the controller to enter a low-power state (D3). This is
> > > particularly relevant during system power state transition and
> > > runtime power management for power efficiency.
> > > Highlights:
> > > - Align suspend callback with dwc3_suspend_common() for deciding
> > > between a full teardown and hibernation in host mode.
> > > - Integration with `psw` (power switchable) and `top` power domains,
> > > managing their states and device links to support hibernation.
> > > - A notifier callback dwc3_google_usb_psw_pd_notifier() for
> > > `psw` power domain events to manage controller state
> > > transitions to/from D3.
> > > - Coordination of the `non_sticky` reset during power state
> > > transitions, asserting it on D3 entry and deasserting on D0 entry
> > > in hibernation scenario.
> > > - Handling of high-speed and super-speed PME interrupts
> > > that are generated by remote wakeup during hibernation.
> > >
> > > Co-developed-by: Joy Chakraborty <joychakr@google.com>
> > > Signed-off-by: Joy Chakraborty <joychakr@google.com>
> > > Co-developed-by: Naveen Kumar <mnkumar@google.com>
> > > Signed-off-by: Naveen Kumar <mnkumar@google.com>
> > > Signed-off-by: Roy Luo <royluo@google.com>
> > > ---
> > > 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.
> >
>
> No worry, thanks for paying attention to this patch!
>
> > 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.
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.
>
> | 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.
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
>
> >
> > 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
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-10-17 23:34 ` [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver Roy Luo
2025-10-23 22:43 ` Thinh Nguyen
@ 2025-10-30 1:35 ` Thinh Nguyen
2025-11-01 0:49 ` Roy Luo
1 sibling, 1 reply; 17+ messages in thread
From: Thinh Nguyen @ 2025-10-30 1:35 UTC (permalink / raw)
To: Roy Luo
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Thinh Nguyen, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Fri, Oct 17, 2025, Roy Luo wrote:
> Add support for the DWC3 USB controller found on Google Tensor G5.
> The controller features dual-role functionality and hibernation.
>
> The primary focus is implementing hibernation support in host mode,
> enabling the controller to enter a low-power state (D3). This is
> particularly relevant during system power state transition and
> runtime power management for power efficiency.
> Highlights:
> - Align suspend callback with dwc3_suspend_common() for deciding
> between a full teardown and hibernation in host mode.
> - Integration with `psw` (power switchable) and `top` power domains,
> managing their states and device links to support hibernation.
> - A notifier callback dwc3_google_usb_psw_pd_notifier() for
> `psw` power domain events to manage controller state
> transitions to/from D3.
> - Coordination of the `non_sticky` reset during power state
> transitions, asserting it on D3 entry and deasserting on D0 entry
> in hibernation scenario.
> - Handling of high-speed and super-speed PME interrupts
> that are generated by remote wakeup during hibernation.
>
> Co-developed-by: Joy Chakraborty <joychakr@google.com>
> Signed-off-by: Joy Chakraborty <joychakr@google.com>
> Co-developed-by: Naveen Kumar <mnkumar@google.com>
> Signed-off-by: Naveen Kumar <mnkumar@google.com>
> Signed-off-by: Roy Luo <royluo@google.com>
> ---
> 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
>
> diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
> index 310d182e10b5..467515d5f937 100644
> --- a/drivers/usb/dwc3/Kconfig
> +++ b/drivers/usb/dwc3/Kconfig
> @@ -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
> diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
> index 830e6c9e5fe0..a94982630657 100644
> --- a/drivers/usb/dwc3/Makefile
> +++ b/drivers/usb/dwc3/Makefile
> @@ -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
> diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c
> new file mode 100644
> index 000000000000..d3fec6fb4dcc
> --- /dev/null
> +++ b/drivers/usb/dwc3/dwc3-google.c
> @@ -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 <royluo@google.com>
> + */
> +
> +#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;
> + 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?
> + 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.
> + 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;
> +
> + 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?
> + 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");
> + 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
> + 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
>
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-10-30 1:35 ` Thinh Nguyen
@ 2025-11-01 0:49 ` Roy Luo
2025-11-01 6:10 ` Greg Kroah-Hartman
2025-11-04 2:07 ` Thinh Nguyen
0 siblings, 2 replies; 17+ messages in thread
From: Roy Luo @ 2025-11-01 0:49 UTC (permalink / raw)
To: Thinh Nguyen
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
>
> On Fri, Oct 17, 2025, Roy Luo wrote:
> > Add support for the DWC3 USB controller found on Google Tensor G5.
> > The controller features dual-role functionality and hibernation.
> >
> > The primary focus is implementing hibernation support in host mode,
> > enabling the controller to enter a low-power state (D3). This is
> > particularly relevant during system power state transition and
> > runtime power management for power efficiency.
> > Highlights:
> > - Align suspend callback with dwc3_suspend_common() for deciding
> > between a full teardown and hibernation in host mode.
> > - Integration with `psw` (power switchable) and `top` power domains,
> > managing their states and device links to support hibernation.
> > - A notifier callback dwc3_google_usb_psw_pd_notifier() for
> > `psw` power domain events to manage controller state
> > transitions to/from D3.
> > - Coordination of the `non_sticky` reset during power state
> > transitions, asserting it on D3 entry and deasserting on D0 entry
> > in hibernation scenario.
> > - Handling of high-speed and super-speed PME interrupts
> > that are generated by remote wakeup during hibernation.
> >
> > Co-developed-by: Joy Chakraborty <joychakr@google.com>
> > Signed-off-by: Joy Chakraborty <joychakr@google.com>
> > Co-developed-by: Naveen Kumar <mnkumar@google.com>
> > Signed-off-by: Naveen Kumar <mnkumar@google.com>
> > Signed-off-by: Roy Luo <royluo@google.com>
> > ---
> > 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
> >
> > diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
> > index 310d182e10b5..467515d5f937 100644
> > --- a/drivers/usb/dwc3/Kconfig
> > +++ b/drivers/usb/dwc3/Kconfig
> > @@ -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
> > diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
> > index 830e6c9e5fe0..a94982630657 100644
> > --- a/drivers/usb/dwc3/Makefile
> > +++ b/drivers/usb/dwc3/Makefile
> > @@ -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
> > diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c
> > new file mode 100644
> > index 000000000000..d3fec6fb4dcc
> > --- /dev/null
> > +++ b/drivers/usb/dwc3/dwc3-google.c
> > @@ -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 <royluo@google.com>
> > + */
> > +
> > +#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;
> > + u32 irq_status, dr_role;
> > +
> > + irq_status = dwc3_google_clear_pme_irqs(google);
> > + dr_role = dwc->current_dr_role;
> > +
> > + if (!irq_status || !google->is_hibernation ||
>
> Any handling of race condition with is_hibernation?
IIUC, you're referring to the race between
- pme irq handler: dwc3_google_resume_irq() reads is_hibernation
- suspend/resume work: sets and unsets is_hibernation
Possible scenarios:
1. Suspend into hibernation: everything is serialized.
dwc3_google_suspend() sets is_hibernation
-> usb psw pdom is turned off
-> controller enters D3
-> pme irq could be triggered
-> pme irq handler
2.1 Resume from hibernation: irq handler wins the race.
pme irq handler (is_hibernation = true) triggers resume
-> usb psw pdom is turned on
-> dwc3_google_resume() unset is_hibernation
2.2 Resume from hibernation: resume work wins the race.
usb psw pdom is turned on
-> dwc3_google_resume() unset is_hibernation
-> pme irq handler (is_hibernation = false) does nothing
So far I don't see any issues in any of the scenarios, please
let me know if otherwise.
>
> > + 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.
>
> > + 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;
> > +
> > + 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.
>
> > + 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");
> > + 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
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
>
> > + 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
> >
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-11-01 0:49 ` Roy Luo
@ 2025-11-01 6:10 ` Greg Kroah-Hartman
2025-11-04 10:28 ` Roy Luo
2025-11-04 2:07 ` Thinh Nguyen
1 sibling, 1 reply; 17+ messages in thread
From: Greg Kroah-Hartman @ 2025-11-01 6:10 UTC (permalink / raw)
To: Roy Luo
Cc: Thinh Nguyen, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Peter Griffin, André Draszik, Tudor Ambarus,
Joy Chakraborty, Naveen Kumar, Badhri Jagan Sridharan,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-usb@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Fri, Oct 31, 2025 at 05:49:28PM -0700, Roy Luo wrote:
> > > + dr_role != DWC3_GCTL_PRTCAP_HOST) {
> > > + dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
> > > + irq, google->is_hibernation, dr_role);
> >
> > Should we limit this print and do we need this to be dev_warn? It may be
> > noisy wouldn't it.
>
> Ack, will make it WARN_ONCE in the next version.
So you really want to panic your system if this happens (remember, the
HUGE majority of Linux systems run with panic-on-warn enabled)?
Please do not, handle the issue, dump a message to the log if you really
need to, and move on, don't crash.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-11-01 0:49 ` Roy Luo
2025-11-01 6:10 ` Greg Kroah-Hartman
@ 2025-11-04 2:07 ` Thinh Nguyen
2025-11-04 10:55 ` Roy Luo
1 sibling, 1 reply; 17+ messages in thread
From: Thinh Nguyen @ 2025-11-04 2:07 UTC (permalink / raw)
To: Roy Luo
Cc: Thinh Nguyen, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Fri, Oct 31, 2025, Roy Luo wrote:
> On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> >
> > On Fri, Oct 17, 2025, Roy Luo wrote:
> > > Add support for the DWC3 USB controller found on Google Tensor G5.
> > > The controller features dual-role functionality and hibernation.
> > >
> > > The primary focus is implementing hibernation support in host mode,
> > > enabling the controller to enter a low-power state (D3). This is
> > > particularly relevant during system power state transition and
> > > runtime power management for power efficiency.
> > > Highlights:
> > > - Align suspend callback with dwc3_suspend_common() for deciding
> > > between a full teardown and hibernation in host mode.
> > > - Integration with `psw` (power switchable) and `top` power domains,
> > > managing their states and device links to support hibernation.
> > > - A notifier callback dwc3_google_usb_psw_pd_notifier() for
> > > `psw` power domain events to manage controller state
> > > transitions to/from D3.
> > > - Coordination of the `non_sticky` reset during power state
> > > transitions, asserting it on D3 entry and deasserting on D0 entry
> > > in hibernation scenario.
> > > - Handling of high-speed and super-speed PME interrupts
> > > that are generated by remote wakeup during hibernation.
> > >
> > > Co-developed-by: Joy Chakraborty <joychakr@google.com>
> > > Signed-off-by: Joy Chakraborty <joychakr@google.com>
> > > Co-developed-by: Naveen Kumar <mnkumar@google.com>
> > > Signed-off-by: Naveen Kumar <mnkumar@google.com>
> > > Signed-off-by: Roy Luo <royluo@google.com>
> > > ---
> > > 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
> > >
> > > diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
> > > index 310d182e10b5..467515d5f937 100644
> > > --- a/drivers/usb/dwc3/Kconfig
> > > +++ b/drivers/usb/dwc3/Kconfig
> > > @@ -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
> > > diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
> > > index 830e6c9e5fe0..a94982630657 100644
> > > --- a/drivers/usb/dwc3/Makefile
> > > +++ b/drivers/usb/dwc3/Makefile
> > > @@ -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
> > > diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c
> > > new file mode 100644
> > > index 000000000000..d3fec6fb4dcc
> > > --- /dev/null
> > > +++ b/drivers/usb/dwc3/dwc3-google.c
> > > @@ -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 <royluo@google.com>
> > > + */
> > > +
> > > +#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;
> > > + 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
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.
> 2.1 Resume from hibernation: irq handler wins the race.
> pme irq handler (is_hibernation = true) triggers resume
> -> usb psw pdom is turned on
> -> dwc3_google_resume() unset is_hibernation
> 2.2 Resume from hibernation: resume work wins the race.
> usb psw pdom is turned on
> -> dwc3_google_resume() unset is_hibernation
> -> pme irq handler (is_hibernation = false) does nothing
>
> So far I don't see any issues in any of the scenarios, please
> let me know if otherwise.
>
> >
> > > + dr_role != DWC3_GCTL_PRTCAP_HOST) {
> > > + dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
> > > + irq, google->is_hibernation, dr_role);
> >
> > Should we limit this print and do we need this to be dev_warn? It may be
> > noisy wouldn't it.
>
> Ack, will make it WARN_ONCE in the next version.
As Greg noted, don't use WARN_ONCE. I don't know how often spurious
come, if there are many, perhaps use dev_dbg_ratelimited? If not, just
stick to dev_dbg(). The regular user doesn't need to know about this
right?
>
> >
> > > + 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;
> > > +
> > > + 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.
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.
>
> >
> > > + 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");
> > > + 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
>
> 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.
>
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
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-11-01 6:10 ` Greg Kroah-Hartman
@ 2025-11-04 10:28 ` Roy Luo
0 siblings, 0 replies; 17+ messages in thread
From: Roy Luo @ 2025-11-04 10:28 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Thinh Nguyen, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Peter Griffin, André Draszik, Tudor Ambarus,
Joy Chakraborty, Naveen Kumar, Badhri Jagan Sridharan,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-usb@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Sat, Nov 1, 2025 at 2:10 PM Greg Kroah-Hartman
<gregkh@linuxfoundation.org> wrote:
>
> On Fri, Oct 31, 2025 at 05:49:28PM -0700, Roy Luo wrote:
> > > > + dr_role != DWC3_GCTL_PRTCAP_HOST) {
> > > > + dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
> > > > + irq, google->is_hibernation, dr_role);
> > >
> > > Should we limit this print and do we need this to be dev_warn? It may be
> > > noisy wouldn't it.
> >
> > Ack, will make it WARN_ONCE in the next version.
>
> So you really want to panic your system if this happens (remember, the
> HUGE majority of Linux systems run with panic-on-warn enabled)?
>
> Please do not, handle the issue, dump a message to the log if you really
> need to, and move on, don't crash.
>
> thanks,
>
> greg k-h
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
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-11-04 2:07 ` Thinh Nguyen
@ 2025-11-04 10:55 ` Roy Luo
2025-11-06 0:38 ` Thinh Nguyen
0 siblings, 1 reply; 17+ messages in thread
From: Roy Luo @ 2025-11-04 10:55 UTC (permalink / raw)
To: Thinh Nguyen
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Tue, Nov 4, 2025 at 10:07 AM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
>
> On Fri, Oct 31, 2025, Roy Luo wrote:
> > On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > >
> > > On Fri, Oct 17, 2025, Roy Luo wrote:
> > > > Add support for the DWC3 USB controller found on Google Tensor G5.
> > > > The controller features dual-role functionality and hibernation.
> > > >
> > > > The primary focus is implementing hibernation support in host mode,
> > > > enabling the controller to enter a low-power state (D3). This is
> > > > particularly relevant during system power state transition and
> > > > runtime power management for power efficiency.
> > > > Highlights:
> > > > - Align suspend callback with dwc3_suspend_common() for deciding
> > > > between a full teardown and hibernation in host mode.
> > > > - Integration with `psw` (power switchable) and `top` power domains,
> > > > managing their states and device links to support hibernation.
> > > > - A notifier callback dwc3_google_usb_psw_pd_notifier() for
> > > > `psw` power domain events to manage controller state
> > > > transitions to/from D3.
> > > > - Coordination of the `non_sticky` reset during power state
> > > > transitions, asserting it on D3 entry and deasserting on D0 entry
> > > > in hibernation scenario.
> > > > - Handling of high-speed and super-speed PME interrupts
> > > > that are generated by remote wakeup during hibernation.
> > > >
> > > > Co-developed-by: Joy Chakraborty <joychakr@google.com>
> > > > Signed-off-by: Joy Chakraborty <joychakr@google.com>
> > > > Co-developed-by: Naveen Kumar <mnkumar@google.com>
> > > > Signed-off-by: Naveen Kumar <mnkumar@google.com>
> > > > Signed-off-by: Roy Luo <royluo@google.com>
> > > > ---
> > > > 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
> > > >
> > > > diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
> > > > index 310d182e10b5..467515d5f937 100644
> > > > --- a/drivers/usb/dwc3/Kconfig
> > > > +++ b/drivers/usb/dwc3/Kconfig
> > > > @@ -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
> > > > diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
> > > > index 830e6c9e5fe0..a94982630657 100644
> > > > --- a/drivers/usb/dwc3/Makefile
> > > > +++ b/drivers/usb/dwc3/Makefile
> > > > @@ -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
> > > > diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c
> > > > new file mode 100644
> > > > index 000000000000..d3fec6fb4dcc
> > > > --- /dev/null
> > > > +++ b/drivers/usb/dwc3/dwc3-google.c
> > > > @@ -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 <royluo@google.com>
> > > > + */
> > > > +
> > > > +#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;
> > > > + 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
>
> In dwc3_google_suspend(), looks like is_hibernation is set after you
> enable pme irq, probably very unlikely, but can the interrupt be
> asserted then? If so, will there be another interrupt asserted?
> Otherwise the current logic may think it was spurious interrupt a miss
> an event.
The pme interrupt can only be asserted after controller is in
hibernation, that is, after the usb psw dom is turned off and
the dwc3_google_usb_psw_pd_notifier() callback is
completed. So no, the interrupt won't fire before is_hibernation
is set.
>
> > 2.1 Resume from hibernation: irq handler wins the race.
> > pme irq handler (is_hibernation = true) triggers resume
> > -> usb psw pdom is turned on
> > -> dwc3_google_resume() unset is_hibernation
> > 2.2 Resume from hibernation: resume work wins the race.
> > usb psw pdom is turned on
> > -> dwc3_google_resume() unset is_hibernation
> > -> pme irq handler (is_hibernation = false) does nothing
> >
> > So far I don't see any issues in any of the scenarios, please
> > let me know if otherwise.
> >
> > >
> > > > + dr_role != DWC3_GCTL_PRTCAP_HOST) {
> > > > + dev_warn(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
> > > > + irq, google->is_hibernation, dr_role);
> > >
> > > Should we limit this print and do we need this to be dev_warn? It may be
> > > noisy wouldn't it.
> >
> > Ack, will make it WARN_ONCE in the next version.
>
> As Greg noted, don't use WARN_ONCE. I don't know how often spurious
> come, if there are many, perhaps use dev_dbg_ratelimited? If not, just
> stick to dev_dbg(). The regular user doesn't need to know about this
> right?
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.
>
> >
> > >
> > > > + 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;
> > > > +
> > > > + 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.
>
> 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.
>
> >
> > >
> > > > + 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");
> > > > + 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
> >
> > 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.
> >
>
> Yes. This is very helpful.
>
> So, while the glue driver is bound, usb_top_pd is always ON? Even when
> xhci driver is not bound or when in device mode?
Since usb_top_pd is the parent power domain of usb_psw_pd, and
usb_psw_pd RPM is directly tied to glue device, usb_top_pd would
be ON when glue device is active (because usb_psw_pd is ON)
and would be OFF when glue device suspends in non-hibernation
scenarios (because usb_psw_pd is OFF). In hibernation scenario,
a vote is casted for usb_top_pd to keep it on even when the
glue device is suspended and usb_psw_pd is OFF.
To your question, usb_top_pd is not always ON because it would be
turned off when the glue device suspends in non-hibernation scenario.
When in device mode and provided dwc3 dev is active, usb_top_pd
would be ON because its child usb_psw_pd is ON.
Regards,
Roy Luo
>
> BR,
> Thinh
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-11-04 10:55 ` Roy Luo
@ 2025-11-06 0:38 ` Thinh Nguyen
2025-11-06 2:38 ` Roy Luo
0 siblings, 1 reply; 17+ messages in thread
From: Thinh Nguyen @ 2025-11-06 0:38 UTC (permalink / raw)
To: Roy Luo
Cc: Thinh Nguyen, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Tue, Nov 04, 2025, Roy Luo wrote:
> On Tue, Nov 4, 2025 at 10:07 AM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> >
> > On Fri, Oct 31, 2025, Roy Luo wrote:
> > > On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> >
> > In dwc3_google_suspend(), looks like is_hibernation is set after you
> > enable pme irq, probably very unlikely, but can the interrupt be
> > asserted then? If so, will there be another interrupt asserted?
> > Otherwise the current logic may think it was spurious interrupt a miss
> > an event.
>
> The pme interrupt can only be asserted after controller is in
> hibernation, that is, after the usb psw dom is turned off and
> the dwc3_google_usb_psw_pd_notifier() callback is
> completed. So no, the interrupt won't fire before is_hibernation
> is set.
Thanks for the confirmation.
<snip>
> > > >
> > > > I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and
> > > > the google->dev are working together in the glue here, particularly why
> > > > usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this
> > > > glued? Do you do anything except setting wakeup-capable?
> > > >
> > > > BR,
> > > > Thinh
> > >
> > > To provide more context, the underlying usb power domain has 3 power
> > > states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd
> > > are the logical power domains to represent the 3 power states.
> > > - Full Power: usb_psw_pd ON, usb_top_p ON.
> > > - Power Gated: usb_psw_pd OFF, usb_top_p ON.
> > > - Off: usb_psw_pd OFF, usb_top_p OFF.
> > >
> > > To enter hibernation, the usb power domain must enter Power Gated
> > > state. To achieve this, this glue driver holds a handle to usb_top_pd
> > > and would cast a vote to keep it ON when attempting to enter
> > > hibernation. In addition, the usb_psw_pd runtime PM is directly tied
> > > to google->dev so that usb_psw_pd would be OFF when google->dev
> > > suspends. Together, the usb power domain would reach Power Gated
> > > state when device suspends.
> > >
> > > I hope this information helps.
> > >
> >
> > Yes. This is very helpful.
> >
> > So, while the glue driver is bound, usb_top_pd is always ON? Even when
> > xhci driver is not bound or when in device mode?
>
> Since usb_top_pd is the parent power domain of usb_psw_pd, and
> usb_psw_pd RPM is directly tied to glue device, usb_top_pd would
> be ON when glue device is active (because usb_psw_pd is ON)
> and would be OFF when glue device suspends in non-hibernation
> scenarios (because usb_psw_pd is OFF). In hibernation scenario,
> a vote is casted for usb_top_pd to keep it on even when the
> glue device is suspended and usb_psw_pd is OFF.
>
> To your question, usb_top_pd is not always ON because it would be
> turned off when the glue device suspends in non-hibernation scenario.
> When in device mode and provided dwc3 dev is active, usb_top_pd
> would be ON because its child usb_psw_pd is ON.
>
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
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-11-06 0:38 ` Thinh Nguyen
@ 2025-11-06 2:38 ` Roy Luo
2025-11-06 23:48 ` Thinh Nguyen
0 siblings, 1 reply; 17+ messages in thread
From: Roy Luo @ 2025-11-06 2:38 UTC (permalink / raw)
To: Thinh Nguyen
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Thu, Nov 6, 2025 at 8:38 AM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
>
> On Tue, Nov 04, 2025, Roy Luo wrote:
> > On Tue, Nov 4, 2025 at 10:07 AM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > >
> > > On Fri, Oct 31, 2025, Roy Luo wrote:
> > > > On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > >
> > > In dwc3_google_suspend(), looks like is_hibernation is set after you
> > > enable pme irq, probably very unlikely, but can the interrupt be
> > > asserted then? If so, will there be another interrupt asserted?
> > > Otherwise the current logic may think it was spurious interrupt a miss
> > > an event.
> >
> > The pme interrupt can only be asserted after controller is in
> > hibernation, that is, after the usb psw dom is turned off and
> > the dwc3_google_usb_psw_pd_notifier() callback is
> > completed. So no, the interrupt won't fire before is_hibernation
> > is set.
>
> Thanks for the confirmation.
>
>
> <snip>
>
>
> > > > >
> > > > > I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and
> > > > > the google->dev are working together in the glue here, particularly why
> > > > > usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this
> > > > > glued? Do you do anything except setting wakeup-capable?
> > > > >
> > > > > BR,
> > > > > Thinh
> > > >
> > > > To provide more context, the underlying usb power domain has 3 power
> > > > states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd
> > > > are the logical power domains to represent the 3 power states.
> > > > - Full Power: usb_psw_pd ON, usb_top_p ON.
> > > > - Power Gated: usb_psw_pd OFF, usb_top_p ON.
> > > > - Off: usb_psw_pd OFF, usb_top_p OFF.
> > > >
> > > > To enter hibernation, the usb power domain must enter Power Gated
> > > > state. To achieve this, this glue driver holds a handle to usb_top_pd
> > > > and would cast a vote to keep it ON when attempting to enter
> > > > hibernation. In addition, the usb_psw_pd runtime PM is directly tied
> > > > to google->dev so that usb_psw_pd would be OFF when google->dev
> > > > suspends. Together, the usb power domain would reach Power Gated
> > > > state when device suspends.
> > > >
> > > > I hope this information helps.
> > > >
> > >
> > > Yes. This is very helpful.
> > >
> > > So, while the glue driver is bound, usb_top_pd is always ON? Even when
> > > xhci driver is not bound or when in device mode?
> >
> > Since usb_top_pd is the parent power domain of usb_psw_pd, and
> > usb_psw_pd RPM is directly tied to glue device, usb_top_pd would
> > be ON when glue device is active (because usb_psw_pd is ON)
> > and would be OFF when glue device suspends in non-hibernation
> > scenarios (because usb_psw_pd is OFF). In hibernation scenario,
> > a vote is casted for usb_top_pd to keep it on even when the
> > glue device is suspended and usb_psw_pd is OFF.
> >
> > To your question, usb_top_pd is not always ON because it would be
> > turned off when the glue device suspends in non-hibernation scenario.
> > When in device mode and provided dwc3 dev is active, usb_top_pd
> > would be ON because its child usb_psw_pd is ON.
> >
>
> 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.
>
> 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
>
> BR,
> Thinh
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-11-06 2:38 ` Roy Luo
@ 2025-11-06 23:48 ` Thinh Nguyen
2025-11-07 9:11 ` Roy Luo
0 siblings, 1 reply; 17+ messages in thread
From: Thinh Nguyen @ 2025-11-06 23:48 UTC (permalink / raw)
To: Roy Luo
Cc: Thinh Nguyen, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Thu, Nov 06, 2025, Roy Luo wrote:
> On Thu, Nov 6, 2025 at 8:38 AM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> >
> > On Tue, Nov 04, 2025, Roy Luo wrote:
> > > On Tue, Nov 4, 2025 at 10:07 AM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > > >
> > > > On Fri, Oct 31, 2025, Roy Luo wrote:
> > > > > On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > > >
> > > > In dwc3_google_suspend(), looks like is_hibernation is set after you
> > > > enable pme irq, probably very unlikely, but can the interrupt be
> > > > asserted then? If so, will there be another interrupt asserted?
> > > > Otherwise the current logic may think it was spurious interrupt a miss
> > > > an event.
> > >
> > > The pme interrupt can only be asserted after controller is in
> > > hibernation, that is, after the usb psw dom is turned off and
> > > the dwc3_google_usb_psw_pd_notifier() callback is
> > > completed. So no, the interrupt won't fire before is_hibernation
> > > is set.
> >
> > Thanks for the confirmation.
> >
> >
> > <snip>
> >
> >
> > > > > >
> > > > > > I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and
> > > > > > the google->dev are working together in the glue here, particularly why
> > > > > > usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this
> > > > > > glued? Do you do anything except setting wakeup-capable?
> > > > > >
> > > > > > BR,
> > > > > > Thinh
> > > > >
> > > > > To provide more context, the underlying usb power domain has 3 power
> > > > > states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd
> > > > > are the logical power domains to represent the 3 power states.
> > > > > - Full Power: usb_psw_pd ON, usb_top_p ON.
> > > > > - Power Gated: usb_psw_pd OFF, usb_top_p ON.
> > > > > - Off: usb_psw_pd OFF, usb_top_p OFF.
> > > > >
> > > > > To enter hibernation, the usb power domain must enter Power Gated
> > > > > state. To achieve this, this glue driver holds a handle to usb_top_pd
> > > > > and would cast a vote to keep it ON when attempting to enter
> > > > > hibernation. In addition, the usb_psw_pd runtime PM is directly tied
> > > > > to google->dev so that usb_psw_pd would be OFF when google->dev
> > > > > suspends. Together, the usb power domain would reach Power Gated
> > > > > state when device suspends.
> > > > >
> > > > > I hope this information helps.
> > > > >
> > > >
> > > > Yes. This is very helpful.
> > > >
> > > > So, while the glue driver is bound, usb_top_pd is always ON? Even when
> > > > xhci driver is not bound or when in device mode?
> > >
> > > Since usb_top_pd is the parent power domain of usb_psw_pd, and
> > > usb_psw_pd RPM is directly tied to glue device, usb_top_pd would
> > > be ON when glue device is active (because usb_psw_pd is ON)
> > > and would be OFF when glue device suspends in non-hibernation
> > > scenarios (because usb_psw_pd is OFF). In hibernation scenario,
> > > a vote is casted for usb_top_pd to keep it on even when the
> > > glue device is suspended and usb_psw_pd is OFF.
> > >
> > > To your question, usb_top_pd is not always ON because it would be
> > > turned off when the glue device suspends in non-hibernation scenario.
> > > When in device mode and provided dwc3 dev is active, usb_top_pd
> > > would be ON because its child usb_psw_pd is ON.
> > >
> >
> > 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.
>
> >
> > 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:
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Thanks,
Thinh
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver
2025-11-06 23:48 ` Thinh Nguyen
@ 2025-11-07 9:11 ` Roy Luo
0 siblings, 0 replies; 17+ messages in thread
From: Roy Luo @ 2025-11-07 9:11 UTC (permalink / raw)
To: Thinh Nguyen
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Greg Kroah-Hartman, Philipp Zabel, Peter Griffin,
André Draszik, Tudor Ambarus, Joy Chakraborty, Naveen Kumar,
Badhri Jagan Sridharan, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-samsung-soc@vger.kernel.org
On Fri, Nov 7, 2025 at 7:48 AM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
>
> On Thu, Nov 06, 2025, Roy Luo wrote:
> > On Thu, Nov 6, 2025 at 8:38 AM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > >
> > > On Tue, Nov 04, 2025, Roy Luo wrote:
> > > > On Tue, Nov 4, 2025 at 10:07 AM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > > > >
> > > > > On Fri, Oct 31, 2025, Roy Luo wrote:
> > > > > > On Wed, Oct 29, 2025 at 6:35 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > > > >
> > > > > In dwc3_google_suspend(), looks like is_hibernation is set after you
> > > > > enable pme irq, probably very unlikely, but can the interrupt be
> > > > > asserted then? If so, will there be another interrupt asserted?
> > > > > Otherwise the current logic may think it was spurious interrupt a miss
> > > > > an event.
> > > >
> > > > The pme interrupt can only be asserted after controller is in
> > > > hibernation, that is, after the usb psw dom is turned off and
> > > > the dwc3_google_usb_psw_pd_notifier() callback is
> > > > completed. So no, the interrupt won't fire before is_hibernation
> > > > is set.
> > >
> > > Thanks for the confirmation.
> > >
> > >
> > > <snip>
> > >
> > >
> > > > > > >
> > > > > > > I'm still trying to wrap my head around how usb_top_pd, usb_psw_pd, and
> > > > > > > the google->dev are working together in the glue here, particularly why
> > > > > > > usb_top_pd is needed. It seems usb_top_pd shouldn't be handled by this
> > > > > > > glued? Do you do anything except setting wakeup-capable?
> > > > > > >
> > > > > > > BR,
> > > > > > > Thinh
> > > > > >
> > > > > > To provide more context, the underlying usb power domain has 3 power
> > > > > > states: Full Power, Power Gated, Off. The usb_top_pd and usb_psw_pd
> > > > > > are the logical power domains to represent the 3 power states.
> > > > > > - Full Power: usb_psw_pd ON, usb_top_p ON.
> > > > > > - Power Gated: usb_psw_pd OFF, usb_top_p ON.
> > > > > > - Off: usb_psw_pd OFF, usb_top_p OFF.
> > > > > >
> > > > > > To enter hibernation, the usb power domain must enter Power Gated
> > > > > > state. To achieve this, this glue driver holds a handle to usb_top_pd
> > > > > > and would cast a vote to keep it ON when attempting to enter
> > > > > > hibernation. In addition, the usb_psw_pd runtime PM is directly tied
> > > > > > to google->dev so that usb_psw_pd would be OFF when google->dev
> > > > > > suspends. Together, the usb power domain would reach Power Gated
> > > > > > state when device suspends.
> > > > > >
> > > > > > I hope this information helps.
> > > > > >
> > > > >
> > > > > Yes. This is very helpful.
> > > > >
> > > > > So, while the glue driver is bound, usb_top_pd is always ON? Even when
> > > > > xhci driver is not bound or when in device mode?
> > > >
> > > > Since usb_top_pd is the parent power domain of usb_psw_pd, and
> > > > usb_psw_pd RPM is directly tied to glue device, usb_top_pd would
> > > > be ON when glue device is active (because usb_psw_pd is ON)
> > > > and would be OFF when glue device suspends in non-hibernation
> > > > scenarios (because usb_psw_pd is OFF). In hibernation scenario,
> > > > a vote is casted for usb_top_pd to keep it on even when the
> > > > glue device is suspended and usb_psw_pd is OFF.
> > > >
> > > > To your question, usb_top_pd is not always ON because it would be
> > > > turned off when the glue device suspends in non-hibernation scenario.
> > > > When in device mode and provided dwc3 dev is active, usb_top_pd
> > > > would be ON because its child usb_psw_pd is ON.
> > > >
> > >
> > > 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.
> >
> > >
> > > 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:
>
> Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
>
> 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/89733ddf-8af3-42d0-b6e5-20b7a4ef588c@kernel.org/
Regards,
Roy Luo
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2025-11-07 9:12 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-17 23:34 [PATCH v4 0/2] Add Google Tensor SoC USB controller support Roy Luo
2025-10-17 23:34 ` [PATCH v4 1/2] dt-bindings: usb: dwc3: Add Google Tensor G5 DWC3 Roy Luo
2025-10-22 6:30 ` Krzysztof Kozlowski
2025-10-17 23:34 ` [PATCH v4 2/2] usb: dwc3: Add Google Tensor SoC DWC3 glue driver Roy Luo
2025-10-23 22:43 ` Thinh Nguyen
2025-10-25 0:25 ` Roy Luo
2025-10-30 1:02 ` Thinh Nguyen
2025-10-30 1:35 ` Thinh Nguyen
2025-11-01 0:49 ` Roy Luo
2025-11-01 6:10 ` Greg Kroah-Hartman
2025-11-04 10:28 ` Roy Luo
2025-11-04 2:07 ` Thinh Nguyen
2025-11-04 10:55 ` Roy Luo
2025-11-06 0:38 ` Thinh Nguyen
2025-11-06 2:38 ` Roy Luo
2025-11-06 23:48 ` Thinh Nguyen
2025-11-07 9:11 ` Roy Luo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).