diff options
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 13 | ||||
-rw-r--r-- | drivers/usb/host/ehci-q.c | 68 | ||||
-rw-r--r-- | drivers/usb/host/ehci-timer.c | 49 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 5 |
4 files changed, 86 insertions, 49 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index fd7ae16f77be..21d6fbc0a327 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c | |||
@@ -95,7 +95,6 @@ static const char hcd_name [] = "ehci_hcd"; | |||
95 | 95 | ||
96 | #define EHCI_IAA_MSECS 10 /* arbitrary */ | 96 | #define EHCI_IAA_MSECS 10 /* arbitrary */ |
97 | #define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ | 97 | #define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ |
98 | #define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ | ||
99 | #define EHCI_SHRINK_JIFFIES (DIV_ROUND_UP(HZ, 200) + 1) | 98 | #define EHCI_SHRINK_JIFFIES (DIV_ROUND_UP(HZ, 200) + 1) |
100 | /* 5-ms async qh unlink delay */ | 99 | /* 5-ms async qh unlink delay */ |
101 | 100 | ||
@@ -137,7 +136,7 @@ timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) | |||
137 | * SHRINK were pending, OFF would never be requested. | 136 | * SHRINK were pending, OFF would never be requested. |
138 | */ | 137 | */ |
139 | if (timer_pending(&ehci->watchdog) | 138 | if (timer_pending(&ehci->watchdog) |
140 | && ((BIT(TIMER_ASYNC_SHRINK) | BIT(TIMER_ASYNC_OFF)) | 139 | && (BIT(TIMER_ASYNC_SHRINK) |
141 | & ehci->actions)) | 140 | & ehci->actions)) |
142 | return; | 141 | return; |
143 | 142 | ||
@@ -150,9 +149,6 @@ timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) | |||
150 | return; | 149 | return; |
151 | t = EHCI_IO_JIFFIES; | 150 | t = EHCI_IO_JIFFIES; |
152 | break; | 151 | break; |
153 | case TIMER_ASYNC_OFF: | ||
154 | t = EHCI_ASYNC_JIFFIES; | ||
155 | break; | ||
156 | /* case TIMER_ASYNC_SHRINK: */ | 152 | /* case TIMER_ASYNC_SHRINK: */ |
157 | default: | 153 | default: |
158 | t = EHCI_SHRINK_JIFFIES; | 154 | t = EHCI_SHRINK_JIFFIES; |
@@ -376,10 +372,6 @@ static void ehci_watchdog(unsigned long param) | |||
376 | 372 | ||
377 | spin_lock_irqsave(&ehci->lock, flags); | 373 | spin_lock_irqsave(&ehci->lock, flags); |
378 | 374 | ||
379 | /* stop async processing after it's idled a bit */ | ||
380 | if (test_bit (TIMER_ASYNC_OFF, &ehci->actions)) | ||
381 | start_unlink_async (ehci, ehci->async); | ||
382 | |||
383 | /* ehci could run by timer, without IRQs ... */ | 375 | /* ehci could run by timer, without IRQs ... */ |
384 | ehci_work (ehci); | 376 | ehci_work (ehci); |
385 | 377 | ||
@@ -470,7 +462,8 @@ static void ehci_work (struct ehci_hcd *ehci) | |||
470 | if (ehci->scanning) | 462 | if (ehci->scanning) |
471 | return; | 463 | return; |
472 | ehci->scanning = 1; | 464 | ehci->scanning = 1; |
473 | scan_async (ehci); | 465 | if (ehci->async_count) |
466 | scan_async(ehci); | ||
474 | if (ehci->next_uframe != -1) | 467 | if (ehci->next_uframe != -1) |
475 | scan_periodic (ehci); | 468 | scan_periodic (ehci); |
476 | ehci->scanning = 0; | 469 | ehci->scanning = 0; |
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 285d5a0f3f70..d68764ef4476 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c | |||
@@ -964,6 +964,30 @@ done: | |||
964 | 964 | ||
965 | /*-------------------------------------------------------------------------*/ | 965 | /*-------------------------------------------------------------------------*/ |
966 | 966 | ||
967 | static void enable_async(struct ehci_hcd *ehci) | ||
968 | { | ||
969 | if (ehci->async_count++) | ||
970 | return; | ||
971 | |||
972 | /* Stop waiting to turn off the async schedule */ | ||
973 | ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_DISABLE_ASYNC); | ||
974 | |||
975 | /* Don't start the schedule until ASS is 0 */ | ||
976 | ehci_poll_ASS(ehci); | ||
977 | } | ||
978 | |||
979 | static void disable_async(struct ehci_hcd *ehci) | ||
980 | { | ||
981 | if (--ehci->async_count) | ||
982 | return; | ||
983 | |||
984 | /* The async schedule and async_unlink list are supposed to be empty */ | ||
985 | WARN_ON(ehci->async->qh_next.qh || ehci->async_unlink); | ||
986 | |||
987 | /* Don't turn off the schedule until ASS is 1 */ | ||
988 | ehci_poll_ASS(ehci); | ||
989 | } | ||
990 | |||
967 | /* move qh (and its qtds) onto async queue; maybe enable queue. */ | 991 | /* move qh (and its qtds) onto async queue; maybe enable queue. */ |
968 | 992 | ||
969 | static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) | 993 | static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) |
@@ -977,24 +1001,11 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) | |||
977 | 1001 | ||
978 | WARN_ON(qh->qh_state != QH_STATE_IDLE); | 1002 | WARN_ON(qh->qh_state != QH_STATE_IDLE); |
979 | 1003 | ||
980 | /* (re)start the async schedule? */ | ||
981 | head = ehci->async; | ||
982 | timer_action_done (ehci, TIMER_ASYNC_OFF); | ||
983 | if (!head->qh_next.qh) { | ||
984 | if (!(ehci->command & CMD_ASE)) { | ||
985 | /* in case a clear of CMD_ASE didn't take yet */ | ||
986 | (void)handshake(ehci, &ehci->regs->status, | ||
987 | STS_ASS, 0, 150); | ||
988 | ehci->command |= CMD_ASE; | ||
989 | ehci_writel(ehci, ehci->command, &ehci->regs->command); | ||
990 | /* posted write need not be known to HC yet ... */ | ||
991 | } | ||
992 | } | ||
993 | |||
994 | /* clear halt and/or toggle; and maybe recover from silicon quirk */ | 1004 | /* clear halt and/or toggle; and maybe recover from silicon quirk */ |
995 | qh_refresh(ehci, qh); | 1005 | qh_refresh(ehci, qh); |
996 | 1006 | ||
997 | /* splice right after start */ | 1007 | /* splice right after start */ |
1008 | head = ehci->async; | ||
998 | qh->qh_next = head->qh_next; | 1009 | qh->qh_next = head->qh_next; |
999 | qh->hw->hw_next = head->hw->hw_next; | 1010 | qh->hw->hw_next = head->hw->hw_next; |
1000 | wmb (); | 1011 | wmb (); |
@@ -1005,6 +1016,8 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) | |||
1005 | qh->xacterrs = 0; | 1016 | qh->xacterrs = 0; |
1006 | qh->qh_state = QH_STATE_LINKED; | 1017 | qh->qh_state = QH_STATE_LINKED; |
1007 | /* qtd completions reported later by interrupt */ | 1018 | /* qtd completions reported later by interrupt */ |
1019 | |||
1020 | enable_async(ehci); | ||
1008 | } | 1021 | } |
1009 | 1022 | ||
1010 | /*-------------------------------------------------------------------------*/ | 1023 | /*-------------------------------------------------------------------------*/ |
@@ -1173,16 +1186,10 @@ static void end_unlink_async (struct ehci_hcd *ehci) | |||
1173 | 1186 | ||
1174 | qh_completions (ehci, qh); | 1187 | qh_completions (ehci, qh); |
1175 | 1188 | ||
1176 | if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) { | 1189 | if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) |
1177 | qh_link_async (ehci, qh); | 1190 | qh_link_async (ehci, qh); |
1178 | } else { | 1191 | |
1179 | /* it's not free to turn the async schedule on/off; leave it | 1192 | disable_async(ehci); |
1180 | * active but idle for a while once it empties. | ||
1181 | */ | ||
1182 | if (ehci->rh_state == EHCI_RH_RUNNING | ||
1183 | && ehci->async->qh_next.qh == NULL) | ||
1184 | timer_action (ehci, TIMER_ASYNC_OFF); | ||
1185 | } | ||
1186 | 1193 | ||
1187 | if (next) { | 1194 | if (next) { |
1188 | ehci->async_unlink = NULL; | 1195 | ehci->async_unlink = NULL; |
@@ -1210,21 +1217,6 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) | |||
1210 | BUG (); | 1217 | BUG (); |
1211 | #endif | 1218 | #endif |
1212 | 1219 | ||
1213 | /* stop async schedule right now? */ | ||
1214 | if (unlikely (qh == ehci->async)) { | ||
1215 | /* can't get here without STS_ASS set */ | ||
1216 | if (ehci->rh_state != EHCI_RH_HALTED | ||
1217 | && !ehci->async_unlink) { | ||
1218 | /* ... and CMD_IAAD clear */ | ||
1219 | ehci->command &= ~CMD_ASE; | ||
1220 | ehci_writel(ehci, ehci->command, &ehci->regs->command); | ||
1221 | wmb (); | ||
1222 | // handshake later, if we need to | ||
1223 | timer_action_done (ehci, TIMER_ASYNC_OFF); | ||
1224 | } | ||
1225 | return; | ||
1226 | } | ||
1227 | |||
1228 | qh->qh_state = QH_STATE_UNLINK; | 1220 | qh->qh_state = QH_STATE_UNLINK; |
1229 | ehci->async_unlink = qh; | 1221 | ehci->async_unlink = qh; |
1230 | if (!qh->unlink_next) | 1222 | if (!qh->unlink_next) |
diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index ecd3296157c6..1e907dd3bb1b 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c | |||
@@ -67,8 +67,10 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit) | |||
67 | * the event types indexed by enum ehci_hrtimer_event in ehci.h. | 67 | * the event types indexed by enum ehci_hrtimer_event in ehci.h. |
68 | */ | 68 | */ |
69 | static unsigned event_delays_ns[] = { | 69 | static unsigned event_delays_ns[] = { |
70 | 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */ | ||
70 | 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ | 71 | 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ |
71 | 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ | 72 | 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ |
73 | 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ | ||
72 | }; | 74 | }; |
73 | 75 | ||
74 | /* Enable a pending hrtimer event */ | 76 | /* Enable a pending hrtimer event */ |
@@ -91,6 +93,51 @@ static void ehci_enable_event(struct ehci_hcd *ehci, unsigned event, | |||
91 | } | 93 | } |
92 | 94 | ||
93 | 95 | ||
96 | /* Poll the STS_ASS status bit; see when it agrees with CMD_ASE */ | ||
97 | static void ehci_poll_ASS(struct ehci_hcd *ehci) | ||
98 | { | ||
99 | unsigned actual, want; | ||
100 | |||
101 | /* Don't enable anything if the controller isn't running (e.g., died) */ | ||
102 | if (ehci->rh_state != EHCI_RH_RUNNING) | ||
103 | return; | ||
104 | |||
105 | want = (ehci->command & CMD_ASE) ? STS_ASS : 0; | ||
106 | actual = ehci_readl(ehci, &ehci->regs->status) & STS_ASS; | ||
107 | |||
108 | if (want != actual) { | ||
109 | |||
110 | /* Poll again later, but give up after about 20 ms */ | ||
111 | if (ehci->ASS_poll_count++ < 20) { | ||
112 | ehci_enable_event(ehci, EHCI_HRTIMER_POLL_ASS, true); | ||
113 | return; | ||
114 | } | ||
115 | ehci_warn(ehci, "Waited too long for the async schedule status, giving up\n"); | ||
116 | } | ||
117 | ehci->ASS_poll_count = 0; | ||
118 | |||
119 | /* The status is up-to-date; restart or stop the schedule as needed */ | ||
120 | if (want == 0) { /* Stopped */ | ||
121 | if (ehci->async_count > 0) | ||
122 | ehci_set_command_bit(ehci, CMD_ASE); | ||
123 | |||
124 | } else { /* Running */ | ||
125 | if (ehci->async_count == 0) { | ||
126 | |||
127 | /* Turn off the schedule after a while */ | ||
128 | ehci_enable_event(ehci, EHCI_HRTIMER_DISABLE_ASYNC, | ||
129 | true); | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | |||
134 | /* Turn off the async schedule after a brief delay */ | ||
135 | static void ehci_disable_ASE(struct ehci_hcd *ehci) | ||
136 | { | ||
137 | ehci_clear_command_bit(ehci, CMD_ASE); | ||
138 | } | ||
139 | |||
140 | |||
94 | /* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */ | 141 | /* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */ |
95 | static void ehci_poll_PSS(struct ehci_hcd *ehci) | 142 | static void ehci_poll_PSS(struct ehci_hcd *ehci) |
96 | { | 143 | { |
@@ -151,8 +198,10 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci) | |||
151 | * enum ehci_hrtimer_event in ehci.h. | 198 | * enum ehci_hrtimer_event in ehci.h. |
152 | */ | 199 | */ |
153 | static void (*event_handlers[])(struct ehci_hcd *) = { | 200 | static void (*event_handlers[])(struct ehci_hcd *) = { |
201 | ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */ | ||
154 | ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ | 202 | ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ |
155 | ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ | 203 | ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ |
204 | ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ | ||
156 | }; | 205 | }; |
157 | 206 | ||
158 | static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) | 207 | static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) |
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index da2e0ab23850..bf06bbb77ba4 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h | |||
@@ -79,8 +79,10 @@ enum ehci_rh_state { | |||
79 | * ehci-timer.c) in parallel with this list. | 79 | * ehci-timer.c) in parallel with this list. |
80 | */ | 80 | */ |
81 | enum ehci_hrtimer_event { | 81 | enum ehci_hrtimer_event { |
82 | EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */ | ||
82 | EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ | 83 | EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ |
83 | EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ | 84 | EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ |
85 | EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ | ||
84 | EHCI_HRTIMER_NUM_EVENTS /* Must come last */ | 86 | EHCI_HRTIMER_NUM_EVENTS /* Must come last */ |
85 | }; | 87 | }; |
86 | #define EHCI_HRTIMER_NO_EVENT 99 | 88 | #define EHCI_HRTIMER_NO_EVENT 99 |
@@ -93,6 +95,7 @@ struct ehci_hcd { /* one per controller */ | |||
93 | struct hrtimer hrtimer; | 95 | struct hrtimer hrtimer; |
94 | 96 | ||
95 | int PSS_poll_count; | 97 | int PSS_poll_count; |
98 | int ASS_poll_count; | ||
96 | 99 | ||
97 | /* glue to PCI and HCD framework */ | 100 | /* glue to PCI and HCD framework */ |
98 | struct ehci_caps __iomem *caps; | 101 | struct ehci_caps __iomem *caps; |
@@ -110,6 +113,7 @@ struct ehci_hcd { /* one per controller */ | |||
110 | struct ehci_qh *async_unlink_last; | 113 | struct ehci_qh *async_unlink_last; |
111 | struct ehci_qh *qh_scan_next; | 114 | struct ehci_qh *qh_scan_next; |
112 | unsigned scanning : 1; | 115 | unsigned scanning : 1; |
116 | unsigned async_count; /* async activity count */ | ||
113 | 117 | ||
114 | /* periodic schedule support */ | 118 | /* periodic schedule support */ |
115 | #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ | 119 | #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ |
@@ -229,7 +233,6 @@ static inline void iaa_watchdog_done(struct ehci_hcd *ehci) | |||
229 | enum ehci_timer_action { | 233 | enum ehci_timer_action { |
230 | TIMER_IO_WATCHDOG, | 234 | TIMER_IO_WATCHDOG, |
231 | TIMER_ASYNC_SHRINK, | 235 | TIMER_ASYNC_SHRINK, |
232 | TIMER_ASYNC_OFF, | ||
233 | }; | 236 | }; |
234 | 237 | ||
235 | static inline void | 238 | static inline void |