bus: mhi: core: Add support for MHI suspend and resume
authorManivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Mon, 27 Apr 2020 07:58:27 +0000 (13:28 +0530)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 28 Apr 2020 12:42:32 +0000 (14:42 +0200)
Add support for MHI suspend and resume states. While at it, the
mhi_notify() function needs to be exported as well.

Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Link: https://lore.kernel.org/r/20200427075829.9304-2-manivannan.sadhasivam@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/bus/mhi/core/main.c
drivers/bus/mhi/core/pm.c
include/linux/mhi.h

index eb4256b81406a2e9144926effb5eabcb3f9e6ee9..3e9aa3b2da77277bb030891ebe403f26bf0a1bc6 100644 (file)
@@ -267,7 +267,7 @@ int mhi_destroy_device(struct device *dev, void *data)
        return 0;
 }
 
-static void mhi_notify(struct mhi_device *mhi_dev, enum mhi_callback cb_reason)
+void mhi_notify(struct mhi_device *mhi_dev, enum mhi_callback cb_reason)
 {
        struct mhi_driver *mhi_drv;
 
@@ -279,6 +279,7 @@ static void mhi_notify(struct mhi_device *mhi_dev, enum mhi_callback cb_reason)
        if (mhi_drv->status_cb)
                mhi_drv->status_cb(mhi_dev, cb_reason);
 }
+EXPORT_SYMBOL_GPL(mhi_notify);
 
 /* Bind MHI channels to MHI devices */
 void mhi_create_devices(struct mhi_controller *mhi_cntrl)
index 52690cb5c89cb5a4602494d04aed8513e4dfb1d4..3529419d076bce8fecd04eaa1aea9a3c139e5d37 100644 (file)
@@ -669,6 +669,149 @@ void mhi_pm_st_worker(struct work_struct *work)
        }
 }
 
