diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index c377250c5c9f..23213ecfd590 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -510,6 +510,7 @@ struct dwc3_ep_events { * @number: endpoint number (1 - 15) * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK * @resource_index: Resource transfer index + * @current_uf: Current uf received through last event parameter * @interval: the interval on which the ISOC transfer is started * @name: a human readable name e.g. ep1out-bulk * @direction: true for TX, false for RX @@ -545,6 +546,7 @@ struct dwc3_ep { u8 number; u8 type; u8 resource_index; + u16 current_uf; u32 interval; char name[20]; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 33c16b6596ec..5d69618b5fac 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -354,7 +354,16 @@ int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, dwc3_trace(trace_dwc3_gadget, "Command Complete --> %d", DWC3_DEPCMD_STATUS(reg)); - if (DWC3_DEPCMD_STATUS(reg)) + + /* SW issues START TRANSFER command to isochronous ep + * with future frame interval. If future interval time + * has already passed when core recieves command, core + * will respond with an error(bit13 in Command complete + * event. Hence return error in this case. + */ + if (reg & 0x2000) + return -EAGAIN; + else if (DWC3_DEPCMD_STATUS(reg)) return -EINVAL; return 0; } @@ -978,7 +987,7 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, int start_new) { struct dwc3_gadget_ep_cmd_params params; - struct dwc3_request *req; + struct dwc3_request *req, *req1, *n; struct dwc3 *dwc = dep->dwc; int ret; u32 cmd; @@ -1027,6 +1036,35 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, if (ret < 0) { dev_dbg(dwc->dev, "failed to send STARTTRANSFER command\n"); + if ((ret == -EAGAIN) && start_new && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + /* If bit13 in Command complete event is set, software + * must issue ENDTRANSFER command and wait for + * Xfernotready event to queue the requests again. + */ + if (!dep->resource_index) { + dep->resource_index = + dwc3_gadget_ep_get_transfer_index(dwc, + dep->number); + WARN_ON_ONCE(!dep->resource_index); + } + dwc3_stop_active_transfer(dwc, dep->number, true); + list_for_each_entry_safe_reverse(req1, n, + &dep->req_queued, list) { + req1->trb = NULL; + dwc3_gadget_move_request_list_front(req1); + if (req->request.num_mapped_sgs) + dep->busy_slot += + req->request.num_mapped_sgs; + else + dep->busy_slot++; + if ((dep->busy_slot & DWC3_TRB_MASK) == + DWC3_TRB_NUM - 1) + dep->busy_slot++; + } + return ret; + } + /* * FIXME we need to iterate over the list of requests * here and stop, unmap, free and del each of the linked @@ -1055,6 +1093,8 @@ static void __dwc3_gadget_start_isoc(struct dwc3 *dwc, u32 uf; int ret; + dep->current_uf = cur_uf; + if (list_empty(&dep->request_list)) { dwc3_trace(trace_dwc3_gadget, "ISOC ep %s run out for requests", @@ -1153,10 +1193,17 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) * notion of current microframe. */ if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { - if (list_empty(&dep->req_queued)) { + /* If xfernotready event is recieved before issuing + * START TRANSFER command, don't issue END TRANSFER. + * Rather start queueing the requests by issuing START + * TRANSFER command. + */ + if (list_empty(&dep->req_queued) && dep->resource_index) dwc3_stop_active_transfer(dwc, dep->number, true); - dep->flags = DWC3_EP_ENABLED; - } + else + __dwc3_gadget_start_isoc(dwc, dep, + dep->current_uf); + dep->flags &= ~DWC3_EP_PENDING_REQUEST; return 0; } @@ -2127,18 +2174,17 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && list_empty(&dep->req_queued)) { - if (list_empty(&dep->request_list)) { + if (list_empty(&dep->request_list)) /* * If there is no entry in request list then do * not issue END TRANSFER now. Just set PENDING * flag, so that END TRANSFER is issued when an * entry is added into request list. */ - dep->flags = DWC3_EP_PENDING_REQUEST; - } else { + dep->flags |= DWC3_EP_PENDING_REQUEST; + else dwc3_stop_active_transfer(dwc, dep->number, true); - dep->flags = DWC3_EP_ENABLED; - } + dep->flags &= ~DWC3_EP_MISSED_ISOC; return 1; } diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index bff248cd827a..2938cd6f9c84 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -68,6 +68,14 @@ static inline struct dwc3_request *next_request(struct list_head *list) return list_first_entry(list, struct dwc3_request, list); } +static inline void dwc3_gadget_move_request_list_front(struct dwc3_request *req) +{ + struct dwc3_ep *dep = req->dep; + + req->queued = false; + list_move(&req->list, &dep->request_list); +} + static inline void dwc3_gadget_move_request_queued(struct dwc3_request *req) { struct dwc3_ep *dep = req->dep;