Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3ae6c8b3 authored by Sujeev Dias's avatar Sujeev Dias Committed by Gerrit - the friendly Code Review server
Browse files

mhi: core: add support for silent suspend and resume



For low power mode use case, we allows apps subsystem to go into
idle power collapse while device in active state. This change
silently transitions MHI host into suspended state without
suspending device.

CRs-Fixed: 2418347
Change-Id: I23b4c9a981ad39f4a25422ccd569d1181bc2a58e
Signed-off-by: default avatarSujeev Dias <sdias@codeaurora.org>
parent 52d7fb1d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ const char * const mhi_state_str[MHI_STATE_MAX] = {
	[MHI_STATE_M1] = "M1",
	[MHI_STATE_M2] = "M2",
	[MHI_STATE_M3] = "M3",
	[MHI_STATE_M3_FAST] = "M3_FAST",
	[MHI_STATE_BHI] = "BHI",
	[MHI_STATE_SYS_ERR] = "SYS_ERR",
};
+11 −3
Original line number Diff line number Diff line
@@ -1359,8 +1359,16 @@ void mhi_ctrl_ev_task(unsigned long data)
	 * pm_state can change from reg access valid to no access while this
	 * therad being executed.
	 */
	if (!MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state))
	if (!MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) {
		/*
		 * we may have a pending event but not allowed to
		 * process it since we probably in a suspended state,
		 * trigger a resume.
		 */
		mhi_cntrl->runtime_get(mhi_cntrl, mhi_cntrl->priv_data);
		mhi_cntrl->runtime_put(mhi_cntrl, mhi_cntrl->priv_data);
		return;
	}

	/* process ctrl events events */
	ret = mhi_event->process_event(mhi_cntrl, mhi_event, U32_MAX);
@@ -1818,12 +1826,12 @@ int mhi_debugfs_mhi_states_show(struct seq_file *m, void *d)
	struct mhi_controller *mhi_cntrl = m->private;

	seq_printf(m,
		   "pm_state:%s dev_state:%s EE:%s M0:%u M2:%u M3:%u wake:%d dev_wake:%u alloc_size:%u pending_pkts:%u\n",
		   "pm_state:%s dev_state:%s EE:%s M0:%u M2:%u M3:%u M3_Fast:%u wake:%d dev_wake:%u alloc_size:%u pending_pkts:%u\n",
		   to_mhi_pm_state_str(mhi_cntrl->pm_state),
		   TO_MHI_STATE_STR(mhi_cntrl->dev_state),
		   TO_MHI_EXEC_STR(mhi_cntrl->ee),
		   mhi_cntrl->M0, mhi_cntrl->M2, mhi_cntrl->M3,
		   mhi_cntrl->wake_set,
		   mhi_cntrl->M3_FAST, mhi_cntrl->wake_set,
		   atomic_read(&mhi_cntrl->dev_wake),
		   atomic_read(&mhi_cntrl->alloc_size),
		   atomic_read(&mhi_cntrl->pending_pkts));
+191 −4
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@
 *     POR -> M0 -> M2 --> M0
 *     POR -> FW_DL_ERR
 *     FW_DL_ERR <--> FW_DL_ERR
 *     M0 <--> M0
 *     M0 -> FW_DL_ERR
 *     M0 -> M3_ENTER -> M3 -> M3_EXIT --> M0
 * L1: SYS_ERR_DETECT -> SYS_ERR_PROCESS --> POR
@@ -51,9 +52,9 @@ static struct mhi_pm_transitions const mhi_state_transitions[] = {
	},
	{
		MHI_PM_M0,
		MHI_PM_M2 | MHI_PM_M3_ENTER | MHI_PM_SYS_ERR_DETECT |
		MHI_PM_SHUTDOWN_PROCESS | MHI_PM_LD_ERR_FATAL_DETECT |
		MHI_PM_FW_DL_ERR
		MHI_PM_M0 | MHI_PM_M2 | MHI_PM_M3_ENTER |
		MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS |
		MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_FW_DL_ERR
	},
	{
		MHI_PM_M2,
@@ -319,7 +320,7 @@ int mhi_pm_m0_transition(struct mhi_controller *mhi_cntrl)
	}
	mhi_cntrl->M0++;
	read_lock_bh(&mhi_cntrl->pm_lock);
	mhi_cntrl->wake_get(mhi_cntrl, false);
	mhi_cntrl->wake_get(mhi_cntrl, true);

	/* ring all event rings and CMD ring only if we're in mission mode */
	if (MHI_IN_MISSION_MODE(mhi_cntrl->ee)) {
@@ -1023,6 +1024,114 @@ int mhi_pm_suspend(struct mhi_controller *mhi_cntrl)
}
EXPORT_SYMBOL(mhi_pm_suspend);

