From 64927730c66333c9d5987aa72a0e6d44ed91cec7 Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Thu, 14 Oct 2010 07:22:45 -0700 Subject: USB: xHCI: Add pointer to udev in struct xhci_virt_device Add a pointer to udev in struct xhci_virt_device. When allocate a new virt_device, make the pointer point to the corresponding udev. Modify xhci_check_args(), check if virt_dev->udev matches the target udev, to make sure command is issued to the right device. Signed-off-by: Andiry Xu Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 83 ++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 49 deletions(-) (limited to 'drivers/usb/host/xhci.c') diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index d5c550ea3e68..0bec04070334 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -607,7 +607,11 @@ unsigned int xhci_last_valid_endpoint(u32 added_ctxs) * returns 0 this is a root hub; returns -EINVAL for NULL pointers. */ int xhci_check_args(struct usb_hcd *hcd, struct usb_device *udev, - struct usb_host_endpoint *ep, int check_ep, const char *func) { + struct usb_host_endpoint *ep, int check_ep, bool check_virt_dev, + const char *func) { + struct xhci_hcd *xhci; + struct xhci_virt_device *virt_dev; + if (!hcd || (check_ep && !ep) || !udev) { printk(KERN_DEBUG "xHCI %s called with invalid args\n", func); @@ -618,11 +622,24 @@ int xhci_check_args(struct usb_hcd *hcd, struct usb_device *udev, func); return 0; } - if (!udev->slot_id) { - printk(KERN_DEBUG "xHCI %s called with unaddressed device\n", - func); - return -EINVAL; + + if (check_virt_dev) { + xhci = hcd_to_xhci(hcd); + if (!udev->slot_id || !xhci->devs + || !xhci->devs[udev->slot_id]) { + printk(KERN_DEBUG "xHCI %s called with unaddressed " + "device\n", func); + return -EINVAL; + } + + virt_dev = xhci->devs[udev->slot_id]; + if (virt_dev->udev != udev) { + printk(KERN_DEBUG "xHCI %s called with udev and " + "virt_dev does not match\n", func); + return -EINVAL; + } } + return 1; } @@ -704,18 +721,13 @@ int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) struct urb_priv *urb_priv; int size, i; - if (!urb || xhci_check_args(hcd, urb->dev, urb->ep, true, __func__) <= 0) + if (!urb || xhci_check_args(hcd, urb->dev, urb->ep, + true, true, __func__) <= 0) return -EINVAL; slot_id = urb->dev->slot_id; ep_index = xhci_get_endpoint_index(&urb->ep->desc); - if (!xhci->devs || !xhci->devs[slot_id]) { - if (!in_interrupt()) - dev_warn(&urb->dev->dev, "WARN: urb submitted for dev with no Slot ID\n"); - ret = -EINVAL; - goto exit; - } if (!HCD_HW_ACCESSIBLE(hcd)) { if (!in_interrupt()) xhci_dbg(xhci, "urb submitted during PCI suspend\n"); @@ -991,7 +1003,7 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, u32 new_add_flags, new_drop_flags, new_slot_info; int ret; - ret = xhci_check_args(hcd, udev, ep, 1, __func__); + ret = xhci_check_args(hcd, udev, ep, 1, true, __func__); if (ret <= 0) return ret; xhci = hcd_to_xhci(hcd); @@ -1004,12 +1016,6 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, return 0; } - if (!xhci->devs || !xhci->devs[udev->slot_id]) { - xhci_warn(xhci, "xHCI %s called with unaddressed device\n", - __func__); - return -EINVAL; - } - in_ctx = xhci->devs[udev->slot_id]->in_ctx; out_ctx = xhci->devs[udev->slot_id]->out_ctx; ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); @@ -1078,7 +1084,7 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, u32 new_add_flags, new_drop_flags, new_slot_info; int ret = 0; - ret = xhci_check_args(hcd, udev, ep, 1, __func__); + ret = xhci_check_args(hcd, udev, ep, 1, true, __func__); if (ret <= 0) { /* So we won't queue a reset ep command for a root hub */ ep->hcpriv = NULL; @@ -1098,12 +1104,6 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, return 0; } - if (!xhci->devs || !xhci->devs[udev->slot_id]) { - xhci_warn(xhci, "xHCI %s called with unaddressed device\n", - __func__); - return -EINVAL; - } - in_ctx = xhci->devs[udev->slot_id]->in_ctx; out_ctx = xhci->devs[udev->slot_id]->out_ctx; ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); @@ -1346,16 +1346,11 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) struct xhci_input_control_ctx *ctrl_ctx; struct xhci_slot_ctx *slot_ctx; - ret = xhci_check_args(hcd, udev, NULL, 0, __func__); + ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); if (ret <= 0) return ret; xhci = hcd_to_xhci(hcd); - if (!udev->slot_id || !xhci->devs || !xhci->devs[udev->slot_id]) { - xhci_warn(xhci, "xHCI %s called with unaddressed device\n", - __func__); - return -EINVAL; - } xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); virt_dev = xhci->devs[udev->slot_id]; @@ -1405,16 +1400,11 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) struct xhci_virt_device *virt_dev; int i, ret; - ret = xhci_check_args(hcd, udev, NULL, 0, __func__); + ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); if (ret <= 0) return; xhci = hcd_to_xhci(hcd); - if (!xhci->devs || !xhci->devs[udev->slot_id]) { - xhci_warn(xhci, "xHCI %s called with unaddressed device\n", - __func__); - return; - } xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); virt_dev = xhci->devs[udev->slot_id]; /* Free any rings allocated for added endpoints */ @@ -1575,7 +1565,7 @@ static int xhci_check_streams_endpoint(struct xhci_hcd *xhci, if (!ep) return -EINVAL; - ret = xhci_check_args(xhci_to_hcd(xhci), udev, ep, 1, __func__); + ret = xhci_check_args(xhci_to_hcd(xhci), udev, ep, 1, true, __func__); if (ret <= 0) return -EINVAL; if (ep->ss_ep_comp.bmAttributes == 0) { @@ -1965,17 +1955,12 @@ int xhci_reset_device(struct usb_hcd *hcd, struct usb_device *udev) int timeleft; int last_freed_endpoint; - ret = xhci_check_args(hcd, udev, NULL, 0, __func__); + ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); if (ret <= 0) return ret; xhci = hcd_to_xhci(hcd); slot_id = udev->slot_id; virt_dev = xhci->devs[slot_id]; - if (!virt_dev) { - xhci_dbg(xhci, "%s called with invalid slot ID %u\n", - __func__, slot_id); - return -EINVAL; - } xhci_dbg(xhci, "Resetting device with slot ID %u\n", slot_id); /* Allocate the command structure that holds the struct completion. @@ -2077,13 +2062,13 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) struct xhci_virt_device *virt_dev; unsigned long flags; u32 state; - int i; + int i, ret; - if (udev->slot_id == 0) + ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); + if (ret <= 0) return; + virt_dev = xhci->devs[udev->slot_id]; - if (!virt_dev) - return; /* Stop any wayward timer functions (which may grab the lock) */ for (i = 0; i < 31; ++i) { -- cgit v1.2.3 From f0615c45ce5feb141c1172480c5198d4b8d25436 Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Thu, 14 Oct 2010 07:22:48 -0700 Subject: USB: xHCI: change xhci_reset_device() to allocate new device Rename xhci_reset_device() to xhci_discover_or_reset_device(). If xhci_discover_or_reset_device() is called to reset a device which does not exist or does not match the udev, it calls xhci_alloc_dev() to re-allocate the device. This would prevent the reset device failure, possibly due to the xHC restore error during S3/S4 resume. Signed-off-by: Andiry Xu Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 2 +- drivers/usb/host/xhci.c | 44 +++++++++++++++++++++++++++++++++++++++----- drivers/usb/host/xhci.h | 2 +- 3 files changed, 41 insertions(+), 7 deletions(-) (limited to 'drivers/usb/host/xhci.c') diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index f7efe025beda..aefc3496376a 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -152,7 +152,7 @@ static const struct hc_driver xhci_pci_hc_driver = { .reset_bandwidth = xhci_reset_bandwidth, .address_device = xhci_address_device, .update_hub_device = xhci_update_hub_device, - .reset_device = xhci_reset_device, + .reset_device = xhci_discover_or_reset_device, /* * scheduling support diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 0bec04070334..7928af5c91cb 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1943,8 +1943,13 @@ int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, * Wait for the Reset Device command to finish. Remove all structures * associated with the endpoints that were disabled. Clear the input device * structure? Cache the rings? Reset the control endpoint 0 max packet size? + * + * If the virt_dev to be reset does not exist or does not match the udev, + * it means the device is lost, possibly due to the xHC restore error and + * re-initialization during S3/S4. In this case, call xhci_alloc_dev() to + * re-allocate the device. */ -int xhci_reset_device(struct usb_hcd *hcd, struct usb_device *udev) +int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev) { int ret, i; unsigned long flags; @@ -1955,12 +1960,36 @@ int xhci_reset_device(struct usb_hcd *hcd, struct usb_device *udev) int timeleft; int last_freed_endpoint; - ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); + ret = xhci_check_args(hcd, udev, NULL, 0, false, __func__); if (ret <= 0) return ret; xhci = hcd_to_xhci(hcd); slot_id = udev->slot_id; virt_dev = xhci->devs[slot_id]; + if (!virt_dev) { + xhci_dbg(xhci, "The device to be reset with slot ID %u does " + "not exist. Re-allocate the device\n", slot_id); + ret = xhci_alloc_dev(hcd, udev); + if (ret == 1) + return 0; + else + return -EINVAL; + } + + if (virt_dev->udev != udev) { + /* If the virt_dev and the udev does not match, this virt_dev + * may belong to another udev. + * Re-allocate the device. + */ + xhci_dbg(xhci, "The device to be reset with slot ID %u does " + "not match the udev. Re-allocate the device\n", + slot_id); + ret = xhci_alloc_dev(hcd, udev); + if (ret == 1) + return 0; + else + return -EINVAL; + } xhci_dbg(xhci, "Resetting device with slot ID %u\n", slot_id); /* Allocate the command structure that holds the struct completion. @@ -2176,12 +2205,17 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) virt_dev = xhci->devs[udev->slot_id]; - /* If this is a Set Address to an unconfigured device, setup ep 0 */ - if (!udev->config) + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + /* + * If this is the first Set Address since device plug-in or + * virt_device realloaction after a resume with an xHCI power loss, + * then set up the slot context. + */ + if (!slot_ctx->dev_info) xhci_setup_addressable_virt_dev(xhci, udev); + /* Otherwise, update the control endpoint ring enqueue pointer. */ else xhci_copy_ep0_dequeue_into_input_ctx(xhci, udev); - /* Otherwise, assume the core has the device configured how it wants */ xhci_dbg(xhci, "Slot ID %d Input Context:\n", udev->slot_id); xhci_dbg_ctx(xhci, virt_dev->in_ctx, 2); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index f03f140a7d9a..490409f918f2 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1389,7 +1389,7 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep); int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep); void xhci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep); -int xhci_reset_device(struct usb_hcd *hcd, struct usb_device *udev); +int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev); int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); -- cgit v1.2.3 From c8d4af8e2af12cd4835ba5c4b54bdafe9deda71a Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Thu, 14 Oct 2010 07:22:51 -0700 Subject: USB: core: use kernel assigned address for devices under xHCI xHCI driver uses hardware assigned device address. This may cause device address conflict in certain cases. Use kernel assigned address for devices under xHCI. Store the xHC assigned address locally in xHCI driver. Signed-off-by: Andiry Xu Signed-off-by: Sarah Sharp --- drivers/usb/core/hub.c | 27 +++++++++++++-------------- drivers/usb/host/xhci.c | 8 ++++---- drivers/usb/host/xhci.h | 2 ++ 3 files changed, 19 insertions(+), 18 deletions(-) (limited to 'drivers/usb/host/xhci.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5da546c4fd79..f07ab71859dd 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2594,16 +2594,14 @@ static int hub_set_address(struct usb_device *udev, int devnum) return 0; if (udev->state != USB_STATE_DEFAULT) return -EINVAL; - if (hcd->driver->address_device) { + if (hcd->driver->address_device) retval = hcd->driver->address_device(hcd, udev); - } else { + else retval = usb_control_msg(udev, usb_sndaddr0pipe(), USB_REQ_SET_ADDRESS, 0, devnum, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); - if (retval == 0) - update_address(udev, devnum); - } if (retval == 0) { + update_address(udev, devnum); /* Device now using proper address. */ usb_set_device_state(udev, USB_STATE_ADDRESS); usb_ep0_reinit(udev); @@ -3097,16 +3095,17 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, udev->speed = USB_SPEED_UNKNOWN; /* - * xHCI needs to issue an address device command later - * in the hub_port_init sequence for SS/HS/FS/LS devices. + * Set the address. + * Note xHCI needs to issue an address device command later + * in the hub_port_init sequence for SS/HS/FS/LS devices, + * and xHC will assign an address to the device. But use + * kernel assigned address here, to avoid any address conflict + * issue. */ - if (!(hcd->driver->flags & HCD_USB3)) { - /* set the address */ - choose_address(udev); - if (udev->devnum <= 0) { - status = -ENOTCONN; /* Don't retry */ - goto loop; - } + choose_address(udev); + if (udev->devnum <= 0) { + status = -ENOTCONN; /* Don't retry */ + goto loop; } /* reset (non-USB 3.0 devices) and get descriptor */ diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 7928af5c91cb..caccecb7368e 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -2287,15 +2287,15 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) * address given back to us by the HC. */ slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); - udev->devnum = (slot_ctx->dev_state & DEV_ADDR_MASK) + 1; + /* Use kernel assigned address for devices; store xHC assigned + * address locally. */ + virt_dev->address = (slot_ctx->dev_state & DEV_ADDR_MASK) + 1; /* Zero the input context control for later use */ ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); ctrl_ctx->add_flags = 0; ctrl_ctx->drop_flags = 0; - xhci_dbg(xhci, "Device address = %d\n", udev->devnum); - /* XXX Meh, not sure if anyone else but choose_address uses this. */ - set_bit(udev->devnum, udev->bus->devmap.devicemap); + xhci_dbg(xhci, "Internal device address = %d\n", virt_dev->address); return 0; } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 490409f918f2..a7181b491e67 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -746,6 +746,8 @@ struct xhci_virt_device { /* Rings saved to ensure old alt settings can be re-instated */ struct xhci_ring **ring_cache; int num_rings_cached; + /* Store xHC assigned device address */ + int address; #define XHCI_MAX_RINGS_CACHED 31 struct xhci_virt_ep eps[31]; struct completion cmd_completion; -- cgit v1.2.3 From be88fe4f4dda93e3264a887745123b1e6c4a6845 Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Thu, 14 Oct 2010 07:22:57 -0700 Subject: 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 Signed-off-by: Libin Yang Signed-off-by: Andiry Xu Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 196 ++++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/xhci-mem.c | 1 + drivers/usb/host/xhci-ring.c | 44 ++++++++-- drivers/usb/host/xhci.c | 2 +- drivers/usb/host/xhci.h | 17 +++- 5 files changed, 248 insertions(+), 12 deletions(-) (limited to 'drivers/usb/host/xhci.c') diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index a1a7a9795536..14b48b261e06 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -129,6 +129,99 @@ static u32 xhci_port_state_to_neutral(u32 state) return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); } +/* + * find slot id based on port number. + */ +static 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. + */ +static 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 +255,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; @@ -182,6 +279,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u32 temp, status; int retval = 0; u32 __iomem *addr; + int slot_id; ports = HCS_MAX_PORTS(xhci->hcs_params1); @@ -211,9 +309,21 @@ 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_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 +336,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 +350,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 +419,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: diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index be901808e474..858a82867e1d 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -867,6 +867,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? */ diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 48e60d166ff0..b18e00ecb468 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -68,6 +68,10 @@ #include #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); @@ -2347,7 +2368,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 +2952,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 +3129,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 caccecb7368e..3d2af688157a 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -968,7 +968,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 a7181b491e67..73e5db3e89c9 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -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: @@ -510,6 +514,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) @@ -754,6 +759,7 @@ struct xhci_virt_device { /* Status of the last command issued for this device */ u32 cmd_status; struct list_head cmd_list; + u8 port; }; @@ -884,6 +890,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) @@ -1202,6 +1212,9 @@ 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 */ }; /* For testing purposes */ @@ -1409,7 +1422,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, @@ -1439,6 +1452,8 @@ 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, -- cgit v1.2.3 From 5535b1d5f8885695c6ded783c692e3c0d0eda8ca Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Thu, 14 Oct 2010 07:23:06 -0700 Subject: 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 Signed-off-by: Dong Nguyen Signed-off-by: Andiry Xu Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 34 ++++++- drivers/usb/host/xhci.c | 210 ++++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci.h | 16 +++- 3 files changed, 258 insertions(+), 2 deletions(-) (limited to 'drivers/usb/host/xhci.c') diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 3865f8c6f647..bb668a894ab9 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, @@ -188,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.c b/drivers/usb/host/xhci.c index 3d2af688157a..33d0034d8a6f 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; +} + /*-------------------------------------------------------------------------*/ /** diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 196e21fb36ff..c08928adc524 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. */ @@ -1130,6 +1130,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 { @@ -1198,6 +1209,7 @@ struct xhci_hcd { 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) @@ -1393,6 +1405,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); -- cgit v1.2.3 From b5b5c3ac5123dab047cfeff472053ab737052e9b Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Fri, 15 Oct 2010 11:24:14 -0700 Subject: USB: xhci: Fix compile error when CONFIG_PM=n Fix this error when CONFIG_PM is not enabled: drivers/usb/host/xhci.c:675: error: implicit declaration of function 'usb_root_hub_lost_power' Wrap xhci_suspend() and xhci_resume() into an ifdef CONFIG_PM, along with the functions that only they call -- xhci_save_registers() and xhci_restore_registers(). Reported-by: Randy Dunlap Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers/usb/host/xhci.c') diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 33d0034d8a6f..9dc5be557d92 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -551,6 +551,7 @@ void xhci_shutdown(struct usb_hcd *hcd) xhci_readl(xhci, &xhci->op_regs->status)); } +#ifdef CONFIG_PM static void xhci_save_registers(struct xhci_hcd *xhci) { xhci->s3.command = xhci_readl(xhci, &xhci->op_regs->command); @@ -761,6 +762,13 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) return 0; } +#else + +#define xhci_suspend NULL +#define xhci_resume NULL + +#endif /* CONFIG_PM */ + /*-------------------------------------------------------------------------*/ /** -- cgit v1.2.3 From 436a389096e1feda2c382cad83b6a8d6de8615a0 Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Fri, 15 Oct 2010 14:59:15 -0700 Subject: usb: Fix linker errors with CONFIG_PM=n Fix these linker errors when CONFIG_PM=n: ERROR: "xhci_bus_resume" [drivers/usb/host/xhci-hcd.ko] undefined! ERROR: "xhci_bus_suspend" [drivers/usb/host/xhci-hcd.ko] undefined! Reported-by: Randy Dunlap Signed-off-by: Sarah Sharp Acked-by: Randy Dunlap Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 7 +------ drivers/usb/host/xhci.c | 6 ------ drivers/usb/host/xhci.h | 14 ++++++++++++++ 3 files changed, 15 insertions(+), 12 deletions(-) (limited to 'drivers/usb/host/xhci.c') diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 7f2f63cb6c53..fef5a1f9d483 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -742,9 +742,4 @@ int xhci_bus_resume(struct usb_hcd *hcd) return 0; } -#else - -#define xhci_bus_suspend NULL -#define xhci_bus_resume NULL - -#endif +#endif /* CONFIG_PM */ diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 9dc5be557d92..5d7d4e951ea4 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -761,12 +761,6 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) spin_unlock_irq(&xhci->lock); return 0; } - -#else - -#define xhci_suspend NULL -#define xhci_resume NULL - #endif /* CONFIG_PM */ /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index c08928adc524..93d3bf4d213c 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1405,8 +1405,15 @@ 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); + +#ifdef CONFIG_PM int xhci_suspend(struct xhci_hcd *xhci); int xhci_resume(struct xhci_hcd *xhci, bool hibernated); +#else +#define xhci_suspend NULL +#define xhci_resume NULL +#endif + 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); @@ -1481,8 +1488,15 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); + +#ifdef CONFIG_PM int xhci_bus_suspend(struct usb_hcd *hcd); int xhci_bus_resume(struct usb_hcd *hcd); +#else +#define xhci_bus_suspend NULL +#define xhci_bus_resume NULL +#endif /* CONFIG_PM */ + 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); -- cgit v1.2.3