static int isp1760_hub_control()

in isp1760/isp1760-hcd.c [2196:2449]


static int isp1760_hub_control(struct usb_hcd *hcd, u16 typeReq,
		u16 wValue, u16 wIndex, char *buf, u16 wLength)
{
	struct isp1760_hcd *priv = hcd_to_priv(hcd);
	u32 status;
	unsigned long flags;
	int retval = 0;
	int ports;

	ports = isp1760_hcd_n_ports(hcd);

	/*
	 * 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(&priv->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--;

		/*
		 * 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:
			isp1760_hcd_clear(hcd, PORT_PE);
			break;
		case USB_PORT_FEAT_C_ENABLE:
			/* XXX error? */
			break;
		case USB_PORT_FEAT_SUSPEND:
			if (isp1760_hcd_is_set(hcd, PORT_RESET))
				goto error;

			if (isp1760_hcd_is_set(hcd, PORT_SUSPEND)) {
				if (!isp1760_hcd_is_set(hcd, PORT_PE))
					goto error;
				/* resume signaling for 20 msec */
				isp1760_hcd_clear(hcd, PORT_CSC);
				isp1760_hcd_set(hcd, PORT_RESUME);

				priv->reset_done = jiffies +
					msecs_to_jiffies(USB_RESUME_TIMEOUT);
			}
			break;
		case USB_PORT_FEAT_C_SUSPEND:
			/* we auto-clear this feature */
			break;
		case USB_PORT_FEAT_POWER:
			if (isp1760_hcd_ppc_is_set(hcd))
				isp1760_hcd_clear(hcd, PORT_POWER);
			break;
		case USB_PORT_FEAT_C_CONNECTION:
			isp1760_hcd_set(hcd, PORT_CSC);
			break;
		case USB_PORT_FEAT_C_OVER_CURRENT:
			/* XXX error ?*/
			break;
		case USB_PORT_FEAT_C_RESET:
			/* GetPortStatus clears reset */
			break;
		default:
			goto error;
		}
		isp1760_hcd_read(hcd, CMD_RUN);
		break;
	case GetHubDescriptor:
		isp1760_hub_descriptor(priv, (struct usb_hub_descriptor *)
			buf);
		break;
	case GetHubStatus:
		/* no hub-wide feature/status flags */
		memset(buf, 0, 4);
		break;
	case GetPortStatus:
		if (!wIndex || wIndex > ports)
			goto error;
		wIndex--;
		status = 0;

		/* wPortChange bits */
		if (isp1760_hcd_is_set(hcd, PORT_CSC))
			status |= USB_PORT_STAT_C_CONNECTION << 16;

		/* whoever resumes must GetPortStatus to complete it!! */
		if (isp1760_hcd_is_set(hcd, PORT_RESUME)) {
			dev_err(hcd->self.controller, "Port resume should be skipped.\n");

			/* Remote Wakeup received? */
			if (!priv->reset_done) {
				/* resume signaling for 20 msec */
				priv->reset_done = jiffies
						+ msecs_to_jiffies(20);
				/* check the port again */
				mod_timer(&hcd->rh_timer, priv->reset_done);
			}

			/* resume completed? */
			else if (time_after_eq(jiffies,
					priv->reset_done)) {
				status |= USB_PORT_STAT_C_SUSPEND << 16;
				priv->reset_done = 0;

				/* stop resume signaling */
				isp1760_hcd_clear(hcd, PORT_CSC);

				retval = isp1760_hcd_clear_and_wait(hcd,
							  PORT_RESUME, 2000);
				if (retval != 0) {
					dev_err(hcd->self.controller,
						"port %d resume error %d\n",
						wIndex + 1, retval);
					goto error;
				}
			}
		}

		/* whoever resets must GetPortStatus to complete it!! */
		if (isp1760_hcd_is_set(hcd, PORT_RESET) &&
		    time_after_eq(jiffies, priv->reset_done)) {
			status |= USB_PORT_STAT_C_RESET << 16;
			priv->reset_done = 0;

			/* force reset to complete */
			/* REVISIT:  some hardware needs 550+ usec to clear
			 * this bit; seems too long to spin routinely...
			 */
			retval = isp1760_hcd_clear_and_wait(hcd, PORT_RESET,
							    750);
			if (retval != 0) {
				dev_err(hcd->self.controller, "port %d reset error %d\n",
					wIndex + 1, retval);
				goto error;
			}

			/* see what we found out */
			check_reset_complete(hcd, wIndex);
		}
		/*
		 * 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 (isp1760_hcd_is_set(hcd, PORT_OWNER))
			dev_err(hcd->self.controller, "PORT_OWNER is set\n");

		if (isp1760_hcd_is_set(hcd, PORT_CONNECT)) {
			status |= USB_PORT_STAT_CONNECTION;
			/* status may be from integrated TT */
			status |= USB_PORT_STAT_HIGH_SPEED;
		}
		if (isp1760_hcd_is_set(hcd, PORT_PE))
			status |= USB_PORT_STAT_ENABLE;
		if (isp1760_hcd_is_set(hcd, PORT_SUSPEND) &&
		    isp1760_hcd_is_set(hcd, PORT_RESUME))
			status |= USB_PORT_STAT_SUSPEND;
		if (isp1760_hcd_is_set(hcd, PORT_RESET))
			status |= USB_PORT_STAT_RESET;
		if (isp1760_hcd_is_set(hcd, PORT_POWER))
			status |= USB_PORT_STAT_POWER;

		put_unaligned(cpu_to_le32(status), (__le32 *) 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:
		wIndex &= 0xff;
		if (!wIndex || wIndex > ports)
			goto error;
		wIndex--;

		if (isp1760_hcd_is_set(hcd, PORT_OWNER))
			break;

		switch (wValue) {
		case USB_PORT_FEAT_ENABLE:
			isp1760_hcd_set(hcd, PORT_PE);
			break;

		case USB_PORT_FEAT_SUSPEND:
			if (!isp1760_hcd_is_set(hcd, PORT_PE) ||
			    isp1760_hcd_is_set(hcd, PORT_RESET))
				goto error;

			isp1760_hcd_set(hcd, PORT_SUSPEND);
			break;
		case USB_PORT_FEAT_POWER:
			if (isp1760_hcd_ppc_is_set(hcd))
				isp1760_hcd_set(hcd, PORT_POWER);
			break;
		case USB_PORT_FEAT_RESET:
			if (isp1760_hcd_is_set(hcd, 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 ((isp1760_hcd_is_set(hcd, PORT_CONNECT) &&
			     !isp1760_hcd_is_set(hcd, PORT_PE)) &&
			    (isp1760_hcd_read(hcd, PORT_LSTATUS) == 1)) {
				isp1760_hcd_set(hcd, PORT_OWNER);
			} else {
				isp1760_hcd_set(hcd, PORT_RESET);
				isp1760_hcd_clear(hcd, PORT_PE);

				/*
				 * caller must wait, then call GetPortStatus
				 * usb 2.0 spec says 50 ms resets on root
				 */
				priv->reset_done = jiffies +
					msecs_to_jiffies(50);
			}
			break;
		default:
			goto error;
		}
		break;

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