diff options
author | Andiry Xu <andiry.xu@amd.com> | 2010-10-14 10:23:00 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-10-22 13:22:13 -0400 |
commit | 561925318725a41189a69f36ebe99199b3fb84c4 (patch) | |
tree | e84c72692f8fd9ed67c298db17d26ccb9970a7a8 | |
parent | be88fe4f4dda93e3264a887745123b1e6c4a6845 (diff) |
USB: xHCI: port remote wakeup implementation
This commit implements port remote wakeup.
When a port is in U3 state and resume signaling is detected from a device,
the port transitions to the Resume state, and the xHC generates a Port Status
Change Event.
For USB3 port, software write a '0' to the PLS field to complete the resume
signaling. For USB2 port, the resume should be signaling for at least 20ms,
irq handler set a timer for port remote wakeup, and then finishes process in
hub_control GetPortStatus.
Some codes are borrowed from EHCI code.
Signed-off-by: Andiry Xu <andiry.xu@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 44 | ||||
-rw-r--r-- | drivers/usb/host/xhci-mem.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/xhci-ring.c | 57 | ||||
-rw-r--r-- | drivers/usb/host/xhci.h | 4 |
4 files changed, 101 insertions, 6 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 14b48b261e06..8163f17e7043 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c | |||
@@ -123,7 +123,7 @@ static unsigned int xhci_port_speed(unsigned int port_status) | |||
123 | * writing a 0 clears the bit and writing a 1 sets the bit (RWS). | 123 | * writing a 0 clears the bit and writing a 1 sets the bit (RWS). |
124 | * For all other types (RW1S, RW1CS, RW, and RZ), writing a '0' has no effect. | 124 | * For all other types (RW1S, RW1CS, RW, and RZ), writing a '0' has no effect. |
125 | */ | 125 | */ |
126 | static u32 xhci_port_state_to_neutral(u32 state) | 126 | u32 xhci_port_state_to_neutral(u32 state) |
127 | { | 127 | { |
128 | /* Save read-only status and port state */ | 128 | /* Save read-only status and port state */ |
129 | return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); | 129 | return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); |
@@ -132,7 +132,7 @@ static u32 xhci_port_state_to_neutral(u32 state) | |||
132 | /* | 132 | /* |
133 | * find slot id based on port number. | 133 | * find slot id based on port number. |
134 | */ | 134 | */ |
135 | static int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port) | 135 | int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port) |
136 | { | 136 | { |
137 | int slot_id; | 137 | int slot_id; |
138 | int i; | 138 | int i; |
@@ -210,7 +210,7 @@ command_cleanup: | |||
210 | /* | 210 | /* |
211 | * Ring device, it rings the all doorbells unconditionally. | 211 | * Ring device, it rings the all doorbells unconditionally. |
212 | */ | 212 | */ |
213 | static void xhci_ring_device(struct xhci_hcd *xhci, int slot_id) | 213 | void xhci_ring_device(struct xhci_hcd *xhci, int slot_id) |
214 | { | 214 | { |
215 | int i; | 215 | int i; |
216 | 216 | ||
@@ -276,7 +276,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
276 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | 276 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); |
277 | int ports; | 277 | int ports; |
278 | unsigned long flags; | 278 | unsigned long flags; |
279 | u32 temp, status; | 279 | u32 temp, temp1, status; |
280 | int retval = 0; | 280 | int retval = 0; |
281 | u32 __iomem *addr; | 281 | u32 __iomem *addr; |
282 | int slot_id; | 282 | int slot_id; |
@@ -315,6 +315,34 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
315 | if ((temp & PORT_PLS_MASK) == XDEV_U3 | 315 | if ((temp & PORT_PLS_MASK) == XDEV_U3 |
316 | && (temp & PORT_POWER)) | 316 | && (temp & PORT_POWER)) |
317 | status |= 1 << USB_PORT_FEAT_SUSPEND; | 317 | status |= 1 << USB_PORT_FEAT_SUSPEND; |
318 | if ((temp & PORT_PLS_MASK) == XDEV_RESUME) { | ||
319 | if ((temp & PORT_RESET) || !(temp & PORT_PE)) | ||
320 | goto error; | ||
321 | if (!DEV_SUPERSPEED(temp) && time_after_eq(jiffies, | ||
322 | xhci->resume_done[wIndex])) { | ||
323 | xhci_dbg(xhci, "Resume USB2 port %d\n", | ||
324 | wIndex + 1); | ||
325 | xhci->resume_done[wIndex] = 0; | ||
326 | temp1 = xhci_port_state_to_neutral(temp); | ||
327 | temp1 &= ~PORT_PLS_MASK; | ||
328 | temp1 |= PORT_LINK_STROBE | XDEV_U0; | ||
329 | xhci_writel(xhci, temp1, addr); | ||
330 | |||
331 | xhci_dbg(xhci, "set port %d resume\n", | ||
332 | wIndex + 1); | ||
333 | slot_id = xhci_find_slot_id_by_port(xhci, | ||
334 | wIndex + 1); | ||
335 | if (!slot_id) { | ||
336 | xhci_dbg(xhci, "slot_id is zero\n"); | ||
337 | goto error; | ||
338 | } | ||
339 | xhci_ring_device(xhci, slot_id); | ||
340 | xhci->port_c_suspend[wIndex >> 5] |= | ||
341 | 1 << (wIndex & 31); | ||
342 | xhci->suspended_ports[wIndex >> 5] &= | ||
343 | ~(1 << (wIndex & 31)); | ||
344 | } | ||
345 | } | ||
318 | if ((temp & PORT_PLS_MASK) == XDEV_U0 | 346 | if ((temp & PORT_PLS_MASK) == XDEV_U0 |
319 | && (temp & PORT_POWER) | 347 | && (temp & PORT_POWER) |
320 | && (xhci->suspended_ports[wIndex >> 5] & | 348 | && (xhci->suspended_ports[wIndex >> 5] & |
@@ -500,6 +528,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) | |||
500 | { | 528 | { |
501 | unsigned long flags; | 529 | unsigned long flags; |
502 | u32 temp, status; | 530 | u32 temp, status; |
531 | u32 mask; | ||
503 | int i, retval; | 532 | int i, retval; |
504 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | 533 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); |
505 | int ports; | 534 | int ports; |
@@ -512,13 +541,18 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) | |||
512 | memset(buf, 0, retval); | 541 | memset(buf, 0, retval); |
513 | status = 0; | 542 | status = 0; |
514 | 543 | ||
544 | mask = PORT_CSC | PORT_PEC | PORT_OCC; | ||
545 | |||
515 | spin_lock_irqsave(&xhci->lock, flags); | 546 | spin_lock_irqsave(&xhci->lock, flags); |
516 | /* For each port, did anything change? If so, set that bit in buf. */ | 547 | /* For each port, did anything change? If so, set that bit in buf. */ |
517 | for (i = 0; i < ports; i++) { | 548 | for (i = 0; i < ports; i++) { |
518 | addr = &xhci->op_regs->port_status_base + | 549 | addr = &xhci->op_regs->port_status_base + |
519 | NUM_PORT_REGS*i; | 550 | NUM_PORT_REGS*i; |
520 | temp = xhci_readl(xhci, addr); | 551 | temp = xhci_readl(xhci, addr); |
521 | if (temp & (PORT_CSC | PORT_PEC | PORT_OCC)) { | 552 | if ((temp & mask) != 0 || |
553 | (xhci->port_c_suspend[i >> 5] & 1 << (i & 31)) || | ||
554 | (xhci->resume_done[i] && time_after_eq( | ||
555 | jiffies, xhci->resume_done[i]))) { | ||
522 | buf[(i + 1) / 8] |= 1 << (i + 1) % 8; | 556 | buf[(i + 1) / 8] |= 1 << (i + 1) % 8; |
523 | status = 1; | 557 | status = 1; |
524 | } | 558 | } |
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 858a82867e1d..fd888bc0422b 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c | |||
@@ -1803,6 +1803,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) | |||
1803 | init_completion(&xhci->addr_dev); | 1803 | init_completion(&xhci->addr_dev); |
1804 | for (i = 0; i < MAX_HC_SLOTS; ++i) | 1804 | for (i = 0; i < MAX_HC_SLOTS; ++i) |
1805 | xhci->devs[i] = NULL; | 1805 | xhci->devs[i] = NULL; |
1806 | for (i = 0; i < MAX_HC_PORTS; ++i) | ||
1807 | xhci->resume_done[i] = 0; | ||
1806 | 1808 | ||
1807 | if (scratchpad_alloc(xhci, flags)) | 1809 | if (scratchpad_alloc(xhci, flags)) |
1808 | goto fail; | 1810 | goto fail; |
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index b18e00ecb468..9f3115e729b1 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c | |||
@@ -1165,17 +1165,72 @@ static void handle_vendor_event(struct xhci_hcd *xhci, | |||
1165 | static void handle_port_status(struct xhci_hcd *xhci, | 1165 | static void handle_port_status(struct xhci_hcd *xhci, |
1166 | union xhci_trb *event) | 1166 | union xhci_trb *event) |
1167 | { | 1167 | { |
1168 | struct usb_hcd *hcd = xhci_to_hcd(xhci); | ||
1168 | u32 port_id; | 1169 | u32 port_id; |
1170 | u32 temp, temp1; | ||
1171 | u32 __iomem *addr; | ||
1172 | int ports; | ||
1173 | int slot_id; | ||
1169 | 1174 | ||
1170 | /* Port status change events always have a successful completion code */ | 1175 | /* Port status change events always have a successful completion code */ |
1171 | if (GET_COMP_CODE(event->generic.field[2]) != COMP_SUCCESS) { | 1176 | if (GET_COMP_CODE(event->generic.field[2]) != COMP_SUCCESS) { |
1172 | xhci_warn(xhci, "WARN: xHC returned failed port status event\n"); | 1177 | xhci_warn(xhci, "WARN: xHC returned failed port status event\n"); |
1173 | xhci->error_bitmask |= 1 << 8; | 1178 | xhci->error_bitmask |= 1 << 8; |
1174 | } | 1179 | } |
1175 | /* FIXME: core doesn't care about all port link state changes yet */ | ||
1176 | port_id = GET_PORT_ID(event->generic.field[0]); | 1180 | port_id = GET_PORT_ID(event->generic.field[0]); |
1177 | xhci_dbg(xhci, "Port Status Change Event for port %d\n", port_id); | 1181 | xhci_dbg(xhci, "Port Status Change Event for port %d\n", port_id); |
1178 | 1182 | ||
1183 | ports = HCS_MAX_PORTS(xhci->hcs_params1); | ||
1184 | if ((port_id <= 0) || (port_id > ports)) { | ||
1185 | xhci_warn(xhci, "Invalid port id %d\n", port_id); | ||
1186 | goto cleanup; | ||
1187 | } | ||
1188 | |||
1189 | addr = &xhci->op_regs->port_status_base + NUM_PORT_REGS * (port_id - 1); | ||
1190 | temp = xhci_readl(xhci, addr); | ||
1191 | if ((temp & PORT_CONNECT) && (hcd->state == HC_STATE_SUSPENDED)) { | ||
1192 | xhci_dbg(xhci, "resume root hub\n"); | ||
1193 | usb_hcd_resume_root_hub(hcd); | ||
1194 | } | ||
1195 | |||
1196 | if ((temp & PORT_PLC) && (temp & PORT_PLS_MASK) == XDEV_RESUME) { | ||
1197 | xhci_dbg(xhci, "port resume event for port %d\n", port_id); | ||
1198 | |||
1199 | temp1 = xhci_readl(xhci, &xhci->op_regs->command); | ||
1200 | if (!(temp1 & CMD_RUN)) { | ||
1201 | xhci_warn(xhci, "xHC is not running.\n"); | ||
1202 | goto cleanup; | ||
1203 | } | ||
1204 | |||
1205 | if (DEV_SUPERSPEED(temp)) { | ||
1206 | xhci_dbg(xhci, "resume SS port %d\n", port_id); | ||
1207 | temp = xhci_port_state_to_neutral(temp); | ||
1208 | temp &= ~PORT_PLS_MASK; | ||
1209 | temp |= PORT_LINK_STROBE | XDEV_U0; | ||
1210 | xhci_writel(xhci, temp, addr); | ||
1211 | slot_id = xhci_find_slot_id_by_port(xhci, port_id); | ||
1212 | if (!slot_id) { | ||
1213 | xhci_dbg(xhci, "slot_id is zero\n"); | ||
1214 | goto cleanup; | ||
1215 | } | ||
1216 | xhci_ring_device(xhci, slot_id); | ||
1217 | xhci_dbg(xhci, "resume SS port %d finished\n", port_id); | ||
1218 | /* Clear PORT_PLC */ | ||
1219 | temp = xhci_readl(xhci, addr); | ||
1220 | temp = xhci_port_state_to_neutral(temp); | ||
1221 | temp |= PORT_PLC; | ||
1222 | xhci_writel(xhci, temp, addr); | ||
1223 | } else { | ||
1224 | xhci_dbg(xhci, "resume HS port %d\n", port_id); | ||
1225 | xhci->resume_done[port_id - 1] = jiffies + | ||
1226 | msecs_to_jiffies(20); | ||
1227 | mod_timer(&hcd->rh_timer, | ||
1228 | xhci->resume_done[port_id - 1]); | ||
1229 | /* Do the rest in GetPortStatus */ | ||
1230 | } | ||
1231 | } | ||
1232 | |||
1233 | cleanup: | ||
1179 | /* Update event ring dequeue pointer before dropping the lock */ | 1234 | /* Update event ring dequeue pointer before dropping the lock */ |
1180 | inc_deq(xhci, xhci->event_ring, true); | 1235 | inc_deq(xhci, xhci->event_ring, true); |
1181 | 1236 | ||
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 73e5db3e89c9..ca4a923dc810 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h | |||
@@ -1215,6 +1215,7 @@ struct xhci_hcd { | |||
1215 | u32 port_c_suspend[8]; /* port suspend change*/ | 1215 | u32 port_c_suspend[8]; /* port suspend change*/ |
1216 | u32 suspended_ports[8]; /* which ports are | 1216 | u32 suspended_ports[8]; /* which ports are |
1217 | suspended */ | 1217 | suspended */ |
1218 | unsigned long resume_done[MAX_HC_PORTS]; | ||
1218 | }; | 1219 | }; |
1219 | 1220 | ||
1220 | /* For testing purposes */ | 1221 | /* For testing purposes */ |
@@ -1459,6 +1460,9 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, | |||
1459 | int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, | 1460 | int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, |
1460 | char *buf, u16 wLength); | 1461 | char *buf, u16 wLength); |
1461 | int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); | 1462 | int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); |
1463 | u32 xhci_port_state_to_neutral(u32 state); | ||
1464 | int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port); | ||
1465 | void xhci_ring_device(struct xhci_hcd *xhci, int slot_id); | ||
1462 | 1466 | ||
1463 | /* xHCI contexts */ | 1467 | /* xHCI contexts */ |
1464 | struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); | 1468 | struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); |