| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
| David Brownell | d49d431 | 2005-05-07 13:21:50 -0700 | [diff] [blame] | 2 |  * Copyright (C) 2001-2004 by David Brownell | 
| David Brownell | 53bd6a6 | 2006-08-30 14:50:06 -0700 | [diff] [blame] | 3 |  * | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 4 |  * This program is free software; you can redistribute it and/or modify it | 
 | 5 |  * under the terms of the GNU General Public License as published by the | 
 | 6 |  * Free Software Foundation; either version 2 of the License, or (at your | 
 | 7 |  * option) any later version. | 
 | 8 |  * | 
 | 9 |  * This program is distributed in the hope that it will be useful, but | 
 | 10 |  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | 
 | 11 |  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License | 
 | 12 |  * for more details. | 
 | 13 |  * | 
 | 14 |  * You should have received a copy of the GNU General Public License | 
 | 15 |  * along with this program; if not, write to the Free Software Foundation, | 
 | 16 |  * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | 
 | 17 |  */ | 
 | 18 |  | 
 | 19 | /* this file is part of ehci-hcd.c */ | 
 | 20 |  | 
 | 21 | /*-------------------------------------------------------------------------*/ | 
 | 22 |  | 
 | 23 | /* | 
 | 24 |  * EHCI Root Hub ... the nonsharable stuff | 
 | 25 |  * | 
 | 26 |  * Registers don't need cpu_to_le32, that happens transparently | 
 | 27 |  */ | 
 | 28 |  | 
 | 29 | /*-------------------------------------------------------------------------*/ | 
 | 30 |  | 
 | 31 | #ifdef	CONFIG_PM | 
 | 32 |  | 
| Alan Stern | 0c0382e | 2005-10-13 17:08:02 -0400 | [diff] [blame] | 33 | static int ehci_bus_suspend (struct usb_hcd *hcd) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 34 | { | 
 | 35 | 	struct ehci_hcd		*ehci = hcd_to_ehci (hcd); | 
 | 36 | 	int			port; | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 37 | 	int			mask; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 38 |  | 
| Alan Stern | 8c774fe | 2007-02-01 16:09:59 -0500 | [diff] [blame] | 39 | 	ehci_dbg(ehci, "suspend root hub\n"); | 
 | 40 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 41 | 	if (time_before (jiffies, ehci->next_statechange)) | 
 | 42 | 		msleep(5); | 
 | 43 |  | 
 | 44 | 	port = HCS_N_PORTS (ehci->hcs_params); | 
 | 45 | 	spin_lock_irq (&ehci->lock); | 
 | 46 |  | 
 | 47 | 	/* stop schedules, clean any completed work */ | 
 | 48 | 	if (HC_IS_RUNNING(hcd->state)) { | 
 | 49 | 		ehci_quiesce (ehci); | 
 | 50 | 		hcd->state = HC_STATE_QUIESCING; | 
 | 51 | 	} | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 52 | 	ehci->command = ehci_readl(ehci, &ehci->regs->command); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 53 | 	if (ehci->reclaim) | 
| Greg Kroah-Hartman | 64f8979 | 2006-10-17 13:57:18 -0700 | [diff] [blame] | 54 | 		ehci->reclaim_ready = 1; | 
| David Howells | 7d12e78 | 2006-10-05 14:55:46 +0100 | [diff] [blame] | 55 | 	ehci_work(ehci); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 56 |  | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 57 | 	/* Unlike other USB host controller types, EHCI doesn't have | 
 | 58 | 	 * any notion of "global" or bus-wide suspend.  The driver has | 
 | 59 | 	 * to manually suspend all the active unsuspended ports, and | 
 | 60 | 	 * then manually resume them in the bus_resume() routine. | 
 | 61 | 	 */ | 
 | 62 | 	ehci->bus_suspended = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 63 | 	while (port--) { | 
 | 64 | 		u32 __iomem	*reg = &ehci->regs->port_status [port]; | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 65 | 		u32		t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 66 | 		u32		t2 = t1; | 
 | 67 |  | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 68 | 		/* keep track of which ports we suspend */ | 
 | 69 | 		if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) && | 
 | 70 | 				!(t1 & PORT_SUSPEND)) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 71 | 			t2 |= PORT_SUSPEND; | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 72 | 			set_bit(port, &ehci->bus_suspended); | 
 | 73 | 		} | 
 | 74 |  | 
 | 75 | 		/* enable remote wakeup on all ports */ | 
| David Brownell | 2c1c3c4 | 2005-11-07 15:24:46 -0800 | [diff] [blame] | 76 | 		if (device_may_wakeup(&hcd->self.root_hub->dev)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 77 | 			t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E; | 
 | 78 | 		else | 
 | 79 | 			t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E); | 
 | 80 |  | 
 | 81 | 		if (t1 != t2) { | 
 | 82 | 			ehci_vdbg (ehci, "port %d, %08x -> %08x\n", | 
 | 83 | 				port + 1, t1, t2); | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 84 | 			ehci_writel(ehci, t2, reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 85 | 		} | 
 | 86 | 	} | 
 | 87 |  | 
 | 88 | 	/* turn off now-idle HC */ | 
| David Brownell | 4756ae5 | 2005-05-09 17:23:51 -0700 | [diff] [blame] | 89 | 	del_timer_sync (&ehci->watchdog); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 90 | 	ehci_halt (ehci); | 
 | 91 | 	hcd->state = HC_STATE_SUSPENDED; | 
 | 92 |  | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 93 | 	/* allow remote wakeup */ | 
 | 94 | 	mask = INTR_MASK; | 
 | 95 | 	if (!device_may_wakeup(&hcd->self.root_hub->dev)) | 
 | 96 | 		mask &= ~STS_PCD; | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 97 | 	ehci_writel(ehci, mask, &ehci->regs->intr_enable); | 
 | 98 | 	ehci_readl(ehci, &ehci->regs->intr_enable); | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 99 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 100 | 	ehci->next_statechange = jiffies + msecs_to_jiffies(10); | 
 | 101 | 	spin_unlock_irq (&ehci->lock); | 
 | 102 | 	return 0; | 
 | 103 | } | 
 | 104 |  | 
 | 105 |  | 
 | 106 | /* caller has locked the root hub, and should reset/reinit on error */ | 
