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