summaryrefslogtreecommitdiff
path: root/drivers/usb/host/xhci-plat.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/xhci-plat.c')
-rw-r--r--drivers/usb/host/xhci-plat.c145
1 files changed, 126 insertions, 19 deletions
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 4fe7c9b56bc0..fcb9b4b822aa 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -73,6 +73,60 @@ static int xhci_plat_start(struct usb_hcd *hcd)
return xhci_run(hcd);
}
+static ssize_t config_imod_store(struct device *pdev,
+ struct device_attribute *attr, const char *buff, size_t size)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(pdev);
+ struct xhci_hcd *xhci;
+ u32 temp;
+ u32 imod;
+ unsigned long flags;
+
+ if (kstrtouint(buff, 10, &imod) != 1)
+ return 0;
+
+ imod &= ER_IRQ_INTERVAL_MASK;
+ xhci = hcd_to_xhci(hcd);
+
+ if (xhci->shared_hcd->state == HC_STATE_SUSPENDED
+ && hcd->state == HC_STATE_SUSPENDED)
+ return -EACCES;
+
+ spin_lock_irqsave(&xhci->lock, flags);
+ temp = readl_relaxed(&xhci->ir_set->irq_control);
+ temp &= ~ER_IRQ_INTERVAL_MASK;
+ temp |= imod;
+ writel_relaxed(temp, &xhci->ir_set->irq_control);
+ spin_unlock_irqrestore(&xhci->lock, flags);
+
+ return size;
+}
+
+static ssize_t config_imod_show(struct device *pdev,
+ struct device_attribute *attr, char *buff)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(pdev);
+ struct xhci_hcd *xhci;
+ u32 temp;
+ unsigned long flags;
+
+ xhci = hcd_to_xhci(hcd);
+
+ if (xhci->shared_hcd->state == HC_STATE_SUSPENDED
+ && hcd->state == HC_STATE_SUSPENDED)
+ return -EACCES;
+
+ spin_lock_irqsave(&xhci->lock, flags);
+ temp = readl_relaxed(&xhci->ir_set->irq_control) &
+ ER_IRQ_INTERVAL_MASK;
+ spin_unlock_irqrestore(&xhci->lock, flags);
+
+ return snprintf(buff, PAGE_SIZE, "%08u\n", temp);
+}
+
+static DEVICE_ATTR(config_imod, S_IRUGO | S_IWUSR,
+ config_imod_show, config_imod_store);
+
static int xhci_plat_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
@@ -113,6 +167,8 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (!hcd)
return -ENOMEM;
+ hcd_to_bus(hcd)->skip_resume = true;
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hcd->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(hcd->regs)) {
@@ -137,6 +193,15 @@ static int xhci_plat_probe(struct platform_device *pdev)
goto put_hcd;
}
+ if (pdev->dev.parent)
+ pm_runtime_resume(pdev->dev.parent);
+
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 1000);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_get_sync(&pdev->dev);
+
if (of_device_is_compatible(pdev->dev.of_node,
"marvell,armada-375-xhci") ||
of_device_is_compatible(pdev->dev.of_node,
@@ -158,6 +223,8 @@ static int xhci_plat_probe(struct platform_device *pdev)
goto disable_clk;
}
+ hcd_to_bus(xhci->shared_hcd)->skip_resume = true;
+
if ((node && of_property_read_bool(node, "usb3-lpm-capable")) ||
(pdata && pdata->usb3_lpm_capable))
xhci->quirks |= XHCI_LPM_SUPPORT;
@@ -177,14 +244,26 @@ static int xhci_plat_probe(struct platform_device *pdev)
goto put_usb3_hcd;
}
- ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
+ ret = usb_add_hcd(hcd, irq, IRQF_SHARED | IRQF_ONESHOT);
if (ret)
goto disable_usb_phy;
- ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED);
+ device_wakeup_enable(&hcd->self.root_hub->dev);
+
+ ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED | IRQF_ONESHOT);
if (ret)
goto dealloc_usb2_hcd;
+ device_wakeup_enable(&xhci->shared_hcd->self.root_hub->dev);
+
+ ret = device_create_file(&pdev->dev, &dev_attr_config_imod);
+ if (ret)
+ dev_err(&pdev->dev, "%s: unable to create imod sysfs entry\n",
+ __func__);
+
+ pm_runtime_mark_last_busy(&pdev->dev);
+ pm_runtime_put_autosuspend(&pdev->dev);
+
return 0;
@@ -213,6 +292,10 @@ static int xhci_plat_remove(struct platform_device *dev)
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct clk *clk = xhci->clk;
+ pm_runtime_disable(&dev->dev);
+
+ device_remove_file(&dev->dev, &dev_attr_config_imod);
+ xhci->xhc_state |= XHCI_STATE_REMOVING;
usb_remove_hcd(xhci->shared_hcd);
usb_phy_shutdown(hcd->usb_phy);
@@ -226,33 +309,57 @@ static int xhci_plat_remove(struct platform_device *dev)
return 0;
}
-#ifdef CONFIG_PM_SLEEP
-static int xhci_plat_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int xhci_plat_runtime_idle(struct device *dev)
{
- struct usb_hcd *hcd = dev_get_drvdata(dev);
- struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-
/*
- * xhci_suspend() needs `do_wakeup` to know whether host is allowed
- * to do wakeup during suspend. Since xhci_plat_suspend is currently
- * only designed for system suspend, device_may_wakeup() is enough
- * to dertermine whether host is allowed to do wakeup. Need to
- * reconsider this when xhci_plat_suspend enlarges its scope, e.g.,
- * also applies to runtime suspend.
+ * When pm_runtime_put_autosuspend() is called on this device,
+ * after this idle callback returns the PM core will schedule the
+ * autosuspend if there is any remaining time until expiry. However,
+ * when reaching this point because the child_count becomes 0, the
+ * core does not honor autosuspend in that case and results in
+ * idle/suspend happening immediately. In order to have a delay
+ * before suspend we have to call pm_runtime_autosuspend() manually.
*/
- return xhci_suspend(xhci, device_may_wakeup(dev));
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_autosuspend(dev);
+ return -EBUSY;
}
-static int xhci_plat_resume(struct device *dev)
+static int xhci_plat_runtime_suspend(struct device *dev)
{
- struct usb_hcd *hcd = dev_get_drvdata(dev);
- struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ if (!xhci)
+ return 0;
+
+ dev_dbg(dev, "xhci-plat runtime suspend\n");
- return xhci_resume(xhci, 0);
+ return 0;
+}
+
+static int xhci_plat_runtime_resume(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ int ret;
+
+ if (!xhci)
+ return 0;
+
+ dev_dbg(dev, "xhci-plat runtime resume\n");
+
+ ret = 0;
+ pm_runtime_mark_last_busy(dev);
+
+ return ret;
}
static const struct dev_pm_ops xhci_plat_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume)
+ SET_SYSTEM_SLEEP_PM_OPS(NULL, NULL)
+ SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, xhci_plat_runtime_resume,
+ xhci_plat_runtime_idle)
};
#define DEV_PM_OPS (&xhci_plat_pm_ops)
#else