usb: cdns3: Add USBSSP platform driver support

The Cadence USBSSP (CDNSP) controller was previously only accessible
through PCI, coupling the gadget driver with the PCI glue layer into a
single monolithic module (cdnsp-udc-pci). This prevented using the
CDNSP IP on SoC/platform designs that expose the controller through
device tree. It restructures the driver to decouple the CDNSP gadget
from PCI.

- Introduce CONFIG_USB_CDNSP as a standalone tristate (analogous to
  CONFIG_USB_CDNS3), with USB_CDNSP_GADGET and USB_CDNSP_HOST as
  bool sub-options. The gadget code builds as a separate cdnsp.ko
  module.

- Regroup USBSSP and CDNS3 Kconfig options under the USB_CDNS_SUPPORT
  menu so they appear properly grouped in menuconfig.

- Refactor cdnsp-pci.c into a thin PCI-to-platform wrapper (similar
  to cdns3-pci-wrap.c) that registers a platform device and passes
  PCI resources and platform data to the common platform driver.

- Auto-detect the controller version (USBSS vs USBSSP) at runtime by
  reading the DRD/OTG Device ID register in cdns_drd_init(), and select
  the appropriate gadget init function (cdns3_gadget_init or
  cdnsp_gadget_init) based on cdns->version. This follows the same
  pattern already used for host initialization.

- Fix gadget-export.h to use IS_REACHABLE() keyed on the tristate
  module config (CONFIG_USB_CDNS3/CONFIG_USB_CDNSP) instead of
  IS_ENABLED() on the bool gadget config. The bool configs are always
  'y' when enabled, causing IS_ENABLED/IS_REACHABLE to always return
  true and resulting in link errors when cdns-usb-common is built-in
  but the gadget module is loadable.

- Add missing MODULE_LICENSE()/MODULE_DESCRIPTION() and
  EXPORT_SYMBOL_GPL() to the cdns3 and cdnsp gadget modules, required
  by modpost.

- Pass override_apb_timeout through cdns3_platform_data so the PCI
  wrapper can communicate PCI-specific APB timeout values to the
  common driver.

This patch is Assisted-by: Cursor:claude-4.6-opus

Signed-off-by: Peter Chen <peter.chen@cixtech.com>
Acked-by: Pawel Laszczak <pawell@cadence.com>
Link: https://patch.msgid.link/20260316064831.274865-3-peter.chen@cixtech.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Peter Chen 2026-03-16 14:48:31 +08:00 committed by Greg Kroah-Hartman
parent fb14e7f7cb
commit 6076388ca1
9 changed files with 160 additions and 174 deletions

View File

@ -20,10 +20,6 @@ config USB_CDNS3
Say Y here if your system has a Cadence USB3 dual-role controller.
It supports: dual-role switch, Host-only, and Peripheral-only.
If you choose to build this driver is a dynamically linked
as module, the module will be called cdns3.ko.
endif
if USB_CDNS3
config USB_CDNS3_GADGET
@ -89,29 +85,27 @@ config USB_CDNS3_STARFIVE
If you choose to build this driver as module it will
be dynamically linked and module will be called cdns3-starfive.ko
endif
if USB_CDNS_SUPPORT
endif # USB_CDNS3
config USB_CDNSP_PCI
tristate "Cadence CDNSP Dual-Role Controller"
depends on USB_CDNS_SUPPORT && USB_PCI && ACPI
config USB_CDNSP
tristate "Cadence USBSSP Dual-Role Controller"
depends on USB_CDNS_SUPPORT
help
Say Y here if your system has a Cadence CDNSP dual-role controller.
It supports: dual-role switch Host-only, and Peripheral-only.
Say Y here if your system has a Cadence USBSSP dual-role controller.
It supports: dual-role switch, Host-only, and Peripheral-only.
Cadence CDNSP Controller device mode is very similar to XHCI controller.
Therefore some algorithms used has been taken from xHCI driver.
Host controller is compliant with XHCI so it uses standard XHCI driver.
If you choose to build this driver is a dynamically linked
module, the module will be called cdnsp.ko.
endif
if USB_CDNSP_PCI
if USB_CDNSP
config USB_CDNSP_GADGET
bool "Cadence CDNSP device controller"
depends on USB_GADGET=y || USB_GADGET=USB_CDNSP_PCI
bool "Cadence USBSSP device controller"
depends on USB_GADGET=y || USB_GADGET=USB_CDNSP
help
Say Y here to enable device controller functionality of the
Cadence CDNSP-DEV driver.
Cadence USBSSP-DEV driver.
Cadence CDNSP Device Controller in device mode is
very similar to XHCI controller. Therefore some algorithms
@ -120,8 +114,8 @@ config USB_CDNSP_GADGET
It doesn't support LS.
config USB_CDNSP_HOST
bool "Cadence CDNSP host controller"
depends on USB=y || USB=USB_CDNSP_PCI
bool "Cadence USBSSP host controller"
depends on USB=y || USB=USB_CDNSP
select USB_CDNS_HOST
help
Say Y here to enable host controller functionality of the
@ -130,4 +124,16 @@ config USB_CDNSP_HOST
Host controller is compliant with XHCI so it uses
standard XHCI driver.
endif
config USB_CDNSP_PCI
tristate "Cadence USBSSP support on PCIe-based platforms"
depends on USB_PCI && ACPI
help
If you're using the USBSSP Core IP with a PCIe, please say
'Y' or 'M' here.
If you choose to build this driver as module it will
be dynamically linked and module will be called cdnsp-pci.ko
endif # USB_CDNSP
endif # USB_CDNS_SUPPORT

