diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2006-09-26 14:46:16 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2006-09-28 18:36:46 -0400 |
commit | 8d1a243ba5dda5c1a3cca5df8fb19ab8b138f074 (patch) | |
tree | c9be9a9d8d4f5477a47bfc5f8a6f612a0c4867ca /drivers/usb/host/ohci-hub.c | |
parent | 1f7e1a3b7e05c833229c4b6e9d3c96262df59e99 (diff) |
OHCI: add auto-stop support
This patch (as790b) adds "autostop" support to ohci-hcd: the driver
will automatically stop the host controller when no devices have been
connected for at least one second. This feature is useful when the
USB autosuspend facility isn't available, such as when
CONFIG_USB_SUSPEND hasn't been set.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/ohci-hub.c')
-rw-r--r-- | drivers/usb/host/ohci-hub.c | 229 |
1 files changed, 156 insertions, 73 deletions
diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index ef4965450de5..f36cbd02736d 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c | |||
@@ -41,31 +41,20 @@ static void ohci_rhsc_enable (struct usb_hcd *hcd) | |||
41 | { | 41 | { |
42 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); | 42 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
43 | 43 | ||
44 | hcd->poll_rh = 0; | ||
45 | ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable); | 44 | ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable); |
46 | } | 45 | } |
47 | 46 | ||
48 | #ifdef CONFIG_PM | ||
49 | |||
50 | #define OHCI_SCHED_ENABLES \ | 47 | #define OHCI_SCHED_ENABLES \ |
51 | (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE) | 48 | (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE) |
52 | 49 | ||
53 | static void dl_done_list (struct ohci_hcd *, struct pt_regs *); | 50 | static void dl_done_list (struct ohci_hcd *, struct pt_regs *); |
54 | static void finish_unlinks (struct ohci_hcd *, u16 , struct pt_regs *); | 51 | static void finish_unlinks (struct ohci_hcd *, u16 , struct pt_regs *); |
55 | static int ohci_restart (struct ohci_hcd *ohci); | ||
56 | 52 | ||
57 | static int ohci_bus_suspend (struct usb_hcd *hcd) | 53 | static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop) |
54 | __releases(ohci->lock) | ||
55 | __acquires(ohci->lock) | ||
58 | { | 56 | { |
59 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); | ||
60 | int status = 0; | 57 | int status = 0; |
61 | unsigned long flags; | ||
62 | |||
63 | spin_lock_irqsave (&ohci->lock, flags); | ||
64 | |||
65 | if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) { | ||
66 | spin_unlock_irqrestore (&ohci->lock, flags); | ||
67 | return -ESHUTDOWN; | ||
68 | } | ||
69 | 58 | ||
70 | ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); | 59 | ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); |
71 | switch (ohci->hc_control & OHCI_CTRL_HCFS) { | 60 | switch (ohci->hc_control & OHCI_CTRL_HCFS) { |
@@ -81,15 +70,16 @@ static int ohci_bus_suspend (struct usb_hcd *hcd) | |||
81 | ohci_dbg (ohci, "needs reinit!\n"); | 70 | ohci_dbg (ohci, "needs reinit!\n"); |
82 | goto done; | 71 | goto done; |
83 | case OHCI_USB_SUSPEND: | 72 | case OHCI_USB_SUSPEND: |
84 | ohci_dbg (ohci, "already suspended\n"); | 73 | if (!ohci->autostop) { |
85 | goto done; | 74 | ohci_dbg (ohci, "already suspended\n"); |
75 | goto done; | ||
76 | } | ||
86 | } | 77 | } |
87 | ohci_dbg (ohci, "suspend root hub\n"); | 78 | ohci_dbg (ohci, "%s root hub\n", |
79 | autostop ? "auto-stop" : "suspend"); | ||
88 | 80 | ||
89 | /* First stop any processing */ | 81 | /* First stop any processing */ |
90 | if (ohci->hc_control & OHCI_SCHED_ENABLES) { | 82 | if (!autostop && (ohci->hc_control & OHCI_SCHED_ENABLES)) { |
91 | int limit; | ||
92 | |||
93 | ohci->hc_control &= ~OHCI_SCHED_ENABLES; | 83 | ohci->hc_control &= ~OHCI_SCHED_ENABLES; |
94 | ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); | 84 | ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); |
95 | ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); | 85 | ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); |
@@ -99,24 +89,17 @@ static int ohci_bus_suspend (struct usb_hcd *hcd) | |||
99 | * then the last WDH could take 6+ msec | 89 | * then the last WDH could take 6+ msec |
100 | */ | 90 | */ |
101 | ohci_dbg (ohci, "stopping schedules ...\n"); | 91 | ohci_dbg (ohci, "stopping schedules ...\n"); |
102 | limit = 2000; | 92 | ohci->autostop = 0; |
103 | while (limit > 0) { | 93 | spin_unlock_irq (&ohci->lock); |
104 | udelay (250); | 94 | msleep (8); |
105 | limit =- 250; | 95 | spin_lock_irq (&ohci->lock); |
106 | if (ohci_readl (ohci, &ohci->regs->intrstatus) | ||
107 | & OHCI_INTR_SF) | ||
108 | break; | ||
109 | } | ||
110 | dl_done_list (ohci, NULL); | ||
111 | mdelay (7); | ||
112 | } | 96 | } |
113 | dl_done_list (ohci, NULL); | 97 | dl_done_list (ohci, NULL); |
114 | finish_unlinks (ohci, ohci_frame_no(ohci), NULL); | 98 | finish_unlinks (ohci, ohci_frame_no(ohci), NULL); |
115 | ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus), | ||
116 | &ohci->regs->intrstatus); | ||
117 | 99 | ||
118 | /* maybe resume can wake root hub */ | 100 | /* maybe resume can wake root hub */ |
119 | if (device_may_wakeup(&ohci_to_hcd(ohci)->self.root_hub->dev)) | 101 | if (device_may_wakeup(&ohci_to_hcd(ohci)->self.root_hub->dev) || |
102 | autostop) | ||
120 | ohci->hc_control |= OHCI_CTRL_RWE; | 103 | ohci->hc_control |= OHCI_CTRL_RWE; |
121 | else { | 104 | else { |
122 | ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable); | 105 | ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable); |
@@ -132,13 +115,12 @@ static int ohci_bus_suspend (struct usb_hcd *hcd) | |||
132 | (void) ohci_readl (ohci, &ohci->regs->control); | 115 | (void) ohci_readl (ohci, &ohci->regs->control); |
133 | 116 | ||
134 | /* no resumes until devices finish suspending */ | 117 | /* no resumes until devices finish suspending */ |
135 | ohci->next_statechange = jiffies + msecs_to_jiffies (5); | 118 | if (!autostop) { |
136 | 119 | ohci->next_statechange = jiffies + msecs_to_jiffies (5); | |
137 | /* no timer polling */ | 120 | ohci->autostop = 0; |
138 | hcd->poll_rh = 0; | 121 | } |
139 | 122 | ||
140 | done: | 123 | done: |
141 | spin_unlock_irqrestore (&ohci->lock, flags); | ||
142 | return status; | 124 | return status; |
143 | } | 125 | } |
144 | 126 | ||
@@ -151,24 +133,16 @@ static inline struct ed *find_head (struct ed *ed) | |||
151 | } | 133 | } |
152 | 134 | ||
153 | /* caller has locked the root hub */ | 135 | /* caller has locked the root hub */ |
154 | static int ohci_bus_resume (struct usb_hcd *hcd) | 136 | static int ohci_rh_resume (struct ohci_hcd *ohci) |
137 | __releases(ohci->lock) | ||
138 | __acquires(ohci->lock) | ||
155 | { | 139 | { |
156 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); | 140 | struct usb_hcd *hcd = ohci_to_hcd (ohci); |
157 | u32 temp, enables; | 141 | u32 temp, enables; |
158 | int status = -EINPROGRESS; | 142 | int status = -EINPROGRESS; |
159 | unsigned long flags; | 143 | int autostopped = ohci->autostop; |
160 | |||
161 | if (time_before (jiffies, ohci->next_statechange)) | ||
162 | msleep(5); | ||
163 | |||
164 | spin_lock_irqsave (&ohci->lock, flags); | ||
165 | |||
166 | if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) { | ||
167 | spin_unlock_irqrestore (&ohci->lock, flags); | ||
168 | return -ESHUTDOWN; | ||
169 | } | ||
170 | |||
171 | 144 | ||
145 | ohci->autostop = 0; | ||
172 | ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); | 146 | ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); |
173 | 147 | ||
174 | if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) { | 148 | if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) { |
@@ -188,7 +162,8 @@ static int ohci_bus_resume (struct usb_hcd *hcd) | |||
188 | ohci->hc_control |= OHCI_USB_RESUME; | 162 | ohci->hc_control |= OHCI_USB_RESUME; |
189 | ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); | 163 | ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); |
190 | (void) ohci_readl (ohci, &ohci->regs->control); | 164 | (void) ohci_readl (ohci, &ohci->regs->control); |
191 | ohci_dbg (ohci, "resume root hub\n"); | 165 | ohci_dbg (ohci, "%s root hub\n", |
166 | autostopped ? "auto-start" : "resume"); | ||
192 | break; | 167 | break; |
193 | case OHCI_USB_RESUME: | 168 | case OHCI_USB_RESUME: |
194 | /* HCFS changes sometime after INTR_RD */ | 169 | /* HCFS changes sometime after INTR_RD */ |
@@ -203,16 +178,26 @@ static int ohci_bus_resume (struct usb_hcd *hcd) | |||
203 | ohci_dbg (ohci, "lost power\n"); | 178 | ohci_dbg (ohci, "lost power\n"); |
204 | status = -EBUSY; | 179 | status = -EBUSY; |
205 | } | 180 | } |
206 | spin_unlock_irqrestore (&ohci->lock, flags); | 181 | #ifdef CONFIG_PM |
207 | if (status == -EBUSY) { | 182 | if (status == -EBUSY) { |
208 | (void) ohci_init (ohci); | 183 | if (!autostopped) { |
209 | return ohci_restart (ohci); | 184 | static int ohci_restart (struct ohci_hcd *ohci); |
185 | |||
186 | spin_unlock_irq (&ohci->lock); | ||
187 | (void) ohci_init (ohci); | ||
188 | status = ohci_restart (ohci); | ||
189 | spin_lock_irq (&ohci->lock); | ||
190 | } | ||
191 | return status; | ||
210 | } | 192 | } |
193 | #endif | ||
211 | if (status != -EINPROGRESS) | 194 | if (status != -EINPROGRESS) |
212 | return status; | 195 | return status; |
196 | if (autostopped) | ||
197 | goto skip_resume; | ||
198 | spin_unlock_irq (&ohci->lock); | ||
213 | 199 | ||
214 | temp = ohci->num_ports; | 200 | temp = ohci->num_ports; |
215 | enables = 0; | ||
216 | while (temp--) { | 201 | while (temp--) { |
217 | u32 stat = ohci_readl (ohci, | 202 | u32 stat = ohci_readl (ohci, |
218 | &ohci->regs->roothub.portstatus [temp]); | 203 | &ohci->regs->roothub.portstatus [temp]); |
@@ -245,17 +230,21 @@ static int ohci_bus_resume (struct usb_hcd *hcd) | |||
245 | /* Sometimes PCI D3 suspend trashes frame timings ... */ | 230 | /* Sometimes PCI D3 suspend trashes frame timings ... */ |
246 | periodic_reinit (ohci); | 231 | periodic_reinit (ohci); |
247 | 232 | ||
233 | /* the following code is executed with ohci->lock held and | ||
234 | * irqs disabled if and only if autostopped is true | ||
235 | */ | ||
236 | |||
237 | skip_resume: | ||
248 | /* interrupts might have been disabled */ | 238 | /* interrupts might have been disabled */ |
249 | ohci_writel (ohci, OHCI_INTR_INIT, &ohci->regs->intrenable); | 239 | ohci_writel (ohci, OHCI_INTR_INIT, &ohci->regs->intrenable); |
250 | if (ohci->ed_rm_list) | 240 | if (ohci->ed_rm_list) |
251 | ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable); | 241 | ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable); |
252 | ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus), | ||
253 | &ohci->regs->intrstatus); | ||
254 | 242 | ||
255 | /* Then re-enable operations */ | 243 | /* Then re-enable operations */ |
256 | ohci_writel (ohci, OHCI_USB_OPER, &ohci->regs->control); | 244 | ohci_writel (ohci, OHCI_USB_OPER, &ohci->regs->control); |
257 | (void) ohci_readl (ohci, &ohci->regs->control); | 245 | (void) ohci_readl (ohci, &ohci->regs->control); |
258 | msleep (3); | 246 | if (!autostopped) |
247 | msleep (3); | ||
259 | 248 | ||
260 | temp = ohci->hc_control; | 249 | temp = ohci->hc_control; |
261 | temp &= OHCI_CTRL_RWC; | 250 | temp &= OHCI_CTRL_RWC; |
@@ -265,7 +254,11 @@ static int ohci_bus_resume (struct usb_hcd *hcd) | |||
265 | (void) ohci_readl (ohci, &ohci->regs->control); | 254 | (void) ohci_readl (ohci, &ohci->regs->control); |
266 | 255 | ||
267 | /* TRSMRCY */ | 256 | /* TRSMRCY */ |
268 | msleep (10); | 257 | if (!autostopped) { |
258 | msleep (10); | ||
259 | spin_lock_irq (&ohci->lock); | ||
260 | } | ||
261 | /* now ohci->lock is always held and irqs are always disabled */ | ||
269 | 262 | ||
270 | /* keep it alive for more than ~5x suspend + resume costs */ | 263 | /* keep it alive for more than ~5x suspend + resume costs */ |
271 | ohci->next_statechange = jiffies + STATECHANGE_DELAY; | 264 | ohci->next_statechange = jiffies + STATECHANGE_DELAY; |
@@ -302,6 +295,45 @@ static int ohci_bus_resume (struct usb_hcd *hcd) | |||
302 | return 0; | 295 | return 0; |
303 | } | 296 | } |
304 | 297 | ||
298 | #ifdef CONFIG_PM | ||
299 | |||
300 | static int ohci_bus_suspend (struct usb_hcd *hcd) | ||
301 | { | ||
302 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); | ||
303 | int rc; | ||
304 | |||
305 | spin_lock_irq (&ohci->lock); | ||
306 | |||
307 | if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) | ||
308 | rc = -ESHUTDOWN; | ||
309 | else | ||
310 | rc = ohci_rh_suspend (ohci, 0); | ||
311 | spin_unlock_irq (&ohci->lock); | ||
312 | return rc; | ||
313 | } | ||
314 | |||
315 | static int ohci_bus_resume (struct usb_hcd *hcd) | ||
316 | { | ||
317 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); | ||
318 | int rc; | ||
319 | |||
320 | if (time_before (jiffies, ohci->next_statechange)) | ||
321 | msleep(5); | ||
322 | |||
323 | spin_lock_irq (&ohci->lock); | ||
324 | |||
325 | if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) | ||
326 | rc = -ESHUTDOWN; | ||
327 | else | ||
328 | rc = ohci_rh_resume (ohci); | ||
329 | spin_unlock_irq (&ohci->lock); | ||
330 | |||
331 | /* poll until we know a device is connected or we autostop */ | ||
332 | if (rc == 0) | ||
333 | usb_hcd_poll_rh_status(hcd); | ||
334 | return rc; | ||
335 | } | ||
336 | |||
305 | #endif /* CONFIG_PM */ | 337 | #endif /* CONFIG_PM */ |
306 | 338 | ||
307 | /*-------------------------------------------------------------------------*/ | 339 | /*-------------------------------------------------------------------------*/ |
@@ -313,17 +345,11 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) | |||
313 | { | 345 | { |
314 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); | 346 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
315 | int i, changed = 0, length = 1; | 347 | int i, changed = 0, length = 1; |
348 | int any_connected = 0, rhsc_enabled = 1; | ||
316 | unsigned long flags; | 349 | unsigned long flags; |
317 | 350 | ||
318 | spin_lock_irqsave (&ohci->lock, flags); | 351 | spin_lock_irqsave (&ohci->lock, flags); |
319 | 352 | ||
320 | /* handle autosuspended root: finish resuming before | ||
321 | * letting khubd or root hub timer see state changes. | ||
322 | */ | ||
323 | if (unlikely((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER | ||
324 | || !HC_IS_RUNNING(hcd->state))) | ||
325 | goto done; | ||
326 | |||
327 | /* undocumented erratum seen on at least rev D */ | 353 | /* undocumented erratum seen on at least rev D */ |
328 | if ((ohci->flags & OHCI_QUIRK_AMD756) | 354 | if ((ohci->flags & OHCI_QUIRK_AMD756) |
329 | && (roothub_a (ohci) & RH_A_NDP) > MAX_ROOT_PORTS) { | 355 | && (roothub_a (ohci) & RH_A_NDP) > MAX_ROOT_PORTS) { |
@@ -347,6 +373,9 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) | |||
347 | for (i = 0; i < ohci->num_ports; i++) { | 373 | for (i = 0; i < ohci->num_ports; i++) { |
348 | u32 status = roothub_portstatus (ohci, i); | 374 | u32 status = roothub_portstatus (ohci, i); |
349 | 375 | ||
376 | /* can't autostop if ports are connected */ | ||
377 | any_connected |= (status & RH_PS_CCS); | ||
378 | |||
350 | if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC | 379 | if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC |
351 | | RH_PS_OCIC | RH_PS_PRSC)) { | 380 | | RH_PS_OCIC | RH_PS_PRSC)) { |
352 | changed = 1; | 381 | changed = 1; |
@@ -354,15 +383,69 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) | |||
354 | buf [0] |= 1 << (i + 1); | 383 | buf [0] |= 1 << (i + 1); |
355 | else | 384 | else |
356 | buf [1] |= 1 << (i - 7); | 385 | buf [1] |= 1 << (i - 7); |
357 | continue; | ||
358 | } | 386 | } |
359 | } | 387 | } |
360 | 388 | ||
361 | /* after root hub changes, stop polling after debouncing | 389 | /* NOTE: vendors didn't always make the same implementation |
362 | * for a while and maybe kicking in autosuspend | 390 | * choices for RHSC. Sometimes it triggers on an edge (like |
391 | * setting and maybe clearing a port status change bit); and | ||
392 | * it's level-triggered on other silicon, active until khubd | ||
393 | * clears all active port status change bits. If it's still | ||
394 | * set (level-triggered) we must disable it and rely on | ||
395 | * polling until khubd re-enables it. | ||
363 | */ | 396 | */ |
364 | if (changed) | 397 | if (ohci_readl (ohci, &ohci->regs->intrstatus) & OHCI_INTR_RHSC) { |
365 | ohci->next_statechange = jiffies + STATECHANGE_DELAY; | 398 | ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable); |
399 | (void) ohci_readl (ohci, &ohci->regs->intrdisable); | ||
400 | rhsc_enabled = 0; | ||
401 | } | ||
402 | hcd->poll_rh = 1; | ||
403 | |||
404 | /* carry out appropriate state changes */ | ||
405 | switch (ohci->hc_control & OHCI_CTRL_HCFS) { | ||
406 | |||
407 | case OHCI_USB_OPER: | ||
408 | /* keep on polling until we know a device is connected | ||
409 | * and RHSC is enabled */ | ||
410 | if (!ohci->autostop) { | ||
411 | if (any_connected) { | ||
412 | if (rhsc_enabled) | ||
413 | hcd->poll_rh = 0; | ||
414 | } else { | ||
415 | ohci->autostop = 1; | ||
416 | ohci->next_statechange = jiffies + HZ; | ||
417 | } | ||
418 | |||
419 | /* if no devices have been attached for one second, autostop */ | ||
420 | } else { | ||
421 | if (changed || any_connected) { | ||
422 | ohci->autostop = 0; | ||
423 | ohci->next_statechange = jiffies + | ||
424 | STATECHANGE_DELAY; | ||
425 | } else if (time_after_eq (jiffies, | ||
426 | ohci->next_statechange) | ||
427 | && !ohci->ed_rm_list | ||
428 | && !(ohci->hc_control & | ||
429 | OHCI_SCHED_ENABLES)) { | ||
430 | ohci_rh_suspend (ohci, 1); | ||
431 | } | ||
432 | } | ||
433 | break; | ||
434 | |||
435 | /* if there is a port change, autostart or ask to be resumed */ | ||
436 | case OHCI_USB_SUSPEND: | ||
437 | case OHCI_USB_RESUME: | ||
438 | if (changed) { | ||
439 | if (ohci->autostop) | ||
440 | ohci_rh_resume (ohci); | ||
441 | else | ||
442 | usb_hcd_resume_root_hub (hcd); | ||
443 | } else { | ||
444 | /* everything is idle, no need for polling */ | ||
445 | hcd->poll_rh = 0; | ||
446 | } | ||
447 | break; | ||
448 | } | ||
366 | 449 | ||
367 | done: | 450 | done: |
368 | spin_unlock_irqrestore (&ohci->lock, flags); | 451 | spin_unlock_irqrestore (&ohci->lock, flags); |