summaryrefslogtreecommitdiff
path: root/drivers/usb/host/xhci-hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
-rw-r--r--drivers/usb/host/xhci-hub.c211
1 files changed, 199 insertions, 12 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index a8a2d5005e6e..fae1222d4bc8 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -20,7 +20,7 @@
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-
+#include <linux/gfp.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
@@ -376,10 +376,6 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend)
int i;
ret = 0;
- virt_dev = xhci->devs[slot_id];
- if (!virt_dev)
- return -ENODEV;
-
cmd = xhci_alloc_command(xhci, false, true, GFP_NOIO);
if (!cmd) {
xhci_dbg(xhci, "Couldn't allocate command structure.\n");
@@ -387,6 +383,13 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend)
}
spin_lock_irqsave(&xhci->lock, flags);
+ virt_dev = xhci->devs[slot_id];
+ if (!virt_dev) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ xhci_free_command(xhci, cmd);
+ return -ENODEV;
+ }
+
for (i = LAST_EP_INDEX; i > 0; i--) {
if (virt_dev->eps[i].ring && virt_dev->eps[i].ring->dequeue) {
struct xhci_command *command;
@@ -398,11 +401,21 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend)
return -ENOMEM;
}
- xhci_queue_stop_endpoint(xhci, command, slot_id, i,
- suspend);
+
+ ret = xhci_queue_stop_endpoint(xhci, command, slot_id,
+ i, suspend);
+ if (ret) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ xhci_free_command(xhci, command);
+ goto err_cmd_queue;
+ }
}
}
- xhci_queue_stop_endpoint(xhci, cmd, slot_id, 0, suspend);
+ ret = xhci_queue_stop_endpoint(xhci, cmd, slot_id, 0, suspend);
+ if (ret) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ goto err_cmd_queue;
+ }
xhci_ring_cmd_db(xhci);
spin_unlock_irqrestore(&xhci->lock, flags);
@@ -413,6 +426,8 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend)
xhci_warn(xhci, "Timeout while waiting for stop endpoint command\n");
ret = -ETIME;
}
+
+err_cmd_queue:
xhci_free_command(xhci, cmd);
return ret;
}
@@ -866,6 +881,151 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,
return status;
}
+static void xhci_single_step_completion(struct urb *urb)
+{
+ struct completion *done = urb->context;
+
+ complete(done);
+}
+
+/*
+ * Allocate a URB and initialize the various fields of it.
+ * This API is used by the single_step_set_feature test of
+ * EHSET where IN packet of the GetDescriptor request is
+ * sent 15secs after the SETUP packet.
+ * Return NULL if failed.
+ */
+static struct urb *xhci_request_single_step_set_feature_urb(
+ struct usb_device *udev,
+ void *dr,
+ void *buf,
+ struct completion *done)
+{
+ struct urb *urb;
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+ struct usb_host_endpoint *ep;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return NULL;
+
+ urb->pipe = usb_rcvctrlpipe(udev, 0);
+ ep = udev->ep_in[usb_pipeendpoint(urb->pipe)];
+ if (!ep) {
+ usb_free_urb(urb);
+ return NULL;
+ }
+
+ /*
+ * Initialize the various URB fields as these are used by the HCD
+ * driver to queue it and as well as when completion happens.
+ */
+ urb->ep = ep;
+ urb->dev = udev;
+ urb->setup_packet = dr;
+ urb->transfer_buffer = buf;
+ urb->transfer_buffer_length = USB_DT_DEVICE_SIZE;
+ urb->complete = xhci_single_step_completion;
+ urb->status = -EINPROGRESS;
+ urb->actual_length = 0;
+ urb->transfer_flags = URB_DIR_IN;
+ usb_get_urb(urb);
+ atomic_inc(&urb->use_count);
+ atomic_inc(&urb->dev->urbnum);
+ usb_hcd_map_urb_for_dma(hcd, urb, GFP_KERNEL);
+ urb->context = done;
+ return urb;
+}
+
+/*
+ * This function implements the USB_PORT_FEAT_TEST handling of the
+ * SINGLE_STEP_SET_FEATURE test mode as defined in the Embedded
+ * High-Speed Electrical Test (EHSET) specification. This simply
+ * issues a GetDescriptor control transfer, with an inserted 15-second
+ * delay after the end of the SETUP stage and before the IN token of
+ * the DATA stage is set. The idea is that this gives the test operator
+ * enough time to configure the oscilloscope to perform a measurement
+ * of the response time between the DATA and ACK packets that follow.
+ */
+static int xhci_ehset_single_step_set_feature(struct usb_hcd *hcd, int port)
+{
+ int retval;
+ struct usb_ctrlrequest *dr;
+ struct urb *urb;
+ struct usb_device *udev;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct usb_device_descriptor *buf;
+ unsigned long flags;
+ DECLARE_COMPLETION_ONSTACK(done);
+
+ /* Obtain udev of the rhub's child port */
+ udev = usb_hub_find_child(hcd->self.root_hub, port);
+ if (!udev) {
+ xhci_err(xhci, "No device attached to the RootHub\n");
+ return -ENODEV;
+ }
+ buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
+ if (!dr) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ /* Fill Setup packet for GetDescriptor */
+ dr->bRequestType = USB_DIR_IN;
+ dr->bRequest = USB_REQ_GET_DESCRIPTOR;
+ dr->wValue = cpu_to_le16(USB_DT_DEVICE << 8);
+ dr->wIndex = 0;
+ dr->wLength = cpu_to_le16(USB_DT_DEVICE_SIZE);
+ urb = xhci_request_single_step_set_feature_urb(udev, dr, buf, &done);
+ if (!urb) {
+ retval = -ENOMEM;
+ goto cleanup;
+ }
+
+ /* Now complete just the SETUP stage */
+ spin_lock_irqsave(&xhci->lock, flags);
+ retval = xhci_submit_single_step_set_feature(hcd, urb, 1);
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ if (retval)
+ goto out1;
+
+ if (!wait_for_completion_timeout(&done, msecs_to_jiffies(2000))) {
+ usb_kill_urb(urb);
+ retval = -ETIMEDOUT;
+ xhci_err(xhci, "%s SETUP stage timed out on ep0\n", __func__);
+ goto out1;
+ }
+
+ /* Sleep for 15 seconds; HC will send SOFs during this period */
+ msleep(15 * 1000);
+
+ /* Complete remaining DATA and status stages. Re-use same URB */
+ urb->status = -EINPROGRESS;
+ usb_get_urb(urb);
+ atomic_inc(&urb->use_count);
+ atomic_inc(&urb->dev->urbnum);
+
+ spin_lock_irqsave(&xhci->lock, flags);
+ retval = xhci_submit_single_step_set_feature(hcd, urb, 0);
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ if (!retval && !wait_for_completion_timeout(&done,
+ msecs_to_jiffies(2000))) {
+ usb_kill_urb(urb);
+ retval = -ETIMEDOUT;
+ xhci_err(xhci, "%s IN stage timed out on ep0\n", __func__);
+ }
+out1:
+ usb_free_urb(urb);
+cleanup:
+ kfree(dr);
+ kfree(buf);
+ return retval;
+}
+
int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 wIndex, char *buf, u16 wLength)
{
@@ -880,6 +1040,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 link_state = 0;
u16 wake_mask = 0;
u16 timeout = 0;
+ u16 test_mode = 0;
max_ports = xhci_get_ports(hcd, &port_array);
bus_state = &xhci->bus_state[hcd_index(hcd)];
@@ -953,8 +1114,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
link_state = (wIndex & 0xff00) >> 3;
if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK)
wake_mask = wIndex & 0xff00;
- /* The MSB of wIndex is the U1/U2 timeout */
- timeout = (wIndex & 0xff00) >> 8;
+ /* The MSB of wIndex is the U1/U2 timeout OR TEST mode*/
+ test_mode = timeout = (wIndex & 0xff00) >> 8;
wIndex &= 0xff;
if (!wIndex || wIndex > max_ports)
goto error;
@@ -1128,6 +1289,32 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
temp |= PORT_U2_TIMEOUT(timeout);
writel(temp, port_array[wIndex] + PORTPMSC);
break;
+ case USB_PORT_FEAT_TEST:
+ slot_id = xhci_find_slot_id_by_port(hcd, xhci,
+ wIndex + 1);
+ if (test_mode && test_mode <= 5) {
+ /* 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);
+ xhci_halt(xhci);
+
+ temp = readl_relaxed(port_array[wIndex] +
+ PORTPMSC);
+ temp |= test_mode << 28;
+ writel_relaxed(temp, port_array[wIndex] +
+ PORTPMSC);
+ /* to make sure above write goes through */
+ mb();
+ } else if (test_mode == 6) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ retval = xhci_ehset_single_step_set_feature(hcd,
+ wIndex);
+ spin_lock_irqsave(&xhci->lock, flags);
+ } else {
+ goto error;
+ }
+ break;
default:
goto error;
}
@@ -1160,7 +1347,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
xhci_set_link_state(xhci, port_array, wIndex,
XDEV_RESUME);
spin_unlock_irqrestore(&xhci->lock, flags);
- msleep(USB_RESUME_TIMEOUT);
+ usleep_range(21000, 21500);
spin_lock_irqsave(&xhci->lock, flags);
xhci_set_link_state(xhci, port_array, wIndex,
XDEV_U0);
@@ -1441,7 +1628,7 @@ int xhci_bus_resume(struct usb_hcd *hcd)
if (need_usb2_u3_exit) {
spin_unlock_irqrestore(&xhci->lock, flags);
- msleep(USB_RESUME_TIMEOUT);
+ usleep_range(21000, 21500);
spin_lock_irqsave(&xhci->lock, flags);
}