@@ -62,6 +62,14 @@ config USB_XHCI_PLATFORM
If unsure, say N.
+config USB_XHCI_SNPS
+ bool "xHCI fine tune for Synopsys platforms"
+ help
+ Say 'Y' to enable additional fine tune for Synopsys DWC_usb3x xHCI
+ controllers.
+
+ If unsure, say N.
+
config USB_XHCI_HISTB
tristate "xHCI support for HiSilicon STB SoCs"
depends on USB_XHCI_PLATFORM && (ARCH_HISI || COMPILE_TEST)
@@ -28,6 +28,9 @@ endif
ifneq ($(CONFIG_USB_XHCI_RCAR), )
xhci-plat-hcd-y += xhci-rcar.o
endif
+ifneq ($(CONFIG_USB_XHCI_SNPS), )
+ xhci-plat-hcd-y += xhci-snps.o
+endif
ifneq ($(CONFIG_DEBUG_FS),)
xhci-hcd-y += xhci-debugfs.o
@@ -24,16 +24,20 @@
#include "xhci-plat.h"
#include "xhci-mvebu.h"
#include "xhci-rcar.h"
+#include "xhci-snps.h"
static struct hc_driver __read_mostly xhci_plat_hc_driver;
static int xhci_plat_setup(struct usb_hcd *hcd);
static int xhci_plat_start(struct usb_hcd *hcd);
+static int xhci_plat_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
static const struct xhci_driver_overrides xhci_plat_overrides __initconst = {
.extra_priv_size = sizeof(struct xhci_plat_priv),
.reset = xhci_plat_setup,
.start = xhci_plat_start,
+ .add_endpoint = xhci_plat_add_endpoint,
};
static void xhci_priv_plat_start(struct usb_hcd *hcd)
@@ -105,6 +109,20 @@ static int xhci_plat_start(struct usb_hcd *hcd)
return xhci_run(hcd);
}
+static int xhci_plat_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
+{
+ struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
+ int ret;
+
+ if (priv && priv->add_endpoint_quirk)
+ ret = priv->add_endpoint_quirk(hcd, udev, ep);
+ else
+ ret = xhci_add_endpoint(hcd, udev, ep);
+
+ return ret;
+}
+
#ifdef CONFIG_OF
static const struct xhci_plat_priv xhci_plat_marvell_armada = {
.init_quirk = xhci_mvebu_mbus_init_quirk,
@@ -173,6 +191,25 @@ static const struct of_device_id usb_xhci_of_match[] = {
MODULE_DEVICE_TABLE(of, usb_xhci_of_match);
#endif
+static int xhci_plat_setup_snps_quirks(struct usb_hcd *hcd)
+{
+ struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_USB_XHCI_SNPS))
+ return 0;
+
+ ret = xhci_snps_init(hcd);
+ if (ret) {
+ dev_err(hcd->self.controller, "SNPS extension setup fails\n");
+ return ret;
+ }
+
+ priv->init_quirk = &xhci_snps_setup;
+ priv->add_endpoint_quirk = &xhci_snps_add_endpoint;
+ return 0;
+}
+
static int xhci_plat_probe(struct platform_device *pdev)
{
const struct xhci_plat_priv *priv_match;
@@ -301,6 +338,9 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (device_property_read_bool(tmpdev, "quirk-broken-port-ped"))
xhci->quirks |= XHCI_BROKEN_PORT_PED;
+ if (device_property_read_bool(tmpdev, "xhci-snps-quirks"))
+ xhci_plat_setup_snps_quirks(hcd);
+
device_property_read_u32(tmpdev, "imod-interval-ns",
&xhci->imod_interval);
}
@@ -17,6 +17,9 @@ struct xhci_plat_priv {
int (*init_quirk)(struct usb_hcd *);
int (*suspend_quirk)(struct usb_hcd *);
int (*resume_quirk)(struct usb_hcd *);
+ int (*add_endpoint_quirk)(struct usb_hcd *hcd,
+ struct usb_device *udev,
+ struct usb_host_endpoint *ep);
};
#define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv)
new file mode 100644
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * xhci-snps.c - xHCI glue extension for Synopsys DWC_usb3x IPs.
+ *
+ * Copyright (C) 2022 Synopsys Inc.
+ *
+ * Author: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
+ *
+ * Contains some borrowed code from dwc3 driver.
+ */
+
+#include "xhci.h"
+#include "xhci-snps.h"
+
+#define DWC3_GSNPSID 0xc120
+#define DWC3_VER_NUMBER 0xc1a0
+#define DWC3_VER_TYPE 0xc1a4
+
+#define DWC3_GLOBALS_REGS_END 0xc6ff
+
+#define DWC3_GSNPSID_MASK 0xffff0000
+#define DWC3_GSNPS_ID(p) (((p) & DWC3_GSNPSID_MASK) >> 16)
+
+#define DWC3_IP 0x5533
+#define DWC31_IP 0x3331
+#define DWC32_IP 0x3332
+
+#define DWC3_REVISION_ANY 0x0
+#define DWC3_REVISION_173A 0x5533173a
+#define DWC3_REVISION_175A 0x5533175a
+#define DWC3_REVISION_180A 0x5533180a
+#define DWC3_REVISION_183A 0x5533183a
+#define DWC3_REVISION_185A 0x5533185a
+#define DWC3_REVISION_187A 0x5533187a
+#define DWC3_REVISION_188A 0x5533188a
+#define DWC3_REVISION_190A 0x5533190a
+#define DWC3_REVISION_194A 0x5533194a
+#define DWC3_REVISION_200A 0x5533200a
+#define DWC3_REVISION_202A 0x5533202a
+#define DWC3_REVISION_210A 0x5533210a
+#define DWC3_REVISION_220A 0x5533220a
+#define DWC3_REVISION_230A 0x5533230a
+#define DWC3_REVISION_240A 0x5533240a
+#define DWC3_REVISION_250A 0x5533250a
+#define DWC3_REVISION_260A 0x5533260a
+#define DWC3_REVISION_270A 0x5533270a
+#define DWC3_REVISION_280A 0x5533280a
+#define DWC3_REVISION_290A 0x5533290a
+#define DWC3_REVISION_300A 0x5533300a
+#define DWC3_REVISION_310A 0x5533310a
+#define DWC3_REVISION_330A 0x5533330a
+
+#define DWC31_REVISION_ANY 0x0
+#define DWC31_REVISION_110A 0x3131302a
+#define DWC31_REVISION_120A 0x3132302a
+#define DWC31_REVISION_160A 0x3136302a
+#define DWC31_REVISION_170A 0x3137302a
+#define DWC31_REVISION_180A 0x3138302a
+#define DWC31_REVISION_190A 0x3139302a
+
+#define DWC32_REVISION_ANY 0x0
+#define DWC32_REVISION_100A 0x3130302a
+
+#define DWC31_VERSIONTYPE_ANY 0x0
+#define DWC31_VERSIONTYPE_EA01 0x65613031
+#define DWC31_VERSIONTYPE_EA02 0x65613032
+#define DWC31_VERSIONTYPE_EA03 0x65613033
+#define DWC31_VERSIONTYPE_EA04 0x65613034
+#define DWC31_VERSIONTYPE_EA05 0x65613035
+#define DWC31_VERSIONTYPE_EA06 0x65613036
+
+#define DWC3_IP_IS(_ip) \
+ (snps->ip == _ip##_IP)
+
+#define DWC3_VER_IS(_ip, _ver) \
+ (DWC3_IP_IS(_ip) && snps->revision == _ip##_REVISION_##_ver)
+
+#define DWC3_VER_IS_PRIOR(_ip, _ver) \
+ (DWC3_IP_IS(_ip) && snps->revision < _ip##_REVISION_##_ver)
+
+#define DWC3_VER_IS_WITHIN(_ip, _from, _to) \
+ (DWC3_IP_IS(_ip) && \
+ snps->revision >= _ip##_REVISION_##_from && \
+ (!(_ip##_REVISION_##_to) || \
+ snps->revision <= _ip##_REVISION_##_to))
+
+#define DWC3_VER_TYPE_IS_WITHIN(_ip, _ver, _from, _to) \
+ (DWC3_VER_IS(_ip, _ver) && \
+ snps->version_type >= _ip##_VERSIONTYPE_##_from && \
+ (!(_ip##_VERSIONTYPE_##_to) || \
+ snps->version_type <= _ip##_VERSIONTYPE_##_to))
+
+/**
+ * struct xhci_snps - Wrapper for DWC_usb3x host controller
+ * @hcd: the main host device
+ * @ip: controller's ID
+ * @revision: controller's version of an IP
+ * @version_type: VERSIONTYPE register contents, a sub release of a revision
+ */
+struct xhci_snps {
+ struct usb_hcd *hcd;
+ u32 ip;
+ u32 revision;
+ u32 version_type;
+};
+
+#define dev_to_xhci_snps(p) (container_of((p), struct xhci_snps, dev))
+
+int xhci_snps_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
+{
+ struct xhci_snps *snps = dev_get_drvdata(hcd->self.controller);
+
+ if (DWC3_VER_IS_WITHIN(DWC31, ANY, 190A) &&
+ usb_endpoint_xfer_int(&ep->desc) &&
+ udev->speed == USB_SPEED_FULL && ep->desc.bInterval == 6)
+ ep->desc.bInterval = 5;
+
+ return xhci_add_endpoint(hcd, udev, ep);
+}
+
+static bool xhci_is_snps(struct usb_hcd *hcd)
+{
+ u32 id;
+
+ if (hcd->rsrc_len < DWC3_GLOBALS_REGS_END)
+ return false;
+
+ id = DWC3_GSNPS_ID(readl(hcd->regs + DWC3_GSNPSID));
+
+ return (id == DWC3_IP || id == DWC31_IP || id == DWC32_IP);
+}
+
+static int xhci_snps_init_version(struct xhci_snps *snps)
+{
+ void __iomem *regs = snps->hcd->regs;
+ u32 reg;
+
+ reg = readl(regs + DWC3_GSNPSID);
+ snps->ip = DWC3_GSNPS_ID(reg);
+
+ if (DWC3_IP_IS(DWC3)) {
+ snps->revision = reg;
+ } else if (DWC3_IP_IS(DWC31) || DWC3_IP_IS(DWC32)) {
+ snps->revision = readl(regs + DWC3_VER_NUMBER);
+ snps->version_type = readl(regs + DWC3_VER_TYPE);
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int xhci_snps_setup(struct usb_hcd *hcd)
+{
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ /* These quirks are applicable to all DWC_usb3x IPs and versions */
+ xhci->quirks |= XHCI_SKIP_PHY_INIT | XHCI_SG_TRB_CACHE_SIZE_QUIRK;
+
+ return 0;
+}
+
+int xhci_snps_init(struct usb_hcd *hcd)
+{
+ struct device *dev = hcd->self.controller;
+ struct xhci_snps *snps;
+ int ret;
+
+ if (!xhci_is_snps(hcd))
+ return -EINVAL;
+
+ snps = devm_kzalloc(dev, sizeof(*snps), GFP_KERNEL);
+ if (!snps)
+ return -ENOMEM;
+
+ snps->hcd = hcd;
+
+ ret = xhci_snps_init_version(snps);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(dev, snps);
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Synopsys Inc.
+ */
+
+#ifndef _XHCI_SNPS_H
+#define _XHCI_SNPS_H
+
+#if IS_ENABLED(CONFIG_USB_XHCI_SNPS)
+int xhci_snps_init(struct usb_hcd *hcd);
+int xhci_snps_setup(struct usb_hcd *hcd);
+int xhci_snps_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
+#else
+static inline int xhci_snps_init(struct usb_hcd *hcd)
+{
+ return 0;
+}
+
+static inline int xhci_snps_setup(struct usb_hcd *hcd)
+{
+ return 0;
+}
+
+static int xhci_snps_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
+{
+ return 0;
+}
+#endif
+
+#endif /* _XHCI_SNPS_H */
Synopsys DWC_usb3x IPs are used on many different platforms. Since they share the same IP, often the quirks are common across different platforms and versions. This drives the need to find a way to handle all the common (and platform specific) quirks and separate its logic from dwc3 and xhci core logic. Hopefully this helps reduce introducing new device properties while maintaining abstraction. So, let's create a xhci-snps glue extension that can apply to xhci-plat and xhci-pci glue drivers and teach it to handle DWC_usb3x hosts. For this particular change, we'll start with xhci-plat glue driver. Signed-off-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com> --- drivers/usb/host/Kconfig | 8 ++ drivers/usb/host/Makefile | 3 + drivers/usb/host/xhci-plat.c | 40 ++++++++ drivers/usb/host/xhci-plat.h | 3 + drivers/usb/host/xhci-snps.c | 185 +++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-snps.h | 32 ++++++ 6 files changed, 271 insertions(+) create mode 100644 drivers/usb/host/xhci-snps.c create mode 100644 drivers/usb/host/xhci-snps.h