USB: EHCI: fix counting of transaction error retries
[pandora-kernel.git] / drivers / usb / host / ehci-sched.c
index b7853c8..e813ca8 100644 (file)
@@ -437,6 +437,9 @@ static int enable_periodic (struct ehci_hcd *ehci)
        u32     cmd;
        int     status;
 
+       if (ehci->periodic_sched++)
+               return 0;
+
        /* did clearing PSE did take effect yet?
         * takes effect only at frame boundaries...
         */
@@ -461,6 +464,9 @@ static int disable_periodic (struct ehci_hcd *ehci)
        u32     cmd;
        int     status;
 
+       if (--ehci->periodic_sched)
+               return 0;
+
        /* did setting PSE not take effect yet?
         * takes effect only at frame boundaries...
         */
@@ -536,6 +542,7 @@ static int qh_link_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh)
                }
        }
        qh->qh_state = QH_STATE_LINKED;
+       qh->xacterrs = 0;
        qh_get (qh);
 
        /* update per-qh bandwidth for usbfs */
@@ -544,13 +551,10 @@ static int qh_link_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh)
                : (qh->usecs * 8);
 
        /* maybe enable periodic schedule processing */
-       if (!ehci->periodic_sched++)
-               return enable_periodic (ehci);
-
-       return 0;
+       return enable_periodic(ehci);
 }
 
-static void qh_unlink_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh)
+static int qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh)
 {
        unsigned        i;
        unsigned        period;
@@ -586,9 +590,7 @@ static void qh_unlink_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh)
        qh_put (qh);
 
        /* maybe turn off periodic schedule */
-       ehci->periodic_sched--;
-       if (!ehci->periodic_sched)
-               (void) disable_periodic (ehci);
+       return disable_periodic(ehci);
 }
 
 static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh)
