This patch implemented a feature to dynamic switch to host or device role under debugfs for some physical limitation that unable to leverage connector A/B cables (ID pin) to change roles. The default role should be set as OTG mode. Then use below commands: [1] switch to host: echo host > /sys/kernel/debug/dwc3.0.auto/mode [2] switch to device: echo device > /sys/kernel/debug/dwc3.0.auto/mode [3] switch to otg (default mode): echo otg > /sys/kernel/debug/dwc3.0.auto/mode Signed-off-by: Huang Rui <ray.huang@xxxxxxx> --- drivers/usb/dwc3/Makefile | 2 +- drivers/usb/dwc3/core.c | 4 +- drivers/usb/dwc3/core.h | 6 ++ drivers/usb/dwc3/debugfs.c | 19 ++++-- drivers/usb/dwc3/drd.c | 163 +++++++++++++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/drd.h | 30 +++++++++ drivers/usb/dwc3/gadget.c | 92 ++++++++++++++++--------- drivers/usb/dwc3/gadget.h | 3 + drivers/usb/dwc3/host.c | 2 + 9 files changed, 280 insertions(+), 41 deletions(-) create mode 100644 drivers/usb/dwc3/drd.c create mode 100644 drivers/usb/dwc3/drd.h diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index bb34fbc..115bbc7 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -13,7 +13,7 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) $(CONFIG_USB_DWC3_DUAL_ROLE)),) endif ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),) - dwc3-y += gadget.o ep0.o + dwc3-y += gadget.o ep0.o drd.o endif ifneq ($(CONFIG_DEBUG_FS),) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 6138c5d..7b50584 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -61,7 +61,7 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode) * dwc3_core_soft_reset - Issues core soft reset and PHY reset * @dwc: pointer to our context structure */ -static int dwc3_core_soft_reset(struct dwc3 *dwc) +int dwc3_core_soft_reset(struct dwc3 *dwc) { u32 reg; int ret; @@ -228,7 +228,7 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length) * * Returns 0 on success otherwise negative errno. */ -static int dwc3_event_buffers_setup(struct dwc3 *dwc) +int dwc3_event_buffers_setup(struct dwc3 *dwc) { struct dwc3_event_buffer *evt; int n; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index a1c7dc5..3a30f33 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -643,6 +643,8 @@ struct dwc3_scratchpad_array { * @maximum_speed: maximum speed requested (mainly for testing purposes) * @revision: revision register contents * @quirks: represents different SOCs hardware work-arounds and quirks + * @has_gadget: true when gadget is initialized + * @has_xhci: true when xhci is initialized * @dr_mode: requested mode of operation * @usb2_phy: pointer to USB2 PHY * @usb3_phy: pointer to USB3 PHY @@ -750,6 +752,8 @@ struct dwc3 { #define DWC3_AMD_NL_PLAT (1 << 0) + bool has_gadget; + bool has_xhci; enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; enum dwc3_link_state link_state; @@ -935,6 +939,8 @@ struct dwc3_gadget_ep_cmd_params { /* prototypes */ void dwc3_set_mode(struct dwc3 *dwc, u32 mode); int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc); +int dwc3_core_soft_reset(struct dwc3 *dwc); +int dwc3_event_buffers_setup(struct dwc3 *dwc); #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) int dwc3_host_init(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 9ac37fe..76384df 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -32,6 +32,7 @@ #include "gadget.h" #include "io.h" #include "debug.h" +#include "drd.h" #define dump_register(nm) \ { \ @@ -394,7 +395,6 @@ static ssize_t dwc3_mode_write(struct file *file, { struct seq_file *s = file->private_data; struct dwc3 *dwc = s->private; - unsigned long flags; u32 mode = 0; char buf[32]; @@ -410,10 +410,19 @@ static ssize_t dwc3_mode_write(struct file *file, if (!strncmp(buf, "otg", 3)) mode |= DWC3_GCTL_PRTCAP_OTG; - if (mode) { - spin_lock_irqsave(&dwc->lock, flags); - dwc3_set_mode(dwc, mode); - spin_unlock_irqrestore(&dwc->lock, flags); + switch (mode) { + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_drd_to_device(dwc); + break; + case DWC3_GCTL_PRTCAP_HOST: + dwc3_drd_to_host(dwc); + break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_drd_to_otg(dwc); + break; + default: + /* Should never happen */ + break; } return count; } diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c new file mode 100644 index 0000000..53d9a9eb --- /dev/null +++ b/drivers/usb/dwc3/drd.c @@ -0,0 +1,163 @@ +/** + * drd.c - DesignWare USB3 DRD Controller Dual Role Switch Funciton File + * + * Copyright (C) 2014 Advanced Micro Devices, Inc. + * + * Author: Huang Rui <ray.huang@xxxxxxx> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/of.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> + +#include "core.h" +#include "gadget.h" +#include "io.h" +#include "drd.h" + + +int dwc3_drd_to_host(struct dwc3 *dwc) +{ + int ret; + unsigned long flags = 0; + + if (dwc->has_xhci) + dwc3_host_exit(dwc); + if (dwc->has_gadget) + dwc3_gadget_stop_on_switch(dwc); + + ret = dwc3_core_soft_reset(dwc); + if (ret) { + dev_err(dwc->dev, "soft reset failed\n"); + goto err0; + } + + spin_lock_irqsave(&dwc->lock, flags); + ret = dwc3_event_buffers_setup(dwc); + if (ret) { + dev_err(dwc->dev, "failed to setup event buffers\n"); + goto err0; + } + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dwc->dev, "failed to init host\n"); + goto err0; + } +err0: + spin_unlock_irqrestore(&dwc->lock, flags); + return ret; +} + +int dwc3_drd_to_device(struct dwc3 *dwc) +{ + int ret; + unsigned long timeout, flags = 0; + u32 reg; + + if (dwc->has_xhci) + dwc3_host_exit(dwc); + if (dwc->has_gadget) + dwc3_gadget_stop_on_switch(dwc); + + ret = dwc3_core_soft_reset(dwc); + if (ret) { + dev_err(dwc->dev, "soft reset failed\n"); + goto err0; + } + + spin_lock_irqsave(&dwc->lock, flags); + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + + /* issue device SoftReset too */ + timeout = jiffies + msecs_to_jiffies(500); + dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_CSFTRST); + do { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (!(reg & DWC3_DCTL_CSFTRST)) + break; + + if (time_after(jiffies, timeout)) { + dev_err(dwc->dev, "Reset Timed Out\n"); + ret = -ETIMEDOUT; + goto err0; + } + + cpu_relax(); + } while (true); + + ret = dwc3_event_buffers_setup(dwc); + if (ret) { + dev_err(dwc->dev, "failed to setup event buffers\n"); + goto err0; + } + + ret = dwc3_gadget_restart(dwc); + if (ret) { + dev_err(dwc->dev, "failed to restart gadget\n"); + goto err0; + } +err0: + spin_unlock_irqrestore(&dwc->lock, flags); + return ret; +} + +int dwc3_drd_to_otg(struct dwc3 *dwc) +{ + int ret = 0; + unsigned long flags = 0; + + if (dwc->has_xhci) + dwc3_host_exit(dwc); + if (dwc->has_gadget) + dwc3_gadget_stop_on_switch(dwc); + + ret = dwc3_core_soft_reset(dwc); + if (ret) { + dev_err(dwc->dev, "soft reset failed\n"); + goto err0; + } + + spin_lock_irqsave(&dwc->lock, flags); + ret = dwc3_event_buffers_setup(dwc); + if (ret) { + dev_err(dwc->dev, "failed to setup event buffers\n"); + goto err0; + } + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dwc->dev, "failed to init host\n"); + goto err0; + } + +err0: + spin_unlock_irqrestore(&dwc->lock, flags); + return ret; +} diff --git a/drivers/usb/dwc3/drd.h b/drivers/usb/dwc3/drd.h new file mode 100644 index 0000000..14d4944 --- /dev/null +++ b/drivers/usb/dwc3/drd.h @@ -0,0 +1,30 @@ +/** + * drd.c - DesignWare USB3 DRD Controller Dual Role Switch Funciton Header + * + * Copyright (C) 2014 Advanced Micro Devices, Inc. + * + * Author: Huang Rui <ray.huang@xxxxxxx> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __DRIVERS_USB_DWC3_DRD_H +#define __DRIVERS_USB_DWC3_DRD_H + +#include "core.h" +#include "gadget.h" +#include "io.h" + +extern int dwc3_drd_to_host(struct dwc3 *dwc); +extern int dwc3_drd_to_device(struct dwc3 *dwc); +extern int dwc3_drd_to_otg(struct dwc3 *dwc); + +#endif /* __DRIVERS_USB_DWC3_CORE_H */ + diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 8277065..84252f3 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1501,37 +1501,13 @@ static void dwc3_gadget_disable_irq(struct dwc3 *dwc) static irqreturn_t dwc3_interrupt(int irq, void *_dwc); static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc); -static int dwc3_gadget_start(struct usb_gadget *g, - struct usb_gadget_driver *driver) +int dwc3_gadget_restart(struct dwc3 *dwc) { - struct dwc3 *dwc = gadget_to_dwc(g); - struct dwc3_ep *dep; - unsigned long flags; int ret = 0; - int irq; u32 reg; + struct dwc3_ep *dep; - irq = platform_get_irq(to_platform_device(dwc->dev), 0); - ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt, - IRQF_SHARED, "dwc3", dwc); - if (ret) { - dev_err(dwc->dev, "failed to request irq #%d --> %d\n", - irq, ret); - goto err0; - } - - spin_lock_irqsave(&dwc->lock, flags); - - if (dwc->gadget_driver) { - dev_err(dwc->dev, "%s is already bound to %s\n", - dwc->gadget.name, - dwc->gadget_driver->driver.name); - ret = -EBUSY; - goto err1; - } - - dwc->gadget_driver = driver; - + dwc->has_gadget = true; reg = dwc3_readl(dwc->regs, DWC3_DCFG); reg &= ~(DWC3_DCFG_SPEED_MASK); @@ -1579,7 +1555,7 @@ static int dwc3_gadget_start(struct usb_gadget *g, false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); - goto err2; + goto err0; } dep = dwc->eps[1]; @@ -1587,7 +1563,7 @@ static int dwc3_gadget_start(struct usb_gadget *g, false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); - goto err3; + goto err1; } /* begin to receive SETUP packets */ @@ -1596,16 +1572,54 @@ static int dwc3_gadget_start(struct usb_gadget *g, dwc3_gadget_enable_irq(dwc); - spin_unlock_irqrestore(&dwc->lock, flags); - return 0; -err3: +err1: __dwc3_gadget_ep_disable(dwc->eps[0]); -err2: +err0: dwc->gadget_driver = NULL; + return ret; +} + +static int dwc3_gadget_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + int ret = 0; + int irq; + + irq = platform_get_irq(to_platform_device(dwc->dev), 0); + ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt, + IRQF_SHARED, "dwc3", dwc); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + irq, ret); + goto err0; + } + + spin_lock_irqsave(&dwc->lock, flags); + + if (dwc->gadget_driver) { + dev_err(dwc->dev, "%s is already bound to %s\n", + dwc->gadget.name, + dwc->gadget_driver->driver.name); + ret = -EBUSY; + goto err1; + } + + dwc->gadget_driver = driver; + + ret = dwc3_gadget_restart(dwc); + if (ret) + goto err1; + + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; + err1: spin_unlock_irqrestore(&dwc->lock, flags); @@ -1615,6 +1629,16 @@ err0: return ret; } + +int dwc3_gadget_stop_on_switch(struct dwc3 *dwc) +{ + dwc->has_gadget = false; + __dwc3_gadget_ep_disable(dwc->eps[0]); + __dwc3_gadget_ep_disable(dwc->eps[1]); + + return 0; +} + static int dwc3_gadget_stop(struct usb_gadget *g, struct usb_gadget_driver *driver) { @@ -2669,6 +2693,7 @@ int dwc3_gadget_init(struct dwc3 *dwc) goto err3; } + dwc->has_gadget = true; dwc->gadget.ops = &dwc3_gadget_ops; dwc->gadget.max_speed = USB_SPEED_SUPER; dwc->gadget.speed = USB_SPEED_UNKNOWN; @@ -2729,6 +2754,7 @@ err0: void dwc3_gadget_exit(struct dwc3 *dwc) { + dwc->has_gadget = false; usb_del_gadget_udc(&dwc->gadget); dwc3_gadget_free_endpoints(dwc); diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index 178ad89..b3f628a 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -103,4 +103,7 @@ static inline u32 dwc3_gadget_ep_get_transfer_index(struct dwc3 *dwc, u8 number) return DWC3_DEPCMD_GET_RSC_IDX(res_id); } +int dwc3_gadget_restart(struct dwc3 *dwc); +int dwc3_gadget_stop_on_switch(struct dwc3 *dwc); + #endif /* __DRIVERS_USB_DWC3_GADGET_H */ diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index dcb8ca0..dc6d5f0 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -40,6 +40,7 @@ int dwc3_host_init(struct dwc3 *dwc) xhci->dev.dma_parms = dwc->dev->dma_parms; dwc->xhci = xhci; + dwc->has_xhci = true; ret = platform_device_add_resources(xhci, dwc->xhci_resources, DWC3_XHCI_RESOURCES_NUM); @@ -77,5 +78,6 @@ err0: void dwc3_host_exit(struct dwc3 *dwc) { + dwc->has_xhci = false; platform_device_unregister(dwc->xhci); } -- 1.8.1.2 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html