| Alan Stern | 0c0382e | 2005-10-13 17:08:02 -0400 | [diff] [blame] | 107 | static int ehci_bus_resume (struct usb_hcd *hcd) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 108 | { | 
 | 109 | 	struct ehci_hcd		*ehci = hcd_to_ehci (hcd); | 
 | 110 | 	u32			temp; | 
 | 111 | 	int			i; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 112 |  | 
 | 113 | 	if (time_before (jiffies, ehci->next_statechange)) | 
 | 114 | 		msleep(5); | 
 | 115 | 	spin_lock_irq (&ehci->lock); | 
 | 116 |  | 
| David Brownell | f03c17f | 2005-11-23 15:45:28 -0800 | [diff] [blame] | 117 | 	/* Ideally and we've got a real resume here, and no port's power | 
 | 118 | 	 * was lost.  (For PCI, that means Vaux was maintained.)  But we | 
 | 119 | 	 * could instead be restoring a swsusp snapshot -- so that BIOS was | 
 | 120 | 	 * the last user of the controller, not reset/pm hardware keeping | 
 | 121 | 	 * state we gave to it. | 
 | 122 | 	 */ | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 123 | 	temp = ehci_readl(ehci, &ehci->regs->intr_enable); | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 124 | 	ehci_dbg(ehci, "resume root hub%s\n", temp ? "" : " after power loss"); | 
| David Brownell | f03c17f | 2005-11-23 15:45:28 -0800 | [diff] [blame] | 125 |  | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 126 | 	/* at least some APM implementations will try to deliver | 
 | 127 | 	 * IRQs right away, so delay them until we're ready. | 
 | 128 | 	 */ | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 129 | 	ehci_writel(ehci, 0, &ehci->regs->intr_enable); | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 130 |  | 
 | 131 | 	/* re-init operational registers */ | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 132 | 	ehci_writel(ehci, 0, &ehci->regs->segment); | 
 | 133 | 	ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); | 
 | 134 | 	ehci_writel(ehci, (u32) ehci->async->qh_dma, &ehci->regs->async_next); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 135 |  | 
 | 136 | 	/* restore CMD_RUN, framelist size, and irq threshold */ | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 137 | 	ehci_writel(ehci, ehci->command, &ehci->regs->command); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 138 |  | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 139 | 	/* manually resume the ports we suspended during bus_suspend() */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 140 | 	i = HCS_N_PORTS (ehci->hcs_params); | 
 | 141 | 	while (i--) { | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 142 | 		temp = ehci_readl(ehci, &ehci->regs->port_status [i]); | 
| David Brownell | 10f6524 | 2005-08-31 10:55:38 -0700 | [diff] [blame] | 143 | 		temp &= ~(PORT_RWC_BITS | 
 | 144 | 			| PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E); | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 145 | 		if (test_bit(i, &ehci->bus_suspended) && | 
 | 146 | 				(temp & PORT_SUSPEND)) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 147 | 			ehci->reset_done [i] = jiffies + msecs_to_jiffies (20); | 
 | 148 | 			temp |= PORT_RESUME; | 
 | 149 | 		} | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 150 | 		ehci_writel(ehci, temp, &ehci->regs->port_status [i]); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 151 | 	} | 
 | 152 | 	i = HCS_N_PORTS (ehci->hcs_params); | 
 | 153 | 	mdelay (20); | 
 | 154 | 	while (i--) { | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 155 | 		temp = ehci_readl(ehci, &ehci->regs->port_status [i]); | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 156 | 		if (test_bit(i, &ehci->bus_suspended) && | 
 | 157 | 				(temp & PORT_SUSPEND)) { | 
 | 158 | 			temp &= ~(PORT_RWC_BITS | PORT_RESUME); | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 159 | 			ehci_writel(ehci, temp, &ehci->regs->port_status [i]); | 
| Alan Stern | 8c03356 | 2006-11-09 14:42:16 -0500 | [diff] [blame] | 160 | 			ehci_vdbg (ehci, "resumed port %d\n", i + 1); | 
 | 161 | 		} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 162 | 	} | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 163 | 	(void) ehci_readl(ehci, &ehci->regs->command); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 164 |  | 
 | 165 | 	/* maybe re-activate the schedule(s) */ | 
 | 166 | 	temp = 0; | 
 | 167 | 	if (ehci->async->qh_next.qh) | 
 | 168 | 		temp |= CMD_ASE; | 
 | 169 | 	if (ehci->periodic_sched) | 
 | 170 | 		temp |= CMD_PSE; | 
 | 171 | 	if (temp) { | 
 | 172 | 		ehci->command |= temp; | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 173 | 		ehci_writel(ehci, ehci->command, &ehci->regs->command); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 174 | 	} | 
 | 175 |  | 
 | 176 | 	ehci->next_statechange = jiffies + msecs_to_jiffies(5); | 
 | 177 | 	hcd->state = HC_STATE_RUNNING; | 
 | 178 |  | 
 | 179 | 	/* Now we can safely re-enable irqs */ | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 180 | 	ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 181 |  | 
 | 182 | 	spin_unlock_irq (&ehci->lock); | 
 | 183 | 	return 0; | 
 | 184 | } | 
 | 185 |  | 
 | 186 | #else | 
 | 187 |  | 
