From 561925318725a41189a69f36ebe99199b3fb84c4 Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Thu, 14 Oct 2010 07:23:00 -0700 Subject: 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 Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 44 ++++++++++++++++++++++++++++++---- drivers/usb/host/xhci-mem.c | 2 ++ drivers/usb/host/xhci-ring.c | 57 +++++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/xhci.h | 4 ++++ 4 files changed, 101 insertions(+), 6 deletions(-) (limited to 'drivers/usb') 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) * writing a 0 clears the bit and writing a 1 sets the bit (RWS). * For all other types (RW1S, RW1CS, RW, and RZ), writing a '0' has no effect. */ -static u32 xhci_port_state_to_neutral(u32 state) +u32 xhci_port_state_to_neutral(u32 state) { /* Save read-only status and port state */ return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); @@ -132,7 +132,7 @@ static u32 xhci_port_state_to_neutral(u32 state) /* * find slot id based on port number. */ -static int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port) +int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port) { int slot_id; int i; @@ -210,7 +210,7 @@ command_cleanup: /* * Ring device, it rings the all doorbells unconditionally. */ -static void xhci_ring_device(struct xhci_hcd *xhci, int slot_id) +void xhci_ring_device(struct xhci_hcd *xhci, int slot_id) { int i; @@ -276,7 +276,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, struct xhci_hcd *xhci = hcd_to_xhci(hcd); int ports; unsigned long flags; - u32 temp, status; + u32 temp, temp1, status; int retval = 0; u32 __iomem *addr; int slot_id; @@ -315,6 +315,34 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if ((temp & PORT_PLS_MASK) == XDEV_U3 && (temp & PORT_POWER)) status |= 1 << USB_PORT_FEAT_SUSPEND; + if ((temp & PORT_PLS_MASK) == XDEV_RESUME) { + if ((temp & PORT_RESET) || !(temp & PORT_PE)) + goto error; + if (!DEV_SUPERSPEED(temp) && time_after_eq(jiffies, + xhci->resume_done[wIndex])) { + xhci_dbg(xhci, "Resume USB2 port %d\n", + wIndex + 1); + xhci->resume_done[wIndex] = 0; + temp1 = xhci_port_state_to_neutral(temp); + temp1 &= ~PORT_PLS_MASK; + temp1 |= PORT_LINK_STROBE | XDEV_U0; + xhci_writel(xhci, temp1, addr); + + xhci_dbg(xhci, "set port %d resume\n", + wIndex + 1); + slot_id = xhci_find_slot_id_by_port(xhci, + wIndex + 1); + if (!slot_id) { + xhci_dbg(xhci, "slot_id is zero\n"); + goto error; + } + xhci_ring_device(xhci, slot_id); + xhci->port_c_suspend[wIndex >> 5] |= + 1 << (wIndex & 31); + xhci->suspended_ports[wIndex >> 5] &= + ~(1 << (wIndex & 31)); + } + } if ((temp & PORT_PLS_MASK) == XDEV_U0 && (temp & PORT_POWER) && (xhci->suspended_ports[wIndex >> 5] & @@ -500,6 +528,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) { unsigned long flags; u32 temp, status; + u32 mask; int i, retval; struct xhci_hcd *xhci = hcd_to_xhci(hcd); int ports; @@ -512,13 +541,18 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) memset(buf, 0, retval); status = 0; + mask = PORT_CSC | PORT_PEC | PORT_OCC; + spin_lock_irqsave(&xhci->lock, flags); /* For each port, did anything change? If so, set that bit in buf. */ for (i = 0; i < ports; i++) { addr = &xhci->op_regs->port_status_base + NUM_PORT_REGS*i; temp = xhci_readl(xhci, addr); - if (temp & (PORT_CSC | PORT_PEC | PORT_OCC)) { + if ((temp & mask) != 0 || + (xhci->port_c_suspend[i >> 5] & 1 << (i & 31)) || + (xhci->resume_done[i] && time_after_eq( + jiffies, xhci->resume_done[i]))) { buf[(i + 1) / 8] |= 1 << (i + 1) % 8; status = 1; } 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) init_completion(&xhci->addr_dev); for (i = 0; i < MAX_HC_SLOTS; ++i) xhci->devs[i] = NULL; + for (i = 0; i < MAX_HC_PORTS; ++i) + xhci->resume_done[i] = 0; if (scratchpad_alloc(xhci, flags)) 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, static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) { + struct usb_hcd *hcd = xhci_to_hcd(xhci); u32 port_id; + u32 temp, temp1; + u32 __iomem *addr; + int ports; + int slot_id; /* Port status change events always have a successful completion code */ if (GET_COMP_CODE(event->generic.field[2]) != COMP_SUCCESS) { xhci_warn(xhci, "WARN: xHC returned failed port status event\n"); xhci->error_bitmask |= 1 << 8; } - /* FIXME: core doesn't care about all port link state changes yet */ port_id = GET_PORT_ID(event->generic.field[0]); xhci_dbg(xhci, "Port Status Change Event for port %d\n", port_id); + ports = HCS_MAX_PORTS(xhci->hcs_params1); + if ((port_id <= 0) || (port_id > ports)) { + xhci_warn(xhci, "Invalid port id %d\n", port_id); + goto cleanup; + } + + addr = &xhci->op_regs->port_status_base + NUM_PORT_REGS * (port_id - 1); + temp = xhci_readl(xhci, addr); + if ((temp & PORT_CONNECT) && (hcd->state == HC_STATE_SUSPENDED)) { + xhci_dbg(xhci, "resume root hub\n"); + usb_hcd_resume_root_hub(hcd); + } + + if ((temp & PORT_PLC) && (temp & PORT_PLS_MASK) == XDEV_RESUME) { + xhci_dbg(xhci, "port resume event for port %d\n", port_id); + + temp1 = xhci_readl(xhci, &xhci->op_regs->command); + if (!(temp1 & CMD_RUN)) { + xhci_warn(xhci, "xHC is not running.\n"); + goto cleanup; + } + + if (DEV_SUPERSPEED(temp)) { + xhci_dbg(xhci, "resume SS port %d\n", port_id); + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_U0; + xhci_writel(xhci, temp, addr); + slot_id = xhci_find_slot_id_by_port(xhci, port_id); + if (!slot_id) { + xhci_dbg(xhci, "slot_id is zero\n"); + goto cleanup; + } + xhci_ring_device(xhci, slot_id); + xhci_dbg(xhci, "resume SS port %d finished\n", port_id); + /* Clear PORT_PLC */ + temp = xhci_readl(xhci, addr); + temp = xhci_port_state_to_neutral(temp); + temp |= PORT_PLC; + xhci_writel(xhci, temp, addr); + } else { + xhci_dbg(xhci, "resume HS port %d\n", port_id); + xhci->resume_done[port_id - 1] = jiffies + + msecs_to_jiffies(20); + mod_timer(&hcd->rh_timer, + xhci->resume_done[port_id - 1]); + /* Do the rest in GetPortStatus */ + } + } + +cleanup: /* Update event ring dequeue pointer before dropping the lock */ inc_deq(xhci, xhci->event_ring, true); 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 { u32 port_c_suspend[8]; /* port suspend change*/ u32 suspended_ports[8]; /* which ports are suspended */ + unsigned long resume_done[MAX_HC_PORTS]; }; /* For testing purposes */ @@ -1459,6 +1460,9 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); +u32 xhci_port_state_to_neutral(u32 state); +int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port); +void xhci_ring_device(struct xhci_hcd *xhci, int slot_id); /* xHCI contexts */ struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); -- cgit v1.2.3