kernel/xhci_hcd-suspend-resume.patch

1290 lines
41 KiB
Diff
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
/* Array of pointers to USB 2.0 PORTSC registers */
u32 __iomem **usb2_ports;
unsigned int num_usb2_ports;
+ 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);