xhci: Handle mid-TD isoc error on the last TD on hosts with missing events

NEC and possible other hosts don't generate a TD completion event if
there already was an error event mid TD.

Tag a host if it has failed to generate the TD completion event after
error, and give back the last TD immediately if it has an error mid
that last TD. If TD is not the last then events for coming TDs will
take care of that pending TD.

Assume that the host won't touch that TD anymore as it hasn't generated
completion events for it earlier. Note, this might be an incorrect

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 9f0f694..2c7803b 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -2408,8 +2408,14 @@
 		if (ep_trb == td->last_trb)
+		/*
+		 * Error mid TD. Wait for last TRB or next TD event
+		 * Only give back the TD here if no more events are expected
+		 */
+		if (xhci->one_event_per_isoc_td &&
+		    list_is_last(&td->td_list, &ep_ring->td_list))
+			break;
-		/* Error mid TD, don't give TD back yet */
 		td->error_mid_td = true;
 		td->urb_length_set = true;
@@ -2862,6 +2868,7 @@
 				if (ep_seg) {
 					/* give back previous TD, start handling new */
 					xhci_dbg(xhci, "Missing TD completion event after mid TD error\n");
+					xhci->one_event_per_isoc_td = 1;
 					ep_ring->dequeue = td->last_trb;
 					ep_ring->deq_seg = td->last_trb_seg;
 					inc_deq(xhci, ep_ring);
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 6f82d40..eba1332 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1894,6 +1894,8 @@
 	unsigned		broken_suspend:1;
 	/* Indicates that omitting hcd is supported if root hub has no ports */
 	unsigned		allow_single_roothub:1;
+	/* No extra event for last TRB in isoc TD if one is sent mid TD  */
+	unsigned		one_event_per_isoc_td:1;
 	/* cached usb2 extened protocol capabilites */
 	u32                     *ext_caps;
 	unsigned int            num_ext_caps;