Linux USB
 help / color / mirror / Atom feed
* [PATCH v7 0/2] Add CIX Sky1 USBSSP controller support
@ 2026-11-06 10:12 Hongliang Yang
  2026-11-06 10:12 ` [PATCH v7 1/2] dt-bindings: usb: Add CIX Sky1 USBSSP controller Hongliang Yang
  2026-11-06 10:12 ` [PATCH v7 2/2] usb: cdns3: Add CIX Sky1 glue driver Hongliang Yang
  0 siblings, 2 replies; 5+ messages in thread
From: Hongliang Yang @ 2026-11-06 10:12 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Peter Chen, Pawel Laszczak, Roger Quadros,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
	Gary Yang, Fugang Duan
  Cc: linux-usb, linux-arm-kernel, devicetree, linux-kernel,
	cix-kernel-upstream, hongliang.yang

This series adds support for the Cadence USBSSP DRD controller
integrated in the CIX Sky1 SoC: the device tree binding and the glue
driver. It is a USB series only. The arm64 device tree nodes belong to
the SoC subsystem, so they are not part of this series and will be sent
separately once the binding is accepted.

Tested on Radxa Orion O6 (Sky1) in host mode, together with the CIX
USB3 PHY series posted separately. The series applies on top of
v7.4-rc1.

Each controller is described as one address range in the USB30 Type-A
DRD RCSU block at 0x091c0000, starting at its AXI cache attribute strap
register and ending after its AXI clock status register. The two Type-A
controllers interleave their strap and status registers in that block,
so the two ranges overlap, and the range is mapped with devm_ioremap()
instead of devm_platform_ioremap_resource(): requesting the region would
make the second controller fail with -EBUSY. The register map of the
block is in the binding commit message.

Changes in v7:
- Describe the RCSU registers of each controller as one address range
  starting at its AXI cache attribute register, instead of two 16-byte
  register slots, and drop the reg-names property.
- Map that range with devm_ioremap(); request_mem_region() cannot be
  used because the two Type-A controllers share the block and their
  ranges overlap.
- Take the offset of the AXI clock status register from per-compatible
  driver data (0xec on Type-A, 0xf0 on Type-C), instead of two
  reg-names.
- Dropped #syscon-cells from the S5 system controller binding: the
  property does not exist in the kernel, and the series no longer
  touches bindings/soc/cix.
- Removed the arm64 dts from the series: it is a separate SoC patchset.

Changes in v6:
- Dropped the simple-bus RCSU container; each node is placed at the
  address of its glue registers and the core is a child at its own
  address.
- Replaced the usbN aliases and the undocumented id property with the
  register offset and bit position arguments of cix,usb-syscon.
- Converted probe error paths to dev_err_probe().
- Removed the unused over-current GPIO handling and the unused dr_base
  and cdnsp_pdev fields.
- Fixed the 125us prescaler value: time * frequency - 1.
- Made the AXI halt writes read-modify-write so the preserved and RW1C
  bits of XEC_AXI_CAP keep their state.

Changes in v5:
- Dropped the cix,sky1-usbss-rcsu syscon binding and the cix,usb-rcsu
  phandle; the glue registers are described in the controller node.
- Added the MAINTAINERS entry for the glue driver.

Changes in v4:
- Split the combined PHY/USBSSP series into two independent series.
- Restructured the RCSU registers as a shared syscon region with offsets
  (reverted in v5).
- Fixed the indentation reported by checkpatch.

Changes in v3:
- Fixed the interrupt specifiers of the core nodes: the Sky1 GICv3 uses
  four cells, so every entry needs the fourth (empty) cell.
- PHY driver: assert the resets before registering the PHY provider,
  disable clocks in LIFO order, and fix the error unroll path so clocks
  that were never enabled are not disabled.

Changes in v2:
- Split the single binding into a PHY binding and a USBSSP binding.
- clock-names: apb/ref for the PHY, sof/axi/lpm/apb for the controller.
- reset-names: core/apb instead of reset/preset.
- Dropped axi_bmax_value from DT; it is a driver constant.
- Added the MAINTAINERS entries.

Changes in v1: initial version.

Hongliang Yang (2):
  dt-bindings: usb: Add CIX Sky1 USBSSP controller
  usb: cdns3: Add CIX Sky1 glue driver

 .../bindings/usb/cix,sky1-usbssp.yaml         | 154 ++++
 MAINTAINERS                                   |   2 +
 drivers/usb/cdns3/Kconfig                     |  13 +
 drivers/usb/cdns3/Makefile                    |   1 +
 drivers/usb/cdns3/cdnsp-sky1.c                | 710 ++++++++++++++++++
 drivers/usb/cdns3/cdnsp-sky1.h                | 100 +++
 6 files changed, 980 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml
 create mode 100644 drivers/usb/cdns3/cdnsp-sky1.c
 create mode 100644 drivers/usb/cdns3/cdnsp-sky1.h

--
2.54.0


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

* [PATCH v7 1/2] dt-bindings: usb: Add CIX Sky1 USBSSP controller
  2026-11-06 10:12 [PATCH v7 0/2] Add CIX Sky1 USBSSP controller support Hongliang Yang
