summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/dwc3/gadget.c72
1 files changed, 39 insertions, 33 deletions
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 76c383eecfd3..0387b94b8f94 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -947,7 +947,7 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
dma_addr_t dma, unsigned int length, unsigned int chain,
unsigned int node, unsigned int stream_id,
unsigned int short_not_ok, unsigned int no_interrupt,
- unsigned int is_last)
+ unsigned int is_last, bool must_interrupt)
{
struct dwc3 *dwc = dep->dwc;
struct usb_gadget *gadget = dwc->gadget;
@@ -1034,7 +1034,7 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI;
}
- if ((!no_interrupt && !chain) ||
+ if ((!no_interrupt && !chain) || must_interrupt ||
(dwc3_calc_trbs_left(dep) == 1))
trb->ctrl |= DWC3_TRB_CTRL_IOC;
@@ -1061,10 +1061,12 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
* @chain: should this TRB be chained to the next?
* @node: only for isochronous endpoints. First TRB needs different type.
* @use_bounce_buffer: set to use bounce buffer
+ * @must_interrupt: set to interrupt on TRB completion
*/
static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
struct dwc3_request *req, unsigned int trb_length,
- unsigned int chain, unsigned int node, bool use_bounce_buffer)
+ unsigned int chain, unsigned int node, bool use_bounce_buffer,
+ bool must_interrupt)
{
struct dwc3_trb *trb;
dma_addr_t dma;
@@ -1091,7 +1093,21 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
req->num_trbs++;
__dwc3_prepare_one_trb(dep, trb, dma, trb_length, chain, node,
- stream_id, short_not_ok, no_interrupt, is_last);
+ stream_id, short_not_ok, no_interrupt, is_last,
+ must_interrupt);
+}
+
+static bool dwc3_needs_extra_trb(struct dwc3_ep *dep, struct dwc3_request *req)
+{
+ unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc);
+ unsigned int rem = req->request.length % maxp;
+
+ if ((req->request.length && req->request.zero && !rem &&
+ !usb_endpoint_xfer_isoc(dep->endpoint.desc)) ||
+ (!req->direction && rem))
+ return true;
+
+ return false;
}
/**
@@ -1111,9 +1127,7 @@ static int dwc3_prepare_last_sg(struct dwc3_ep *dep,
unsigned int rem = req->request.length % maxp;
unsigned int num_trbs = 1;
- if ((req->request.length && req->request.zero && !rem &&
- !usb_endpoint_xfer_isoc(dep->endpoint.desc)) ||
- (!req->direction && rem))
+ if (dwc3_needs_extra_trb(dep, req))
num_trbs++;
if (dwc3_calc_trbs_left(dep) < num_trbs)
@@ -1124,13 +1138,13 @@ static int dwc3_prepare_last_sg(struct dwc3_ep *dep,
/* Prepare a normal TRB */
if (req->direction || req->request.length)
dwc3_prepare_one_trb(dep, req, entry_length,
- req->needs_extra_trb, node, false);
+ req->needs_extra_trb, node, false, false);
/* Prepare extra TRBs for ZLP and MPS OUT transfer alignment */
if ((!req->direction && !req->request.length) || req->needs_extra_trb)
dwc3_prepare_one_trb(dep, req,
req->direction ? 0 : maxp - rem,
- false, 1, true);
+ false, 1, true, false);
return num_trbs;
}
@@ -1145,6 +1159,7 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
unsigned int remaining = req->request.num_mapped_sgs
- req->num_queued_sgs;
unsigned int num_trbs = req->num_trbs;
+ bool needs_extra_trb = dwc3_needs_extra_trb(dep, req);
/*
* If we resume preparing the request, then get the remaining length of
@@ -1155,6 +1170,7 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
for_each_sg(sg, s, remaining, i) {
unsigned int trb_length;
+ bool must_interrupt = false;
bool last_sg = false;
trb_length = min_t(unsigned int, length, sg_dma_len(s));
@@ -1176,9 +1192,20 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
if (last_sg) {
if (!dwc3_prepare_last_sg(dep, req, trb_length, i))
- goto out;
+ break;
} else {
- dwc3_prepare_one_trb(dep, req, trb_length, 1, i, false);
+ /*
+ * Look ahead to check if we have enough TRBs for the
+ * last SG entry. If not, set interrupt on this TRB to
+ * resume preparing the last SG entry when more TRBs are
+ * free.
+ */
+ if (needs_extra_trb && dwc3_calc_trbs_left(dep) <= 2 &&
+ sg_dma_len(sg_next(s)) >= length)
+ must_interrupt = true;
+
+ dwc3_prepare_one_trb(dep, req, trb_length, 1, i, false,
+ must_interrupt);
}
/*
@@ -1203,32 +1230,11 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
break;
}
- if (!dwc3_calc_trbs_left(dep))
+ if (!dwc3_calc_trbs_left(dep) || must_interrupt)
break;
}
return req->num_trbs - num_trbs;
-
-out:
- /*
- * If we run out of TRBs for MPS alignment setup, then set IOC on the
- * previous TRB to get notified for TRB completion to resume when more
- * TRBs are available.
- *
- * Note: normally we shouldn't update the TRB after the HWO bit is set.
- * However, the controller doesn't update its internal cache to handle
- * the newly prepared TRBs until UPDATE_TRANSFER or START_TRANSFER
- * command is executed. At this point, it doesn't happen yet, so we
- * should be fine modifying it here.
- */
- if (i) {
- struct dwc3_trb *trb;
-
- trb = dwc3_ep_prev_trb(dep, dep->trb_enqueue);
- trb->ctrl |= DWC3_TRB_CTRL_IOC;
- }
-
- return req->num_trbs - num_trbs;
}
static int dwc3_prepare_trbs_linear(struct dwc3_ep *dep,