fb258264b3fee1b4ed091c2cb450282e3caeac58
[linux-flexiantxendom0-3.2.10.git] / drivers / usb / host / ehci-hub.c
1 /*
2  * Copyright (c) 2001-2002 by David Brownell
3  * 
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 static int check_reset_complete (
32         struct ehci_hcd *ehci,
33         int             index,
34         int             port_status
35 ) {
36         if (!(port_status & PORT_CONNECT)) {
37                 ehci->reset_done [index] = 0;
38                 return port_status;
39         }
40
41         /* if reset finished and it's still not enabled -- handoff */
42         if (!(port_status & PORT_PE)) {
43                 ehci_dbg (ehci, "port %d full speed --> companion\n",
44                         index + 1);
45
46                 // what happens if HCS_N_CC(params) == 0 ?
47                 port_status |= PORT_OWNER;
48                 writel (port_status, &ehci->regs->port_status [index]);
49
50         } else
51                 ehci_dbg (ehci, "port %d high speed\n", index + 1);
52
53         return port_status;
54 }
55
56 /*-------------------------------------------------------------------------*/
57
58
59 /* build "status change" packet (one or two bytes) from HC registers */
60
61 static int
62 ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
63 {
64         struct ehci_hcd *ehci = hcd_to_ehci (hcd);
65         u32             temp, status = 0;
66         int             ports, i, retval = 1;
67         unsigned long   flags;
68
69         /* init status to no-changes */
70         buf [0] = 0;
71         ports = HCS_N_PORTS (ehci->hcs_params);
72         if (ports > 7) {
73                 buf [1] = 0;
74                 retval++;
75         }
76         
77         /* no hub change reports (bit 0) for now (power, ...) */
78
79         /* port N changes (bit N)? */
80         spin_lock_irqsave (&ehci->lock, flags);
81         for (i = 0; i < ports; i++) {
82                 temp = readl (&ehci->regs->port_status [i]);
83                 if (temp & PORT_OWNER) {
84                         /* don't report this in GetPortStatus */
85                         if (temp & PORT_CSC) {
86                                 temp &= ~PORT_CSC;
87                                 writel (temp, &ehci->regs->port_status [i]);
88                         }
89                         continue;
90                 }
91                 if (!(temp & PORT_CONNECT))
92                         ehci->reset_done [i] = 0;
93                 if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0) {
94                         if (i < 7)
95                             buf [0] |= 1 << (i + 1);
96                         else
97                             buf [1] |= 1 << (i - 7);
98                         status = STS_PCD;
99                 }
100         }
101         spin_unlock_irqrestore (&ehci->lock, flags);
102         return status ? retval : 0;
103 }
104
105 /*-------------------------------------------------------------------------*/
106
107 static void
108 ehci_hub_descriptor (
109         struct ehci_hcd                 *ehci,
110         struct usb_hub_descriptor       *desc
111 ) {
112         int             ports = HCS_N_PORTS (ehci->hcs_params);
113         u16             temp;
114
115         desc->bDescriptorType = 0x29;
116         desc->bPwrOn2PwrGood = 10;      /* FIXME: f(system power) */
117         desc->bHubContrCurrent = 0;
118
119         desc->bNbrPorts = ports;
120         temp = 1 + (ports / 8);
121         desc->bDescLength = 7 + 2 * temp;
122
123         /* two bitmaps:  ports removable, and usb 1.0 legacy PortPwrCtrlMask */
124         memset (&desc->bitmap [0], 0, temp);
125         memset (&desc->bitmap [temp], 0xff, temp);
126
127         temp = 0x0008;                  /* per-port overcurrent reporting */
128         if (HCS_PPC (ehci->hcs_params))
129                 temp |= 0x0001;         /* per-port power control */
130         if (HCS_INDICATOR (ehci->hcs_params))
131                 temp |= 0x0080;         /* per-port indicators (LEDs) */
132         desc->wHubCharacteristics = cpu_to_le16 (temp);
133 }
134
135 /*-------------------------------------------------------------------------*/
136
137 static int ehci_hub_control (
138         struct usb_hcd  *hcd,
139         u16             typeReq,
140         u16             wValue,
141         u16             wIndex,
142         char            *buf,
143         u16             wLength
144 ) {
145         struct ehci_hcd *ehci = hcd_to_ehci (hcd);
146         int             ports = HCS_N_PORTS (ehci->hcs_params);
147         u32             temp, status;
148         unsigned long   flags;
149         int             retval = 0;
150
151         /*
152          * FIXME:  support SetPortFeatures USB_PORT_FEAT_INDICATOR.
153          * HCS_INDICATOR may say we can change LEDs to off/amber/green.
154          * (track current state ourselves) ... blink for diagnostics,
155          * power, "this is the one", etc.  EHCI spec supports this.
156          */
157
158         spin_lock_irqsave (&ehci->lock, flags);
159         switch (typeReq) {
160         case ClearHubFeature:
161                 switch (wValue) {
162                 case C_HUB_LOCAL_POWER:
163                 case C_HUB_OVER_CURRENT:
164                         /* no hub-wide feature/status flags */
165                         break;
166                 default:
167                         goto error;
168                 }
169                 break;
170         case ClearPortFeature:
171                 if (!wIndex || wIndex > ports)
172                         goto error;
173                 wIndex--;
174                 temp = readl (&ehci->regs->port_status [wIndex]);
175                 if (temp & PORT_OWNER)
176                         break;
177
178                 switch (wValue) {
179                 case USB_PORT_FEAT_ENABLE:
180                         writel (temp & ~PORT_PE,
181                                 &ehci->regs->port_status [wIndex]);
182                         break;
183                 case USB_PORT_FEAT_C_ENABLE:
184                         writel (temp | PORT_PEC,
185                                 &ehci->regs->port_status [wIndex]);
186                         break;
187                 case USB_PORT_FEAT_SUSPEND:
188                 case USB_PORT_FEAT_C_SUSPEND:
189                         /* ? */
190                         break;
191                 case USB_PORT_FEAT_POWER:
192                         if (HCS_PPC (ehci->hcs_params))
193                                 writel (temp & ~PORT_POWER,
194                                         &ehci->regs->port_status [wIndex]);
195                         break;
196                 case USB_PORT_FEAT_C_CONNECTION:
197                         writel (temp | PORT_CSC,
198                                 &ehci->regs->port_status [wIndex]);
199                         break;
200                 case USB_PORT_FEAT_C_OVER_CURRENT:
201                         writel (temp | PORT_OCC,
202                                 &ehci->regs->port_status [wIndex]);
203                         break;
204                 case USB_PORT_FEAT_C_RESET:
205                         /* GetPortStatus clears reset */
206                         break;
207                 default:
208                         goto error;
209                 }
210                 readl (&ehci->regs->command);   /* unblock posted write */
211                 break;
212         case GetHubDescriptor:
213                 ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *)
214                         buf);
215                 break;
216         case GetHubStatus:
217                 /* no hub-wide feature/status flags */
218                 memset (buf, 0, 4);
219                 //cpu_to_le32s ((u32 *) buf);
220                 break;
221         case GetPortStatus:
222                 if (!wIndex || wIndex > ports)
223                         goto error;
224                 wIndex--;
225                 status = 0;
226                 temp = readl (&ehci->regs->port_status [wIndex]);
227
228                 // wPortChange bits
229                 if (temp & PORT_CSC)
230                         status |= 1 << USB_PORT_FEAT_C_CONNECTION;
231                 if (temp & PORT_PEC)
232                         status |= 1 << USB_PORT_FEAT_C_ENABLE;
233                 // USB_PORT_FEAT_C_SUSPEND
234                 if (temp & PORT_OCC)
235                         status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
236
237                 /* whoever resets must GetPortStatus to complete it!! */
238                 if ((temp & PORT_RESET)
239                                 && time_after (jiffies,
240                                         ehci->reset_done [wIndex])) {
241                         status |= 1 << USB_PORT_FEAT_C_RESET;
242
243                         /* force reset to complete */
244                         writel (temp & ~PORT_RESET,
245                                         &ehci->regs->port_status [wIndex]);
246                         do {
247                                 temp = readl (
248                                         &ehci->regs->port_status [wIndex]);
249                                 udelay (10);
250                         } while (temp & PORT_RESET);
251
252                         /* see what we found out */
253                         temp = check_reset_complete (ehci, wIndex, temp);
254                 }
255
256                 // don't show wPortStatus if it's owned by a companion hc
257                 if (!(temp & PORT_OWNER)) {
258                         if (temp & PORT_CONNECT) {
259                                 status |= 1 << USB_PORT_FEAT_CONNECTION;
260                                 status |= 1 << USB_PORT_FEAT_HIGHSPEED;
261                         }
262                         if (temp & PORT_PE)
263                                 status |= 1 << USB_PORT_FEAT_ENABLE;
264                         if (temp & PORT_SUSPEND)
265                                 status |= 1 << USB_PORT_FEAT_SUSPEND;
266                         if (temp & PORT_OC)
267                                 status |= 1 << USB_PORT_FEAT_OVER_CURRENT;
268                         if (temp & PORT_RESET)
269                                 status |= 1 << USB_PORT_FEAT_RESET;
270                         if (temp & PORT_POWER)
271                                 status |= 1 << USB_PORT_FEAT_POWER;
272                 }
273
274 #ifndef EHCI_VERBOSE_DEBUG
275         if (status & ~0xffff)   /* only if wPortChange is interesting */
276 #endif
277                 dbg_port (ehci, "GetStatus", wIndex + 1, temp);
278                 // we "know" this alignment is good, caller used kmalloc()...
279                 *((u32 *) buf) = cpu_to_le32 (status);
280                 break;
281         case SetHubFeature:
282                 switch (wValue) {
283                 case C_HUB_LOCAL_POWER:
284                 case C_HUB_OVER_CURRENT:
285                         /* no hub-wide feature/status flags */
286                         break;
287                 default:
288                         goto error;
289                 }
290                 break;
291         case SetPortFeature:
292                 if (!wIndex || wIndex > ports)
293                         goto error;
294                 wIndex--;
295                 temp = readl (&ehci->regs->port_status [wIndex]);
296                 if (temp & PORT_OWNER)
297                         break;
298
299                 switch (wValue) {
300                 case USB_PORT_FEAT_SUSPEND:
301                         writel (temp | PORT_SUSPEND,
302                                 &ehci->regs->port_status [wIndex]);
303                         break;
304                 case USB_PORT_FEAT_POWER:
305                         if (HCS_PPC (ehci->hcs_params))
306                                 writel (temp | PORT_POWER,
307                                         &ehci->regs->port_status [wIndex]);
308                         break;
309                 case USB_PORT_FEAT_RESET:
310                         /* line status bits may report this as low speed */
311                         if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
312                                         && PORT_USB11 (temp)) {
313                                 ehci_dbg (ehci,
314                                         "port %d low speed --> companion\n",
315                                         wIndex + 1);
316                                 temp |= PORT_OWNER;
317                         } else {
318                                 ehci_vdbg (ehci, "port %d reset\n", wIndex + 1);
319                                 temp |= PORT_RESET;
320                                 temp &= ~PORT_PE;
321
322                                 /*
323                                  * caller must wait, then call GetPortStatus
324                                  * usb 2.0 spec says 50 ms resets on root
325                                  */
326                                 ehci->reset_done [wIndex] = jiffies
327                                         + ((50 /* msec */ * HZ) / 1000);
328                         }
329                         writel (temp, &ehci->regs->port_status [wIndex]);
330                         break;
331                 default:
332                         goto error;
333                 }
334                 readl (&ehci->regs->command);   /* unblock posted writes */
335                 break;
336
337         default:
338 error:
339                 /* "stall" on error */
340                 retval = -EPIPE;
341         }
342         spin_unlock_irqrestore (&ehci->lock, flags);
343         return retval;
344 }