@ 2026-11-06 10:12 ` Hongliang Yang
  2026-11-06 10:21   ` sashiko-bot
  2026-11-06 10:12 ` [PATCH v7 2/2] usb: cdns3: Add CIX Sky1 glue driver Hongliang Yang
  1 sibling, 1 reply; 5+ messages in thread
From: Hongliang Yang @ 2026-11-06 10:12 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Peter Chen, Pawel Laszczak, Roger Quadros,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
	Gary Yang, Fugang Duan
  Cc: linux-usb, linux-arm-kernel, devicetree, linux-kernel,
	cix-kernel-upstream, hongliang.yang

Add device tree binding for the CIX Sky1 Cadence USBSSP controller.
Each instance wraps one of the two USB3 Type-A DRD controllers of the
SoC and owns a register range in the USB30 Type-A DRD RCSU block at
0x091c0000:

  offset  register       contents                       controller
  0x0300  STRAP_PIN0     stream ID (SID/SSID)           USB4
  0x0304  STRAP_PIN1     stream ID (SID/SSID)           USB5
  0x030c  STRAP_PIN3     clock enables, ctrl and PHYs   USB4, USB5
  0x0314  STRAP_PIN5     awcache[3:0], arcache[7:4]     USB4
  0x0324  STRAP_PIN9     awcache[3:0], arcache[7:4]     USB5
  0x0328  STRAP_PIN10    USB2 PLL controls              USB4
  0x0400  STRAP_STATUS0  AXI clock enable/valid/req     USB4
  0x0410  STRAP_STATUS4  AXI clock enable/valid/req     USB5

Controller 0 of the block is used by USB4, controller 1 is used by
USB5. The range of each instance starts at its own AXI cache attribute
strap register and ends after its own AXI clock status register, so the
two ranges overlap: the strap and status registers of the two
controllers are interleaved in the block.

The Cadence USBSSP core itself is described by a child node at its own
address. Mode strap controls are programmed through the Sky1 S5 system
controller referenced by the cix,usb-syscon phandle, whose arguments
give the register offset and the bit position of the strap field.

Signed-off-by: Hongliang Yang <hongliang.yang@cixtech.com>
---
Changes in v7:
- Describe the registers of each controller as one address range
  starting at its AXI cache attribute strap register, instead of two
  16-byte register slots, and drop the reg-names property.
- Document the register map of the shared RCSU block and the resulting
  overlap of the two ranges.
- Do not touch the S5 system controller binding.

Changes in v6:
- Describe the glue registers at their real addresses instead of
  wrapping them in a simple-bus container with translated offsets.
- Document the register offset and bit position arguments of the
  cix,usb-syscon phandle.
- Document the ssp-disable and u3-port-disable properties read by the
  glue driver.
- Fix the property order reported by sashiko.
---
 .../bindings/usb/cix,sky1-usbssp.yaml         | 154 ++++++++++++++++++
 MAINTAINERS                                   |   1 +
 2 files changed, 155 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml

diff --git a/Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml b/Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml
new file mode 100644
index 000000000000..137ef9ac1335
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml
@@ -0,0 +1,154 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/cix,sky1-usbssp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: CIX Sky1 Cadence USBSSP Controller
+
+maintainers:
+  - Hongliang Yang <hongliang.yang@cixtech.com>
+
+description: |
+  The CIX Sky1 USBSSP controller is based on the Cadence USBSSP DRD
+  controller. Each instance wraps one of the two USB3 Type-A DRD
+  controllers of the SoC.
+
+  The register range of an instance is a window in the USB30 Type-A DRD
+  RCSU block at 0x091c0000, starting at the AXI cache attribute strap
+  register of the instance and ending after its AXI clock status
+  register. The strap registers of the two controllers and of their PHYs
+  are interleaved in that block, so the ranges of the two instances
+  overlap.
+
+  The Cadence USBSSP core itself is described by a child node at its own
+  address. Mode strap controls are programmed through the Sky1 S5 system
+  controller referenced by the cix,usb-syscon phandle, whose arguments
+  give the register offset and the bit position of the strap field.
+
+properties:
+  compatible:
+    const: cix,sky1-usbssp
+
+  reg:
+    maxItems: 1
+    description:
+      Window from the AXI cache attribute strap register of this instance
+      up to and including its AXI clock status register.
+
+  ranges: true
+
+  "#address-cells":
+    const: 2
+
+  "#size-cells":
+    const: 2
+
+  clocks:
+    items:
+      - description: Start-of-frame clock
+      - description: AXI bus clock
+      - description: Low-power mode clock
+      - description: APB register interface clock
+
+  clock-names:
+    items:
+      - const: sof
+      - const: axi
+      - const: lpm
+      - const: apb
+
+  resets:
+    items:
+      - description: APB register reset
+      - description: Controller reset
+
+  reset-names:
+    items:
+      - const: apb
+      - const: core
+
+  ssp-disable:
+    type: boolean
+    description: Disable SuperSpeed Plus mode of the controller.
+
+  u3-port-disable:
+    type: boolean
+    description: The board does not route the USB3 port of the controller.
+
+  cix,usb-syscon:
+    $ref: /schemas/types.yaml#/definitions/phandle-array
+    description:
+      Phandle to the Sky1 S5 system controller followed by the register
+      offset and the bit position of the USB mode strap field.
+    items:
+      - items:
+          - description: Phandle to the Sky1 S5 system controller
+          - description: Register offset of the USB mode strap field
+          - description: Bit position of the USB mode strap field
+
+required:
+  - compatible
+  - reg
+  - ranges
+  - "#address-cells"
+  - "#size-cells"
+  - clocks
+  - clock-names
+  - resets
+  - reset-names
+  - cix,usb-syscon
+
+patternProperties:
+  "^usb@[0-9a-f]+$":
+    $ref: cdns,usb3.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/cix,sky1.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/reset/cix,sky1-s5-system-control.h>
+
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        usb@91c0314 {
+            compatible = "cix,sky1-usbssp";
+            reg = <0x00 0x91c0314 0x00 0x100>;
+            ranges;
+            #address-cells = <2>;
+            #size-cells = <2>;
+
+            clocks = <&scmi_clk CLK_TREE_USB3A_H0_CLK_SOF>,
+                     <&scmi_clk CLK_TREE_USB3A_0_AXI_GATE>,
+                     <&scmi_clk CLK_TREE_USB3A_H0_CLK_LPM>,
+                     <&scmi_clk CLK_TREE_USB3A_0_APB_GATE>;
+            clock-names = "sof", "axi", "lpm", "apb";
+            resets = <&src SKY1_USBC_SS2_PRST_N>,
+                     <&src SKY1_USBC_SS2_RST_N>;
+            reset-names = "apb", "core";
+            cix,usb-syscon = <&src 0x424 8>;
+
+            usb@91d0000 {
+                compatible = "cdns,usb3";
+                reg = <0x00 0x91d0000 0x00 0x4000>,
+                      <0x00 0x91d8000 0x00 0x8000>,
+                      <0x00 0x91d4000 0x00 0x4000>;
+                reg-names = "otg", "xhci", "dev";
+                dr_mode = "otg";
+
+                interrupts = <GIC_SPI 168 IRQ_TYPE_LEVEL_HIGH>,
+                             <GIC_SPI 169 IRQ_TYPE_LEVEL_HIGH>,
+                             <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>,
+                             <GIC_SPI 171 IRQ_TYPE_LEVEL_HIGH>;
+                interrupt-names = "host", "peripheral", "otg", "wakeup";
+                maximum-speed = "super-speed-plus";
+
+                phys = <&usb3_phy4_0>, <&usb2_phy6>;
+                phy-names = "cdns3,usb3-phy", "cdns3,usb2-phy";
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 93abbb315065..5144d02fc998 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2903,6 +2903,7 @@ S:	Maintained
 T:	git https://github.com/cixtech/linux-mainline.git
 F:	Documentation/devicetree/bindings/arm/cix.yaml
 F:	Documentation/devicetree/bindings/mailbox/cix,sky1-mbox.yaml
+F:	Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml
 F:	arch/arm64/boot/dts/cix/
 F:	drivers/mailbox/cix-mailbox.c
 K:	\bcix\b
-- 
2.54.0


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

* [PATCH v7 2/2] usb: cdns3: Add CIX Sky1 glue driver
  2026-11-06 10:12 [PATCH v7 0/2] Add CIX Sky1 USBSSP controller support Hongliang Yang
  2026-11-06 10:12 ` [PATCH v7 1/2] dt-bindings: usb: Add CIX Sky1 USBSSP controller Hongliang Yang
