| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2022 Linaro Ltd. |
| * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> |
| */ |
| |
| #include <linux/errno.h> |
| #include <linux/mhi_ep.h> |
| #include "internal.h" |
| |
| bool __must_check mhi_ep_check_mhi_state(struct mhi_ep_cntrl *mhi_cntrl, |
| enum mhi_state cur_mhi_state, |
| enum mhi_state mhi_state) |
| { |
| if (mhi_state == MHI_STATE_SYS_ERR) |
| return true; /* Allowed in any state */ |
| |
| if (mhi_state == MHI_STATE_READY) |
| return cur_mhi_state == MHI_STATE_RESET; |
| |
| if (mhi_state == MHI_STATE_M0) |
| return cur_mhi_state == MHI_STATE_M3 || cur_mhi_state == MHI_STATE_READY; |
| |
| if (mhi_state == MHI_STATE_M3) |
| return cur_mhi_state == MHI_STATE_M0; |
| |
| return false; |
| } |
| |
| int mhi_ep_set_mhi_state(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_state mhi_state) |
| { |
| struct device *dev = &mhi_cntrl->mhi_dev->dev; |
| |
| if (!mhi_ep_check_mhi_state(mhi_cntrl, mhi_cntrl->mhi_state, mhi_state)) { |
| dev_err(dev, "MHI state change to %s from %s is not allowed!\n", |
| mhi_state_str(mhi_state), |
| mhi_state_str(mhi_cntrl->mhi_state)); |
| return -EACCES; |
| } |
| |
| /* TODO: Add support for M1 and M2 states */ |
| if (mhi_state == MHI_STATE_M1 || mhi_state == MHI_STATE_M2) { |
| dev_err(dev, "MHI state (%s) not supported\n", mhi_state_str(mhi_state)); |
| return -EOPNOTSUPP; |
| } |
| |
| mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHISTATUS, MHISTATUS_MHISTATE_MASK, mhi_state); |
| mhi_cntrl->mhi_state = mhi_state; |
| |
| if (mhi_state == MHI_STATE_READY) |
| mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHISTATUS, MHISTATUS_READY_MASK, 1); |
| |
| if (mhi_state == MHI_STATE_SYS_ERR) |
| mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHISTATUS, MHISTATUS_SYSERR_MASK, 1); |
| |
| return 0; |
| } |
| |
| int mhi_ep_set_m0_state(struct mhi_ep_cntrl *mhi_cntrl) |
| { |
| struct device *dev = &mhi_cntrl->mhi_dev->dev; |
| enum mhi_state old_state; |
| int ret; |
| |
| /* If MHI is in M3, resume suspended channels */ |
| spin_lock_bh(&mhi_cntrl->state_lock); |
| old_state = mhi_cntrl->mhi_state; |
| if (old_state == MHI_STATE_M3) |
| mhi_ep_resume_channels(mhi_cntrl); |
| |
| ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_M0); |
| spin_unlock_bh(&mhi_cntrl->state_lock); |
| |
| if (ret) { |
| mhi_ep_handle_syserr(mhi_cntrl); |
| return ret; |
| } |
| |
| /* Signal host that the device moved to M0 */ |
| ret = mhi_ep_send_state_change_event(mhi_cntrl, MHI_STATE_M0); |
| if (ret) { |
| dev_err(dev, "Failed sending M0 state change event\n"); |
| return ret; |
| } |
| |
| if (old_state == MHI_STATE_READY) { |
| /* Send AMSS EE event to host */ |
| ret = mhi_ep_send_ee_event(mhi_cntrl, MHI_EE_AMSS); |
| if (ret) { |
| dev_err(dev, "Failed sending AMSS EE event\n"); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int mhi_ep_set_m3_state(struct mhi_ep_cntrl *mhi_cntrl) |
| { |
| struct device *dev = &mhi_cntrl->mhi_dev->dev; |
| int ret; |
| |
| spin_lock_bh(&mhi_cntrl->state_lock); |
| ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_M3); |
| spin_unlock_bh(&mhi_cntrl->state_lock); |
| |
| if (ret) { |
| mhi_ep_handle_syserr(mhi_cntrl); |
| return ret; |
| } |
| |
| mhi_ep_suspend_channels(mhi_cntrl); |
| |
| /* Signal host that the device moved to M3 */ |
| ret = mhi_ep_send_state_change_event(mhi_cntrl, MHI_STATE_M3); |
| if (ret) { |
| dev_err(dev, "Failed sending M3 state change event\n"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int mhi_ep_set_ready_state(struct mhi_ep_cntrl *mhi_cntrl) |
| { |
| struct device *dev = &mhi_cntrl->mhi_dev->dev; |
| enum mhi_state mhi_state; |
| int ret, is_ready; |
| |
| spin_lock_bh(&mhi_cntrl->state_lock); |
| /* Ensure that the MHISTATUS is set to RESET by host */ |
| mhi_state = mhi_ep_mmio_masked_read(mhi_cntrl, EP_MHISTATUS, MHISTATUS_MHISTATE_MASK); |
| is_ready = mhi_ep_mmio_masked_read(mhi_cntrl, EP_MHISTATUS, MHISTATUS_READY_MASK); |
| |
| if (mhi_state != MHI_STATE_RESET || is_ready) { |
| dev_err(dev, "READY state transition failed. MHI host not in RESET state\n"); |
| spin_unlock_bh(&mhi_cntrl->state_lock); |
| return -EIO; |
| } |
| |
| ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_READY); |
| spin_unlock_bh(&mhi_cntrl->state_lock); |
| |
| if (ret) |
| mhi_ep_handle_syserr(mhi_cntrl); |
| |
| return ret; |
| } |