| Alan Stern | 0c0382e | 2005-10-13 17:08:02 -0400 | [diff] [blame] | 188 | #define ehci_bus_suspend	NULL | 
 | 189 | #define ehci_bus_resume		NULL | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 190 |  | 
 | 191 | #endif	/* CONFIG_PM */ | 
 | 192 |  | 
 | 193 | /*-------------------------------------------------------------------------*/ | 
 | 194 |  | 
| Alan Stern | 57e06c1 | 2007-01-16 11:59:45 -0500 | [diff] [blame] | 195 | /* Display the ports dedicated to the companion controller */ | 
 | 196 | static ssize_t show_companion(struct class_device *class_dev, char *buf) | 
 | 197 | { | 
 | 198 | 	struct ehci_hcd		*ehci; | 
 | 199 | 	int			nports, index, n; | 
 | 200 | 	int			count = PAGE_SIZE; | 
 | 201 | 	char			*ptr = buf; | 
 | 202 |  | 
 | 203 | 	ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev))); | 
 | 204 | 	nports = HCS_N_PORTS(ehci->hcs_params); | 
 | 205 |  | 
 | 206 | 	for (index = 0; index < nports; ++index) { | 
 | 207 | 		if (test_bit(index, &ehci->companion_ports)) { | 
 | 208 | 			n = scnprintf(ptr, count, "%d\n", index + 1); | 
 | 209 | 			ptr += n; | 
 | 210 | 			count -= n; | 
 | 211 | 		} | 
 | 212 | 	} | 
 | 213 | 	return ptr - buf; | 
 | 214 | } | 
 | 215 |  | 
 | 216 | /* | 
 | 217 |  * Dedicate or undedicate a port to the companion controller. | 
 | 218 |  * Syntax is "[-]portnum", where a leading '-' sign means | 
 | 219 |  * return control of the port to the EHCI controller. | 
 | 220 |  */ | 
 | 221 | static ssize_t store_companion(struct class_device *class_dev, | 
 | 222 | 		const char *buf, size_t count) | 
 | 223 | { | 
 | 224 | 	struct ehci_hcd		*ehci; | 
 | 225 | 	int			portnum, new_owner, try; | 
 | 226 | 	u32 __iomem		*status_reg; | 
 | 227 | 	u32			port_status; | 
 | 228 |  | 
 | 229 | 	ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev))); | 
 | 230 | 	new_owner = PORT_OWNER;		/* Owned by companion */ | 
 | 231 | 	if (sscanf(buf, "%d", &portnum) != 1) | 
 | 232 | 		return -EINVAL; | 
 | 233 | 	if (portnum < 0) { | 
 | 234 | 		portnum = - portnum; | 
 | 235 | 		new_owner = 0;		/* Owned by EHCI */ | 
 | 236 | 	} | 
 | 237 | 	if (portnum <= 0 || portnum > HCS_N_PORTS(ehci->hcs_params)) | 
 | 238 | 		return -ENOENT; | 
 | 239 | 	status_reg = &ehci->regs->port_status[--portnum]; | 
 | 240 | 	if (new_owner) | 
 | 241 | 		set_bit(portnum, &ehci->companion_ports); | 
 | 242 | 	else | 
 | 243 | 		clear_bit(portnum, &ehci->companion_ports); | 
 | 244 |  | 
 | 245 | 	/* | 
 | 246 | 	 * The controller won't set the OWNER bit if the port is | 
 | 247 | 	 * enabled, so this loop will sometimes require at least two | 
 | 248 | 	 * iterations: one to disable the port and one to set OWNER. | 
 | 249 | 	 */ | 
 | 250 |  | 
 | 251 | 	for (try = 4; try > 0; --try) { | 
 | 252 | 		spin_lock_irq(&ehci->lock); | 
 | 253 | 		port_status = ehci_readl(ehci, status_reg); | 
 | 254 | 		if ((port_status & PORT_OWNER) == new_owner | 
 | 255 | 				|| (port_status & (PORT_OWNER | PORT_CONNECT)) | 
 | 256 | 					== 0) | 
 | 257 | 			try = 0; | 
 | 258 | 		else { | 
 | 259 | 			port_status ^= PORT_OWNER; | 
 | 260 | 			port_status &= ~(PORT_PE | PORT_RWC_BITS); | 
 | 261 | 			ehci_writel(ehci, port_status, status_reg); | 
 | 262 | 		} | 
 | 263 | 		spin_unlock_irq(&ehci->lock); | 
 | 264 | 		if (try > 1) | 
 | 265 | 			msleep(5); | 
 | 266 | 	} | 
 | 267 | 	return count; | 
 | 268 | } | 
 | 269 | static CLASS_DEVICE_ATTR(companion, 0644, show_companion, store_companion); | 
 | 270 |  | 
 | 271 | static inline void create_companion_file(struct ehci_hcd *ehci) | 
 | 272 | { | 
 | 273 | 	int	i; | 
 | 274 |  | 
 | 275 | 	/* with integrated TT there is no companion! */ | 
 | 276 | 	if (!ehci_is_TDI(ehci)) | 
 | 277 | 		i = class_device_create_file(ehci_to_hcd(ehci)->self.class_dev, | 
 | 278 | 				&class_device_attr_companion); | 
 | 279 | } | 
 | 280 |  | 
 | 281 | static inline void remove_companion_file(struct ehci_hcd *ehci) | 
 | 282 | { | 
 | 283 | 	/* with integrated TT there is no companion! */ | 
 | 284 | 	if (!ehci_is_TDI(ehci)) | 
 | 285 | 		class_device_remove_file(ehci_to_hcd(ehci)->self.class_dev, | 
 | 286 | 				&class_device_attr_companion); | 
 | 287 | } | 
 | 288 |  | 
 | 289 |  | 
 | 290 | /*-------------------------------------------------------------------------*/ | 
 | 291 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 292 | static int check_reset_complete ( | 
 | 293 | 	struct ehci_hcd	*ehci, | 
 | 294 | 	int		index, | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 295 | 	u32 __iomem	*status_reg, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 296 | 	int		port_status | 
 | 297 | ) { | 
 | 298 | 	if (!(port_status & PORT_CONNECT)) { | 
 | 299 | 		ehci->reset_done [index] = 0; | 
 | 300 | 		return port_status; | 
 | 301 | 	} | 
 | 302 |  | 
 | 303 | 	/* if reset finished and it's still not enabled -- handoff */ | 
 | 304 | 	if (!(port_status & PORT_PE)) { | 
 | 305 |  | 
 | 306 | 		/* with integrated TT, there's nobody to hand it to! */ | 
 | 307 | 		if (ehci_is_TDI(ehci)) { | 
 | 308 | 			ehci_dbg (ehci, | 
 | 309 | 				"Failed to enable port %d on root hub TT\n", | 
 | 310 | 				index+1); | 
 | 311 | 			return port_status; | 
 | 312 | 		} | 
 | 313 |  | 
 | 314 | 		ehci_dbg (ehci, "port %d full speed --> companion\n", | 
 | 315 | 			index + 1); | 
 | 316 |  | 
 | 317 | 		// what happens if HCS_N_CC(params) == 0 ? | 
 | 318 | 		port_status |= PORT_OWNER; | 
| David Brownell | 10f6524 | 2005-08-31 10:55:38 -0700 | [diff] [blame] | 319 | 		port_status &= ~PORT_RWC_BITS; | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 320 | 		ehci_writel(ehci, port_status, status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 321 |  | 
 | 322 | 	} else | 
 | 323 | 		ehci_dbg (ehci, "port %d high speed\n", index + 1); | 
 | 324 |  | 
 | 325 | 	return port_status; | 
 | 326 | } | 
 | 327 |  | 
 | 328 | /*-------------------------------------------------------------------------*/ | 
 | 329 |  | 
 | 330 |  | 
 | 331 | /* build "status change" packet (one or two bytes) from HC registers */ | 
 | 332 |  | 
 | 333 | static int | 
 | 334 | ehci_hub_status_data (struct usb_hcd *hcd, char *buf) | 
 | 335 | { | 
 | 336 | 	struct ehci_hcd	*ehci = hcd_to_ehci (hcd); | 
 | 337 | 	u32		temp, status = 0; | 
| David Brownell | 93f1a47 | 2006-11-16 23:34:58 -0800 | [diff] [blame] | 338 | 	u32		mask; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 339 | 	int		ports, i, retval = 1; | 
 | 340 | 	unsigned long	flags; | 
 | 341 |  | 
 | 342 | 	/* if !USB_SUSPEND, root hub timers won't get shut down ... */ | 
 | 343 | 	if (!HC_IS_RUNNING(hcd->state)) | 
 | 344 | 		return 0; | 
 | 345 |  | 
 | 346 | 	/* init status to no-changes */ | 
 | 347 | 	buf [0] = 0; | 
 | 348 | 	ports = HCS_N_PORTS (ehci->hcs_params); | 
 | 349 | 	if (ports > 7) { | 
 | 350 | 		buf [1] = 0; | 
 | 351 | 		retval++; | 
 | 352 | 	} | 
| David Brownell | 53bd6a6 | 2006-08-30 14:50:06 -0700 | [diff] [blame] | 353 |  | 
| David Brownell | 93f1a47 | 2006-11-16 23:34:58 -0800 | [diff] [blame] | 354 | 	/* Some boards (mostly VIA?) report bogus overcurrent indications, | 
 | 355 | 	 * causing massive log spam unless we completely ignore them.  It | 
 | 356 | 	 * may be relevant that VIA VT8235 controlers, where PORT_POWER is | 
 | 357 | 	 * always set, seem to clear PORT_OCC and PORT_CSC when writing to | 
 | 358 | 	 * PORT_POWER; that's surprising, but maybe within-spec. | 
 | 359 | 	 */ | 
 | 360 | 	if (!ignore_oc) | 
 | 361 | 		mask = PORT_CSC | PORT_PEC | PORT_OCC; | 
 | 362 | 	else | 
 | 363 | 		mask = PORT_CSC | PORT_PEC; | 
 | 364 | 	// PORT_RESUME from hardware ~= PORT_STAT_C_SUSPEND | 
 | 365 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 366 | 	/* no hub change reports (bit 0) for now (power, ...) */ | 
 | 367 |  | 
 | 368 | 	/* port N changes (bit N)? */ | 
 | 369 | 	spin_lock_irqsave (&ehci->lock, flags); | 
 | 370 | 	for (i = 0; i < ports; i++) { | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 371 | 		temp = ehci_readl(ehci, &ehci->regs->port_status [i]); | 
| Alan Stern | 625b5c9 | 2007-01-16 11:58:47 -0500 | [diff] [blame] | 372 |  | 
 | 373 | 		/* | 
 | 374 | 		 * Return status information even for ports with OWNER set. | 
 | 375 | 		 * Otherwise khubd wouldn't see the disconnect event when a | 
 | 376 | 		 * high-speed device is switched over to the companion | 
 | 377 | 		 * controller by the user. | 
 | 378 | 		 */ | 
 | 379 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 380 | 		if (!(temp & PORT_CONNECT)) | 
 | 381 | 			ehci->reset_done [i] = 0; | 
| David Brownell | 93f1a47 | 2006-11-16 23:34:58 -0800 | [diff] [blame] | 382 | 		if ((temp & mask) != 0 | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 383 | 				|| ((temp & PORT_RESUME) != 0 | 
| Alan Stern | 629e442 | 2007-01-22 16:08:53 -0500 | [diff] [blame] | 384 | 					&& time_after_eq(jiffies, | 
 | 385 | 						ehci->reset_done[i]))) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 386 | 			if (i < 7) | 
 | 387 | 			    buf [0] |= 1 << (i + 1); | 
 | 388 | 			else | 
 | 389 | 			    buf [1] |= 1 << (i - 7); | 
 | 390 | 			status = STS_PCD; | 
 | 391 | 		} | 
 | 392 | 	} | 
 | 393 | 	/* FIXME autosuspend idle root hubs */ | 
 | 394 | 	spin_unlock_irqrestore (&ehci->lock, flags); | 
 | 395 | 	return status ? retval : 0; | 
 | 396 | } | 
 | 397 |  | 
 | 398 | /*-------------------------------------------------------------------------*/ | 
 | 399 |  | 
 | 400 | static void | 
 | 401 | ehci_hub_descriptor ( | 
 | 402 | 	struct ehci_hcd			*ehci, | 
 | 403 | 	struct usb_hub_descriptor	*desc | 
 | 404 | ) { | 
 | 405 | 	int		ports = HCS_N_PORTS (ehci->hcs_params); | 
 | 406 | 	u16		temp; | 
 | 407 |  | 
 | 408 | 	desc->bDescriptorType = 0x29; | 
 | 409 | 	desc->bPwrOn2PwrGood = 10;	/* ehci 1.0, 2.3.9 says 20ms max */ | 
 | 410 | 	desc->bHubContrCurrent = 0; | 
 | 411 |  | 
 | 412 | 	desc->bNbrPorts = ports; | 
 | 413 | 	temp = 1 + (ports / 8); | 
 | 414 | 	desc->bDescLength = 7 + 2 * temp; | 
 | 415 |  | 
 | 416 | 	/* two bitmaps:  ports removable, and usb 1.0 legacy PortPwrCtrlMask */ | 
 | 417 | 	memset (&desc->bitmap [0], 0, temp); | 
 | 418 | 	memset (&desc->bitmap [temp], 0xff, temp); | 
 | 419 |  | 
 | 420 | 	temp = 0x0008;			/* per-port overcurrent reporting */ | 
 | 421 | 	if (HCS_PPC (ehci->hcs_params)) | 
 | 422 | 		temp |= 0x0001;		/* per-port power control */ | 
| David Brownell | 56c1e26 | 2005-04-09 09:00:29 -0700 | [diff] [blame] | 423 | 	else | 
 | 424 | 		temp |= 0x0002;		/* no power switching */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 425 | #if 0 | 
 | 426 | // re-enable when we support USB_PORT_FEAT_INDICATOR below. | 
 | 427 | 	if (HCS_INDICATOR (ehci->hcs_params)) | 
 | 428 | 		temp |= 0x0080;		/* per-port indicators (LEDs) */ | 
 | 429 | #endif | 
 | 430 | 	desc->wHubCharacteristics = (__force __u16)cpu_to_le16 (temp); | 
 | 431 | } | 
 | 432 |  | 
 | 433 | /*-------------------------------------------------------------------------*/ | 
 | 434 |  | 