@ 2026-11-06 10:12 ` Hongliang Yang
  2026-11-06 10:42   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: Hongliang Yang @ 2026-11-06 10:12 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Peter Chen, Pawel Laszczak, Roger Quadros,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
	Gary Yang, Fugang Duan
  Cc: linux-usb, linux-arm-kernel, devicetree, linux-kernel,
	cix-kernel-upstream, hongliang.yang

Add a glue layer driver for the Cadence USBSSP controller integrated in
the CIX Sky1 SoC. The driver manages the controller clocks, resets and
the mode strap controls located in the Sky1 S5 system controller, and
writes the AXI cache attributes and polls the AXI clock status of its
own controller in the RCSU block.

The register range is mapped with devm_ioremap() instead of
devm_platform_ioremap_resource(): the two Type-A controllers share one
RCSU block and their register ranges overlap, so reserving the region
would make the second controller fail with -EBUSY. The offset of the
AXI clock status register inside the range is taken from the
per-compatible match data, as it differs between the Type-A and Type-C
register blocks of the SoC.

Signed-off-by: Hongliang Yang <hongliang.yang@cixtech.com>
---
Changes in v7:
- Map one register range taken from the first reg entry, instead of two
  ranges selected with reg-names.
- Map it with devm_ioremap() because the ranges of the two Type-A
  controllers overlap.
- Take the offset of the AXI clock status register from the match data.

Changes in v6:
- Read the USB mode strap register offset and bit position from the
  cix,usb-syscon phandle arguments instead of relying on the usbN
  aliases and the undocumented id property.
- Use dev_err_probe() in the probe error paths.
- Drop the unused over-current GPIO handling and the unused dr_base and
  cdnsp_pdev fields.
- Do not disable the clocks before they have been acquired.
- Drop the redundant depends on USB_CDNS3 and fix the compatible name
  in the Kconfig help text.
---
 MAINTAINERS                    |   1 +
 drivers/usb/cdns3/Kconfig      |  13 +
 drivers/usb/cdns3/Makefile     |   1 +
 drivers/usb/cdns3/cdnsp-sky1.c | 710 +++++++++++++++++++++++++++++++++
 drivers/usb/cdns3/cdnsp-sky1.h | 100 +++++
 5 files changed, 825 insertions(+)
 create mode 100644 drivers/usb/cdns3/cdnsp-sky1.c
 create mode 100644 drivers/usb/cdns3/cdnsp-sky1.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 5144d02fc998..3c609d371306 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2906,6 +2906,7 @@ F:	Documentation/devicetree/bindings/mailbox/cix,sky1-mbox.yaml
 F:	Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml
 F:	arch/arm64/boot/dts/cix/
 F:	drivers/mailbox/cix-mailbox.c
+F:	drivers/usb/cdns3/cdnsp-sky1.*
 K:	\bcix\b
 
 ARM/CLKDEV SUPPORT
diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
index 39ad23d1ada8..678cedd8de97 100644
--- a/drivers/usb/cdns3/Kconfig
+++ b/drivers/usb/cdns3/Kconfig
@@ -77,6 +77,19 @@ config USB_CDNSP_PCI
 	  If you choose to build this driver as module it will
 	  be dynamically linked and module will be called cdnsp-pci.ko
 
+config USB_CDNSP_SKY1
+	tristate "Cadence USB3 support on CIX Sky1 SoC platforms"
+	depends on ARCH_CIX || COMPILE_TEST
+	default USB_CDNS3
+	help
+	  Glue driver for the Cadence USB dual-role controllers on CIX Sky1
+	  (device tree compatible cix,sky1-usbssp). It enables clocks and
+	  resets from the SoC, then uses the shared cdns3 platform core
+	  (cdns.ko).
+
+	  If built as a module, the module is named cdnsp-sky1.ko and must be
+	  loaded after the cdns core module when both are loadable modules.
+
 config USB_CDNS3_TI
 	tristate "Cadence USB3 support on TI platforms"
 	depends on ARCH_K3 || COMPILE_TEST
diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
index b2e4ba6a49a3..ab813aaf9940 100644
--- a/drivers/usb/cdns3/Makefile
+++ b/drivers/usb/cdns3/Makefile
@@ -28,3 +28,4 @@ obj-$(CONFIG_USB_CDNSP_PCI)			+= cdnsp-pci.o
 obj-$(CONFIG_USB_CDNS3_TI)			+= cdns3-ti.o
 obj-$(CONFIG_USB_CDNS3_IMX)			+= cdns3-imx.o
 obj-$(CONFIG_USB_CDNS3_STARFIVE)		+= cdns3-starfive.o
+obj-$(CONFIG_USB_CDNSP_SKY1)			+= cdnsp-sky1.o
diff --git a/drivers/usb/cdns3/cdnsp-sky1.c b/drivers/usb/cdns3/cdnsp-sky1.c
new file mode 100644
index 000000000000..65c2c692d2a3
--- /dev/null
+++ b/drivers/usb/cdns3/cdnsp-sky1.c
@@ -0,0 +1,710 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * cdnsp-sky1.c - CIX Sky1 glue for Cadence USBSSP DRD controller
+ *
+ * Copyright (C) 2026 CIX Technology Group Co., Ltd.
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "cdnsp-sky1.h"
+#include "core.h"
+
+static const char *cix_usb_clk_names[CIX_USB_CLK_NUM] = {
+	"sof",
+	"axi",
+	"lpm",
+	"apb",
+};
+
+static int cdnsp_sky1_set_mode(struct device *dev, int mode)
+{
+	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+
+	return regmap_update_bits(data->usb_syscon, data->strap_offset,
+				  GENMASK(data->strap_bit + 1, data->strap_bit),
+				  mode << data->strap_bit);
+}
+
+/**
+ * cdnsp_sky1_clk_enable_all() - enable all clocks for usb controller
+ * @dev:	Pointer to the device of platform_device
+ *
+ */
+
+static int cdnsp_sky1_clk_enable_all(struct device *dev)
+{
+	int i, ret = 0;
+	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+	struct clk **cix_usb_clks = data->cix_usb_clks;
+
+	for (i = 0; i < CIX_USB_CLK_NUM; i++) {
+		cix_usb_clks[i] = devm_clk_get(dev, cix_usb_clk_names[i]);
+		if (IS_ERR(cix_usb_clks[i])) {
+			ret = dev_err_probe(dev, PTR_ERR(cix_usb_clks[i]),
+					    "could not get %s clock\n",
+					    cix_usb_clk_names[i]);
+			goto err_usb_clks;
+		}
+		ret = clk_prepare_enable(cix_usb_clks[i]);
+		if (ret) {
+			dev_err(dev, "%s enable failed:%d\n", cix_usb_clk_names[i], ret);
+			goto err_usb_clks;
+		}
+	}
+	dev_dbg(dev, "enable sky1 USB clock done\n");
+	return ret;
+
+err_usb_clks:
+	while (--i >= 0)
+		clk_disable_unprepare(cix_usb_clks[i]);
+	return ret;
+};
+
+/**
+ * cdnsp_sky1_clk_disable_all() - disable all clocks for usb controller
+ * @dev:	Pointer to the device of platform_device
+ *
+ */
+
+static void cdnsp_sky1_clk_disable_all(struct device *dev)
+{
+	int i;
+	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+	struct clk **cix_usb_clks = data->cix_usb_clks;
+
+	for (i = 0; i < CIX_USB_CLK_NUM; i++)
+		clk_disable_unprepare(cix_usb_clks[i]);
+};
+
+/**
+ * cdnsp_sky1_clk_enable_resume() - enable the clocks that are turned
+ * off while suspend
+ * @dev:	Pointer to the device of platform_device
+ *
+ */
+
+static int cdnsp_sky1_clk_enable_resume(struct device *dev)
+{
+	int i, ret;
+	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+	struct clk **cix_usb_clks = data->cix_usb_clks;
+
+	for (i = 0; i < CIX_USB_CLK_OFF_NUM; i++) {
+		ret = clk_prepare_enable(cix_usb_clks[i]);
+		if (ret) {
+			dev_err(dev, "failed to enable clock %s: %d\n",
+				cix_usb_clk_names[i], ret);
+			goto err_usb_clks;
+		}
+	}
+	return 0;
+
+err_usb_clks:
+	while (--i >= 0)
+		clk_disable_unprepare(cix_usb_clks[i]);
+	return ret;
+};
+
+/**
+ * cdnsp_sky1_clk_disable_suspend() - disable the clocks which are not
+ * needed when suspend
+ * @dev:	Pointer to the device of platform_device
+ *
+ */
+
+static void cdnsp_sky1_clk_disable_suspend(struct device *dev)
+{
+	int i;
+	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+	struct clk **cix_usb_clks = data->cix_usb_clks;
+
+	for (i = 0; i < CIX_USB_CLK_OFF_NUM; i++)
+		clk_disable_unprepare(cix_usb_clks[i]);
+};
+
+static u32 sky1_rcsu_read_status(struct cdnsp_sky1 *data)
+{
+	return readl(data->rcsu_base + data->rcsu_status_offset);
+}
+
+static int sky1_rcsu_handshake(struct cdnsp_sky1 *data, u32 mask, u32 done,
+			       u64 timeout_us)
+{
+	u32	result;
+	int	ret;
+
+	ret = readl_poll_timeout(data->rcsu_base + data->rcsu_status_offset,
+				 result,
+				 (result & mask) == done ||
+				 result == U32_MAX,
+				 10, timeout_us);
+	if (result == U32_MAX)		/* card removed */
+		return -ENODEV;
+	return ret;
+}
+
+static int cdns_sky1_platform_suspend(struct device *dev,
+				      bool suspend, bool wakeup)
+{
+	struct cdns *cdns = dev_get_drvdata(dev);
+	struct platform_device *xhci_dev = cdns->host_dev;
+	struct usb_hcd  *hcd;
+	struct device *parent = cdns->dev->parent;
+	struct cdnsp_sky1 *data = dev_get_drvdata(parent);
+	u32 value;
+	int poll;
+	int ret = 0;
+	int count = 3;
+
+	data->wakeup = wakeup;
+
+	if (cdns->role != USB_ROLE_HOST)
+		return 0;
+
+	if (!xhci_dev) {
+		dev_dbg(dev, "host controller has not been registered\n");
+		return 0;
+	}
+
+	hcd = dev_get_drvdata(&xhci_dev->dev);
+	if (!hcd) {
+		dev_dbg(dev, "host controller have not registered\n");
+		return 0;
+	}
+
+	if (suspend) {
+		while (count--) {
+			value = readl(hcd->regs + XECP_PM_PMCSR);
+			value &= ~PS_MASK;
+			value |= PS_D3 | PS_PME_En;
+			writel(value, hcd->regs + XECP_PM_PMCSR);
+			/* After controller enters D3, disable AXI and SOF
+			 * until AXI valid flag changes to 0.
+			 */
+			if (sky1_rcsu_handshake(data, AXI_CLOCK_VALID, 0,
+						100ULL * 1000)) {
+				u32 ctst;
+
+				ctst = sky1_rcsu_read_status(data);
+				dev_dbg(dev, "enter D3 failed, register value:%x\n",
+					ctst);
+			} else {
+				break;
+			}
+		}
+		if (count < 0) {
+			u32 ctst;
+
+			ctst = sky1_rcsu_read_status(data);
+			dev_err(dev, "enter D3 failed after retries, register value:%x\n",
+				ctst);
+		}
+	} else {
+		while (count--) {
+			value = readl(hcd->regs + XECP_PM_PMCSR);
+			value &= ~PS_MASK;
+			value |= PS_D0;
+			value &= ~PS_PME_En;
+			writel(value, hcd->regs + XECP_PM_PMCSR);
+			/* Wait power state back to D0 */
+			poll = readl_poll_timeout(hcd->regs + XECP_PM_PMCSR,
+						  value,
+						  (value & PS_MASK) == 0 ||
+						  value == U32_MAX,
+						  10, 100ULL * 1000);
+			if (poll) {
+				dev_dbg(dev, "exit D3 timeout, power state=0x%lx\n",
+					value & PS_MASK);
+			} else {
+				break;
+			}
+		}
+		if (count < 0) {
+			dev_err(dev, "exit D3 timeout after retries, power state=0x%lx\n",
+				value & PS_MASK);
+		}
+	}
+
+	return ret;
+}
+
+static void cdnsp_sky1_configure_controller(struct cdnsp_sky1 *data)
+{
+	int clk;
+	int v0, v1, v2, v3;
+	u32 dev_cap, xhci_cap;
+	u32 val = 0;
+
+	if (data->u3_disable) {
+		dev_dbg(data->dev, "disable u3 port\n");
+		writel(D_XEC_CFG_3XPORT_MODE_VALUE, data->device_base
+			+ D_XEC_CFG_3XPORT_MODE);
+	}
+	dev_cap = readl(data->device_base + D_XEC_AXI_CAP);
+	xhci_cap = readl(data->xhci_base + D_XEC_AXI_CAP);
+	writel(dev_cap | AXI_HALT, data->device_base + D_XEC_AXI_CAP);
+	writel(xhci_cap | AXI_HALT, data->xhci_base + D_XEC_AXI_CAP);
+	writel(data->axi_bmax_value, data->device_base + D_XEC_AXI_CTRL0);
+	writel(data->axi_bmax_value, data->xhci_base + D_XEC_AXI_CTRL0);
+	writel(dev_cap & ~AXI_HALT, data->device_base + D_XEC_AXI_CAP);
+	writel(xhci_cap & ~AXI_HALT, data->xhci_base + D_XEC_AXI_CAP);
+	clk = data->sof_clk_freq;
+	v0 =  25 * clk / 100000000;
+	v1 = clk / 10000;
+	v2 = clk / 10;
+	v3 = 125 * clk / 1000000;
+	writel(((v0 > 1) ? v0 - 1 : 1), data->device_base
+		+ D_XEC_PRE_REG_250NS);
+	writel((unsigned int)((v1 / 100 > 1) > 0 ? (v1 / 100) - 1 : 1),
+	       data->device_base + D_XEC_PRE_REG_1US);
+	writel((unsigned int)((v1 / 10 > 1) > 0 ? (v1 / 10) - 1 : 1),
+	       data->device_base + D_XEC_PRE_REG_10US);
+	writel(((v1) > 1 ? v1 - 1 : 1), data->device_base
+		+ D_XEC_PRE_REG_100US);
+	writel((unsigned int)(v3 > 1 ? v3 - 1 : 1),
+	       data->device_base + D_XEC_PRE_REG_125US);
+	writel(((v2 / 100 > 1) ? (v2 / 100) - 1 : 1), data->device_base
+		+ D_XEC_PRE_REG_1MS);
+	writel(((v2 / 10 > 1) ? (v2 / 10) - 1 : 1), data->device_base
+		+ D_XEC_PRE_REG_10MS);
+	writel((v2 > 1 ? v2 - 1 : 1), data->device_base
+		+ D_XEC_PRE_REG_100MS);
+	dev_dbg(data->dev, "readl:%x, %x ,%x, %x, %x, %x, %x, %x\n",
+		readl(data->device_base + D_XEC_PRE_REG_250NS),
+		readl(data->device_base + D_XEC_PRE_REG_1US),
+		readl(data->device_base + D_XEC_PRE_REG_10US),
+		readl(data->device_base + D_XEC_PRE_REG_100US),
+		readl(data->device_base + D_XEC_PRE_REG_125US),
+		readl(data->device_base + D_XEC_PRE_REG_1MS),
+		readl(data->device_base + D_XEC_PRE_REG_10MS),
+		readl(data->device_base + D_XEC_PRE_REG_100MS));
+	clk = data->lpm_clk_freq;
+	v0 =  25 * clk / 100000000;
+	v1 = clk / 10000;
+	v2 = clk / 10;
+	v3 = 125 * clk / 1000000;
+	writel(((v0 > 1) ? v0 - 1 : 1), data->device_base
+		+ D_XEC_LPM_PRE_REG_250NS);
+	writel((unsigned int)((v1 / 100 > 1) > 0 ? (v1 / 100) - 1 : 1), data->device_base
+		+ D_XEC_LPM_PRE_REG_1US);
+	writel((unsigned int)((v1 / 10 > 1) > 0 ? (v1 / 10) - 1 : 1), data->device_base
+		+ D_XEC_LPM_PRE_REG_10US);
+	writel(((v1) > 1 ? v1 - 1 : 1), data->device_base
+		+ D_XEC_LPM_PRE_REG_100US);
+	writel((unsigned int)(v3 > 1 ? v3 - 1 : 1),
+	       data->device_base + D_XEC_LPM_PRE_REG_125US);
+	writel(((v2 / 100 > 1) ? (v2 / 100) - 1 : 1), data->device_base
+		+ D_XEC_LPM_PRE_REG_1MS);
+	writel(((v2 / 10 > 1) ? (v2 / 10) - 1 : 1), data->device_base
+		+ D_XEC_LPM_PRE_REG_10MS);
+	writel((v2 > 1 ? v2 - 1 : 1), data->device_base
+		+ D_XEC_LPM_PRE_REG_100MS);
+	v0 = readl(data->xhci_base + XEC_USBSSP_CHICKEN_BITS_3);
+	v0 &= ~(CFG_APB_TIMEOUT_PSLVERR_EN | CFG_APB_PSLVERR_EN);
+	writel(v0, data->xhci_base + XEC_USBSSP_CHICKEN_BITS_3);
+	if (data->u3_disable) {
+		dev_dbg(data->dev, "disable u3 port\n");
+		writel(XEC_CFG_3XPORT_MODE_VALUE, data->xhci_base
+			+ XEC_CFG_3XPORT_MODE);
+	} else if (data->ssp_disable) {
+		dev_dbg(data->dev, "disable ssp\n");
+		v0 = readl(data->xhci_base + XEC_CFG_3XPORT_MODE);
+		writel(v0  & CFG_3XPORT_MODE_DIS_SSP, data->xhci_base
+			+ XEC_CFG_3XPORT_MODE);
+	}
+	clk = data->sof_clk_freq;
+	v0 =  25 * clk / 100000000;
+	v1 = clk / 10000;
+	v2 = clk / 10;
+	v3 = 125 * clk / 1000000;
+	writel(((v0 > 1) ? v0 - 1 : 0), data->xhci_base
+		+ XEC_PRE_REG_250NS);
+	writel((unsigned int)((v1 / 100 > 1) > 0 ? (v1 / 100) - 1 : 0), data->xhci_base
+		+ XEC_PRE_REG_1US);
+	writel((unsigned int)((v1 / 10 > 1) > 0 ? (v1 / 10) - 1 : 0), data->xhci_base
+		+ XEC_PRE_REG_10US);
+	writel(((v1) > 1 ? v1 - 1 : 0), data->xhci_base
+		+ XEC_PRE_REG_100US);
+	writel((unsigned int)(v3 > 1 ? v3 - 1 : 0),
+	       data->xhci_base + XEC_PRE_REG_125US);
+	writel(((v2 / 100 > 1) ? (v2 / 100) - 1 : 0), data->xhci_base
+		+ XEC_PRE_REG_1MS);
+	writel(((v2 / 10 > 1) ? (v2 / 10) - 1 : 0), data->xhci_base
+		+ XEC_PRE_REG_10MS);
+	writel((v2 > 1 ? v2 - 1 : 0), data->xhci_base
+		+ XEC_PRE_REG_100MS);
+	clk = data->lpm_clk_freq;
+	v0 =  25 * clk / 100000000;
+	v1 = clk / 10000;
+	v2 = clk / 10;
+	v3 = 125 * clk / 1000000;
+	writel(((v0 > 1) ? v0 - 1 : 0), data->xhci_base
+		+ XEC_LPM_PRE_REG_250NS);
+	writel((unsigned int)((v1 / 100 > 1) > 0 ? (v1 / 100) - 1 : 0), data->xhci_base
+		+ XEC_LPM_PRE_REG_1US);
+	writel((unsigned int)((v1 / 10 > 1) > 0 ? (v1 / 10) - 1 : 0), data->xhci_base
+		+ XEC_LPM_PRE_REG_10US);
+	writel(((v1) > 1 ? v1 - 1 : 0), data->xhci_base
+		+ XEC_LPM_PRE_REG_100US);
+	writel((unsigned int)(v3 > 1 ? v3 - 1 : 0),
+	       data->xhci_base + XEC_LPM_PRE_REG_125US);
+	writel(((v2 / 100 > 1) ? (v2 / 100) - 1 : 0), data->xhci_base
+		+ XEC_LPM_PRE_REG_1MS);
+	writel(((v2 / 10 > 1) ? (v2 / 10) - 1 : 0), data->xhci_base
+		+ XEC_LPM_PRE_REG_10MS);
+	writel((v2 > 1 ? v2 - 1 : 0), data->xhci_base
+		+ XEC_LPM_PRE_REG_100MS);
+	val = readl(data->xhci_base + XEC_USBSSP_CLK_GATING_CTRL);
+	val |= HOST20_ACLK_GATING_DISABLE | HOST20_UTMI_GATING_DISABLE;
+	writel(val, data->xhci_base + XEC_USBSSP_CLK_GATING_CTRL);
+}
+
+static int cdnsp_sky1_drd_init(struct cdnsp_sky1 *data)
+{
+	int ret = 0;
+
+	reset_control_assert(data->reset);
+	reset_control_assert(data->preset);
+	ret = cdnsp_sky1_clk_enable_all(data->dev);
+	if (ret)
+		return ret;
+	writel(CIX_USB_AXI_WR_CACHE_VALUE, data->rcsu_base);
+	cdnsp_sky1_set_mode(data->dev, MODE_STRAP_OTG);
+	reset_control_deassert(data->preset);
+	cdnsp_sky1_configure_controller(data);
+	reset_control_deassert(data->reset);
+	return ret;
+}
+
+static void __iomem *sky1_of_get_addr_by_name(struct device_node *parent, const char *name)
+{
+	void __iomem *addr = NULL;
+	struct device_node *node;
+	int index;
+
+	for_each_child_of_node(parent, node) {
+		index = of_property_match_string(node, "reg-names", name);
+		if (index >= 0) {
+			addr = of_iomap(node, index);
+			of_node_put(node);
+			break;
+		}
+	}
+
+	return addr;
+}
+
+static void __iomem *sky1_get_addr_by_name(struct device *dev, const char *name)
+{
+	return sky1_of_get_addr_by_name(dev->of_node, name);
+}
+
+static void sky1_put_addr(void __iomem *regs)
+{
+	if (regs)
+		iounmap(regs);
+}
+
+struct sky1_usbssp_cfg {
+	unsigned int rcsu_status_offset;
+};
+
+/* Offset of STRAP_STATUS0, the AXI clock status register, from 0x91c0314. */
+static const struct sky1_usbssp_cfg sky1_usbssp_typea_cfg = {
+	.rcsu_status_offset = 0xec,
+};
+
+static int cdnsp_sky1_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct of_dev_auxdata cdns_sky1_auxdata[] = {
+		{ .compatible = "cdns,usb3", },
+		{},
+	};
+	const struct sky1_usbssp_cfg *cfg;
+	struct cdnsp_sky1 *data;
+	struct cdns3_platform_data *cdns_sky1_pdata;
+	struct resource *res;
+	unsigned int args[2];
+	int ret = 0;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	cfg = of_device_get_match_data(dev);
+	if (!cfg)
+		return dev_err_probe(dev, -ENODEV, "unsupported device\n");
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return dev_err_probe(dev, -ENODEV, "missing register range\n");
+	/*
+	 * The two Type-A controllers share one RCSU block and their
+	 * register ranges overlap, so the range is mapped without
+	 * requesting it: request_mem_region() would make the second
+	 * controller fail with -EBUSY.
+	 */
+	data->rcsu_base = devm_ioremap(dev, res->start, resource_size(res));
+	if (!data->rcsu_base)
+		return dev_err_probe(dev, -ENOMEM, "failed to map registers\n");
+	data->rcsu_status_offset = cfg->rcsu_status_offset;
+	data->reset = devm_reset_control_get_exclusive(&pdev->dev, "core");
+	if (IS_ERR(data->reset))
+		return dev_err_probe(dev, PTR_ERR(data->reset),
+				     "failed to get core reset\n");
+	data->preset = devm_reset_control_get_exclusive(&pdev->dev, "apb");
+	if (IS_ERR(data->preset))
+		return dev_err_probe(dev, PTR_ERR(data->preset),
+				     "failed to get APB reset\n");
+	platform_set_drvdata(pdev, data);
+	data->dev = dev;
+	data->usb_syscon =
+		syscon_regmap_lookup_by_phandle_args(node, "cix,usb-syscon",
+						     2, args);
+	if (IS_ERR(data->usb_syscon))
+		return dev_err_probe(dev, PTR_ERR(data->usb_syscon),
+				     "failed to get S5 system controller\n");
+	data->strap_offset = args[0];
+	data->strap_bit = args[1];
+	data->u3_disable = device_property_read_bool(dev, "u3-port-disable");
+	data->ssp_disable = device_property_read_bool(dev, "ssp-disable");
+	data->sof_clk_freq = CIX_USB_CLK_8M;
+	data->lpm_clk_freq = CIX_USB_CLK_32K;
+	data->axi_bmax_value = AXI_BMAX_VALUE_DEFAULT;
+	data->xhci_base = sky1_get_addr_by_name(dev, "xhci");
+	if (!data->xhci_base) {
+		ret = -ENODEV;
+		goto err_put_xhci;
+	}
+	data->device_base = sky1_get_addr_by_name(dev, "dev");
+	if (!data->device_base) {
+		ret = -ENODEV;
+		goto err_put_addrs;
+	}
+	ret = cdnsp_sky1_drd_init(data);
+	if (ret == -ETIMEDOUT)
+		ret = -EPROBE_DEFER;
+	if (ret) {
+		/*
+		 * drd_init() leaves the clocks disabled and the resets
+		 * asserted on failure; only the address mappings are
+		 * left to release.
+		 */
+		goto err_put_addrs;
+	}
+	/*
+	 * The platform data is devm-allocated, so it stays alive as long as
+	 * this device; the child devices are depopulated before it is freed.
+	 */
+	cdns_sky1_pdata = devm_kzalloc(dev, sizeof(*cdns_sky1_pdata), GFP_KERNEL);
+	if (!cdns_sky1_pdata) {
+		ret = -ENOMEM;
+		goto err_quiesce;
+	}
+	cdns_sky1_pdata->platform_suspend = cdns_sky1_platform_suspend;
+	cdns_sky1_pdata->quirks = CDNS3_DEFAULT_PM_RUNTIME_ALLOW;
+	cdns_sky1_auxdata[0].platform_data = cdns_sky1_pdata;
+	ret = of_platform_populate(node, NULL, cdns_sky1_auxdata, dev);
+	if (ret) {
+		dev_err(dev, "failed to create children: %d\n", ret);
+		goto err_quiesce;
+	}
+	device_set_wakeup_capable(dev, true);
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	return 0;
+
+err_quiesce:
+	reset_control_assert(data->reset);
+	reset_control_assert(data->preset);
+	cdnsp_sky1_clk_disable_all(dev);
+err_put_addrs:
+	sky1_put_addr(data->device_base);
+err_put_xhci:
+	sky1_put_addr(data->xhci_base);
+	return ret;
+}
+
+static void cdnsp_sky1_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+
+	pm_runtime_get_sync(dev);
+	of_platform_depopulate(dev);
+	sky1_put_addr(data->xhci_base);
+	sky1_put_addr(data->device_base);
+	reset_control_assert(data->reset);
+	reset_control_assert(data->preset);
+	cdnsp_sky1_clk_disable_all(dev);
+	pm_runtime_disable(dev);
+	pm_runtime_set_suspended(dev);
+	pm_runtime_put_noidle(dev);
+}
+
+/*
+ * Because the wake-up interrupt and host interrupt are the same interrupt,
+ * closing the axi and sof clock will result in the inability to generate
+ * port status change interrupt.
+ */
+static int cdnsp_sky1_system_suspend(struct device *dev)
+{
+	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+
+	if (!data->wakeup) {
+		reset_control_assert(data->reset);
+		reset_control_assert(data->preset);
+	}
+	cdnsp_sky1_clk_disable_suspend(dev);
+	return 0;
+}
+
+static int cdnsp_sky1_system_resume(struct device *dev)
+{
+	int ret = 0;
+	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+
+	ret = cdnsp_sky1_clk_enable_resume(dev);
+	if (ret)
+		return ret;
+	if (!data->wakeup) {
+		writel(CIX_USB_AXI_WR_CACHE_VALUE, data->rcsu_base);
+		cdnsp_sky1_set_mode(data->dev, MODE_STRAP_OTG);
+		reset_control_deassert(data->preset);
+		cdnsp_sky1_configure_controller(data);
+		reset_control_deassert(data->reset);
+	}
+	return 0;
+}
+
+static const struct dev_pm_ops cdnsp_sky1_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(cdnsp_sky1_system_suspend, cdnsp_sky1_system_resume)
+};
+
+static const struct of_device_id cdns_sky1_of_match[] = {
+	{
+		.compatible = "cix,sky1-usbssp",
+		.data = &sky1_usbssp_typea_cfg,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, cdns_sky1_of_match);
+
+static int cdnsp_sky1_find_cdns_dev(struct device *dev, void *data)
+{
+	struct device **cdns_dev = data;
+
+	if (dev->of_node && of_device_is_compatible(dev->of_node, "cdns,usb3")) {
+		/* keep a reference: the pointer is used after the iteration */
+		*cdns_dev = get_device(dev);
+		return 1;
+	}
+	return 0;
+}
+
+static int cdnsp_sky1_find_gadget_match(struct device *dev, void *data)
+{
+	struct device **gadget_dev = data;
+	const char *name = dev_name(dev);
+	static const char gadget_prefix[] = "gadget.";
+
+	/*
+	 * The gadget device is registered on the gadget bus with name
+	 * "gadget.%d" (see usb_add_gadget_udc -> dev_set_name).
+	 * It sits on the gadget bus and has the function driver bound to it.
+	 * Verify the device is on the gadget bus by checking the bus name.
+	 * This prevents matching devices that happen to have "gadget." prefix
+	 * in their name but are not real gadget devices, and also avoids
+	 * NULL pointer dereference when device bus is being removed.
+	 */
+	if (name && dev->bus && !strcmp(dev->bus->name, "gadget") &&
+	    !strncmp(name, gadget_prefix, sizeof(gadget_prefix) - 1)) {
+		/* keep a reference: the pointer is used after the iteration */
+		*gadget_dev = get_device(dev);
+		return 1;
+	}
+	return 0;
+}
+
+static void cdnsp_sky1_shutdown(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+	struct device *cdns_dev = NULL;
+	struct device *gadget_dev = NULL;
+	struct cdns *cdns = NULL;
+
+	if (!device_may_wakeup(dev)) {
+		/*
+		 * Find the cdns3 child device, then find its gadget device
+		 * and release the function driver before disabling clocks.
+		 * This ensures all register accesses in gadget_unbind_driver
+		 * complete before clocks are turned off.
+		 */
+		device_for_each_child(dev, &cdns_dev, cdnsp_sky1_find_cdns_dev);
+		if (cdns_dev)
+			cdns = dev_get_drvdata(cdns_dev);
+		if (cdns)
+			device_for_each_child(cdns_dev, &gadget_dev,
+					      cdnsp_sky1_find_gadget_match);
+		if (gadget_dev)
+			device_release_driver(gadget_dev);
+		if (cdns && cdns->host_dev) {
+			struct usb_hcd *hcd = platform_get_drvdata(cdns->host_dev);
+
+			if (hcd && hcd->irq > 0) {
+				/*
+				 * Clear HCD_FLAG_HW_ACCESSIBLE before disable_irq.
+				 * This prevents usb_hcd_irq from calling xhci_irq
+				 * (which reads USBSTS) after clocks are disabled.
+				 */
+				clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+				disable_irq(hcd->irq);
+				synchronize_irq(hcd->irq);
+			}
+		}
+		reset_control_assert(data->reset);
+		reset_control_assert(data->preset);
+		cdnsp_sky1_clk_disable_all(dev);
+		put_device(gadget_dev);
+		put_device(cdns_dev);
+	}
+}
+
+static struct platform_driver cdnsp_sky1_driver = {
+	.probe		= cdnsp_sky1_probe,
+	.remove		= cdnsp_sky1_remove,
+	.shutdown	= cdnsp_sky1_shutdown,
+	.driver		= {
+		.name	= "cdnsp-sky1",
+		.of_match_table	= cdns_sky1_of_match,
+		.pm	= pm_sleep_ptr(&cdnsp_sky1_pm_ops),
+	},
+};
+
+module_platform_driver(cdnsp_sky1_driver);
+
+MODULE_ALIAS("platform:cdnsp-sky1");
+MODULE_DESCRIPTION("CIX Sky1 Cadence USBSSP DRD glue driver");
+MODULE_AUTHOR("Hongliang Yang <hongliang.yang@cixtech.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/cdns3/cdnsp-sky1.h b/drivers/usb/cdns3/cdnsp-sky1.h
new file mode 100644
index 000000000000..751904f7bfb9
--- /dev/null
+++ b/drivers/usb/cdns3/cdnsp-sky1.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _CDNSP_SKY1_H
+#define _CDNSP_SKY1_H
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+
+#define MODE_STRAP_OTG                  0
+
+#define AXI_HALT                        BIT(31)
+#define AXI_BMAX_VALUE_DEFAULT          0x7
+
+#define D_XEC_CFG_3XPORT_MODE           0x2040
+#define D_XEC_AXI_CAP                   0x2174
+#define D_XEC_AXI_CTRL0                 0x217C
+#define D_XEC_PRE_REG_250NS             0x21E8
+#define D_XEC_PRE_REG_1US               0x21EC
+#define D_XEC_PRE_REG_10US              0x21F0
+#define D_XEC_PRE_REG_100US             0x21F4
+#define D_XEC_PRE_REG_125US             0x21F8
+#define D_XEC_PRE_REG_1MS               0x21FC
+#define D_XEC_PRE_REG_10MS              0x2200
+#define D_XEC_PRE_REG_100MS             0x2204
+#define D_XEC_LPM_PRE_REG_250NS         0x2208
+#define D_XEC_LPM_PRE_REG_1US           0x220C
+#define D_XEC_LPM_PRE_REG_10US          0x2210
+#define D_XEC_LPM_PRE_REG_100US         0x2214
+#define D_XEC_LPM_PRE_REG_125US         0x2218
+#define D_XEC_LPM_PRE_REG_1MS           0x221C
+#define D_XEC_LPM_PRE_REG_10MS          0x2220
+#define D_XEC_LPM_PRE_REG_100MS         0x2224
+
+#define XEC_CFG_3XPORT_MODE             0x2040
+#define XEC_PRE_REG_250NS               0x21E8
+#define XEC_PRE_REG_1US                 0x21EC
+#define XEC_PRE_REG_10US                0x21F0
+#define XEC_PRE_REG_100US               0x21F4
+#define XEC_PRE_REG_125US               0x21F8
+#define XEC_PRE_REG_1MS                 0x21FC
+#define XEC_PRE_REG_10MS                0x2200
+#define XEC_PRE_REG_100MS               0x2204
+#define XEC_LPM_PRE_REG_250NS           0x2208
+#define XEC_LPM_PRE_REG_1US             0x220C
+#define XEC_LPM_PRE_REG_10US            0x2210
+#define XEC_LPM_PRE_REG_100US           0x2214
+#define XEC_LPM_PRE_REG_125US           0x2218
+#define XEC_LPM_PRE_REG_1MS             0x221C
+#define XEC_LPM_PRE_REG_10MS            0x2220
+#define XEC_LPM_PRE_REG_100MS           0x2224
+#define XEC_USBSSP_CHICKEN_BITS_3       0x2230
+#define XEC_USBSSP_CLK_GATING_CTRL      0x2234
+#define XECP_PM_PMCSR                   0x2240
+
+/* XEC_USBSSP_CLK_GATING_CTRL */
+#define HOST20_ACLK_GATING_DISABLE      BIT(4)
+#define HOST20_UTMI_GATING_DISABLE      BIT(5)
+
+/* XECP_PM_PMCSR */
+#define PS_MASK                         GENMASK(1, 0)
+#define PS_D0                           0
+#define PS_D3                           3
+#define PS_PME_En                       BIT(8)
+
+#define D_XEC_CFG_3XPORT_MODE_VALUE     0xa0031e07
+#define XEC_CFG_3XPORT_MODE_VALUE       0xa0031e07
+#define CFG_3XPORT_MODE_DIS_SSP         (~BIT(31))
+
+#define CFG_APB_TIMEOUT_PSLVERR_EN      BIT(22)
+#define CFG_APB_PSLVERR_EN              BIT(23)
+
+#define CIX_USB_CLK_NUM                 (4)
+#define CIX_USB_CLK_OFF_NUM             (2)
+#define CIX_USB_AXI_WR_CACHE_VALUE      0x33
+#define CIX_USB_CLK_32K                 32000
+#define CIX_USB_CLK_8M                  8000000
+
+#define AXI_CLOCK_VALID                 BIT(1)
+
+struct cdnsp_sky1 {
+	struct device *dev;
+	void __iomem *rcsu_base;
+	unsigned int rcsu_status_offset;
+	void __iomem *xhci_base;
+	void __iomem *device_base;
+	struct reset_control *reset;
+	struct reset_control *preset;
+	struct clk *cix_usb_clks[CIX_USB_CLK_NUM];
+	unsigned int strap_offset;
+	unsigned int strap_bit;
+	struct regmap	*usb_syscon;
+	int lpm_clk_freq;
+	int sof_clk_freq;
+	bool u3_disable;
+	bool ssp_disable;
+	int axi_bmax_value;
+	bool wakeup;
+};
+
+#endif /* _CDNSP_SKY1_H */
-- 
2.54.0


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

* Re: [PATCH v7 1/2] dt-bindings: usb: Add CIX Sky1 USBSSP controller
  2026-11-06 10:12 ` [PATCH v7 1/2] dt-bindings: usb: Add CIX Sky1 USBSSP controller Hongliang Yang
