From: Hongliang Yang <hongliang.yang@cixtech.com>
To: peter.chen@kernel.org, pawell@cadence.com, vkoul@kernel.org,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org
Cc: gregkh@linuxfoundation.org, rogerq@kernel.org,
neil.armstrong@linaro.org, p.zabel@pengutronix.de,
fugang.duan@cixtech.com, cix-kernel-upstream@cixtech.com,
linux-usb@vger.kernel.org, linux-phy@lists.infradead.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org,
Hongliang Yang <hongliang.yang@cixtech.com>,
Gary Yang <gary.yang@cixtech.com>
Subject: [PATCH v1 3/4] usb: cdns3: Add CIX Sky1 glue driver
Date: Mon, 31 Aug 2026 16:23:07 +0800 [thread overview]
Message-ID: <20260831082309.3854507-4-hongliang.yang@cixtech.com> (raw)
In-Reply-To: <20260831082309.3854507-1-hongliang.yang@cixtech.com>
Add glue layer for the Cadence USBSSP DRD controller on CIX Sky1
SoCs. The glue driver handles platform-specific initialization
including clocks, resets, and PHY configuration.
Signed-off-by: Hongliang Yang <hongliang.yang@cixtech.com>
Reviewed-by: Gary Yang <gary.yang@cixtech.com>
---
drivers/usb/cdns3/Kconfig | 13 +
drivers/usb/cdns3/Makefile | 1 +
drivers/usb/cdns3/cdnsp-sky1.c | 682 +++++++++++++++++++++++++++++++++
drivers/usb/cdns3/cdnsp-sky1.h | 127 ++++++
4 files changed, 823 insertions(+)
create mode 100644 drivers/usb/cdns3/cdnsp-sky1.c
create mode 100644 drivers/usb/cdns3/cdnsp-sky1.h
diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
index 39ad23d1ada8..03d8e170a98a 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 USB_CDNS3
+ 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-usb3). 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..3dd20451569e
--- /dev/null
+++ b/drivers/usb/cdns3/cdnsp-sky1.c
@@ -0,0 +1,682 @@
+// 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/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.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_clk",
+ "usb_aclk",
+ "lpm_clk",
+ "usb_pclk",
+};
+
+struct cdnsp_sky1_strap_signal {
+ unsigned int offset, bit;
+};
+
+static const struct cdnsp_sky1_strap_signal strap_signals[SKY1_USB_S5_NUM] = {
+ /* usb config in s5 domain */
+ [U3_TYPEC_DRD_ID] = { USB_MODE_STRAP_S5_DOMAIN, U3_TYPEC_DRD_MODE_STRAP_BIT },
+ [U3_TYPEC_HOST0_ID] = { USB_MODE_STRAP_S5_DOMAIN, U3_TYPEC_HOST0_MODE_STRAP_BIT },
+ [U3_TYPEC_HOST1_ID] = { USB_MODE_STRAP_S5_DOMAIN, U3_TYPEC_HOST1_MODE_STRAP_BIT },
+ [U3_TYPEC_HOST2_ID] = { USB_MODE_STRAP_S5_DOMAIN, U3_TYPEC_HOST2_MODE_STRAP_BIT },
+ [U3_TYPEA_CTRL0_ID] = { USB_MODE_STRAP_S5_DOMAIN, U3_TYPEA_CTRL0_MODE_STRAP_BIT },
+ [U3_TYPEA_CTRL1_ID] = { USB_MODE_STRAP_S5_DOMAIN, U3_TYPEA_CTRL1_MODE_STRAP_BIT},
+ [U2_HOST0_ID] = { USB_MODE_STRAP_S5_DOMAIN, U2_HOST0_MODE_STRAP_BIT },
+ [U2_HOST1_ID] = { USB_MODE_STRAP_S5_DOMAIN, U2_HOST1_MODE_STRAP_BIT },
+ [U2_HOST2_ID] = { USB_MODE_STRAP_S5_DOMAIN, U2_HOST2_MODE_STRAP_BIT },
+ [U2_HOST3_ID] = { USB_MODE_STRAP_S5_DOMAIN, U2_HOST3_MODE_STRAP_BIT },
+};
+
+static int cdnsp_sky1_set_mode_by_id(struct device *dev, int mode)
+{
+ struct cdnsp_sky1 *data = dev_get_drvdata(dev);
+
+ return regmap_update_bits(data->usb_syscon,
+ strap_signals[data->id].offset,
+ GENMASK(strap_signals[data->id].bit + 1,
+ strap_signals[data->id].bit),
+ mode << strap_signals[data->id].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:
+ cix_usb_clks[i] = NULL;
+ while (--i >= 0) {
+ clk_disable_unprepare(cix_usb_clks[i]);
+ cix_usb_clks[i] = NULL;
+ }
+ 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:
+ cix_usb_clks[i] = NULL;
+ while (--i >= 0) {
+ clk_disable_unprepare(cix_usb_clks[i]);
+ cix_usb_clks[i] = NULL;
+ }
+ 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 int sky1_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us)
+{
+ u32 result;
+ int ret;
+
+ ret = readl_poll_timeout(ptr, 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 ret = 0;
+ int count = 3;
+
+ data->wakeup = wakeup;
+
+ if (cdns->role != USB_ROLE_HOST)
+ 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_handshake(data->ctst_base, AXI_CLOCK_VALID, 0, 100ULL * 1000))
+ dev_dbg(dev, "enter D3 failed,register value:%x\n",
+ readl(data->ctst_base));
+ else
+ break;
+ }
+ if (count < 0) {
+ dev_err(dev, "enter D3 failed after retries, register value:%x\n",
+ readl(data->ctst_base));
+ }
+ } 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 */
+ if (sky1_handshake(hcd->regs + XECP_PM_PMCSR, PS_MASK, 0, 100ULL * 1000)) {
+ dev_dbg(dev, "exit D3 timeout, power state=0x%lx\n",
+ readl(hcd->regs + XECP_PM_PMCSR) & PS_MASK);
+ } else {
+ break;
+ }
+ }
+ if (count < 0) {
+ dev_err(dev, "exit D3 timeout after retries, power state=0x%lx\n",
+ readl(hcd->regs + XECP_PM_PMCSR) & PS_MASK);
+ }
+ }
+
+ return ret;
+}
+
+static void cdnsp_sky1_configure_controller(struct cdnsp_sky1 *data)
+{
+ int clk;
+ int v0, v1, v2;
+ 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);
+ }
+ writel(AXI_HALT, data->device_base + D_XEC_AXI_CAP);
+ writel(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((~(u32)(AXI_HALT)), data->device_base + D_XEC_AXI_CAP);
+ writel((~(u32)(AXI_HALT)), data->xhci_base + D_XEC_AXI_CAP);
+ clk = data->sof_clk_freq;
+ v0 = 25 * clk / 100000000;
+ v1 = clk / 10000;
+ v2 = clk / 10;
+ 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)((125 * clk / 1000000) > 1 ? (125 * clk / 1000000) : 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;
+ 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)((125 * clk / 1000000) > 1 ? (125 * clk / 1000000) : 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;
+ 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)((125 * clk / 1000000) > 1 ? (125 * clk / 1000000) : 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;
+ 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)((125 * clk / 1000000) > 1 ? (125 * clk / 1000000) : 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);
+ cdnsp_sky1_clk_disable_all(data->dev);
+ ret = cdnsp_sky1_clk_enable_all(data->dev);
+ if (ret)
+ return ret;
+ writel(CIX_USB_AXI_WR_CACHE_VALUE, data->axi_base);
+ cdnsp_sky1_set_mode_by_id(data->dev, MODE_STRAP_OTG);
+ reset_control_deassert(data->preset);
+ cdnsp_sky1_configure_controller(data);
+ reset_control_deassert(data->reset);
+ return ret;
+}
+
+static void *sky1_of_get_addr_by_name(struct device_node *parent, char *name)
+{
+ struct device_node *node;
+ int index;
+
+ node = of_get_next_child(parent, NULL);
+ if (node) {
+ index = of_property_match_string(node, "reg-names", name);
+ if (index >= 0)
+ return of_iomap(node, index);
+ }
+ return NULL;
+}
+
+static void *sky1_get_addr_by_name(struct device *dev, 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);
+}
+
+static struct of_dev_auxdata cdns_sky1_auxdata[] = {
+ {
+ .compatible = "cdns,usb3",
+ },
+ {},
+};
+
+static int cdnsp_sky1_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct cdnsp_sky1 *data;
+ int ret = 0;
+ struct cdns3_platform_data *cdns_sky1_pdata;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ data->axi_base = devm_platform_ioremap_resource_byname(pdev, "axi_property");
+ if (IS_ERR(data->axi_base)) {
+ dev_err(dev, "can't map IOMEM resource\n");
+ return PTR_ERR(data->axi_base);
+ }
+ data->ctst_base = devm_platform_ioremap_resource_byname(pdev, "controller_status");
+ if (IS_ERR(data->ctst_base)) {
+ dev_err(dev, "can't map IOMEM resource\n");
+ return PTR_ERR(data->ctst_base);
+ }
+ data->reset = devm_reset_control_get(&pdev->dev, "usb_reset");
+ if (IS_ERR(data->reset)) {
+ ret = PTR_ERR(data->reset);
+ dev_err(dev, "get reset error:%d\n", ret);
+ return ret;
+ }
+ data->preset = devm_reset_control_get(&pdev->dev, "usb_preset");
+ if (IS_ERR(data->preset)) {
+ ret = PTR_ERR(data->preset);
+ dev_err(dev, "get reset error:%d\n", ret);
+ return ret;
+ }
+ platform_set_drvdata(pdev, data);
+ data->dev = dev;
+ ret = of_alias_get_id(dev->of_node, "usb");
+ if (ret == -ENODEV) {
+ if (device_property_read_u32(dev, "id", &ret))
+ ret = -ENODEV;
+ }
+ if (ret < 0 || ret >= SKY1_USB_S5_NUM) {
+ dev_err(dev, "get alias failed.\n");
+ return ret;
+ }
+ data->id = ret;
+ data->usb_syscon = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "cix,usb_syscon");
+ if (IS_ERR(data->usb_syscon)) {
+ dev_err(dev, "Unable to get cix,usb_syscon regmap");
+ return PTR_ERR(data->usb_syscon);
+ }
+ data->u3_disable = device_property_read_bool(dev, "u3-port-disable");
+ data->ssp_disable = device_property_read_bool(dev, "ssp-disable");
+ if (!device_property_read_u32(dev, "sof_clk_freq", &ret))
+ data->sof_clk_freq = ret;
+ else
+ data->sof_clk_freq = CIX_USB_CLK_32K;
+ if (!device_property_read_u32(dev, "lpm_clk_freq", &ret))
+ data->lpm_clk_freq = ret;
+ else
+ data->lpm_clk_freq = CIX_USB_CLK_8M;
+ if (!device_property_read_u32(dev, "axi_bmax_value", &ret))
+ data->axi_bmax_value = ret;
+ else
+ data->axi_bmax_value = AXI_BMAX_VALUE_DEFAULT;
+ data->xhci_base = sky1_get_addr_by_name(dev, "xhci");
+ if (!data->xhci_base)
+ return -ENODEV;
+ data->device_base = sky1_get_addr_by_name(dev, "dev");
+ if (!data->device_base)
+ return -ENODEV;
+ ret = cdnsp_sky1_drd_init(data);
+ if (ret == -ETIMEDOUT)
+ return -EPROBE_DEFER;
+ if (ret)
+ return ret;
+ data->oc_gpio = devm_gpiod_get_optional(data->dev, "oc", GPIOD_IN);
+ if (IS_ERR(data->oc_gpio)) {
+ dev_err(data->dev, "can not get oc_gpio\n");
+ ret = PTR_ERR(data->oc_gpio);
+ return ret;
+ }
+ if (data->oc_gpio) {
+ ret = gpiod_direction_input(data->oc_gpio);
+ if (ret < 0)
+ dev_err(data->dev, "set oc_gpio input failed:%d\n", ret);
+ }
+ /* release by platform_device_release */
+ cdns_sky1_pdata = kzalloc(sizeof(*cdns_sky1_pdata), GFP_KERNEL);
+ if (!cdns_sky1_pdata)
+ return -ENOMEM;
+ cdns_sky1_pdata->platform_suspend = cdns_sky1_platform_suspend;
+ cdns_sky1_pdata->quirks = CDNS3_DEFAULT_PM_RUNTIME_ALLOW;
+ cdns_sky1_auxdata->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;
+ }
+ device_set_wakeup_capable(dev, true);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ return 0;
+err:
+ kfree(cdns_sky1_pdata);
+ 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_deassert(data->reset);
+ reset_control_deassert(data->preset);
+ cdnsp_sky1_clk_disable_all(dev);
+ platform_set_drvdata(pdev, NULL);
+}
+
+#ifdef CONFIG_PM
+/* 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->axi_base);
+ cdnsp_sky1_set_mode_by_id(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 = {
+ .suspend = cdnsp_sky1_system_suspend,
+ .resume = cdnsp_sky1_system_resume,
+};
+#endif /* CONFIG_PM */
+
+static const struct of_device_id cdns_sky1_of_match[] = {
+ { .compatible = "cix,sky1-usbssp", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cdns_sky1_of_match);
+
+static int cdnsp_sky1_find_cdns(struct device *dev, void *data)
+{
+ struct cdns **cdns_ptr = data;
+
+ if (dev->of_node && of_device_is_compatible(dev->of_node, "cdns,usb3")) {
+ *cdns_ptr = dev_get_drvdata(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)) {
+ *gadget_dev = 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 cdns *cdns = NULL;
+ struct device *gadget_dev = 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, cdnsp_sky1_find_cdns);
+ 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) {
+ disable_irq(hcd->irq);
+ synchronize_irq(hcd->irq);
+ /*
+ * 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);
+ }
+ }
+ reset_control_assert(data->reset);
+ reset_control_assert(data->preset);
+ cdnsp_sky1_clk_disable_all(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_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..49c3d5eab174
--- /dev/null
+++ b/drivers/usb/cdns3/cdnsp-sky1.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _CDNSP_SKY1_H
+#define _CDNSP_SKY1_H
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/reset.h>
+
+#define USB_MODE_STRAP_S5_DOMAIN 0x424
+
+#define MODE_STRAP_OTG 0
+
+#define U3_TYPEC_DRD_ID 0
+#define U3_TYPEC_HOST0_ID 1
+#define U3_TYPEC_HOST1_ID 2
+#define U3_TYPEC_HOST2_ID 3
+#define U3_TYPEA_CTRL0_ID 4
+#define U3_TYPEA_CTRL1_ID 5
+#define U2_HOST0_ID 6
+#define U2_HOST1_ID 7
+#define U2_HOST2_ID 8
+#define U2_HOST3_ID 9
+#define SKY1_USB_S5_NUM 10
+
+#define U3_TYPEC_DRD_MODE_STRAP_BIT 12
+#define U3_TYPEC_HOST0_MODE_STRAP_BIT 14
+#define U3_TYPEC_HOST1_MODE_STRAP_BIT 16
+#define U3_TYPEC_HOST2_MODE_STRAP_BIT 18
+#define U3_TYPEA_CTRL0_MODE_STRAP_BIT 8
+#define U3_TYPEA_CTRL1_MODE_STRAP_BIT 10
+#define U2_HOST0_MODE_STRAP_BIT 0
+#define U2_HOST1_MODE_STRAP_BIT 2
+#define U2_HOST2_MODE_STRAP_BIT 4
+#define U2_HOST3_MODE_STRAP_BIT 6
+
+#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 (~(1 << 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 *axi_base;
+ void __iomem *ctst_base;
+ void __iomem *dr_base;
+ void __iomem *xhci_base;
+ void __iomem *device_base;
+ struct platform_device *cdnsp_pdev;
+ struct reset_control *reset;
+ struct reset_control *preset;
+ struct clk *cix_usb_clks[CIX_USB_CLK_NUM];
+ int id;
+ struct regmap *usb_syscon;
+ int lpm_clk_freq;
+ int sof_clk_freq;
+ bool u3_disable;
+ bool ssp_disable;
+ int axi_bmax_value;
+ struct gpio_desc *oc_gpio;
+ bool wakeup;
+};
+
+#endif /* _CDNSP_SKY1_H */
--
2.54.0
next prev parent reply other threads:[~2026-08-31 8:23 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-31 8:23 [PATCH v1 0/4] Add CIX Sky1 USB3/USBSSP support Hongliang Yang
2026-08-31 8:23 ` [PATCH v1 1/4] dt-bindings: Add CIX Sky1 USB bindings Hongliang Yang
2026-08-31 16:07 ` Conor Dooley
2026-09-01 5:47 ` Krzysztof Kozlowski
2026-09-02 3:23 ` Hongliang Yang
2026-08-31 8:23 ` [PATCH v1 2/4] phy: cix: Add CIX Sky1 USB3 PHY driver Hongliang Yang
2026-08-31 8:23 ` Hongliang Yang [this message]
2026-08-31 8:57 ` [PATCH v1 3/4] usb: cdns3: Add CIX Sky1 glue driver Philipp Zabel
2026-08-31 8:23 ` [PATCH v1 4/4] arm64: dts: cix: Add USB4/USB5 nodes for Sky1 Hongliang Yang
2026-09-02 3:21 ` [PATCH v2 0/5] Add CIX Sky1 USB3/USBSSP support Hongliang Yang
2026-09-02 3:21 ` [PATCH v2 1/5] dt-bindings: phy: Add CIX Sky1 USB3 PHY Hongliang Yang
2026-09-02 3:21 ` [PATCH v2 2/5] dt-bindings: usb: Add CIX Sky1 USBSSP controller Hongliang Yang
2026-09-02 3:21 ` [PATCH v2 3/5] phy: cix: Add CIX Sky1 USB3 PHY driver Hongliang Yang
2026-09-02 3:21 ` [PATCH v2 4/5] usb: cdns3: Add CIX Sky1 glue driver Hongliang Yang
2026-09-02 3:21 ` [PATCH v2 5/5] arm64: dts: cix: Add USB4/USB5 nodes for Sky1 Hongliang Yang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260831082309.3854507-4-hongliang.yang@cixtech.com \
--to=hongliang.yang@cixtech.com \
--cc=cix-kernel-upstream@cixtech.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=fugang.duan@cixtech.com \
--cc=gary.yang@cixtech.com \
--cc=gregkh@linuxfoundation.org \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-phy@lists.infradead.org \
--cc=linux-usb@vger.kernel.org \
--cc=neil.armstrong@linaro.org \
--cc=p.zabel@pengutronix.de \
--cc=pawell@cadence.com \
--cc=peter.chen@kernel.org \
--cc=robh@kernel.org \
--cc=rogerq@kernel.org \
--cc=vkoul@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox