1290 lines
41 KiB
Diff
1290 lines
41 KiB
Diff
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
|
||
--- a/drivers/usb/host/xhci-hub.c
|
||
+++ b/drivers/usb/host/xhci-hub.c
|
||
@@ -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
|
||
--- a/drivers/usb/host/xhci-mem.c
|
||
+++ b/drivers/usb/host/xhci-mem.c
|
||
@@ -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
|
||
--- a/drivers/usb/host/xhci-pci.c
|
||
+++ b/drivers/usb/host/xhci-pci.c
|
||
@@ -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
|
||
--- a/drivers/usb/host/xhci-ring.c
|
||
+++ b/drivers/usb/host/xhci-ring.c
|
||
@@ -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
|
||
--- a/drivers/usb/host/xhci.c
|
||
+++ b/drivers/usb/host/xhci.c
|
||
@@ -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
|
||
--- a/drivers/usb/host/xhci.h
|
||
+++ b/drivers/usb/host/xhci.h
|
||
@@ -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);
|