@ 2026-11-06 10:21   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-11-06 10:21 UTC (permalink / raw)
  To: Hongliang Yang; +Cc: linux-usb, devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] dt-bindings: usb: cix,sky1-usbssp: overlapping reg windows in
  RCSU block
- [Medium] dt-bindings: usb: cix,sky1-usbssp: redundant unprefixed
  disable properties
--

commit 96269a442880206984d4933991d01ca45a5cc83e
Author: Hongliang Yang <hongliang.yang@cixtech.com>

dt-bindings: usb: Add CIX Sky1 USBSSP controller

This commit adds the Device Tree binding schema for the CIX Sky1 Cadence
USBSSP controller wrapper and its child cdns,usb3 node.

> diff --git a/Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml b/Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml
> new file mode 100644
> index 0000000000000..137ef9ac13356
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/usb/cix,sky1-usbssp.yaml
> @@ -0,0 +1,154 @@
[ ... ]
> +  The register range of an instance is a window in the USB30 Type-A DRD
> +  RCSU block at 0x091c0000, starting at the AXI cache attribute strap
> +  register of the instance and ending after its AXI clock status
> +  register. The strap registers of the two controllers and of their PHYs
> +  are interleaved in that block, so the ranges of the two instances
> +  overlap.
[ ... ]
> +properties:
> +  compatible:
> +    const: cix,sky1-usbssp
> +
> +  reg:
> +    maxItems: 1
> +    description:
> +      Window from the AXI cache attribute strap register of this instance
> +      up to and including its AXI clock status register.