View File

@ -4,41 +4,33 @@ CFLAGS_cdns3-trace.o := -I$(src)
CFLAGS_cdnsp-trace.o := -I$(src)
cdns-usb-common-y := core.o drd.o
cdns3-y := cdns3-plat.o
ifeq ($(CONFIG_USB),m)
obj-m += cdns-usb-common.o
obj-m += cdns3.o
obj-m += cdns3-plat.o
else
obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns-usb-common.o
obj-$(CONFIG_USB_CDNS3) += cdns3.o
obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns3-plat.o
endif
cdns-usb-common-$(CONFIG_USB_CDNS_HOST) += host.o
cdns3-$(CONFIG_USB_CDNS3_GADGET) += cdns3-gadget.o cdns3-ep0.o
# For CDNS3 gadget
ifneq ($(CONFIG_USB_CDNS3_GADGET),)
cdns3-y := cdns3-gadget.o cdns3-ep0.o
cdns3-$(CONFIG_TRACING) += cdns3-trace.o
obj-$(CONFIG_USB_CDNS3) += cdns3.o
endif
obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.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
cdnsp-udc-pci-y := cdnsp-pci.o
ifdef CONFIG_USB_CDNSP_PCI
ifeq ($(CONFIG_USB),m)
obj-m += cdnsp-udc-pci.o
else
obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-udc-pci.o
endif
endif
cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET) += cdnsp-ring.o cdnsp-gadget.o \
cdnsp-mem.o cdnsp-ep0.o
# For CDNSP gadget
ifneq ($(CONFIG_USB_CDNSP_GADGET),)
cdnsp-udc-pci-$(CONFIG_TRACING) += cdnsp-trace.o
cdnsp-y := cdnsp-ring.o cdnsp-gadget.o \
cdnsp-mem.o cdnsp-ep0.o
cdnsp-$(CONFIG_TRACING) += cdnsp-trace.o
obj-$(CONFIG_USB_CDNSP) += cdnsp.o
endif
obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-pci.o

View File

@ -3508,3 +3508,7 @@ int cdns3_gadget_init(struct cdns *cdns)
return 0;
}
EXPORT_SYMBOL_GPL(cdns3_gadget_init);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Cadence USBSS DRD Driver - gadget");

View File

