diff options
Diffstat (limited to 'drivers/usb/host/xhci-plat.c')
-rw-r--r-- | drivers/usb/host/xhci-plat.c | 145 |
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 |