/* * Copyright (C) 2012 Intel Corp. * Author: Yu Wang * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include "../host/xhci.h" #include "core.h" #include "otg.h" #define WAIT_DISC_EVENT_COMPLETE_TIMEOUT 5 /* 100ms */ #define PORTSC_IO_ADDR 0xf9100430 #define USBCMD_IO_ADDR 0xf9100020 static int dwc3_start_host(struct usb_hcd *hcd); static int dwc3_stop_host(struct usb_hcd *hcd); static struct platform_driver dwc3_xhci_driver; static int __dwc3_stop_host(struct usb_hcd *hcd); static int __dwc3_start_host(struct usb_hcd *hcd); static int dwc3_suspend_host(struct usb_hcd *hcd); static int dwc3_resume_host(struct usb_hcd *hcd); static struct dwc3_xhci_hcd { struct wake_lock wakelock; struct xhci_hcd *xhci; struct work_struct reset_hcd; struct work_struct poll_loopback; int is_rx_test; int otg_irqnum; bool host_started; bool comp_test_enable; void __iomem *portsc_mmaddr; } dwc3_xhci; static void dwc3_host_quirks(struct device *dev, struct xhci_hcd *xhci) { struct dwc_otg2 *otg = dwc3_get_otg(); struct intel_dwc_otg_pdata *data = NULL; data = (struct intel_dwc_otg_pdata *)otg->otg_data; if (otg && otg->otg_data) data = (struct intel_dwc_otg_pdata *)otg->otg_data; if (data && data->utmi_fs_det_wa) xhci->quirks |= XHCI_PORT_RESET; /* * As of now platform drivers don't provide MSI support so we ensure * here that the generic code does not try to make a pci_dev from our * dev struct in order to setup MSI */ xhci->quirks |= XHCI_PLAT; /* * Due to some fatal silicon errors, the controller have to do reset * for make driver continue work. */ xhci->quirks |= XHCI_RESET; /* * Change SS port host reset to warm reset, due to individual USB3.0 * UMS address fail caused by link state unstable afer hot reset. */ xhci->quirks |= XHCI_FORCE_WR; } static int dwc3_host_setup(struct usb_hcd *hcd) { return xhci_gen_setup(hcd, dwc3_host_quirks); } static int xhci_dwc_bus_resume(struct usb_hcd *hcd) { int ret; /* before resume bus, delay 1ms to waiting core stable */ mdelay(1); ret = xhci_bus_resume(hcd); return ret; } static const struct hc_driver xhci_dwc_hc_driver = { .description = "dwc-xhci", .product_desc = "xHCI Host Controller", .hcd_priv_size = sizeof(struct xhci_hcd *), /* * generic hardware linkage */ .irq = xhci_irq, .flags = HCD_MEMORY | HCD_USB3 | HCD_SHARED, /* * basic lifecycle operations */ .reset = dwc3_host_setup, .start = xhci_run, .stop = xhci_stop, .shutdown = xhci_shutdown, /* * managing i/o requests and associated device resources */ .urb_enqueue = xhci_urb_enqueue, .urb_dequeue = xhci_urb_dequeue, .alloc_dev = xhci_alloc_dev, .free_dev = xhci_free_dev, .alloc_streams = xhci_alloc_streams, .free_streams = xhci_free_streams, .add_endpoint = xhci_add_endpoint, .drop_endpoint = xhci_drop_endpoint, .endpoint_reset = xhci_endpoint_reset, .check_bandwidth = xhci_check_bandwidth, .reset_bandwidth = xhci_reset_bandwidth, .address_device = xhci_address_device, .update_hub_device = xhci_update_hub_device, .reset_device = xhci_discover_or_reset_device, /* * scheduling support */ .get_frame_number = xhci_get_frame, /* Root hub support */ .hub_control = xhci_hub_control, .hub_status_data = xhci_hub_status_data, .bus_suspend = xhci_bus_suspend, .bus_resume = xhci_dwc_bus_resume, }; static int if_usb_devices_connected(struct xhci_hcd *xhci) { struct usb_device *usb_dev; int i, connected_devices = 0; if (!xhci) return -EINVAL; usb_dev = xhci->main_hcd->self.root_hub; for (i = 1; i <= usb_dev->maxchild; ++i) { if (usb_hub_find_child(usb_dev, i)) connected_devices++; } usb_dev = xhci->shared_hcd->self.root_hub; for (i = 1; i <= usb_dev->maxchild; ++i) { if (usb_hub_find_child(usb_dev, i)) connected_devices++; } if (connected_devices) return 1; return 0; } /* For USB3 host electronic compliance test. Controller have to enter * Loopback mode for RX test. But controller easier enter compliance * mode by mistake. So driver need to trigger warm reset until enter * loopback mode successful. **/ static void dwc3_poll_lp(struct work_struct *data) { u32 pls, val; if (!dwc3_xhci.comp_test_enable) return; val = readl(dwc3_xhci.portsc_mmaddr); pls = val & PORT_PLS_MASK; if (pls == XDEV_COMP && dwc3_xhci.is_rx_test) writel(val | PORT_WR, dwc3_xhci.portsc_mmaddr); if (!dwc3_xhci.is_rx_test || pls == XDEV_LOOPBACK) { iounmap(dwc3_xhci.portsc_mmaddr); return; } else schedule_work(&dwc3_xhci.poll_loopback); } /* Do xHCI driver reinitialize when met fatal errors */ static void dwc3_host_reset(struct work_struct *data) { struct usb_hcd *hcd; if (!dwc3_xhci.host_started || !data) return; if (!dwc3_xhci.xhci) return; hcd = dwc3_xhci.xhci->main_hcd; /* Need hold wakelock to prevent the S3 interrupt * the reset work.*/ wake_lock(&dwc3_xhci.wakelock); __dwc3_stop_host(hcd); __dwc3_start_host(hcd); wake_unlock(&dwc3_xhci.wakelock); } static void dwc_xhci_enable_phy_auto_resume(struct usb_hcd *hcd, bool enable) { u32 val; val = readl(hcd->regs + GUSB2PHYCFG0); if (enable) val |= GUSB2PHYCFG_ULPI_AUTO_RESUME; else val &= ~GUSB2PHYCFG_ULPI_AUTO_RESUME; writel(val, hcd->regs + GUSB2PHYCFG0); } static void dwc_xhci_enable_phy_suspend(struct usb_hcd *hcd, bool enable) { u32 val; val = readl(hcd->regs + GUSB3PIPECTL0); if (enable) val |= GUSB3PIPECTL_SUS_EN; else val &= ~GUSB3PIPECTL_SUS_EN; writel(val, hcd->regs + GUSB3PIPECTL0); val = readl(hcd->regs + GUSB2PHYCFG0); if (enable) val |= GUSB2PHYCFG_SUS_PHY; else val &= ~GUSB2PHYCFG_SUS_PHY; writel(val, hcd->regs + GUSB2PHYCFG0); } /* Some SS UMS will be enter polling state after plug in with micro A cable. * If trigger warm reset, then link can be rescued to U0. * * This function copy from hub_port_reset function is USB core. */ static int dwc3_link_issue_wa(struct xhci_hcd *xhci) { __le32 __iomem **addr; int delay_time, ret; u32 pls, val, delay; addr = dwc3_xhci.xhci->usb3_ports; val = xhci_readl(dwc3_xhci.xhci, addr[0]); /* If PORTSC.CCS bit haven't set. We can trigger warm reset * to double confirm if really have no device or link can't trained to * U0. */ if (!(val & PORT_CONNECT)) { val |= PORT_WR; xhci_writel(xhci, val, addr[0]); xhci_dbg(xhci, "%s: trigger warm reset\n", __func__); } /* Waiting warm reset complete. */ for (delay_time = 0; delay_time < 800; delay_time += delay) { msleep(delay); val = xhci_readl(dwc3_xhci.xhci, addr[0]); if (!(val & PORT_RESET)) break; if (delay_time >= 20) delay = 200; } if (val & PORT_RESET) xhci_err(xhci, "%s port reset failed!\n", __func__); return 0; } static void dwc_silicon_wa(struct usb_hcd *hcd) { void __iomem *addr; u32 val; /* Clear GUCTL bit 15 as workaround of DWC controller Bugs * This Bug cause the xHCI driver does not see any * transfer complete events for certain EP after exit * from hibernation mode.*/ val = readl(hcd->regs + GUCTL); val &= ~GUCTL_CMDEVADDR; writel(val, hcd->regs + GUCTL); /* Disable OTG3-EXI interface by default. It is one * workaround for silicon BUG. It will cause transfer * failed on EP#8 of any USB device. */ addr = ioremap_nocache(APBFC_EXIOTG3_MISC0_REG, 4); val = readl(addr); val |= (1 << 3); writel(val, addr); iounmap(addr); } static void dwc_core_reset(struct usb_hcd *hcd) { u32 val; val = readl(hcd->regs + GCTL); val |= GCTL_CORESOFTRESET; writel(val, hcd->regs + GCTL); val = readl(hcd->regs + GUSB3PIPECTL0); val |= GUSB3PIPECTL_PHYSOFTRST; writel(val, hcd->regs + GUSB3PIPECTL0); val = readl(hcd->regs + GUSB2PHYCFG0); val |= GUSB2PHYCFG_PHYSOFTRST; writel(val, hcd->regs + GUSB2PHYCFG0); msleep(100); val = readl(hcd->regs + GUSB3PIPECTL0); val &= ~GUSB3PIPECTL_PHYSOFTRST; writel(val, hcd->regs + GUSB3PIPECTL0); val = readl(hcd->regs + GUSB2PHYCFG0); val &= ~GUSB2PHYCFG_PHYSOFTRST; writel(val, hcd->regs + GUSB2PHYCFG0); msleep(20); val = readl(hcd->regs + GCTL); val &= ~GCTL_CORESOFTRESET; writel(val, hcd->regs + GCTL); } /* * On MERR platform, the suspend clock is 19.2MHz. * Hence PwrDnScale = 19200 / 16 = 1200 (= 0x4B0). * To account for possible jitter of suspend clock and to have margin, * So recommend it to be set to 1250 (= 0x4E2). * */ static void dwc_set_ssphy_p3_clockrate(struct usb_hcd *hcd) { u32 gctl; gctl = readl(hcd->regs + GCTL); gctl &= ~GCTL_PWRDNSCALE_MASK; gctl |= GCTL_PWRDNSCALE(0x4E2); writel(gctl, hcd->regs + GCTL); } /* * This is for host compliance test * * */ static ssize_t show_host_comp_test(struct device *_dev, struct device_attribute *attr, char *buf) { char *next; unsigned size, t; next = buf; size = PAGE_SIZE; t = scnprintf(next, size, "%s\n", (dwc3_xhci.comp_test_enable ? "compliance test enabled, echo 0 to disable" : "compliance test disabled, echo 1 to enable") ); size -= t; next += t; return PAGE_SIZE - size; } static ssize_t store_host_comp_test(struct device *_dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *pdev = to_platform_device(_dev); struct usb_hcd *hcd = platform_get_drvdata(pdev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); void __iomem *addr; u32 val; if (count != 2) { dev_err(hcd->self.controller, "return EINVAL\n"); return -EINVAL; } if (count > 0 && buf[count-1] == '\n') ((char *) buf)[count-1] = 0; switch (buf[0]) { case 'R': if (!dwc3_xhci.comp_test_enable) break; dwc3_xhci.is_rx_test = 1; dwc3_xhci.portsc_mmaddr = ioremap_nocache(PORTSC_IO_ADDR, 4); if (!dwc3_xhci.portsc_mmaddr) { dev_err(hcd->self.controller, "ioremap failed!\n"); return -ENOMEM; } schedule_work(&dwc3_xhci.poll_loopback); break; case 'T': dwc3_xhci.is_rx_test = 0; if (!dwc3_xhci.comp_test_enable) break; break; case '0': if (dwc3_xhci.comp_test_enable) { dev_dbg(hcd->self.controller, "run xHC\n"); addr = ioremap_nocache(USBCMD_IO_ADDR, 4); if (!addr) { dev_err(hcd->self.controller, "ioremap failed!\n"); return -ENOMEM; } val = readl(addr); val |= CMD_RUN; writel(val, addr); iounmap(addr); pm_runtime_put(hcd->self.controller); wake_unlock(&hcd->wake_lock); dwc3_xhci.comp_test_enable = false; } break; case '1': if (!dwc3_xhci.comp_test_enable) { dev_dbg(hcd->self.controller, "halt xHC\n"); wake_lock(&hcd->wake_lock); pm_runtime_get_sync(hcd->self.controller); addr = ioremap_nocache(USBCMD_IO_ADDR, 4); if (!addr) { dev_err(hcd->self.controller, "ioremap failed!\n"); return -ENOMEM; } val = readl(addr); val &= ~CMD_RUN; writel(val, addr); iounmap(addr); dwc3_xhci.comp_test_enable = true; } break; default: dev_dbg(hcd->self.controller, "Just support 0(halt)/1(run)\n"); return -EINVAL; } return count; } static DEVICE_ATTR(host_comp_test, S_IRUGO|S_IWUSR|S_IWGRP, show_host_comp_test, store_host_comp_test); static void dwc_set_host_mode(struct usb_hcd *hcd) { writel(0x45801000, hcd->regs + GCTL); msleep(20); } static int dwc3_start_host(struct usb_hcd *hcd) { dwc3_xhci.host_started = true; __dwc3_start_host(hcd); return 0; } static int __dwc3_start_host(struct usb_hcd *hcd) { int ret = -EINVAL; struct xhci_hcd *xhci; struct usb_hcd *xhci_shared_hcd; if (!hcd) return ret; if (hcd->rh_registered) { dev_dbg(hcd->self.controller, "%s() - Already registered", __func__); return 0; } if (dwc3_xhci.comp_test_enable) { dev_dbg(hcd->self.controller, "%s() - Now is in comp test mode", __func__); return 0; } pm_runtime_get_sync(hcd->self.controller); dwc_core_reset(hcd); dwc_silicon_wa(hcd); dwc_set_host_mode(hcd); dwc_set_ssphy_p3_clockrate(hcd); /* Clear the hcd->flags. * To prevent incorrect flags set during last time. */ hcd->flags = 0; ret = usb_add_hcd(hcd, dwc3_xhci.otg_irqnum, IRQF_SHARED); if (ret) return -EINVAL; xhci = hcd_to_xhci(hcd); dwc3_xhci.xhci = xhci; xhci->reset_hcd_work = &dwc3_xhci.reset_hcd; xhci->shared_hcd = usb_create_shared_hcd(&xhci_dwc_hc_driver, hcd->self.controller, dev_name(hcd->self.controller), hcd); if (!xhci->shared_hcd) { ret = -ENOMEM; goto dealloc_usb2_hcd; } /* Set the xHCI pointer before xhci_pci_setup() (aka hcd_driver.reset) * is called by usb_add_hcd(). */ *((struct xhci_hcd **) xhci->shared_hcd->hcd_priv) = xhci; xhci->shared_hcd->regs = hcd->regs; xhci->shared_hcd->rsrc_start = hcd->rsrc_start; xhci->shared_hcd->rsrc_len = hcd->rsrc_len; ret = usb_add_hcd(xhci->shared_hcd, dwc3_xhci.otg_irqnum, IRQF_SHARED); if (ret) goto put_usb3_hcd; dwc3_link_issue_wa(xhci); pm_runtime_put(hcd->self.controller); dwc3_xhci_driver.shutdown = usb_hcd_platform_shutdown; return ret; put_usb3_hcd: if (xhci->shared_hcd) { xhci_shared_hcd = xhci->shared_hcd; usb_remove_hcd(xhci_shared_hcd); usb_put_hcd(xhci_shared_hcd); } dealloc_usb2_hcd: local_irq_disable(); usb_hcd_irq(0, hcd); local_irq_enable(); usb_remove_hcd(hcd); kfree(xhci); *((struct xhci_hcd **) hcd->hcd_priv) = NULL; pm_runtime_put(hcd->self.controller); return ret; } 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); return 0; } static int dwc3_stop_host(struct usb_hcd *hcd) { struct xhci_hcd *xhci; xhci = hcd_to_xhci(hcd); if (!xhci) return -ENODEV; dwc3_xhci.host_started = false; cancel_work_sync(xhci->reset_hcd_work); __dwc3_stop_host(hcd); return 0; } static int xhci_dwc_drv_probe(struct platform_device *pdev) { struct dwc_otg2 *otg; struct usb_phy *usb_phy; struct dwc_device_par *pdata; struct usb_hcd *hcd; struct resource *res; int retval = 0; int ret; if (usb_disabled()) return -ENODEV; pr_debug("initializing FSL-SOC USB Controller\n"); /* Need platform data for setup */ pdata = (struct dwc_device_par *)pdev->dev.platform_data; if (!pdata) { dev_err(&pdev->dev, "No platform data for %s.\n", dev_name(&pdev->dev)); return -ENODEV; } res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { dev_err(&pdev->dev, "Found HC with no IRQ. Check %s setup!\n", dev_name(&pdev->dev)); return -ENODEV; } dwc3_xhci.otg_irqnum = res->start; hcd = usb_create_hcd(&xhci_dwc_hc_driver, &pdev->dev, dev_name(&pdev->dev)); if (!hcd) { retval = -ENOMEM; return retval; } hcd->regs = pdata->io_addr; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Found HC with no IRQ. Check %s setup!\n", dev_name(&pdev->dev)); return -ENODEV; } hcd->rsrc_start = res->start; hcd->rsrc_len = res->end - res->start; usb_phy = usb_get_phy(USB_PHY_TYPE_USB2); if (usb_phy) otg_set_host(usb_phy->otg, &hcd->self); otg = container_of(usb_phy->otg, struct dwc_otg2, otg); if (otg) { otg->start_host = dwc3_start_host; otg->stop_host = dwc3_stop_host; otg->suspend_host = dwc3_suspend_host; otg->resume_host = dwc3_resume_host; } usb_put_phy(usb_phy); /* Enable wakeup irq */ hcd->has_wakeup_irq = 1; INIT_WORK(&dwc3_xhci.reset_hcd, dwc3_host_reset); INIT_WORK(&dwc3_xhci.poll_loopback, dwc3_poll_lp); wake_lock_init(&dwc3_xhci.wakelock, WAKE_LOCK_SUSPEND, "dwc3_host_wakelock"); platform_set_drvdata(pdev, hcd); pm_runtime_no_callbacks(hcd->self.controller); pm_runtime_enable(hcd->self.controller); ret = device_create_file(hcd->self.controller, &dev_attr_host_comp_test); if (ret < 0) dev_err(hcd->self.controller, "Can't register sysfs attribute: %d\n", ret); return retval; } static int xhci_dwc_drv_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct usb_phy *usb_phy; struct xhci_hcd *xhci = hcd_to_xhci(hcd); usb_phy = usb_get_phy(USB_PHY_TYPE_USB2); otg_set_host(usb_phy->otg, NULL); usb_put_phy(usb_phy); if (xhci) dwc3_stop_host(hcd); usb_put_hcd(hcd); pm_runtime_disable(hcd->self.controller); pm_runtime_set_suspended(hcd->self.controller); wake_lock_destroy(&dwc3_xhci.wakelock); return 0; } #ifdef CONFIG_PM /* 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; } static int dwc_hcd_resume_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; if (!xhci) return 0; if (HCD_RH_RUNNING(hcd) || (hcd->shared_hcd && HCD_RH_RUNNING(hcd->shared_hcd))) { dev_dbg(dev, "can't resume, not suspended!\n"); return 0; } if (!HCD_DEAD(hcd)) { retval = xhci_resume(xhci, false); if (retval) { dev_err(dev, "PCI post-resume error %d!\n", retval); if (hcd->shared_hcd) usb_hc_died(hcd->shared_hcd); usb_hc_died(hcd); } } dev_dbg(dev, "hcd_pci_runtime_resume: %d\n", retval); return retval; } static int dwc3_suspend_host(struct usb_hcd *hcd) { int retval; if (!hcd) return -EINVAL; retval = dwc_hcd_suspend_common(hcd->self.controller); if (retval) dwc_xhci_enable_phy_auto_resume( hcd, false); dev_dbg(hcd->self.controller, "%s: %d\n", __func__, retval); return retval; } static int dwc3_resume_host(struct usb_hcd *hcd) { int retval; if (!hcd) return -EINVAL; dwc_xhci_enable_phy_auto_resume( hcd, false); retval = dwc_hcd_resume_common(hcd->self.controller); dev_dbg(hcd->self.controller, "%s: %d\n", __func__, retval); return retval; } #endif static struct platform_driver dwc3_xhci_driver = { .probe = xhci_dwc_drv_probe, .remove = xhci_dwc_drv_remove, .driver = { .name = "dwc3-host", }, };