| David Brownell | 53bd6a6 | 2006-08-30 14:50:06 -0700 | [diff] [blame] | 435 | #define	PORT_WAKE_BITS	(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 436 |  | 
 | 437 | static int ehci_hub_control ( | 
 | 438 | 	struct usb_hcd	*hcd, | 
 | 439 | 	u16		typeReq, | 
 | 440 | 	u16		wValue, | 
 | 441 | 	u16		wIndex, | 
 | 442 | 	char		*buf, | 
 | 443 | 	u16		wLength | 
 | 444 | ) { | 
 | 445 | 	struct ehci_hcd	*ehci = hcd_to_ehci (hcd); | 
 | 446 | 	int		ports = HCS_N_PORTS (ehci->hcs_params); | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 447 | 	u32 __iomem	*status_reg = &ehci->regs->port_status[wIndex - 1]; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 448 | 	u32		temp, status; | 
 | 449 | 	unsigned long	flags; | 
 | 450 | 	int		retval = 0; | 
| David Brownell | f0d7f27 | 2006-11-16 23:56:15 -0800 | [diff] [blame] | 451 | 	unsigned	selector; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 452 |  | 
 | 453 | 	/* | 
 | 454 | 	 * FIXME:  support SetPortFeatures USB_PORT_FEAT_INDICATOR. | 
 | 455 | 	 * HCS_INDICATOR may say we can change LEDs to off/amber/green. | 
 | 456 | 	 * (track current state ourselves) ... blink for diagnostics, | 
 | 457 | 	 * power, "this is the one", etc.  EHCI spec supports this. | 
 | 458 | 	 */ | 
 | 459 |  | 
 | 460 | 	spin_lock_irqsave (&ehci->lock, flags); | 
 | 461 | 	switch (typeReq) { | 
 | 462 | 	case ClearHubFeature: | 
 | 463 | 		switch (wValue) { | 
 | 464 | 		case C_HUB_LOCAL_POWER: | 
 | 465 | 		case C_HUB_OVER_CURRENT: | 
 | 466 | 			/* no hub-wide feature/status flags */ | 
 | 467 | 			break; | 
 | 468 | 		default: | 
 | 469 | 			goto error; | 
 | 470 | 		} | 
 | 471 | 		break; | 
 | 472 | 	case ClearPortFeature: | 
 | 473 | 		if (!wIndex || wIndex > ports) | 
 | 474 | 			goto error; | 
 | 475 | 		wIndex--; | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 476 | 		temp = ehci_readl(ehci, status_reg); | 
| Alan Stern | 625b5c9 | 2007-01-16 11:58:47 -0500 | [diff] [blame] | 477 |  | 
 | 478 | 		/* | 
 | 479 | 		 * Even if OWNER is set, so the port is owned by the | 
 | 480 | 		 * companion controller, khubd needs to be able to clear | 
 | 481 | 		 * the port-change status bits (especially | 
 | 482 | 		 * USB_PORT_FEAT_C_CONNECTION). | 
 | 483 | 		 */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 484 |  | 
 | 485 | 		switch (wValue) { | 
 | 486 | 		case USB_PORT_FEAT_ENABLE: | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 487 | 			ehci_writel(ehci, temp & ~PORT_PE, status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 488 | 			break; | 
 | 489 | 		case USB_PORT_FEAT_C_ENABLE: | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 490 | 			ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_PEC, | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 491 | 					status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 492 | 			break; | 
 | 493 | 		case USB_PORT_FEAT_SUSPEND: | 
 | 494 | 			if (temp & PORT_RESET) | 
 | 495 | 				goto error; | 
| David Brownell | f8aeb3b | 2006-01-20 13:55:14 -0800 | [diff] [blame] | 496 | 			if (ehci->no_selective_suspend) | 
 | 497 | 				break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 498 | 			if (temp & PORT_SUSPEND) { | 
 | 499 | 				if ((temp & PORT_PE) == 0) | 
 | 500 | 					goto error; | 
 | 501 | 				/* resume signaling for 20 msec */ | 
| David Brownell | 10f6524 | 2005-08-31 10:55:38 -0700 | [diff] [blame] | 502 | 				temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 503 | 				ehci_writel(ehci, temp | PORT_RESUME, | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 504 | 						status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 505 | 				ehci->reset_done [wIndex] = jiffies | 
 | 506 | 						+ msecs_to_jiffies (20); | 
 | 507 | 			} | 
 | 508 | 			break; | 
 | 509 | 		case USB_PORT_FEAT_C_SUSPEND: | 
 | 510 | 			/* we auto-clear this feature */ | 
 | 511 | 			break; | 
 | 512 | 		case USB_PORT_FEAT_POWER: | 
 | 513 | 			if (HCS_PPC (ehci->hcs_params)) | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 514 | 				ehci_writel(ehci, | 
 | 515 | 					  temp & ~(PORT_RWC_BITS | PORT_POWER), | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 516 | 					  status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 517 | 			break; | 
 | 518 | 		case USB_PORT_FEAT_C_CONNECTION: | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 519 | 			ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_CSC, | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 520 | 					status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 521 | 			break; | 
 | 522 | 		case USB_PORT_FEAT_C_OVER_CURRENT: | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 523 | 			ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_OCC, | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 524 | 					status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 525 | 			break; | 
 | 526 | 		case USB_PORT_FEAT_C_RESET: | 
 | 527 | 			/* GetPortStatus clears reset */ | 
 | 528 | 			break; | 
 | 529 | 		default: | 
 | 530 | 			goto error; | 
 | 531 | 		} | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 532 | 		ehci_readl(ehci, &ehci->regs->command);	/* unblock posted write */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 533 | 		break; | 
 | 534 | 	case GetHubDescriptor: | 
 | 535 | 		ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *) | 
 | 536 | 			buf); | 
 | 537 | 		break; | 
 | 538 | 	case GetHubStatus: | 
 | 539 | 		/* no hub-wide feature/status flags */ | 
 | 540 | 		memset (buf, 0, 4); | 
 | 541 | 		//cpu_to_le32s ((u32 *) buf); | 
 | 542 | 		break; | 
 | 543 | 	case GetPortStatus: | 
 | 544 | 		if (!wIndex || wIndex > ports) | 
 | 545 | 			goto error; | 
 | 546 | 		wIndex--; | 
 | 547 | 		status = 0; | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 548 | 		temp = ehci_readl(ehci, status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 549 |  | 
 | 550 | 		// wPortChange bits | 
 | 551 | 		if (temp & PORT_CSC) | 
 | 552 | 			status |= 1 << USB_PORT_FEAT_C_CONNECTION; | 
 | 553 | 		if (temp & PORT_PEC) | 
 | 554 | 			status |= 1 << USB_PORT_FEAT_C_ENABLE; | 
| David Brownell | 93f1a47 | 2006-11-16 23:34:58 -0800 | [diff] [blame] | 555 | 		if ((temp & PORT_OCC) && !ignore_oc) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 556 | 			status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; | 
 | 557 |  | 
 | 558 | 		/* whoever resumes must GetPortStatus to complete it!! */ | 
| Alan Stern | 629e442 | 2007-01-22 16:08:53 -0500 | [diff] [blame] | 559 | 		if (temp & PORT_RESUME) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 560 |  | 
| Alan Stern | 629e442 | 2007-01-22 16:08:53 -0500 | [diff] [blame] | 561 | 			/* Remote Wakeup received? */ | 
 | 562 | 			if (!ehci->reset_done[wIndex]) { | 
 | 563 | 				/* resume signaling for 20 msec */ | 
 | 564 | 				ehci->reset_done[wIndex] = jiffies | 
 | 565 | 						+ msecs_to_jiffies(20); | 
 | 566 | 				/* check the port again */ | 
 | 567 | 				mod_timer(&ehci_to_hcd(ehci)->rh_timer, | 
 | 568 | 						ehci->reset_done[wIndex]); | 
 | 569 | 			} | 
 | 570 |  | 
 | 571 | 			/* resume completed? */ | 
 | 572 | 			else if (time_after_eq(jiffies, | 
 | 573 | 					ehci->reset_done[wIndex])) { | 
 | 574 | 				status |= 1 << USB_PORT_FEAT_C_SUSPEND; | 
 | 575 | 				ehci->reset_done[wIndex] = 0; | 
 | 576 |  | 
 | 577 | 				/* stop resume signaling */ | 
 | 578 | 				temp = ehci_readl(ehci, status_reg); | 
 | 579 | 				ehci_writel(ehci, | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 580 | 					temp & ~(PORT_RWC_BITS | PORT_RESUME), | 
 | 581 | 					status_reg); | 
| Alan Stern | 629e442 | 2007-01-22 16:08:53 -0500 | [diff] [blame] | 582 | 				retval = handshake(ehci, status_reg, | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 583 | 					   PORT_RESUME, 0, 2000 /* 2msec */); | 
| Alan Stern | 629e442 | 2007-01-22 16:08:53 -0500 | [diff] [blame] | 584 | 				if (retval != 0) { | 
 | 585 | 					ehci_err(ehci, | 
 | 586 | 						"port %d resume error %d\n", | 
 | 587 | 						wIndex + 1, retval); | 
 | 588 | 					goto error; | 
 | 589 | 				} | 
 | 590 | 				temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 591 | 			} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 592 | 		} | 
 | 593 |  | 
 | 594 | 		/* whoever resets must GetPortStatus to complete it!! */ | 
 | 595 | 		if ((temp & PORT_RESET) | 
| Alan Stern | 629e442 | 2007-01-22 16:08:53 -0500 | [diff] [blame] | 596 | 				&& time_after_eq(jiffies, | 
 | 597 | 					ehci->reset_done[wIndex])) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 598 | 			status |= 1 << USB_PORT_FEAT_C_RESET; | 
 | 599 | 			ehci->reset_done [wIndex] = 0; | 
 | 600 |  | 
 | 601 | 			/* force reset to complete */ | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 602 | 			ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_RESET), | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 603 | 					status_reg); | 
| David Brownell | c22fa3a | 2005-06-13 07:15:28 -0700 | [diff] [blame] | 604 | 			/* REVISIT:  some hardware needs 550+ usec to clear | 
 | 605 | 			 * this bit; seems too long to spin routinely... | 
 | 606 | 			 */ | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 607 | 			retval = handshake(ehci, status_reg, | 
| David Brownell | c22fa3a | 2005-06-13 07:15:28 -0700 | [diff] [blame] | 608 | 					PORT_RESET, 0, 750); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 609 | 			if (retval != 0) { | 
 | 610 | 				ehci_err (ehci, "port %d reset error %d\n", | 
 | 611 | 					wIndex + 1, retval); | 
 | 612 | 				goto error; | 
 | 613 | 			} | 
 | 614 |  | 
 | 615 | 			/* see what we found out */ | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 616 | 			temp = check_reset_complete (ehci, wIndex, status_reg, | 
 | 617 | 					ehci_readl(ehci, status_reg)); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 618 | 		} | 
 | 619 |  | 
