> > >Introduce runtime PM and wakeup interrupt handler for cdns3, >the runtime PM is default off since other cdns3 may not >implement glue layer support for runtime PM. > >One typical wakeup event use case is xHCI runtime suspend will clear >USBCMD.RS bit, after that the xHCI will not trigger any interrupts, >so its parent (cdns core device) needs to resume xHCI device when >any (wakeup) events occurs at host port. > >When the controller is in low power mode, the lpm flag will be set. >The interrupt triggered later than lpm flag is set considers as >wakeup interrupt and handled at cdns_wakeup_irq. Once the wakeup >occurs, it first disables interrupt to avoid later interrupt >occurrence since the controller is in low power mode at that >time, and access registers may be invalid at that time. At wakeup >handler, it will call pm_request_resume to wakeup xHCI device, and >at runtime resume handler, it will enable interrupt again. > >The API platform_suspend is introduced for glue layer to implement >platform specific PM sequence. > >Signed-off-by: Peter Chen <peter.chen@xxxxxxx> Reviewed-by: Pawel Laszczak <pawell@xxxxxxxxxxx> >--- > drivers/usb/cdns3/core.c | 153 ++++++++++++++++++++++++++++++++----- > drivers/usb/cdns3/core.h | 16 ++++ > drivers/usb/cdns3/drd.c | 3 + > drivers/usb/cdns3/gadget.c | 4 + > drivers/usb/cdns3/host.c | 7 ++ > 5 files changed, 166 insertions(+), 17 deletions(-) > >diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c >index 8818935d157b..f98c3c1a6b26 100644 >--- a/drivers/usb/cdns3/core.c >+++ b/drivers/usb/cdns3/core.c >@@ -405,6 +405,30 @@ static void set_phy_power_off(struct cdns3 *cdns) > phy_power_off(cdns->usb2_phy); > } > >+/** >+ * cdns3_wakeup_irq - interrupt handler for wakeup events >+ * >+ * @irq: irq number for cdns3 core device >+ * @data: structure of cdns3 >+ * >+ * Returns IRQ_HANDLED or IRQ_NONE >+ */ >+static irqreturn_t cdns3_wakeup_irq(int irq, void *data) >+{ >+ struct cdns3 *cdns = data; >+ >+ if (cdns->in_lpm) { >+ disable_irq_nosync(irq); >+ cdns->wakeup_pending = true; >+ if ((cdns->role == USB_ROLE_HOST) && cdns->host_dev) >+ pm_request_resume(&cdns->host_dev->dev); >+ >+ return IRQ_HANDLED; >+ } >+ >+ return IRQ_NONE; >+} >+ > /** > * cdns3_probe - probe for cdns3 core device > * @pdev: Pointer to cdns3 core platform device >@@ -431,6 +455,7 @@ static int cdns3_probe(struct platform_device *pdev) > return -ENOMEM; > > cdns->dev = dev; >+ cdns->pdata = dev_get_platdata(dev); > > platform_set_drvdata(pdev, cdns); > >@@ -480,6 +505,15 @@ static int cdns3_probe(struct platform_device *pdev) > > cdns->otg_res = *res; > >+ cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); >+ if (cdns->wakeup_irq == -EPROBE_DEFER) >+ return cdns->wakeup_irq; >+ >+ if (cdns->wakeup_irq < 0) { >+ dev_dbg(dev, "couldn't get wakeup irq\n"); >+ cdns->wakeup_irq = 0x0; >+ } >+ > mutex_init(&cdns->mutex); > > cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); >@@ -516,6 +550,19 @@ static int cdns3_probe(struct platform_device *pdev) > goto err3; > } > >+ if (cdns->wakeup_irq) { >+ ret = devm_request_threaded_irq(cdns->dev, cdns->wakeup_irq, >+ cdns3_wakeup_irq, >+ NULL, >+ IRQF_SHARED, >+ dev_name(cdns->dev), cdns); >+ >+ if (ret) { >+ dev_err(cdns->dev, "couldn't register wakeup irq handler\n"); >+ goto err3; >+ } >+ } >+ > ret = cdns3_drd_init(cdns); > if (ret) > goto err4; >@@ -524,9 +571,11 @@ static int cdns3_probe(struct platform_device *pdev) > if (ret) > goto err4; > >+ spin_lock_init(&cdns->lock); > device_set_wakeup_capable(dev, true); > pm_runtime_set_active(dev); > pm_runtime_enable(dev); >+ pm_runtime_forbid(dev); > > /* > * The controller needs less time between bus and controller suspend, >@@ -573,52 +622,122 @@ static int cdns3_remove(struct platform_device *pdev) > return 0; > } > >-#ifdef CONFIG_PM_SLEEP >+#ifdef CONFIG_PM > >-static int cdns3_suspend(struct device *dev) >+static int cdns3_set_platform_suspend(struct device *dev, >+ bool suspend, bool wakeup) >+{ >+ struct cdns3 *cdns = dev_get_drvdata(dev); >+ int ret = 0; >+ >+ if (cdns->pdata && cdns->pdata->platform_suspend) >+ ret = cdns->pdata->platform_suspend(dev, suspend, wakeup); >+ >+ return ret; >+} >+ >+static int cdns3_controller_suspend(struct device *dev, pm_message_t msg) > { > struct cdns3 *cdns = dev_get_drvdata(dev); >+ bool wakeup; > unsigned long flags; > >- if (cdns->role == USB_ROLE_HOST) >+ if (cdns->in_lpm) > return 0; > >- if (pm_runtime_status_suspended(dev)) >- pm_runtime_resume(dev); >+ if (PMSG_IS_AUTO(msg)) >+ wakeup = true; >+ else >+ wakeup = device_may_wakeup(dev); > >- if (cdns->roles[cdns->role]->suspend) { >- spin_lock_irqsave(&cdns->gadget_dev->lock, flags); >- cdns->roles[cdns->role]->suspend(cdns, false); >- spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags); >- } >+ cdns3_set_platform_suspend(cdns->dev, true, wakeup); >+ set_phy_power_off(cdns); >+ spin_lock_irqsave(&cdns->lock, flags); >+ cdns->in_lpm = true; >+ spin_unlock_irqrestore(&cdns->lock, flags); >+ dev_dbg(cdns->dev, "%s ends\n", __func__); > > return 0; > } > >-static int cdns3_resume(struct device *dev) >+static int cdns3_controller_resume(struct device *dev, pm_message_t msg) > { > struct cdns3 *cdns = dev_get_drvdata(dev); >+ int ret; > unsigned long flags; > >- if (cdns->role == USB_ROLE_HOST) >+ if (!cdns->in_lpm) > return 0; > >- if (cdns->roles[cdns->role]->resume) { >- spin_lock_irqsave(&cdns->gadget_dev->lock, flags); >+ ret = set_phy_power_on(cdns); >+ if (ret) >+ return ret; >+ >+ cdns3_set_platform_suspend(cdns->dev, false, false); >+ >+ spin_lock_irqsave(&cdns->lock, flags); >+ if (cdns->roles[cdns->role]->resume && !PMSG_IS_AUTO(msg)) > cdns->roles[cdns->role]->resume(cdns, false); >- spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags); >+ >+ cdns->in_lpm = false; >+ spin_unlock_irqrestore(&cdns->lock, flags); >+ if (cdns->wakeup_pending) { >+ cdns->wakeup_pending = false; >+ enable_irq(cdns->wakeup_irq); >+ } >+ dev_dbg(cdns->dev, "%s ends\n", __func__); >+ >+ return ret; >+} >+ >+static int cdns3_runtime_suspend(struct device *dev) >+{ >+ return cdns3_controller_suspend(dev, PMSG_AUTO_SUSPEND); >+} >+ >+static int cdns3_runtime_resume(struct device *dev) >+{ >+ return cdns3_controller_resume(dev, PMSG_AUTO_RESUME); >+} >+#ifdef CONFIG_PM_SLEEP >+ >+static int cdns3_suspend(struct device *dev) >+{ >+ struct cdns3 *cdns = dev_get_drvdata(dev); >+ unsigned long flags; >+ >+ if (pm_runtime_status_suspended(dev)) >+ pm_runtime_resume(dev); >+ >+ if (cdns->roles[cdns->role]->suspend) { >+ spin_lock_irqsave(&cdns->lock, flags); >+ cdns->roles[cdns->role]->suspend(cdns, false); >+ spin_unlock_irqrestore(&cdns->lock, flags); > } > >+ return cdns3_controller_suspend(dev, PMSG_SUSPEND); >+} >+ >+static int cdns3_resume(struct device *dev) >+{ >+ int ret; >+ >+ ret = cdns3_controller_resume(dev, PMSG_RESUME); >+ if (ret) >+ return ret; >+ > pm_runtime_disable(dev); > pm_runtime_set_active(dev); > pm_runtime_enable(dev); > >- return 0; >+ return ret; > } >-#endif >+#endif /* CONFIG_PM_SLEEP */ >+#endif /* CONFIG_PM */ > > static const struct dev_pm_ops cdns3_pm_ops = { > SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume) >+ SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL) > }; > > #ifdef CONFIG_OF >diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h >index 1ad1f1fe61e9..1b1707796db2 100644 >--- a/drivers/usb/cdns3/core.h >+++ b/drivers/usb/cdns3/core.h >@@ -38,6 +38,12 @@ struct cdns3_role_driver { > }; > > #define CDNS3_XHCI_RESOURCES_NUM 2 >+ >+struct cdns3_platform_data { >+ int (*platform_suspend)(struct device *dev, >+ bool suspend, bool wakeup); >+}; >+ > /** > * struct cdns3 - Representation of Cadence USB3 DRD controller. > * @dev: pointer to Cadence device struct >@@ -50,6 +56,7 @@ struct cdns3_role_driver { > * @otg_regs: pointer to base of otg registers > * @otg_irq: irq number for otg controller > * @dev_irq: irq number for device controller >+ * @wakeup_irq: irq number for wakeup event, it is optional > * @roles: array of supported roles for this controller > * @role: current role > * @host_dev: the child host device pointer for cdns3 core >@@ -62,6 +69,10 @@ struct cdns3_role_driver { > * This field based on firmware setting, kernel configuration > * and hardware configuration. > * @role_sw: pointer to role switch object. >+ * @in_lpm: indicate the controller is in low power mode >+ * @wakeup_pending: wakeup interrupt pending >+ * @pdata: platform data from glue layer >+ * @lock: spinlock structure > */ > struct cdns3 { > struct device *dev; >@@ -79,6 +90,7 @@ struct cdns3 { > > int otg_irq; > int dev_irq; >+ int wakeup_irq; > struct cdns3_role_driver *roles[USB_ROLE_DEVICE + 1]; > enum usb_role role; > struct platform_device *host_dev; >@@ -89,6 +101,10 @@ struct cdns3 { > struct mutex mutex; > enum usb_dr_mode dr_mode; > struct usb_role_switch *role_sw; >+ bool in_lpm; >+ bool wakeup_pending; >+ struct cdns3_platform_data *pdata; >+ spinlock_t lock; > }; > > int cdns3_hw_role_switch(struct cdns3 *cdns); >diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c >index 58089841ed52..ac90a484e63c 100644 >--- a/drivers/usb/cdns3/drd.c >+++ b/drivers/usb/cdns3/drd.c >@@ -281,6 +281,9 @@ static irqreturn_t cdns3_drd_irq(int irq, void *data) > if (cdns->dr_mode != USB_DR_MODE_OTG) > return ret; > >+ if (cdns->in_lpm) >+ return ret; >+ > reg = readl(&cdns->otg_regs->ivect); > > if (!reg) >diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c >index 7c2913bc8bd7..0111fba95797 100644 >--- a/drivers/usb/cdns3/gadget.c >+++ b/drivers/usb/cdns3/gadget.c >@@ -1766,9 +1766,13 @@ static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev, > static irqreturn_t cdns3_device_irq_handler(int irq, void *data) > { > struct cdns3_device *priv_dev = data; >+ struct cdns3 *cdns = dev_get_drvdata(priv_dev->dev); > irqreturn_t ret = IRQ_NONE; > u32 reg; > >+ if (cdns->in_lpm) >+ return ret; >+ > /* check USB device interrupt */ > reg = readl(&priv_dev->regs->usb_ists); > if (reg) { >diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c >index ad788bf3fe4f..b579ef15f4e0 100644 >--- a/drivers/usb/cdns3/host.c >+++ b/drivers/usb/cdns3/host.c >@@ -13,11 +13,13 @@ > #include "core.h" > #include "drd.h" > #include "host-export.h" >+#include <linux/usb/hcd.h> > > static int __cdns3_host_init(struct cdns3 *cdns) > { > struct platform_device *xhci; > int ret; >+ struct usb_hcd *hcd; > > cdns3_drd_switch_host(cdns, 1); > >@@ -43,6 +45,11 @@ static int __cdns3_host_init(struct cdns3 *cdns) > goto err1; > } > >+ /* Glue needs to access xHCI region register for Power management */ >+ hcd = platform_get_drvdata(xhci); >+ if (hcd) >+ cdns->xhci_regs = hcd->regs; >+ > return 0; > err1: > platform_device_put(xhci); >-- >2.17.1 Regards, Pawel