aboutsummaryrefslogtreecommitdiff
path: root/drivers/bus/mhi/ep/sm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/bus/mhi/ep/sm.c')
-rw-r--r--drivers/bus/mhi/ep/sm.c436
1 files changed, 436 insertions, 0 deletions
diff --git a/drivers/bus/mhi/ep/sm.c b/drivers/bus/mhi/ep/sm.c
new file mode 100644
index 000000000000..5d7b5d84a26e
--- /dev/null
+++ b/drivers/bus/mhi/ep/sm.c
@@ -0,0 +1,436 @@
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/workqueue.h>
+#include <linux/device.h>
+#include <linux/mhi_ep.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+
+#include "internal.h"
+
+static const char *mhi_sm_dev_event_str(enum mhi_ep_event_type state)
+{
+ const char *str;
+
+ switch (state) {
+ case MHI_EP_EVENT_CTRL_TRIG:
+ str = "MHI_EP_EVENT_CTRL_TRIG";
+ break;
+ case MHI_EP_EVENT_M0_STATE:
+ str = "MHI_EP_EVENT_M0_STATE";
+ break;
+ case MHI_EP_EVENT_M1_STATE:
+ str = "MHI_EP_EVENT_M1_STATE";
+ break;
+ case MHI_EP_EVENT_M2_STATE:
+ str = "MHI_EP_EVENT_M2_STATE";
+ break;
+ case MHI_EP_EVENT_M3_STATE:
+ str = "MHI_EP_EVENT_M3_STATE";
+ break;
+ case MHI_EP_EVENT_HW_ACC_WAKEUP:
+ str = "MHI_EP_EVENT_HW_ACC_WAKEUP";
+ break;
+ case MHI_EP_EVENT_CORE_WAKEUP:
+ str = "MHI_EP_EVENT_CORE_WAKEUP";
+ break;
+ default:
+ str = "INVALID MHI_EP_EVENT";
+ }
+
+ return str;
+}
+
+static const char *mhi_sm_mstate_str(enum mhi_ep_state state)
+{
+ const char *str;
+
+ switch (state) {
+ case MHI_EP_RESET_STATE:
+ str = "RESET";
+ break;
+ case MHI_EP_READY_STATE:
+ str = "READY";
+ break;
+ case MHI_EP_M0_STATE:
+ str = "M0";
+ break;
+ case MHI_EP_M1_STATE:
+ str = "M1";
+ break;
+ case MHI_EP_M2_STATE:
+ str = "M2";
+ break;
+ case MHI_EP_M3_STATE:
+ str = "M3";
+ break;
+ case MHI_EP_SYSERR_STATE:
+ str = "SYSTEM ERROR";
+ break;
+ default:
+ str = "INVALID";
+ break;
+ }
+
+ return str;
+}
+
+static const char *mhi_sm_dstate_str(enum mhi_ep_pcie_state state)
+{
+ const char *str;
+
+ switch (state) {
+ case MHI_EP_PCIE_LINK_DISABLE:
+ str = "EP_PCIE_LINK_DISABLE";
+ break;
+ case MHI_EP_PCIE_D0_STATE:
+ str = "D0_STATE";
+ break;
+ case MHI_EP_PCIE_D3_HOT_STATE:
+ str = "D3_HOT_STATE";
+ break;
+ case MHI_EP_PCIE_D3_COLD_STATE:
+ str = "D3_COLD_STATE";
+ break;
+ default:
+ str = "INVALID D-STATE";
+ break;
+ }
+
+ return str;
+}
+
+static inline const char *mhi_sm_pcie_event_str(enum mhi_ep_pcie_event event)
+{
+ const char *str;
+
+ switch (event) {
+ case MHI_EP_PCIE_EVENT_LINKDOWN:
+ str = "EP_PCIE_LINKDOWN_EVENT";
+ break;
+ case MHI_EP_PCIE_EVENT_LINKUP:
+ str = "EP_PCIE_LINKUP_EVENT";
+ break;
+ case MHI_EP_PCIE_EVENT_PM_D3_HOT:
+ str = "EP_PCIE_PM_D3_HOT_EVENT";
+ break;
+ case MHI_EP_PCIE_EVENT_PM_D3_COLD:
+ str = "EP_PCIE_PM_D3_COLD_EVENT";
+ break;
+ case MHI_EP_PCIE_EVENT_PM_RST_DEAST:
+ str = "EP_PCIE_PM_RST_DEAST_EVENT";
+ break;
+ case MHI_EP_PCIE_EVENT_PM_D0:
+ str = "EP_PCIE_PM_D0_EVENT";
+ break;
+ case MHI_EP_PCIE_EVENT_MHI_A7:
+ str = "EP_PCIE_MHI_A7";
+ break;
+ default:
+ str = "INVALID_PCIE_EVENT";
+ break;
+ }
+
+ return str;
+}
+
+static void mhi_ep_sm_mmio_set_status(struct mhi_ep_cntrl *mhi_cntrl,
+ enum mhi_ep_state state)
+{
+ struct mhi_ep_sm *sm = mhi_cntrl->sm;
+
+ switch (state) {
+ case MHI_EP_READY_STATE:
+ dev_dbg(&mhi_cntrl->mhi_dev->dev, "set MHISTATUS to READY mode\n");
+ mhi_ep_mmio_masked_write(mhi_cntrl, MHISTATUS,
+ MHISTATUS_READY_MASK,
+ MHISTATUS_READY_SHIFT, 1);
+
+ mhi_ep_mmio_masked_write(mhi_cntrl, MHISTATUS,
+ MHISTATUS_MHISTATE_MASK,
+ MHISTATUS_MHISTATE_SHIFT, state);
+ break;
+ case MHI_EP_SYSERR_STATE:
+ dev_dbg(&mhi_cntrl->mhi_dev->dev, "set MHISTATUS to SYSTEM ERROR mode\n");
+ mhi_ep_mmio_masked_write(mhi_cntrl, MHISTATUS,
+ MHISTATUS_SYSERR_MASK,
+ MHISTATUS_SYSERR_SHIFT, 1);
+
+ mhi_ep_mmio_masked_write(mhi_cntrl, MHISTATUS,
+ MHISTATUS_MHISTATE_MASK,
+ MHISTATUS_MHISTATE_SHIFT, state);
+ break;
+ case MHI_EP_M1_STATE:
+ case MHI_EP_M2_STATE:
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Not supported state, can't set MHISTATUS to %s\n",
+ mhi_sm_mstate_str(state));
+ return;
+ case MHI_EP_M0_STATE:
+ case MHI_EP_M3_STATE:
+ dev_dbg(&mhi_cntrl->mhi_dev->dev, "set MHISTATUS.MHISTATE to %s state\n",
+ mhi_sm_mstate_str(state));
+ mhi_ep_mmio_masked_write(mhi_cntrl, MHISTATUS,
+ MHISTATUS_MHISTATE_MASK,
+ MHISTATUS_MHISTATE_SHIFT, state);
+ break;
+ default:
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Invalid mhi state: 0x%x state", state);
+ return;
+ }
+
+ sm->state = state;
+}
+
+/**
+ * mhi_sm_is_legal_event_on_state() - Determine if MHI state transition is valid
+ * @curr_state: current MHI state
+ * @event: MHI state change event
+ *
+ * Determine according to MHI state management if the state change event
+ * is valid on the current mhi state.
+ * Note: The decision doesn't take into account M1 and M2 states.
+ *
+ * Return: true: transition is valid
+ * false: transition is not valid
+ */
+static bool mhi_sm_is_legal_event_on_state(struct mhi_ep_sm *sm,
+ enum mhi_ep_state curr_state,
+ enum mhi_ep_event_type event)
+{
+ struct mhi_ep_cntrl *mhi_cntrl = sm->mhi_cntrl;
+ bool res;
+
+ switch (event) {
+ case MHI_EP_EVENT_M0_STATE:
+ res = (sm->d_state == MHI_EP_PCIE_D0_STATE &&
+ curr_state != MHI_EP_RESET_STATE);
+ break;
+ case MHI_EP_EVENT_M3_STATE:
+ case MHI_EP_EVENT_HW_ACC_WAKEUP:
+ case MHI_EP_EVENT_CORE_WAKEUP:
+ res = (curr_state == MHI_EP_M3_STATE ||
+ curr_state == MHI_EP_M0_STATE);
+ break;
+ default:
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Received invalid event: %s\n",
+ mhi_sm_dev_event_str(event));
+ res = false;
+ break;
+ }
+
+ return res;
+}
+
+/**
+ * mhi_sm_change_to_M0() - switch to M0 state.
+ *
+ * Switch MHI-device state to M0, if possible according to MHI state machine.
+ * Notify the MHI-host on the transition. If MHI is suspended, resume MHI.
+ *
+ * Return: 0: success
+ * negative: failure
+ */
+static int mhi_sm_change_to_M0(struct mhi_ep_cntrl *mhi_cntrl)
+{
+ struct mhi_ep_sm *sm = mhi_cntrl->sm;
+ enum mhi_ep_state old_state;
+ int ret;
+
+ old_state = sm->state;
+
+ switch (old_state) {
+ case MHI_EP_M0_STATE:
+ dev_dbg(&mhi_cntrl->mhi_dev->dev, "Nothing to do, already in M0 state\n");
+ return 0;
+ case MHI_EP_M3_STATE:
+ case MHI_EP_READY_STATE:
+ break;
+ default:
+ dev_err(&mhi_cntrl->mhi_dev->dev, "unexpected old_state: %s\n",
+ mhi_sm_mstate_str(old_state));
+ return -EINVAL;
+ }
+
+ mhi_ep_sm_mmio_set_status(mhi_cntrl, MHI_EP_M0_STATE);
+
+ /* Tell the host, device move to M0 */
+ if (old_state == MHI_EP_M3_STATE) {
+ /* TODO: Resume MHI */
+#if 0
+ res = mhi_ep_resume(sm);
+ if (res) {
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Failed resuming mhi core, returned %d",
+ res);
+ goto exit;
+ }
+#endif
+ }
+
+ ret = mhi_ep_send_state_change_event(mhi_cntrl, MHI_EP_M0_STATE);
+ if (ret) {
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Failed sending M0 state change event to host: %d\n", ret);
+ return ret;
+ }
+
+ if (old_state == MHI_EP_READY_STATE) {
+ /* Allow the host to process state change event */
+ mdelay(1);
+
+ /* Tell the host the EE */
+ ret = mhi_ep_send_ee_event(mhi_cntrl, 2);
+ if (ret) {
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Failed sending EE event to host: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void mhi_ep_sm_handle_event(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_ep_event_type event)
+{
+ struct mhi_ep_sm *sm = mhi_cntrl->sm;
+ int ret;
+
+ mutex_lock(&sm->lock);
+ dev_dbg(&mhi_cntrl->mhi_dev->dev, "Start handling %s event, current states: %s & %s\n",
+ mhi_sm_dev_event_str(event),
+ mhi_sm_mstate_str(sm->state),
+ mhi_sm_dstate_str(sm->d_state));
+
+ /* TODO: Check for syserr before handling the event */
+
+ if (!mhi_sm_is_legal_event_on_state(sm, sm->state, event)) {
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Event %s illegal in current MHI states: %s and %s\n",
+ mhi_sm_dev_event_str(event),
+ mhi_sm_mstate_str(sm->state),
+ mhi_sm_dstate_str(sm->d_state));
+ /* TODO: Transition to syserr */
+ goto unlock_and_exit;
+ }
+
+ switch (event) {
+ case MHI_EP_EVENT_M0_STATE:
+ ret = mhi_sm_change_to_M0(mhi_cntrl);
+ if (ret)
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Failed switching to M0 state\n");
+ break;
+ case MHI_EP_EVENT_M3_STATE:
+// ret = mhi_sm_change_to_M3();
+ if (ret)
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Failed switching to M3 state\n");
+// mhi_ep_pm_relax();
+ break;
+ case MHI_EP_EVENT_HW_ACC_WAKEUP:
+ case MHI_EP_EVENT_CORE_WAKEUP:
+// ret = mhi_sm_wakeup_host(event);
+ if (ret)
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Failed to wakeup MHI host\n");
+ break;
+ case MHI_EP_EVENT_CTRL_TRIG:
+ case MHI_EP_EVENT_M1_STATE:
+ case MHI_EP_EVENT_M2_STATE:
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Error: %s event is not supported\n",
+ mhi_sm_dev_event_str(event));
+ break;
+ default:
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Error: Invalid event, 0x%x", event);
+ break;
+ }
+
+unlock_and_exit:
+ mutex_unlock(&sm->lock);
+}
+
+int mhi_ep_notify_sm_event(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_ep_event_type event)
+{
+ int ret;
+
+ switch (event) {
+ case MHI_EP_EVENT_M0_STATE:
+// sm->stats.m0_event_cnt++;
+ break;
+ case MHI_EP_EVENT_M3_STATE:
+// sm->stats.m3_event_cnt++;
+ break;
+ case MHI_EP_EVENT_HW_ACC_WAKEUP:
+// sm->stats.hw_acc_wakeup_event_cnt++;
+ break;
+ case MHI_EP_EVENT_CORE_WAKEUP:
+// sm->stats.mhi_core_wakeup_event_cnt++;
+ break;
+ case MHI_EP_EVENT_CTRL_TRIG:
+ case MHI_EP_EVENT_M1_STATE:
+ case MHI_EP_EVENT_M2_STATE:
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Received unsupported event: %s\n",
+ mhi_sm_dev_event_str(event));
+ return -ENOTSUPP;
+ goto exit;
+ default:
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Received invalid event: %d\n", event);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Change to wq is possible */
+ mhi_ep_sm_handle_event(mhi_cntrl, event);
+
+ return 0;
+
+exit:
+ return ret;
+}
+EXPORT_SYMBOL(mhi_ep_notify_sm_event);
+
+int mhi_ep_sm_set_ready(struct mhi_ep_cntrl *mhi_cntrl)
+{
+ struct mhi_ep_sm *sm = mhi_cntrl->sm;
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ enum mhi_ep_state state;
+ int is_ready;
+
+ mutex_lock(&sm->lock);
+
+ /* Ensure that the MHISTATUS is set to RESET by host */
+ mhi_ep_mmio_masked_read(mhi_cntrl, MHISTATUS, MHISTATUS_MHISTATE_MASK,
+ MHISTATUS_MHISTATE_SHIFT, &state);
+ mhi_ep_mmio_masked_read(mhi_cntrl, MHISTATUS, MHISTATUS_READY_MASK,
+ MHISTATUS_READY_SHIFT, &is_ready);
+
+ if (state != MHI_EP_RESET_STATE || is_ready) {
+ dev_err(dev, "READY transition failed. MHI host not in RESET state\n");
+ mutex_unlock(&sm->lock);
+ return -EFAULT;
+ }
+
+ mhi_ep_sm_mmio_set_status(mhi_cntrl, MHI_EP_READY_STATE);
+
+ mutex_unlock(&sm->lock);
+ return 0;
+}
+EXPORT_SYMBOL(mhi_ep_sm_set_ready);
+
+int mhi_ep_sm_init(struct mhi_ep_cntrl *mhi_cntrl)
+{
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ struct mhi_ep_sm *sm;
+
+ sm = devm_kzalloc(dev, sizeof(*mhi_cntrl->sm), GFP_KERNEL);
+ if (!sm)
+ return -ENOMEM;
+
+ sm->sm_wq = alloc_workqueue("mhi_ep_sm_wq", WQ_HIGHPRI | WQ_UNBOUND, 1);
+ if (!sm->sm_wq) {
+ dev_err(dev, "Failed to create SM workqueue\n");
+ return -ENOMEM;
+ }
+
+ sm->mhi_cntrl = mhi_cntrl;
+ sm->state = MHI_EP_RESET_STATE;
+ sm->d_state = MHI_EP_PCIE_D0_STATE;
+ mutex_init(&sm->lock);
+ mhi_cntrl->sm = sm;
+
+ return 0;
+}
+EXPORT_SYMBOL(mhi_ep_sm_init);