fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / usb / host / uhci-hcd.c
index 4151f61..e0d4c23 100644 (file)
@@ -40,6 +40,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/usb.h>
 #include <linux/bitops.h>
+#include <linux/dmi.h>
 
 #include <asm/uaccess.h>
 #include <asm/io.h>
@@ -59,6 +60,11 @@ Randy Dunlap, Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber, \
 Alan Stern"
 #define DRIVER_DESC "USB Universal Host Controller Interface driver"
 
+/* for flakey hardware, ignore overcurrent indicators */
+static int ignore_oc;
+module_param(ignore_oc, bool, S_IRUGO);
+MODULE_PARM_DESC(ignore_oc, "ignore hardware overcurrent indications");
+
 /*
  * debug = 0, no debugging messages
  * debug = 1, dump failed URBs except for stalls
@@ -80,7 +86,7 @@ MODULE_PARM_DESC(debug, "Debug level");
 static char *errbuf;
 #define ERRBUF_LEN    (32 * 1024)
 
-static kmem_cache_t *uhci_up_cachep;   /* urb_priv */
+static struct kmem_cache *uhci_up_cachep;      /* urb_priv */
 
 static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state);
 static void wakeup_rh(struct uhci_hcd *uhci);
@@ -168,6 +174,11 @@ static int resume_detect_interrupts_are_broken(struct uhci_hcd *uhci)
 {
        int port;
 
+       /* If we have to ignore overcurrent events then almost by definition
+        * we can't depend on resume-detect interrupts. */
+       if (ignore_oc)
+               return 1;
+
        switch (to_pci_dev(uhci_dev(uhci))->vendor) {
            default:
                break;
@@ -196,12 +207,34 @@ static int resume_detect_interrupts_are_broken(struct uhci_hcd *uhci)
        return 0;
 }
 
+static int remote_wakeup_is_broken(struct uhci_hcd *uhci)
+{
+       int port;
+       char *sys_info;
+       static char bad_Asus_board[] = "A7V8X";
+
+       /* One of Asus's motherboards has a bug which causes it to
+        * wake up immediately from suspend-to-RAM if any of the ports
+        * are connected.  In such cases we will not set EGSM.
+        */
+       sys_info = dmi_get_system_info(DMI_BOARD_NAME);
+       if (sys_info && !strcmp(sys_info, bad_Asus_board)) {
+               for (port = 0; port < uhci->rh_numports; ++port) {
+                       if (inw(uhci->io_addr + USBPORTSC1 + port * 2) &
+                                       USBPORTSC_CCS)
+                               return 1;
+               }
+       }
+
+       return 0;
+}
+
 static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state)
 __releases(uhci->lock)
 __acquires(uhci->lock)
 {
        int auto_stop;
-       int int_enable;
+       int int_enable, egsm_enable;
 
        auto_stop = (new_state == UHCI_RH_AUTO_STOPPED);
        dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,
@@ -217,15 +250,20 @@ __acquires(uhci->lock)
        }
 
        /* Enable resume-detect interrupts if they work.
-        * Then enter Global Suspend mode, still configured.
+        * Then enter Global Suspend mode if _it_ works, still configured.
         */
+       egsm_enable = USBCMD_EGSM;
        uhci->working_RD = 1;
        int_enable = USBINTR_RESUME;
-       if (resume_detect_interrupts_are_broken(uhci)) {
+       if (remote_wakeup_is_broken(uhci))
+               egsm_enable = 0;
+       if (resume_detect_interrupts_are_broken(uhci) || !egsm_enable ||
+                       !device_may_wakeup(
+                               &uhci_to_hcd(uhci)->self.root_hub->dev))
                uhci->working_RD = int_enable = 0;
-       }
+
        outw(int_enable, uhci->io_addr + USBINTR);
-       outw(USBCMD_EGSM | USBCMD_CF, uhci->io_addr + USBCMD);
+       outw(egsm_enable | USBCMD_CF, uhci->io_addr + USBCMD);
        mb();
        udelay(5);
 
@@ -252,7 +290,7 @@ __acquires(uhci->lock)
        uhci->is_stopped = UHCI_IS_STOPPED;
        uhci_to_hcd(uhci)->poll_rh = !int_enable;
 
-       uhci_scan_schedule(uhci, NULL);
+       uhci_scan_schedule(uhci);
        uhci_fsbr_off(uhci);
 }
 
@@ -309,7 +347,7 @@ __acquires(uhci->lock)
        mod_timer(&uhci_to_hcd(uhci)->rh_timer, jiffies);
 }
 
-static irqreturn_t uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs)
+static irqreturn_t uhci_irq(struct usb_hcd *hcd)
 {
        struct uhci_hcd *uhci = hcd_to_uhci(hcd);
        unsigned short status;
@@ -358,7 +396,7 @@ static irqreturn_t uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs)
                usb_hcd_poll_rh_status(hcd);
        else {
                spin_lock_irqsave(&uhci->lock, flags);
-               uhci_scan_schedule(uhci, regs);
+               uhci_scan_schedule(uhci);
                spin_unlock_irqrestore(&uhci->lock, flags);
        }
 
@@ -671,7 +709,7 @@ static void uhci_stop(struct usb_hcd *hcd)
        spin_lock_irq(&uhci->lock);
        if (test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) && !uhci->dead)
                uhci_hc_died(uhci);
-       uhci_scan_schedule(uhci, NULL);
+       uhci_scan_schedule(uhci);
        spin_unlock_irq(&uhci->lock);
 
        del_timer_sync(&uhci->fsbr_timer);
@@ -734,6 +772,10 @@ static int uhci_suspend(struct usb_hcd *hcd, pm_message_t message)
 
        /* FIXME: Enable non-PME# remote wakeup? */
 
+       /* make sure snapshot being resumed re-enumerates everything */
+       if (message.event == PM_EVENT_PRETHAW)
+               uhci_hc_died(uhci);
+
 done_okay:
        clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
 done:
@@ -883,7 +925,8 @@ static int __init uhci_hcd_init(void)
 {
        int retval = -ENOMEM;
 
-       printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION "\n");
+       printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION "%s\n",
+                       ignore_oc ? ", overcurrent ignored" : "");
 
        if (usb_disabled())
                return -ENODEV;
@@ -909,8 +952,7 @@ static int __init uhci_hcd_init(void)
        return 0;
 
 init_failed:
-       if (kmem_cache_destroy(uhci_up_cachep))
-               warn("not all urb_privs were freed!");
+       kmem_cache_destroy(uhci_up_cachep);
 
 up_failed:
        debugfs_remove(uhci_debugfs_root);
@@ -926,10 +968,7 @@ errbuf_failed:
 static void __exit uhci_hcd_cleanup(void) 
 {
        pci_unregister_driver(&uhci_pci_driver);
-       
-       if (kmem_cache_destroy(uhci_up_cachep))
-               warn("not all urb_privs were freed!");
-
+       kmem_cache_destroy(uhci_up_cachep);
        debugfs_remove(uhci_debugfs_root);
        kfree(errbuf);
 }