@ -44,6 +44,14 @@ static void set_phy_power_off(struct cdns *cdns)
phy_power_off(cdns->usb2_phy);
}
static int cdns3_plat_gadget_init(struct cdns *cdns)
{
if (cdns->version < CDNSP_CONTROLLER_V2)
return cdns3_gadget_init(cdns);
else
return cdnsp_gadget_init(cdns);
}
/**
* cdns3_plat_probe - probe for cdns3 core device
* @pdev: Pointer to cdns3 core platform device
@ -64,6 +72,8 @@ static int cdns3_plat_probe(struct platform_device *pdev)
cdns->dev = dev;
cdns->pdata = dev_get_platdata(dev);
if (cdns->pdata && cdns->pdata->override_apb_timeout)
cdns->override_apb_timeout = cdns->pdata->override_apb_timeout;
platform_set_drvdata(pdev, cdns);
@ -143,12 +153,15 @@ static int cdns3_plat_probe(struct platform_device *pdev)
if (ret)
goto err_phy_power_on;
cdns->gadget_init = cdns3_gadget_init;
ret = cdns_init(cdns);
if (ret)
goto err_cdns_init;
cdns->gadget_init = cdns3_plat_gadget_init;
ret = cdns_core_init_role(cdns);
if (ret)
goto err_cdns_init;
device_set_wakeup_capable(dev, true);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);

View File

@ -2075,3 +2075,7 @@ int cdnsp_gadget_init(struct cdns *cdns)
return 0;
}
EXPORT_SYMBOL_GPL(cdnsp_gadget_init);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Cadence CDNSP DRD Driver - gadget");

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Cadence PCI Glue driver.
* Cadence USBSSP PCI Glue driver.
*
* Copyright (C) 2019 Cadence.
*
@ -16,7 +16,19 @@
#include <linux/pci.h>
#include "core.h"
#include "gadget-export.h"
struct cdnsp_wrap {
struct platform_device *plat_dev;
struct resource dev_res[6];
int devfn;
};
#define RES_IRQ_HOST_ID 0
#define RES_IRQ_PERIPHERAL_ID 1
#define RES_IRQ_OTG_ID 2
#define RES_HOST_ID 3
#define RES_DEV_ID 4
#define RES_DRD_ID 5
#define PCI_BAR_HOST 0
#define PCI_BAR_OTG 0
@ -26,16 +38,16 @@
#define PCI_DEV_FN_OTG 1
#define PCI_DRIVER_NAME "cdns-pci-usbssp"
#define PLAT_DRIVER_NAME "cdns-usbssp"
#define PLAT_DRIVER_NAME "cdns-usb3"
#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20
#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20
static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev)
{
/*
* Gets the second function.
* Platform has two function. The fist keeps resources for
* Host/Device while the secon keeps resources for DRD/OTG.
* Platform has two function. The first keeps resources for
* Host/Device while the second keeps resources for DRD/OTG.
*/
if (pdev->device == PCI_DEVICE_ID_CDNS_USBSSP)
return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSS, NULL);
@ -48,11 +60,12 @@ static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev)
static int cdnsp_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct device *dev = &pdev->dev;
struct pci_dev *func;
struct platform_device_info plat_info;
static struct cdns3_platform_data pdata;
struct cdnsp_wrap *wrap;
struct resource *res;
struct cdns *cdnsp;
int ret;
struct pci_dev *func;
int ret = 0;
/*
* For GADGET/HOST PCI (devfn) function number is 0,
@ -79,146 +92,105 @@ static int cdnsp_pci_probe(struct pci_dev *pdev,
}
pci_set_master(pdev);
if (pci_is_enabled(func)) {
cdnsp = pci_get_drvdata(func);
wrap = pci_get_drvdata(func);
} else {
cdnsp = kzalloc_obj(*cdnsp);
if (!cdnsp) {
wrap = kzalloc_obj(*wrap);
if (!wrap) {
ret = -ENOMEM;
goto put_pci;
}
}
/* For GADGET device function number is 0. */
if (pdev->devfn == 0) {
resource_size_t rsrc_start, rsrc_len;
res = wrap->dev_res;
/* Function 0: host(BAR_0) + device(BAR_1).*/
dev_dbg(dev, "Initialize resources\n");
rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV);
rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV);
res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev");
if (!res) {
dev_dbg(dev, "controller already in use\n");
ret = -EBUSY;
goto free_cdnsp;
}
if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) {
/* Function 0: host(BAR_0) + device(BAR_2). */
dev_dbg(&pdev->dev, "Initialize Device resources\n");
res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV);
res[RES_DEV_ID].end = pci_resource_end(pdev, PCI_BAR_DEV);
res[RES_DEV_ID].name = "dev";
res[RES_DEV_ID].flags = IORESOURCE_MEM;
dev_dbg(&pdev->dev, "USBSSP-DEV physical base addr: %pa\n",
&res[RES_DEV_ID].start);
cdnsp->dev_regs = devm_ioremap(dev, rsrc_start, rsrc_len);
if (!cdnsp->dev_regs) {
dev_dbg(dev, "error mapping memory\n");
ret = -EFAULT;
goto free_cdnsp;
}
res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST);
res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST);
res[RES_HOST_ID].name = "xhci";
res[RES_HOST_ID].flags = IORESOURCE_MEM;
dev_dbg(&pdev->dev, "USBSSP-XHCI physical base addr: %pa\n",
&res[RES_HOST_ID].start);
cdnsp->dev_irq = pdev->irq;
dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n",
&rsrc_start);
/* Interrupt for XHCI */
wrap->dev_res[RES_IRQ_HOST_ID].start = pdev->irq;
wrap->dev_res[RES_IRQ_HOST_ID].name = "host";
wrap->dev_res[RES_IRQ_HOST_ID].flags = IORESOURCE_IRQ;
res = &cdnsp->xhci_res[0];
res->start = pci_resource_start(pdev, PCI_BAR_HOST);
res->end = pci_resource_end(pdev, PCI_BAR_HOST);
res->name = "xhci";
res->flags = IORESOURCE_MEM;
dev_dbg(dev, "USBSS-XHCI physical base addr: %pa\n",
&res->start);
/* Interrupt for XHCI, */
res = &cdnsp->xhci_res[1];
res->start = pdev->irq;
res->name = "host";
res->flags = IORESOURCE_IRQ;
/* Interrupt for device. It's the same as for HOST. */
wrap->dev_res[RES_IRQ_PERIPHERAL_ID].start = pdev->irq;
wrap->dev_res[RES_IRQ_PERIPHERAL_ID].name = "peripheral";
wrap->dev_res[RES_IRQ_PERIPHERAL_ID].flags = IORESOURCE_IRQ;
} else {
res = &cdnsp->otg_res;
res->start = pci_resource_start(pdev, PCI_BAR_OTG);
res->end = pci_resource_end(pdev, PCI_BAR_OTG);
res->name = "otg";
res->flags = IORESOURCE_MEM;
dev_dbg(dev, "CDNSP-DRD physical base addr: %pa\n",
&res->start);
res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG);
res[RES_DRD_ID].end = pci_resource_end(pdev, PCI_BAR_OTG);
res[RES_DRD_ID].name = "otg";
res[RES_DRD_ID].flags = IORESOURCE_MEM;
dev_dbg(&pdev->dev, "CDNSP-DRD physical base addr: %pa\n",
&res[RES_DRD_ID].start);
/* Interrupt for OTG/DRD. */
cdnsp->otg_irq = pdev->irq;
wrap->dev_res[RES_IRQ_OTG_ID].start = pdev->irq;
wrap->dev_res[RES_IRQ_OTG_ID].name = "otg";
wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ;
}
/*
* Cadence PCI based platform require some longer timeout for APB
* to fixes domain clock synchronization issue after resuming
* controller from L1 state.
*/
cdnsp->override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE;
pci_set_drvdata(pdev, cdnsp);
if (pci_is_enabled(func)) {
cdnsp->dev = dev;
cdnsp->gadget_init = cdnsp_gadget_init;
ret = cdns_init(cdnsp);
if (ret)
goto free_cdnsp;
/* set up platform device info */
pdata.override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE;
memset(&plat_info, 0, sizeof(plat_info));
plat_info.parent = &pdev->dev;
plat_info.fwnode = pdev->dev.fwnode;
plat_info.name = PLAT_DRIVER_NAME;
plat_info.id = pdev->devfn;
plat_info.res = wrap->dev_res;
plat_info.num_res = ARRAY_SIZE(wrap->dev_res);
plat_info.dma_mask = pdev->dma_mask;
plat_info.data = &pdata;
plat_info.size_data = sizeof(pdata);
wrap->devfn = pdev->devfn;
/* register platform device */
wrap->plat_dev = platform_device_register_full(&plat_info);
if (IS_ERR(wrap->plat_dev)) {
ret = PTR_ERR(wrap->plat_dev);
kfree(wrap);
goto put_pci;
}
}
device_wakeup_enable(&pdev->dev);
if (pci_dev_run_wake(pdev))
pm_runtime_put_noidle(&pdev->dev);
return 0;
free_cdnsp:
if (!pci_is_enabled(func))
kfree(cdnsp);
pci_set_drvdata(pdev, wrap);
put_pci:
pci_dev_put(func);
return ret;
}
static void cdnsp_pci_remove(struct pci_dev *pdev)
{
struct cdns *cdnsp;
struct cdnsp_wrap *wrap;
struct pci_dev *func;
func = cdnsp_get_second_fun(pdev);
cdnsp = (struct cdns *)pci_get_drvdata(pdev);
wrap = pci_get_drvdata(pdev);
if (pci_dev_run_wake(pdev))
pm_runtime_get_noresume(&pdev->dev);
if (wrap->devfn == pdev->devfn)
platform_device_unregister(wrap->plat_dev);
if (pci_is_enabled(func)) {
cdns_remove(cdnsp);
} else {
kfree(cdnsp);
}
if (!pci_is_enabled(func))
kfree(wrap);
pci_dev_put(func);
}
static int __maybe_unused cdnsp_pci_suspend(struct device *dev)
{
struct cdns *cdns = dev_get_drvdata(dev);
return cdns_suspend(cdns);
}
static int __maybe_unused cdnsp_pci_resume(struct device *dev)
{
struct cdns *cdns = dev_get_drvdata(dev);
unsigned long flags;
int ret;
spin_lock_irqsave(&cdns->lock, flags);
ret = cdns_resume(cdns);
spin_unlock_irqrestore(&cdns->lock, flags);
cdns_set_active(cdns, 1);
return ret;
}
static const struct dev_pm_ops cdnsp_pci_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(cdnsp_pci_suspend, cdnsp_pci_resume)
};
static const struct pci_device_id cdnsp_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP),
.class = PCI_CLASS_SERIAL_USB_DEVICE },
@ -230,13 +202,10 @@ static const struct pci_device_id cdnsp_pci_ids[] = {
};
static struct pci_driver cdnsp_pci_driver = {
.name = "cdnsp-pci",
.name = PCI_DRIVER_NAME,
.id_table = cdnsp_pci_ids,
.probe = cdnsp_pci_probe,
.remove = cdnsp_pci_remove,
.driver = {
.pm = &cdnsp_pci_pm_ops,
}
};
module_pci_driver(cdnsp_pci_driver);
@ -245,4 +214,4 @@ MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids);
MODULE_ALIAS("pci:cdnsp");
MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Cadence CDNSP PCI driver");
MODULE_DESCRIPTION("Cadence CDNSP PCI wrapper");

