| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * xHCI host controller driver | 
|  | 3 | * | 
|  | 4 | * Copyright (C) 2008 Intel Corp. | 
|  | 5 | * | 
|  | 6 | * Author: Sarah Sharp | 
|  | 7 | * Some code borrowed from the Linux EHCI driver. | 
|  | 8 | * | 
|  | 9 | * This program is free software; you can redistribute it and/or modify | 
|  | 10 | * it under the terms of the GNU General Public License version 2 as | 
|  | 11 | * published by the Free Software Foundation. | 
|  | 12 | * | 
|  | 13 | * This program is distributed in the hope that it will be useful, but | 
|  | 14 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | 
|  | 15 | * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License | 
|  | 16 | * for more details. | 
|  | 17 | * | 
|  | 18 | * You should have received a copy of the GNU General Public License | 
|  | 19 | * along with this program; if not, write to the Free Software Foundation, | 
|  | 20 | * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | 
|  | 21 | */ | 
|  | 22 |  | 
| Paul Gortmaker | 4bcbcc9 | 2011-07-18 14:42:00 -0400 | [diff] [blame] | 23 | #include <linux/gfp.h> | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 24 | #include <asm/unaligned.h> | 
|  | 25 |  | 
|  | 26 | #include "xhci.h" | 
|  | 27 |  | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 28 | #define	PORT_WAKE_BITS	(PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E) | 
|  | 29 | #define	PORT_RWC_BITS	(PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ | 
|  | 30 | PORT_RC | PORT_PLC | PORT_PE) | 
|  | 31 |  | 
| Sarah Sharp | 48e8236 | 2011-10-06 11:54:23 -0700 | [diff] [blame] | 32 | /* usb 1.1 root hub device descriptor */ | 
|  | 33 | static u8 usb_bos_descriptor [] = { | 
|  | 34 | USB_DT_BOS_SIZE,		/*  __u8 bLength, 5 bytes */ | 
|  | 35 | USB_DT_BOS,			/*  __u8 bDescriptorType */ | 
|  | 36 | 0x0F, 0x00,			/*  __le16 wTotalLength, 15 bytes */ | 
|  | 37 | 0x1,				/*  __u8 bNumDeviceCaps */ | 
|  | 38 | /* First device capability */ | 
|  | 39 | USB_DT_USB_SS_CAP_SIZE,		/*  __u8 bLength, 10 bytes */ | 
|  | 40 | USB_DT_DEVICE_CAPABILITY,	/* Device Capability */ | 
|  | 41 | USB_SS_CAP_TYPE,		/* bDevCapabilityType, SUPERSPEED_USB */ | 
|  | 42 | 0x00,				/* bmAttributes, LTM off by default */ | 
|  | 43 | USB_5GBPS_OPERATION, 0x00,	/* wSpeedsSupported, 5Gbps only */ | 
|  | 44 | 0x03,				/* bFunctionalitySupport, | 
|  | 45 | USB 3.0 speed only */ | 
|  | 46 | 0x00,				/* bU1DevExitLat, set later. */ | 
|  | 47 | 0x00, 0x00			/* __le16 bU2DevExitLat, set later. */ | 
|  | 48 | }; | 
|  | 49 |  | 
|  | 50 |  | 
| Sarah Sharp | 4bbb0ac | 2010-11-29 16:14:37 -0800 | [diff] [blame] | 51 | static void xhci_common_hub_descriptor(struct xhci_hcd *xhci, | 
|  | 52 | struct usb_hub_descriptor *desc, int ports) | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 53 | { | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 54 | u16 temp; | 
|  | 55 |  | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 56 | desc->bPwrOn2PwrGood = 10;	/* xhci section 5.4.9 says 20ms max */ | 
|  | 57 | desc->bHubContrCurrent = 0; | 
|  | 58 |  | 
|  | 59 | desc->bNbrPorts = ports; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 60 | /* Ugh, these should be #defines, FIXME */ | 
|  | 61 | /* Using table 11-13 in USB 2.0 spec. */ | 
|  | 62 | temp = 0; | 
|  | 63 | /* Bits 1:0 - support port power switching, or power always on */ | 
|  | 64 | if (HCC_PPC(xhci->hcc_params)) | 
|  | 65 | temp |= 0x0001; | 
|  | 66 | else | 
|  | 67 | temp |= 0x0002; | 
|  | 68 | /* Bit  2 - root hubs are not part of a compound device */ | 
|  | 69 | /* Bits 4:3 - individual port over current protection */ | 
|  | 70 | temp |= 0x0008; | 
|  | 71 | /* Bits 6:5 - no TTs in root ports */ | 
|  | 72 | /* Bit  7 - no port indicators */ | 
| Matt Evans | 28ccd29 | 2011-03-29 13:40:46 +1100 | [diff] [blame] | 73 | desc->wHubCharacteristics = cpu_to_le16(temp); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 74 | } | 
|  | 75 |  | 
| Sarah Sharp | 4bbb0ac | 2010-11-29 16:14:37 -0800 | [diff] [blame] | 76 | /* Fill in the USB 2.0 roothub descriptor */ | 
|  | 77 | static void xhci_usb2_hub_descriptor(struct usb_hcd *hcd, struct xhci_hcd *xhci, | 
|  | 78 | struct usb_hub_descriptor *desc) | 
|  | 79 | { | 
|  | 80 | int ports; | 
|  | 81 | u16 temp; | 
|  | 82 | __u8 port_removable[(USB_MAXCHILDREN + 1 + 7) / 8]; | 
|  | 83 | u32 portsc; | 
|  | 84 | unsigned int i; | 
|  | 85 |  | 
|  | 86 | ports = xhci->num_usb2_ports; | 
|  | 87 |  | 
|  | 88 | xhci_common_hub_descriptor(xhci, desc, ports); | 
|  | 89 | desc->bDescriptorType = 0x29; | 
|  | 90 | temp = 1 + (ports / 8); | 
|  | 91 | desc->bDescLength = 7 + 2 * temp; | 
|  | 92 |  | 
|  | 93 | /* The Device Removable bits are reported on a byte granularity. | 
|  | 94 | * If the port doesn't exist within that byte, the bit is set to 0. | 
|  | 95 | */ | 
|  | 96 | memset(port_removable, 0, sizeof(port_removable)); | 
|  | 97 | for (i = 0; i < ports; i++) { | 
|  | 98 | portsc = xhci_readl(xhci, xhci->usb3_ports[i]); | 
|  | 99 | /* If a device is removable, PORTSC reports a 0, same as in the | 
|  | 100 | * hub descriptor DeviceRemovable bits. | 
|  | 101 | */ | 
|  | 102 | if (portsc & PORT_DEV_REMOVE) | 
|  | 103 | /* This math is hairy because bit 0 of DeviceRemovable | 
|  | 104 | * is reserved, and bit 1 is for port 1, etc. | 
|  | 105 | */ | 
|  | 106 | port_removable[(i + 1) / 8] |= 1 << ((i + 1) % 8); | 
|  | 107 | } | 
|  | 108 |  | 
|  | 109 | /* ch11.h defines a hub descriptor that has room for USB_MAXCHILDREN | 
|  | 110 | * ports on it.  The USB 2.0 specification says that there are two | 
|  | 111 | * variable length fields at the end of the hub descriptor: | 
|  | 112 | * DeviceRemovable and PortPwrCtrlMask.  But since we can have less than | 
|  | 113 | * USB_MAXCHILDREN ports, we may need to use the DeviceRemovable array | 
|  | 114 | * to set PortPwrCtrlMask bits.  PortPwrCtrlMask must always be set to | 
|  | 115 | * 0xFF, so we initialize the both arrays (DeviceRemovable and | 
|  | 116 | * PortPwrCtrlMask) to 0xFF.  Then we set the DeviceRemovable for each | 
|  | 117 | * set of ports that actually exist. | 
|  | 118 | */ | 
|  | 119 | memset(desc->u.hs.DeviceRemovable, 0xff, | 
|  | 120 | sizeof(desc->u.hs.DeviceRemovable)); | 
|  | 121 | memset(desc->u.hs.PortPwrCtrlMask, 0xff, | 
|  | 122 | sizeof(desc->u.hs.PortPwrCtrlMask)); | 
|  | 123 |  | 
|  | 124 | for (i = 0; i < (ports + 1 + 7) / 8; i++) | 
|  | 125 | memset(&desc->u.hs.DeviceRemovable[i], port_removable[i], | 
|  | 126 | sizeof(__u8)); | 
|  | 127 | } | 
|  | 128 |  | 
|  | 129 | /* Fill in the USB 3.0 roothub descriptor */ | 
|  | 130 | static void xhci_usb3_hub_descriptor(struct usb_hcd *hcd, struct xhci_hcd *xhci, | 
|  | 131 | struct usb_hub_descriptor *desc) | 
|  | 132 | { | 
|  | 133 | int ports; | 
|  | 134 | u16 port_removable; | 
|  | 135 | u32 portsc; | 
|  | 136 | unsigned int i; | 
|  | 137 |  | 
|  | 138 | ports = xhci->num_usb3_ports; | 
|  | 139 | xhci_common_hub_descriptor(xhci, desc, ports); | 
|  | 140 | desc->bDescriptorType = 0x2a; | 
|  | 141 | desc->bDescLength = 12; | 
|  | 142 |  | 
|  | 143 | /* header decode latency should be zero for roothubs, | 
|  | 144 | * see section 4.23.5.2. | 
|  | 145 | */ | 
|  | 146 | desc->u.ss.bHubHdrDecLat = 0; | 
|  | 147 | desc->u.ss.wHubDelay = 0; | 
|  | 148 |  | 
|  | 149 | port_removable = 0; | 
|  | 150 | /* bit 0 is reserved, bit 1 is for port 1, etc. */ | 
|  | 151 | for (i = 0; i < ports; i++) { | 
|  | 152 | portsc = xhci_readl(xhci, xhci->usb3_ports[i]); | 
|  | 153 | if (portsc & PORT_DEV_REMOVE) | 
|  | 154 | port_removable |= 1 << (i + 1); | 
|  | 155 | } | 
|  | 156 | memset(&desc->u.ss.DeviceRemovable, | 
|  | 157 | (__force __u16) cpu_to_le16(port_removable), | 
|  | 158 | sizeof(__u16)); | 
|  | 159 | } | 
|  | 160 |  | 
|  | 161 | static void xhci_hub_descriptor(struct usb_hcd *hcd, struct xhci_hcd *xhci, | 
|  | 162 | struct usb_hub_descriptor *desc) | 
|  | 163 | { | 
|  | 164 |  | 
|  | 165 | if (hcd->speed == HCD_USB3) | 
|  | 166 | xhci_usb3_hub_descriptor(hcd, xhci, desc); | 
|  | 167 | else | 
|  | 168 | xhci_usb2_hub_descriptor(hcd, xhci, desc); | 
|  | 169 |  | 
|  | 170 | } | 
|  | 171 |  | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 172 | static unsigned int xhci_port_speed(unsigned int port_status) | 
|  | 173 | { | 
|  | 174 | if (DEV_LOWSPEED(port_status)) | 
| Alan Stern | 288ead4 | 2010-03-04 11:32:30 -0500 | [diff] [blame] | 175 | return USB_PORT_STAT_LOW_SPEED; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 176 | if (DEV_HIGHSPEED(port_status)) | 
| Alan Stern | 288ead4 | 2010-03-04 11:32:30 -0500 | [diff] [blame] | 177 | return USB_PORT_STAT_HIGH_SPEED; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 178 | /* | 
|  | 179 | * FIXME: Yes, we should check for full speed, but the core uses that as | 
|  | 180 | * a default in portspeed() in usb/core/hub.c (which is the only place | 
| Alan Stern | 288ead4 | 2010-03-04 11:32:30 -0500 | [diff] [blame] | 181 | * USB_PORT_STAT_*_SPEED is used). | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 182 | */ | 
|  | 183 | return 0; | 
|  | 184 | } | 
|  | 185 |  | 
|  | 186 | /* | 
|  | 187 | * These bits are Read Only (RO) and should be saved and written to the | 
|  | 188 | * registers: 0, 3, 10:13, 30 | 
|  | 189 | * connect status, over-current status, port speed, and device removable. | 
|  | 190 | * connect status and port speed are also sticky - meaning they're in | 
|  | 191 | * the AUX well and they aren't changed by a hot, warm, or cold reset. | 
|  | 192 | */ | 
|  | 193 | #define	XHCI_PORT_RO	((1<<0) | (1<<3) | (0xf<<10) | (1<<30)) | 
|  | 194 | /* | 
|  | 195 | * These bits are RW; writing a 0 clears the bit, writing a 1 sets the bit: | 
|  | 196 | * bits 5:8, 9, 14:15, 25:27 | 
|  | 197 | * link state, port power, port indicator state, "wake on" enable state | 
|  | 198 | */ | 
|  | 199 | #define XHCI_PORT_RWS	((0xf<<5) | (1<<9) | (0x3<<14) | (0x7<<25)) | 
|  | 200 | /* | 
|  | 201 | * These bits are RW; writing a 1 sets the bit, writing a 0 has no effect: | 
|  | 202 | * bit 4 (port reset) | 
|  | 203 | */ | 
|  | 204 | #define	XHCI_PORT_RW1S	((1<<4)) | 
|  | 205 | /* | 
|  | 206 | * These bits are RW; writing a 1 clears the bit, writing a 0 has no effect: | 
|  | 207 | * bits 1, 17, 18, 19, 20, 21, 22, 23 | 
|  | 208 | * port enable/disable, and | 
|  | 209 | * change bits: connect, PED, warm port reset changed (reserved zero for USB 2.0 ports), | 
|  | 210 | * over-current, reset, link state, and L1 change | 
|  | 211 | */ | 
|  | 212 | #define XHCI_PORT_RW1CS	((1<<1) | (0x7f<<17)) | 
|  | 213 | /* | 
|  | 214 | * Bit 16 is RW, and writing a '1' to it causes the link state control to be | 
|  | 215 | * latched in | 
|  | 216 | */ | 
|  | 217 | #define	XHCI_PORT_RW	((1<<16)) | 
|  | 218 | /* | 
|  | 219 | * These bits are Reserved Zero (RsvdZ) and zero should be written to them: | 
|  | 220 | * bits 2, 24, 28:31 | 
|  | 221 | */ | 
|  | 222 | #define	XHCI_PORT_RZ	((1<<2) | (1<<24) | (0xf<<28)) | 
|  | 223 |  | 
|  | 224 | /* | 
|  | 225 | * Given a port state, this function returns a value that would result in the | 
|  | 226 | * port being in the same state, if the value was written to the port status | 
|  | 227 | * control register. | 
|  | 228 | * Save Read Only (RO) bits and save read/write bits where | 
|  | 229 | * writing a 0 clears the bit and writing a 1 sets the bit (RWS). | 
|  | 230 | * For all other types (RW1S, RW1CS, RW, and RZ), writing a '0' has no effect. | 
|  | 231 | */ | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 232 | u32 xhci_port_state_to_neutral(u32 state) | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 233 | { | 
|  | 234 | /* Save read-only status and port state */ | 
|  | 235 | return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); | 
|  | 236 | } | 
|  | 237 |  | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 238 | /* | 
|  | 239 | * find slot id based on port number. | 
| Sarah Sharp | f6ff0ac | 2010-12-16 11:21:10 -0800 | [diff] [blame] | 240 | * @port: The one-based port number from one of the two split roothubs. | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 241 | */ | 
| Sarah Sharp | 5233630 | 2010-12-16 10:49:09 -0800 | [diff] [blame] | 242 | int xhci_find_slot_id_by_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, | 
|  | 243 | u16 port) | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 244 | { | 
|  | 245 | int slot_id; | 
|  | 246 | int i; | 
| Sarah Sharp | f6ff0ac | 2010-12-16 11:21:10 -0800 | [diff] [blame] | 247 | enum usb_device_speed speed; | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 248 |  | 
|  | 249 | slot_id = 0; | 
|  | 250 | for (i = 0; i < MAX_HC_SLOTS; i++) { | 
|  | 251 | if (!xhci->devs[i]) | 
|  | 252 | continue; | 
| Sarah Sharp | f6ff0ac | 2010-12-16 11:21:10 -0800 | [diff] [blame] | 253 | speed = xhci->devs[i]->udev->speed; | 
|  | 254 | if (((speed == USB_SPEED_SUPER) == (hcd->speed == HCD_USB3)) | 
| Sarah Sharp | fe30182 | 2011-09-02 11:05:41 -0700 | [diff] [blame] | 255 | && xhci->devs[i]->fake_port == port) { | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 256 | slot_id = i; | 
|  | 257 | break; | 
|  | 258 | } | 
|  | 259 | } | 
|  | 260 |  | 
|  | 261 | return slot_id; | 
|  | 262 | } | 
|  | 263 |  | 
|  | 264 | /* | 
|  | 265 | * Stop device | 
|  | 266 | * It issues stop endpoint command for EP 0 to 30. And wait the last command | 
|  | 267 | * to complete. | 
|  | 268 | * suspend will set to 1, if suspend bit need to set in command. | 
|  | 269 | */ | 
|  | 270 | static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend) | 
|  | 271 | { | 
|  | 272 | struct xhci_virt_device *virt_dev; | 
|  | 273 | struct xhci_command *cmd; | 
|  | 274 | unsigned long flags; | 
|  | 275 | int timeleft; | 
|  | 276 | int ret; | 
|  | 277 | int i; | 
|  | 278 |  | 
|  | 279 | ret = 0; | 
|  | 280 | virt_dev = xhci->devs[slot_id]; | 
|  | 281 | cmd = xhci_alloc_command(xhci, false, true, GFP_NOIO); | 
|  | 282 | if (!cmd) { | 
|  | 283 | xhci_dbg(xhci, "Couldn't allocate command structure.\n"); | 
|  | 284 | return -ENOMEM; | 
|  | 285 | } | 
|  | 286 |  | 
|  | 287 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 288 | for (i = LAST_EP_INDEX; i > 0; i--) { | 
|  | 289 | if (virt_dev->eps[i].ring && virt_dev->eps[i].ring->dequeue) | 
|  | 290 | xhci_queue_stop_endpoint(xhci, slot_id, i, suspend); | 
|  | 291 | } | 
|  | 292 | cmd->command_trb = xhci->cmd_ring->enqueue; | 
|  | 293 | list_add_tail(&cmd->cmd_list, &virt_dev->cmd_list); | 
|  | 294 | xhci_queue_stop_endpoint(xhci, slot_id, 0, suspend); | 
|  | 295 | xhci_ring_cmd_db(xhci); | 
|  | 296 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 297 |  | 
|  | 298 | /* Wait for last stop endpoint command to finish */ | 
|  | 299 | timeleft = wait_for_completion_interruptible_timeout( | 
|  | 300 | cmd->completion, | 
|  | 301 | USB_CTRL_SET_TIMEOUT); | 
|  | 302 | if (timeleft <= 0) { | 
|  | 303 | xhci_warn(xhci, "%s while waiting for stop endpoint command\n", | 
|  | 304 | timeleft == 0 ? "Timeout" : "Signal"); | 
|  | 305 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 306 | /* The timeout might have raced with the event ring handler, so | 
|  | 307 | * only delete from the list if the item isn't poisoned. | 
|  | 308 | */ | 
|  | 309 | if (cmd->cmd_list.next != LIST_POISON1) | 
|  | 310 | list_del(&cmd->cmd_list); | 
|  | 311 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 312 | ret = -ETIME; | 
|  | 313 | goto command_cleanup; | 
|  | 314 | } | 
|  | 315 |  | 
|  | 316 | command_cleanup: | 
|  | 317 | xhci_free_command(xhci, cmd); | 
|  | 318 | return ret; | 
|  | 319 | } | 
|  | 320 |  | 
|  | 321 | /* | 
|  | 322 | * Ring device, it rings the all doorbells unconditionally. | 
|  | 323 | */ | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 324 | void xhci_ring_device(struct xhci_hcd *xhci, int slot_id) | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 325 | { | 
|  | 326 | int i; | 
|  | 327 |  | 
|  | 328 | for (i = 0; i < LAST_EP_INDEX + 1; i++) | 
|  | 329 | if (xhci->devs[slot_id]->eps[i].ring && | 
|  | 330 | xhci->devs[slot_id]->eps[i].ring->dequeue) | 
|  | 331 | xhci_ring_ep_doorbell(xhci, slot_id, i, 0); | 
|  | 332 |  | 
|  | 333 | return; | 
|  | 334 | } | 
|  | 335 |  | 
| Sarah Sharp | f6ff0ac | 2010-12-16 11:21:10 -0800 | [diff] [blame] | 336 | static void xhci_disable_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, | 
| Matt Evans | 28ccd29 | 2011-03-29 13:40:46 +1100 | [diff] [blame] | 337 | u16 wIndex, __le32 __iomem *addr, u32 port_status) | 
| Sarah Sharp | 6219c04 | 2009-12-09 15:59:11 -0800 | [diff] [blame] | 338 | { | 
| Sarah Sharp | 6dd0a3a | 2010-11-16 15:58:52 -0800 | [diff] [blame] | 339 | /* Don't allow the USB core to disable SuperSpeed ports. */ | 
| Sarah Sharp | f6ff0ac | 2010-12-16 11:21:10 -0800 | [diff] [blame] | 340 | if (hcd->speed == HCD_USB3) { | 
| Sarah Sharp | 6dd0a3a | 2010-11-16 15:58:52 -0800 | [diff] [blame] | 341 | xhci_dbg(xhci, "Ignoring request to disable " | 
|  | 342 | "SuperSpeed port.\n"); | 
|  | 343 | return; | 
|  | 344 | } | 
|  | 345 |  | 
| Sarah Sharp | 6219c04 | 2009-12-09 15:59:11 -0800 | [diff] [blame] | 346 | /* Write 1 to disable the port */ | 
|  | 347 | xhci_writel(xhci, port_status | PORT_PE, addr); | 
|  | 348 | port_status = xhci_readl(xhci, addr); | 
|  | 349 | xhci_dbg(xhci, "disable port, actual port %d status  = 0x%x\n", | 
|  | 350 | wIndex, port_status); | 
|  | 351 | } | 
|  | 352 |  | 
| Sarah Sharp | 34fb562 | 2009-12-09 15:59:08 -0800 | [diff] [blame] | 353 | static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, | 
| Matt Evans | 28ccd29 | 2011-03-29 13:40:46 +1100 | [diff] [blame] | 354 | u16 wIndex, __le32 __iomem *addr, u32 port_status) | 
| Sarah Sharp | 34fb562 | 2009-12-09 15:59:08 -0800 | [diff] [blame] | 355 | { | 
|  | 356 | char *port_change_bit; | 
|  | 357 | u32 status; | 
|  | 358 |  | 
|  | 359 | switch (wValue) { | 
|  | 360 | case USB_PORT_FEAT_C_RESET: | 
|  | 361 | status = PORT_RC; | 
|  | 362 | port_change_bit = "reset"; | 
|  | 363 | break; | 
| Andiry Xu | a11496e | 2011-04-27 18:07:29 +0800 | [diff] [blame] | 364 | case USB_PORT_FEAT_C_BH_PORT_RESET: | 
|  | 365 | status = PORT_WRC; | 
|  | 366 | port_change_bit = "warm(BH) reset"; | 
|  | 367 | break; | 
| Sarah Sharp | 34fb562 | 2009-12-09 15:59:08 -0800 | [diff] [blame] | 368 | case USB_PORT_FEAT_C_CONNECTION: | 
|  | 369 | status = PORT_CSC; | 
|  | 370 | port_change_bit = "connect"; | 
|  | 371 | break; | 
|  | 372 | case USB_PORT_FEAT_C_OVER_CURRENT: | 
|  | 373 | status = PORT_OCC; | 
|  | 374 | port_change_bit = "over-current"; | 
|  | 375 | break; | 
| Sarah Sharp | 6219c04 | 2009-12-09 15:59:11 -0800 | [diff] [blame] | 376 | case USB_PORT_FEAT_C_ENABLE: | 
|  | 377 | status = PORT_PEC; | 
|  | 378 | port_change_bit = "enable/disable"; | 
|  | 379 | break; | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 380 | case USB_PORT_FEAT_C_SUSPEND: | 
|  | 381 | status = PORT_PLC; | 
|  | 382 | port_change_bit = "suspend/resume"; | 
|  | 383 | break; | 
| Andiry Xu | 85387c0 | 2011-04-27 18:07:35 +0800 | [diff] [blame] | 384 | case USB_PORT_FEAT_C_PORT_LINK_STATE: | 
|  | 385 | status = PORT_PLC; | 
|  | 386 | port_change_bit = "link state"; | 
|  | 387 | break; | 
| Sarah Sharp | 34fb562 | 2009-12-09 15:59:08 -0800 | [diff] [blame] | 388 | default: | 
|  | 389 | /* Should never happen */ | 
|  | 390 | return; | 
|  | 391 | } | 
|  | 392 | /* Change bits are all write 1 to clear */ | 
|  | 393 | xhci_writel(xhci, port_status | status, addr); | 
|  | 394 | port_status = xhci_readl(xhci, addr); | 
|  | 395 | xhci_dbg(xhci, "clear port %s change, actual port %d status  = 0x%x\n", | 
|  | 396 | port_change_bit, wIndex, port_status); | 
|  | 397 | } | 
|  | 398 |  | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 399 | static int xhci_get_ports(struct usb_hcd *hcd, __le32 __iomem ***port_array) | 
|  | 400 | { | 
|  | 401 | int max_ports; | 
|  | 402 | struct xhci_hcd	*xhci = hcd_to_xhci(hcd); | 
|  | 403 |  | 
|  | 404 | if (hcd->speed == HCD_USB3) { | 
|  | 405 | max_ports = xhci->num_usb3_ports; | 
|  | 406 | *port_array = xhci->usb3_ports; | 
|  | 407 | } else { | 
|  | 408 | max_ports = xhci->num_usb2_ports; | 
|  | 409 | *port_array = xhci->usb2_ports; | 
|  | 410 | } | 
|  | 411 |  | 
|  | 412 | return max_ports; | 
|  | 413 | } | 
|  | 414 |  | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 415 | void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array, | 
|  | 416 | int port_id, u32 link_state) | 
|  | 417 | { | 
|  | 418 | u32 temp; | 
|  | 419 |  | 
|  | 420 | temp = xhci_readl(xhci, port_array[port_id]); | 
|  | 421 | temp = xhci_port_state_to_neutral(temp); | 
|  | 422 | temp &= ~PORT_PLS_MASK; | 
|  | 423 | temp |= PORT_LINK_STROBE | link_state; | 
|  | 424 | xhci_writel(xhci, temp, port_array[port_id]); | 
|  | 425 | } | 
|  | 426 |  | 
| Andiry Xu | d2f52c9 | 2011-09-23 14:19:49 -0700 | [diff] [blame] | 427 | /* Test and clear port RWC bit */ | 
|  | 428 | void xhci_test_and_clear_bit(struct xhci_hcd *xhci, __le32 __iomem **port_array, | 
|  | 429 | int port_id, u32 port_bit) | 
|  | 430 | { | 
|  | 431 | u32 temp; | 
|  | 432 |  | 
|  | 433 | temp = xhci_readl(xhci, port_array[port_id]); | 
|  | 434 | if (temp & port_bit) { | 
|  | 435 | temp = xhci_port_state_to_neutral(temp); | 
|  | 436 | temp |= port_bit; | 
|  | 437 | xhci_writel(xhci, temp, port_array[port_id]); | 
|  | 438 | } | 
|  | 439 | } | 
|  | 440 |  | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 441 | int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | 
|  | 442 | u16 wIndex, char *buf, u16 wLength) | 
|  | 443 | { | 
|  | 444 | struct xhci_hcd	*xhci = hcd_to_xhci(hcd); | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 445 | int max_ports; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 446 | unsigned long flags; | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 447 | u32 temp, status; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 448 | int retval = 0; | 
| Matt Evans | 28ccd29 | 2011-03-29 13:40:46 +1100 | [diff] [blame] | 449 | __le32 __iomem **port_array; | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 450 | int slot_id; | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 451 | struct xhci_bus_state *bus_state; | 
| Andiry Xu | 2c44178 | 2011-04-27 18:07:39 +0800 | [diff] [blame] | 452 | u16 link_state = 0; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 453 |  | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 454 | max_ports = xhci_get_ports(hcd, &port_array); | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 455 | bus_state = &xhci->bus_state[hcd_index(hcd)]; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 456 |  | 
|  | 457 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 458 | switch (typeReq) { | 
|  | 459 | case GetHubStatus: | 
|  | 460 | /* No power source, over-current reported per port */ | 
|  | 461 | memset(buf, 0, 4); | 
|  | 462 | break; | 
|  | 463 | case GetHubDescriptor: | 
| Sarah Sharp | 4bbb0ac | 2010-11-29 16:14:37 -0800 | [diff] [blame] | 464 | /* Check to make sure userspace is asking for the USB 3.0 hub | 
|  | 465 | * descriptor for the USB 3.0 roothub.  If not, we stall the | 
|  | 466 | * endpoint, like external hubs do. | 
|  | 467 | */ | 
|  | 468 | if (hcd->speed == HCD_USB3 && | 
|  | 469 | (wLength < USB_DT_SS_HUB_SIZE || | 
|  | 470 | wValue != (USB_DT_SS_HUB << 8))) { | 
|  | 471 | xhci_dbg(xhci, "Wrong hub descriptor type for " | 
|  | 472 | "USB 3.0 roothub.\n"); | 
|  | 473 | goto error; | 
|  | 474 | } | 
| Sarah Sharp | f6ff0ac | 2010-12-16 11:21:10 -0800 | [diff] [blame] | 475 | xhci_hub_descriptor(hcd, xhci, | 
|  | 476 | (struct usb_hub_descriptor *) buf); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 477 | break; | 
| Sarah Sharp | 48e8236 | 2011-10-06 11:54:23 -0700 | [diff] [blame] | 478 | case DeviceRequest | USB_REQ_GET_DESCRIPTOR: | 
|  | 479 | if ((wValue & 0xff00) != (USB_DT_BOS << 8)) | 
|  | 480 | goto error; | 
|  | 481 |  | 
|  | 482 | if (hcd->speed != HCD_USB3) | 
|  | 483 | goto error; | 
|  | 484 |  | 
|  | 485 | memcpy(buf, &usb_bos_descriptor, | 
|  | 486 | USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE); | 
|  | 487 | temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params3); | 
|  | 488 | buf[12] = HCS_U1_LATENCY(temp); | 
|  | 489 | put_unaligned_le16(HCS_U2_LATENCY(temp), &buf[13]); | 
|  | 490 |  | 
|  | 491 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 492 | return USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 493 | case GetPortStatus: | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 494 | if (!wIndex || wIndex > max_ports) | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 495 | goto error; | 
|  | 496 | wIndex--; | 
|  | 497 | status = 0; | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 498 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Sarah Sharp | f9de815 | 2010-10-29 14:37:23 -0700 | [diff] [blame] | 499 | if (temp == 0xffffffff) { | 
|  | 500 | retval = -ENODEV; | 
|  | 501 | break; | 
|  | 502 | } | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 503 | xhci_dbg(xhci, "get port status, actual port %d status  = 0x%x\n", wIndex, temp); | 
|  | 504 |  | 
|  | 505 | /* wPortChange bits */ | 
|  | 506 | if (temp & PORT_CSC) | 
| Alan Stern | 749da5f | 2010-03-04 17:05:08 -0500 | [diff] [blame] | 507 | status |= USB_PORT_STAT_C_CONNECTION << 16; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 508 | if (temp & PORT_PEC) | 
| Alan Stern | 749da5f | 2010-03-04 17:05:08 -0500 | [diff] [blame] | 509 | status |= USB_PORT_STAT_C_ENABLE << 16; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 510 | if ((temp & PORT_OCC)) | 
| Alan Stern | 749da5f | 2010-03-04 17:05:08 -0500 | [diff] [blame] | 511 | status |= USB_PORT_STAT_C_OVERCURRENT << 16; | 
| Andiry Xu | 0ed9a57 | 2011-04-27 18:07:43 +0800 | [diff] [blame] | 512 | if ((temp & PORT_RC)) | 
|  | 513 | status |= USB_PORT_STAT_C_RESET << 16; | 
|  | 514 | /* USB3.0 only */ | 
|  | 515 | if (hcd->speed == HCD_USB3) { | 
|  | 516 | if ((temp & PORT_PLC)) | 
|  | 517 | status |= USB_PORT_STAT_C_LINK_STATE << 16; | 
|  | 518 | if ((temp & PORT_WRC)) | 
|  | 519 | status |= USB_PORT_STAT_C_BH_RESET << 16; | 
|  | 520 | } | 
|  | 521 |  | 
|  | 522 | if (hcd->speed != HCD_USB3) { | 
|  | 523 | if ((temp & PORT_PLS_MASK) == XDEV_U3 | 
|  | 524 | && (temp & PORT_POWER)) | 
|  | 525 | status |= USB_PORT_STAT_SUSPEND; | 
|  | 526 | } | 
| Andiry Xu | 8a8ff2f | 2011-08-03 16:46:49 +0800 | [diff] [blame] | 527 | if ((temp & PORT_PLS_MASK) == XDEV_RESUME && | 
|  | 528 | !DEV_SUPERSPEED(temp)) { | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 529 | if ((temp & PORT_RESET) || !(temp & PORT_PE)) | 
|  | 530 | goto error; | 
| Andiry Xu | 8a8ff2f | 2011-08-03 16:46:49 +0800 | [diff] [blame] | 531 | if (time_after_eq(jiffies, | 
|  | 532 | bus_state->resume_done[wIndex])) { | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 533 | xhci_dbg(xhci, "Resume USB2 port %d\n", | 
|  | 534 | wIndex + 1); | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 535 | bus_state->resume_done[wIndex] = 0; | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 536 | xhci_set_link_state(xhci, port_array, wIndex, | 
|  | 537 | XDEV_U0); | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 538 | xhci_dbg(xhci, "set port %d resume\n", | 
|  | 539 | wIndex + 1); | 
| Sarah Sharp | 5233630 | 2010-12-16 10:49:09 -0800 | [diff] [blame] | 540 | slot_id = xhci_find_slot_id_by_port(hcd, xhci, | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 541 | wIndex + 1); | 
|  | 542 | if (!slot_id) { | 
|  | 543 | xhci_dbg(xhci, "slot_id is zero\n"); | 
|  | 544 | goto error; | 
|  | 545 | } | 
|  | 546 | xhci_ring_device(xhci, slot_id); | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 547 | bus_state->port_c_suspend |= 1 << wIndex; | 
|  | 548 | bus_state->suspended_ports &= ~(1 << wIndex); | 
| Andiry Xu | 8a8ff2f | 2011-08-03 16:46:49 +0800 | [diff] [blame] | 549 | } else { | 
|  | 550 | /* | 
|  | 551 | * The resume has been signaling for less than | 
|  | 552 | * 20ms. Report the port status as SUSPEND, | 
|  | 553 | * let the usbcore check port status again | 
|  | 554 | * and clear resume signaling later. | 
|  | 555 | */ | 
|  | 556 | status |= USB_PORT_STAT_SUSPEND; | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 557 | } | 
|  | 558 | } | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 559 | if ((temp & PORT_PLS_MASK) == XDEV_U0 | 
|  | 560 | && (temp & PORT_POWER) | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 561 | && (bus_state->suspended_ports & (1 << wIndex))) { | 
|  | 562 | bus_state->suspended_ports &= ~(1 << wIndex); | 
| Andiry Xu | a711423 | 2011-04-27 18:07:50 +0800 | [diff] [blame] | 563 | if (hcd->speed != HCD_USB3) | 
|  | 564 | bus_state->port_c_suspend |= 1 << wIndex; | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 565 | } | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 566 | if (temp & PORT_CONNECT) { | 
| Alan Stern | 749da5f | 2010-03-04 17:05:08 -0500 | [diff] [blame] | 567 | status |= USB_PORT_STAT_CONNECTION; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 568 | status |= xhci_port_speed(temp); | 
|  | 569 | } | 
|  | 570 | if (temp & PORT_PE) | 
| Alan Stern | 749da5f | 2010-03-04 17:05:08 -0500 | [diff] [blame] | 571 | status |= USB_PORT_STAT_ENABLE; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 572 | if (temp & PORT_OC) | 
| Alan Stern | 749da5f | 2010-03-04 17:05:08 -0500 | [diff] [blame] | 573 | status |= USB_PORT_STAT_OVERCURRENT; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 574 | if (temp & PORT_RESET) | 
| Alan Stern | 749da5f | 2010-03-04 17:05:08 -0500 | [diff] [blame] | 575 | status |= USB_PORT_STAT_RESET; | 
| Andiry Xu | 0ed9a57 | 2011-04-27 18:07:43 +0800 | [diff] [blame] | 576 | if (temp & PORT_POWER) { | 
|  | 577 | if (hcd->speed == HCD_USB3) | 
|  | 578 | status |= USB_SS_PORT_STAT_POWER; | 
|  | 579 | else | 
|  | 580 | status |= USB_PORT_STAT_POWER; | 
|  | 581 | } | 
|  | 582 | /* Port Link State */ | 
|  | 583 | if (hcd->speed == HCD_USB3) { | 
|  | 584 | /* resume state is a xHCI internal state. | 
|  | 585 | * Do not report it to usb core. | 
|  | 586 | */ | 
|  | 587 | if ((temp & PORT_PLS_MASK) != XDEV_RESUME) | 
|  | 588 | status |= (temp & PORT_PLS_MASK); | 
|  | 589 | } | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 590 | if (bus_state->port_c_suspend & (1 << wIndex)) | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 591 | status |= 1 << USB_PORT_FEAT_C_SUSPEND; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 592 | xhci_dbg(xhci, "Get port status returned 0x%x\n", status); | 
|  | 593 | put_unaligned(cpu_to_le32(status), (__le32 *) buf); | 
|  | 594 | break; | 
|  | 595 | case SetPortFeature: | 
| Andiry Xu | 2c44178 | 2011-04-27 18:07:39 +0800 | [diff] [blame] | 596 | if (wValue == USB_PORT_FEAT_LINK_STATE) | 
|  | 597 | link_state = (wIndex & 0xff00) >> 3; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 598 | wIndex &= 0xff; | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 599 | if (!wIndex || wIndex > max_ports) | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 600 | goto error; | 
|  | 601 | wIndex--; | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 602 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Sarah Sharp | f9de815 | 2010-10-29 14:37:23 -0700 | [diff] [blame] | 603 | if (temp == 0xffffffff) { | 
|  | 604 | retval = -ENODEV; | 
|  | 605 | break; | 
|  | 606 | } | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 607 | temp = xhci_port_state_to_neutral(temp); | 
| Sarah Sharp | 4bbb0ac | 2010-11-29 16:14:37 -0800 | [diff] [blame] | 608 | /* FIXME: What new port features do we need to support? */ | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 609 | switch (wValue) { | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 610 | case USB_PORT_FEAT_SUSPEND: | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 611 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Andiry Xu | 65580b43 | 2011-09-23 14:19:52 -0700 | [diff] [blame] | 612 | if ((temp & PORT_PLS_MASK) != XDEV_U0) { | 
|  | 613 | /* Resume the port to U0 first */ | 
|  | 614 | xhci_set_link_state(xhci, port_array, wIndex, | 
|  | 615 | XDEV_U0); | 
|  | 616 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 617 | msleep(10); | 
|  | 618 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 619 | } | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 620 | /* In spec software should not attempt to suspend | 
|  | 621 | * a port unless the port reports that it is in the | 
|  | 622 | * enabled (PED = ‘1’,PLS < ‘3’) state. | 
|  | 623 | */ | 
| Andiry Xu | 65580b43 | 2011-09-23 14:19:52 -0700 | [diff] [blame] | 624 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 625 | if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) | 
|  | 626 | || (temp & PORT_PLS_MASK) >= XDEV_U3) { | 
|  | 627 | xhci_warn(xhci, "USB core suspending device " | 
|  | 628 | "not in U0/U1/U2.\n"); | 
|  | 629 | goto error; | 
|  | 630 | } | 
|  | 631 |  | 
| Sarah Sharp | 5233630 | 2010-12-16 10:49:09 -0800 | [diff] [blame] | 632 | slot_id = xhci_find_slot_id_by_port(hcd, xhci, | 
|  | 633 | wIndex + 1); | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 634 | if (!slot_id) { | 
|  | 635 | xhci_warn(xhci, "slot_id is zero\n"); | 
|  | 636 | goto error; | 
|  | 637 | } | 
|  | 638 | /* unlock to execute stop endpoint commands */ | 
|  | 639 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 640 | xhci_stop_device(xhci, slot_id, 1); | 
|  | 641 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 642 |  | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 643 | xhci_set_link_state(xhci, port_array, wIndex, XDEV_U3); | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 644 |  | 
|  | 645 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 646 | msleep(10); /* wait device to enter */ | 
|  | 647 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 648 |  | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 649 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 650 | bus_state->suspended_ports |= 1 << wIndex; | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 651 | break; | 
| Andiry Xu | 2c44178 | 2011-04-27 18:07:39 +0800 | [diff] [blame] | 652 | case USB_PORT_FEAT_LINK_STATE: | 
|  | 653 | temp = xhci_readl(xhci, port_array[wIndex]); | 
|  | 654 | /* Software should not attempt to set | 
|  | 655 | * port link state above '5' (Rx.Detect) and the port | 
|  | 656 | * must be enabled. | 
|  | 657 | */ | 
|  | 658 | if ((temp & PORT_PE) == 0 || | 
|  | 659 | (link_state > USB_SS_PORT_LS_RX_DETECT)) { | 
|  | 660 | xhci_warn(xhci, "Cannot set link state.\n"); | 
|  | 661 | goto error; | 
|  | 662 | } | 
|  | 663 |  | 
|  | 664 | if (link_state == USB_SS_PORT_LS_U3) { | 
|  | 665 | slot_id = xhci_find_slot_id_by_port(hcd, xhci, | 
|  | 666 | wIndex + 1); | 
|  | 667 | if (slot_id) { | 
|  | 668 | /* unlock to execute stop endpoint | 
|  | 669 | * commands */ | 
|  | 670 | spin_unlock_irqrestore(&xhci->lock, | 
|  | 671 | flags); | 
|  | 672 | xhci_stop_device(xhci, slot_id, 1); | 
|  | 673 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 674 | } | 
|  | 675 | } | 
|  | 676 |  | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 677 | xhci_set_link_state(xhci, port_array, wIndex, | 
|  | 678 | link_state); | 
| Andiry Xu | 2c44178 | 2011-04-27 18:07:39 +0800 | [diff] [blame] | 679 |  | 
|  | 680 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 681 | msleep(20); /* wait device to enter */ | 
|  | 682 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 683 |  | 
|  | 684 | temp = xhci_readl(xhci, port_array[wIndex]); | 
|  | 685 | if (link_state == USB_SS_PORT_LS_U3) | 
|  | 686 | bus_state->suspended_ports |= 1 << wIndex; | 
|  | 687 | break; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 688 | case USB_PORT_FEAT_POWER: | 
|  | 689 | /* | 
|  | 690 | * Turn on ports, even if there isn't per-port switching. | 
|  | 691 | * HC will report connect events even before this is set. | 
|  | 692 | * However, khubd will ignore the roothub events until | 
|  | 693 | * the roothub is registered. | 
|  | 694 | */ | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 695 | xhci_writel(xhci, temp | PORT_POWER, | 
|  | 696 | port_array[wIndex]); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 697 |  | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 698 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 699 | xhci_dbg(xhci, "set port power, actual port %d status  = 0x%x\n", wIndex, temp); | 
|  | 700 | break; | 
|  | 701 | case USB_PORT_FEAT_RESET: | 
|  | 702 | temp = (temp | PORT_RESET); | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 703 | xhci_writel(xhci, temp, port_array[wIndex]); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 704 |  | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 705 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 706 | xhci_dbg(xhci, "set port reset, actual port %d status  = 0x%x\n", wIndex, temp); | 
|  | 707 | break; | 
| Andiry Xu | a11496e | 2011-04-27 18:07:29 +0800 | [diff] [blame] | 708 | case USB_PORT_FEAT_BH_PORT_RESET: | 
|  | 709 | temp |= PORT_WR; | 
|  | 710 | xhci_writel(xhci, temp, port_array[wIndex]); | 
|  | 711 |  | 
|  | 712 | temp = xhci_readl(xhci, port_array[wIndex]); | 
|  | 713 | break; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 714 | default: | 
|  | 715 | goto error; | 
|  | 716 | } | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 717 | /* unblock any posted writes */ | 
|  | 718 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 719 | break; | 
|  | 720 | case ClearPortFeature: | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 721 | if (!wIndex || wIndex > max_ports) | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 722 | goto error; | 
|  | 723 | wIndex--; | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 724 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Sarah Sharp | f9de815 | 2010-10-29 14:37:23 -0700 | [diff] [blame] | 725 | if (temp == 0xffffffff) { | 
|  | 726 | retval = -ENODEV; | 
|  | 727 | break; | 
|  | 728 | } | 
| Sarah Sharp | 4bbb0ac | 2010-11-29 16:14:37 -0800 | [diff] [blame] | 729 | /* FIXME: What new port features do we need to support? */ | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 730 | temp = xhci_port_state_to_neutral(temp); | 
|  | 731 | switch (wValue) { | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 732 | case USB_PORT_FEAT_SUSPEND: | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 733 | temp = xhci_readl(xhci, port_array[wIndex]); | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 734 | xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n"); | 
|  | 735 | xhci_dbg(xhci, "PORTSC %04x\n", temp); | 
|  | 736 | if (temp & PORT_RESET) | 
|  | 737 | goto error; | 
| Andiry Xu | 5ac04bf | 2011-08-03 16:46:48 +0800 | [diff] [blame] | 738 | if ((temp & PORT_PLS_MASK) == XDEV_U3) { | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 739 | if ((temp & PORT_PE) == 0) | 
|  | 740 | goto error; | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 741 |  | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 742 | xhci_set_link_state(xhci, port_array, wIndex, | 
|  | 743 | XDEV_RESUME); | 
|  | 744 | spin_unlock_irqrestore(&xhci->lock, flags); | 
| Andiry Xu | a711423 | 2011-04-27 18:07:50 +0800 | [diff] [blame] | 745 | msleep(20); | 
|  | 746 | spin_lock_irqsave(&xhci->lock, flags); | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 747 | xhci_set_link_state(xhci, port_array, wIndex, | 
|  | 748 | XDEV_U0); | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 749 | } | 
| Andiry Xu | a711423 | 2011-04-27 18:07:50 +0800 | [diff] [blame] | 750 | bus_state->port_c_suspend |= 1 << wIndex; | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 751 |  | 
| Sarah Sharp | 5233630 | 2010-12-16 10:49:09 -0800 | [diff] [blame] | 752 | slot_id = xhci_find_slot_id_by_port(hcd, xhci, | 
|  | 753 | wIndex + 1); | 
| Andiry Xu | be88fe4 | 2010-10-14 07:22:57 -0700 | [diff] [blame] | 754 | if (!slot_id) { | 
|  | 755 | xhci_dbg(xhci, "slot_id is zero\n"); | 
|  | 756 | goto error; | 
|  | 757 | } | 
|  | 758 | xhci_ring_device(xhci, slot_id); | 
|  | 759 | break; | 
|  | 760 | case USB_PORT_FEAT_C_SUSPEND: | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 761 | bus_state->port_c_suspend &= ~(1 << wIndex); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 762 | case USB_PORT_FEAT_C_RESET: | 
| Andiry Xu | a11496e | 2011-04-27 18:07:29 +0800 | [diff] [blame] | 763 | case USB_PORT_FEAT_C_BH_PORT_RESET: | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 764 | case USB_PORT_FEAT_C_CONNECTION: | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 765 | case USB_PORT_FEAT_C_OVER_CURRENT: | 
| Sarah Sharp | 6219c04 | 2009-12-09 15:59:11 -0800 | [diff] [blame] | 766 | case USB_PORT_FEAT_C_ENABLE: | 
| Andiry Xu | 85387c0 | 2011-04-27 18:07:35 +0800 | [diff] [blame] | 767 | case USB_PORT_FEAT_C_PORT_LINK_STATE: | 
| Sarah Sharp | 34fb562 | 2009-12-09 15:59:08 -0800 | [diff] [blame] | 768 | xhci_clear_port_change_bit(xhci, wValue, wIndex, | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 769 | port_array[wIndex], temp); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 770 | break; | 
| Sarah Sharp | 6219c04 | 2009-12-09 15:59:11 -0800 | [diff] [blame] | 771 | case USB_PORT_FEAT_ENABLE: | 
| Sarah Sharp | f6ff0ac | 2010-12-16 11:21:10 -0800 | [diff] [blame] | 772 | xhci_disable_port(hcd, xhci, wIndex, | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 773 | port_array[wIndex], temp); | 
| Sarah Sharp | 6219c04 | 2009-12-09 15:59:11 -0800 | [diff] [blame] | 774 | break; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 775 | default: | 
|  | 776 | goto error; | 
|  | 777 | } | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 778 | break; | 
|  | 779 | default: | 
|  | 780 | error: | 
|  | 781 | /* "stall" on error */ | 
|  | 782 | retval = -EPIPE; | 
|  | 783 | } | 
|  | 784 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 785 | return retval; | 
|  | 786 | } | 
|  | 787 |  | 
|  | 788 | /* | 
|  | 789 | * Returns 0 if the status hasn't changed, or the number of bytes in buf. | 
|  | 790 | * Ports are 0-indexed from the HCD point of view, | 
|  | 791 | * and 1-indexed from the USB core pointer of view. | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 792 | * | 
|  | 793 | * Note that the status change bits will be cleared as soon as a port status | 
|  | 794 | * change event is generated, so we use the saved status from that event. | 
|  | 795 | */ | 
|  | 796 | int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) | 
|  | 797 | { | 
|  | 798 | unsigned long flags; | 
|  | 799 | u32 temp, status; | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 800 | u32 mask; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 801 | int i, retval; | 
|  | 802 | struct xhci_hcd	*xhci = hcd_to_xhci(hcd); | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 803 | int max_ports; | 
| Matt Evans | 28ccd29 | 2011-03-29 13:40:46 +1100 | [diff] [blame] | 804 | __le32 __iomem **port_array; | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 805 | struct xhci_bus_state *bus_state; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 806 |  | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 807 | max_ports = xhci_get_ports(hcd, &port_array); | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 808 | bus_state = &xhci->bus_state[hcd_index(hcd)]; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 809 |  | 
|  | 810 | /* Initial status is no changes */ | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 811 | retval = (max_ports + 8) / 8; | 
| William Gulland | 419a8e8 | 2010-05-12 10:20:34 -0700 | [diff] [blame] | 812 | memset(buf, 0, retval); | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 813 | status = 0; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 814 |  | 
| Greg KH | 44f4c3e | 2011-09-19 16:05:11 -0700 | [diff] [blame] | 815 | mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC; | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 816 |  | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 817 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 818 | /* For each port, did anything change?  If so, set that bit in buf. */ | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 819 | for (i = 0; i < max_ports; i++) { | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 820 | temp = xhci_readl(xhci, port_array[i]); | 
| Sarah Sharp | f9de815 | 2010-10-29 14:37:23 -0700 | [diff] [blame] | 821 | if (temp == 0xffffffff) { | 
|  | 822 | retval = -ENODEV; | 
|  | 823 | break; | 
|  | 824 | } | 
| Andiry Xu | 5619253 | 2010-10-14 07:23:00 -0700 | [diff] [blame] | 825 | if ((temp & mask) != 0 || | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 826 | (bus_state->port_c_suspend & 1 << i) || | 
|  | 827 | (bus_state->resume_done[i] && time_after_eq( | 
|  | 828 | jiffies, bus_state->resume_done[i]))) { | 
| William Gulland | 419a8e8 | 2010-05-12 10:20:34 -0700 | [diff] [blame] | 829 | buf[(i + 1) / 8] |= 1 << (i + 1) % 8; | 
| Sarah Sharp | 0f2a793 | 2009-04-27 19:57:12 -0700 | [diff] [blame] | 830 | status = 1; | 
|  | 831 | } | 
|  | 832 | } | 
|  | 833 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 834 | return status ? retval : 0; | 
|  | 835 | } | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 836 |  | 
|  | 837 | #ifdef CONFIG_PM | 
|  | 838 |  | 
|  | 839 | int xhci_bus_suspend(struct usb_hcd *hcd) | 
|  | 840 | { | 
|  | 841 | struct xhci_hcd	*xhci = hcd_to_xhci(hcd); | 
| Sarah Sharp | 518e848 | 2010-12-15 11:56:29 -0800 | [diff] [blame] | 842 | int max_ports, port_index; | 
| Matt Evans | 28ccd29 | 2011-03-29 13:40:46 +1100 | [diff] [blame] | 843 | __le32 __iomem **port_array; | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 844 | struct xhci_bus_state *bus_state; | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 845 | unsigned long flags; | 
|  | 846 |  | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 847 | max_ports = xhci_get_ports(hcd, &port_array); | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 848 | bus_state = &xhci->bus_state[hcd_index(hcd)]; | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 849 |  | 
|  | 850 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 851 |  | 
|  | 852 | if (hcd->self.root_hub->do_remote_wakeup) { | 
| Sarah Sharp | 518e848 | 2010-12-15 11:56:29 -0800 | [diff] [blame] | 853 | port_index = max_ports; | 
|  | 854 | while (port_index--) { | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 855 | if (bus_state->resume_done[port_index] != 0) { | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 856 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 857 | xhci_dbg(xhci, "suspend failed because " | 
|  | 858 | "port %d is resuming\n", | 
| Sarah Sharp | 518e848 | 2010-12-15 11:56:29 -0800 | [diff] [blame] | 859 | port_index + 1); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 860 | return -EBUSY; | 
|  | 861 | } | 
|  | 862 | } | 
|  | 863 | } | 
|  | 864 |  | 
| Sarah Sharp | 518e848 | 2010-12-15 11:56:29 -0800 | [diff] [blame] | 865 | port_index = max_ports; | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 866 | bus_state->bus_suspended = 0; | 
| Sarah Sharp | 518e848 | 2010-12-15 11:56:29 -0800 | [diff] [blame] | 867 | while (port_index--) { | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 868 | /* suspend the port if the port is not suspended */ | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 869 | u32 t1, t2; | 
|  | 870 | int slot_id; | 
|  | 871 |  | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 872 | t1 = xhci_readl(xhci, port_array[port_index]); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 873 | t2 = xhci_port_state_to_neutral(t1); | 
|  | 874 |  | 
|  | 875 | if ((t1 & PORT_PE) && !(t1 & PORT_PLS_MASK)) { | 
| Sarah Sharp | 518e848 | 2010-12-15 11:56:29 -0800 | [diff] [blame] | 876 | xhci_dbg(xhci, "port %d not suspended\n", port_index); | 
| Sarah Sharp | 5233630 | 2010-12-16 10:49:09 -0800 | [diff] [blame] | 877 | slot_id = xhci_find_slot_id_by_port(hcd, xhci, | 
| Sarah Sharp | 518e848 | 2010-12-15 11:56:29 -0800 | [diff] [blame] | 878 | port_index + 1); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 879 | if (slot_id) { | 
|  | 880 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 881 | xhci_stop_device(xhci, slot_id, 1); | 
|  | 882 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 883 | } | 
|  | 884 | t2 &= ~PORT_PLS_MASK; | 
|  | 885 | t2 |= PORT_LINK_STROBE | XDEV_U3; | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 886 | set_bit(port_index, &bus_state->bus_suspended); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 887 | } | 
|  | 888 | if (hcd->self.root_hub->do_remote_wakeup) { | 
|  | 889 | if (t1 & PORT_CONNECT) { | 
|  | 890 | t2 |= PORT_WKOC_E | PORT_WKDISC_E; | 
|  | 891 | t2 &= ~PORT_WKCONN_E; | 
|  | 892 | } else { | 
|  | 893 | t2 |= PORT_WKOC_E | PORT_WKCONN_E; | 
|  | 894 | t2 &= ~PORT_WKDISC_E; | 
|  | 895 | } | 
|  | 896 | } else | 
|  | 897 | t2 &= ~PORT_WAKE_BITS; | 
|  | 898 |  | 
|  | 899 | t1 = xhci_port_state_to_neutral(t1); | 
|  | 900 | if (t1 != t2) | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 901 | xhci_writel(xhci, t2, port_array[port_index]); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 902 |  | 
| Andiry Xu | 4f0871a | 2011-04-19 17:17:39 +0800 | [diff] [blame] | 903 | if (hcd->speed != HCD_USB3) { | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 904 | /* enable remote wake up for USB 2.0 */ | 
| Matt Evans | 28ccd29 | 2011-03-29 13:40:46 +1100 | [diff] [blame] | 905 | __le32 __iomem *addr; | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 906 | u32 tmp; | 
|  | 907 |  | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 908 | /* Add one to the port status register address to get | 
|  | 909 | * the port power control register address. | 
|  | 910 | */ | 
|  | 911 | addr = port_array[port_index] + 1; | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 912 | tmp = xhci_readl(xhci, addr); | 
|  | 913 | tmp |= PORT_RWE; | 
|  | 914 | xhci_writel(xhci, tmp, addr); | 
|  | 915 | } | 
|  | 916 | } | 
|  | 917 | hcd->state = HC_STATE_SUSPENDED; | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 918 | bus_state->next_statechange = jiffies + msecs_to_jiffies(10); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 919 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 920 | return 0; | 
|  | 921 | } | 
|  | 922 |  | 
|  | 923 | int xhci_bus_resume(struct usb_hcd *hcd) | 
|  | 924 | { | 
|  | 925 | struct xhci_hcd	*xhci = hcd_to_xhci(hcd); | 
| Sarah Sharp | 518e848 | 2010-12-15 11:56:29 -0800 | [diff] [blame] | 926 | int max_ports, port_index; | 
| Matt Evans | 28ccd29 | 2011-03-29 13:40:46 +1100 | [diff] [blame] | 927 | __le32 __iomem **port_array; | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 928 | struct xhci_bus_state *bus_state; | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 929 | u32 temp; | 
|  | 930 | unsigned long flags; | 
|  | 931 |  | 
| huajun li | a088592 | 2011-05-03 21:11:00 +0800 | [diff] [blame] | 932 | max_ports = xhci_get_ports(hcd, &port_array); | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 933 | bus_state = &xhci->bus_state[hcd_index(hcd)]; | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 934 |  | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 935 | if (time_before(jiffies, bus_state->next_statechange)) | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 936 | msleep(5); | 
|  | 937 |  | 
|  | 938 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 939 | if (!HCD_HW_ACCESSIBLE(hcd)) { | 
|  | 940 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 941 | return -ESHUTDOWN; | 
|  | 942 | } | 
|  | 943 |  | 
|  | 944 | /* delay the irqs */ | 
|  | 945 | temp = xhci_readl(xhci, &xhci->op_regs->command); | 
|  | 946 | temp &= ~CMD_EIE; | 
|  | 947 | xhci_writel(xhci, temp, &xhci->op_regs->command); | 
|  | 948 |  | 
| Sarah Sharp | 518e848 | 2010-12-15 11:56:29 -0800 | [diff] [blame] | 949 | port_index = max_ports; | 
|  | 950 | while (port_index--) { | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 951 | /* Check whether need resume ports. If needed | 
|  | 952 | resume port and disable remote wakeup */ | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 953 | u32 temp; | 
|  | 954 | int slot_id; | 
|  | 955 |  | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 956 | temp = xhci_readl(xhci, port_array[port_index]); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 957 | if (DEV_SUPERSPEED(temp)) | 
|  | 958 | temp &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); | 
|  | 959 | else | 
|  | 960 | temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 961 | if (test_bit(port_index, &bus_state->bus_suspended) && | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 962 | (temp & PORT_PLS_MASK)) { | 
|  | 963 | if (DEV_SUPERSPEED(temp)) { | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 964 | xhci_set_link_state(xhci, port_array, | 
|  | 965 | port_index, XDEV_U0); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 966 | } else { | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 967 | xhci_set_link_state(xhci, port_array, | 
|  | 968 | port_index, XDEV_RESUME); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 969 |  | 
|  | 970 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 971 | msleep(20); | 
|  | 972 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 973 |  | 
| Andiry Xu | c9682df | 2011-09-23 14:19:48 -0700 | [diff] [blame] | 974 | xhci_set_link_state(xhci, port_array, | 
|  | 975 | port_index, XDEV_U0); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 976 | } | 
| Andiry Xu | 4f0871a | 2011-04-19 17:17:39 +0800 | [diff] [blame] | 977 | /* wait for the port to enter U0 and report port link | 
|  | 978 | * state change. | 
|  | 979 | */ | 
|  | 980 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 981 | msleep(20); | 
|  | 982 | spin_lock_irqsave(&xhci->lock, flags); | 
|  | 983 |  | 
|  | 984 | /* Clear PLC */ | 
| Andiry Xu | d2f52c9 | 2011-09-23 14:19:49 -0700 | [diff] [blame] | 985 | xhci_test_and_clear_bit(xhci, port_array, port_index, | 
|  | 986 | PORT_PLC); | 
| Andiry Xu | 4f0871a | 2011-04-19 17:17:39 +0800 | [diff] [blame] | 987 |  | 
| Sarah Sharp | 5233630 | 2010-12-16 10:49:09 -0800 | [diff] [blame] | 988 | slot_id = xhci_find_slot_id_by_port(hcd, | 
|  | 989 | xhci, port_index + 1); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 990 | if (slot_id) | 
|  | 991 | xhci_ring_device(xhci, slot_id); | 
|  | 992 | } else | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 993 | xhci_writel(xhci, temp, port_array[port_index]); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 994 |  | 
| Andiry Xu | 4f0871a | 2011-04-19 17:17:39 +0800 | [diff] [blame] | 995 | if (hcd->speed != HCD_USB3) { | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 996 | /* disable remote wake up for USB 2.0 */ | 
| Matt Evans | 28ccd29 | 2011-03-29 13:40:46 +1100 | [diff] [blame] | 997 | __le32 __iomem *addr; | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 998 | u32 tmp; | 
|  | 999 |  | 
| Sarah Sharp | 5308a91 | 2010-12-01 11:34:59 -0800 | [diff] [blame] | 1000 | /* Add one to the port status register address to get | 
|  | 1001 | * the port power control register address. | 
|  | 1002 | */ | 
|  | 1003 | addr = port_array[port_index] + 1; | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 1004 | tmp = xhci_readl(xhci, addr); | 
|  | 1005 | tmp &= ~PORT_RWE; | 
|  | 1006 | xhci_writel(xhci, tmp, addr); | 
|  | 1007 | } | 
|  | 1008 | } | 
|  | 1009 |  | 
|  | 1010 | (void) xhci_readl(xhci, &xhci->op_regs->command); | 
|  | 1011 |  | 
| Sarah Sharp | 20b67cf | 2010-12-15 12:47:14 -0800 | [diff] [blame] | 1012 | bus_state->next_statechange = jiffies + msecs_to_jiffies(5); | 
| Andiry Xu | 9777e3c | 2010-10-14 07:23:03 -0700 | [diff] [blame] | 1013 | /* re-enable irqs */ | 
|  | 1014 | temp = xhci_readl(xhci, &xhci->op_regs->command); | 
|  | 1015 | temp |= CMD_EIE; | 
|  | 1016 | xhci_writel(xhci, temp, &xhci->op_regs->command); | 
|  | 1017 | temp = xhci_readl(xhci, &xhci->op_regs->command); | 
|  | 1018 |  | 
|  | 1019 | spin_unlock_irqrestore(&xhci->lock, flags); | 
|  | 1020 | return 0; | 
|  | 1021 | } | 
|  | 1022 |  | 
| Sarah Sharp | 436a389 | 2010-10-15 14:59:15 -0700 | [diff] [blame] | 1023 | #endif	/* CONFIG_PM */ |