diff options
author | Peter Chen <peter.chen@freescale.com> | 2015-02-11 05:44:45 +0100 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-03-18 16:19:08 +0100 |
commit | 1f874edcb7318c5dd71025df9f3849715b4e4f71 (patch) | |
tree | 740938fad546965bbe8a371fbd1062fc39046af1 /drivers/usb/chipidea | |
parent | usb: chipidea: imx: simplify the usbmisc callers (diff) | |
download | linux-1f874edcb7318c5dd71025df9f3849715b4e4f71.tar.xz linux-1f874edcb7318c5dd71025df9f3849715b4e4f71.zip |
usb: chipidea: add runtime power management support
Add runtime power management support.
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/chipidea')
-rw-r--r-- | drivers/usb/chipidea/ci.h | 6 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 100 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg.c | 2 |
3 files changed, 102 insertions, 6 deletions
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 65913d48f0c8..a0aeb8df9280 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -169,6 +169,9 @@ struct hw_bank { * @b_sess_valid_event: indicates there is a vbus event, and handled * at ci_otg_work * @imx28_write_fix: Freescale imx28 needs swp instruction for writing + * @supports_runtime_pm: if runtime pm is supported + * @in_lpm: if the core in low power mode + * @wakeup_int: if wakeup interrupt occur */ struct ci_hdrc { struct device *dev; @@ -211,6 +214,9 @@ struct ci_hdrc { bool id_event; bool b_sess_valid_event; bool imx28_write_fix; + bool supports_runtime_pm; + bool in_lpm; + bool wakeup_int; }; static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index a57dc8866fc5..63d2b398c9a0 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -491,6 +491,13 @@ static irqreturn_t ci_irq(int irq, void *data) irqreturn_t ret = IRQ_NONE; u32 otgsc = 0; + if (ci->in_lpm) { + disable_irq_nosync(irq); + ci->wakeup_int = true; + pm_runtime_get(ci->dev); + return IRQ_HANDLED; + } + if (ci->is_otg) { otgsc = hw_read_otgsc(ci, ~0); if (ci_otg_is_fsm_mode(ci)) { @@ -673,6 +680,8 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci->platdata = dev_get_platdata(dev); ci->imx28_write_fix = !!(ci->platdata->flags & CI_HDRC_IMX28_WRITE_FIX); + ci->supports_runtime_pm = !!(ci->platdata->flags & + CI_HDRC_SUPPORTS_RUNTIME_PM); ret = hw_device_init(ci, base); if (ret < 0) { @@ -788,6 +797,14 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; + if (ci->supports_runtime_pm) { + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); + pm_runtime_mark_last_busy(ci->dev); + pm_runtime_use_autosuspend(&pdev->dev); + } + if (ci_otg_is_fsm_mode(ci)) ci_hdrc_otg_fsm_start(ci); @@ -807,6 +824,12 @@ static int ci_hdrc_remove(struct platform_device *pdev) { struct ci_hdrc *ci = platform_get_drvdata(pdev); + if (ci->supports_runtime_pm) { + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + } + dbg_remove_files(ci); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); @@ -815,13 +838,14 @@ static int ci_hdrc_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM_SLEEP +#ifdef CONFIG_PM static void ci_controller_suspend(struct ci_hdrc *ci) { + disable_irq(ci->irq); ci_hdrc_enter_lpm(ci, true); - - if (ci->usb_phy) - usb_phy_set_suspend(ci->usb_phy, 1); + usb_phy_set_suspend(ci->usb_phy, 1); + ci->in_lpm = true; + enable_irq(ci->irq); } static int ci_controller_resume(struct device *dev) @@ -830,23 +854,49 @@ static int ci_controller_resume(struct device *dev) dev_dbg(dev, "at %s\n", __func__); - ci_hdrc_enter_lpm(ci, false); + if (!ci->in_lpm) { + WARN_ON(1); + return 0; + } + ci_hdrc_enter_lpm(ci, false); if (ci->usb_phy) { usb_phy_set_suspend(ci->usb_phy, 0); usb_phy_set_wakeup(ci->usb_phy, false); hw_wait_phy_stable(); } + ci->in_lpm = false; + if (ci->wakeup_int) { + ci->wakeup_int = false; + pm_runtime_mark_last_busy(ci->dev); + pm_runtime_put_autosuspend(ci->dev); + enable_irq(ci->irq); + } + return 0; } +#ifdef CONFIG_PM_SLEEP static int ci_suspend(struct device *dev) { struct ci_hdrc *ci = dev_get_drvdata(dev); if (ci->wq) flush_workqueue(ci->wq); + /* + * Controller needs to be active during suspend, otherwise the core + * may run resume when the parent is at suspend if other driver's + * suspend fails, it occurs before parent's suspend has not started, + * but the core suspend has finished. + */ + if (ci->in_lpm) + pm_runtime_resume(dev); + + if (ci->in_lpm) { + WARN_ON(1); + return 0; + } ci_controller_suspend(ci); @@ -855,13 +905,51 @@ static int ci_suspend(struct device *dev) static int ci_resume(struct device *dev) { - return ci_controller_resume(dev); + struct ci_hdrc *ci = dev_get_drvdata(dev); + int ret; + + ret = ci_controller_resume(dev); + if (ret) + return ret; + + if (ci->supports_runtime_pm) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + return ret; } #endif /* CONFIG_PM_SLEEP */ +static int ci_runtime_suspend(struct device *dev) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + dev_dbg(dev, "at %s\n", __func__); + + if (ci->in_lpm) { + WARN_ON(1); + return 0; + } + + usb_phy_set_wakeup(ci->usb_phy, true); + ci_controller_suspend(ci); + + return 0; +} + +static int ci_runtime_resume(struct device *dev) +{ + return ci_controller_resume(dev); +} + +#endif /* CONFIG_PM */ static const struct dev_pm_ops ci_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ci_suspend, ci_resume) + SET_RUNTIME_PM_OPS(ci_runtime_suspend, ci_runtime_resume, NULL) }; + static struct platform_driver ci_hdrc_driver = { .probe = ci_hdrc_probe, .remove = ci_hdrc_remove, diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index a048b08b9d4d..ad6c87a4653c 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -96,6 +96,7 @@ static void ci_otg_work(struct work_struct *work) return; } + pm_runtime_get_sync(ci->dev); if (ci->id_event) { ci->id_event = false; ci_handle_id_switch(ci); @@ -104,6 +105,7 @@ static void ci_otg_work(struct work_struct *work) ci_handle_vbus_change(ci); } else dev_err(ci->dev, "unexpected event occurs at %s\n", __func__); + pm_runtime_put_sync(ci->dev); enable_irq(ci->irq); } |