USB: Fix NEC OHCI chip silicon bug
authorMichael Hanselmann <linux-kernel@hansmi.ch>
Thu, 31 May 2007 21:34:27 +0000 (23:34 +0200)
committerGreg Kroah-Hartman <gregkh@suse.de>
Thu, 12 Jul 2007 23:34:29 +0000 (16:34 -0700)
This patch fixes a silicon bug in some NEC OHCI chips. The bug appears
at random times and is very, very difficult to reproduce. Without the
following patch, Linux would shut the chip and its associated devices
down. In Apple PowerBooks this leads to an unusable keyboard and mouse
(SSH still working). The idea of restarting the chip is taken from
public Darwin code.

Signed-off-by: Michael Hanselmann <linux-kernel@hansmi.ch>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/ohci-hcd.c
drivers/usb/host/ohci-hub.c
drivers/usb/host/ohci-mem.c
drivers/usb/host/ohci-pci.c
drivers/usb/host/ohci.h

index ce05e5f7bed6bf0da5f59c3c04eb9207e598366c..44717fab7435cebe7afa684bdc044611b18507e7 100644 (file)
@@ -35,6 +35,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/dmapool.h>
 #include <linux/reboot.h>
+#include <linux/workqueue.h>
 
 #include <asm/io.h>
 #include <asm/irq.h>
@@ -82,6 +83,8 @@ static const char     hcd_name [] = "ohci_hcd";
 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"
@@ -651,9 +654,20 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
        }
 
        if (ints & OHCI_INTR_UE) {
-               disable (ohci);
-               ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n");
                // e.g. due to PCI Master/Target Abort
+               if (ohci->flags & OHCI_QUIRK_NEC) {
+                       /* Workaround for a silicon bug in some NEC chips used
+                        * in Apple's PowerBooks. Adapted from Darwin code.
+                        */
+                       ohci_err (ohci, "OHCI Unrecoverable Error, scheduling NEC chip restart\n");
+
+                       ohci_writel (ohci, OHCI_INTR_UE, &regs->intrdisable);
+
+                       schedule_work (&ohci->nec_work);
+               } else {
+                       disable (ohci);
+                       ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n");
+               }
 
                ohci_dump (ohci, 1);
                ohci_usb_reset (ohci);
@@ -755,23 +769,16 @@ static void ohci_stop (struct usb_hcd *hcd)
 /*-------------------------------------------------------------------------*/
 
 /* must not be called from interrupt context */
-
-#ifdef CONFIG_PM
-
 static int ohci_restart (struct ohci_hcd *ohci)
 {
        int temp;
        int i;
        struct urb_priv *priv;
 
-       /* mark any devices gone, so they do nothing till khubd disconnects.
-        * recycle any "live" eds/tds (and urbs) right away.
-        * later, khubd disconnect processing will recycle the other state,
-        * (either as disconnect/reconnect, or maybe someday as a reset).
-        */
        spin_lock_irq(&ohci->lock);
        disable (ohci);
-       usb_root_hub_lost_power(ohci_to_hcd(ohci)->self.root_hub);
+
+       /* Recycle any "live" eds/tds (and urbs). */
        if (!list_empty (&ohci->pending))
                ohci_dbg(ohci, "abort schedule...\n");
        list_for_each_entry (priv, &ohci->pending, pending) {
@@ -822,7 +829,27 @@ static int ohci_restart (struct ohci_hcd *ohci)
        ohci_dbg(ohci, "restart complete\n");
        return 0;
 }
-#endif
+
+/*-------------------------------------------------------------------------*/
+
+/* 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);
+}
 
 /*-------------------------------------------------------------------------*/
 
index bb9cc595219e84cb0582250dc857d9892a503c0a..48e4b11f4d3ea53a2d837636e5ae72de4a0724af 100644 (file)
@@ -55,8 +55,6 @@ static void dl_done_list (struct ohci_hcd *);
 static void finish_unlinks (struct ohci_hcd *, u16);
 
 #ifdef CONFIG_PM
-static int ohci_restart(struct ohci_hcd *ohci);
-
 static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop)
 __releases(ohci->lock)
 __acquires(ohci->lock)
@@ -191,6 +189,9 @@ __acquires(ohci->lock)
                        spin_unlock_irq (&ohci->lock);
                        (void) ohci_init (ohci);
                        status = ohci_restart (ohci);
+
+                       usb_root_hub_lost_power(hcd->self.root_hub);
+
                        spin_lock_irq (&ohci->lock);
                }
                return status;
index 2f20d3dc895b8b4a959459573f4089e2014f27f8..450c7b460c5ab092e62ab13828ec41874f483b95 100644 (file)
@@ -28,6 +28,7 @@ static void ohci_hcd_init (struct ohci_hcd *ohci)
        ohci->next_statechange = jiffies;
        spin_lock_init (&ohci->lock);
        INIT_LIST_HEAD (&ohci->pending);
+       INIT_WORK (&ohci->nec_work, ohci_quirk_nec_worker);
 }
 
 /*-------------------------------------------------------------------------*/
index 15013f4519ad658b1b3f2f581513c283417cd512..a5e2eb85d073bd69cb9e356cffc5f202e489d305 100644 (file)
@@ -111,6 +111,18 @@ static int ohci_quirk_toshiba_scc(struct usb_hcd *hcd)
 #endif
 }
 
+/* Check for NEC chip and apply quirk for allegedly lost interrupts.
+ */
+static int ohci_quirk_nec(struct usb_hcd *hcd)
+{
+       struct ohci_hcd *ohci = hcd_to_ohci (hcd);
+
+       ohci->flags |= OHCI_QUIRK_NEC;
+       ohci_dbg (ohci, "enabled NEC chipset lost interrupt quirk\n");
+
+       return 0;
+}
+
 /* List of quirks for OHCI */
 static const struct pci_device_id ohci_pci_quirks[] = {
        {
@@ -133,6 +145,10 @@ static const struct pci_device_id ohci_pci_quirks[] = {
                PCI_DEVICE(PCI_VENDOR_ID_TOSHIBA_2, 0x01b6),
                .driver_data = (unsigned long)ohci_quirk_toshiba_scc,
        },
+       {
+               PCI_DEVICE(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_USB),
+               .driver_data = (unsigned long)ohci_quirk_nec,
+       },
        {
                /* Toshiba portege 4000 */
                .vendor         = PCI_VENDOR_ID_AL,
index c2b5ecfe5e9fa8ca88b33a28b792dd15e3a43fb8..4ada43cf1387db28fefe811f18f5e02ac098666c 100644 (file)
@@ -397,8 +397,10 @@ struct ohci_hcd {
 #define        OHCI_QUIRK_BE_DESC      0x08                    /* BE descriptors */
 #define        OHCI_QUIRK_BE_MMIO      0x10                    /* BE registers */
 #define        OHCI_QUIRK_ZFMICRO      0x20                    /* Compaq ZFMicro chipset*/
+#define        OHCI_QUIRK_NEC          0x40                    /* lost interrupts */
        // there are also chip quirks/bugs in init logic
 
+       struct work_struct      nec_work;       /* Worker for NEC quirk */
 };
 
 /* convert between an hcd pointer and the corresponding ohci_hcd */