@@ -917,7 +919,7 @@ iso_stream_init (
                 */
                stream->usecs = HS_USECS_ISO (maxp);
                bandwidth = stream->usecs * 8;
-               bandwidth /= 1 << (interval - 1);
+               bandwidth /= interval;
 
        } else {
                u32             addr;
@@ -950,7 +952,7 @@ iso_stream_init (
                } else
                        stream->raw_mask = smask_out [hs_transfers - 1];
                bandwidth = stream->usecs + stream->c_usecs;
-               bandwidth /= 1 << (interval + 2);
+               bandwidth /= interval << 3;
 
                /* stream->splits gets created from raw_mask later */
                stream->address = cpu_to_hc32(ehci, addr);
@@ -1003,7 +1005,8 @@ iso_stream_put(struct ehci_hcd *ehci, struct ehci_iso_stream *stream)
 
                is_in = (stream->bEndpointAddress & USB_DIR_IN) ? 0x10 : 0;
                stream->bEndpointAddress &= 0x0f;
-               stream->ep->hcpriv = NULL;
+               if (stream->ep)
+                       stream->ep->hcpriv = NULL;
 
                if (stream->rescheduled) {
                        ehci_info (ehci, "ep%d%s-iso rescheduled "
@@ -1534,7 +1537,7 @@ itd_link_urb (
                                        struct ehci_itd, itd_list);
                        list_move_tail (&itd->itd_list, &stream->td_list);
                        itd->stream = iso_stream_get (stream);
-                       itd->urb = usb_get_urb (urb);
+                       itd->urb = urb;
                        itd_init (ehci, stream, itd);
                }
 
@@ -1562,9 +1565,7 @@ itd_link_urb (
        urb->hcpriv = NULL;
 
        timer_action (ehci, TIMER_IO_WATCHDOG);
-       if (unlikely (!ehci->periodic_sched++))
-               return enable_periodic (ehci);
-       return 0;
+       return enable_periodic(ehci);
 }
 
 #define        ISO_ERRS (EHCI_ISOC_BUF_ERR | EHCI_ISOC_BABBLE | EHCI_ISOC_XACTERR)
@@ -1617,11 +1618,14 @@ itd_complete (
                                desc->status = -EPROTO;
 
                        /* HC need not update length with this error */
-                       if (!(t & EHCI_ISOC_BABBLE))
-                               desc->actual_length = EHCI_ITD_LENGTH (t);
+                       if (!(t & EHCI_ISOC_BABBLE)) {
+                               desc->actual_length = EHCI_ITD_LENGTH(t);
+                               urb->actual_length += desc->actual_length;
+                       }
                } else if (likely ((t & EHCI_ISOC_ACTIVE) == 0)) {
                        desc->status = 0;
-                       desc->actual_length = EHCI_ITD_LENGTH (t);
+                       desc->actual_length = EHCI_ITD_LENGTH(t);
+                       urb->actual_length += desc->actual_length;
                } else {
                        /* URB was too late */
                        desc->status = -EXDEV;
@@ -1642,10 +1646,10 @@ itd_complete (
        ehci_urb_done(ehci, urb, 0);
        retval = true;
        urb = NULL;
-       ehci->periodic_sched--;
+       (void) disable_periodic(ehci);
        ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--;
 
-       if (unlikely (list_empty (&stream->td_list))) {
+       if (unlikely(list_is_singular(&stream->td_list))) {
                ehci_to_hcd(ehci)->self.bandwidth_allocated
                                -= stream->bandwidth;
                ehci_vdbg (ehci,
@@ -1654,14 +1658,27 @@ itd_complete (
                        (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out");
        }
        iso_stream_put (ehci, stream);
-       /* OK to recycle this ITD now that its completion callback ran. */
+
 done:
-       usb_put_urb(urb);
        itd->urb = NULL;
-       itd->stream = NULL;
-       list_move(&itd->itd_list, &stream->free_list);
-       iso_stream_put(ehci, stream);
-
+       if (ehci->clock_frame != itd->frame || itd->index[7] != -1) {
+               /* OK to recycle this ITD now. */
+               itd->stream = NULL;
+               list_move(&itd->itd_list, &stream->free_list);
+               iso_stream_put(ehci, stream);
+       } else {
+               /* HW might remember this ITD, so we can't recycle it yet.
+                * Move it to a safe place until a new frame starts.
+                */
+               list_move(&itd->itd_list, &ehci->cached_itd_list);
+               if (stream->refcount == 2) {
+                       /* If iso_stream_put() were called here, stream
+                        * would be freed.  Instead, just prevent reuse.
+                        */
+                       stream->ep->hcpriv = NULL;
+                       stream->ep = NULL;
+               }
+       }
        return retval;
 }
 
@@ -1935,7 +1952,7 @@ sitd_link_urb (
                                struct ehci_sitd, sitd_list);
                list_move_tail (&sitd->sitd_list, &stream->td_list);
                sitd->stream = iso_stream_get (stream);
-               sitd->urb = usb_get_urb (urb);
+               sitd->urb = urb;
 
                sitd_patch(ehci, stream, sitd, sched, packet);
                sitd_link (ehci, (next_uframe >> 3) % ehci->periodic_size,
@@ -1951,9 +1968,7 @@ sitd_link_urb (
        urb->hcpriv = NULL;
 
        timer_action (ehci, TIMER_IO_WATCHDOG);
-       if (!ehci->periodic_sched++)
-               return enable_periodic (ehci);
-       return 0;
+       return enable_periodic(ehci);
 }
 
 /*-------------------------------------------------------------------------*/
@@ -2001,7 +2016,8 @@ sitd_complete (
                        desc->status = -EPROTO;
        } else {
                desc->status = 0;
-               desc->actual_length = desc->length - SITD_LENGTH (t);
+               desc->actual_length = desc->length - SITD_LENGTH(t);
+               urb->actual_length += desc->actual_length;
        }
        stream->depth -= stream->interval << 3;
 
@@ -2019,10 +2035,10 @@ sitd_complete (
        ehci_urb_done(ehci, urb, 0);
        retval = true;
        urb = NULL;
-       ehci->periodic_sched--;
+       (void) disable_periodic(ehci);
        ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--;
 
-       if (list_empty (&stream->td_list)) {
+       if (list_is_singular(&stream->td_list)) {
                ehci_to_hcd(ehci)->self.bandwidth_allocated
                                -= stream->bandwidth;
                ehci_vdbg (ehci,
@@ -2033,7 +2049,6 @@ sitd_complete (
        iso_stream_put (ehci, stream);
        /* OK to recycle this SITD now that its completion callback ran. */
 done:
-       usb_put_urb(urb);
        sitd->urb = NULL;
        sitd->stream = NULL;
        list_move(&sitd->sitd_list, &stream->free_list);
@@ -2104,6 +2119,20 @@ done:
 
 /*-------------------------------------------------------------------------*/
 
+static void free_cached_itd_list(struct ehci_hcd *ehci)
+{
+       struct ehci_itd *itd, *n;
+
+       list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) {
+               struct ehci_iso_stream  *stream = itd->stream;
+               itd->stream = NULL;
+               list_move(&itd->itd_list, &stream->free_list);
+               iso_stream_put(ehci, stream);
+       }
+}
+
+/*-------------------------------------------------------------------------*/
+
 static void
 scan_periodic (struct ehci_hcd *ehci)
 {
@@ -2118,10 +2147,17 @@ scan_periodic (struct ehci_hcd *ehci)
         * Touches as few pages as possible:  cache-friendly.
         */
        now_uframe = ehci->next_uframe;
-       if (HC_IS_RUNNING (ehci_to_hcd(ehci)->state))
+       if (HC_IS_RUNNING(ehci_to_hcd(ehci)->state)) {
                clock = ehci_readl(ehci, &ehci->regs->frame_index);
-       else
+               clock_frame = (clock >> 3) % ehci->periodic_size;
+       } else  {
                clock = now_uframe + mod - 1;
+               clock_frame = -1;
+       }
+       if (ehci->clock_frame != clock_frame) {
+               free_cached_itd_list(ehci);
+               ehci->clock_frame = clock_frame;
+       }
        clock %= mod;
        clock_frame = clock >> 3;
 
@@ -2243,8 +2279,7 @@ restart:
                        if (unlikely (modified)) {
                                if (likely(ehci->periodic_sched > 0))
                                        goto restart;
-                               /* maybe we can short-circuit this scan! */
-                               disable_periodic(ehci);
+                               /* short-circuit this scan */
                                now_uframe = clock;
                                break;
                        }
@@ -2281,6 +2316,10 @@ restart:
                        /* rescan the rest of this frame, then ... */
                        clock = now;
                        clock_frame = clock >> 3;
+                       if (ehci->clock_frame != clock_frame) {
+                               free_cached_itd_list(ehci);
+                               ehci->clock_frame = clock_frame;
+                       }
                } else {
                        now_uframe++;
                        now_uframe %= mod;