Root/target/linux/adm5120/files/drivers/usb/host/adm5120-hub.c

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

Archive Download this file



interactive