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); +} - dwc->current_dr_role = mode; +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 = 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 #include #include +#include #include #include @@ -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; }