Ejemplo n.º 1
0
/**
 * dwc3_otg_start_host -  helper function for starting/stoping the host controller driver.
 *
 * @otg: Pointer to the otg_transceiver structure.
 * @on: start / stop the host controller driver.
 *
 * Returns 0 on success otherwise negative errno.
 */
static int dwc3_otg_start_host(struct usb_otg *otg, int on)
{
	struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg);
	struct dwc3_ext_xceiv *ext_xceiv = dotg->ext_xceiv;
	struct dwc3 *dwc = dotg->dwc;
	int ret = 0;

	if (!dwc->xhci)
		return -EINVAL;

	if (!dotg->vbus_otg) {
		dotg->vbus_otg = devm_regulator_get(dwc->dev->parent,
							"vbus_dwc3");
		if (IS_ERR(dotg->vbus_otg)) {
			dev_err(dwc->dev, "Failed to get vbus regulator\n");
			ret = PTR_ERR(dotg->vbus_otg);
			dotg->vbus_otg = 0;
			return ret;
		}
	}

	if (on) {
		dev_dbg(otg->phy->dev, "%s: turn on host\n", __func__);

		dwc3_otg_notify_host_mode(otg, on);

		/* register ocp notification */
		if (ext_xceiv && ext_xceiv->otg_capability &&
				ext_xceiv->ext_ocp_notification.notify) {
			ret = regulator_register_ocp_notification(
					dotg->vbus_otg,
					&ext_xceiv->ext_ocp_notification);
			if (ret)
				dev_err(otg->phy->dev,
					"unable to register ocp\n");
		}

		ret = regulator_enable(dotg->vbus_otg);
		if (ret) {
			dev_err(otg->phy->dev, "unable to enable vbus_otg\n");
			dwc3_otg_notify_host_mode(otg, 0);
			return ret;
		}

		/* The delay between enabling regulator and adding the
		   platform device is needed to succeed in the enumeration
		   for certain devices. */
		usleep(10000);

		/*
		 * This should be revisited for more testing post-silicon.
		 * In worst case we may need to disconnect the root hub
		 * before stopping the controller so that it does not
		 * interfere with runtime pm/system pm.
		 * We can also consider registering and unregistering xhci
		 * platform device. It is almost similar to add_hcd and
		 * remove_hcd, But we may not use standard set_host method
		 * anymore.
		 */
		dwc3_otg_set_hsphy_auto_suspend(dotg, true);
		dwc3_otg_set_host_regs(dotg);
		/*
		 * FIXME If micro A cable is disconnected during system suspend,
		 * xhci platform device will be removed before runtime pm is
		 * enabled for xhci device. Due to this, disable_depth becomes
		 * greater than one and runtimepm is not enabled for next microA
		 * connect. Fix this by calling pm_runtime_init for xhci device.
		 */
		pm_runtime_init(&dwc->xhci->dev);
		ret = platform_device_add(dwc->xhci);
		if (ret) {
			dev_err(otg->phy->dev,
				"%s: failed to add XHCI pdev ret=%d\n",
				__func__, ret);
			regulator_disable(dotg->vbus_otg);
			dwc3_otg_notify_host_mode(otg, 0);
			return ret;
		}

		/* re-init OTG EVTEN register as XHCI reset clears it */
		if (ext_xceiv && !ext_xceiv->otg_capability)
			dwc3_otg_reset(dotg);
	} else {
		dev_dbg(otg->phy->dev, "%s: turn off host\n", __func__);

		ret = regulator_disable(dotg->vbus_otg);
		if (ret) {
			dev_err(otg->phy->dev, "unable to disable vbus_otg\n");
			return ret;
		}

		/* unregister ocp notification */
		if (ext_xceiv && ext_xceiv->otg_capability &&
				ext_xceiv->ext_ocp_notification.notify) {
			ret = regulator_register_ocp_notification(
					dotg->vbus_otg, NULL);
			if (ret)
				dev_err(otg->phy->dev,
					"unable to unregister ocp\n");
		}

		dwc3_otg_notify_host_mode(otg, on);

		platform_device_del(dwc->xhci);
		/*
		 * Perform USB hardware RESET (both core reset and DBM reset)
		 * when moving from host to peripheral. This is required for
		 * peripheral mode to work.
		 */
		if (ext_xceiv && ext_xceiv->otg_capability &&
						ext_xceiv->ext_block_reset)
			ext_xceiv->ext_block_reset(ext_xceiv, true);

		dwc3_otg_set_hsphy_auto_suspend(dotg, false);
		dwc3_otg_set_peripheral_regs(dotg);

		/* re-init core and OTG registers as block reset clears these */
		dwc3_post_host_reset_core_init(dwc);
		if (ext_xceiv && !ext_xceiv->otg_capability)
			dwc3_otg_reset(dotg);
	}

	return 0;
}
Ejemplo n.º 2
0
/**
 * dwc3_otg_start_host -  helper function for starting/stoping the host controller driver.
 *
 * @otg: Pointer to the otg_transceiver structure.
 * @on: start / stop the host controller driver.
 *
 * Returns 0 on success otherwise negative errno.
 */
static int dwc3_otg_start_host(struct usb_otg *otg, int on)
{
	struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg);
	struct dwc3_ext_xceiv *ext_xceiv = dotg->ext_xceiv;
	struct dwc3 *dwc = dotg->dwc;
	struct usb_hcd *hcd;
	int ret = 0;

	if (!dwc->xhci)
		return -EINVAL;

	if (!dotg->vbus_otg) {
		dotg->vbus_otg = devm_regulator_get(dwc->dev->parent,
							"vbus_dwc3");
		if (IS_ERR(dotg->vbus_otg)) {
			dev_err(dwc->dev, "Failed to get vbus regulator\n");
			ret = PTR_ERR(dotg->vbus_otg);
			dotg->vbus_otg = 0;
			return ret;
		}
	}

	if (on) {
		dev_dbg(otg->phy->dev, "%s: turn on host\n", __func__);

		dwc3_otg_notify_host_mode(otg, on);
		usb_phy_notify_connect(dotg->dwc->usb2_phy, USB_SPEED_HIGH);

		/* register ocp notification */
		if (ext_xceiv && ext_xceiv->ext_ocp_notification.notify) {
			ret = regulator_register_ocp_notification(
					dotg->vbus_otg,
					&ext_xceiv->ext_ocp_notification);
			if (ret)
				dev_err(otg->phy->dev,
					"unable to register ocp\n");
		}

		ret = regulator_enable(dotg->vbus_otg);
		if (ret) {
			dev_err(otg->phy->dev, "unable to enable vbus_otg\n");
			dwc3_otg_notify_host_mode(otg, 0);
			return ret;
		}

		/* The delay between enabling regulator and adding the
		   platform device is needed to succeed in the enumeration
		   for certain devices. */
		usleep(10000);

		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);

		/*
		 * FIXME If micro A cable is disconnected during system suspend,
		 * xhci platform device will be removed before runtime pm is
		 * enabled for xhci device. Due to this, disable_depth becomes
		 * greater than one and runtimepm is not enabled for next microA
		 * connect. Fix this by calling pm_runtime_init for xhci device.
		 */
		pm_runtime_init(&dwc->xhci->dev);
		ret = platform_device_add(dwc->xhci);
		if (ret) {
			dev_err(otg->phy->dev,
				"%s: failed to add XHCI pdev ret=%d\n",
				__func__, ret);
			regulator_disable(dotg->vbus_otg);
			dwc3_otg_notify_host_mode(otg, 0);
			return ret;
		}

		/*
		 * WORKAROUND: avoids entering a suspend state, because PMIC
		 * is damaged by known issue when attempt to suspend.
		 */
		wake_lock(&dotg->host_wakelock);

		/*
		 * WORKAROUND: currently host mode suspend isn't working well.
		 * Disable xHCI's runtime PM for now.
		 */
		pm_runtime_disable(&dwc->xhci->dev);

		if (no_device_timeout_enable) {
			dotg->no_device_timeout_enabled = 1;
			dotg->device_count = 0;
			dotg->retry_count = 0;
			dotg->usbdev_nb.notifier_call = dwc3_usbdev_notify;
			usb_register_notify(&dotg->usbdev_nb);
			dwc3_otg_start_no_device_work(dotg, true);
		}

		hcd = platform_get_drvdata(dwc->xhci);
		otg->host = &hcd->self;

		dwc3_gadget_usb3_phy_suspend(dwc, true);
	} else {
		dev_dbg(otg->phy->dev, "%s: turn off host\n", __func__);

		ret = regulator_disable(dotg->vbus_otg);
		if (ret) {
			dev_err(otg->phy->dev, "unable to disable vbus_otg\n");
			return ret;
		}

		dbg_event(0xFF, "StHost get", 0);

		/* unregister ocp notification */
		if (ext_xceiv && ext_xceiv->ext_ocp_notification.notify) {
			ret = regulator_register_ocp_notification(
					dotg->vbus_otg, NULL);
			if (ret)
				dev_err(otg->phy->dev,
					"unable to unregister ocp\n");
		}

		pm_runtime_get(dwc->dev);
		usb_phy_notify_disconnect(dotg->dwc->usb2_phy, USB_SPEED_HIGH);
		dwc3_otg_notify_host_mode(otg, on);
		otg->host = NULL;
		if (dotg->no_device_timeout_enabled) {
			dotg->no_device_timeout_enabled = 0;
			dwc3_otg_start_no_device_work(dotg, false);
			usb_unregister_notify(&dotg->usbdev_nb);
		}
		platform_device_del(dwc->xhci);

		/* WORKAROUND: delays execution of the block-reset because it
		 * causes HWWD if ext_xceive access the HW during block reset.
		 */
		usleep(10000);

		if (wake_lock_active(&dotg->host_wakelock))
			wake_unlock(&dotg->host_wakelock);
		/*
		 * Perform USB hardware RESET (both core reset and DBM reset)
		 * when moving from host to peripheral. This is required for
		 * peripheral mode to work.
		 */
		if (ext_xceiv && ext_xceiv->ext_block_reset)
			ext_xceiv->ext_block_reset(ext_xceiv, true);

		dwc3_gadget_usb3_phy_suspend(dwc, false);
		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);

		/* re-init core and OTG registers as block reset clears these */
		dwc3_post_host_reset_core_init(dwc);
		dbg_event(0xFF, "StHost put", 0);
		pm_runtime_put(dwc->dev);
	}

	return 0;
}