]> git.enpas.org Git - openwrt.git/blob - target/linux/adm5120/files/drivers/usb/host/adm5120-hub.c
f8bda82e00b2ae71a7f365285f3908dc7cd79bbe
[openwrt.git] / target / linux / adm5120 / files / drivers / usb / host / adm5120-hub.c
1 /*
2  * OHCI HCD (Host Controller Driver) for USB.
3  *
4  * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
5  * (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net>
6  *
7  * This file is licenced under GPL
8  */
9
10 /*-------------------------------------------------------------------------*/
11
12 /*
13  * OHCI Root Hub ... the nonsharable stuff
14  */
15
16 #define dbg_port(hc,label,num,value) \
17         admhc_dbg(hc, \
18                 "%s port%d " \
19                 "= 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \
20                 label, num, value, \
21                 (value & ADMHC_PS_PRSC) ? " PRSC" : "", \
22                 (value & ADMHC_PS_OCIC) ? " OCIC" : "", \
23                 (value & ADMHC_PS_PSSC) ? " PSSC" : "", \
24                 (value & ADMHC_PS_PESC) ? " PESC" : "", \
25                 (value & ADMHC_PS_CSC) ? " CSC" : "", \
26                 \
27                 (value & ADMHC_PS_LSDA) ? " LSDA" : "", \
28                 (value & ADMHC_PS_PPS) ? " PPS" : "", \
29                 (value & ADMHC_PS_PRS) ? " PRS" : "", \
30                 (value & ADMHC_PS_POCI) ? " POCI" : "", \
31                 (value & ADMHC_PS_PSS) ? " PSS" : "", \
32                 \
33                 (value & ADMHC_PS_PES) ? " PES" : "", \
34                 (value & ADMHC_PS_CCS) ? " CCS" : "" \
35                 );
36
37 #define dbg_port_write(hc,label,num,value) \
38         admhc_dbg(hc, \
39                 "%s port%d " \
40                 "= 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \
41                 label, num, value, \
42                 (value & ADMHC_PS_PRSC) ? " PRSC" : "", \
43                 (value & ADMHC_PS_OCIC) ? " OCIC" : "", \
44                 (value & ADMHC_PS_PSSC) ? " PSSC" : "", \
45                 (value & ADMHC_PS_PESC) ? " PESC" : "", \
46                 (value & ADMHC_PS_CSC) ? " CSC" : "", \
47                 \
48                 (value & ADMHC_PS_CPP) ? " CPP" : "", \
49                 (value & ADMHC_PS_SPP) ? " SPP" : "", \
50                 (value & ADMHC_PS_SPR) ? " SPR" : "", \
51                 (value & ADMHC_PS_CPS) ? " CPS" : "", \
52                 (value & ADMHC_PS_SPS) ? " SPS" : "", \
53                 \
54                 (value & ADMHC_PS_SPE) ? " SPE" : "", \
55                 (value & ADMHC_PS_CPE) ? " CPE" : "" \
56                 );
57
58 /*-------------------------------------------------------------------------*/
59
60 /* hcd->hub_irq_enable() */
61 static void admhc_hub_irq_enable(struct usb_hcd *hcd)
62 {
63         struct admhcd   *ahcd = hcd_to_admhcd(hcd);
64
65         spin_lock_irq(&ahcd->lock);
66         if (!ahcd->autostop)
67                 del_timer(&hcd->rh_timer);      /* Prevent next poll */
68         admhc_intr_enable(ahcd, ADMHC_INTR_INSM);
69         spin_unlock_irq(&ahcd->lock);
70 }
71
72 /*-------------------------------------------------------------------------*/
73
74 /* build "status change" packet (one or two bytes) from HC registers */
75
76 static int
77 admhc_hub_status_data(struct usb_hcd *hcd, char *buf)
78 {
79         struct admhcd   *ahcd = hcd_to_admhcd(hcd);
80         int             i, changed = 0, length = 1;
81         int             any_connected = 0;
82         unsigned long   flags;
83         u32             status;
84
85         spin_lock_irqsave(&ahcd->lock, flags);
86         if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))
87                 goto done;
88
89         /* init status */
90         status = admhc_read_rhdesc(ahcd);
91         if (status & (ADMHC_RH_LPSC | ADMHC_RH_OCIC))
92                 buf [0] = changed = 1;
93         else
94                 buf [0] = 0;
95         if (ahcd->num_ports > 7) {
96                 buf [1] = 0;
97                 length++;
98         }
99
100         /* look at each port */
101         for (i = 0; i < ahcd->num_ports; i++) {
102                 status = admhc_read_portstatus(ahcd, i);
103
104                 /* can't autostop if ports are connected */
105                 any_connected |= (status & ADMHC_PS_CCS);
106
107                 if (status & (ADMHC_PS_CSC | ADMHC_PS_PESC | ADMHC_PS_PSSC
108                                 | ADMHC_PS_OCIC | ADMHC_PS_PRSC)) {
109                         changed = 1;
110                         if (i < 7)
111                             buf [0] |= 1 << (i + 1);
112                         else
113                             buf [1] |= 1 << (i - 7);
114                 }
115         }
116
117         hcd->poll_rh = admhc_root_hub_state_changes(ahcd, changed,
118                         any_connected);
119
120 done:
121         spin_unlock_irqrestore(&ahcd->lock, flags);
122
123         return changed ? length : 0;
124 }
125
126 /*-------------------------------------------------------------------------*/
127
128 static int admhc_get_hub_descriptor(struct admhcd *ahcd, char *buf)
129 {
130         struct usb_hub_descriptor *desc = (struct usb_hub_descriptor *)buf;
131         u32 rh = admhc_read_rhdesc(ahcd);
132         u16 temp;
133
134         desc->bDescriptorType = USB_DT_HUB;     /* Hub-descriptor */
135         desc->bPwrOn2PwrGood = ADMHC_POTPGT/2;  /* use default value */
136         desc->bHubContrCurrent = 0x00;          /* 0mA */
137
138         desc->bNbrPorts = ahcd->num_ports;
139         temp = 1 + (ahcd->num_ports / 8);
140         desc->bDescLength = USB_DT_HUB_NONVAR_SIZE + 2 * temp;
141
142         /* FIXME */
143         temp = 0;
144         if (rh & ADMHC_RH_NPS)          /* no power switching? */
145             temp |= 0x0002;
146         if (rh & ADMHC_RH_PSM)          /* per-port power switching? */
147             temp |= 0x0001;
148         if (rh & ADMHC_RH_NOCP)         /* no overcurrent reporting? */
149             temp |= 0x0010;
150         else if (rh & ADMHC_RH_OCPM)    /* per-port overcurrent reporting? */
151             temp |= 0x0008;
152         desc->wHubCharacteristics = (__force __u16)cpu_to_hc16(ahcd, temp);
153
154         /* two bitmaps:  ports removable, and usb 1.0 legacy PortPwrCtrlMask */
155         desc->bitmap[0] = 0;
156         desc->bitmap[0] = ~0;
157
158         return 0;
159 }
160
161 static int admhc_get_hub_status(struct admhcd *ahcd, char *buf)
162 {
163         struct usb_hub_status *hs = (struct usb_hub_status *)buf;
164         u32 t = admhc_read_rhdesc(ahcd);
165         u16 status, change;
166
167         status = 0;
168         status |= (t & ADMHC_RH_LPS) ? HUB_STATUS_LOCAL_POWER : 0;
169         status |= (t & ADMHC_RH_OCI) ? HUB_STATUS_OVERCURRENT : 0;
170
171         change = 0;
172         change |= (t & ADMHC_RH_LPSC) ? HUB_CHANGE_LOCAL_POWER : 0;
173         change |= (t & ADMHC_RH_OCIC) ? HUB_CHANGE_OVERCURRENT : 0;
174
175         hs->wHubStatus = (__force __u16)cpu_to_hc16(ahcd, status);
176         hs->wHubChange = (__force __u16)cpu_to_hc16(ahcd, change);
177
178         return 0;
179 }
180
181 static int admhc_get_port_status(struct admhcd *ahcd, unsigned port, char *buf)
182 {
183         struct usb_port_status *ps = (struct usb_port_status *)buf;
184         u32 t = admhc_read_portstatus(ahcd, port);
185         u16 status, change;
186
187         status = 0;
188         status |= (t & ADMHC_PS_CCS) ? USB_PORT_STAT_CONNECTION : 0;
189         status |= (t & ADMHC_PS_PES) ? USB_PORT_STAT_ENABLE : 0;
190         status |= (t & ADMHC_PS_PSS) ? USB_PORT_STAT_SUSPEND : 0;
191         status |= (t & ADMHC_PS_POCI) ? USB_PORT_STAT_OVERCURRENT : 0;
192         status |= (t & ADMHC_PS_PRS) ? USB_PORT_STAT_RESET : 0;
193         status |= (t & ADMHC_PS_PPS) ? USB_PORT_STAT_POWER : 0;
194         status |= (t & ADMHC_PS_LSDA) ? USB_PORT_STAT_LOW_SPEED : 0;
195
196         change = 0;
197         change |= (t & ADMHC_PS_CSC) ? USB_PORT_STAT_C_CONNECTION : 0;
198         change |= (t & ADMHC_PS_PESC) ? USB_PORT_STAT_C_ENABLE : 0;
199         change |= (t & ADMHC_PS_PSSC) ? USB_PORT_STAT_C_SUSPEND : 0;
200         change |= (t & ADMHC_PS_OCIC) ? USB_PORT_STAT_C_OVERCURRENT : 0;
201         change |= (t & ADMHC_PS_PRSC) ? USB_PORT_STAT_C_RESET : 0;
202
203         ps->wPortStatus = (__force __u16)cpu_to_hc16(ahcd, status);
204         ps->wPortChange = (__force __u16)cpu_to_hc16(ahcd, change);
205
206         return 0;
207 }
208
209 /*-------------------------------------------------------------------------*/
210
211 #ifdef  CONFIG_USB_OTG
212
213 static int admhc_start_port_reset(struct usb_hcd *hcd, unsigned port)
214 {
215         struct admhcd   *ahcd = hcd_to_admhcd(hcd);
216         u32                     status;
217
218         if (!port)
219                 return -EINVAL;
220         port--;
221
222         /* start port reset before HNP protocol times out */
223         status = admhc_read_portstatus(ahcd, port);
224         if (!(status & ADMHC_PS_CCS))
225                 return -ENODEV;
226
227         /* khubd will finish the reset later */
228         admhc_write_portstatus(ahcd, port, ADMHC_PS_PRS);
229         return 0;
230 }
231
232 static void start_hnp(struct admhcd *ahcd);
233
234 #else
235
236 #define admhc_start_port_reset          NULL
237
238 #endif
239
240 /*-------------------------------------------------------------------------*/
241
242
243 /* See usb 7.1.7.5:  root hubs must issue at least 50 msec reset signaling,
244  * not necessarily continuous ... to guard against resume signaling.
245  * The short timeout is safe for non-root hubs, and is backward-compatible
246  * with earlier Linux hosts.
247  */
248 #ifdef  CONFIG_USB_SUSPEND
249 #define PORT_RESET_MSEC         50
250 #else
251 #define PORT_RESET_MSEC         10
252 #endif
253
254 /* this timer value might be vendor-specific ... */
255 #define PORT_RESET_HW_MSEC      10
256
257 /* wrap-aware logic morphed from <linux/jiffies.h> */
258 #define tick_before(t1,t2) ((s16)(((s16)(t1))-((s16)(t2))) < 0)
259
260 /* called from some task, normally khubd */
261 static inline int admhc_port_reset(struct admhcd *ahcd, unsigned port)
262 {
263         u32 t;
264         int c;
265
266         admhc_vdbg(ahcd, "reset port%d\n", port);
267
268         t = admhc_read_portstatus(ahcd, port);
269         if (!(t & ADMHC_PS_CCS))
270                 return -ENODEV;
271
272         if ((t & ADMHC_PS_PRS))
273                 return 0;
274
275         admhc_write_portstatus(ahcd, port, ADMHC_PS_PRS);
276         c = 0;
277         do {
278                 t = admhc_read_portstatus(ahcd, port);
279                 if (t & ADMHC_PS_PRSC)
280                         break;
281
282                 if (++c > 20) {
283                         admhc_err(ahcd, "port%d reset timed out\n",port);
284                         return -EPIPE;
285                 }
286
287                 mdelay(PORT_RESET_HW_MSEC);
288         } while (1);
289         admhc_vdbg(ahcd, "port%d reset completed within %dms\n", port,
290                         c * PORT_RESET_HW_MSEC);
291
292         t = admhc_read_portstatus(ahcd, port);
293         if (!(t & ADMHC_PS_CCS)) {
294                 admhc_err(ahcd, "port%d is not connected after reset\n",port);
295                 return -ENODEV;
296         }
297
298         admhc_write_portstatus(ahcd, port, ADMHC_PS_SPE);
299         c = 0;
300         do {
301                 t = admhc_read_portstatus(ahcd, port);
302                 if (t & ADMHC_PS_PESC)
303                         break;
304
305                 if (++c > 20) {
306                         admhc_err(ahcd, "port%d enable timed out\n",port);
307                         return -EPIPE;
308                 }
309
310                 mdelay(PORT_RESET_HW_MSEC);
311         } while (1);
312         admhc_vdbg(ahcd, "port%d enable completed within %dms\n", port,
313                         c * PORT_RESET_HW_MSEC);
314
315         admhc_write_portstatus(ahcd, port, ADMHC_PS_CSC);
316
317         return 0;
318 }
319
320 static inline int admhc_port_enable(struct admhcd *ahcd, unsigned port)
321 {
322         u32 t;
323
324         admhc_vdbg(ahcd, "enable port%d\n", port);
325         t = admhc_read_portstatus(ahcd, port);
326         if (!(t & ADMHC_PS_CCS))
327                 return -ENODEV;
328
329         admhc_write_portstatus(ahcd, port, ADMHC_PS_SPE);
330
331         return 0;
332 }
333
334 static inline int admhc_port_disable(struct admhcd *ahcd, unsigned port)
335 {
336         u32 t;
337
338         admhc_vdbg(ahcd, "disable port%d\n", port);
339         t = admhc_read_portstatus(ahcd, port);
340         if (!(t & ADMHC_PS_CCS))
341                 return -ENODEV;
342
343         admhc_write_portstatus(ahcd, ADMHC_PS_CPE, port);
344
345         return 0;
346 }
347
348 static inline int admhc_port_write(struct admhcd *ahcd, unsigned port,
349                 u32 val)
350 {
351         dbg_port_write(ahcd, "write", port, val);
352         admhc_write_portstatus(ahcd, port, val);
353
354         return 0;
355 }
356
357 static int admhc_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
358                 u16 wIndex, char *buf, u16 wLength)
359 {
360         struct admhcd   *ahcd = hcd_to_admhcd(hcd);
361         int             ports = hcd_to_bus(hcd)->root_hub->maxchild;
362         int             ret = 0;
363
364         if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
365                 return -ESHUTDOWN;
366
367         switch (typeReq) {
368         case ClearHubFeature:
369                 switch (wValue) {
370                 case C_HUB_OVER_CURRENT:
371 #if 0                   /* FIXME */
372                         admhc_writel(ahcd, ADMHC_RH_OCIC,
373                                         &ahcd->regs->roothub.status);
374 #endif
375                 case C_HUB_LOCAL_POWER:
376                         break;
377                 default:
378                         goto error;
379                 }
380                 break;
381         case ClearPortFeature:
382                 if (!wIndex || wIndex > ports)
383                         goto error;
384                 wIndex--;
385
386                 switch (wValue) {
387                 case USB_PORT_FEAT_ENABLE:
388                         ret = admhc_port_disable(ahcd, wIndex);
389                         break;
390                 case USB_PORT_FEAT_SUSPEND:
391                         ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_CPS);
392                         break;
393                 case USB_PORT_FEAT_POWER:
394                         ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_CPP);
395                         break;
396                 case USB_PORT_FEAT_C_CONNECTION:
397                         ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_CSC);
398                         break;
399                 case USB_PORT_FEAT_C_ENABLE:
400                         ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_PESC);
401                         break;
402                 case USB_PORT_FEAT_C_SUSPEND:
403                         ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_PSSC);
404                         break;
405                 case USB_PORT_FEAT_C_OVER_CURRENT:
406                         ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_OCIC);
407                         break;
408                 case USB_PORT_FEAT_C_RESET:
409                         ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_PRSC);
410                         break;
411                 default:
412                         goto error;
413                 }
414                 break;
415         case GetHubDescriptor:
416                 ret = admhc_get_hub_descriptor(ahcd, buf);
417                 break;
418         case GetHubStatus:
419                 ret = admhc_get_hub_status(ahcd, buf);
420                 break;
421         case GetPortStatus:
422                 if (!wIndex || wIndex > ports)
423                         goto error;
424                 wIndex--;
425
426                 ret = admhc_get_port_status(ahcd, wIndex, buf);
427                 break;
428         case SetHubFeature:
429                 switch (wValue) {
430                 case C_HUB_OVER_CURRENT:
431                         /* FIXME:  this can be cleared, yes? */
432                 case C_HUB_LOCAL_POWER:
433                         break;
434                 default:
435                         goto error;
436                 }
437                 break;
438         case SetPortFeature:
439                 if (!wIndex || wIndex > ports)
440                         goto error;
441                 wIndex--;
442
443                 switch (wValue) {
444                 case USB_PORT_FEAT_ENABLE:
445                         ret = admhc_port_enable(ahcd, wIndex);
446                         break;
447                 case USB_PORT_FEAT_RESET:
448                         ret = admhc_port_reset(ahcd, wIndex);
449                         break;
450                 case USB_PORT_FEAT_SUSPEND:
451 #ifdef  CONFIG_USB_OTG
452                         if (hcd->self.otg_port == (wIndex + 1)
453                                         && hcd->self.b_hnp_enable)
454                                 start_hnp(ahcd);
455                         else
456 #endif
457                         ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_SPS);
458                         break;
459                 case USB_PORT_FEAT_POWER:
460                         ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_SPP);
461                         break;
462                 default:
463                         goto error;
464                 }
465                 break;
466
467         default:
468 error:
469                 /* "protocol stall" on error */
470                 ret = -EPIPE;
471         }
472
473         return ret;
474 }
475