summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/dwc3/core.c103
-rw-r--r--drivers/usb/dwc3/core.h7
-rw-r--r--drivers/usb/dwc3/debugfs.c8
3 files changed, 80 insertions, 38 deletions
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 68e6a7b6fd81..458e7c6cc002 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -100,7 +100,10 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
return 0;
}
-void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
+static void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
+static int dwc3_event_buffers_setup(struct dwc3 *dwc);
+
+static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
{
u32 reg;
@@ -108,8 +111,69 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
reg |= DWC3_GCTL_PRTCAPDIR(mode);
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+}
+
+static void __dwc3_set_mode(struct work_struct *work)
+{
+ struct dwc3 *dwc = work_to_dwc(work);
+ unsigned long flags;
+ int ret;
+
+ if (!dwc->desired_dr_role)
+ return;
+
+ if (dwc->desired_dr_role == dwc->current_dr_role)
+ return;
+
+ if (dwc->dr_mode != USB_DR_MODE_OTG)
+ return;
+
+ switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_HOST:
+ dwc3_host_exit(dwc);
+ break;
+ case DWC3_GCTL_PRTCAP_DEVICE:
+ dwc3_gadget_exit(dwc);
+ dwc3_event_buffers_cleanup(dwc);
+ break;
+ default:
+ break;
+ }
+
+ spin_lock_irqsave(&dwc->lock, flags);
+
+ dwc3_set_prtcap(dwc, dwc->desired_dr_role);
- dwc->current_dr_role = mode;
+ dwc->current_dr_role = dwc->desired_dr_role;
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ switch (dwc->desired_dr_role) {
+ case DWC3_GCTL_PRTCAP_HOST:
+ ret = dwc3_host_init(dwc);
+ if (ret)
+ dev_err(dwc->dev, "failed to initialize host\n");
+ break;
+ case DWC3_GCTL_PRTCAP_DEVICE:
+ dwc3_event_buffers_setup(dwc);
+ ret = dwc3_gadget_init(dwc);
+ if (ret)
+ dev_err(dwc->dev, "failed to initialize peripheral\n");
+ break;
+ default:
+ break;
+ }
+}
+
+void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+ dwc->desired_dr_role = mode;
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ queue_work(system_power_efficient_wq, &dwc->drd_work);
}
u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type)
@@ -721,21 +785,6 @@ static int dwc3_core_init(struct dwc3 *dwc)
goto err4;
}
- switch (dwc->dr_mode) {
- case USB_DR_MODE_PERIPHERAL:
- dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
- break;
- case USB_DR_MODE_HOST:
- dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
- break;
- case USB_DR_MODE_OTG:
- dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
- break;
- default:
- dev_warn(dwc->dev, "Unsupported mode %d\n", dwc->dr_mode);
- break;
- }
-
/*
* ENDXFER polling is available on version 3.10a and later of
* the DWC_usb3 controller. It is NOT available in the
@@ -853,6 +902,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
ret = dwc3_gadget_init(dwc);
if (ret) {
if (ret != -EPROBE_DEFER)
@@ -861,6 +911,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
}
break;
case USB_DR_MODE_HOST:
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
ret = dwc3_host_init(dwc);
if (ret) {
if (ret != -EPROBE_DEFER)
@@ -869,19 +920,8 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
}
break;
case USB_DR_MODE_OTG:
- ret = dwc3_host_init(dwc);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to initialize host\n");
- return ret;
- }
-
- ret = dwc3_gadget_init(dwc);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to initialize gadget\n");
- return ret;
- }
+ INIT_WORK(&dwc->drd_work, __dwc3_set_mode);
+ dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -901,8 +941,9 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc)
dwc3_host_exit(dwc);
break;
case USB_DR_MODE_OTG:
- dwc3_host_exit(dwc);
+ dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
dwc3_gadget_exit(dwc);
+ flush_work(&dwc->drd_work);
break;
default:
/* do nothing */
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 3464dbfe153b..1fe23e36485f 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -28,6 +28,7 @@
#include <linux/mm.h>
#include <linux/debugfs.h>
#include <linux/wait.h>
+#include <linux/workqueue.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
@@ -760,6 +761,7 @@ struct dwc3_scratchpad_array {
/**
* struct dwc3 - representation of our controller
+ * @drd_work - workqueue used for role swapping
* @ep0_trb: trb which is used for the ctrl_req
* @setup_buf: used while precessing STD USB requests
* @ep0_trb: dma address of ep0_trb
@@ -782,6 +784,7 @@ struct dwc3_scratchpad_array {
* @revision: revision register contents
* @dr_mode: requested mode of operation
* @current_dr_role: current role of operation when in dual-role mode
+ * @desired_dr_role: desired role of operation when in dual-role mode
* @hsphy_mode: UTMI phy mode, one of following:
* - USBPHY_INTERFACE_MODE_UTMI
* - USBPHY_INTERFACE_MODE_UTMIW
@@ -855,6 +858,7 @@ struct dwc3_scratchpad_array {
* increments or 0 to disable.
*/
struct dwc3 {
+ struct work_struct drd_work;
struct dwc3_trb *ep0_trb;
void *bounce;
void *scratchbuf;
@@ -893,6 +897,7 @@ struct dwc3 {
enum usb_dr_mode dr_mode;
u32 current_dr_role;
+ u32 desired_dr_role;
enum usb_phy_interface hsphy_mode;
u32 fladj;
@@ -1002,7 +1007,7 @@ struct dwc3 {
u16 imod_interval;
};
-/* -------------------------------------------------------------------------- */
+#define work_to_dwc(w) (container_of((w), struct dwc3, drd_work))
/* -------------------------------------------------------------------------- */
diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c
index e72f2634e493..7be963dd8e3b 100644
--- a/drivers/usb/dwc3/debugfs.c
+++ b/drivers/usb/dwc3/debugfs.c
@@ -319,7 +319,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];
@@ -335,11 +334,8 @@ 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);
- }
+ dwc3_set_mode(dwc, mode);
+
return count;
}