diff options
author | Michael Hanselmann <linux-kernel@hansmi.ch> | 2007-05-31 17:34:27 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-07-12 19:34:29 -0400 |
commit | d576bb9f2769b315a795f77f0c33322a976add7a (patch) | |
tree | 56d59afa4d1e96b519e81434bd09598f76bf1fee /drivers/usb/host/ohci-hcd.c | |
parent | 020363384adfb02f26c1c038a127ed3da3d5cf37 (diff) |
USB: Fix NEC OHCI chip silicon bug
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>
Diffstat (limited to 'drivers/usb/host/ohci-hcd.c')
-rw-r--r-- | drivers/usb/host/ohci-hcd.c | 51 |
1 files changed, 39 insertions, 12 deletions
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index ce05e5f7bed6..44717fab7435 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c | |||
@@ -35,6 +35,7 @@ | |||
35 | #include <linux/dma-mapping.h> | 35 | #include <linux/dma-mapping.h> |
36 | #include <linux/dmapool.h> | 36 | #include <linux/dmapool.h> |
37 | #include <linux/reboot.h> | 37 | #include <linux/reboot.h> |
38 | #include <linux/workqueue.h> | ||
38 | 39 | ||
39 | #include <asm/io.h> | 40 | #include <asm/io.h> |
40 | #include <asm/irq.h> | 41 | #include <asm/irq.h> |
@@ -82,6 +83,8 @@ static const char hcd_name [] = "ohci_hcd"; | |||
82 | static void ohci_dump (struct ohci_hcd *ohci, int verbose); | 83 | static void ohci_dump (struct ohci_hcd *ohci, int verbose); |
83 | static int ohci_init (struct ohci_hcd *ohci); | 84 | static int ohci_init (struct ohci_hcd *ohci); |
84 | static void ohci_stop (struct usb_hcd *hcd); | 85 | static void ohci_stop (struct usb_hcd *hcd); |
86 | static int ohci_restart (struct ohci_hcd *ohci); | ||
87 | static void ohci_quirk_nec_worker (struct work_struct *work); | ||
85 | 88 | ||
86 | #include "ohci-hub.c" | 89 | #include "ohci-hub.c" |
87 | #include "ohci-dbg.c" | 90 | #include "ohci-dbg.c" |
@@ -651,9 +654,20 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd) | |||
651 | } | 654 | } |
652 | 655 | ||
653 | if (ints & OHCI_INTR_UE) { | 656 | if (ints & OHCI_INTR_UE) { |
654 | disable (ohci); | ||
655 | ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n"); | ||
656 | // e.g. due to PCI Master/Target Abort | 657 | // e.g. due to PCI Master/Target Abort |
658 | if (ohci->flags & OHCI_QUIRK_NEC) { | ||
659 | /* Workaround for a silicon bug in some NEC chips used | ||
660 | * in Apple's PowerBooks. Adapted from Darwin code. | ||
661 | */ | ||
662 | ohci_err (ohci, "OHCI Unrecoverable Error, scheduling NEC chip restart\n"); | ||
663 | |||
664 | ohci_writel (ohci, OHCI_INTR_UE, ®s->intrdisable); | ||
665 | |||
666 | schedule_work (&ohci->nec_work); | ||
667 | } else { | ||
668 | disable (ohci); | ||
669 | ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n"); | ||
670 | } | ||
657 | 671 | ||
658 | ohci_dump (ohci, 1); | 672 | ohci_dump (ohci, 1); |
659 | ohci_usb_reset (ohci); | 673 | ohci_usb_reset (ohci); |
@@ -755,23 +769,16 @@ static void ohci_stop (struct usb_hcd *hcd) | |||
755 | /*-------------------------------------------------------------------------*/ | 769 | /*-------------------------------------------------------------------------*/ |
756 | 770 | ||
757 | /* must not be called from interrupt context */ | 771 | /* must not be called from interrupt context */ |
758 | |||
759 | #ifdef CONFIG_PM | ||
760 | |||
761 | static int ohci_restart (struct ohci_hcd *ohci) | 772 | static int ohci_restart (struct ohci_hcd *ohci) |
762 | { | 773 | { |
763 | int temp; | 774 | int temp; |
764 | int i; | 775 | int i; |
765 | struct urb_priv *priv; | 776 | struct urb_priv *priv; |
766 | 777 | ||
767 | /* mark any devices gone, so they do nothing till khubd disconnects. | ||
768 | * recycle any "live" eds/tds (and urbs) right away. | ||
769 | * later, khubd disconnect processing will recycle the other state, | ||
770 | * (either as disconnect/reconnect, or maybe someday as a reset). | ||
771 | */ | ||
772 | spin_lock_irq(&ohci->lock); | 778 | spin_lock_irq(&ohci->lock); |
773 | disable (ohci); | 779 | disable (ohci); |
774 | usb_root_hub_lost_power(ohci_to_hcd(ohci)->self.root_hub); | 780 | |
781 | /* Recycle any "live" eds/tds (and urbs). */ | ||
775 | if (!list_empty (&ohci->pending)) | 782 | if (!list_empty (&ohci->pending)) |
776 | ohci_dbg(ohci, "abort schedule...\n"); | 783 | ohci_dbg(ohci, "abort schedule...\n"); |
777 | list_for_each_entry (priv, &ohci->pending, pending) { | 784 | list_for_each_entry (priv, &ohci->pending, pending) { |
@@ -822,7 +829,27 @@ static int ohci_restart (struct ohci_hcd *ohci) | |||
822 | ohci_dbg(ohci, "restart complete\n"); | 829 | ohci_dbg(ohci, "restart complete\n"); |
823 | return 0; | 830 | return 0; |
824 | } | 831 | } |
825 | #endif | 832 | |
833 | /*-------------------------------------------------------------------------*/ | ||
834 | |||
835 | /* NEC workaround */ | ||
836 | static void ohci_quirk_nec_worker(struct work_struct *work) | ||
837 | { | ||
838 | struct ohci_hcd *ohci = container_of(work, struct ohci_hcd, nec_work); | ||
839 | int status; | ||
840 | |||
841 | status = ohci_init(ohci); | ||
842 | if (status != 0) { | ||
843 | ohci_err(ohci, "Restarting NEC controller failed " | ||
844 | "in ohci_init, %d\n", status); | ||
845 | return; | ||
846 | } | ||
847 | |||
848 | status = ohci_restart(ohci); | ||
849 | if (status != 0) | ||
850 | ohci_err(ohci, "Restarting NEC controller failed " | ||
851 | "in ohci_restart, %d\n", status); | ||
852 | } | ||
826 | 853 | ||
827 | /*-------------------------------------------------------------------------*/ | 854 | /*-------------------------------------------------------------------------*/ |
828 | 855 | ||