blob: 467d0e3f178acc1e539cb74e9f4cafc6a9af0bcd [file] [log] [blame]
/*
* 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 <linux/kernel.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/wakelock.h>
#include <linux/usb/otg.h>
#include <linux/platform_device.h>
#include <linux/usb/dwc3-intel-mid.h>
#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",
},
};