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 (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))
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    hcd->poll_rh = admhc_root_hub_state_changes(ahcd, changed,
110            any_connected);
111
112done:
113    spin_unlock_irqrestore(&ahcd->lock, flags);
114
115    return changed ? length : 0;
116}
117
118/*-------------------------------------------------------------------------*/
119
120static int admhc_get_hub_descriptor(struct admhcd *ahcd, char *buf)
121{
122    struct usb_hub_descriptor *desc = (struct usb_hub_descriptor *)buf;
123    u32 rh = admhc_read_rhdesc(ahcd);
124    u16 temp;
125
126    desc->bDescriptorType = USB_DT_HUB; /* Hub-descriptor */
127    desc->bPwrOn2PwrGood = ADMHC_POTPGT/2; /* use default value */
128    desc->bHubContrCurrent = 0x00; /* 0mA */
129
130    desc->bNbrPorts = ahcd->num_ports;
131    temp = 1 + (ahcd->num_ports / 8);
132    desc->bDescLength = USB_DT_HUB_NONVAR_SIZE + 2 * temp;
133
134    /* FIXME */
135    temp = 0;
136    if (rh & ADMHC_RH_NPS) /* no power switching? */
137        temp |= 0x0002;
138    if (rh & ADMHC_RH_PSM) /* per-port power switching? */
139        temp |= 0x0001;
140    if (rh & ADMHC_RH_NOCP) /* no overcurrent reporting? */
141        temp |= 0x0010;
142    else if (rh & ADMHC_RH_OCPM) /* per-port overcurrent reporting? */
143        temp |= 0x0008;
144    desc->wHubCharacteristics = (__force __u16)cpu_to_hc16(ahcd, temp);
145
146    /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
147    desc->bitmap[0] = 0;
148    desc->bitmap[0] = ~0;
149
150    return 0;
151}
152
153static int admhc_get_hub_status(struct admhcd *ahcd, char *buf)
154{
155    struct usb_hub_status *hs = (struct usb_hub_status *)buf;
156    u32 t = admhc_read_rhdesc(ahcd);
157    u16 status, change;
158
159    status = 0;
160    status |= (t & ADMHC_RH_LPS) ? HUB_STATUS_LOCAL_POWER : 0;
161    status |= (t & ADMHC_RH_OCI) ? HUB_STATUS_OVERCURRENT : 0;
162
163    change = 0;
164    change |= (t & ADMHC_RH_LPSC) ? HUB_CHANGE_LOCAL_POWER : 0;
165    change |= (t & ADMHC_RH_OCIC) ? HUB_CHANGE_OVERCURRENT : 0;
166
167    hs->wHubStatus = (__force __u16)cpu_to_hc16(ahcd, status);
168    hs->wHubChange = (__force __u16)cpu_to_hc16(ahcd, change);
169
170    return 0;
171}
172
173static int admhc_get_port_status(struct admhcd *ahcd, unsigned port, char *buf)
174{
175    struct usb_port_status *ps = (struct usb_port_status *)buf;
176    u32 t = admhc_read_portstatus(ahcd, port);
177    u16 status, change;
178
179    status = 0;
180    status |= (t & ADMHC_PS_CCS) ? USB_PORT_STAT_CONNECTION : 0;
181    status |= (t & ADMHC_PS_PES) ? USB_PORT_STAT_ENABLE : 0;
182    status |= (t & ADMHC_PS_PSS) ? USB_PORT_STAT_SUSPEND : 0;
183    status |= (t & ADMHC_PS_POCI) ? USB_PORT_STAT_OVERCURRENT : 0;
184    status |= (t & ADMHC_PS_PRS) ? USB_PORT_STAT_RESET : 0;
185    status |= (t & ADMHC_PS_PPS) ? USB_PORT_STAT_POWER : 0;
186    status |= (t & ADMHC_PS_LSDA) ? USB_PORT_STAT_LOW_SPEED : 0;
187
188    change = 0;
189    change |= (t & ADMHC_PS_CSC) ? USB_PORT_STAT_C_CONNECTION : 0;
190    change |= (t & ADMHC_PS_PESC) ? USB_PORT_STAT_C_ENABLE : 0;
191    change |= (t & ADMHC_PS_PSSC) ? USB_PORT_STAT_C_SUSPEND : 0;
192    change |= (t & ADMHC_PS_OCIC) ? USB_PORT_STAT_C_OVERCURRENT : 0;
193    change |= (t & ADMHC_PS_PRSC) ? USB_PORT_STAT_C_RESET : 0;
194
195    ps->wPortStatus = (__force __u16)cpu_to_hc16(ahcd, status);
196    ps->wPortChange = (__force __u16)cpu_to_hc16(ahcd, change);
197
198    return 0;
199}
200
201/*-------------------------------------------------------------------------*/
202
203#ifdef CONFIG_USB_OTG
204
205static int admhc_start_port_reset(struct usb_hcd *hcd, unsigned port)
206{
207    struct admhcd *ahcd = hcd_to_admhcd(hcd);
208    u32 status;
209
210    if (!port)
211        return -EINVAL;
212    port--;
213
214    /* start port reset before HNP protocol times out */
215    status = admhc_read_portstatus(ahcd, port);
216    if (!(status & ADMHC_PS_CCS))
217        return -ENODEV;
218
219    /* khubd will finish the reset later */
220    admhc_write_portstatus(ahcd, port, ADMHC_PS_PRS);
221    return 0;
222}
223
224static void start_hnp(struct admhcd *ahcd);
225
226#else
227
228#define admhc_start_port_reset NULL
229
230#endif
231
232/*-------------------------------------------------------------------------*/
233
234
235/* See usb 7.1.7.5: root hubs must issue at least 50 msec reset signaling,
236 * not necessarily continuous ... to guard against resume signaling.
237 * The short timeout is safe for non-root hubs, and is backward-compatible
238 * with earlier Linux hosts.
239 */
240#ifdef CONFIG_USB_SUSPEND
241#define PORT_RESET_MSEC 50
242#else
243#define PORT_RESET_MSEC 10
244#endif
245
246/* this timer value might be vendor-specific ... */
247#define PORT_RESET_HW_MSEC 10
248
249/* wrap-aware logic morphed from <linux/jiffies.h> */
250#define tick_before(t1, t2) ((s16)(((s16)(t1)) - ((s16)(t2))) < 0)
251
252/* called from some task, normally khubd */
253static inline int admhc_port_reset(struct admhcd *ahcd, unsigned port)
254{
255    u32 t;
256
257    admhc_vdbg(ahcd, "reset port%d\n", port);
258    t = admhc_read_portstatus(ahcd, port);
259    if (!(t & ADMHC_PS_CCS))
260        return -ENODEV;
261
262    admhc_write_portstatus(ahcd, port, ADMHC_PS_SPR);
263    mdelay(10);
264    admhc_write_portstatus(ahcd, port, (ADMHC_PS_SPE | ADMHC_PS_CSC));
265    mdelay(100);
266
267    return 0;
268}
269
270static inline int admhc_port_enable(struct admhcd *ahcd, unsigned port)
271{
272    u32 t;
273
274    admhc_vdbg(ahcd, "enable port%d\n", port);
275    t = admhc_read_portstatus(ahcd, port);
276    if (!(t & ADMHC_PS_CCS))
277        return -ENODEV;
278
279    admhc_write_portstatus(ahcd, port, ADMHC_PS_SPE);
280
281    return 0;
282}
283
284static inline int admhc_port_disable(struct admhcd *ahcd, unsigned port)
285{
286    u32 t;
287
288    admhc_vdbg(ahcd, "disable port%d\n", port);
289    t = admhc_read_portstatus(ahcd, port);
290    if (!(t & ADMHC_PS_CCS))
291        return -ENODEV;
292
293    admhc_write_portstatus(ahcd, port, ADMHC_PS_CPE);
294
295    return 0;
296}
297
298static inline int admhc_port_write(struct admhcd *ahcd, unsigned port,
299        u32 val)
300{
301#ifdef ADMHC_VERBOSE_DEBUG
302    dbg_port_write(ahcd, "write", port, val);
303#endif
304    admhc_write_portstatus(ahcd, port, val);
305
306    return 0;
307}
308
309static int admhc_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
310        u16 wIndex, char *buf, u16 wLength)
311{
312    struct admhcd *ahcd = hcd_to_admhcd(hcd);
313    int ports = hcd_to_bus(hcd)->root_hub->maxchild;
314    int ret = 0;
315
316    if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
317        return -ESHUTDOWN;
318
319    switch (typeReq) {
320    case ClearHubFeature:
321        switch (wValue) {
322        case C_HUB_OVER_CURRENT:
323#if 0 /* FIXME */
324            admhc_writel(ahcd, ADMHC_RH_OCIC,
325                    &ahcd->regs->roothub.status);
326#endif
327        case C_HUB_LOCAL_POWER:
328            break;
329        default:
330            goto error;
331        }
332        break;
333    case ClearPortFeature:
334        if (!wIndex || wIndex > ports)
335            goto error;
336        wIndex--;
337
338        switch (wValue) {
339        case USB_PORT_FEAT_ENABLE:
340            ret = admhc_port_disable(ahcd, wIndex);
341            break;
342        case USB_PORT_FEAT_SUSPEND:
343            ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_CPS);
344            break;
345        case USB_PORT_FEAT_POWER:
346            ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_CPP);
347            break;
348        case USB_PORT_FEAT_C_CONNECTION:
349            ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_CSC);
350            break;
351        case USB_PORT_FEAT_C_ENABLE:
352            ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_PESC);
353            break;
354        case USB_PORT_FEAT_C_SUSPEND:
355            ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_PSSC);
356            break;
357        case USB_PORT_FEAT_C_OVER_CURRENT:
358            ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_OCIC);
359            break;
360        case USB_PORT_FEAT_C_RESET:
361            ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_PRSC);
362            break;
363        default:
364            goto error;
365        }
366        break;
367    case GetHubDescriptor:
368        ret = admhc_get_hub_descriptor(ahcd, buf);
369        break;
370    case GetHubStatus:
371        ret = admhc_get_hub_status(ahcd, buf);
372        break;
373    case GetPortStatus:
374        if (!wIndex || wIndex > ports)
375            goto error;
376        wIndex--;
377
378        ret = admhc_get_port_status(ahcd, wIndex, buf);
379        break;
380    case SetHubFeature:
381        switch (wValue) {
382        case C_HUB_OVER_CURRENT:
383            /* FIXME: this can be cleared, yes? */
384        case C_HUB_LOCAL_POWER:
385            break;
386        default:
387            goto error;
388        }
389        break;
390    case SetPortFeature:
391        if (!wIndex || wIndex > ports)
392            goto error;
393        wIndex--;
394
395        switch (wValue) {
396        case USB_PORT_FEAT_ENABLE:
397            ret = admhc_port_enable(ahcd, wIndex);
398            break;
399        case USB_PORT_FEAT_RESET:
400            ret = admhc_port_reset(ahcd, wIndex);
401            break;
402        case USB_PORT_FEAT_SUSPEND:
403#ifdef CONFIG_USB_OTG
404            if (hcd->self.otg_port == (wIndex + 1)
405                    && hcd->self.b_hnp_enable)
406                start_hnp(ahcd);
407            else
408#endif
409            ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_SPS);
410            break;
411        case USB_PORT_FEAT_POWER:
412            ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_SPP);
413            break;
414        default:
415            goto error;
416        }
417        break;
418
419    default:
420error:
421        /* "protocol stall" on error */
422        ret = -EPIPE;
423    }
424
425    return ret;
426}
427
428

Archive Download this file



interactive