summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/host/ohci-hcd.c36
-rw-r--r--drivers/usb/host/ohci.h1
2 files changed, 35 insertions, 2 deletions
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index aba8f19eae4d..46987735a2e3 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -72,7 +72,7 @@
 static const char	hcd_name [] = "ohci_hcd";
 
 #define	STATECHANGE_DELAY	msecs_to_jiffies(300)
-#define IO_WATCHDOG_DELAY	msecs_to_jiffies(250)
+#define	IO_WATCHDOG_DELAY	msecs_to_jiffies(250)
 
 #include "ohci.h"
 #include "pci-quirks.h"
@@ -230,9 +230,11 @@ static int ohci_urb_enqueue (
 
 		/* Start up the I/O watchdog timer, if it's not running */
 		if (!timer_pending(&ohci->io_watchdog) &&
-				list_empty(&ohci->eds_in_use))
+				list_empty(&ohci->eds_in_use)) {
+			ohci->prev_frame_no = ohci_frame_no(ohci);
 			mod_timer(&ohci->io_watchdog,
 					jiffies + IO_WATCHDOG_DELAY);
+		}
 		list_add(&ed->in_use_list, &ohci->eds_in_use);
 
 		if (ed->type == PIPE_ISOCHRONOUS) {
@@ -727,6 +729,7 @@ static void io_watchdog_func(unsigned long _ohci)
 	u32		head;
 	struct ed	*ed;
 	struct td	*td, *td_start, *td_next;
+	unsigned	frame_no;
 	unsigned long	flags;
 
 	spin_lock_irqsave(&ohci->lock, flags);
@@ -742,6 +745,7 @@ static void io_watchdog_func(unsigned long _ohci)
 	if (!(status & OHCI_INTR_WDH) && ohci->wdh_cnt == ohci->prev_wdh_cnt) {
 		if (ohci->prev_donehead) {
 			ohci_err(ohci, "HcDoneHead not written back; disabled\n");
+ died:
 			usb_hc_died(ohci_to_hcd(ohci));
 			ohci_dump(ohci);
 			ohci_shutdown(ohci_to_hcd(ohci));
@@ -802,7 +806,35 @@ static void io_watchdog_func(unsigned long _ohci)
 	ohci_work(ohci);
 
 	if (ohci->rh_state == OHCI_RH_RUNNING) {
+
+		/*
+		 * Sometimes a controller just stops working.  We can tell
+		 * by checking that the frame counter has advanced since
+		 * the last time we ran.
+		 *
+		 * But be careful: Some controllers violate the spec by
+		 * stopping their frame counter when no ports are active.
+		 */
+		frame_no = ohci_frame_no(ohci);
+		if (frame_no == ohci->prev_frame_no) {
+			int		active_cnt = 0;
+			int		i;
+			unsigned	tmp;
+
+			for (i = 0; i < ohci->num_ports; ++i) {
+				tmp = roothub_portstatus(ohci, i);
+				/* Enabled and not suspended? */
+				if ((tmp & RH_PS_PES) && !(tmp & RH_PS_PSS))
+					++active_cnt;
+			}
+
+			if (active_cnt > 0) {
+				ohci_err(ohci, "frame counter not updating; disabled\n");
+				goto died;
+			}
+		}
 		if (!list_empty(&ohci->eds_in_use)) {
+			ohci->prev_frame_no = frame_no;
 			ohci->prev_wdh_cnt = ohci->wdh_cnt;
 			ohci->prev_donehead = ohci_readl(ohci,
 					&ohci->regs->donehead);
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index 0548f5ca18e2..59f424567a8d 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -421,6 +421,7 @@ struct ohci_hcd {
 
 	// there are also chip quirks/bugs in init logic
 
+	unsigned		prev_frame_no;
 	unsigned		wdh_cnt, prev_wdh_cnt;
 	u32			prev_donehead;
 	struct timer_list	io_watchdog;