View File

@ -80,7 +80,7 @@ static void cdns_exit_roles(struct cdns *cdns)
*
* Returns 0 on success otherwise negative errno
*/
static int cdns_core_init_role(struct cdns *cdns)
int cdns_core_init_role(struct cdns *cdns)
{
struct device *dev = cdns->dev;
enum usb_dr_mode best_dr_mode;
@ -197,11 +197,14 @@ static int cdns_core_init_role(struct cdns *cdns)
goto err;
}
dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
return 0;
err:
cdns_exit_roles(cdns);
return ret;
}
EXPORT_SYMBOL_GPL(cdns_core_init_role);
/**
* cdns_hw_role_state_machine - role switch state machine based on hw events.
@ -469,14 +472,8 @@ int cdns_init(struct cdns *cdns)
if (ret)
goto init_failed;
ret = cdns_core_init_role(cdns);
if (ret)
goto init_failed;
spin_lock_init(&cdns->lock);
dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
return 0;
init_failed:
cdns_drd_exit(cdns);

View File

@ -45,6 +45,7 @@ struct cdns3_platform_data {
unsigned long quirks;
#define CDNS3_DEFAULT_PM_RUNTIME_ALLOW BIT(0)
#define CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE BIT(1)
u32 override_apb_timeout; /* 0 = use default (e.g. for PCI) */
};
/**
@ -119,14 +120,14 @@ struct cdns {
struct cdns3_platform_data *pdata;
spinlock_t lock;
struct xhci_plat_priv *xhci_plat_data;
u32 override_apb_timeout;
int (*gadget_init)(struct cdns *cdns);
u32 override_apb_timeout;
};
int cdns_hw_role_switch(struct cdns *cdns);
int cdns_init(struct cdns *cdns);
int cdns_remove(struct cdns *cdns);
int cdns_core_init_role(struct cdns *cdns);
#ifdef CONFIG_PM_SLEEP
int cdns_resume(struct cdns *cdns);

View File

@ -10,7 +10,7 @@
#ifndef __LINUX_CDNS3_GADGET_EXPORT
#define __LINUX_CDNS3_GADGET_EXPORT
#if IS_ENABLED(CONFIG_USB_CDNSP_GADGET)
#if defined(CONFIG_USB_CDNSP_GADGET) && IS_REACHABLE(CONFIG_USB_CDNSP)
int cdnsp_gadget_init(struct cdns *cdns);
#else
@ -22,7 +22,7 @@ static inline int cdnsp_gadget_init(struct cdns *cdns)
#endif /* CONFIG_USB_CDNSP_GADGET */
#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET)
#if defined(CONFIG_USB_CDNS3_GADGET) && IS_REACHABLE(CONFIG_USB_CDNS3)
int cdns3_gadget_init(struct cdns *cdns);
#else