/**
 * mhi_pm_fast_suspend - Faster suspend path where we transition host to
 * inactive state w/o suspending device.  Useful for cases where we want apps to
 * go into power collapse but keep the physical link in active state.
 */
int mhi_pm_fast_suspend(struct mhi_controller *mhi_cntrl, bool notify_client)
{
	int ret;
	enum MHI_PM_STATE new_state;
	struct mhi_chan *itr, *tmp;

	if (mhi_cntrl->pm_state == MHI_PM_DISABLE)
		return -EINVAL;

	if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))
		return -EIO;

	/* do a quick check to see if any pending votes to keep us busy */
	if (atomic_read(&mhi_cntrl->pending_pkts)) {
		MHI_VERB("Busy, aborting M3\n");
		return -EBUSY;
	}

	/* disable ctrl event processing */
	tasklet_disable(&mhi_cntrl->mhi_event->task);

	write_lock_irq(&mhi_cntrl->pm_lock);

	/*
	 * Check the votes once more to see if we should abort
	 * suspend.
	 */
	if (atomic_read(&mhi_cntrl->pending_pkts)) {
		MHI_VERB("Busy, aborting M3\n");
		ret = -EBUSY;
		goto error_suspend;
	}

	/* anytime after this, we will resume thru runtime pm framework */
	MHI_LOG("Allowing Fast M3 transition\n");

	/* save the current states */
	mhi_cntrl->saved_pm_state = mhi_cntrl->pm_state;
	mhi_cntrl->saved_dev_state = mhi_cntrl->dev_state;

	/* If we're in M2, we need to switch back to M0 first */
	if (mhi_cntrl->pm_state == MHI_PM_M2) {
		new_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M0);
		if (new_state != MHI_PM_M0) {
			MHI_ERR("Error set pm_state to:%s from pm_state:%s\n",
				to_mhi_pm_state_str(MHI_PM_M0),
				to_mhi_pm_state_str(mhi_cntrl->pm_state));
			ret = -EIO;
			goto error_suspend;
		}
	}

	new_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3_ENTER);
	if (new_state != MHI_PM_M3_ENTER) {
		MHI_ERR("Error setting to pm_state:%s from pm_state:%s\n",
			to_mhi_pm_state_str(MHI_PM_M3_ENTER),
			to_mhi_pm_state_str(mhi_cntrl->pm_state));
		ret = -EIO;
		goto error_suspend;
	}

	/* set dev to M3_FAST and host to M3 */
	new_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3);
	if (new_state != MHI_PM_M3) {
		MHI_ERR("Error setting to pm_state:%s from pm_state:%s\n",
			to_mhi_pm_state_str(MHI_PM_M3),
			to_mhi_pm_state_str(mhi_cntrl->pm_state));
		ret = -EIO;
		goto error_suspend;
	}

	mhi_cntrl->dev_state = MHI_STATE_M3_FAST;
	mhi_cntrl->M3_FAST++;
	write_unlock_irq(&mhi_cntrl->pm_lock);

	/* now safe to check ctrl event ring */
	tasklet_enable(&mhi_cntrl->mhi_event->task);
	mhi_msi_handlr(0, mhi_cntrl->mhi_event);

	if (!notify_client)
		return 0;

	/* notify any clients we enter 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;

error_suspend:
	write_unlock_irq(&mhi_cntrl->pm_lock);

	/* check ctrl event ring for pending work */
	tasklet_enable(&mhi_cntrl->mhi_event->task);
	mhi_msi_handlr(0, mhi_cntrl->mhi_event);

	return ret;
}
EXPORT_SYMBOL(mhi_pm_fast_suspend);

int mhi_pm_resume(struct mhi_controller *mhi_cntrl)
{
	enum MHI_PM_STATE cur_state;
@@ -1089,6 +1198,80 @@ int mhi_pm_resume(struct mhi_controller *mhi_cntrl)
	return 0;
}

