| commit 5e5563661948c57f72cc16b3a0cc5dc205ed4900 |
| Author: Andiry Xu <andiry.xu@amd.com> |
| Date: Thu Oct 14 07:23:06 2010 -0700 |
| |
| USB: xHCI: PCI power management implementation |
| |
| This patch implements the PCI suspend/resume. |
| |
| Please refer to xHCI spec for doing the suspend/resume operation. |
| |
| For S3, CSS/SRS in USBCMD is used to save/restore the internal state. |
| However, an error maybe occurs while restoring the internal state. |
| In this case, it means that HC internal state is wrong and HC will be |
| re-initialized. |
| |
| Signed-off-by: Libin Yang <libin.yang@amd.com> |
| Signed-off-by: Dong Nguyen <dong.nguyen@amd.com> |
| 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> |
| |
| commit 96520f33d383c9a3ba1ca571cac5fa75325728f5 |
| Author: Andiry Xu <andiry.xu@amd.com> |
| Date: Thu Oct 14 07:23:03 2010 -0700 |
| |
| USB: xHCI: bus power management implementation |
| |
| This patch implements xHCI bus suspend/resume function hook. |
| |
| In the patch it goes through all the ports and suspend/resume |
| the ports if needed. |
| |
| If any port is in remote wakeup, abort bus suspend as what ehci/ohci do. |
| |
| Signed-off-by: Libin Yang <libin.yang@amd.com> |
| Signed-off-by: Crane Cai <crane.cai@amd.com> |
| 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> |
| |
| commit 7b29198e193ab6f5e8bfcd48c59340b8c7689f5c |
| Author: Andiry Xu <andiry.xu@amd.com> |
| Date: Thu Oct 14 07:23:00 2010 -0700 |
| |
| 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> |
| |
| commit 9ada0dec259dfe796a757ff2c9b63a05e6408e5f |
| Author: Andiry Xu <andiry.xu@amd.com> |
| Date: Thu Oct 14 07:22:57 2010 -0700 |
| |
| USB: xHCI: port power management implementation |
| |
| Add software trigger USB device suspend resume function hook. |
| Do port suspend & resume in terms of xHCI spec. |
| |
| Port Suspend: |
| Stop all endpoints via Stop Endpoint Command with Suspend (SP) flag set. |
| Place individual ports into suspend mode by writing '3' for Port Link State |
| (PLS) field into PORTSC register. This can only be done when the port is in |
| Enabled state. When writing, the Port Link State Write Strobe (LWS) bit shall |
| be set to '1'. |
| Allocate an xhci_command and stash it in xhci_virt_device to wait completion for |
| the last Stop Endpoint Command. Use the Suspend bit in TRB to indicate the Stop |
| Endpoint Command is for port suspend. Based on Sarah's suggestion. |
| |
| Port Resume: |
| Write '0' in PLS field, device will transition to running state. |
| Ring an endpoints' doorbell to restart it. |
| |
| Ref: USB device remote wake need another patch to implement. For details of |
| how USB subsystem do power management, please see: |
| Documentation/usb/power-management.txt |
| |
| Signed-off-by: Crane Cai <crane.cai@amd.com> |
| Signed-off-by: Libin Yang <libin.yang@amd.com> |
| 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> |
| |
| drivers/usb/host/xhci-hub.c | 424 +++++++++++++++++++++++++++++++++++++++++- |
| drivers/usb/host/xhci-mem.c | 4 + |
| drivers/usb/host/xhci-pci.c | 36 ++++- |
| drivers/usb/host/xhci-ring.c | 101 +++++++++- |
| drivers/usb/host/xhci.c | 212 +++++++++++++++++++++- |
| drivers/usb/host/xhci.h | 46 +++++- |
| 6 files changed, 805 insertions(+), 18 deletions(-) |
| |
| diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c |
| index a1a7a97..7f2f63c 100644 |
| |
| |
| @@ -24,6 +24,10 @@ |
| |
| #include "xhci.h" |
| |
| +#define PORT_WAKE_BITS (PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E) |
| +#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ |
| + PORT_RC | PORT_PLC | PORT_PE) |
| + |
| static void xhci_hub_descriptor(struct xhci_hcd *xhci, |
| struct usb_hub_descriptor *desc) |
| { |
| @@ -123,12 +127,105 @@ 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); |
| } |
| |
| +/* |
| + * find slot id based on port number. |
| + */ |
| +int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port) |
| +{ |
| + int slot_id; |
| + int i; |
| + |
| + slot_id = 0; |
| + for (i = 0; i < MAX_HC_SLOTS; i++) { |
| + if (!xhci->devs[i]) |
| + continue; |
| + if (xhci->devs[i]->port == port) { |
| + slot_id = i; |
| + break; |
| + } |
| + } |
| + |
| + return slot_id; |
| +} |
| + |
| +/* |
| + * Stop device |
| + * It issues stop endpoint command for EP 0 to 30. And wait the last command |
| + * to complete. |
| + * suspend will set to 1, if suspend bit need to set in command. |
| + */ |
| +static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend) |
| +{ |
| + struct xhci_virt_device *virt_dev; |
| + struct xhci_command *cmd; |
| + unsigned long flags; |
| + int timeleft; |
| + int ret; |
| + int i; |
| + |
| + ret = 0; |
| + virt_dev = xhci->devs[slot_id]; |
| + cmd = xhci_alloc_command(xhci, false, true, GFP_NOIO); |
| + if (!cmd) { |
| + xhci_dbg(xhci, "Couldn't allocate command structure.\n"); |
| + return -ENOMEM; |
| + } |
| + |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + for (i = LAST_EP_INDEX; i > 0; i--) { |
| + if (virt_dev->eps[i].ring && virt_dev->eps[i].ring->dequeue) |
| + xhci_queue_stop_endpoint(xhci, slot_id, i, suspend); |
| + } |
| + cmd->command_trb = xhci->cmd_ring->enqueue; |
| + list_add_tail(&cmd->cmd_list, &virt_dev->cmd_list); |
| + xhci_queue_stop_endpoint(xhci, slot_id, 0, suspend); |
| + xhci_ring_cmd_db(xhci); |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + |
| + /* Wait for last stop endpoint command to finish */ |
| + timeleft = wait_for_completion_interruptible_timeout( |
| + cmd->completion, |
| + USB_CTRL_SET_TIMEOUT); |
| + if (timeleft <= 0) { |
| + xhci_warn(xhci, "%s while waiting for stop endpoint command\n", |
| + timeleft == 0 ? "Timeout" : "Signal"); |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + /* The timeout might have raced with the event ring handler, so |
| + * only delete from the list if the item isn't poisoned. |
| + */ |
| + if (cmd->cmd_list.next != LIST_POISON1) |
| + list_del(&cmd->cmd_list); |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + ret = -ETIME; |
| + goto command_cleanup; |
| + } |
| + |
| +command_cleanup: |
| + xhci_free_command(xhci, cmd); |
| + return ret; |
| +} |
| + |
| +/* |
| + * Ring device, it rings the all doorbells unconditionally. |
| + */ |
| +void xhci_ring_device(struct xhci_hcd *xhci, int slot_id) |
| +{ |
| + int i; |
| + |
| + for (i = 0; i < LAST_EP_INDEX + 1; i++) |
| + if (xhci->devs[slot_id]->eps[i].ring && |
| + xhci->devs[slot_id]->eps[i].ring->dequeue) |
| + xhci_ring_ep_doorbell(xhci, slot_id, i, 0); |
| + |
| + return; |
| +} |
| + |
| static void xhci_disable_port(struct xhci_hcd *xhci, u16 wIndex, |
| u32 __iomem *addr, u32 port_status) |
| { |
| @@ -162,6 +259,10 @@ static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, |
| status = PORT_PEC; |
| port_change_bit = "enable/disable"; |
| break; |
| + case USB_PORT_FEAT_C_SUSPEND: |
| + status = PORT_PLC; |
| + port_change_bit = "suspend/resume"; |
| + break; |
| default: |
| /* Should never happen */ |
| return; |
| @@ -179,9 +280,10 @@ 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; |
| |
| ports = HCS_MAX_PORTS(xhci->hcs_params1); |
| |
| @@ -211,9 +313,49 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, |
| if ((temp & PORT_OCC)) |
| status |= USB_PORT_STAT_C_OVERCURRENT << 16; |
| /* |
| - * FIXME ignoring suspend, reset, and USB 2.1/3.0 specific |
| + * FIXME ignoring reset and USB 2.1/3.0 specific |
| * changes |
| */ |
| + 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] & |
| + (1 << (wIndex & 31)))) { |
| + xhci->suspended_ports[wIndex >> 5] &= |
| + ~(1 << (wIndex & 31)); |
| + xhci->port_c_suspend[wIndex >> 5] |= |
| + 1 << (wIndex & 31); |
| + } |
| if (temp & PORT_CONNECT) { |
| status |= USB_PORT_STAT_CONNECTION; |
| status |= xhci_port_speed(temp); |
| @@ -226,6 +368,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, |
| status |= USB_PORT_STAT_RESET; |
| if (temp & PORT_POWER) |
| status |= USB_PORT_STAT_POWER; |
| + if (xhci->port_c_suspend[wIndex >> 5] & (1 << (wIndex & 31))) |
| + status |= 1 << USB_PORT_FEAT_C_SUSPEND; |
| xhci_dbg(xhci, "Get port status returned 0x%x\n", status); |
| put_unaligned(cpu_to_le32(status), (__le32 *) buf); |
| break; |
| @@ -238,6 +382,42 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, |
| temp = xhci_readl(xhci, addr); |
| temp = xhci_port_state_to_neutral(temp); |
| switch (wValue) { |
| + case USB_PORT_FEAT_SUSPEND: |
| + temp = xhci_readl(xhci, addr); |
| + /* In spec software should not attempt to suspend |
| + * a port unless the port reports that it is in the |
| + * enabled (PED = ‘1’,PLS < ‘3’) state. |
| + */ |
| + if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) |
| + || (temp & PORT_PLS_MASK) >= XDEV_U3) { |
| + xhci_warn(xhci, "USB core suspending device " |
| + "not in U0/U1/U2.\n"); |
| + goto error; |
| + } |
| + |
| + slot_id = xhci_find_slot_id_by_port(xhci, wIndex + 1); |
| + if (!slot_id) { |
| + xhci_warn(xhci, "slot_id is zero\n"); |
| + goto error; |
| + } |
| + /* unlock to execute stop endpoint commands */ |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + xhci_stop_device(xhci, slot_id, 1); |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + |
| + temp = xhci_port_state_to_neutral(temp); |
| + temp &= ~PORT_PLS_MASK; |
| + temp |= PORT_LINK_STROBE | XDEV_U3; |
| + xhci_writel(xhci, temp, addr); |
| + |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + msleep(10); /* wait device to enter */ |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + |
| + temp = xhci_readl(xhci, addr); |
| + xhci->suspended_ports[wIndex >> 5] |= |
| + 1 << (wIndex & (31)); |
| + break; |
| case USB_PORT_FEAT_POWER: |
| /* |
| * Turn on ports, even if there isn't per-port switching. |
| @@ -271,6 +451,52 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, |
| temp = xhci_readl(xhci, addr); |
| temp = xhci_port_state_to_neutral(temp); |
| switch (wValue) { |
| + case USB_PORT_FEAT_SUSPEND: |
| + temp = xhci_readl(xhci, addr); |
| + xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n"); |
| + xhci_dbg(xhci, "PORTSC %04x\n", temp); |
| + if (temp & PORT_RESET) |
| + goto error; |
| + if (temp & XDEV_U3) { |
| + if ((temp & PORT_PE) == 0) |
| + goto error; |
| + if (DEV_SUPERSPEED(temp)) { |
| + temp = xhci_port_state_to_neutral(temp); |
| + temp &= ~PORT_PLS_MASK; |
| + temp |= PORT_LINK_STROBE | XDEV_U0; |
| + xhci_writel(xhci, temp, addr); |
| + xhci_readl(xhci, addr); |
| + } else { |
| + temp = xhci_port_state_to_neutral(temp); |
| + temp &= ~PORT_PLS_MASK; |
| + temp |= PORT_LINK_STROBE | XDEV_RESUME; |
| + xhci_writel(xhci, temp, addr); |
| + |
| + spin_unlock_irqrestore(&xhci->lock, |
| + flags); |
| + msleep(20); |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + |
| + temp = xhci_readl(xhci, addr); |
| + temp = xhci_port_state_to_neutral(temp); |
| + temp &= ~PORT_PLS_MASK; |
| + temp |= PORT_LINK_STROBE | XDEV_U0; |
| + xhci_writel(xhci, temp, addr); |
| + } |
| + xhci->port_c_suspend[wIndex >> 5] |= |
| + 1 << (wIndex & 31); |
| + } |
| + |
| + 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); |
| + break; |
| + case USB_PORT_FEAT_C_SUSPEND: |
| + xhci->port_c_suspend[wIndex >> 5] &= |
| + ~(1 << (wIndex & 31)); |
| case USB_PORT_FEAT_C_RESET: |
| case USB_PORT_FEAT_C_CONNECTION: |
| case USB_PORT_FEAT_C_OVER_CURRENT: |
| @@ -306,6 +532,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; |
| @@ -318,13 +545,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; |
| } |
| @@ -332,3 +564,187 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) |
| spin_unlock_irqrestore(&xhci->lock, flags); |
| return status ? retval : 0; |
| } |
| + |
| +#ifdef CONFIG_PM |
| + |
| +int xhci_bus_suspend(struct usb_hcd *hcd) |
| +{ |
| + struct xhci_hcd *xhci = hcd_to_xhci(hcd); |
| + int port; |
| + unsigned long flags; |
| + |
| + xhci_dbg(xhci, "suspend root hub\n"); |
| + |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + |
| + if (hcd->self.root_hub->do_remote_wakeup) { |
| + port = HCS_MAX_PORTS(xhci->hcs_params1); |
| + while (port--) { |
| + if (xhci->resume_done[port] != 0) { |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + xhci_dbg(xhci, "suspend failed because " |
| + "port %d is resuming\n", |
| + port + 1); |
| + return -EBUSY; |
| + } |
| + } |
| + } |
| + |
| + port = HCS_MAX_PORTS(xhci->hcs_params1); |
| + xhci->bus_suspended = 0; |
| + while (port--) { |
| + /* suspend the port if the port is not suspended */ |
| + u32 __iomem *addr; |
| + u32 t1, t2; |
| + int slot_id; |
| + |
| + addr = &xhci->op_regs->port_status_base + |
| + NUM_PORT_REGS * (port & 0xff); |
| + t1 = xhci_readl(xhci, addr); |
| + t2 = xhci_port_state_to_neutral(t1); |
| + |
| + if ((t1 & PORT_PE) && !(t1 & PORT_PLS_MASK)) { |
| + xhci_dbg(xhci, "port %d not suspended\n", port); |
| + slot_id = xhci_find_slot_id_by_port(xhci, port + 1); |
| + if (slot_id) { |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + xhci_stop_device(xhci, slot_id, 1); |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + } |
| + t2 &= ~PORT_PLS_MASK; |
| + t2 |= PORT_LINK_STROBE | XDEV_U3; |
| + set_bit(port, &xhci->bus_suspended); |
| + } |
| + if (hcd->self.root_hub->do_remote_wakeup) { |
| + if (t1 & PORT_CONNECT) { |
| + t2 |= PORT_WKOC_E | PORT_WKDISC_E; |
| + t2 &= ~PORT_WKCONN_E; |
| + } else { |
| + t2 |= PORT_WKOC_E | PORT_WKCONN_E; |
| + t2 &= ~PORT_WKDISC_E; |
| + } |
| + } else |
| + t2 &= ~PORT_WAKE_BITS; |
| + |
| + t1 = xhci_port_state_to_neutral(t1); |
| + if (t1 != t2) |
| + xhci_writel(xhci, t2, addr); |
| + |
| + if (DEV_HIGHSPEED(t1)) { |
| + /* enable remote wake up for USB 2.0 */ |
| + u32 __iomem *addr; |
| + u32 tmp; |
| + |
| + addr = &xhci->op_regs->port_power_base + |
| + NUM_PORT_REGS * (port & 0xff); |
| + tmp = xhci_readl(xhci, addr); |
| + tmp |= PORT_RWE; |
| + xhci_writel(xhci, tmp, addr); |
| + } |
| + } |
| + hcd->state = HC_STATE_SUSPENDED; |
| + xhci->next_statechange = jiffies + msecs_to_jiffies(10); |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + return 0; |
| +} |
| + |
| +int xhci_bus_resume(struct usb_hcd *hcd) |
| +{ |
| + struct xhci_hcd *xhci = hcd_to_xhci(hcd); |
| + int port; |
| + u32 temp; |
| + unsigned long flags; |
| + |
| + xhci_dbg(xhci, "resume root hub\n"); |
| + |
| + if (time_before(jiffies, xhci->next_statechange)) |
| + msleep(5); |
| + |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + if (!HCD_HW_ACCESSIBLE(hcd)) { |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + return -ESHUTDOWN; |
| + } |
| + |
| + /* delay the irqs */ |
| + temp = xhci_readl(xhci, &xhci->op_regs->command); |
| + temp &= ~CMD_EIE; |
| + xhci_writel(xhci, temp, &xhci->op_regs->command); |
| + |
| + port = HCS_MAX_PORTS(xhci->hcs_params1); |
| + while (port--) { |
| + /* Check whether need resume ports. If needed |
| + resume port and disable remote wakeup */ |
| + u32 __iomem *addr; |
| + u32 temp; |
| + int slot_id; |
| + |
| + addr = &xhci->op_regs->port_status_base + |
| + NUM_PORT_REGS * (port & 0xff); |
| + temp = xhci_readl(xhci, addr); |
| + if (DEV_SUPERSPEED(temp)) |
| + temp &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); |
| + else |
| + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); |
| + if (test_bit(port, &xhci->bus_suspended) && |
| + (temp & PORT_PLS_MASK)) { |
| + if (DEV_SUPERSPEED(temp)) { |
| + temp = xhci_port_state_to_neutral(temp); |
| + temp &= ~PORT_PLS_MASK; |
| + temp |= PORT_LINK_STROBE | XDEV_U0; |
| + xhci_writel(xhci, temp, addr); |
| + } else { |
| + temp = xhci_port_state_to_neutral(temp); |
| + temp &= ~PORT_PLS_MASK; |
| + temp |= PORT_LINK_STROBE | XDEV_RESUME; |
| + xhci_writel(xhci, temp, addr); |
| + |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + msleep(20); |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + |
| + temp = xhci_readl(xhci, addr); |
| + 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 + 1); |
| + if (slot_id) |
| + xhci_ring_device(xhci, slot_id); |
| + } else |
| + xhci_writel(xhci, temp, addr); |
| + |
| + if (DEV_HIGHSPEED(temp)) { |
| + /* disable remote wake up for USB 2.0 */ |
| + u32 __iomem *addr; |
| + u32 tmp; |
| + |
| + addr = &xhci->op_regs->port_power_base + |
| + NUM_PORT_REGS * (port & 0xff); |
| + tmp = xhci_readl(xhci, addr); |
| + tmp &= ~PORT_RWE; |
| + xhci_writel(xhci, tmp, addr); |
| + } |
| + } |
| + |
| + (void) xhci_readl(xhci, &xhci->op_regs->command); |
| + |
| + xhci->next_statechange = jiffies + msecs_to_jiffies(5); |
| + hcd->state = HC_STATE_RUNNING; |
| + /* re-enable irqs */ |
| + temp = xhci_readl(xhci, &xhci->op_regs->command); |
| + temp |= CMD_EIE; |
| + xhci_writel(xhci, temp, &xhci->op_regs->command); |
| + temp = xhci_readl(xhci, &xhci->op_regs->command); |
| + |
| + spin_unlock_irqrestore(&xhci->lock, flags); |
| + return 0; |
| +} |
| + |
| +#else |
| + |
| +#define xhci_bus_suspend NULL |
| +#define xhci_bus_resume NULL |
| + |
| +#endif |
| diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c |
| index 4e51343..cef8d81 100644 |
| |
| |
| @@ -866,6 +866,7 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud |
| top_dev = top_dev->parent) |
| /* Found device below root hub */; |
| slot_ctx->dev_info2 |= (u32) ROOT_HUB_PORT(top_dev->portnum); |
| + dev->port = top_dev->portnum; |
| xhci_dbg(xhci, "Set root hub portnum to %d\n", top_dev->portnum); |
| |
| /* Is this a LS/FS device under a HS hub? */ |
| @@ -1443,6 +1444,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) |
| scratchpad_free(xhci); |
| xhci->page_size = 0; |
| xhci->page_shift = 0; |
| + xhci->bus_suspended = 0; |
| } |
| |
| static int xhci_test_trb_in_td(struct xhci_hcd *xhci, |
| @@ -1801,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-pci.c b/drivers/usb/host/xhci-pci.c |
| index f7efe02..e3a5924 100644 |
| |
| |
| @@ -116,6 +116,30 @@ static int xhci_pci_setup(struct usb_hcd *hcd) |
| return xhci_pci_reinit(xhci, pdev); |
| } |
| |
| +#ifdef CONFIG_PM |
| +static int xhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup) |
| +{ |
| + struct xhci_hcd *xhci = hcd_to_xhci(hcd); |
| + int retval = 0; |
| + |
| + if (hcd->state != HC_STATE_SUSPENDED) |
| + return -EINVAL; |
| + |
| + retval = xhci_suspend(xhci); |
| + |
| + return retval; |
| +} |
| + |
| +static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated) |
| +{ |
| + struct xhci_hcd *xhci = hcd_to_xhci(hcd); |
| + int retval = 0; |
| + |
| + retval = xhci_resume(xhci, hibernated); |
| + return retval; |
| +} |
| +#endif /* CONFIG_PM */ |
| + |
| static const struct hc_driver xhci_pci_hc_driver = { |
| .description = hcd_name, |
| .product_desc = "xHCI Host Controller", |
| @@ -132,7 +156,10 @@ static const struct hc_driver xhci_pci_hc_driver = { |
| */ |
| .reset = xhci_pci_setup, |
| .start = xhci_run, |
| - /* suspend and resume implemented later */ |
| +#ifdef CONFIG_PM |
| + .pci_suspend = xhci_pci_suspend, |
| + .pci_resume = xhci_pci_resume, |
| +#endif |
| .stop = xhci_stop, |
| .shutdown = xhci_shutdown, |
| |
| @@ -162,6 +189,8 @@ static const struct hc_driver xhci_pci_hc_driver = { |
| /* Root hub support */ |
| .hub_control = xhci_hub_control, |
| .hub_status_data = xhci_hub_status_data, |
| + .bus_suspend = xhci_bus_suspend, |
| + .bus_resume = xhci_bus_resume, |
| }; |
| |
| /*-------------------------------------------------------------------------*/ |
| @@ -186,6 +215,11 @@ static struct pci_driver xhci_pci_driver = { |
| /* suspend and resume implemented later */ |
| |
| .shutdown = usb_hcd_pci_shutdown, |
| +#ifdef CONFIG_PM_SLEEP |
| + .driver = { |
| + .pm = &usb_hcd_pci_pm_ops |
| + }, |
| +#endif |
| }; |
| |
| int xhci_register_pci(void) |
| diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c |
| index 48e60d1..9f3115e 100644 |
| |
| |
| @@ -68,6 +68,10 @@ |
| #include <linux/slab.h> |
| #include "xhci.h" |
| |
| +static int handle_cmd_in_cmd_wait_list(struct xhci_hcd *xhci, |
| + struct xhci_virt_device *virt_dev, |
| + struct xhci_event_cmd *event); |
| + |
| /* |
| * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA |
| * address of the TRB. |
| @@ -313,7 +317,7 @@ void xhci_ring_cmd_db(struct xhci_hcd *xhci) |
| xhci_readl(xhci, &xhci->dba->doorbell[0]); |
| } |
| |
| -static void ring_ep_doorbell(struct xhci_hcd *xhci, |
| +void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, |
| unsigned int slot_id, |
| unsigned int ep_index, |
| unsigned int stream_id) |
| @@ -353,7 +357,7 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, |
| /* A ring has pending URBs if its TD list is not empty */ |
| if (!(ep->ep_state & EP_HAS_STREAMS)) { |
| if (!(list_empty(&ep->ring->td_list))) |
| - ring_ep_doorbell(xhci, slot_id, ep_index, 0); |
| + xhci_ring_ep_doorbell(xhci, slot_id, ep_index, 0); |
| return; |
| } |
| |
| @@ -361,7 +365,8 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, |
| stream_id++) { |
| struct xhci_stream_info *stream_info = ep->stream_info; |
| if (!list_empty(&stream_info->stream_rings[stream_id]->td_list)) |
| - ring_ep_doorbell(xhci, slot_id, ep_index, stream_id); |
| + xhci_ring_ep_doorbell(xhci, slot_id, ep_index, |
| + stream_id); |
| } |
| } |
| |
| @@ -626,10 +631,11 @@ static void xhci_giveback_urb_in_irq(struct xhci_hcd *xhci, |
| * bit cleared) so that the HW will skip over them. |
| */ |
| static void handle_stopped_endpoint(struct xhci_hcd *xhci, |
| - union xhci_trb *trb) |
| + union xhci_trb *trb, struct xhci_event_cmd *event) |
| { |
| unsigned int slot_id; |
| unsigned int ep_index; |
| + struct xhci_virt_device *virt_dev; |
| struct xhci_ring *ep_ring; |
| struct xhci_virt_ep *ep; |
| struct list_head *entry; |
| @@ -638,6 +644,21 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, |
| |
| struct xhci_dequeue_state deq_state; |
| |
| + if (unlikely(TRB_TO_SUSPEND_PORT( |
| + xhci->cmd_ring->dequeue->generic.field[3]))) { |
| + slot_id = TRB_TO_SLOT_ID( |
| + xhci->cmd_ring->dequeue->generic.field[3]); |
| + virt_dev = xhci->devs[slot_id]; |
| + if (virt_dev) |
| + handle_cmd_in_cmd_wait_list(xhci, virt_dev, |
| + event); |
| + else |
| + xhci_warn(xhci, "Stop endpoint command " |
| + "completion for disabled slot %u\n", |
| + slot_id); |
| + return; |
| + } |
| + |
| memset(&deq_state, 0, sizeof(deq_state)); |
| slot_id = TRB_TO_SLOT_ID(trb->generic.field[3]); |
| ep_index = TRB_TO_EP_INDEX(trb->generic.field[3]); |
| @@ -1091,7 +1112,7 @@ bandwidth_change: |
| complete(&xhci->addr_dev); |
| break; |
| case TRB_TYPE(TRB_STOP_RING): |
| - handle_stopped_endpoint(xhci, xhci->cmd_ring->dequeue); |
| + handle_stopped_endpoint(xhci, xhci->cmd_ring->dequeue, event); |
| break; |
| case TRB_TYPE(TRB_SET_DEQ): |
| handle_set_deq_completion(xhci, event, xhci->cmd_ring->dequeue); |
| @@ -1144,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); |
| |
| @@ -2347,7 +2423,7 @@ static void giveback_first_trb(struct xhci_hcd *xhci, int slot_id, |
| */ |
| wmb(); |
| start_trb->field[3] |= start_cycle; |
| - ring_ep_doorbell(xhci, slot_id, ep_index, stream_id); |
| + xhci_ring_ep_doorbell(xhci, slot_id, ep_index, stream_id); |
| } |
| |
| /* |
| @@ -2931,7 +3007,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, |
| wmb(); |
| start_trb->field[3] |= start_cycle; |
| |
| - ring_ep_doorbell(xhci, slot_id, ep_index, urb->stream_id); |
| + xhci_ring_ep_doorbell(xhci, slot_id, ep_index, urb->stream_id); |
| return 0; |
| } |
| |
| @@ -3108,15 +3184,20 @@ int xhci_queue_evaluate_context(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, |
| false); |
| } |
| |
| +/* |
| + * Suspend is set to indicate "Stop Endpoint Command" is being issued to stop |
| + * activity on an endpoint that is about to be suspended. |
| + */ |
| int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, |
| - unsigned int ep_index) |
| + unsigned int ep_index, int suspend) |
| { |
| u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); |
| u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); |
| u32 type = TRB_TYPE(TRB_STOP_RING); |
| + u32 trb_suspend = SUSPEND_PORT_FOR_TRB(suspend); |
| |
| return queue_command(xhci, 0, 0, 0, |
| - trb_slot_id | trb_ep_index | type, false); |
| + trb_slot_id | trb_ep_index | type | trb_suspend, false); |
| } |
| |
| /* Set Transfer Ring Dequeue Pointer command. |
| diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c |
| index d5c550e..34f1b3b 100644 |
| |
| |
| @@ -551,6 +551,216 @@ void xhci_shutdown(struct usb_hcd *hcd) |
| xhci_readl(xhci, &xhci->op_regs->status)); |
| } |
| |
| +static void xhci_save_registers(struct xhci_hcd *xhci) |
| +{ |
| + xhci->s3.command = xhci_readl(xhci, &xhci->op_regs->command); |
| + xhci->s3.dev_nt = xhci_readl(xhci, &xhci->op_regs->dev_notification); |
| + xhci->s3.dcbaa_ptr = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); |
| + xhci->s3.config_reg = xhci_readl(xhci, &xhci->op_regs->config_reg); |
| + xhci->s3.irq_pending = xhci_readl(xhci, &xhci->ir_set->irq_pending); |
| + xhci->s3.irq_control = xhci_readl(xhci, &xhci->ir_set->irq_control); |
| + xhci->s3.erst_size = xhci_readl(xhci, &xhci->ir_set->erst_size); |
| + xhci->s3.erst_base = xhci_read_64(xhci, &xhci->ir_set->erst_base); |
| + xhci->s3.erst_dequeue = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); |
| +} |
| + |
| +static void xhci_restore_registers(struct xhci_hcd *xhci) |
| +{ |
| + xhci_writel(xhci, xhci->s3.command, &xhci->op_regs->command); |
| + xhci_writel(xhci, xhci->s3.dev_nt, &xhci->op_regs->dev_notification); |
| + xhci_write_64(xhci, xhci->s3.dcbaa_ptr, &xhci->op_regs->dcbaa_ptr); |
| + xhci_writel(xhci, xhci->s3.config_reg, &xhci->op_regs->config_reg); |
| + xhci_writel(xhci, xhci->s3.irq_pending, &xhci->ir_set->irq_pending); |
| + xhci_writel(xhci, xhci->s3.irq_control, &xhci->ir_set->irq_control); |
| + xhci_writel(xhci, xhci->s3.erst_size, &xhci->ir_set->erst_size); |
| + xhci_write_64(xhci, xhci->s3.erst_base, &xhci->ir_set->erst_base); |
| +} |
| + |
| +/* |
| + * Stop HC (not bus-specific) |
| + * |
| + * This is called when the machine transition into S3/S4 mode. |
| + * |
| + */ |
| +int xhci_suspend(struct xhci_hcd *xhci) |
| +{ |
| + int rc = 0; |
| + struct usb_hcd *hcd = xhci_to_hcd(xhci); |
| + u32 command; |
| + |
| + spin_lock_irq(&xhci->lock); |
| + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); |
| + /* step 1: stop endpoint */ |
| + /* skipped assuming that port suspend has done */ |
| + |
| + /* step 2: clear Run/Stop bit */ |
| + command = xhci_readl(xhci, &xhci->op_regs->command); |
| + command &= ~CMD_RUN; |
| + xhci_writel(xhci, command, &xhci->op_regs->command); |
| + if (handshake(xhci, &xhci->op_regs->status, |
| + STS_HALT, STS_HALT, 100*100)) { |
| + xhci_warn(xhci, "WARN: xHC CMD_RUN timeout\n"); |
| + spin_unlock_irq(&xhci->lock); |
| + return -ETIMEDOUT; |
| + } |
| + |
| + /* step 3: save registers */ |
| + xhci_save_registers(xhci); |
| + |
| + /* step 4: set CSS flag */ |
| + command = xhci_readl(xhci, &xhci->op_regs->command); |
| + command |= CMD_CSS; |
| + xhci_writel(xhci, command, &xhci->op_regs->command); |
| + if (handshake(xhci, &xhci->op_regs->status, STS_SAVE, 0, 10*100)) { |
| + xhci_warn(xhci, "WARN: xHC CMD_CSS timeout\n"); |
| + spin_unlock_irq(&xhci->lock); |
| + return -ETIMEDOUT; |
| + } |
| + /* step 5: remove core well power */ |
| + xhci_cleanup_msix(xhci); |
| + spin_unlock_irq(&xhci->lock); |
| + |
| + return rc; |
| +} |
| + |
| +/* |
| + * start xHC (not bus-specific) |
| + * |
| + * This is called when the machine transition from S3/S4 mode. |
| + * |
| + */ |
| +int xhci_resume(struct xhci_hcd *xhci, bool hibernated) |
| +{ |
| + u32 command, temp = 0; |
| + struct usb_hcd *hcd = xhci_to_hcd(xhci); |
| + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); |
| + u64 val_64; |
| + int old_state, retval; |
| + |
| + old_state = hcd->state; |
| + if (time_before(jiffies, xhci->next_statechange)) |
| + msleep(100); |
| + |
| + spin_lock_irq(&xhci->lock); |
| + |
| + if (!hibernated) { |
| + /* step 1: restore register */ |
| + xhci_restore_registers(xhci); |
| + /* step 2: initialize command ring buffer */ |
| + val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); |
| + val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | |
| + (xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, |
| + xhci->cmd_ring->dequeue) & |
| + (u64) ~CMD_RING_RSVD_BITS) | |
| + xhci->cmd_ring->cycle_state; |
| + xhci_dbg(xhci, "// Setting command ring address to 0x%llx\n", |
| + (long unsigned long) val_64); |
| + xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); |
| + /* step 3: restore state and start state*/ |
| + /* step 3: set CRS flag */ |
| + command = xhci_readl(xhci, &xhci->op_regs->command); |
| + command |= CMD_CRS; |
| + xhci_writel(xhci, command, &xhci->op_regs->command); |
| + if (handshake(xhci, &xhci->op_regs->status, |
| + STS_RESTORE, 0, 10*100)) { |
| + xhci_dbg(xhci, "WARN: xHC CMD_CSS timeout\n"); |
| + spin_unlock_irq(&xhci->lock); |
| + return -ETIMEDOUT; |
| + } |
| + temp = xhci_readl(xhci, &xhci->op_regs->status); |
| + } |
| + |
| + /* If restore operation fails, re-initialize the HC during resume */ |
| + if ((temp & STS_SRE) || hibernated) { |
| + usb_root_hub_lost_power(hcd->self.root_hub); |
| + |
| + xhci_dbg(xhci, "Stop HCD\n"); |
| + xhci_halt(xhci); |
| + xhci_reset(xhci); |
| + if (hibernated) |
| + xhci_cleanup_msix(xhci); |
| + spin_unlock_irq(&xhci->lock); |
| + |
| +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING |
| + /* Tell the event ring poll function not to reschedule */ |
| + xhci->zombie = 1; |
| + del_timer_sync(&xhci->event_ring_timer); |
| +#endif |
| + |
| + xhci_dbg(xhci, "// Disabling event ring interrupts\n"); |
| + temp = xhci_readl(xhci, &xhci->op_regs->status); |
| + xhci_writel(xhci, temp & ~STS_EINT, &xhci->op_regs->status); |
| + temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); |
| + xhci_writel(xhci, ER_IRQ_DISABLE(temp), |
| + &xhci->ir_set->irq_pending); |
| + xhci_print_ir_set(xhci, xhci->ir_set, 0); |
| + |
| + xhci_dbg(xhci, "cleaning up memory\n"); |
| + xhci_mem_cleanup(xhci); |
| + xhci_dbg(xhci, "xhci_stop completed - status = %x\n", |
| + xhci_readl(xhci, &xhci->op_regs->status)); |
| + |
| + xhci_dbg(xhci, "Initialize the HCD\n"); |
| + retval = xhci_init(hcd); |
| + if (retval) |
| + return retval; |
| + |
| + xhci_dbg(xhci, "Start the HCD\n"); |
| + retval = xhci_run(hcd); |
| + if (!retval) |
| + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); |
| + hcd->state = HC_STATE_SUSPENDED; |
| + return retval; |
| + } |
| + |
| + /* Re-setup MSI-X */ |
| + if (hcd->irq) |
| + free_irq(hcd->irq, hcd); |
| + hcd->irq = -1; |
| + |
| + retval = xhci_setup_msix(xhci); |
| + if (retval) |
| + /* fall back to msi*/ |
| + retval = xhci_setup_msi(xhci); |
| + |
| + if (retval) { |
| + /* fall back to legacy interrupt*/ |
| + retval = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED, |
| + hcd->irq_descr, hcd); |
| + if (retval) { |
| + xhci_err(xhci, "request interrupt %d failed\n", |
| + pdev->irq); |
| + return retval; |
| + } |
| + hcd->irq = pdev->irq; |
| + } |
| + |
| + /* step 4: set Run/Stop bit */ |
| + command = xhci_readl(xhci, &xhci->op_regs->command); |
| + command |= CMD_RUN; |
| + xhci_writel(xhci, command, &xhci->op_regs->command); |
| + handshake(xhci, &xhci->op_regs->status, STS_HALT, |
| + 0, 250 * 1000); |
| + |
| + /* step 5: walk topology and initialize portsc, |
| + * portpmsc and portli |
| + */ |
| + /* this is done in bus_resume */ |
| + |
| + /* step 6: restart each of the previously |
| + * Running endpoints by ringing their doorbells |
| + */ |
| + |
| + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); |
| + if (!hibernated) |
| + hcd->state = old_state; |
| + else |
| + hcd->state = HC_STATE_SUSPENDED; |
| + |
| + spin_unlock_irq(&xhci->lock); |
| + return 0; |
| +} |
| + |
| /*-------------------------------------------------------------------------*/ |
| |
| /** |
| @@ -956,7 +1166,7 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) |
| ep->stop_cmd_timer.expires = jiffies + |
| XHCI_STOP_EP_CMD_TIMEOUT * HZ; |
| add_timer(&ep->stop_cmd_timer); |
| - xhci_queue_stop_endpoint(xhci, urb->dev->slot_id, ep_index); |
| + xhci_queue_stop_endpoint(xhci, urb->dev->slot_id, ep_index, 0); |
| xhci_ring_cmd_db(xhci); |
| } |
| done: |
| diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h |
| index 34a60d9..b6d8033 100644 |
| |
| |
| @@ -191,7 +191,7 @@ struct xhci_op_regs { |
| /* bits 4:6 are reserved (and should be preserved on writes). */ |
| /* light reset (port status stays unchanged) - reset completed when this is 0 */ |
| #define CMD_LRESET (1 << 7) |
| -/* FIXME: ignoring host controller save/restore state for now. */ |
| +/* host controller save/restore state. */ |
| #define CMD_CSS (1 << 8) |
| #define CMD_CRS (1 << 9) |
| /* Enable Wrap Event - '1' means xHC generates an event when MFINDEX wraps. */ |
| @@ -269,6 +269,10 @@ struct xhci_op_regs { |
| * A read gives the current link PM state of the port, |
| * a write with Link State Write Strobe set sets the link state. |
| */ |
| +#define PORT_PLS_MASK (0xf << 5) |
| +#define XDEV_U0 (0x0 << 5) |
| +#define XDEV_U3 (0x3 << 5) |
| +#define XDEV_RESUME (0xf << 5) |
| /* true: port has power (see HCC_PPC) */ |
| #define PORT_POWER (1 << 9) |
| /* bits 10:13 indicate device speed: |
| @@ -353,6 +357,8 @@ struct xhci_op_regs { |
| #define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) |
| /* Bits 24:31 for port testing */ |
| |
| +/* USB2 Protocol PORTSPMSC */ |
| +#define PORT_RWE (1 << 0x3) |
| |
| /** |
| * struct xhci_intr_reg - Interrupt Register Set |
| @@ -510,6 +516,7 @@ struct xhci_slot_ctx { |
| #define MAX_EXIT (0xffff) |
| /* Root hub port number that is needed to access the USB device */ |
| #define ROOT_HUB_PORT(p) (((p) & 0xff) << 16) |
| +#define DEVINFO_TO_ROOT_HUB_PORT(p) (((p) >> 16) & 0xff) |
| /* Maximum number of ports under a hub device */ |
| #define XHCI_MAX_PORTS(p) (((p) & 0xff) << 24) |
| |
| @@ -751,6 +758,7 @@ struct xhci_virt_device { |
| /* Status of the last command issued for this device */ |
| u32 cmd_status; |
| struct list_head cmd_list; |
| + u8 port; |
| }; |
| |
| |
| @@ -881,6 +889,10 @@ struct xhci_event_cmd { |
| #define TRB_TO_EP_INDEX(p) ((((p) & (0x1f << 16)) >> 16) - 1) |
| #define EP_ID_FOR_TRB(p) ((((p) + 1) & 0x1f) << 16) |
| |
| +#define SUSPEND_PORT_FOR_TRB(p) (((p) & 1) << 23) |
| +#define TRB_TO_SUSPEND_PORT(p) (((p) & (1 << 23)) >> 23) |
| +#define LAST_EP_INDEX 30 |
| + |
| /* Set TR Dequeue Pointer command TRB fields */ |
| #define TRB_TO_STREAM_ID(p) ((((p) & (0xffff << 16)) >> 16)) |
| #define STREAM_ID_FOR_TRB(p) ((((p)) & 0xffff) << 16) |
| @@ -1115,6 +1127,17 @@ struct urb_priv { |
| #define XHCI_STOP_EP_CMD_TIMEOUT 5 |
| /* XXX: Make these module parameters */ |
| |
| +struct s3_save { |
| + u32 command; |
| + u32 dev_nt; |
| + u64 dcbaa_ptr; |
| + u32 config_reg; |
| + u32 irq_pending; |
| + u32 irq_control; |
| + u32 erst_size; |
| + u64 erst_base; |
| + u64 erst_dequeue; |
| +}; |
| |
| /* There is one ehci_hci structure per controller */ |
| struct xhci_hcd { |
| @@ -1178,6 +1201,12 @@ struct xhci_hcd { |
| #endif |
| /* Host controller watchdog timer structures */ |
| unsigned int xhc_state; |
| + |
| + unsigned long bus_suspended; |
| + unsigned long next_statechange; |
| + |
| + u32 command; |
| + struct s3_save s3; |
| /* Host controller is dying - not responding to commands. "I'm not dead yet!" |
| * |
| * xHC interrupts have been disabled and a watchdog timer will (or has already) |
| @@ -1199,6 +1228,10 @@ struct xhci_hcd { |
| #define XHCI_LINK_TRB_QUIRK (1 << 0) |
| #define XHCI_RESET_EP_QUIRK (1 << 1) |
| #define XHCI_NEC_HOST (1 << 2) |
| + 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 */ |
| @@ -1369,6 +1402,8 @@ int xhci_init(struct usb_hcd *hcd); |
| int xhci_run(struct usb_hcd *hcd); |
| void xhci_stop(struct usb_hcd *hcd); |
| void xhci_shutdown(struct usb_hcd *hcd); |
| +int xhci_suspend(struct xhci_hcd *xhci); |
| +int xhci_resume(struct xhci_hcd *xhci, bool hibernated); |
| int xhci_get_frame(struct usb_hcd *hcd); |
| irqreturn_t xhci_irq(struct usb_hcd *hcd); |
| irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd); |
| @@ -1406,7 +1441,7 @@ int xhci_queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, |
| int xhci_queue_vendor_command(struct xhci_hcd *xhci, |
| u32 field1, u32 field2, u32 field3, u32 field4); |
| int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, |
| - unsigned int ep_index); |
| + unsigned int ep_index, int suspend); |
| int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, |
| int slot_id, unsigned int ep_index); |
| int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, |
| @@ -1436,11 +1471,18 @@ void xhci_queue_config_ep_quirk(struct xhci_hcd *xhci, |
| unsigned int slot_id, unsigned int ep_index, |
| struct xhci_dequeue_state *deq_state); |
| void xhci_stop_endpoint_command_watchdog(unsigned long arg); |
| +void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, |
| + unsigned int ep_index, unsigned int stream_id); |
| |
| /* xHCI roothub code */ |
| 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); |
| +int xhci_bus_suspend(struct usb_hcd *hcd); |
| +int xhci_bus_resume(struct usb_hcd *hcd); |
| +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); |