in host/fotg210-hcd.c [1460:1783]
static int fotg210_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 wIndex, char *buf, u16 wLength)
{
struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd);
int ports = HCS_N_PORTS(fotg210->hcs_params);
u32 __iomem *status_reg = &fotg210->regs->port_status;
u32 temp, temp1, status;
unsigned long flags;
int retval = 0;
unsigned selector;
/*
* FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR.
* HCS_INDICATOR may say we can change LEDs to off/amber/green.
* (track current state ourselves) ... blink for diagnostics,
* power, "this is the one", etc. EHCI spec supports this.
*/
spin_lock_irqsave(&fotg210->lock, flags);
switch (typeReq) {
case ClearHubFeature:
switch (wValue) {
case C_HUB_LOCAL_POWER:
case C_HUB_OVER_CURRENT:
/* no hub-wide feature/status flags */
break;
default:
goto error;
}
break;
case ClearPortFeature:
if (!wIndex || wIndex > ports)
goto error;
wIndex--;
temp = fotg210_readl(fotg210, status_reg);
temp &= ~PORT_RWC_BITS;
/*
* Even if OWNER is set, so the port is owned by the
* companion controller, hub_wq needs to be able to clear
* the port-change status bits (especially
* USB_PORT_STAT_C_CONNECTION).
*/
switch (wValue) {
case USB_PORT_FEAT_ENABLE:
fotg210_writel(fotg210, temp & ~PORT_PE, status_reg);
break;
case USB_PORT_FEAT_C_ENABLE:
fotg210_writel(fotg210, temp | PORT_PEC, status_reg);
break;
case USB_PORT_FEAT_SUSPEND:
if (temp & PORT_RESET)
goto error;
if (!(temp & PORT_SUSPEND))
break;
if ((temp & PORT_PE) == 0)
goto error;
/* resume signaling for 20 msec */
fotg210_writel(fotg210, temp | PORT_RESUME, status_reg);
fotg210->reset_done[wIndex] = jiffies
+ msecs_to_jiffies(USB_RESUME_TIMEOUT);
break;
case USB_PORT_FEAT_C_SUSPEND:
clear_bit(wIndex, &fotg210->port_c_suspend);
break;
case USB_PORT_FEAT_C_CONNECTION:
fotg210_writel(fotg210, temp | PORT_CSC, status_reg);
break;
case USB_PORT_FEAT_C_OVER_CURRENT:
fotg210_writel(fotg210, temp | OTGISR_OVC,
&fotg210->regs->otgisr);
break;
case USB_PORT_FEAT_C_RESET:
/* GetPortStatus clears reset */
break;
default:
goto error;
}
fotg210_readl(fotg210, &fotg210->regs->command);
break;
case GetHubDescriptor:
fotg210_hub_descriptor(fotg210, (struct usb_hub_descriptor *)
buf);
break;
case GetHubStatus:
/* no hub-wide feature/status flags */
memset(buf, 0, 4);
/*cpu_to_le32s ((u32 *) buf); */
break;
case GetPortStatus:
if (!wIndex || wIndex > ports)
goto error;
wIndex--;
status = 0;
temp = fotg210_readl(fotg210, status_reg);
/* wPortChange bits */
if (temp & PORT_CSC)
status |= USB_PORT_STAT_C_CONNECTION << 16;
if (temp & PORT_PEC)
status |= USB_PORT_STAT_C_ENABLE << 16;
temp1 = fotg210_readl(fotg210, &fotg210->regs->otgisr);
if (temp1 & OTGISR_OVC)
status |= USB_PORT_STAT_C_OVERCURRENT << 16;
/* whoever resumes must GetPortStatus to complete it!! */
if (temp & PORT_RESUME) {
/* Remote Wakeup received? */
if (!fotg210->reset_done[wIndex]) {
/* resume signaling for 20 msec */
fotg210->reset_done[wIndex] = jiffies
+ msecs_to_jiffies(20);
/* check the port again */
mod_timer(&fotg210_to_hcd(fotg210)->rh_timer,
fotg210->reset_done[wIndex]);
}
/* resume completed? */
else if (time_after_eq(jiffies,
fotg210->reset_done[wIndex])) {
clear_bit(wIndex, &fotg210->suspended_ports);
set_bit(wIndex, &fotg210->port_c_suspend);
fotg210->reset_done[wIndex] = 0;
/* stop resume signaling */
temp = fotg210_readl(fotg210, status_reg);
fotg210_writel(fotg210, temp &
~(PORT_RWC_BITS | PORT_RESUME),
status_reg);
clear_bit(wIndex, &fotg210->resuming_ports);
retval = handshake(fotg210, status_reg,
PORT_RESUME, 0, 2000);/* 2ms */
if (retval != 0) {
fotg210_err(fotg210,
"port %d resume error %d\n",
wIndex + 1, retval);
goto error;
}
temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
}
}
/* whoever resets must GetPortStatus to complete it!! */
if ((temp & PORT_RESET) && time_after_eq(jiffies,
fotg210->reset_done[wIndex])) {
status |= USB_PORT_STAT_C_RESET << 16;
fotg210->reset_done[wIndex] = 0;
clear_bit(wIndex, &fotg210->resuming_ports);
/* force reset to complete */
fotg210_writel(fotg210,
temp & ~(PORT_RWC_BITS | PORT_RESET),
status_reg);
/* REVISIT: some hardware needs 550+ usec to clear
* this bit; seems too long to spin routinely...
*/
retval = handshake(fotg210, status_reg,
PORT_RESET, 0, 1000);
if (retval != 0) {
fotg210_err(fotg210, "port %d reset error %d\n",
wIndex + 1, retval);
goto error;
}
/* see what we found out */
temp = check_reset_complete(fotg210, wIndex, status_reg,
fotg210_readl(fotg210, status_reg));
/* restart schedule */
fotg210->command |= CMD_RUN;
fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command);
}
if (!(temp & (PORT_RESUME|PORT_RESET))) {
fotg210->reset_done[wIndex] = 0;
clear_bit(wIndex, &fotg210->resuming_ports);
}
/* transfer dedicated ports to the companion hc */
if ((temp & PORT_CONNECT) &&
test_bit(wIndex, &fotg210->companion_ports)) {
temp &= ~PORT_RWC_BITS;
fotg210_writel(fotg210, temp, status_reg);
fotg210_dbg(fotg210, "port %d --> companion\n",
wIndex + 1);
temp = fotg210_readl(fotg210, status_reg);
}
/*
* Even if OWNER is set, there's no harm letting hub_wq
* see the wPortStatus values (they should all be 0 except
* for PORT_POWER anyway).
*/
if (temp & PORT_CONNECT) {
status |= USB_PORT_STAT_CONNECTION;
status |= fotg210_port_speed(fotg210, temp);
}
if (temp & PORT_PE)
status |= USB_PORT_STAT_ENABLE;
/* maybe the port was unsuspended without our knowledge */
if (temp & (PORT_SUSPEND|PORT_RESUME)) {
status |= USB_PORT_STAT_SUSPEND;
} else if (test_bit(wIndex, &fotg210->suspended_ports)) {
clear_bit(wIndex, &fotg210->suspended_ports);
clear_bit(wIndex, &fotg210->resuming_ports);
fotg210->reset_done[wIndex] = 0;
if (temp & PORT_PE)
set_bit(wIndex, &fotg210->port_c_suspend);
}
temp1 = fotg210_readl(fotg210, &fotg210->regs->otgisr);
if (temp1 & OTGISR_OVC)
status |= USB_PORT_STAT_OVERCURRENT;
if (temp & PORT_RESET)
status |= USB_PORT_STAT_RESET;
if (test_bit(wIndex, &fotg210->port_c_suspend))
status |= USB_PORT_STAT_C_SUSPEND << 16;
if (status & ~0xffff) /* only if wPortChange is interesting */
dbg_port(fotg210, "GetStatus", wIndex + 1, temp);
put_unaligned_le32(status, buf);
break;
case SetHubFeature:
switch (wValue) {
case C_HUB_LOCAL_POWER:
case C_HUB_OVER_CURRENT:
/* no hub-wide feature/status flags */
break;
default:
goto error;
}
break;
case SetPortFeature:
selector = wIndex >> 8;
wIndex &= 0xff;
if (!wIndex || wIndex > ports)
goto error;
wIndex--;
temp = fotg210_readl(fotg210, status_reg);
temp &= ~PORT_RWC_BITS;
switch (wValue) {
case USB_PORT_FEAT_SUSPEND:
if ((temp & PORT_PE) == 0
|| (temp & PORT_RESET) != 0)
goto error;
/* After above check the port must be connected.
* Set appropriate bit thus could put phy into low power
* mode if we have hostpc feature
*/
fotg210_writel(fotg210, temp | PORT_SUSPEND,
status_reg);
set_bit(wIndex, &fotg210->suspended_ports);
break;
case USB_PORT_FEAT_RESET:
if (temp & PORT_RESUME)
goto error;
/* line status bits may report this as low speed,
* which can be fine if this root hub has a
* transaction translator built in.
*/
fotg210_dbg(fotg210, "port %d reset\n", wIndex + 1);
temp |= PORT_RESET;
temp &= ~PORT_PE;
/*
* caller must wait, then call GetPortStatus
* usb 2.0 spec says 50 ms resets on root
*/
fotg210->reset_done[wIndex] = jiffies
+ msecs_to_jiffies(50);
fotg210_writel(fotg210, temp, status_reg);
break;
/* For downstream facing ports (these): one hub port is put
* into test mode according to USB2 11.24.2.13, then the hub
* must be reset (which for root hub now means rmmod+modprobe,
* or else system reboot). See EHCI 2.3.9 and 4.14 for info
* about the EHCI-specific stuff.
*/
case USB_PORT_FEAT_TEST:
if (!selector || selector > 5)
goto error;
spin_unlock_irqrestore(&fotg210->lock, flags);
fotg210_quiesce(fotg210);
spin_lock_irqsave(&fotg210->lock, flags);
/* Put all enabled ports into suspend */
temp = fotg210_readl(fotg210, status_reg) &
~PORT_RWC_BITS;
if (temp & PORT_PE)
fotg210_writel(fotg210, temp | PORT_SUSPEND,
status_reg);
spin_unlock_irqrestore(&fotg210->lock, flags);
fotg210_halt(fotg210);
spin_lock_irqsave(&fotg210->lock, flags);
temp = fotg210_readl(fotg210, status_reg);
temp |= selector << 16;
fotg210_writel(fotg210, temp, status_reg);
break;
default:
goto error;
}
fotg210_readl(fotg210, &fotg210->regs->command);
break;
default:
error:
/* "stall" on error */
retval = -EPIPE;
}
spin_unlock_irqrestore(&fotg210->lock, flags);
return retval;
}