int mhi_pm_fast_resume(struct mhi_controller *mhi_cntrl, bool notify_client)
{
	struct mhi_chan *itr, *tmp;
	struct mhi_event *mhi_event;
	int i;

	MHI_LOG("Entered with pm_state:%s dev_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;

	MHI_ASSERT(mhi_cntrl->pm_state != MHI_PM_M3, "mhi_pm_state != M3");

	/* notify any clients we're about to exit lpm */
	if (notify_client) {
		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);
	/* restore the states */
	mhi_cntrl->pm_state = mhi_cntrl->saved_pm_state;
	mhi_cntrl->dev_state = mhi_cntrl->saved_dev_state;
	write_unlock_irq(&mhi_cntrl->pm_lock);

	switch (mhi_cntrl->pm_state) {
	case MHI_PM_M0:
		mhi_pm_m0_transition(mhi_cntrl);
	case MHI_PM_M2:
		read_lock_bh(&mhi_cntrl->pm_lock);
		/*
		 * we're doing a double check of pm_state because by the time we
		 * grab the pm_lock, device may have already initiate a M0 on
		 * its own. If that's the case we should not be toggling device
		 * wake.
		 */
		if (mhi_cntrl->pm_state == MHI_PM_M2) {
			mhi_cntrl->wake_get(mhi_cntrl, true);
			mhi_cntrl->wake_put(mhi_cntrl, true);
		}
		read_unlock_bh(&mhi_cntrl->pm_lock);
	}

	/*
	 * In fast suspend/resume case device is not aware host transition
	 * to suspend state. So, device could be triggering a interrupt while
	 * host not accepting MSI. We have to manually check each event ring
	 * upon resume.
	 */
	mhi_event = mhi_cntrl->mhi_event;
	for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
		if (mhi_event->offload_ev)
			continue;

		mhi_msi_handlr(0, mhi_event);
	}

	MHI_LOG("Exit with pm_state:%s dev_state:%s\n",
		to_mhi_pm_state_str(mhi_cntrl->pm_state),
		TO_MHI_STATE_STR(mhi_cntrl->dev_state));

	return 0;
}

int __mhi_device_get_sync(struct mhi_controller *mhi_cntrl)
{
	int ret;
@@ -1175,6 +1358,10 @@ void mhi_device_put(struct mhi_device *mhi_dev, int vote)
	if (vote & MHI_VOTE_DEVICE) {
		atomic_dec(&mhi_dev->dev_vote);
		read_lock_bh(&mhi_cntrl->pm_lock);
		if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state)) {
			mhi_cntrl->runtime_get(mhi_cntrl, mhi_cntrl->priv_data);
			mhi_cntrl->runtime_put(mhi_cntrl, mhi_cntrl->priv_data);
		}
		mhi_cntrl->wake_put(mhi_cntrl, false);
		read_unlock_bh(&mhi_cntrl->pm_lock);
	}
+20 −2
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ enum mhi_dev_state {
	MHI_STATE_M1 = 0x3,
	MHI_STATE_M2 = 0x4,
	MHI_STATE_M3 = 0x5,
	MHI_STATE_M3_FAST = 0x6,
	MHI_STATE_BHI  = 0x7,
	MHI_STATE_SYS_ERR  = 0xFF,
	MHI_STATE_MAX,
@@ -233,9 +234,11 @@ struct mhi_controller {
	bool pre_init;
	rwlock_t pm_lock;
	u32 pm_state;
	u32 saved_pm_state; /* saved state during fast suspend */
	u32 db_access; /* db access only on these states */
	enum mhi_ee ee;
	enum mhi_dev_state dev_state;
	enum mhi_dev_state saved_dev_state;
	bool wake_set;
	atomic_t dev_wake;
	atomic_t alloc_size;
@@ -245,7 +248,7 @@ struct mhi_controller {
	spinlock_t wlock;

	/* debug counters */
	u32 M0, M2, M3;
	u32 M0, M2, M3, M3_FAST;

	/* worker for different state transitions */
	struct work_struct st_worker;
@@ -593,6 +596,14 @@ void mhi_unprepare_after_power_down(struct mhi_controller *mhi_cntrl);
 */
int mhi_pm_suspend(struct mhi_controller *mhi_cntrl);

/**
 * mhi_pm_fast_suspend - Move host into suspend state while keeping
 * the device in active state.
 * @mhi_cntrl: MHI controller
 * @notify_client: if true, clients will get a notification about lpm transition
 */
int mhi_pm_fast_suspend(struct mhi_controller *mhi_cntrl, bool notify_client);

/**
 * mhi_pm_resume - Resume MHI from suspended state
 * Transition to MHI state M0 state from M3 state
@@ -600,6 +611,13 @@ int mhi_pm_suspend(struct mhi_controller *mhi_cntrl);
 */
int mhi_pm_resume(struct mhi_controller *mhi_cntrl);

/**
 * mhi_pm_fast_resume - Move host into resume state from fast suspend state
 * @mhi_cntrl: MHI controller
 * @notify_client: if true, clients will get a notification about lpm transition
 */
int mhi_pm_fast_resume(struct mhi_controller *mhi_cntrl, bool notify_client);

/**
 * mhi_download_rddm_img - Download ramdump image from device for
 * debugging purpose.
@@ -651,7 +669,7 @@ static inline bool mhi_is_active(struct mhi_device *mhi_dev)
	struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;

	return (mhi_cntrl->dev_state >= MHI_STATE_M0 &&
		mhi_cntrl->dev_state <= MHI_STATE_M3);
		mhi_cntrl->dev_state <= MHI_STATE_M3_FAST);
}

/**