| Alan Stern | 57e06c1 | 2007-01-16 11:59:45 -0500 | [diff] [blame] | 620 | 		/* transfer dedicated ports to the companion hc */ | 
 | 621 | 		if ((temp & PORT_CONNECT) && | 
 | 622 | 				test_bit(wIndex, &ehci->companion_ports)) { | 
 | 623 | 			temp &= ~PORT_RWC_BITS; | 
 | 624 | 			temp |= PORT_OWNER; | 
 | 625 | 			ehci_writel(ehci, temp, status_reg); | 
 | 626 | 			ehci_dbg(ehci, "port %d --> companion\n", wIndex + 1); | 
 | 627 | 			temp = ehci_readl(ehci, status_reg); | 
 | 628 | 		} | 
 | 629 |  | 
| Alan Stern | 625b5c9 | 2007-01-16 11:58:47 -0500 | [diff] [blame] | 630 | 		/* | 
 | 631 | 		 * Even if OWNER is set, there's no harm letting khubd | 
 | 632 | 		 * see the wPortStatus values (they should all be 0 except | 
 | 633 | 		 * for PORT_POWER anyway). | 
 | 634 | 		 */ | 
 | 635 |  | 
 | 636 | 		if (temp & PORT_CONNECT) { | 
 | 637 | 			status |= 1 << USB_PORT_FEAT_CONNECTION; | 
 | 638 | 			// status may be from integrated TT | 
 | 639 | 			status |= ehci_port_speed(ehci, temp); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 640 | 		} | 
