Skip to content

Commit

Permalink
usb: dwc3: core: make dwc3_set_mode() work properly
Browse files Browse the repository at this point in the history
We can't have both Host and Peripheral roles active at the same time
because of one detail on DWC3: it shares the same memory area for both
Host and Peripheral registers.

When swapping roles we must reinitialize the new role every
time. Let's make sure this works for our debugfs interface.

Signed-off-by: Roger Quadros <rogerq@ti.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
  • Loading branch information
Roger Quadros authored and Felipe Balbi committed Apr 11, 2017
1 parent b202c42 commit 41ce145
Showing 3 changed files with 80 additions and 38 deletions.
103 changes: 72 additions & 31 deletions drivers/usb/dwc3/core.c
Original file line number Diff line number Diff line change
@@ -100,16 +100,80 @@ 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;

reg = dwc3_readl(dwc->regs, DWC3_GCTL);
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 */
7 changes: 6 additions & 1 deletion drivers/usb/dwc3/core.h
Original file line number Diff line number Diff line change
@@ -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))

/* -------------------------------------------------------------------------- */

8 changes: 2 additions & 6 deletions drivers/usb/dwc3/debugfs.c
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 41ce145

Please sign in to comment.