summaryrefslogtreecommitdiffstats
path: root/drivers/usb/dwc3/gadget.c
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-07-05 15:15:38 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-07-05 15:15:38 -0700
commit933141509cefd64102a943d61d154c5c53bad889 (patch)
treed36112785cb0742ab3b6d6c91cfd4777978b3220 /drivers/usb/dwc3/gadget.c
parent94ff447cf275170042a7a05b972b6da8b94754ca (diff)
parent6f17f74b3d8345663f5fcfd834145685110075bd (diff)
Merge tag 'dwc3-for-v3.6' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next
usb: dwc3: patches for v3.6 merge window lots of changes for the dwc3 driver which will make the driver a lot more stable and some changes which are just cosmetic. The bulk of changes is related to mis-defined macros which were never used before, some fixes to error path which e.g. prevent the driver from initiating two transfer on ep0out in case of stall, some fixes to request dequeueing and the End Transfer Command. We have some changes to U1/U2 handling where we were enabling those transtions at the wrong spot (though we haven't seen a problem from that as of today). A few patches will make it easier to add support for newer releases of the core by adding definitions for new registers and changing the code to act accordingly when certain revisions are detected. There's also the usual comestic changes to make the driver easier to maintain and make it easier for new- comers to understand the driver. Also one patch fixing a double inclusion of a header.
Diffstat (limited to 'drivers/usb/dwc3/gadget.c')
-rw-r--r--drivers/usb/dwc3/gadget.c363
1 files changed, 216 insertions, 147 deletions
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 4f3e8811dae..58fdfad96b4 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -100,6 +100,23 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state)
int retries = 10000;
u32 reg;
+ /*
+ * Wait until device controller is ready. Only applies to 1.94a and
+ * later RTL.
+ */
+ if (dwc->revision >= DWC3_REVISION_194A) {
+ while (--retries) {
+ reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+ if (reg & DWC3_DSTS_DCNRD)
+ udelay(5);
+ else
+ break;
+ }
+
+ if (retries <= 0)
+ return -ETIMEDOUT;
+ }
+
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
reg &= ~DWC3_DCTL_ULSTCHNGREQ_MASK;
@@ -107,7 +124,15 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state)
reg |= DWC3_DCTL_ULSTCHNGREQ(state);
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ /*
+ * The following code is racy when called from dwc3_gadget_wakeup,
+ * and is not needed, at least on newer versions
+ */
+ if (dwc->revision >= DWC3_REVISION_194A)
+ return 0;
+
/* wait for a change in DSTS */
+ retries = 10000;
while (--retries) {
reg = dwc3_readl(dwc->regs, DWC3_DSTS);
@@ -265,8 +290,8 @@ static const char *dwc3_gadget_ep_cmd_string(u8 cmd)
return "Clear Stall";
case DWC3_DEPCMD_SETSTALL:
return "Set Stall";
- case DWC3_DEPCMD_GETSEQNUMBER:
- return "Get Data Sequence Number";
+ case DWC3_DEPCMD_GETEPSTATE:
+ return "Get Endpoint State";
case DWC3_DEPCMD_SETTRANSFRESOURCE:
return "Set Endpoint Transfer Resource";
case DWC3_DEPCMD_SETEPCONFIG:
@@ -530,9 +555,37 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep)
{
struct dwc3_request *req;
- if (!list_empty(&dep->req_queued))
+ if (!list_empty(&dep->req_queued)) {
dwc3_stop_active_transfer(dwc, dep->number);
+ /*
+ * NOTICE: We are violating what the Databook says about the
+ * EndTransfer command. Ideally we would _always_ wait for the
+ * EndTransfer Command Completion IRQ, but that's causing too
+ * much trouble synchronizing between us and gadget driver.
+ *
+ * We have discussed this with the IP Provider and it was
+ * suggested to giveback all requests here, but give HW some
+ * extra time to synchronize with the interconnect. We're using
+ * an arbitraty 100us delay for that.
+ *
+ * Note also that a similar handling was tested by Synopsys
+ * (thanks a lot Paul) and nothing bad has come out of it.
+ * In short, what we're doing is:
+ *
+ * - Issue EndTransfer WITH CMDIOC bit set
+ * - Wait 100us
+ * - giveback all requests to gadget driver
+ */
+ udelay(100);
+
+ while (!list_empty(&dep->req_queued)) {
+ req = next_request(&dep->req_queued);
+
+ dwc3_gadget_giveback(dep, req, -ESHUTDOWN);
+ }
+ }
+
while (!list_empty(&dep->request_list)) {
req = next_request(&dep->request_list);
@@ -741,8 +794,7 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
case USB_ENDPOINT_XFER_ISOC:
trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS_FIRST;
- /* IOC every DWC3_TRB_NUM / 4 so we can refill */
- if (!(cur_slot % (DWC3_TRB_NUM / 4)))
+ if (!req->request.no_interrupt)
trb->ctrl |= DWC3_TRB_CTRL_IOC;
break;
@@ -958,14 +1010,42 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param,
dep->flags |= DWC3_EP_BUSY;
if (start_new) {
- dep->res_trans_idx = dwc3_gadget_ep_get_transfer_index(dwc,
+ dep->resource_index = dwc3_gadget_ep_get_transfer_index(dwc,
dep->number);
- WARN_ON_ONCE(!dep->res_trans_idx);
+ WARN_ON_ONCE(!dep->resource_index);
}
return 0;
}
+static void __dwc3_gadget_start_isoc(struct dwc3 *dwc,
+ struct dwc3_ep *dep, u32 cur_uf)
+{
+ u32 uf;
+
+ if (list_empty(&dep->request_list)) {
+ dev_vdbg(dwc->dev, "ISOC ep %s run out for requests.\n",
+ dep->name);
+ return;
+ }
+
+ /* 4 micro frames in the future */
+ uf = cur_uf + dep->interval * 4;
+
+ __dwc3_gadget_kick_transfer(dep, uf, 1);
+}
+
+static void dwc3_gadget_start_isoc(struct dwc3 *dwc,
+ struct dwc3_ep *dep, const struct dwc3_event_depevt *event)
+{
+ u32 cur_uf, mask;
+
+ mask = ~(dep->interval - 1);
+ cur_uf = event->parameters & mask;
+
+ __dwc3_gadget_start_isoc(dwc, dep, cur_uf);
+}
+
static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
{
struct dwc3 *dwc = dep->dwc;
@@ -995,11 +1075,8 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
list_add_tail(&req->list, &dep->request_list);
- if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && (dep->flags & DWC3_EP_BUSY))
- dep->flags |= DWC3_EP_PENDING_REQUEST;
-
/*
- * There are two special cases:
+ * There are a few special cases:
*
* 1. XferNotReady with empty list of requests. We need to kick the
* transfer here in that situation, otherwise we will be NAKing
@@ -1008,31 +1085,46 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
* able to receive the data until the next request is queued.
* The following code is handling exactly that.
*
- * 2. XferInProgress on Isoc EP with an active transfer. We need to
- * kick the transfer here after queuing a request, otherwise the
- * core may not see the modified TRB(s).
*/
if (dep->flags & DWC3_EP_PENDING_REQUEST) {
int ret;
- int start_trans = 1;
- u8 trans_idx = dep->res_trans_idx;
- if (usb_endpoint_xfer_isoc(dep->endpoint.desc) &&
- (dep->flags & DWC3_EP_BUSY)) {
- start_trans = 0;
- WARN_ON_ONCE(!trans_idx);
- } else {
- trans_idx = 0;
+ ret = __dwc3_gadget_kick_transfer(dep, 0, true);
+ if (ret && ret != -EBUSY) {
+ struct dwc3 *dwc = dep->dwc;
+
+ dev_dbg(dwc->dev, "%s: failed to kick transfers\n",
+ dep->name);
}
+ }
- ret = __dwc3_gadget_kick_transfer(dep, trans_idx, start_trans);
+ /*
+ * 2. XferInProgress on Isoc EP with an active transfer. We need to
+ * kick the transfer here after queuing a request, otherwise the
+ * core may not see the modified TRB(s).
+ */
+ if (usb_endpoint_xfer_isoc(dep->endpoint.desc) &&
+ (dep->flags & DWC3_EP_BUSY)) {
+ WARN_ON_ONCE(!dep->resource_index);
+ ret = __dwc3_gadget_kick_transfer(dep, dep->resource_index,
+ false);
if (ret && ret != -EBUSY) {
struct dwc3 *dwc = dep->dwc;
dev_dbg(dwc->dev, "%s: failed to kick transfers\n",
dep->name);
}
- };
+ }
+
+ /*
+ * 3. Missed ISOC Handling. We need to start isoc transfer on the saved
+ * uframe number.
+ */
+ if (usb_endpoint_xfer_isoc(dep->endpoint.desc) &&
+ (dep->flags & DWC3_EP_MISSED_ISOC)) {
+ __dwc3_gadget_start_isoc(dwc, dep, dep->current_uf);
+ dep->flags &= ~DWC3_EP_MISSED_ISOC;
+ }
return 0;
}
@@ -1118,15 +1210,6 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value)
memset(&params, 0x00, sizeof(params));
if (value) {
- if (dep->number == 0 || dep->number == 1) {
- /*
- * Whenever EP0 is stalled, we will restart
- * the state machine, thus moving back to
- * Setup Phase
- */
- dwc->ep0state = EP0_SETUP_PHASE;
- }
-
ret = dwc3_send_gadget_ep_cmd(dwc, dep->number,
DWC3_DEPCMD_SETSTALL, &params);
if (ret)
@@ -1186,7 +1269,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep)
dep->flags |= DWC3_EP_WEDGE;
spin_unlock_irqrestore(&dwc->lock, flags);
- return dwc3_gadget_ep_set_halt(ep, 1);
+ if (dep->number == 0 || dep->number == 1)
+ return dwc3_gadget_ep0_set_halt(ep, 1);
+ else
+ return dwc3_gadget_ep_set_halt(ep, 1);
}
/* -------------------------------------------------------------------------- */
@@ -1204,7 +1290,7 @@ static const struct usb_ep_ops dwc3_gadget_ep0_ops = {
.free_request = dwc3_gadget_ep_free_request,
.queue = dwc3_gadget_ep0_queue,
.dequeue = dwc3_gadget_ep_dequeue,
- .set_halt = dwc3_gadget_ep_set_halt,
+ .set_halt = dwc3_gadget_ep0_set_halt,
.set_wedge = dwc3_gadget_ep_set_wedge,
};
@@ -1280,9 +1366,13 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g)
goto out;
}
- /* write zeroes to Link Change Request */
- reg &= ~DWC3_DCTL_ULSTCHNGREQ_MASK;
- dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ /* Recent versions do this automatically */
+ if (dwc->revision < DWC3_REVISION_194A) {
+ /* write zeroes to Link Change Request */
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg &= ~DWC3_DCTL_ULSTCHNGREQ_MASK;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ }
/* poll until Link State changes to ON */
timeout = jiffies + msecs_to_jiffies(100);
@@ -1319,16 +1409,21 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g,
return 0;
}
-static void dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on)
+static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on)
{
u32 reg;
u32 timeout = 500;
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
if (is_on) {
- reg &= ~DWC3_DCTL_TRGTULST_MASK;
- reg |= (DWC3_DCTL_RUN_STOP
- | DWC3_DCTL_TRGTULST_RX_DET);
+ if (dwc->revision <= DWC3_REVISION_187A) {
+ reg &= ~DWC3_DCTL_TRGTULST_MASK;
+ reg |= DWC3_DCTL_TRGTULST_RX_DET;
+ }
+
+ if (dwc->revision >= DWC3_REVISION_194A)
+ reg &= ~DWC3_DCTL_KEEP_CONNECT;
+ reg |= DWC3_DCTL_RUN_STOP;
} else {
reg &= ~DWC3_DCTL_RUN_STOP;
}
@@ -1346,7 +1441,7 @@ static void dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on)
}
timeout--;
if (!timeout)
- break;
+ return -ETIMEDOUT;
udelay(1);
} while (1);
@@ -1354,20 +1449,23 @@ static void dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on)
dwc->gadget_driver
? dwc->gadget_driver->function : "no-function",
is_on ? "connect" : "disconnect");
+
+ return 0;
}
static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
{
struct dwc3 *dwc = gadget_to_dwc(g);
unsigned long flags;
+ int ret;
is_on = !!is_on;
spin_lock_irqsave(&dwc->lock, flags);
- dwc3_gadget_run_stop(dwc, is_on);
+ ret = dwc3_gadget_run_stop(dwc, is_on);
spin_unlock_irqrestore(&dwc->lock, flags);
- return 0;
+ return ret;
}
static int dwc3_gadget_start(struct usb_gadget *g,
@@ -1468,6 +1566,7 @@ static int dwc3_gadget_stop(struct usb_gadget *g,
return 0;
}
+
static const struct usb_gadget_ops dwc3_gadget_ops = {
.get_frame = dwc3_gadget_get_frame,
.wakeup = dwc3_gadget_wakeup,
@@ -1558,6 +1657,7 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep,
struct dwc3_trb *trb;
unsigned int count;
unsigned int s_pkt = 0;
+ unsigned int trb_status;
do {
req = next_request(&dep->req_queued);
@@ -1583,9 +1683,18 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep,
if (dep->direction) {
if (count) {
- dev_err(dwc->dev, "incomplete IN transfer %s\n",
- dep->name);
- status = -ECONNRESET;
+ trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size);
+ if (trb_status == DWC3_TRBSTS_MISSED_ISOC) {
+ dev_dbg(dwc->dev, "incomplete IN transfer %s\n",
+ dep->name);
+ dep->current_uf = event->parameters &
+ ~(dep->interval - 1);
+ dep->flags |= DWC3_EP_MISSED_ISOC;
+ } else {
+ dev_err(dwc->dev, "incomplete IN transfer %s\n",
+ dep->name);
+ status = -ECONNRESET;
+ }
}
} else {
if (count && (event->status & DEPEVT_STATUS_SHORT))
@@ -1604,7 +1713,8 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep,
if (s_pkt)
break;
if ((event->status & DEPEVT_STATUS_LST) &&
- (trb->ctrl & DWC3_TRB_CTRL_LST))
+ (trb->ctrl & (DWC3_TRB_CTRL_LST |
+ DWC3_TRB_CTRL_HWO)))
break;
if ((event->status & DEPEVT_STATUS_IOC) &&
(trb->ctrl & DWC3_TRB_CTRL_IOC))
@@ -1657,65 +1767,6 @@ static void dwc3_endpoint_transfer_complete(struct dwc3 *dwc,
}
}
-static void dwc3_gadget_start_isoc(struct dwc3 *dwc,
- struct dwc3_ep *dep, const struct dwc3_event_depevt *event)
-{
- u32 uf, mask;
-
- if (list_empty(&dep->request_list)) {
- dev_vdbg(dwc->dev, "ISOC ep %s run out for requests.\n",
- dep->name);
- return;
- }
-
- mask = ~(dep->interval - 1);
- uf = event->parameters & mask;
- /* 4 micro frames in the future */
- uf += dep->interval * 4;
-
- __dwc3_gadget_kick_transfer(dep, uf, 1);
-}
-
-static void dwc3_process_ep_cmd_complete(struct dwc3_ep *dep,
- const struct dwc3_event_depevt *event)
-{
- struct dwc3 *dwc = dep->dwc;
- struct dwc3_event_depevt mod_ev = *event;
-
- /*
- * We were asked to remove one request. It is possible that this
- * request and a few others were started together and have the same
- * transfer index. Since we stopped the complete endpoint we don't
- * know how many requests were already completed (and not yet)
- * reported and how could be done (later). We purge them all until
- * the end of the list.
- */
- mod_ev.status = DEPEVT_STATUS_LST;
- dwc3_cleanup_done_reqs(dwc, dep, &mod_ev, -ESHUTDOWN);
- dep->flags &= ~DWC3_EP_BUSY;
- /* pending requests are ignored and are queued on XferNotReady */
-}
-
-static void dwc3_ep_cmd_compl(struct dwc3_ep *dep,
- const struct dwc3_event_depevt *event)
-{
- u32 param = event->parameters;
- u32 cmd_type = (param >> 8) & ((1 << 5) - 1);
-
- switch (cmd_type) {
- case DWC3_DEPCMD_ENDTRANSFER:
- dwc3_process_ep_cmd_complete(dep, event);
- break;
- case DWC3_DEPCMD_STARTTRANSFER:
- dep->res_trans_idx = param & 0x7f;
- break;
- default:
- printk(KERN_ERR "%s() unknown /unexpected type: %d\n",
- __func__, cmd_type);
- break;
- };
-}
-
static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
const struct dwc3_event_depevt *event)
{
@@ -1724,6 +1775,9 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
dep = dwc->eps[epnum];
+ if (!(dep->flags & DWC3_EP_ENABLED))
+ return;
+
dev_vdbg(dwc->dev, "%s: %s\n", dep->name,
dwc3_ep_event_string(event->endpoint_event));
@@ -1734,7 +1788,7 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
switch (event->endpoint_event) {
case DWC3_DEPEVT_XFERCOMPLETE:
- dep->res_trans_idx = 0;
+ dep->resource_index = 0;
if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
dev_dbg(dwc->dev, "%s is an Isochronous endpoint\n",
@@ -1797,7 +1851,7 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
dev_dbg(dwc->dev, "%s FIFO Overrun\n", dep->name);
break;
case DWC3_DEPEVT_EPCMDCMPLT:
- dwc3_ep_cmd_compl(dep, event);
+ dev_vdbg(dwc->dev, "Endpoint Command Complete\n");
break;
}
}
@@ -1820,16 +1874,16 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum)
dep = dwc->eps[epnum];
- WARN_ON(!dep->res_trans_idx);
- if (dep->res_trans_idx) {
- cmd = DWC3_DEPCMD_ENDTRANSFER;
- cmd |= DWC3_DEPCMD_HIPRI_FORCERM | DWC3_DEPCMD_CMDIOC;
- cmd |= DWC3_DEPCMD_PARAM(dep->res_trans_idx);
- memset(&params, 0, sizeof(params));
- ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, &params);
- WARN_ON_ONCE(ret);
- dep->res_trans_idx = 0;
- }
+ if (!dep->resource_index)
+ return;
+
+ cmd = DWC3_DEPCMD_ENDTRANSFER;
+ cmd |= DWC3_DEPCMD_HIPRI_FORCERM | DWC3_DEPCMD_CMDIOC;
+ cmd |= DWC3_DEPCMD_PARAM(dep->resource_index);
+ memset(&params, 0, sizeof(params));
+ ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, &params);
+ WARN_ON_ONCE(ret);
+ dep->resource_index = 0;
}
static void dwc3_stop_active_transfers(struct dwc3 *dwc)
@@ -1872,11 +1926,9 @@ static void dwc3_clear_stall_all_ep(struct dwc3 *dwc)
static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
{
+ int reg;
+
dev_vdbg(dwc->dev, "%s\n", __func__);
-#if 0
- XXX
- U1/U2 is powersave optimization. Skip it for now. Anyway we need to
- enable it before we can disable it.
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
reg &= ~DWC3_DCTL_INITU1ENA;
@@ -1884,9 +1936,7 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
reg &= ~DWC3_DCTL_INITU2ENA;
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
-#endif
- dwc3_stop_active_transfers(dwc);
dwc3_disconnect_gadget(dwc);
dwc->start_config_issued = false;
@@ -1894,30 +1944,30 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
dwc->setup_packet_pending = false;
}
-static void dwc3_gadget_usb3_phy_power(struct dwc3 *dwc, int on)
+static void dwc3_gadget_usb3_phy_suspend(struct dwc3 *dwc, int suspend)
{
u32 reg;
reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
- if (on)
- reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
- else
+ if (suspend)
reg |= DWC3_GUSB3PIPECTL_SUSPHY;
+ else
+ reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
}
-static void dwc3_gadget_usb2_phy_power(struct dwc3 *dwc, int on)
+static void dwc3_gadget_usb2_phy_suspend(struct dwc3 *dwc, int suspend)
{
u32 reg;
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
- if (on)
- reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
- else
+ if (suspend)
reg |= DWC3_GUSB2PHYCFG_SUSPHY;
+ else
+ reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
}
@@ -1962,16 +2012,18 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
/* after reset -> Default State */
dwc->dev_state = DWC3_DEFAULT_STATE;
- /* Enable PHYs */
- dwc3_gadget_usb2_phy_power(dwc, true);
- dwc3_gadget_usb3_phy_power(dwc, true);
+ /* Recent versions support automatic phy suspend and don't need this */
+ if (dwc->revision < DWC3_REVISION_194A) {
+ /* Resume PHYs */
+ dwc3_gadget_usb2_phy_suspend(dwc, false);
+ dwc3_gadget_usb3_phy_suspend(dwc, false);
+ }
if (dwc->gadget.speed != USB_SPEED_UNKNOWN)
dwc3_disconnect_gadget(dwc);
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
reg &= ~DWC3_DCTL_TSTCTRL_MASK;
- reg &= ~(DWC3_DCTL_INITU1ENA | DWC3_DCTL_INITU2ENA);
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
dwc->test_mode = false;
@@ -2010,16 +2062,16 @@ static void dwc3_update_ram_clk_sel(struct dwc3 *dwc, u32 speed)
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
}
-static void dwc3_gadget_disable_phy(struct dwc3 *dwc, u8 speed)
+static void dwc3_gadget_phy_suspend(struct dwc3 *dwc, u8 speed)
{
switch (speed) {
case USB_SPEED_SUPER:
- dwc3_gadget_usb2_phy_power(dwc, false);
+ dwc3_gadget_usb2_phy_suspend(dwc, true);
break;
case USB_SPEED_HIGH:
case USB_SPEED_FULL:
case USB_SPEED_LOW:
- dwc3_gadget_usb3_phy_power(dwc, false);
+ dwc3_gadget_usb3_phy_suspend(dwc, true);
break;
}
}
@@ -2082,8 +2134,11 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
break;
}
- /* Disable unneded PHY */
- dwc3_gadget_disable_phy(dwc, dwc->gadget.speed);
+ /* Recent versions support automatic phy suspend and don't need this */
+ if (dwc->revision < DWC3_REVISION_194A) {
+ /* Suspend unneeded PHY */
+ dwc3_gadget_phy_suspend(dwc, dwc->gadget.speed);
+ }
dep = dwc->eps[0];
ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL);
@@ -2373,10 +2428,6 @@ int __devinit dwc3_gadget_init(struct dwc3 *dwc)
reg |= DWC3_DCFG_LPM_CAP;
dwc3_writel(dwc->regs, DWC3_DCFG, reg);
- reg = dwc3_readl(dwc->regs, DWC3_DCTL);
- reg |= DWC3_DCTL_ACCEPTU1ENA | DWC3_DCTL_ACCEPTU2ENA;
- dwc3_writel(dwc->regs, DWC3_DCTL, reg);
-
/* Enable all but Start and End of Frame IRQs */
reg = (DWC3_DEVTEN_VNDRDEVTSTRCVEDEN |
DWC3_DEVTEN_EVNTOVERFLOWEN |
@@ -2389,6 +2440,24 @@ int __devinit dwc3_gadget_init(struct dwc3 *dwc)
DWC3_DEVTEN_DISCONNEVTEN);
dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
+ /* Enable USB2 LPM and automatic phy suspend only on recent versions */
+ if (dwc->revision >= DWC3_REVISION_194A) {
+ reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+ reg |= DWC3_DCFG_LPM_CAP;
+ dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN);
+
+ /* TODO: This should be configurable */
+ reg |= DWC3_DCTL_HIRD_THRES(28);
+
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+
+ dwc3_gadget_usb2_phy_suspend(dwc, false);
+ dwc3_gadget_usb3_phy_suspend(dwc, false);
+ }
+
ret = device_register(&dwc->gadget.dev);
if (ret) {
dev_err(dwc->dev, "failed to register gadget device\n");