| 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 | |
| 68 | static int |
| 69 | admhc_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 | |
| 112 | done: |
| 113 | spin_unlock_irqrestore(&ahcd->lock, flags); |
| 114 | |
| 115 | return changed ? length : 0; |
| 116 | } |
| 117 | |
| 118 | /*-------------------------------------------------------------------------*/ |
| 119 | |
| 120 | static 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 | |
| 153 | static 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 | |
| 173 | static 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 | |
| 205 | static 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 | |
| 224 | static 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 */ |
| 253 | static 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 | |
| 270 | static 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 | |
| 284 | static 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 | |
| 298 | static 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 | |
| 309 | static 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: |
| 420 | error: |
| 421 | /* "protocol stall" on error */ |
| 422 | ret = -EPIPE; |
| 423 | } |
| 424 | |
| 425 | return ret; |
| 426 | } |
| 427 | |
| 428 | |