| Alan Stern | 625b5c9 | 2007-01-16 11:58:47 -0500 | [diff] [blame] | 641 | 		if (temp & PORT_PE) | 
 | 642 | 			status |= 1 << USB_PORT_FEAT_ENABLE; | 
 | 643 | 		if (temp & (PORT_SUSPEND|PORT_RESUME)) | 
 | 644 | 			status |= 1 << USB_PORT_FEAT_SUSPEND; | 
 | 645 | 		if (temp & PORT_OC) | 
 | 646 | 			status |= 1 << USB_PORT_FEAT_OVER_CURRENT; | 
 | 647 | 		if (temp & PORT_RESET) | 
 | 648 | 			status |= 1 << USB_PORT_FEAT_RESET; | 
 | 649 | 		if (temp & PORT_POWER) | 
 | 650 | 			status |= 1 << USB_PORT_FEAT_POWER; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 651 |  | 
 | 652 | #ifndef	EHCI_VERBOSE_DEBUG | 
 | 653 | 	if (status & ~0xffff)	/* only if wPortChange is interesting */ | 
 | 654 | #endif | 
 | 655 | 		dbg_port (ehci, "GetStatus", wIndex + 1, temp); | 
| Max Dmitrichenko | 6454365 | 2007-03-06 02:45:01 +0300 | [diff] [blame^] | 656 | 		put_unaligned(cpu_to_le32 (status), (__le32 *) buf); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 657 | 		break; | 
 | 658 | 	case SetHubFeature: | 
 | 659 | 		switch (wValue) { | 
 | 660 | 		case C_HUB_LOCAL_POWER: | 
 | 661 | 		case C_HUB_OVER_CURRENT: | 
 | 662 | 			/* no hub-wide feature/status flags */ | 
 | 663 | 			break; | 
 | 664 | 		default: | 
 | 665 | 			goto error; | 
 | 666 | 		} | 
 | 667 | 		break; | 
 | 668 | 	case SetPortFeature: | 
