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