[Severity: Medium]
Should reg describe non-overlapping register regions (or should the shared
RCSU block at 0x091c0000 be modeled as a shared parent or syscon node)
instead of overlapping windows across sibling devices on the same bus?

Defining a single contiguous window from each controller's AXI cache
attribute strap register to its AXI clock status register makes the two
instances overlap in 0x091c0324..0x091c0403. In addition, the example
below specifies reg = <0x00 0x91c0314 0x00 0x100> for usb@91c0314, which
extends to 0x091c0414 and covers the second controller's STRAP_STATUS4
register at 0x091c0410 (an inclusive window ending after STRAP_STATUS0 at
0x091c0400 would be 0xf0 bytes). Because the two sibling nodes claim
overlapping MMIO ranges, standard exclusive resource reservation via
devm_platform_ioremap_resource() fails with -EBUSY on the second instance.

[ ... ]

> +  ssp-disable:
> +    type: boolean
> +    description: Disable SuperSpeed Plus mode of the controller.
> +
> +  u3-port-disable:
> +    type: boolean
> +    description: The board does not route the USB3 port of the controller.

[Severity: Medium]
Can the standard maximum-speed and phys properties in the child cdns,usb3
node be used instead of introducing ssp-disable and u3-port-disable?

Both properties lack a vendor prefix and duplicate standard USB bindings
already present on the child controller node: limiting the controller to
super-speed or high-speed is covered by maximum-speed, and an unrouted
USB3 port is represented by omitting cdns3,usb3-phy from phys/phy-names or
setting maximum-speed to "high-speed".

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106101210.1059256-1-hongliang.yang@cixtech.com?part=1

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

