diff options
author | Pawel Laszczak <pawell@cadence.com> | 2019-07-02 14:38:01 +0100 |
---|---|---|
committer | Felipe Balbi <felipe.balbi@linux.intel.com> | 2019-07-03 10:46:05 +0300 |
commit | 8bc1901ca7b07d864fca11461b3875b31f949765 (patch) | |
tree | 18f26f7b8ef9edf550182f935d6236b761cfc683 /drivers | |
parent | c2af6b07803ebd099950cd608f404a7bff9037b2 (diff) |
usb:cdns3 Add Cadence USB3 DRD Driver
This patch introduce new Cadence USBSS DRD driver to Linux kernel.
The Cadence USBSS DRD Controller is a highly configurable IP Core which
can be instantiated as Dual-Role Device (DRD), Peripheral Only and
Host Only (XHCI)configurations.
The current driver has been validated with FPGA platform. We have
support for PCIe bus, which is used on FPGA prototyping.
The host side of USBSS-DRD controller is compliant with XHCI
specification, so it works with standard XHCI Linux driver.
Signed-off-by: Pawel Laszczak <pawell@cadence.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/Kconfig | 2 | ||||
-rw-r--r-- | drivers/usb/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/cdns3/Kconfig | 44 | ||||
-rw-r--r-- | drivers/usb/cdns3/Makefile | 14 | ||||
-rw-r--r-- | drivers/usb/cdns3/cdns3-pci-wrap.c | 157 | ||||
-rw-r--r-- | drivers/usb/cdns3/core.c | 543 | ||||
-rw-r--r-- | drivers/usb/cdns3/core.h | 121 | ||||
-rw-r--r-- | drivers/usb/cdns3/debug.h | 173 | ||||
-rw-r--r-- | drivers/usb/cdns3/debugfs.c | 153 | ||||
-rw-r--r-- | drivers/usb/cdns3/drd.c | 379 | ||||
-rw-r--r-- | drivers/usb/cdns3/drd.h | 166 | ||||
-rw-r--r-- | drivers/usb/cdns3/ep0.c | 920 | ||||
-rw-r--r-- | drivers/usb/cdns3/gadget-export.h | 28 | ||||
-rw-r--r-- | drivers/usb/cdns3/gadget.c | 2319 | ||||
-rw-r--r-- | drivers/usb/cdns3/gadget.h | 1321 | ||||
-rw-r--r-- | drivers/usb/cdns3/host-export.h | 28 | ||||
-rw-r--r-- | drivers/usb/cdns3/host.c | 76 | ||||
-rw-r--r-- | drivers/usb/cdns3/trace.c | 23 | ||||
-rw-r--r-- | drivers/usb/cdns3/trace.h | 447 |
19 files changed, 6916 insertions, 0 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index e4b27413f528..c2e78882f8c2 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -113,6 +113,8 @@ source "drivers/usb/usbip/Kconfig" endif +source "drivers/usb/cdns3/Kconfig" + source "drivers/usb/mtu3/Kconfig" source "drivers/usb/musb/Kconfig" diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index ecc2de1ffaae..09fc9f2448ce 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -13,6 +13,8 @@ obj-$(CONFIG_USB_DWC3) += dwc3/ obj-$(CONFIG_USB_DWC2) += dwc2/ obj-$(CONFIG_USB_ISP1760) += isp1760/ +obj-$(CONFIG_USB_CDNS3) += cdns3/ + obj-$(CONFIG_USB_MON) += mon/ obj-$(CONFIG_USB_MTU3) += mtu3/ diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig new file mode 100644 index 000000000000..118ec91c4c70 --- /dev/null +++ b/drivers/usb/cdns3/Kconfig @@ -0,0 +1,44 @@ +config USB_CDNS3 + tristate "Cadence USB3 Dual-Role Controller" + depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA + help + 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. + +if USB_CDNS3 + +config USB_CDNS3_GADGET + bool "Cadence USB3 device controller" + depends on USB_GADGET + help + Say Y here to enable device controller functionality of the + Cadence USBSS-DEV driver. + + This controller supports FF, HS and SS mode. It doesn't support + LS and SSP mode. + +config USB_CDNS3_HOST + bool "Cadence USB3 host controller" + depends on USB_XHCI_HCD + help + Say Y here to enable host controller functionality of the + Cadence driver. + + Host controller is compliant with XHCI so it will use + standard XHCI driver. + +config USB_CDNS3_PCI_WRAP + tristate "Cadence USB3 support on PCIe-based platforms" + depends on USB_PCI && ACPI + default USB_CDNS3 + help + If you're using the USBSS 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 cdns3-pci.ko + +endif diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile new file mode 100644 index 000000000000..46c27b94b94b --- /dev/null +++ b/drivers/usb/cdns3/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# define_trace.h needs to know how to find our header +CFLAGS_trace.o := -I$(src) + +cdns3-y := core.o drd.o + +obj-$(CONFIG_USB_CDNS3) += cdns3.o +ifneq ($(CONFIG_DEBUG_FS),) + cdns3-y += debugfs.o +endif + +cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.o ep0.o trace.o +cdns3-$(CONFIG_USB_CDNS3_HOST) += host.o +obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c new file mode 100644 index 000000000000..65b61ea246b1 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-pci-wrap.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS PCI Glue driver + * + * Copyright (C) 2018-2019 Cadence. + * + * Author: Pawel Laszczak <pawell@cadence.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> + +struct cdns3_wrap { + struct platform_device *plat_dev; + struct pci_dev *hg_dev; + struct resource dev_res[6]; +}; + +#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_DEV 2 +#define PCI_BAR_OTG 4 + +#define PCI_DEV_FN_HOST_DEVICE 0 +#define PCI_DEV_FN_OTG 1 + +#define PCI_DRIVER_NAME "cdns3-pci-usbss" +#define PLAT_DRIVER_NAME "cdns-usb3" + +#define CDNS_VENDOR_ID 0x17cd +#define CDNS_DEVICE_ID 0x0100 + +static int cdns3_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct platform_device_info plat_info; + struct cdns3_wrap *wrap; + struct resource *res; + int err; + + /* + * for GADGET/HOST PCI (devfn) function number is 0, + * for OTG PCI (devfn) function number is 1 + */ + if (!id || pdev->devfn != PCI_DEV_FN_HOST_DEVICE) + return -EINVAL; + + err = pcim_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", err); + return err; + } + + pci_set_master(pdev); + wrap = devm_kzalloc(&pdev->dev, sizeof(*wrap), GFP_KERNEL); + if (!wrap) { + pci_disable_device(pdev); + return -ENOMEM; + } + + /* function 0: host(BAR_0) + device(BAR_1) + otg(BAR_2)). */ + dev_dbg(&pdev->dev, "Initialize Device resources\n"); + res = wrap->dev_res; + + 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, "USBSS-DEV physical base addr: %pa\n", + &res[RES_DEV_ID].start); + + 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, "USBSS-XHCI physical base addr: %pa\n", + &res[RES_HOST_ID].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, "USBSS-DRD physical base addr: %pa\n", + &res[RES_DRD_ID].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; + + /* Interrupt 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; + + /* Interrupt for OTG/DRD. */ + 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; + + /* set up platform device info */ + 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; + + /* register platform device */ + wrap->plat_dev = platform_device_register_full(&plat_info); + if (IS_ERR(wrap->plat_dev)) { + pci_disable_device(pdev); + return PTR_ERR(wrap->plat_dev); + } + + pci_set_drvdata(pdev, wrap); + + return err; +} + +static void cdns3_pci_remove(struct pci_dev *pdev) +{ + struct cdns3_wrap *wrap = (struct cdns3_wrap *)pci_get_drvdata(pdev); + + platform_device_unregister(wrap->plat_dev); +} + +static const struct pci_device_id cdns3_pci_ids[] = { + { PCI_DEVICE(CDNS_VENDOR_ID, CDNS_DEVICE_ID), }, + { 0, } +}; + +static struct pci_driver cdns3_pci_driver = { + .name = PCI_DRIVER_NAME, + .id_table = cdns3_pci_ids, + .probe = cdns3_pci_probe, + .remove = cdns3_pci_remove, +}; + +module_pci_driver(cdns3_pci_driver); +MODULE_DEVICE_TABLE(pci, cdns3_pci_ids); + +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence USBSS PCI wrapperr"); diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c new file mode 100644 index 000000000000..ddc73f1c87c5 --- /dev/null +++ b/drivers/usb/cdns3/core.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS DRD Driver. + * + * Copyright (C) 2018-2019 Cadence. + * Copyright (C) 2017-2018 NXP + * Copyright (C) 2019 Texas Instruments + * + * Author: Peter Chen <peter.chen@nxp.com> + * Pawel Laszczak <pawell@cadence.com> + * Roger Quadros <rogerq@ti.com> + */ + +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> + +#include "gadget.h" +#include "core.h" +#include "host-export.h" +#include "gadget-export.h" +#include "drd.h" +#include "debug.h" + +/** + * cdns3_handshake - spin reading until handshake completes or fails + * @ptr: address of device controller register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + */ +int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = readl(ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + + result &= mask; + if (result == done) + return 0; + + udelay(1); + usec--; + } while (usec > 0); + + return -ETIMEDOUT; +} + +static inline +struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns) +{ + WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]); + return cdns->roles[cdns->role]; +} + +static int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role) +{ + int ret; + + if (WARN_ON(role >= CDNS3_ROLE_END)) + return 0; + + if (!cdns->roles[role]) + return -ENXIO; + + if (cdns->roles[role]->state == CDNS3_ROLE_STATE_ACTIVE) + return 0; + + mutex_lock(&cdns->mutex); + cdns->role = role; + ret = cdns->roles[role]->start(cdns); + if (!ret) + cdns->roles[role]->state = CDNS3_ROLE_STATE_ACTIVE; + mutex_unlock(&cdns->mutex); + return ret; +} + +void cdns3_role_stop(struct cdns3 *cdns) +{ + enum cdns3_roles role = cdns->role; + + if (role >= CDNS3_ROLE_END) { + WARN_ON(role > CDNS3_ROLE_END); + return; + } + + if (cdns->roles[role]->state == CDNS3_ROLE_STATE_INACTIVE) + return; + + mutex_lock(&cdns->mutex); + cdns->roles[role]->stop(cdns); + cdns->roles[role]->state = CDNS3_ROLE_STATE_INACTIVE; + mutex_unlock(&cdns->mutex); +} + +static void cdns3_exit_roles(struct cdns3 *cdns) +{ + cdns3_role_stop(cdns); + cdns3_drd_exit(cdns); +} + +enum cdns3_roles cdsn3_get_real_role(struct cdns3 *cdns); + +static int cdns3_idle_role_start(struct cdns3 *cnds) +{ /* Hold PHY in RESET */ + return 0; +} + +static void cdns3_idle_role_stop(struct cdns3 *cnds) +{ + /* Program Lane swap and bring PHY out of RESET */ +} + +static int cdns3_idle_init(struct cdns3 *cdns) +{ + struct cdns3_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = cdns3_idle_role_start; + rdrv->stop = cdns3_idle_role_stop; + rdrv->state = CDNS3_ROLE_STATE_INACTIVE; + rdrv->suspend = NULL; + rdrv->resume = NULL; + rdrv->name = "idle"; + + cdns->roles[CDNS3_ROLE_IDLE] = rdrv; + + return 0; +} + +/** + * cdns3_core_init_role - initialize role of operation + * @cdns: Pointer to cdns3 structure + * + * Returns 0 on success otherwise negative errno + */ +static int cdns3_core_init_role(struct cdns3 *cdns) +{ + struct device *dev = cdns->dev; + enum usb_dr_mode best_dr_mode; + enum usb_dr_mode dr_mode; + int ret = 0; + + dr_mode = usb_get_dr_mode(dev); + cdns->role = CDNS3_ROLE_END; + + /* + * If driver can't read mode by means of usb_get_dr_mode function then + * chooses mode according with Kernel configuration. This setting + * can be restricted later depending on strap pin configuration. + */ + if (dr_mode == USB_DR_MODE_UNKNOWN) { + if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) && + IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) + dr_mode = USB_DR_MODE_OTG; + else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST)) + dr_mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) + dr_mode = USB_DR_MODE_PERIPHERAL; + } + + /* + * At this point cdns->dr_mode contains strap configuration. + * Driver try update this setting considering kernel configuration + */ + best_dr_mode = cdns->dr_mode; + + ret = cdns3_idle_init(cdns); + if (ret) + return ret; + + if (dr_mode == USB_DR_MODE_OTG) { + best_dr_mode = cdns->dr_mode; + } else if (cdns->dr_mode == USB_DR_MODE_OTG) { + best_dr_mode = dr_mode; + } else if (cdns->dr_mode != dr_mode) { + dev_err(dev, "Incorrect DRD configuration\n"); + return -EINVAL; + } + + dr_mode = best_dr_mode; + + if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { + ret = cdns3_host_init(cdns); + if (ret) { + dev_err(dev, "Host initialization failed with %d\n", + ret); + goto err; + } + } + + if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { + ret = cdns3_gadget_init(cdns); + if (ret) { + dev_err(dev, "Device initialization failed with %d\n", + ret); + goto err; + } + } + + cdns->desired_dr_mode = dr_mode; + cdns->dr_mode = dr_mode; + + /* + * desired_dr_mode might have changed so we need to update + * the controller configuration"? + */ + ret = cdns3_drd_update_mode(cdns); + if (ret) + goto err; + + cdns->role = cdsn3_get_real_role(cdns); + + ret = cdns3_role_start(cdns, cdns->role); + if (ret) { + dev_err(dev, "can't start %s role\n", + cdns3_get_current_role_driver(cdns)->name); + goto err; + } + + return ret; +err: + cdns3_exit_roles(cdns); + return ret; +} + +/** + * cdsn3_get_real_role - get real role of controller based on hardware settings. + * @cdns: Pointer to cdns3 structure + * + * Returns role + */ +enum cdns3_roles cdsn3_get_real_role(struct cdns3 *cdns) +{ + enum cdns3_roles role; + int id, vbus; + + if (cdns->current_dr_mode != USB_DR_MODE_OTG) + goto not_otg; + + id = cdns3_get_id(cdns); + vbus = cdns3_get_vbus(cdns); + + /* + * Role change state machine + * Inputs: ID, VBUS + * Previous state: cdns->role + * Next state: role + */ + role = cdns->role; + + switch (role) { + case CDNS3_ROLE_IDLE: /* from IDLE, we can change to HOST or GADGET */ + if (!id) + role = CDNS3_ROLE_HOST; + else if (vbus) + role = CDNS3_ROLE_GADGET; + break; + case CDNS3_ROLE_HOST: /* from HOST, we can only change to IDLE */ + if (id) + role = CDNS3_ROLE_IDLE; + break; + case CDNS3_ROLE_GADGET: /* from GADGET, we can only change to IDLE */ + if (!vbus) + role = CDNS3_ROLE_IDLE; + break; + case CDNS3_ROLE_END: /* only at initialization, move to IDLE */ + role = CDNS3_ROLE_IDLE; + break; + } + + dev_dbg(cdns->dev, "role %d -> %d\n", cdns->role, role); + + return role; + +not_otg: + if (cdns3_is_host(cdns)) + role = CDNS3_ROLE_HOST; + if (cdns3_is_device(cdns)) + role = CDNS3_ROLE_GADGET; + + return role; +} + +/** + * cdns3_role_switch - work queue handler for role switch + * + * @work: work queue item structure + * + * Handles below events: + * - Role switch for dual-role devices + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices + */ +static void cdns3_role_switch(struct work_struct *work) +{ + enum cdns3_roles role = CDNS3_ROLE_END; + struct cdns3_role_driver *role_drv; + enum cdns3_roles current_role; + struct cdns3 *cdns; + int ret = 0; + + cdns = container_of(work, struct cdns3, role_switch_wq); + + pm_runtime_get_sync(cdns->dev); + + role = cdsn3_get_real_role(cdns); + + role_drv = cdns3_get_current_role_driver(cdns); + + /* Disable current role if requested from debugfs */ + if (cdns->debug_disable && role_drv->state == CDNS3_ROLE_STATE_ACTIVE) { + cdns3_role_stop(cdns); + goto exit; + } + + /* Do nothing if nothing changed */ + if (cdns->role == role && role_drv->state == CDNS3_ROLE_STATE_ACTIVE) + goto exit; + + cdns3_role_stop(cdns); + + role = cdsn3_get_real_role(cdns); + + current_role = cdns->role; + dev_dbg(cdns->dev, "Switching role"); + + ret = cdns3_role_start(cdns, role); + if (ret) { + /* Back to current role */ + dev_err(cdns->dev, "set %d has failed, back to %d\n", + role, current_role); + ret = cdns3_role_start(cdns, current_role); + if (ret) + dev_err(cdns->dev, "back to %d failed too\n", + current_role); + } +exit: + pm_runtime_put_sync(cdns->dev); +} + +/** + * cdns3_probe - probe for cdns3 core device + * @pdev: Pointer to cdns3 core platform device + * + * Returns 0 on success otherwise negative errno + */ +static int cdns3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct cdns3 *cdns; + void __iomem *regs; + int ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "error setting dma mask: %d\n", ret); + return -ENODEV; + } + + cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL); + if (!cdns) + return -ENOMEM; + + cdns->dev = dev; + + platform_set_drvdata(pdev, cdns); + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "host"); + if (!res) { + dev_err(dev, "missing host IRQ\n"); + return -ENODEV; + } + + cdns->xhci_res[0] = *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "xhci"); + if (!res) { + dev_err(dev, "couldn't get xhci resource\n"); + return -ENXIO; + } + + cdns->xhci_res[1] = *res; + + cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral"); + if (cdns->dev_irq == -EPROBE_DEFER) + return cdns->dev_irq; + + if (cdns->dev_irq < 0) + dev_err(dev, "couldn't get peripheral irq\n"); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dev"); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) { + dev_err(dev, "couldn't iomap dev resource\n"); + return PTR_ERR(regs); + } + cdns->dev_regs = regs; + + cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); + if (cdns->otg_irq == -EPROBE_DEFER) + return cdns->otg_irq; + + if (cdns->otg_irq < 0) { + dev_err(dev, "couldn't get otg irq\n"); + return cdns->otg_irq; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); + if (!res) { + dev_err(dev, "couldn't get otg resource\n"); + return -ENXIO; + } + + cdns->otg_res = *res; + + mutex_init(&cdns->mutex); + + cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); + if (IS_ERR(cdns->usb2_phy)) + return PTR_ERR(cdns->usb2_phy); + + phy_init(cdns->usb2_phy); + ret = phy_init(cdns->usb2_phy); + if (ret) + return ret; + + cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); + if (IS_ERR(cdns->usb3_phy)) + return PTR_ERR(cdns->usb3_phy); + + phy_init(cdns->usb3_phy); + ret = phy_init(cdns->usb3_phy); + if (ret) + return ret; + + ret = phy_power_on(cdns->usb2_phy); + if (ret) + return ret; + + ret = phy_power_on(cdns->usb3_phy); + if (ret) + goto err1; + + INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch); + + ret = cdns3_drd_init(cdns); + if (ret) + goto err2; + + ret = cdns3_core_init_role(cdns); + if (ret) + goto err2; + + cdns3_debugfs_init(cdns); + device_set_wakeup_capable(dev, true); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + /* + * The controller needs less time between bus and controller suspend, + * and we also needs a small delay to avoid frequently entering low + * power mode. + */ + pm_runtime_set_autosuspend_delay(dev, 20); + pm_runtime_mark_last_busy(dev); + pm_runtime_use_autosuspend(dev); + dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); + + return 0; + +err2: + phy_power_off(cdns->usb3_phy); + +err1: + phy_power_off(cdns->usb2_phy); + phy_exit(cdns->usb2_phy); + phy_exit(cdns->usb3_phy); + + return ret; +} + +/** + * cdns3_remove - unbind drd driver and clean up + * @pdev: Pointer to Linux platform device + * + * Returns 0 on success otherwise negative errno + */ +static int cdns3_remove(struct platform_device *pdev) +{ + struct cdns3 *cdns = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + cdns3_debugfs_exit(cdns); + cdns3_exit_roles(cdns); + phy_power_off(cdns->usb2_phy); + phy_power_off(cdns->usb3_phy); + phy_exit(cdns->usb2_phy); + phy_exit(cdns->usb3_phy); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id of_cdns3_match[] = { + { .compatible = "cdns,usb3" }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_cdns3_match); +#endif + +static struct platform_driver cdns3_driver = { + .probe = cdns3_probe, + .remove = cdns3_remove, + .driver = { + .name = "cdns-usb3", + .of_match_table = of_match_ptr(of_cdns3_match), + }, +}; + +module_platform_driver(cdns3_driver); + +MODULE_ALIAS("platform:cdns3"); +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver"); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h new file mode 100644 index 000000000000..be95696ab17e --- /dev/null +++ b/drivers/usb/cdns3/core.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USBSS DRD Header File. + * + * Copyright (C) 2017-2018 NXP + * Copyright (C) 2018-2019 Cadence. + * + * Authors: Peter Chen <peter.chen@nxp.com> + * Pawel Laszczak <pawell@cadence.com> + */ +#include <linux/usb/otg.h> + +#ifndef __LINUX_CDNS3_CORE_H +#define __LINUX_CDNS3_CORE_H + +struct cdns3; +enum cdns3_roles { + CDNS3_ROLE_IDLE = 0, + CDNS3_ROLE_HOST, + CDNS3_ROLE_GADGET, + CDNS3_ROLE_END, +}; + +/** + * struct cdns3_role_driver - host/gadget role driver + * @start: start this role + * @stop: stop this role + * @suspend: suspend callback for this role + * @resume: resume callback for this role + * @irq: irq handler for this role + * @name: role name string (host/gadget) + * @state: current state + */ +struct cdns3_role_driver { + int (*start)(struct cdns3 *cdns); + void (*stop)(struct cdns3 *cdns); + int (*suspend)(struct cdns3 *cdns, bool do_wakeup); + int (*resume)(struct cdns3 *cdns, bool hibernated); + const char *name; +#define CDNS3_ROLE_STATE_INACTIVE 0 +#define CDNS3_ROLE_STATE_ACTIVE 1 + int state; +}; + +#define CDNS3_XHCI_RESOURCES_NUM 2 +/** + * struct cdns3 - Representation of Cadence USB3 DRD controller. + * @dev: pointer to Cadence device struct + * @xhci_regs: pointer to base of xhci registers + * @xhci_res: the resource for xhci + * @dev_regs: pointer to base of dev registers + * @otg_res: the resource for otg + * @otg_v0_regs: pointer to base of v0 otg registers + * @otg_v1_regs: pointer to base of v1 otg registers + * @otg_regs: pointer to base of otg registers + * @otg_irq: irq number for otg controller + * @dev_irq: irq number for device controller + * @roles: array of supported roles for this controller + * @role: current role + * @host_dev: the child host device pointer for cdns3 core + * @gadget_dev: the child gadget device pointer for cdns3 core + * @usb2_phy: pointer to USB2 PHY + * @usb3_phy: pointer to USB3 PHY + * @role_switch_wq: work queue item for role switch + * @wakeup_int: the wakeup interrupt + * @mutex: the mutex for concurrent code at driver + * @dr_mode: supported mode of operation it can be only Host, only Device + * or OTG mode that allow to switch between Device and Host mode. + * This field based on firmware setting, kernel configuration + * and hardware configuration. + * @current_dr_mode: current mode of operation when in dual-role mode + * @desired_dr_mode: desired mode of operation when in dual-role mode. + * This value can be changed during runtime. + * Available options depends on dr_mode: + * dr_mode | desired_dr_mode and current_dr_mode + * ---------------------------------------------------------------- + * USB_DR_MODE_HOST | only USB_DR_MODE_HOST + * USB_DR_MODE_PERIPHERAL | only USB_DR_MODE_PERIPHERAL + * USB_DR_MODE_OTG | USB_DR_MODE_OTG or USB_DR_MODE_HOST or + * | USB_DR_MODE_PERIPHERAL + * Desired_dr_role can be changed by means of debugfs. + * @root: debugfs root folder pointer + * @debug_disable: + */ +struct cdns3 { + struct device *dev; + void __iomem *xhci_regs; + struct resource xhci_res[CDNS3_XHCI_RESOURCES_NUM]; + struct cdns3_usb_regs __iomem *dev_regs; + + struct resource otg_res; + struct cdns3_otg_legacy_regs *otg_v0_regs; + struct cdns3_otg_regs *otg_v1_regs; + struct cdns3_otg_common_regs *otg_regs; +#define CDNS3_CONTROLLER_V0 0 +#define CDNS3_CONTROLLER_V1 1 + u32 version; + + int otg_irq; + int dev_irq; + struct cdns3_role_driver *roles[CDNS3_ROLE_END]; + enum cdns3_roles role; + struct platform_device *host_dev; + struct cdns3_device *gadget_dev; + struct phy *usb2_phy; + struct phy *usb3_phy; + struct work_struct role_switch_wq; + int wakeup_int:1; + /* mutext used in workqueue*/ + struct mutex mutex; + enum usb_dr_mode dr_mode; + enum usb_dr_mode current_dr_mode; + enum usb_dr_mode desired_dr_mode; + struct dentry *root; + int debug_disable:1; +}; + +void cdns3_role_stop(struct cdns3 *cdns); +int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec); + +#endif /* __LINUX_CDNS3_CORE_H */ diff --git a/drivers/usb/cdns3/debug.h b/drivers/usb/cdns3/debug.h new file mode 100644 index 000000000000..65c3d8a6cd62 --- /dev/null +++ b/drivers/usb/cdns3/debug.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USBSS DRD Driver. + * Debug header file. + * + * Copyright (C) 2018-2019 Cadence. + * + * Author: Pawel Laszczak <pawell@cadence.com> + */ +#ifndef __LINUX_CDNS3_DEBUG +#define __LINUX_CDNS3_DEBUG + +#include "core.h" + +static inline char *cdns3_decode_usb_irq(char *str, + enum usb_device_speed speed, + u32 usb_ists) +{ + int ret; + + ret = sprintf(str, "IRQ %08x = ", usb_ists); + + if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) { + ret += sprintf(str + ret, "Connection %s\n", + usb_speed_string(speed)); + } + if (usb_ists & USB_ISTS_DIS2I || usb_ists & USB_ISTS_DISI) + ret += sprintf(str + ret, "Disconnection "); + if (usb_ists & USB_ISTS_L2ENTI) + ret += sprintf(str + ret, "suspended "); + if (usb_ists & USB_ISTS_L1ENTI) + ret += sprintf(str + ret, "L1 enter "); + if (usb_ists & USB_ISTS_L1EXTI) + ret += sprintf(str + ret, "L1 exit "); + if (usb_ists & USB_ISTS_L2ENTI) + ret += sprintf(str + ret, "L2 enter "); + if (usb_ists & USB_ISTS_L2EXTI) + ret += sprintf(str + ret, "L2 exit "); + if (usb_ists & USB_ISTS_U3EXTI) + ret += sprintf(str + ret, "U3 exit "); + if (usb_ists & USB_ISTS_UWRESI) + ret += sprintf(str + ret, "Warm Reset "); + if (usb_ists & USB_ISTS_UHRESI) + ret += sprintf(str + ret, "Hot Reset "); + if (usb_ists & USB_ISTS_U2RESI) + ret += sprintf(str + ret, "Reset"); + + return str; +} + +static inline char *cdns3_decode_ep_irq(char *str, + u32 ep_sts, + const char *ep_name) +{ + int ret; + + ret = sprintf(str, "IRQ for %s: %08x ", ep_name, ep_sts); + + if (ep_sts & EP_STS_SETUP) + ret += sprintf(str + ret, "SETUP "); + if (ep_sts & EP_STS_IOC) + ret += sprintf(str + ret, "IOC "); + if (ep_sts & EP_STS_ISP) + ret += sprintf(str + ret, "ISP "); + if (ep_sts & EP_STS_DESCMIS) + ret += sprintf(str + ret, "DESCMIS "); + if (ep_sts & EP_STS_STREAMR) + ret += sprintf(str + ret, "STREAMR "); + if (ep_sts & EP_STS_MD_EXIT) + ret += sprintf(str + ret, "MD_EXIT "); + if (ep_sts & EP_STS_TRBERR) + ret += sprintf(str + ret, "TRBERR "); + if (ep_sts & EP_STS_NRDY) + ret += sprintf(str + ret, "NRDY "); + if (ep_sts & EP_STS_PRIME) + ret += sprintf(str + ret, "PRIME "); + if (ep_sts & EP_STS_SIDERR) + ret += sprintf(str + ret, "SIDERRT "); + if (ep_sts & EP_STS_OUTSMM) + ret += sprintf(str + ret, "OUTSMM "); + if (ep_sts & EP_STS_ISOERR) + ret += sprintf(str + ret, "ISOERR "); |