+int mhi_pm_suspend(struct mhi_controller *mhi_cntrl)
+{
+       struct mhi_chan *itr, *tmp;
+       struct device *dev = &mhi_cntrl->mhi_dev->dev;
+       enum mhi_pm_state new_state;
+       int ret;
+
+       if (mhi_cntrl->pm_state == MHI_PM_DISABLE)
+               return -EINVAL;
+
+       if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))
+               return -EIO;
+
+       /* Return busy if there are any pending resources */
+       if (atomic_read(&mhi_cntrl->dev_wake))
+               return -EBUSY;
+
+       /* Take MHI out of M2 state */
+       read_lock_bh(&mhi_cntrl->pm_lock);
+       mhi_cntrl->wake_get(mhi_cntrl, false);
+       read_unlock_bh(&mhi_cntrl->pm_lock);
+
+       ret = wait_event_timeout(mhi_cntrl->state_event,
+                                mhi_cntrl->dev_state == MHI_STATE_M0 ||
+                                mhi_cntrl->dev_state == MHI_STATE_M1 ||
+                                MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
+                                msecs_to_jiffies(mhi_cntrl->timeout_ms));
+
+       read_lock_bh(&mhi_cntrl->pm_lock);
+       mhi_cntrl->wake_put(mhi_cntrl, false);
+       read_unlock_bh(&mhi_cntrl->pm_lock);
+
+       if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
+               dev_err(dev,
+                       "Could not enter M0/M1 state");
+               return -EIO;
+       }
+
+       write_lock_irq(&mhi_cntrl->pm_lock);
+
+       if (atomic_read(&mhi_cntrl->dev_wake)) {
+               write_unlock_irq(&mhi_cntrl->pm_lock);
+               return -EBUSY;
+       }
+
+       dev_info(dev, "Allowing M3 transition\n");
+       new_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3_ENTER);
+       if (new_state != MHI_PM_M3_ENTER) {
+               write_unlock_irq(&mhi_cntrl->pm_lock);
+               dev_err(dev,
+                       "Error setting to PM state: %s from: %s\n",
+                       to_mhi_pm_state_str(MHI_PM_M3_ENTER),
+                       to_mhi_pm_state_str(mhi_cntrl->pm_state));
+               return -EIO;
+       }
+
+       /* Set MHI to M3 and wait for completion */
+       mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M3);
+       write_unlock_irq(&mhi_cntrl->pm_lock);
+       dev_info(dev, "Wait for M3 completion\n");
+
+       ret = wait_event_timeout(mhi_cntrl->state_event,
+                                mhi_cntrl->dev_state == MHI_STATE_M3 ||
+                                MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
+                                msecs_to_jiffies(mhi_cntrl->timeout_ms));
+
+       if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
+               dev_err(dev,
+                       "Did not enter M3 state, MHI state: %s, PM state: %s\n",
+                       TO_MHI_STATE_STR(mhi_cntrl->dev_state),
+                       to_mhi_pm_state_str(mhi_cntrl->pm_state));
+               return -EIO;
+       }
+
+       /* Notify clients about entering LPM */
+       list_for_each_entry_safe(itr, tmp, &mhi_cntrl->lpm_chans, node) {
+               mutex_lock(&itr->mutex);
+               if (itr->mhi_dev)
+                       mhi_notify(itr->mhi_dev, MHI_CB_LPM_ENTER);
+               mutex_unlock(&itr->mutex);
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(mhi_pm_suspend);
+
+int mhi_pm_resume(struct mhi_controller *mhi_cntrl)
+{
+       struct mhi_chan *itr, *tmp;
+       struct device *dev = &mhi_cntrl->mhi_dev->dev;
+       enum mhi_pm_state cur_state;
+       int ret;
+
+       dev_info(dev, "Entered with PM state: %s, MHI state: %s\n",
+                to_mhi_pm_state_str(mhi_cntrl->pm_state),
+                TO_MHI_STATE_STR(mhi_cntrl->dev_state));
+
+       if (mhi_cntrl->pm_state == MHI_PM_DISABLE)
+               return 0;
+
+       if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))
+               return -EIO;
+
+       /* Notify clients about exiting LPM */
+       list_for_each_entry_safe(itr, tmp, &mhi_cntrl->lpm_chans, node) {
+               mutex_lock(&itr->mutex);
+               if (itr->mhi_dev)
+                       mhi_notify(itr->mhi_dev, MHI_CB_LPM_EXIT);
+               mutex_unlock(&itr->mutex);
+       }
+
+       write_lock_irq(&mhi_cntrl->pm_lock);
+       cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3_EXIT);
+       if (cur_state != MHI_PM_M3_EXIT) {
+               write_unlock_irq(&mhi_cntrl->pm_lock);
+               dev_info(dev,
+                        "Error setting to PM state: %s from: %s\n",
+                        to_mhi_pm_state_str(MHI_PM_M3_EXIT),
+                        to_mhi_pm_state_str(mhi_cntrl->pm_state));
+               return -EIO;
+       }
+
+       /* Set MHI to M0 and wait for completion */
+       mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M0);
+       write_unlock_irq(&mhi_cntrl->pm_lock);
+
+       ret = wait_event_timeout(mhi_cntrl->state_event,
+                                mhi_cntrl->dev_state == MHI_STATE_M0 ||
+                                MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
+                                msecs_to_jiffies(mhi_cntrl->timeout_ms));
+
+       if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
+               dev_err(dev,
+                       "Did not enter M0 state, MHI state: %s, PM state: %s\n",
+                       TO_MHI_STATE_STR(mhi_cntrl->dev_state),
+                       to_mhi_pm_state_str(mhi_cntrl->pm_state));
+               return -EIO;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(mhi_pm_resume);
+
 int __mhi_device_get_sync(struct mhi_controller *mhi_cntrl)
 {
        int ret;
index ad19960019653fceb12a220f291483246586f3f2..a4288f4d656f399608e52aacba757d6ffed97fa4 100644 (file)
@@ -568,6 +568,13 @@ void mhi_driver_unregister(struct mhi_driver *mhi_drv);
 void mhi_set_mhi_state(struct mhi_controller *mhi_cntrl,
                       enum mhi_state state);
 
+/**
+ * mhi_notify - Notify the MHI client driver about client device status
+ * @mhi_dev: MHI device instance
+ * @cb_reason: MHI callback reason
+ */
+void mhi_notify(struct mhi_device *mhi_dev, enum mhi_callback cb_reason);
+
 /**
  * mhi_prepare_for_power_up - Do pre-initialization before power up.
  *                            This is optional, call this before power up if
@@ -604,6 +611,18 @@ void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful);
  */
 void mhi_unprepare_after_power_down(struct mhi_controller *mhi_cntrl);
 
+/**
+ * mhi_pm_suspend - Move MHI into a suspended state
+ * @mhi_cntrl: MHI controller
+ */
+int mhi_pm_suspend(struct mhi_controller *mhi_cntrl);
+
+/**
+ * mhi_pm_resume - Resume MHI from suspended state
+ * @mhi_cntrl: MHI controller
+ */
+int mhi_pm_resume(struct mhi_controller *mhi_cntrl);
+
 /**
  * mhi_download_rddm_img - Download ramdump image from device for
  *                         debugging purpose.