aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/host/ohci-hcd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/ohci-hcd.c')
-rw-r--r--drivers/usb/host/ohci-hcd.c234
1 files changed, 180 insertions, 54 deletions
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 6edf4097d2d..240c7f50754 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -81,7 +81,6 @@ static void ohci_dump (struct ohci_hcd *ohci, int verbose);
static int ohci_init (struct ohci_hcd *ohci);
static void ohci_stop (struct usb_hcd *hcd);
static int ohci_restart (struct ohci_hcd *ohci);
-static void ohci_quirk_nec_worker (struct work_struct *work);
#include "ohci-hub.c"
#include "ohci-dbg.c"
@@ -118,7 +117,6 @@ MODULE_PARM_DESC (no_handshake, "true (not default) disables BIOS handshake");
*/
static int ohci_urb_enqueue (
struct usb_hcd *hcd,
- struct usb_host_endpoint *ep,
struct urb *urb,
gfp_t mem_flags
) {
@@ -131,11 +129,11 @@ static int ohci_urb_enqueue (
int retval = 0;
#ifdef OHCI_VERBOSE_DEBUG
- urb_print (urb, "SUB", usb_pipein (pipe));
+ urb_print(urb, "SUB", usb_pipein(pipe), -EINPROGRESS);
#endif
/* every endpoint has a ed, locate and maybe (re)initialize it */
- if (! (ed = ed_get (ohci, ep, urb->dev, pipe, urb->interval)))
+ if (! (ed = ed_get (ohci, urb->ep, urb->dev, pipe, urb->interval)))
return -ENOMEM;
/* for the private part of the URB we need the number of TDs (size) */
@@ -200,22 +198,17 @@ static int ohci_urb_enqueue (
retval = -ENODEV;
goto fail;
}
-
- /* in case of unlink-during-submit */
- spin_lock (&urb->lock);
- if (urb->status != -EINPROGRESS) {
- spin_unlock (&urb->lock);
- urb->hcpriv = urb_priv;
- finish_urb (ohci, urb);
- retval = 0;
+ retval = usb_hcd_link_urb_to_ep(hcd, urb);
+ if (retval)
goto fail;
- }
/* schedule the ed if needed */
if (ed->state == ED_IDLE) {
retval = ed_schedule (ohci, ed);
- if (retval < 0)
- goto fail0;
+ if (retval < 0) {
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ goto fail;
+ }
if (ed->type == PIPE_ISOCHRONOUS) {
u16 frame = ohci_frame_no(ohci);
@@ -239,8 +232,6 @@ static int ohci_urb_enqueue (
urb->hcpriv = urb_priv;
td_submit_urb (ohci, urb);
-fail0:
- spin_unlock (&urb->lock);
fail:
if (retval)
urb_free_priv (ohci, urb_priv);
@@ -249,22 +240,26 @@ fail:
}
/*
- * decouple the URB from the HC queues (TDs, urb_priv); it's
- * already marked using urb->status. reporting is always done
+ * decouple the URB from the HC queues (TDs, urb_priv).
+ * reporting is always done
* asynchronously, and we might be dealing with an urb that's
* partially transferred, or an ED with other urbs being unlinked.
*/
-static int ohci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
+static int ohci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
unsigned long flags;
+ int rc;
#ifdef OHCI_VERBOSE_DEBUG
- urb_print (urb, "UNLINK", 1);
+ urb_print(urb, "UNLINK", 1, status);
#endif
spin_lock_irqsave (&ohci->lock, flags);
- if (HC_IS_RUNNING(hcd->state)) {
+ rc = usb_hcd_check_unlink_urb(hcd, urb, status);
+ if (rc) {
+ ; /* Do nothing */
+ } else if (HC_IS_RUNNING(hcd->state)) {
urb_priv_t *urb_priv;
/* Unless an IRQ completed the unlink while it was being
@@ -282,10 +277,10 @@ static int ohci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
* any more ... just clean up every urb's memory.
*/
if (urb->hcpriv)
- finish_urb (ohci, urb);
+ finish_urb(ohci, urb, status);
}
spin_unlock_irqrestore (&ohci->lock, flags);
- return 0;
+ return rc;
}
/*-------------------------------------------------------------------------*/
@@ -314,6 +309,8 @@ rescan:
if (!HC_IS_RUNNING (hcd->state)) {
sanitize:
ed->state = ED_IDLE;
+ if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
+ ohci->eds_scheduled--;
finish_unlinks (ohci, 0);
}
@@ -321,7 +318,12 @@ sanitize:
case ED_UNLINK: /* wait for hw to finish? */
/* major IRQ delivery trouble loses INTR_SF too... */
if (limit-- == 0) {
- ohci_warn (ohci, "IRQ INTR_SF lossage\n");
+ ohci_warn(ohci, "ED unlink timeout\n");
+ if (quirk_zfmicro(ohci)) {
+ ohci_warn(ohci, "Attempting ZF TD recovery\n");
+ ohci->ed_to_check = ed;
+ ohci->zf_delay = 2;
+ }
goto sanitize;
}
spin_unlock_irqrestore (&ohci->lock, flags);
@@ -379,6 +381,93 @@ ohci_shutdown (struct usb_hcd *hcd)
(void) ohci_readl (ohci, &ohci->regs->control);
}
+static int check_ed(struct ohci_hcd *ohci, struct ed *ed)
+{
+ return (hc32_to_cpu(ohci, ed->hwINFO) & ED_IN) != 0
+ && (hc32_to_cpu(ohci, ed->hwHeadP) & TD_MASK)
+ == (hc32_to_cpu(ohci, ed->hwTailP) & TD_MASK)
+ && !list_empty(&ed->td_list);
+}
+
+/* ZF Micro watchdog timer callback. The ZF Micro chipset sometimes completes
+ * an interrupt TD but neglects to add it to the donelist. On systems with
+ * this chipset, we need to periodically check the state of the queues to look
+ * for such "lost" TDs.
+ */
+static void unlink_watchdog_func(unsigned long _ohci)
+{
+ long flags;
+ unsigned max;
+ unsigned seen_count = 0;
+ unsigned i;
+ struct ed **seen = NULL;
+ struct ohci_hcd *ohci = (struct ohci_hcd *) _ohci;
+
+ spin_lock_irqsave(&ohci->lock, flags);
+ max = ohci->eds_scheduled;
+ if (!max)
+ goto done;
+
+ if (ohci->ed_to_check)
+ goto out;
+
+ seen = kcalloc(max, sizeof *seen, GFP_ATOMIC);
+ if (!seen)
+ goto out;
+
+ for (i = 0; i < NUM_INTS; i++) {
+ struct ed *ed = ohci->periodic[i];
+
+ while (ed) {
+ unsigned temp;
+
+ /* scan this branch of the periodic schedule tree */
+ for (temp = 0; temp < seen_count; temp++) {
+ if (seen[temp] == ed) {
+ /* we've checked it and what's after */
+ ed = NULL;
+ break;
+ }
+ }
+ if (!ed)
+ break;
+ seen[seen_count++] = ed;
+ if (!check_ed(ohci, ed)) {
+ ed = ed->ed_next;
+ continue;
+ }
+
+ /* HC's TD list is empty, but HCD sees at least one
+ * TD that's not been sent through the donelist.
+ */
+ ohci->ed_to_check = ed;
+ ohci->zf_delay = 2;
+
+ /* The HC may wait until the next frame to report the
+ * TD as done through the donelist and INTR_WDH. (We
+ * just *assume* it's not a multi-TD interrupt URB;
+ * those could defer the IRQ more than one frame, using
+ * DI...) Check again after the next INTR_SF.
+ */
+ ohci_writel(ohci, OHCI_INTR_SF,
+ &ohci->regs->intrstatus);
+ ohci_writel(ohci, OHCI_INTR_SF,
+ &ohci->regs->intrenable);
+
+ /* flush those writes */
+ (void) ohci_readl(ohci, &ohci->regs->control);
+
+ goto out;
+ }
+ }
+out:
+ kfree(seen);
+ if (ohci->eds_scheduled)
+ mod_timer(&ohci->unlink_watchdog, round_jiffies_relative(HZ));
+done:
+ spin_unlock_irqrestore(&ohci->lock, flags);
+}
+
/*-------------------------------------------------------------------------*
* HC functions
*-------------------------------------------------------------------------*/
@@ -616,6 +705,15 @@ retry:
mdelay ((temp >> 23) & 0x1fe);
hcd->state = HC_STATE_RUNNING;
+ if (quirk_zfmicro(ohci)) {
+ /* Create timer to watch for bad queue state on ZF Micro */
+ setup_timer(&ohci->unlink_watchdog, unlink_watchdog_func,
+ (unsigned long) ohci);
+
+ ohci->eds_scheduled = 0;
+ ohci->ed_to_check = NULL;
+ }
+
ohci_dump (ohci, 1);
return 0;
@@ -629,10 +727,11 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
struct ohci_regs __iomem *regs = ohci->regs;
- int ints;
+ int ints;
/* we can eliminate a (slow) ohci_readl()
- if _only_ WDH caused this irq */
+ * if _only_ WDH caused this irq
+ */
if ((ohci->hcca->done_head != 0)
&& ! (hc32_to_cpup (ohci, &ohci->hcca->done_head)
& 0x01)) {
@@ -651,7 +750,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
if (ints & OHCI_INTR_UE) {
// e.g. due to PCI Master/Target Abort
- if (ohci->flags & OHCI_QUIRK_NEC) {
+ if (quirk_nec(ohci)) {
/* Workaround for a silicon bug in some NEC chips used
* in Apple's PowerBooks. Adapted from Darwin code.
*/
@@ -713,6 +812,31 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
ohci_writel (ohci, OHCI_INTR_WDH, &regs->intrenable);
}
+ if (quirk_zfmicro(ohci) && (ints & OHCI_INTR_SF)) {
+ spin_lock(&ohci->lock);
+ if (ohci->ed_to_check) {
+ struct ed *ed = ohci->ed_to_check;
+
+ if (check_ed(ohci, ed)) {
+ /* HC thinks the TD list is empty; HCD knows
+ * at least one TD is outstanding
+ */
+ if (--ohci->zf_delay == 0) {
+ struct td *td = list_entry(
+ ed->td_list.next,
+ struct td, td_list);
+ ohci_warn(ohci,
+ "Reclaiming orphan TD %p\n",
+ td);
+ takeback_td(ohci, td);
+ ohci->ed_to_check = NULL;
+ }
+ } else
+ ohci->ed_to_check = NULL;
+ }
+ spin_unlock(&ohci->lock);
+ }
+
/* could track INTR_SO to reduce available PCI/... bandwidth */
/* handle any pending URB/ED unlinks, leaving INTR_SF enabled
@@ -721,7 +845,9 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
spin_lock (&ohci->lock);
if (ohci->ed_rm_list)
finish_unlinks (ohci, ohci_frame_no(ohci));
- if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list
+ if ((ints & OHCI_INTR_SF) != 0
+ && !ohci->ed_rm_list
+ && !ohci->ed_to_check
&& HC_IS_RUNNING(hcd->state))
ohci_writel (ohci, OHCI_INTR_SF, &regs->intrdisable);
spin_unlock (&ohci->lock);
@@ -751,6 +877,9 @@ static void ohci_stop (struct usb_hcd *hcd)
free_irq(hcd->irq, hcd);
hcd->irq = -1;
+ if (quirk_zfmicro(ohci))
+ del_timer(&ohci->unlink_watchdog);
+
remove_debug_files (ohci);
ohci_mem_cleanup (ohci);
if (ohci->hcca) {
@@ -798,9 +927,8 @@ static int ohci_restart (struct ohci_hcd *ohci)
ed, ed->state);
}
- spin_lock (&urb->lock);
- urb->status = -ESHUTDOWN;
- spin_unlock (&urb->lock);
+ if (!urb->unlinked)
+ urb->unlinked = -ESHUTDOWN;
}
finish_unlinks (ohci, 0);
spin_unlock_irq(&ohci->lock);
@@ -828,27 +956,6 @@ static int ohci_restart (struct ohci_hcd *ohci)
/*-------------------------------------------------------------------------*/
-/* NEC workaround */
-static void ohci_quirk_nec_worker(struct work_struct *work)
-{
- struct ohci_hcd *ohci = container_of(work, struct ohci_hcd, nec_work);
- int status;
-
- status = ohci_init(ohci);
- if (status != 0) {
- ohci_err(ohci, "Restarting NEC controller failed "
- "in ohci_init, %d\n", status);
- return;
- }
-
- status = ohci_restart(ohci);
- if (status != 0)
- ohci_err(ohci, "Restarting NEC controller failed "
- "in ohci_restart, %d\n", status);
-}
-
-/*-------------------------------------------------------------------------*/
-
#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC
MODULE_AUTHOR (DRIVER_AUTHOR);
@@ -926,11 +1033,17 @@ MODULE_LICENSE ("GPL");
#define PS3_SYSTEM_BUS_DRIVER ps3_ohci_driver
#endif
+#ifdef CONFIG_USB_OHCI_HCD_SSB
+#include "ohci-ssb.c"
+#define SSB_OHCI_DRIVER ssb_ohci_driver
+#endif
+
#if !defined(PCI_DRIVER) && \
!defined(PLATFORM_DRIVER) && \
!defined(OF_PLATFORM_DRIVER) && \
!defined(SA1111_DRIVER) && \
- !defined(PS3_SYSTEM_BUS_DRIVER)
+ !defined(PS3_SYSTEM_BUS_DRIVER) && \
+ !defined(SSB_OHCI_DRIVER)
#error "missing bus glue for ohci-hcd"
#endif
@@ -975,10 +1088,20 @@ static int __init ohci_hcd_mod_init(void)
goto error_pci;
#endif
+#ifdef SSB_OHCI_DRIVER
+ retval = ssb_driver_register(&SSB_OHCI_DRIVER);
+ if (retval)
+ goto error_ssb;
+#endif
+
return retval;
/* Error path */
+#ifdef SSB_OHCI_DRIVER
+ error_ssb:
+#endif
#ifdef PCI_DRIVER
+ pci_unregister_driver(&PCI_DRIVER);
error_pci:
#endif
#ifdef SA1111_DRIVER
@@ -1003,6 +1126,9 @@ module_init(ohci_hcd_mod_init);
static void __exit ohci_hcd_mod_exit(void)
{
+#ifdef SSB_OHCI_DRIVER
+ ssb_driver_unregister(&SSB_OHCI_DRIVER);
+#endif
#ifdef PCI_DRIVER
pci_unregister_driver(&PCI_DRIVER);
#endif