// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2022 Linaro Ltd. * Author: Manivannan Sadhasivam */ #include #include #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; }