Commit 0c6b20a1 authored by Manivannan Sadhasivam's avatar Manivannan Sadhasivam Committed by Greg Kroah-Hartman

bus: mhi: core: Add support for MHI suspend and resume

Add support for MHI suspend and resume states. While at it, the
mhi_notify() function needs to be exported as well.
Signed-off-by: default avatarManivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Link: https://lore.kernel.org/r/20200427075829.9304-2-manivannan.sadhasivam@linaro.orgSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 48a4cc95
...@@ -267,7 +267,7 @@ int mhi_destroy_device(struct device *dev, void *data) ...@@ -267,7 +267,7 @@ int mhi_destroy_device(struct device *dev, void *data)
return 0; 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; struct mhi_driver *mhi_drv;
...@@ -279,6 +279,7 @@ static void mhi_notify(struct mhi_device *mhi_dev, enum mhi_callback cb_reason) ...@@ -279,6 +279,7 @@ static void mhi_notify(struct mhi_device *mhi_dev, enum mhi_callback cb_reason)
if (mhi_drv->status_cb) if (mhi_drv->status_cb)
mhi_drv->status_cb(mhi_dev, cb_reason); mhi_drv->status_cb(mhi_dev, cb_reason);
} }
EXPORT_SYMBOL_GPL(mhi_notify);
/* Bind MHI channels to MHI devices */ /* Bind MHI channels to MHI devices */
void mhi_create_devices(struct mhi_controller *mhi_cntrl) void mhi_create_devices(struct mhi_controller *mhi_cntrl)
......
...@@ -669,6 +669,149 @@ void mhi_pm_st_worker(struct work_struct *work) ...@@ -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 __mhi_device_get_sync(struct mhi_controller *mhi_cntrl)
{ {
int ret; int ret;
......
...@@ -568,6 +568,13 @@ void mhi_driver_unregister(struct mhi_driver *mhi_drv); ...@@ -568,6 +568,13 @@ void mhi_driver_unregister(struct mhi_driver *mhi_drv);
void mhi_set_mhi_state(struct mhi_controller *mhi_cntrl, void mhi_set_mhi_state(struct mhi_controller *mhi_cntrl,
enum mhi_state state); 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. * mhi_prepare_for_power_up - Do pre-initialization before power up.
* This is optional, call this before power up if * This is optional, call this before power up if
...@@ -604,6 +611,18 @@ void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful); ...@@ -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); 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 * mhi_download_rddm_img - Download ramdump image from device for
* debugging purpose. * debugging purpose.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment