Ejemplo n.º 1
0
static int __dwc3_stop_host(struct usb_hcd *hcd)
{
	int count = 0;
	u32 data;
	struct xhci_hcd *xhci;
	struct usb_hcd *xhci_shared_hcd;

	if (!hcd)
		return -EINVAL;

	if (dwc3_xhci.comp_test_enable) {
		dev_dbg(hcd->self.controller,
				"%s() - Now is in comp test mode", __func__);
		return 0;
	}

	xhci = hcd_to_xhci(hcd);

	pm_runtime_get_sync(hcd->self.controller);

	/* Disable hibernation mode for D0i3cold. */
	data = readl(hcd->regs + GCTL);
	data &= ~GCTL_GBL_HIBERNATION_EN;
	writel(data, hcd->regs + GCTL);

	/* When plug out micro A cable, there will be two flows be executed.
	 * The first one is xHCI controller get disconnect event. The
	 * second one is PMIC get ID change event. During these events
	 * handling, they both try to call usb_disconnect. Then met some
	 * conflicts and cause kernel panic.
	 * So treat disconnect event as first priority, handle the ID change
	 * event until disconnect event handled done.*/
	while (if_usb_devices_connected(xhci)) {
		msleep(20);
		if (count++ > WAIT_DISC_EVENT_COMPLETE_TIMEOUT)
			break;
	};
	dwc3_xhci_driver.shutdown = NULL;

	if (xhci->shared_hcd) {
		xhci_shared_hcd = xhci->shared_hcd;
		usb_remove_hcd(xhci_shared_hcd);
		usb_put_hcd(xhci_shared_hcd);
	}

	usb_remove_hcd(hcd);

	dwc3_xhci.xhci = NULL;
	kfree(xhci);
	*((struct xhci_hcd **) hcd->hcd_priv) = NULL;

	dwc_xhci_enable_phy_suspend(hcd, false);

	pm_runtime_put(hcd->self.controller);
	device_remove_file(hcd->self.controller, &dev_attr_pm_get);
	return 0;
}
/* dwc_hcd_suspend_common and dwc_hcd_resume_common are refer to
 * suspend_common and resume_common in usb core.
 * Because the usb core function just support PCI device.
 * So re-write them in here to support platform devices.
 */
static int dwc_hcd_suspend_common(struct device *dev)
{
	struct platform_device		*pdev = to_platform_device(dev);
	struct usb_hcd		*hcd = platform_get_drvdata(pdev);
	struct xhci_hcd		*xhci = hcd_to_xhci(hcd);
	int			retval = 0;
	u32 data = 0;

	if (!xhci) {
		dev_dbg(dev, "%s: host already stop!\n", __func__);
		return 0;
	}

	/* Root hub suspend should have stopped all downstream traffic,
	 * and all bus master traffic.  And done so for both the interface
	 * and the stub usb_device (which we check here).  But maybe it
	 * didn't; writing sysfs power/state files ignores such rules...
	 */
	if (HCD_RH_RUNNING(hcd)) {
		dev_warn(dev, "Root hub is not suspended\n");
		return -EBUSY;
	}
	if (hcd->shared_hcd) {
		hcd = hcd->shared_hcd;
		if (HCD_RH_RUNNING(hcd)) {
			dev_warn(dev, "Secondary root hub is not suspended\n");
			return -EBUSY;
		}
	}

	if (!HCD_DEAD(hcd)) {
		/* Optimization: Don't suspend if a root-hub wakeup is
		 * pending and it would cause the HCD to wake up anyway.
		 */
		if (HCD_WAKEUP_PENDING(hcd))
			return -EBUSY;
		if (hcd->shared_hcd &&
				HCD_WAKEUP_PENDING(hcd->shared_hcd))
			return -EBUSY;
		if (hcd->state != HC_STATE_SUSPENDED ||
				xhci->shared_hcd->state != HC_STATE_SUSPENDED)
			retval = -EINVAL;

		if (!retval) {
			/* The auto-resume is diabled by default. Need enable it
			 * if there have valid connection. To ensure that when
			 * device resumes, host does resume reflect within
			 * 900 usec as in USB spec.
			 */
			if (if_usb_devices_connected(xhci) == 1)
				dwc_xhci_enable_phy_auto_resume(
						xhci->main_hcd, true);

			/* Ensure that suspend enable are set for
			 * USB2 and USB3 PHY
			 */
			dwc_xhci_enable_phy_suspend(hcd, true);

			data = readl(hcd->regs + GCTL);
			data |= GCTL_GBL_HIBERNATION_EN;
			writel(data, hcd->regs + GCTL);
			dev_dbg(hcd->self.controller, "set xhci hibernation enable!\n");
			retval = xhci_suspend(xhci);
		}

		/* Check again in case wakeup raced with pci_suspend */
		if ((retval == 0 && HCD_WAKEUP_PENDING(hcd)) ||
				(retval == 0 && hcd->shared_hcd &&
				 HCD_WAKEUP_PENDING(hcd->shared_hcd))) {
			xhci_resume(xhci, false);
			retval = -EBUSY;
		}
		if (retval)
			return retval;
	}

	synchronize_irq(dwc3_xhci.otg_irqnum);

	return retval;

}