diff options
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 196 |
1 files changed, 195 insertions, 1 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index a1a7a9795536..14b48b261e06 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c | |||
@@ -129,6 +129,99 @@ static u32 xhci_port_state_to_neutral(u32 state) | |||
129 | return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); | 129 | return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); |
130 | } | 130 | } |
131 | 131 | ||
132 | /* | ||
133 | * find slot id based on port number. | ||
134 | */ | ||
135 | static int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port) | ||
136 | { | ||
137 | int slot_id; | ||
138 | int i; | ||
139 | |||
140 | slot_id = 0; | ||
141 | for (i = 0; i < MAX_HC_SLOTS; i++) { | ||
142 | if (!xhci->devs[i]) | ||
143 | continue; | ||
144 | if (xhci->devs[i]->port == port) { | ||
145 | slot_id = i; | ||
146 | break; | ||
147 | } | ||
148 | } | ||
149 | |||
150 | return slot_id; | ||
151 | } | ||
152 | |||
153 | /* | ||
154 | * Stop device | ||
155 | * It issues stop endpoint command for EP 0 to 30. And wait the last command | ||
156 | * to complete. | ||
157 | * suspend will set to 1, if suspend bit need to set in command. | ||
158 | */ | ||
159 | static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend) | ||
160 | { | ||
161 | struct xhci_virt_device *virt_dev; | ||
162 | struct xhci_command *cmd; | ||
163 | unsigned long flags; | ||
164 | int timeleft; | ||
165 | int ret; | ||
166 | int i; | ||
167 | |||
168 | ret = 0; | ||
169 | virt_dev = xhci->devs[slot_id]; | ||
170 | cmd = xhci_alloc_command(xhci, false, true, GFP_NOIO); | ||
171 | if (!cmd) { | ||
172 | xhci_dbg(xhci, "Couldn't allocate command structure.\n"); | ||
173 | return -ENOMEM; | ||
174 | } | ||
175 | |||
176 | spin_lock_irqsave(&xhci->lock, flags); | ||
177 | for (i = LAST_EP_INDEX; i > 0; i--) { | ||
178 | if (virt_dev->eps[i].ring && virt_dev->eps[i].ring->dequeue) | ||
179 | xhci_queue_stop_endpoint(xhci, slot_id, i, suspend); | ||
180 | } | ||
181 | cmd->command_trb = xhci->cmd_ring->enqueue; | ||
182 | list_add_tail(&cmd->cmd_list, &virt_dev->cmd_list); | ||
183 | xhci_queue_stop_endpoint(xhci, slot_id, 0, suspend); | ||
184 | xhci_ring_cmd_db(xhci); | ||
185 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
186 | |||
187 | /* Wait for last stop endpoint command to finish */ | ||
188 | timeleft = wait_for_completion_interruptible_timeout( | ||
189 | cmd->completion, | ||
190 | USB_CTRL_SET_TIMEOUT); | ||
191 | if (timeleft <= 0) { | ||
192 | xhci_warn(xhci, "%s while waiting for stop endpoint command\n", | ||
193 | timeleft == 0 ? "Timeout" : "Signal"); | ||
194 | spin_lock_irqsave(&xhci->lock, flags); | ||
195 | /* The timeout might have raced with the event ring handler, so | ||
196 | * only delete from the list if the item isn't poisoned. | ||
197 | */ | ||
198 | if (cmd->cmd_list.next != LIST_POISON1) | ||
199 | list_del(&cmd->cmd_list); | ||
200 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
201 | ret = -ETIME; | ||
202 | goto command_cleanup; | ||
203 | } | ||
204 | |||
205 | command_cleanup: | ||
206 | xhci_free_command(xhci, cmd); | ||
207 | return ret; | ||
208 | } | ||
209 | |||
210 | /* | ||
211 | * Ring device, it rings the all doorbells unconditionally. | ||
212 | */ | ||
213 | static void xhci_ring_device(struct xhci_hcd *xhci, int slot_id) | ||
214 | { | ||
215 | int i; | ||
216 | |||
217 | for (i = 0; i < LAST_EP_INDEX + 1; i++) | ||
218 | if (xhci->devs[slot_id]->eps[i].ring && | ||
219 | xhci->devs[slot_id]->eps[i].ring->dequeue) | ||
220 | xhci_ring_ep_doorbell(xhci, slot_id, i, 0); | ||
221 | |||
222 | return; | ||
223 | } | ||
224 | |||
132 | static void xhci_disable_port(struct xhci_hcd *xhci, u16 wIndex, | 225 | static void xhci_disable_port(struct xhci_hcd *xhci, u16 wIndex, |
133 | u32 __iomem *addr, u32 port_status) | 226 | u32 __iomem *addr, u32 port_status) |
134 | { | 227 | { |
@@ -162,6 +255,10 @@ static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, | |||
162 | status = PORT_PEC; | 255 | status = PORT_PEC; |
163 | port_change_bit = "enable/disable"; | 256 | port_change_bit = "enable/disable"; |
164 | break; | 257 | break; |
258 | case USB_PORT_FEAT_C_SUSPEND: | ||
259 | status = PORT_PLC; | ||
260 | port_change_bit = "suspend/resume"; | ||
261 | break; | ||
165 | default: | 262 | default: |
166 | /* Should never happen */ | 263 | /* Should never happen */ |
167 | return; | 264 | return; |
@@ -182,6 +279,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
182 | u32 temp, status; | 279 | u32 temp, status; |
183 | int retval = 0; | 280 | int retval = 0; |
184 | u32 __iomem *addr; | 281 | u32 __iomem *addr; |
282 | int slot_id; | ||
185 | 283 | ||
186 | ports = HCS_MAX_PORTS(xhci->hcs_params1); | 284 | ports = HCS_MAX_PORTS(xhci->hcs_params1); |
187 | 285 | ||
@@ -211,9 +309,21 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
211 | if ((temp & PORT_OCC)) | 309 | if ((temp & PORT_OCC)) |
212 | status |= USB_PORT_STAT_C_OVERCURRENT << 16; | 310 | status |= USB_PORT_STAT_C_OVERCURRENT << 16; |
213 | /* | 311 | /* |
214 | * FIXME ignoring suspend, reset, and USB 2.1/3.0 specific | 312 | * FIXME ignoring reset and USB 2.1/3.0 specific |
215 | * changes | 313 | * changes |
216 | */ | 314 | */ |
315 | if ((temp & PORT_PLS_MASK) == XDEV_U3 | ||
316 | && (temp & PORT_POWER)) | ||
317 | status |= 1 << USB_PORT_FEAT_SUSPEND; | ||
318 | if ((temp & PORT_PLS_MASK) == XDEV_U0 | ||
319 | && (temp & PORT_POWER) | ||
320 | && (xhci->suspended_ports[wIndex >> 5] & | ||
321 | (1 << (wIndex & 31)))) { | ||
322 | xhci->suspended_ports[wIndex >> 5] &= | ||
323 | ~(1 << (wIndex & 31)); | ||
324 | xhci->port_c_suspend[wIndex >> 5] |= | ||
325 | 1 << (wIndex & 31); | ||
326 | } | ||
217 | if (temp & PORT_CONNECT) { | 327 | if (temp & PORT_CONNECT) { |
218 | status |= USB_PORT_STAT_CONNECTION; | 328 | status |= USB_PORT_STAT_CONNECTION; |
219 | status |= xhci_port_speed(temp); | 329 | status |= xhci_port_speed(temp); |
@@ -226,6 +336,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
226 | status |= USB_PORT_STAT_RESET; | 336 | status |= USB_PORT_STAT_RESET; |
227 | if (temp & PORT_POWER) | 337 | if (temp & PORT_POWER) |
228 | status |= USB_PORT_STAT_POWER; | 338 | status |= USB_PORT_STAT_POWER; |
339 | if (xhci->port_c_suspend[wIndex >> 5] & (1 << (wIndex & 31))) | ||
340 | status |= 1 << USB_PORT_FEAT_C_SUSPEND; | ||
229 | xhci_dbg(xhci, "Get port status returned 0x%x\n", status); | 341 | xhci_dbg(xhci, "Get port status returned 0x%x\n", status); |
230 | put_unaligned(cpu_to_le32(status), (__le32 *) buf); | 342 | put_unaligned(cpu_to_le32(status), (__le32 *) buf); |
231 | break; | 343 | break; |
@@ -238,6 +350,42 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
238 | temp = xhci_readl(xhci, addr); | 350 | temp = xhci_readl(xhci, addr); |
239 | temp = xhci_port_state_to_neutral(temp); | 351 | temp = xhci_port_state_to_neutral(temp); |
240 | switch (wValue) { | 352 | switch (wValue) { |
353 | case USB_PORT_FEAT_SUSPEND: | ||
354 | temp = xhci_readl(xhci, addr); | ||
355 | /* In spec software should not attempt to suspend | ||
356 | * a port unless the port reports that it is in the | ||
357 | * enabled (PED = ‘1’,PLS < ‘3’) state. | ||
358 | */ | ||
359 | if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) | ||
360 | || (temp & PORT_PLS_MASK) >= XDEV_U3) { | ||
361 | xhci_warn(xhci, "USB core suspending device " | ||
362 | "not in U0/U1/U2.\n"); | ||
363 | goto error; | ||
364 | } | ||
365 | |||
366 | slot_id = xhci_find_slot_id_by_port(xhci, wIndex + 1); | ||
367 | if (!slot_id) { | ||
368 | xhci_warn(xhci, "slot_id is zero\n"); | ||
369 | goto error; | ||
370 | } | ||
371 | /* unlock to execute stop endpoint commands */ | ||
372 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
373 | xhci_stop_device(xhci, slot_id, 1); | ||
374 | spin_lock_irqsave(&xhci->lock, flags); | ||
375 | |||
376 | temp = xhci_port_state_to_neutral(temp); | ||
377 | temp &= ~PORT_PLS_MASK; | ||
378 | temp |= PORT_LINK_STROBE | XDEV_U3; | ||
379 | xhci_writel(xhci, temp, addr); | ||
380 | |||
381 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
382 | msleep(10); /* wait device to enter */ | ||
383 | spin_lock_irqsave(&xhci->lock, flags); | ||
384 | |||
385 | temp = xhci_readl(xhci, addr); | ||
386 | xhci->suspended_ports[wIndex >> 5] |= | ||
387 | 1 << (wIndex & (31)); | ||
388 | break; | ||
241 | case USB_PORT_FEAT_POWER: | 389 | case USB_PORT_FEAT_POWER: |
242 | /* | 390 | /* |
243 | * Turn on ports, even if there isn't per-port switching. | 391 | * Turn on ports, even if there isn't per-port switching. |
@@ -271,6 +419,52 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
271 | temp = xhci_readl(xhci, addr); | 419 | temp = xhci_readl(xhci, addr); |
272 | temp = xhci_port_state_to_neutral(temp); | 420 | temp = xhci_port_state_to_neutral(temp); |
273 | switch (wValue) { | 421 | switch (wValue) { |
422 | case USB_PORT_FEAT_SUSPEND: | ||
423 | temp = xhci_readl(xhci, addr); | ||
424 | xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n"); | ||
425 | xhci_dbg(xhci, "PORTSC %04x\n", temp); | ||
426 | if (temp & PORT_RESET) | ||
427 | goto error; | ||
428 | if (temp & XDEV_U3) { | ||
429 | if ((temp & PORT_PE) == 0) | ||
430 | goto error; | ||
431 | if (DEV_SUPERSPEED(temp)) { | ||
432 | temp = xhci_port_state_to_neutral(temp); | ||
433 | temp &= ~PORT_PLS_MASK; | ||
434 | temp |= PORT_LINK_STROBE | XDEV_U0; | ||
435 | xhci_writel(xhci, temp, addr); | ||
436 | xhci_readl(xhci, addr); | ||
437 | } else { | ||
438 | temp = xhci_port_state_to_neutral(temp); | ||
439 | temp &= ~PORT_PLS_MASK; | ||
440 | temp |= PORT_LINK_STROBE | XDEV_RESUME; | ||
441 | xhci_writel(xhci, temp, addr); | ||
442 | |||
443 | spin_unlock_irqrestore(&xhci->lock, | ||
444 | flags); | ||
445 | msleep(20); | ||
446 | spin_lock_irqsave(&xhci->lock, flags); | ||
447 | |||
448 | temp = xhci_readl(xhci, addr); | ||
449 | temp = xhci_port_state_to_neutral(temp); | ||
450 | temp &= ~PORT_PLS_MASK; | ||
451 | temp |= PORT_LINK_STROBE | XDEV_U0; | ||
452 | xhci_writel(xhci, temp, addr); | ||
453 | } | ||
454 | xhci->port_c_suspend[wIndex >> 5] |= | ||
455 | 1 << (wIndex & 31); | ||
456 | } | ||
457 | |||
458 | slot_id = xhci_find_slot_id_by_port(xhci, wIndex + 1); | ||
459 | if (!slot_id) { | ||
460 | xhci_dbg(xhci, "slot_id is zero\n"); | ||
461 | goto error; | ||
462 | } | ||
463 | xhci_ring_device(xhci, slot_id); | ||
464 | break; | ||
465 | case USB_PORT_FEAT_C_SUSPEND: | ||
466 | xhci->port_c_suspend[wIndex >> 5] &= | ||
467 | ~(1 << (wIndex & 31)); | ||
274 | case USB_PORT_FEAT_C_RESET: | 468 | case USB_PORT_FEAT_C_RESET: |
275 | case USB_PORT_FEAT_C_CONNECTION: | 469 | case USB_PORT_FEAT_C_CONNECTION: |
276 | case USB_PORT_FEAT_C_OVER_CURRENT: | 470 | case USB_PORT_FEAT_C_OVER_CURRENT: |