diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2007-01-16 11:59:45 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-02-07 18:44:37 -0500 |
commit | 57e06c11372eccf5acebdd4664eb025fee76c561 (patch) | |
tree | a792559056735005ce7037196509fb151d3c3bc5 /drivers/usb/host | |
parent | 625b5c9a0069ef1b61feb3ce599b39f1b04b5666 (diff) |
EHCI: force high-speed devices to run at full speed
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>
Diffstat (limited to 'drivers/usb/host')
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hub.c | 107 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 6 |
3 files changed, 114 insertions, 1 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 9ec896218feb..92c62911f574 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c | |||
@@ -388,6 +388,7 @@ static void ehci_stop (struct usb_hcd *hcd) | |||
388 | /* let companion controllers work when we aren't */ | 388 | /* let companion controllers work when we aren't */ |
389 | ehci_writel(ehci, 0, &ehci->regs->configured_flag); | 389 | ehci_writel(ehci, 0, &ehci->regs->configured_flag); |
390 | 390 | ||
391 | remove_companion_file(ehci); | ||
391 | remove_debug_files (ehci); | 392 | remove_debug_files (ehci); |
392 | 393 | ||
393 | /* root hub is shut down separately (first, when possible) */ | 394 | /* root hub is shut down separately (first, when possible) */ |
@@ -563,6 +564,7 @@ static int ehci_run (struct usb_hcd *hcd) | |||
563 | * since the class device isn't created that early. | 564 | * since the class device isn't created that early. |
564 | */ | 565 | */ |
565 | create_debug_files(ehci); | 566 | create_debug_files(ehci); |
567 | create_companion_file(ehci); | ||
566 | 568 | ||
567 | return 0; | 569 | return 0; |
568 | } | 570 | } |
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 076474d95dbf..3cfba69e0767 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c | |||
@@ -190,6 +190,103 @@ static int ehci_bus_resume (struct usb_hcd *hcd) | |||
190 | 190 | ||
191 | /*-------------------------------------------------------------------------*/ | 191 | /*-------------------------------------------------------------------------*/ |
192 | 192 | ||
193 | /* Display the ports dedicated to the companion controller */ | ||
194 | static ssize_t show_companion(struct class_device *class_dev, char *buf) | ||
195 | { | ||
196 | struct ehci_hcd *ehci; | ||
197 | int nports, index, n; | ||
198 | int count = PAGE_SIZE; | ||
199 | char *ptr = buf; | ||
200 | |||
201 | ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev))); | ||
202 | nports = HCS_N_PORTS(ehci->hcs_params); | ||
203 | |||
204 | for (index = 0; index < nports; ++index) { | ||
205 | if (test_bit(index, &ehci->companion_ports)) { | ||
206 | n = scnprintf(ptr, count, "%d\n", index + 1); | ||
207 | ptr += n; | ||
208 | count -= n; | ||
209 | } | ||
210 | } | ||
211 | return ptr - buf; | ||
212 | } | ||
213 | |||
214 | /* | ||
215 | * Dedicate or undedicate a port to the companion controller. | ||
216 | * Syntax is "[-]portnum", where a leading '-' sign means | ||
217 | * return control of the port to the EHCI controller. | ||
218 | */ | ||
219 | static ssize_t store_companion(struct class_device *class_dev, | ||
220 | const char *buf, size_t count) | ||
221 | { | ||
222 | struct ehci_hcd *ehci; | ||
223 | int portnum, new_owner, try; | ||
224 | u32 __iomem *status_reg; | ||
225 | u32 port_status; | ||
226 | |||
227 | ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev))); | ||
228 | new_owner = PORT_OWNER; /* Owned by companion */ | ||
229 | if (sscanf(buf, "%d", &portnum) != 1) | ||
230 | return -EINVAL; | ||
231 | if (portnum < 0) { | ||
232 | portnum = - portnum; | ||
233 | new_owner = 0; /* Owned by EHCI */ | ||
234 | } | ||
235 | if (portnum <= 0 || portnum > HCS_N_PORTS(ehci->hcs_params)) | ||
236 | return -ENOENT; | ||
237 | status_reg = &ehci->regs->port_status[--portnum]; | ||
238 | if (new_owner) | ||
239 | set_bit(portnum, &ehci->companion_ports); | ||
240 | else | ||
241 | clear_bit(portnum, &ehci->companion_ports); | ||
242 | |||
243 | /* | ||
244 | * The controller won't set the OWNER bit if the port is | ||
245 | * enabled, so this loop will sometimes require at least two | ||
246 | * iterations: one to disable the port and one to set OWNER. | ||
247 | */ | ||
248 | |||
249 | for (try = 4; try > 0; --try) { | ||
250 | spin_lock_irq(&ehci->lock); | ||
251 | port_status = ehci_readl(ehci, status_reg); | ||
252 | if ((port_status & PORT_OWNER) == new_owner | ||
253 | || (port_status & (PORT_OWNER | PORT_CONNECT)) | ||
254 | == 0) | ||
255 | try = 0; | ||
256 | else { | ||
257 | port_status ^= PORT_OWNER; | ||
258 | port_status &= ~(PORT_PE | PORT_RWC_BITS); | ||
259 | ehci_writel(ehci, port_status, status_reg); | ||
260 | } | ||
261 | spin_unlock_irq(&ehci->lock); | ||
262 | if (try > 1) | ||
263 | msleep(5); | ||
264 | } | ||
265 | return count; | ||
266 | } | ||
267 | static CLASS_DEVICE_ATTR(companion, 0644, show_companion, store_companion); | ||
268 | |||
269 | static inline void create_companion_file(struct ehci_hcd *ehci) | ||
270 | { | ||
271 | int i; | ||
272 | |||
273 | /* with integrated TT there is no companion! */ | ||
274 | if (!ehci_is_TDI(ehci)) | ||
275 | i = class_device_create_file(ehci_to_hcd(ehci)->self.class_dev, | ||
276 | &class_device_attr_companion); | ||
277 | } | ||
278 | |||
279 | static inline void remove_companion_file(struct ehci_hcd *ehci) | ||
280 | { | ||
281 | /* with integrated TT there is no companion! */ | ||
282 | if (!ehci_is_TDI(ehci)) | ||
283 | class_device_remove_file(ehci_to_hcd(ehci)->self.class_dev, | ||
284 | &class_device_attr_companion); | ||
285 | } | ||
286 | |||
287 | |||
288 | /*-------------------------------------------------------------------------*/ | ||
289 | |||
193 | static int check_reset_complete ( | 290 | static int check_reset_complete ( |
194 | struct ehci_hcd *ehci, | 291 | struct ehci_hcd *ehci, |
195 | int index, | 292 | int index, |
@@ -504,6 +601,16 @@ static int ehci_hub_control ( | |||
504 | ehci_readl(ehci, status_reg)); | 601 | ehci_readl(ehci, status_reg)); |
505 | } | 602 | } |
506 | 603 | ||
604 | /* transfer dedicated ports to the companion hc */ | ||
605 | if ((temp & PORT_CONNECT) && | ||
606 | test_bit(wIndex, &ehci->companion_ports)) { | ||
607 | temp &= ~PORT_RWC_BITS; | ||
608 | temp |= PORT_OWNER; | ||
609 | ehci_writel(ehci, temp, status_reg); | ||
610 | ehci_dbg(ehci, "port %d --> companion\n", wIndex + 1); | ||
611 | temp = ehci_readl(ehci, status_reg); | ||
612 | } | ||
613 | |||
507 | /* | 614 | /* |
508 | * Even if OWNER is set, there's no harm letting khubd | 615 | * Even if OWNER is set, there's no harm letting khubd |
509 | * see the wPortStatus values (they should all be 0 except | 616 | * see the wPortStatus values (they should all be 0 except |
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 3ce7249085d5..ec0da0343be4 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h | |||
@@ -74,7 +74,11 @@ struct ehci_hcd { /* one per controller */ | |||
74 | 74 | ||
75 | /* per root hub port */ | 75 | /* per root hub port */ |
76 | unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; | 76 | unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; |
77 | unsigned long bus_suspended; | 77 | /* bit vectors (one bit per port) */ |
78 | unsigned long bus_suspended; /* which ports were | ||
79 | already suspended at the start of a bus suspend */ | ||
80 | unsigned long companion_ports; /* which ports are | ||
81 | dedicated to the companion controller */ | ||
78 | 82 | ||
79 | /* per-HC memory pools (could be per-bus, but ...) */ | 83 | /* per-HC memory pools (could be per-bus, but ...) */ |
80 | struct dma_pool *qh_pool; /* qh per active urb */ | 84 | struct dma_pool *qh_pool; /* qh per active urb */ |