int ehci_hub_control()

in host/ehci-hub.c [731:1176]


int ehci_hub_control(
	struct usb_hcd	*hcd,
	u16		typeReq,
	u16		wValue,
	u16		wIndex,
	char		*buf,
	u16		wLength
) {
	struct ehci_hcd	*ehci = hcd_to_ehci (hcd);
	int		ports = HCS_N_PORTS (ehci->hcs_params);
	u32 __iomem	*status_reg, *hostpc_reg;
	u32		temp, temp1, status;
	unsigned long	flags;
	int		retval = 0;
	unsigned	selector;

	/*
	 * Avoid out-of-bounds values while calculating the port index
	 * from wIndex.  The compiler doesn't like pointers to invalid
	 * addresses, even if they are never used.
	 */
	temp = (wIndex - 1) & 0xff;
	if (temp >= HCS_N_PORTS_MAX)
		temp = 0;
	status_reg = &ehci->regs->port_status[temp];
	hostpc_reg = &ehci->regs->hostpc[temp];

	/*
	 * 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 (&ehci->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 = ehci_readl(ehci, 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:
			ehci_writel(ehci, temp & ~PORT_PE, status_reg);
			break;
		case USB_PORT_FEAT_C_ENABLE:
			ehci_writel(ehci, temp | PORT_PEC, status_reg);
			break;
		case USB_PORT_FEAT_SUSPEND:
			if (temp & PORT_RESET)
				goto error;
			if (ehci->no_selective_suspend)
				break;
#ifdef CONFIG_USB_OTG
			if ((hcd->self.otg_port == (wIndex + 1))
			    && hcd->self.b_hnp_enable) {
				otg_start_hnp(hcd->usb_phy->otg);
				break;
			}
#endif
			if (!(temp & PORT_SUSPEND))
				break;
			if ((temp & PORT_PE) == 0)
				goto error;

			/* clear phy low-power mode before resume */
			if (ehci->has_tdi_phy_lpm) {
				temp1 = ehci_readl(ehci, hostpc_reg);
				ehci_writel(ehci, temp1 & ~HOSTPC_PHCD,
						hostpc_reg);
				spin_unlock_irqrestore(&ehci->lock, flags);
				msleep(5);/* wait to leave low-power mode */
				spin_lock_irqsave(&ehci->lock, flags);
			}
			/* resume signaling for 20 msec */
			temp &= ~PORT_WAKE_BITS;
			ehci_writel(ehci, temp | PORT_RESUME, status_reg);
			ehci->reset_done[wIndex] = jiffies
					+ msecs_to_jiffies(USB_RESUME_TIMEOUT);
			set_bit(wIndex, &ehci->resuming_ports);
			usb_hcd_start_port_resume(&hcd->self, wIndex);
			break;
		case USB_PORT_FEAT_C_SUSPEND:
			clear_bit(wIndex, &ehci->port_c_suspend);
			break;
		case USB_PORT_FEAT_POWER:
			if (HCS_PPC(ehci->hcs_params)) {
				spin_unlock_irqrestore(&ehci->lock, flags);
				ehci_port_power(ehci, wIndex, false);
				spin_lock_irqsave(&ehci->lock, flags);
			}
			break;
		case USB_PORT_FEAT_C_CONNECTION:
			ehci_writel(ehci, temp | PORT_CSC, status_reg);
			break;
		case USB_PORT_FEAT_C_OVER_CURRENT:
			ehci_writel(ehci, temp | PORT_OCC, status_reg);
			break;
		case USB_PORT_FEAT_C_RESET:
			/* GetPortStatus clears reset */
			break;
		default:
			goto error;
		}
		ehci_readl(ehci, &ehci->regs->command);	/* unblock posted write */
		break;
	case GetHubDescriptor:
		ehci_hub_descriptor (ehci, (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 = ehci_readl(ehci, 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;

		if ((temp & PORT_OCC) && (!ignore_oc && !ehci->spurious_oc)){
			status |= USB_PORT_STAT_C_OVERCURRENT << 16;

			/*
			 * Hubs should disable port power on over-current.
			 * However, not all EHCI implementations do this
			 * automatically, even if they _do_ support per-port
			 * power switching; they're allowed to just limit the
			 * current.  hub_wq will turn the power back on.
			 */
			if (((temp & PORT_OC) || (ehci->need_oc_pp_cycle))
					&& HCS_PPC(ehci->hcs_params)) {
				spin_unlock_irqrestore(&ehci->lock, flags);
				ehci_port_power(ehci, wIndex, false);
				spin_lock_irqsave(&ehci->lock, flags);
				temp = ehci_readl(ehci, status_reg);
			}
		}

		/* no reset or resume pending */
		if (!ehci->reset_done[wIndex]) {

			/* Remote Wakeup received? */
			if (temp & PORT_RESUME) {
				/* resume signaling for 20 msec */
				ehci->reset_done[wIndex] = jiffies
						+ msecs_to_jiffies(20);
				usb_hcd_start_port_resume(&hcd->self, wIndex);
				set_bit(wIndex, &ehci->resuming_ports);
				/* check the port again */
				mod_timer(&ehci_to_hcd(ehci)->rh_timer,
						ehci->reset_done[wIndex]);
			}

		/* reset or resume not yet complete */
		} else if (!time_after_eq(jiffies, ehci->reset_done[wIndex])) {
			;	/* wait until it is complete */

		/* resume completed */
		} else if (test_bit(wIndex, &ehci->resuming_ports)) {
			clear_bit(wIndex, &ehci->suspended_ports);
			set_bit(wIndex, &ehci->port_c_suspend);
			ehci->reset_done[wIndex] = 0;
			usb_hcd_end_port_resume(&hcd->self, wIndex);

			/* stop resume signaling */
			temp &= ~(PORT_RWC_BITS | PORT_SUSPEND | PORT_RESUME);
			ehci_writel(ehci, temp, status_reg);
			clear_bit(wIndex, &ehci->resuming_ports);
			retval = ehci_handshake(ehci, status_reg,
					PORT_RESUME, 0, 2000 /* 2msec */);
			if (retval != 0) {
				ehci_err(ehci, "port %d resume error %d\n",
						wIndex + 1, retval);
				goto error;
			}
			temp = ehci_readl(ehci, status_reg);

		/* whoever resets must GetPortStatus to complete it!! */
		} else {
			status |= USB_PORT_STAT_C_RESET << 16;
			ehci->reset_done [wIndex] = 0;

			/* force reset to complete */
			ehci_writel(ehci, 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 = ehci_handshake(ehci, status_reg,
					PORT_RESET, 0, 1000);
			if (retval != 0) {
				ehci_err (ehci, "port %d reset error %d\n",
					wIndex + 1, retval);
				goto error;
			}

			/* see what we found out */
			temp = check_reset_complete (ehci, wIndex, status_reg,
					ehci_readl(ehci, status_reg));
		}

		/* transfer dedicated ports to the companion hc */
		if ((temp & PORT_CONNECT) &&
				test_bit(wIndex, &ehci->companion_ports)) {
			temp &= ~PORT_RWC_BITS;
			temp |= PORT_OWNER;
			ehci_writel(ehci, temp, status_reg);
			ehci_dbg(ehci, "port %d --> companion\n", wIndex + 1);
			temp = ehci_readl(ehci, 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 may be from integrated TT
			if (ehci->has_hostpc) {
				temp1 = ehci_readl(ehci, hostpc_reg);
				status |= ehci_port_speed(ehci, temp1);
			} else
				status |= ehci_port_speed(ehci, 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, &ehci->suspended_ports)) {
			clear_bit(wIndex, &ehci->suspended_ports);
			clear_bit(wIndex, &ehci->resuming_ports);
			ehci->reset_done[wIndex] = 0;
			if (temp & PORT_PE)
				set_bit(wIndex, &ehci->port_c_suspend);
			usb_hcd_end_port_resume(&hcd->self, wIndex);
		}

		if (temp & PORT_OC)
			status |= USB_PORT_STAT_OVERCURRENT;
		if (temp & PORT_RESET)
			status |= USB_PORT_STAT_RESET;
		if (temp & PORT_POWER)
			status |= USB_PORT_STAT_POWER;
		if (test_bit(wIndex, &ehci->port_c_suspend))
			status |= USB_PORT_STAT_C_SUSPEND << 16;

		if (status & ~0xffff)	/* only if wPortChange is interesting */
			dbg_port(ehci, "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 (unlikely(ehci->debug)) {
			/* If the debug port is active any port
			 * feature requests should get denied */
			if (wIndex == HCS_DEBUG_PORT(ehci->hcs_params) &&
			    (readl(&ehci->debug->control) & DBGP_ENABLED)) {
				retval = -ENODEV;
				goto error_exit;
			}
		}
		if (!wIndex || wIndex > ports)
			goto error;
		wIndex--;
		temp = ehci_readl(ehci, status_reg);
		if (temp & PORT_OWNER)
			break;

		temp &= ~PORT_RWC_BITS;
		switch (wValue) {
		case USB_PORT_FEAT_SUSPEND:
			if (ehci->no_selective_suspend)
				break;
			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 tdi_phy_lpm feature
			 */
			temp &= ~PORT_WKCONN_E;
			temp |= PORT_WKDISC_E | PORT_WKOC_E;
			ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
			if (ehci->has_tdi_phy_lpm) {
				spin_unlock_irqrestore(&ehci->lock, flags);
				msleep(5);/* 5ms for HCD enter low pwr mode */
				spin_lock_irqsave(&ehci->lock, flags);
				temp1 = ehci_readl(ehci, hostpc_reg);
				ehci_writel(ehci, temp1 | HOSTPC_PHCD,
					hostpc_reg);
				temp1 = ehci_readl(ehci, hostpc_reg);
				ehci_dbg(ehci, "Port%d phy low pwr mode %s\n",
					wIndex, (temp1 & HOSTPC_PHCD) ?
					"succeeded" : "failed");
			}
			if (ehci_has_fsl_susp_errata(ehci)) {
				/* 10ms for HCD enter suspend */
				spin_unlock_irqrestore(&ehci->lock, flags);
				usleep_range(10000, 20000);
				spin_lock_irqsave(&ehci->lock, flags);
			}
			set_bit(wIndex, &ehci->suspended_ports);
			break;
		case USB_PORT_FEAT_POWER:
			if (HCS_PPC(ehci->hcs_params)) {
				spin_unlock_irqrestore(&ehci->lock, flags);
				ehci_port_power(ehci, wIndex, true);
				spin_lock_irqsave(&ehci->lock, flags);
			}
			break;
		case USB_PORT_FEAT_RESET:
			if (temp & (PORT_SUSPEND|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.
			 */
			if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
					&& !ehci_is_TDI(ehci)
					&& PORT_USB11 (temp)) {
				ehci_dbg (ehci,
					"port %d low speed --> companion\n",
					wIndex + 1);
				temp |= PORT_OWNER;
			} else {
				temp |= PORT_RESET;
				temp &= ~PORT_PE;

				/*
				 * caller must wait, then call GetPortStatus
				 * usb 2.0 spec says 50 ms resets on root
				 */
				ehci->reset_done [wIndex] = jiffies
						+ msecs_to_jiffies (50);

				/*
				 * Force full-speed connect for FSL high-speed
				 * erratum; disable HS Chirp by setting PFSC bit
				 */
				if (ehci_has_fsl_hs_errata(ehci))
					temp |= (1 << PORTSC_FSL_PFSC);
			}
			ehci_writel(ehci, 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:
#ifdef CONFIG_USB_HCD_TEST_MODE
			if (selector == EHSET_TEST_SINGLE_STEP_SET_FEATURE) {
				spin_unlock_irqrestore(&ehci->lock, flags);
				retval = ehset_single_step_set_feature(hcd,
								wIndex + 1);
				spin_lock_irqsave(&ehci->lock, flags);
				break;
			}
#endif
			if (!selector || selector > 5)
				goto error;
			spin_unlock_irqrestore(&ehci->lock, flags);
			ehci_quiesce(ehci);
			spin_lock_irqsave(&ehci->lock, flags);

			/* Put all enabled ports into suspend */
			while (ports--) {
				u32 __iomem *sreg =
						&ehci->regs->port_status[ports];

				temp = ehci_readl(ehci, sreg) & ~PORT_RWC_BITS;
				if (temp & PORT_PE)
					ehci_writel(ehci, temp | PORT_SUSPEND,
							sreg);
			}

			spin_unlock_irqrestore(&ehci->lock, flags);
			ehci_halt(ehci);
			spin_lock_irqsave(&ehci->lock, flags);

			temp = ehci_readl(ehci, status_reg);
			temp |= selector << 16;
			ehci_writel(ehci, temp, status_reg);
			break;

		default:
			goto error;
		}
		ehci_readl(ehci, &ehci->regs->command);	/* unblock posted writes */
		break;

	default:
error:
		/* "stall" on error */
		retval = -EPIPE;
	}
error_exit:
	spin_unlock_irqrestore (&ehci->lock, flags);
	return retval;
}