EHCI: force high-speed devices to run at full speed
authorAlan Stern <stern@rowland.harvard.edu>
Tue, 16 Jan 2007 16:59:45 +0000 (11:59 -0500)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 7 Feb 2007 23:44:37 +0000 (15:44 -0800)
This patch (as710) adds a sysfs class-device attribute file named
"companion" for EHCI controllers.  The file contains a list of port
numbers that are dedicated to the companion controller; by writing a
port number to the file the user can force a high-speed device
attached directly to the computer to run at full speed.  (As far as I
know it is not possible to do this for a device attached to an
external hub.)  A port is removed from the file by writing the
negative of its port number.

Several users have asked for this facility and it seems like a useful
thing to have.  Every now and then one runs across a device which
behaves much better at full speed than at high speed.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-hub.c
drivers/usb/host/ehci.h

index 9ec896218feb093be530bebe30596a0c75974624..92c62911f57440ce6d4dbe365788d6e2d82c8ff0 100644 (file)
@@ -388,6 +388,7 @@ static void ehci_stop (struct usb_hcd *hcd)
        /* let companion controllers work when we aren't */
        ehci_writel(ehci, 0, &ehci->regs->configured_flag);
 
+       remove_companion_file(ehci);
        remove_debug_files (ehci);
 
        /* root hub is shut down separately (first, when possible) */
@@ -563,6 +564,7 @@ static int ehci_run (struct usb_hcd *hcd)
         * since the class device isn't created that early.
         */
        create_debug_files(ehci);
+       create_companion_file(ehci);
 
        return 0;
 }
index 076474d95dbfd0e8155c71626b30be06ced555dc..3cfba69e0767be172305f49f2181458896c85f69 100644 (file)
@@ -188,6 +188,103 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
 
 #endif /* CONFIG_PM */
 
+/*-------------------------------------------------------------------------*/
+
+/* Display the ports dedicated to the companion controller */
+static ssize_t show_companion(struct class_device *class_dev, char *buf)
+{
+       struct ehci_hcd         *ehci;
+       int                     nports, index, n;
+       int                     count = PAGE_SIZE;
+       char                    *ptr = buf;
+
+       ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev)));
+       nports = HCS_N_PORTS(ehci->hcs_params);
+
+       for (index = 0; index < nports; ++index) {
+               if (test_bit(index, &ehci->companion_ports)) {
+                       n = scnprintf(ptr, count, "%d\n", index + 1);
+                       ptr += n;
+                       count -= n;
+               }
+       }
+       return ptr - buf;
+}
+
+/*
+ * Dedicate or undedicate a port to the companion controller.
+ * Syntax is "[-]portnum", where a leading '-' sign means
+ * return control of the port to the EHCI controller.
+ */
+static ssize_t store_companion(struct class_device *class_dev,
+               const char *buf, size_t count)
+{
+       struct ehci_hcd         *ehci;
+       int                     portnum, new_owner, try;
+       u32 __iomem             *status_reg;
+       u32                     port_status;
+
+       ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev)));
+       new_owner = PORT_OWNER;         /* Owned by companion */
+       if (sscanf(buf, "%d", &portnum) != 1)
+               return -EINVAL;
+       if (portnum < 0) {
+               portnum = - portnum;
+               new_owner = 0;          /* Owned by EHCI */
+       }
+       if (portnum <= 0 || portnum > HCS_N_PORTS(ehci->hcs_params))
+               return -ENOENT;
+       status_reg = &ehci->regs->port_status[--portnum];
+       if (new_owner)
+               set_bit(portnum, &ehci->companion_ports);
+       else
+               clear_bit(portnum, &ehci->companion_ports);
+
+       /*
+        * The controller won't set the OWNER bit if the port is
+        * enabled, so this loop will sometimes require at least two
+        * iterations: one to disable the port and one to set OWNER.
+        */
+
+       for (try = 4; try > 0; --try) {
+               spin_lock_irq(&ehci->lock);
+               port_status = ehci_readl(ehci, status_reg);
+               if ((port_status & PORT_OWNER) == new_owner
+                               || (port_status & (PORT_OWNER | PORT_CONNECT))
+                                       == 0)
+                       try = 0;
+               else {
+                       port_status ^= PORT_OWNER;
+                       port_status &= ~(PORT_PE | PORT_RWC_BITS);
+                       ehci_writel(ehci, port_status, status_reg);
+               }
+               spin_unlock_irq(&ehci->lock);
+               if (try > 1)
+                       msleep(5);
+       }
+       return count;
+}
+static CLASS_DEVICE_ATTR(companion, 0644, show_companion, store_companion);
+
+static inline void create_companion_file(struct ehci_hcd *ehci)
+{
+       int     i;
+
+       /* with integrated TT there is no companion! */
+       if (!ehci_is_TDI(ehci))
+               i = class_device_create_file(ehci_to_hcd(ehci)->self.class_dev,
+                               &class_device_attr_companion);
+}
+
+static inline void remove_companion_file(struct ehci_hcd *ehci)
+{
+       /* with integrated TT there is no companion! */
+       if (!ehci_is_TDI(ehci))
+               class_device_remove_file(ehci_to_hcd(ehci)->self.class_dev,
+                               &class_device_attr_companion);
+}
+
+
 /*-------------------------------------------------------------------------*/
 
 static int check_reset_complete (
@@ -504,6 +601,16 @@ static int ehci_hub_control (
                                        ehci_readl(ehci, status_reg));
                }
 
+               /* transfer dedicated ports to the companion hc */
+               if ((temp & PORT_CONNECT) &&
+                               test_bit(wIndex, &ehci->companion_ports)) {
+                       temp &= ~PORT_RWC_BITS;
+                       temp |= PORT_OWNER;
+                       ehci_writel(ehci, temp, status_reg);
+                       ehci_dbg(ehci, "port %d --> companion\n", wIndex + 1);
+                       temp = ehci_readl(ehci, status_reg);
+               }
+
                /*
                 * Even if OWNER is set, there's no harm letting khubd
                 * see the wPortStatus values (they should all be 0 except
index 3ce7249085d5a8ad31b0bd9f390196d216ad4620..ec0da0343be477868a0549b950afc937cf897d0d 100644 (file)
@@ -74,7 +74,11 @@ struct ehci_hcd {                    /* one per controller */
 
        /* per root hub port */
        unsigned long           reset_done [EHCI_MAX_ROOT_PORTS];
-       unsigned long           bus_suspended;
+       /* bit vectors (one bit per port) */
+       unsigned long           bus_suspended;          /* which ports were
+                       already suspended at the start of a bus suspend */
+       unsigned long           companion_ports;        /* which ports are
+                       dedicated to the companion controller */
 
        /* per-HC memory pools (could be per-bus, but ...) */
        struct dma_pool         *qh_pool;       /* qh per active urb */