usb: chipidea: add runtime power management support
authorPeter Chen <peter.chen@freescale.com>
Wed, 11 Feb 2015 04:44:45 +0000 (12:44 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 18 Mar 2015 15:19:08 +0000 (16:19 +0100)
Add runtime power management support.

Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/chipidea/ci.h
drivers/usb/chipidea/core.c
drivers/usb/chipidea/otg.c
include/linux/usb/chipidea.h

index 65913d48f0c80538cb4153aeac05ee6142750191..a0aeb8df9280fb5aada3fa07512ff37352677ea4 100644 (file)
@@ -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)
index a57dc8866fc5ff938641686f7945916074723fe8..63d2b398c9a0f0097db74975dedc49f26851d19d 100644 (file)
@@ -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,
index a048b08b9d4dbb1fff35dbc73da7366c8a78f48d..ad6c87a4653c2e4331eeb3b48787f525687e0c8a 100644 (file)
@@ -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);
 }
index 535997a6681b90318e37e9fcabc5e4ecae22109b..39ba00f0d1d53216d22dce7ae386c7c1a55eb620 100644 (file)
@@ -19,6 +19,7 @@ struct ci_hdrc_platform_data {
        enum usb_phy_interface phy_mode;
        unsigned long    flags;
 #define CI_HDRC_REGS_SHARED            BIT(0)
+#define CI_HDRC_SUPPORTS_RUNTIME_PM    BIT(2)
 #define CI_HDRC_DISABLE_STREAMING      BIT(3)
        /*
         * Only set it when DCCPARAMS.DC==1 and DCCPARAMS.HC==1,