| David Brownell | f0d7f27 | 2006-11-16 23:56:15 -0800 | [diff] [blame] | 669 | 		selector = wIndex >> 8; | 
 | 670 | 		wIndex &= 0xff; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 671 | 		if (!wIndex || wIndex > ports) | 
 | 672 | 			goto error; | 
 | 673 | 		wIndex--; | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 674 | 		temp = ehci_readl(ehci, status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 675 | 		if (temp & PORT_OWNER) | 
 | 676 | 			break; | 
 | 677 |  | 
| David Brownell | 10f6524 | 2005-08-31 10:55:38 -0700 | [diff] [blame] | 678 | 		temp &= ~PORT_RWC_BITS; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 679 | 		switch (wValue) { | 
 | 680 | 		case USB_PORT_FEAT_SUSPEND: | 
| David Brownell | f8aeb3b | 2006-01-20 13:55:14 -0800 | [diff] [blame] | 681 | 			if (ehci->no_selective_suspend) | 
 | 682 | 				break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 683 | 			if ((temp & PORT_PE) == 0 | 
 | 684 | 					|| (temp & PORT_RESET) != 0) | 
 | 685 | 				goto error; | 
| David Brownell | 2c1c3c4 | 2005-11-07 15:24:46 -0800 | [diff] [blame] | 686 | 			if (device_may_wakeup(&hcd->self.root_hub->dev)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 687 | 				temp |= PORT_WAKE_BITS; | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 688 | 			ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 689 | 			break; | 
 | 690 | 		case USB_PORT_FEAT_POWER: | 
 | 691 | 			if (HCS_PPC (ehci->hcs_params)) | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 692 | 				ehci_writel(ehci, temp | PORT_POWER, | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 693 | 						status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 694 | 			break; | 
 | 695 | 		case USB_PORT_FEAT_RESET: | 
 | 696 | 			if (temp & PORT_RESUME) | 
 | 697 | 				goto error; | 
 | 698 | 			/* line status bits may report this as low speed, | 
 | 699 | 			 * which can be fine if this root hub has a | 
 | 700 | 			 * transaction translator built in. | 
 | 701 | 			 */ | 
 | 702 | 			if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT | 
 | 703 | 					&& !ehci_is_TDI(ehci) | 
 | 704 | 					&& PORT_USB11 (temp)) { | 
 | 705 | 				ehci_dbg (ehci, | 
 | 706 | 					"port %d low speed --> companion\n", | 
 | 707 | 					wIndex + 1); | 
 | 708 | 				temp |= PORT_OWNER; | 
 | 709 | 			} else { | 
 | 710 | 				ehci_vdbg (ehci, "port %d reset\n", wIndex + 1); | 
 | 711 | 				temp |= PORT_RESET; | 
 | 712 | 				temp &= ~PORT_PE; | 
 | 713 |  | 
 | 714 | 				/* | 
 | 715 | 				 * caller must wait, then call GetPortStatus | 
 | 716 | 				 * usb 2.0 spec says 50 ms resets on root | 
 | 717 | 				 */ | 
 | 718 | 				ehci->reset_done [wIndex] = jiffies | 
 | 719 | 						+ msecs_to_jiffies (50); | 
 | 720 | 			} | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 721 | 			ehci_writel(ehci, temp, status_reg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 722 | 			break; | 
| David Brownell | f0d7f27 | 2006-11-16 23:56:15 -0800 | [diff] [blame] | 723 |  | 
 | 724 | 		/* For downstream facing ports (these):  one hub port is put | 
 | 725 | 		 * into test mode according to USB2 11.24.2.13, then the hub | 
 | 726 | 		 * must be reset (which for root hub now means rmmod+modprobe, | 
 | 727 | 		 * or else system reboot).  See EHCI 2.3.9 and 4.14 for info | 
 | 728 | 		 * about the EHCI-specific stuff. | 
 | 729 | 		 */ | 
 | 730 | 		case USB_PORT_FEAT_TEST: | 
 | 731 | 			if (!selector || selector > 5) | 
 | 732 | 				goto error; | 
 | 733 | 			ehci_quiesce(ehci); | 
 | 734 | 			ehci_halt(ehci); | 
 | 735 | 			temp |= selector << 16; | 
| Alan Stern | e631656 | 2007-01-16 11:58:00 -0500 | [diff] [blame] | 736 | 			ehci_writel(ehci, temp, status_reg); | 
| David Brownell | f0d7f27 | 2006-11-16 23:56:15 -0800 | [diff] [blame] | 737 | 			break; | 
 | 738 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 739 | 		default: | 
 | 740 | 			goto error; | 
 | 741 | 		} | 
| Benjamin Herrenschmidt | 083522d | 2006-12-15 06:54:08 +1100 | [diff] [blame] | 742 | 		ehci_readl(ehci, &ehci->regs->command);	/* unblock posted writes */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 743 | 		break; | 
 | 744 |  | 
 | 745 | 	default: | 
 | 746 | error: | 
 | 747 | 		/* "stall" on error */ | 
 | 748 | 		retval = -EPIPE; | 
 | 749 | 	} | 
 | 750 | 	spin_unlock_irqrestore (&ehci->lock, flags); | 
 | 751 | 	return retval; | 
 | 752 | } |