* Re: [PATCH v7 2/2] usb: cdns3: Add CIX Sky1 glue driver
  2026-11-06 10:12 ` [PATCH v7 2/2] usb: cdns3: Add CIX Sky1 glue driver Hongliang Yang
@ 2026-11-06 10:42   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-11-06 10:42 UTC (permalink / raw)
  To: Hongliang Yang; +Cc: linux-usb, devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 6 potential
issues to consider:
- [High] usb: cdns3: incomplete teardown before clock disable in
  cdnsp_sky1_shutdown()
- [High] usb: cdns3: unrecovered OTG state after reset in
  cdnsp_sky1_system_resume()
- [High] usb: cdns3: invalid kfree() of devres cdns_sky1_pdata on child
  release
- [Medium] usb: cdns3: broken D0 poll and error return in
  cdns_sky1_platform_suspend()
- [Medium] usb: cdns3: inconsistent wakeup checks across suspend and
  shutdown in cdnsp-sky1
- [Medium] usb: cdns3: unconditional data->wakeup overwrite in
  cdns_sky1_platform_suspend()
--

commit a740c36e5f7b737668f9904cdc187603aff7c8f4
Author: Hongliang Yang <hongliang.yang@cixtech.com>

usb: cdns3: Add CIX Sky1 glue driver

This commit adds a platform glue driver for the Cadence USBSSP dual-role
controller on the CIX Sky1 SoC, managing clocks, resets, RCSU AXI settings,
mode straps, and power management.

> diff --git a/drivers/usb/cdns3/cdnsp-sky1.c b/drivers/usb/cdns3/cdnsp-sky1.c
> new file mode 100644
> index 0000000000000..65c2c692d2a35
> --- /dev/null
> +++ b/drivers/usb/cdns3/cdnsp-sky1.c

[ ... ]

> +static int cdns_sky1_platform_suspend(struct device *dev,
> +				      bool suspend, bool wakeup)
> +{
> +	struct cdns *cdns = dev_get_drvdata(dev);
> +	struct platform_device *xhci_dev = cdns->host_dev;
> +	struct usb_hcd  *hcd;
> +	struct device *parent = cdns->dev->parent;
> +	struct cdnsp_sky1 *data = dev_get_drvdata(parent);
> +	u32 value;
> +	int poll;
> +	int ret = 0;
> +	int count = 3;
> +
> +	data->wakeup = wakeup;

[Severity: Medium]
Can overwriting data->wakeup on every call cause cdnsp_sky1_system_suspend()
to reset an active controller?

cdns3_controller_resume() calls platform_suspend(dev, false, false), which
clears data->wakeup.  During system suspend with wakeup enabled on the child
device, runtime PM remains enabled until the suspend_late phase:

CPU0                              CPU1
cdns3_plat_suspend()
  cdns3_controller_suspend()
    cdns_sky1_platform_suspend(..., true, true)
      data->wakeup = true;
    cdns->in_lpm = true;
                                  cdns_wakeup_irq()
                                    pm_request_resume(...)
                                    cdns3_controller_resume()
                                      cdns_sky1_platform_suspend(..., false, false)
                                        data->wakeup = false;
                                      cdns->in_lpm = false;
cdnsp_sky1_system_suspend()
  if (!data->wakeup)
    reset_control_assert(...)     <-- resets active controller

On the subsequent system resume, cdns3_controller_resume() sees
!cdns->in_lpm and returns early without reinitializing the controller.

> +
> +	if (cdns->role != USB_ROLE_HOST)
> +		return 0;
> +
> +	if (!xhci_dev) {
> +		dev_dbg(dev, "host controller has not been registered\n");
> +		return 0;
> +	}
> +
> +	hcd = dev_get_drvdata(&xhci_dev->dev);
> +	if (!hcd) {
> +		dev_dbg(dev, "host controller have not registered\n");
> +		return 0;
> +	}
> +
> +	if (suspend) {
> +		while (count--) {
> +			value = readl(hcd->regs + XECP_PM_PMCSR);
> +			value &= ~PS_MASK;
> +			value |= PS_D3 | PS_PME_En;
> +			writel(value, hcd->regs + XECP_PM_PMCSR);
> +			/* After controller enters D3, disable AXI and SOF
> +			 * until AXI valid flag changes to 0.
> +			 */
> +			if (sky1_rcsu_handshake(data, AXI_CLOCK_VALID, 0,
> +						100ULL * 1000)) {
> +				u32 ctst;
> +
> +				ctst = sky1_rcsu_read_status(data);
> +				dev_dbg(dev, "enter D3 failed, register value:%x\n",
> +					ctst);
> +			} else {
> +				break;
> +			}
> +		}
> +		if (count < 0) {
> +			u32 ctst;
> +
> +			ctst = sky1_rcsu_read_status(data);
> +			dev_err(dev, "enter D3 failed after retries, register value:%x\n",
> +				ctst);
> +		}
> +	} else {
> +		while (count--) {
> +			value = readl(hcd->regs + XECP_PM_PMCSR);
> +			value &= ~PS_MASK;
> +			value |= PS_D0;
> +			value &= ~PS_PME_En;
> +			writel(value, hcd->regs + XECP_PM_PMCSR);
> +			/* Wait power state back to D0 */
> +			poll = readl_poll_timeout(hcd->regs + XECP_PM_PMCSR,
> +						  value,
> +						  (value & PS_MASK) == 0 ||
> +						  value == U32_MAX,
> +						  10, 100ULL * 1000);
> +			if (poll) {
> +				dev_dbg(dev, "exit D3 timeout, power state=0x%lx\n",
> +					value & PS_MASK);
> +			} else {
> +				break;
> +			}
> +		}
> +		if (count < 0) {
> +			dev_err(dev, "exit D3 timeout after retries, power state=0x%lx\n",
> +				value & PS_MASK);
> +		}
> +	}
> +
> +	return ret;
> +}

[Severity: Medium]
Does the D0 polling loop treat value == U32_MAX as a successful transition,
and does cdns_sky1_platform_suspend() always return 0 even when all retries
fail?

In the resume path, readl_poll_timeout() returns 0 whenever its condition
holds, so reading U32_MAX breaks out of the retry loop without checking for
U32_MAX (unlike sky1_rcsu_handshake()).  Also, ret is never updated when
count < 0 in either the suspend or resume branch, and the suspend branch
sets PS_PME_En unconditionally even when wakeup is false.

[ ... ]

> +	/*
> +	 * The platform data is devm-allocated, so it stays alive as long as
> +	 * this device; the child devices are depopulated before it is freed.
> +	 */
> +	cdns_sky1_pdata = devm_kzalloc(dev, sizeof(*cdns_sky1_pdata), GFP_KERNEL);
> +	if (!cdns_sky1_pdata) {
> +		ret = -ENOMEM;
> +		goto err_quiesce;
> +	}
> +	cdns_sky1_pdata->platform_suspend = cdns_sky1_platform_suspend;
> +	cdns_sky1_pdata->quirks = CDNS3_DEFAULT_PM_RUNTIME_ALLOW;
> +	cdns_sky1_auxdata[0].platform_data = cdns_sky1_pdata;
> +	ret = of_platform_populate(node, NULL, cdns_sky1_auxdata, dev);

[Severity: High]
Does passing devm-allocated cdns_sky1_pdata via of_dev_auxdata cause an
invalid kfree() and double free when the child platform device is released?

of_platform_populate() passes auxdata->platform_data to
of_platform_device_create_pdata(), which assigns pdev->dev.platform_data
directly on a platform_device allocated with platform_device_alloc().  When
of_platform_depopulate() unregisters that child in cdnsp_sky1_remove(),
platform_device_release() frees platform_data with kfree():

drivers/base/platform.c:platform_device_release() {
	struct platform_object *pa = container_of(dev, struct platform_object,
						  pdev.dev);

	of_node_put(pa->pdev.dev.of_node);
	kfree(pa->pdev.dev.platform_data);
	kfree(pa->pdev.mfd_cell);
	kfree(pa->pdev.resource);
	kfree(pa->pdev.driver_override);
	kfree(pa);
}

Because devm_kzalloc() returns an interior pointer (dr->data) inside struct
devres, kfree(pa->pdev.dev.platform_data) corrupts the slab allocator and
devres later frees the enclosing allocation a second time.  Keeping struct
cdns3_platform_data as a field inside struct cdnsp_sky1 breaks only if
platform_device_release() still calls kfree() on it, so platform_data passed
through of_dev_auxdata cannot be devres-managed or embedded unless
pdev->dev.platform_data is cleared before put_device() or allocated with
kzalloc().

[ ... ]

> +static int cdnsp_sky1_system_suspend(struct device *dev)
> +{
> +	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
> +
> +	if (!data->wakeup) {
> +		reset_control_assert(data->reset);
> +		reset_control_assert(data->preset);
> +	}
> +	cdnsp_sky1_clk_disable_suspend(dev);
> +	return 0;
> +}
> +
> +static int cdnsp_sky1_system_resume(struct device *dev)
> +{
> +	int ret = 0;
> +	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
> +
> +	ret = cdnsp_sky1_clk_enable_resume(dev);
> +	if (ret)
> +		return ret;
> +	if (!data->wakeup) {
> +		writel(CIX_USB_AXI_WR_CACHE_VALUE, data->rcsu_base);
> +		cdnsp_sky1_set_mode(data->dev, MODE_STRAP_OTG);
> +		reset_control_deassert(data->preset);
> +		cdnsp_sky1_configure_controller(data);
> +		reset_control_deassert(data->reset);
> +	}
> +	return 0;
> +}

[Severity: High]
When wakeup is disabled, does asserting data->reset and data->preset across
system suspend leave the child cdns3 OTG registers uninitialized on resume?

Resetting the controller clears cdns->otg_cdnsp_regs->override
(OVERRIDE_IDPULLUP) and cdns->otg_irq_regs->ien, which are only programmed
during probe in cdns_drd_init() -> cdns_drd_update_mode().  When the child
device resumes afterwards in cdns3_controller_resume(), it first calls
cdns_sky1_platform_suspend(dev, false, false) to poll XECP_PM_PMCSR in
hcd->regs before cdns_resume() has re-enabled the host bus in the OTG block,
and cdns_resume() never re-runs cdns_drd_update_mode(), leaving ID detection
and OTG interrupts disabled after resume.

[ ... ]

> +static void cdnsp_sky1_shutdown(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct cdnsp_sky1 *data = dev_get_drvdata(dev);
> +	struct device *cdns_dev = NULL;
> +	struct device *gadget_dev = NULL;
> +	struct cdns *cdns = NULL;
> +
> +	if (!device_may_wakeup(dev)) {

[Severity: Medium]
Why does cdnsp_sky1_shutdown() check device_may_wakeup(dev) on the parent
glue device while cdnsp_sky1_system_suspend() checks data->wakeup from the
child cdns-usb3 device?

Both cdnsp_sky1_probe() and cdns3_plat_probe() call
device_set_wakeup_capable(dev, true) on their respective devices.  If
userspace enables wakeup on the child cdns-usb3 device (which owns the
wakeup IRQ), device_may_wakeup(dev) on the parent glue device remains false,
so cdnsp_sky1_shutdown() still asserts resets and disables all clocks.

> +		/*
> +		 * Find the cdns3 child device, then find its gadget device
> +		 * and release the function driver before disabling clocks.
> +		 * This ensures all register accesses in gadget_unbind_driver
> +		 * complete before clocks are turned off.
> +		 */
> +		device_for_each_child(dev, &cdns_dev, cdnsp_sky1_find_cdns_dev);
> +		if (cdns_dev)
> +			cdns = dev_get_drvdata(cdns_dev);
> +		if (cdns)
> +			device_for_each_child(cdns_dev, &gadget_dev,
> +					      cdnsp_sky1_find_gadget_match);
> +		if (gadget_dev)
> +			device_release_driver(gadget_dev);
> +		if (cdns && cdns->host_dev) {
> +			struct usb_hcd *hcd = platform_get_drvdata(cdns->host_dev);
> +
> +			if (hcd && hcd->irq > 0) {
> +				/*
> +				 * Clear HCD_FLAG_HW_ACCESSIBLE before disable_irq.
> +				 * This prevents usb_hcd_irq from calling xhci_irq
> +				 * (which reads USBSTS) after clocks are disabled.
> +				 */
> +				clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
> +				disable_irq(hcd->irq);
> +				synchronize_irq(hcd->irq);
> +			}
> +		}
> +		reset_control_assert(data->reset);
> +		reset_control_assert(data->preset);
> +		cdnsp_sky1_clk_disable_all(dev);
> +		put_device(gadget_dev);
> +		put_device(cdns_dev);
> +	}
> +}

[Severity: High]
Can asserting resets and disabling all clocks in cdnsp_sky1_shutdown()
while child interrupts and role switches are still active cause an unclocked
MMIO access or use-after-free?

Unlike cdnsp_sky1_remove(), which calls pm_runtime_get_sync(dev) and
of_platform_depopulate(dev) before asserting resets and disabling clocks,
cdnsp_sky1_shutdown() leaves the child cdns3 device bound:

- cdns_dev may be runtime-suspended (CDNS3_DEFAULT_PM_RUNTIME_ALLOW is set)
  when device_release_driver(gadget_dev) accesses controller registers in
  cdnsp_gadget_pullup() and cdnsp_gadget_udc_stop().
- device_release_driver(gadget_dev) only unbinds the gadget function driver;
  cdns->dev_irq, cdns->otg_irq, and cdns->wakeup_irq remain enabled and
  registered, so any in-flight or subsequent interrupt after
  reset_control_assert() and cdnsp_sky1_clk_disable_all() reads unclocked,
  reset-asserted MMIO registers.
- cdns->host_dev is read without holding cdns->mutex while
  cdns_drd_thread_irq() can concurrently run cdns_hw_role_switch() ->
  cdns_host_exit() and unregister cdns->host_dev, and only the primary
  hcd->flags has HCD_FLAG_HW_ACCESSIBLE cleared while xhci->shared_hcd
  remains marked accessible.

Could cdnsp_sky1_shutdown() instead wake the device with
pm_runtime_get_sync(dev) and call of_platform_depopulate(dev) like
cdnsp_sky1_remove() does?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106101210.1059256-1-hongliang.yang@cixtech.com?part=2

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

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

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-06 10:12 [PATCH v7 0/2] Add CIX Sky1 USBSSP controller support Hongliang Yang
2026-11-06 10:12 ` [PATCH v7 1/2] dt-bindings: usb: Add CIX Sky1 USBSSP controller Hongliang Yang
2026-11-06 10:21   ` sashiko-bot
2026-11-06 10:12 ` [PATCH v7 2/2] usb: cdns3: Add CIX Sky1 glue driver Hongliang Yang
2026-11-06 10:42   ` sashiko-bot

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