static int fotg210_hub_control()

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;
}