1320 lines
33 KiB
C
1320 lines
33 KiB
C
|
/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/export.h>
|
||
|
#include <linux/workqueue.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/ipa.h>
|
||
|
#include "mhi_hwio.h"
|
||
|
#include "mhi_sm.h"
|
||
|
|
||
|
#define MHI_SM_DBG(fmt, args...) \
|
||
|
mhi_log(MHI_MSG_DBG, fmt, ##args)
|
||
|
|
||
|
#define MHI_SM_ERR(fmt, args...) \
|
||
|
mhi_log(MHI_MSG_ERROR, fmt, ##args)
|
||
|
|
||
|
#define MHI_SM_FUNC_ENTRY() MHI_SM_DBG("ENTRY\n")
|
||
|
#define MHI_SM_FUNC_EXIT() MHI_SM_DBG("EXIT\n")
|
||
|
|
||
|
|
||
|
static inline const char *mhi_sm_dev_event_str(enum mhi_dev_event state)
|
||
|
{
|
||
|
const char *str;
|
||
|
|
||
|
switch (state) {
|
||
|
case MHI_DEV_EVENT_CTRL_TRIG:
|
||
|
str = "MHI_DEV_EVENT_CTRL_TRIG";
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_M0_STATE:
|
||
|
str = "MHI_DEV_EVENT_M0_STATE";
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_M1_STATE:
|
||
|
str = "MHI_DEV_EVENT_M1_STATE";
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_M2_STATE:
|
||
|
str = "MHI_DEV_EVENT_M2_STATE";
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_M3_STATE:
|
||
|
str = "MHI_DEV_EVENT_M3_STATE";
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_HW_ACC_WAKEUP:
|
||
|
str = "MHI_DEV_EVENT_HW_ACC_WAKEUP";
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_CORE_WAKEUP:
|
||
|
str = "MHI_DEV_EVENT_CORE_WAKEUP";
|
||
|
break;
|
||
|
default:
|
||
|
str = "INVALID MHI_DEV_EVENT";
|
||
|
}
|
||
|
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
static inline const char *mhi_sm_mstate_str(enum mhi_dev_state state)
|
||
|
{
|
||
|
const char *str;
|
||
|
|
||
|
switch (state) {
|
||
|
case MHI_DEV_RESET_STATE:
|
||
|
str = "RESET";
|
||
|
break;
|
||
|
case MHI_DEV_READY_STATE:
|
||
|
str = "READY";
|
||
|
break;
|
||
|
case MHI_DEV_M0_STATE:
|
||
|
str = "M0";
|
||
|
break;
|
||
|
case MHI_DEV_M1_STATE:
|
||
|
str = "M1";
|
||
|
break;
|
||
|
case MHI_DEV_M2_STATE:
|
||
|
str = "M2";
|
||
|
break;
|
||
|
case MHI_DEV_M3_STATE:
|
||
|
str = "M3";
|
||
|
break;
|
||
|
case MHI_DEV_SYSERR_STATE:
|
||
|
str = "SYSTEM ERROR";
|
||
|
break;
|
||
|
default:
|
||
|
str = "INVALID";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return str;
|
||
|
}
|
||
|
enum mhi_sm_ep_pcie_state {
|
||
|
MHI_SM_EP_PCIE_LINK_DISABLE,
|
||
|
MHI_SM_EP_PCIE_D0_STATE,
|
||
|
MHI_SM_EP_PCIE_D3_HOT_STATE,
|
||
|
MHI_SM_EP_PCIE_D3_COLD_STATE,
|
||
|
};
|
||
|
|
||
|
static inline const char *mhi_sm_dstate_str(enum mhi_sm_ep_pcie_state state)
|
||
|
{
|
||
|
const char *str;
|
||
|
|
||
|
switch (state) {
|
||
|
case MHI_SM_EP_PCIE_LINK_DISABLE:
|
||
|
str = "EP_PCIE_LINK_DISABLE";
|
||
|
break;
|
||
|
case MHI_SM_EP_PCIE_D0_STATE:
|
||
|
str = "D0_STATE";
|
||
|
break;
|
||
|
case MHI_SM_EP_PCIE_D3_HOT_STATE:
|
||
|
str = "D3_HOT_STATE";
|
||
|
break;
|
||
|
case MHI_SM_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 ep_pcie_event event)
|
||
|
{
|
||
|
const char *str;
|
||
|
|
||
|
switch (event) {
|
||
|
case EP_PCIE_EVENT_LINKDOWN:
|
||
|
str = "EP_PCIE_LINKDOWN_EVENT";
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_LINKUP:
|
||
|
str = "EP_PCIE_LINKUP_EVENT";
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D3_HOT:
|
||
|
str = "EP_PCIE_PM_D3_HOT_EVENT";
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D3_COLD:
|
||
|
str = "EP_PCIE_PM_D3_COLD_EVENT";
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_RST_DEAST:
|
||
|
str = "EP_PCIE_PM_RST_DEAST_EVENT";
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D0:
|
||
|
str = "EP_PCIE_PM_D0_EVENT";
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_MHI_A7:
|
||
|
str = "EP_PCIE_MHI_A7";
|
||
|
break;
|
||
|
default:
|
||
|
str = "INVALID_PCIE_EVENT";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* struct mhi_sm_device_event - mhi-core event work
|
||
|
* @event: mhi core state change event
|
||
|
* @work: work struct
|
||
|
*
|
||
|
* used to add work for mhi state change event to mhi_sm_wq
|
||
|
*/
|
||
|
struct mhi_sm_device_event {
|
||
|
enum mhi_dev_event event;
|
||
|
struct work_struct work;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* struct mhi_sm_ep_pcie_event - ep-pcie event work
|
||
|
* @event: ep-pcie link state change event
|
||
|
* @work: work struct
|
||
|
*
|
||
|
* used to add work for ep-pcie link state change event to mhi_sm_wq
|
||
|
*/
|
||
|
struct mhi_sm_ep_pcie_event {
|
||
|
enum ep_pcie_event event;
|
||
|
struct work_struct work;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* struct mhi_sm_stats - MHI state machine statistics, viewable using debugfs
|
||
|
* @m0_event_cnt: total number of MHI_DEV_EVENT_M0_STATE events
|
||
|
* @m3_event_cnt: total number of MHI_DEV_EVENT_M3_STATE events
|
||
|
* @hw_acc_wakeup_event_cnt: total number of MHI_DEV_EVENT_HW_ACC_WAKEUP events
|
||
|
* @mhi_core_wakeup_event_cnt: total number of MHI_DEV_EVENT_CORE_WAKEUP events
|
||
|
* @linkup_event_cnt: total number of EP_PCIE_EVENT_LINKUP events
|
||
|
* @rst_deast_event_cnt: total number of EP_PCIE_EVENT_PM_RST_DEAST events
|
||
|
* @d3_hot_event_cnt: total number of EP_PCIE_EVENT_PM_D3_HOT events
|
||
|
* @d3_cold_event_cnt: total number of EP_PCIE_EVENT_PM_D3_COLD events
|
||
|
* @d0_event_cnt: total number of EP_PCIE_EVENT_PM_D0 events
|
||
|
* @linkdown_event_cnt: total number of EP_PCIE_EVENT_LINKDOWN events
|
||
|
*/
|
||
|
struct mhi_sm_stats {
|
||
|
int m0_event_cnt;
|
||
|
int m3_event_cnt;
|
||
|
int hw_acc_wakeup_event_cnt;
|
||
|
int mhi_core_wakeup_event_cnt;
|
||
|
int linkup_event_cnt;
|
||
|
int rst_deast_event_cnt;
|
||
|
int d3_hot_event_cnt;
|
||
|
int d3_cold_event_cnt;
|
||
|
int d0_event_cnt;
|
||
|
int linkdown_event_cnt;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* struct mhi_sm_dev - MHI state manager context information
|
||
|
* @mhi_state: MHI M state of the MHI device
|
||
|
* @d_state: EP-PCIe D state of the MHI device
|
||
|
* @mhi_dev: MHI device struct pointer
|
||
|
* @mhi_state_lock: mutex for mhi_state
|
||
|
* @syserr_occurred:flag to indicate if a syserr condition has occurred.
|
||
|
* @mhi_sm_wq: workqueue for state change events
|
||
|
* @pending_device_events: number of pending mhi state change events in sm_wq
|
||
|
* @pending_pcie_events: number of pending mhi state change events in sm_wq
|
||
|
* @stats: stats on the handled and pending events
|
||
|
*/
|
||
|
struct mhi_sm_dev {
|
||
|
enum mhi_dev_state mhi_state;
|
||
|
enum mhi_sm_ep_pcie_state d_state;
|
||
|
struct mhi_dev *mhi_dev;
|
||
|
struct mutex mhi_state_lock;
|
||
|
bool syserr_occurred;
|
||
|
struct workqueue_struct *mhi_sm_wq;
|
||
|
atomic_t pending_device_events;
|
||
|
atomic_t pending_pcie_events;
|
||
|
struct mhi_sm_stats stats;
|
||
|
};
|
||
|
static struct mhi_sm_dev *mhi_sm_ctx;
|
||
|
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
#define MHI_SM_MAX_MSG_LEN 1024
|
||
|
static char dbg_buff[MHI_SM_MAX_MSG_LEN];
|
||
|
static struct dentry *dent;
|
||
|
static struct dentry *dfile_stats;
|
||
|
|
||
|
static ssize_t mhi_sm_debugfs_read(struct file *file, char __user *ubuf,
|
||
|
size_t count, loff_t *ppos);
|
||
|
static ssize_t mhi_sm_debugfs_write(struct file *file,
|
||
|
const char __user *ubuf, size_t count, loff_t *ppos);
|
||
|
|
||
|
const struct file_operations mhi_sm_stats_ops = {
|
||
|
.read = mhi_sm_debugfs_read,
|
||
|
.write = mhi_sm_debugfs_write,
|
||
|
};
|
||
|
|
||
|
static void mhi_sm_debugfs_init(void)
|
||
|
{
|
||
|
const mode_t read_write_mode = S_IRUSR | S_IRGRP | S_IROTH |
|
||
|
S_IWUSR | S_IWGRP | S_IWOTH;
|
||
|
|
||
|
dent = debugfs_create_dir("mhi_sm", 0);
|
||
|
if (IS_ERR(dent)) {
|
||
|
MHI_SM_ERR("fail to create folder mhi_sm\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
dfile_stats =
|
||
|
debugfs_create_file("stats", read_write_mode, dent,
|
||
|
0, &mhi_sm_stats_ops);
|
||
|
if (!dfile_stats || IS_ERR(dfile_stats)) {
|
||
|
MHI_SM_ERR("fail to create file stats\n");
|
||
|
goto fail;
|
||
|
}
|
||
|
return;
|
||
|
fail:
|
||
|
debugfs_remove_recursive(dent);
|
||
|
}
|
||
|
|
||
|
static void mhi_sm_debugfs_destroy(void)
|
||
|
{
|
||
|
debugfs_remove_recursive(dent);
|
||
|
}
|
||
|
#else
|
||
|
static inline void mhi_sm_debugfs_init(void) {}
|
||
|
static inline void mhi_sm_debugfs_destroy(void) {}
|
||
|
#endif /*CONFIG_DEBUG_FS*/
|
||
|
|
||
|
|
||
|
static void mhi_sm_mmio_set_mhistatus(enum mhi_dev_state state)
|
||
|
{
|
||
|
struct mhi_dev *dev = mhi_sm_ctx->mhi_dev;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
switch (state) {
|
||
|
case MHI_DEV_READY_STATE:
|
||
|
MHI_SM_DBG("set MHISTATUS to READY mode\n");
|
||
|
mhi_dev_mmio_masked_write(dev, MHISTATUS,
|
||
|
MHISTATUS_READY_MASK,
|
||
|
MHISTATUS_READY_SHIFT, 1);
|
||
|
|
||
|
mhi_dev_mmio_masked_write(dev, MHISTATUS,
|
||
|
MHISTATUS_MHISTATE_MASK,
|
||
|
MHISTATUS_MHISTATE_SHIFT, state);
|
||
|
break;
|
||
|
case MHI_DEV_SYSERR_STATE:
|
||
|
MHI_SM_DBG("set MHISTATUS to SYSTEM ERROR mode\n");
|
||
|
mhi_dev_mmio_masked_write(dev, MHISTATUS,
|
||
|
MHISTATUS_SYSERR_MASK,
|
||
|
MHISTATUS_SYSERR_SHIFT, 1);
|
||
|
|
||
|
mhi_dev_mmio_masked_write(dev, MHISTATUS,
|
||
|
MHISTATUS_MHISTATE_MASK,
|
||
|
MHISTATUS_MHISTATE_SHIFT, state);
|
||
|
break;
|
||
|
case MHI_DEV_M1_STATE:
|
||
|
case MHI_DEV_M2_STATE:
|
||
|
MHI_SM_ERR("Not supported state, can't set MHISTATUS to %s\n",
|
||
|
mhi_sm_mstate_str(state));
|
||
|
goto exit;
|
||
|
case MHI_DEV_M0_STATE:
|
||
|
case MHI_DEV_M3_STATE:
|
||
|
MHI_SM_DBG("set MHISTATUS.MHISTATE to %s state\n",
|
||
|
mhi_sm_mstate_str(state));
|
||
|
mhi_dev_mmio_masked_write(dev, MHISTATUS,
|
||
|
MHISTATUS_MHISTATE_MASK,
|
||
|
MHISTATUS_MHISTATE_SHIFT, state);
|
||
|
break;
|
||
|
default:
|
||
|
MHI_SM_ERR("Invalid mhi state: 0x%x state", state);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
mhi_sm_ctx->mhi_state = state;
|
||
|
|
||
|
exit:
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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(enum mhi_dev_state curr_state,
|
||
|
enum mhi_dev_event event)
|
||
|
{
|
||
|
bool res;
|
||
|
|
||
|
switch (event) {
|
||
|
case MHI_DEV_EVENT_M0_STATE:
|
||
|
res = (mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_D0_STATE &&
|
||
|
curr_state != MHI_DEV_RESET_STATE);
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_M3_STATE:
|
||
|
case MHI_DEV_EVENT_HW_ACC_WAKEUP:
|
||
|
case MHI_DEV_EVENT_CORE_WAKEUP:
|
||
|
res = (curr_state == MHI_DEV_M3_STATE ||
|
||
|
curr_state == MHI_DEV_M0_STATE);
|
||
|
break;
|
||
|
default:
|
||
|
MHI_SM_ERR("Received invalid event: %s\n",
|
||
|
mhi_sm_dev_event_str(event));
|
||
|
res = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* mhi_sm_is_legal_pcie_event_on_state() - Determine if EP-PCIe linke state
|
||
|
* transition is valid on the current system state.
|
||
|
* @curr_mstate: current MHI state
|
||
|
* @curr_dstate: current ep-pcie link, d, state
|
||
|
* @event: ep-pcie link state change event
|
||
|
*
|
||
|
* Return: true: transition is valid
|
||
|
* false: transition is not valid
|
||
|
*/
|
||
|
static bool mhi_sm_is_legal_pcie_event_on_state(enum mhi_dev_state curr_mstate,
|
||
|
enum mhi_sm_ep_pcie_state curr_dstate, enum ep_pcie_event event)
|
||
|
{
|
||
|
bool res;
|
||
|
|
||
|
switch (event) {
|
||
|
case EP_PCIE_EVENT_LINKUP:
|
||
|
case EP_PCIE_EVENT_LINKDOWN:
|
||
|
res = true;
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D3_HOT:
|
||
|
res = (curr_mstate == MHI_DEV_M3_STATE &&
|
||
|
curr_dstate != MHI_SM_EP_PCIE_LINK_DISABLE);
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D3_COLD:
|
||
|
res = (curr_dstate == MHI_SM_EP_PCIE_D3_HOT_STATE ||
|
||
|
curr_dstate == MHI_SM_EP_PCIE_D3_COLD_STATE);
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_RST_DEAST:
|
||
|
res = (curr_dstate == MHI_SM_EP_PCIE_D0_STATE ||
|
||
|
curr_dstate == MHI_SM_EP_PCIE_D3_COLD_STATE);
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D0:
|
||
|
res = (curr_dstate == MHI_SM_EP_PCIE_D0_STATE ||
|
||
|
curr_dstate == MHI_SM_EP_PCIE_D3_HOT_STATE);
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_MHI_A7:
|
||
|
res = true;
|
||
|
break;
|
||
|
default:
|
||
|
MHI_SM_ERR("Invalid ep_pcie event, received: %s\n",
|
||
|
mhi_sm_pcie_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, in case MHI is suspended- resume MHI.
|
||
|
*
|
||
|
* Return: 0: success
|
||
|
* negative: failure
|
||
|
*/
|
||
|
static int mhi_sm_change_to_M0(void)
|
||
|
{
|
||
|
enum mhi_dev_state old_state;
|
||
|
struct ep_pcie_msi_config cfg;
|
||
|
int res;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
old_state = mhi_sm_ctx->mhi_state;
|
||
|
|
||
|
if (old_state == MHI_DEV_M0_STATE) {
|
||
|
MHI_SM_DBG("Nothing to do, already in M0 state\n");
|
||
|
res = 0;
|
||
|
goto exit;
|
||
|
} else if (old_state == MHI_DEV_M3_STATE ||
|
||
|
old_state == MHI_DEV_READY_STATE) {
|
||
|
/* Retrieve MHI configuration*/
|
||
|
res = mhi_dev_config_outbound_iatu(mhi_sm_ctx->mhi_dev);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Fail to configure iATU, returned %d\n",
|
||
|
res);
|
||
|
goto exit;
|
||
|
}
|
||
|
res = ep_pcie_get_msi_config(mhi_sm_ctx->mhi_dev->phandle,
|
||
|
&cfg);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Error retrieving pcie msi logic\n");
|
||
|
goto exit;
|
||
|
}
|
||
|
res = mhi_pcie_config_db_routing(mhi_sm_ctx->mhi_dev);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Error configuring db routing\n");
|
||
|
goto exit;
|
||
|
|
||
|
}
|
||
|
} else {
|
||
|
MHI_SM_ERR("unexpected old_state: %s\n",
|
||
|
mhi_sm_mstate_str(old_state));
|
||
|
goto exit;
|
||
|
}
|
||
|
mhi_sm_mmio_set_mhistatus(MHI_DEV_M0_STATE);
|
||
|
|
||
|
/* Tell the host, device move to M0 */
|
||
|
res = mhi_dev_send_state_change_event(mhi_sm_ctx->mhi_dev,
|
||
|
MHI_DEV_M0_STATE);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed to send event %s to host, returned %d\n",
|
||
|
mhi_sm_dev_event_str(MHI_DEV_EVENT_M0_STATE), res);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
if (old_state == MHI_DEV_READY_STATE) {
|
||
|
/* Tell the host the EE */
|
||
|
res = mhi_dev_send_ee_event(mhi_sm_ctx->mhi_dev, 2);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("failed sending EE event to host\n");
|
||
|
goto exit;
|
||
|
}
|
||
|
} else if (old_state == MHI_DEV_M3_STATE) {
|
||
|
/*Resuming MHI operation*/
|
||
|
res = mhi_dev_resume(mhi_sm_ctx->mhi_dev);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed resuming mhi core, returned %d",
|
||
|
res);
|
||
|
goto exit;
|
||
|
}
|
||
|
res = ipa_mhi_resume();
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed resuming ipa_mhi, returned %d",
|
||
|
res);
|
||
|
goto exit;
|
||
|
}
|
||
|
}
|
||
|
res = 0;
|
||
|
|
||
|
exit:
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* mhi_sm_change_to_M3() - switch to M3 state
|
||
|
*
|
||
|
* Switch MHI-device state to M3, if possible according to MHI state machine.
|
||
|
* Suspend MHI traffic and notify the host on the transition.
|
||
|
*
|
||
|
* Return: 0: success
|
||
|
* negative: failure
|
||
|
*/
|
||
|
static int mhi_sm_change_to_M3(void)
|
||
|
{
|
||
|
enum mhi_dev_state old_state;
|
||
|
int res = 0;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
old_state = mhi_sm_ctx->mhi_state;
|
||
|
if (old_state == MHI_DEV_M3_STATE) {
|
||
|
MHI_SM_DBG("Nothing to do, already in M3 state\n");
|
||
|
res = 0;
|
||
|
goto exit;
|
||
|
}
|
||
|
/* Suspending MHI operation*/
|
||
|
res = mhi_dev_suspend(mhi_sm_ctx->mhi_dev);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed to suspend mhi_core, returned %d\n", res);
|
||
|
goto exit;
|
||
|
}
|
||
|
res = ipa_mhi_suspend(true);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed to suspend ipa_mhi, returned %d\n", res);
|
||
|
goto exit;
|
||
|
}
|
||
|
mhi_sm_mmio_set_mhistatus(MHI_DEV_M3_STATE);
|
||
|
|
||
|
/* tell the host, device move to M3 */
|
||
|
res = mhi_dev_send_state_change_event(mhi_sm_ctx->mhi_dev,
|
||
|
MHI_DEV_M3_STATE);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed sendind event: %s to mhi_host\n",
|
||
|
mhi_sm_dev_event_str(MHI_DEV_EVENT_M3_STATE));
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* mhi_sm_wakeup_host() - wakeup MHI-host
|
||
|
*@event: MHI state chenge event
|
||
|
*
|
||
|
* Sends wekup event to MHI-host via EP-PCIe, in case MHI is in M3 state.
|
||
|
*
|
||
|
* Return: 0:success
|
||
|
* negative: failure
|
||
|
*/
|
||
|
static int mhi_sm_wakeup_host(enum mhi_dev_event event)
|
||
|
{
|
||
|
int res = 0;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
if (mhi_sm_ctx->mhi_state == MHI_DEV_M3_STATE) {
|
||
|
/*
|
||
|
* ep_pcie driver is responsible to send the right wakeup
|
||
|
* event, assert WAKE#, according to Link state
|
||
|
*/
|
||
|
res = ep_pcie_wakeup_host(mhi_sm_ctx->mhi_dev->phandle);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed to wakeup MHI host, returned %d\n",
|
||
|
res);
|
||
|
goto exit;
|
||
|
}
|
||
|
} else {
|
||
|
MHI_SM_DBG("Nothing to do, Host is already awake\n");
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* mhi_sm_handle_syserr() - switch to system error state.
|
||
|
*
|
||
|
* Called on system error condition.
|
||
|
* Switch MHI to SYSERR state, notify MHI-host and ASSERT on the device.
|
||
|
* Synchronic function.
|
||
|
*
|
||
|
* Return: 0: success
|
||
|
* negative: failure
|
||
|
*/
|
||
|
static int mhi_sm_handle_syserr(void)
|
||
|
{
|
||
|
int res;
|
||
|
enum ep_pcie_link_status link_status;
|
||
|
bool link_enabled = false;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
MHI_SM_ERR("Start handling SYSERR, MHI state: %s and %s",
|
||
|
mhi_sm_mstate_str(mhi_sm_ctx->mhi_state),
|
||
|
mhi_sm_dstate_str(mhi_sm_ctx->d_state));
|
||
|
|
||
|
if (mhi_sm_ctx->mhi_state == MHI_DEV_SYSERR_STATE) {
|
||
|
MHI_SM_DBG("Nothing to do, already in SYSERR state\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
mhi_sm_ctx->syserr_occurred = true;
|
||
|
link_status = ep_pcie_get_linkstatus(mhi_sm_ctx->mhi_dev->phandle);
|
||
|
if (link_status == EP_PCIE_LINK_DISABLED) {
|
||
|
/* try to power on ep-pcie, restore mmio, and wakup host */
|
||
|
res = ep_pcie_enable_endpoint(mhi_sm_ctx->mhi_dev->phandle,
|
||
|
EP_PCIE_OPT_POWER_ON);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed to power on ep-pcie, returned %d\n",
|
||
|
res);
|
||
|
goto exit;
|
||
|
}
|
||
|
mhi_dev_restore_mmio(mhi_sm_ctx->mhi_dev);
|
||
|
res = ep_pcie_enable_endpoint(mhi_sm_ctx->mhi_dev->phandle,
|
||
|
EP_PCIE_OPT_AST_WAKE | EP_PCIE_OPT_ENUM);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed to wakup host and enable ep-pcie\n");
|
||
|
goto exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
link_enabled = true;
|
||
|
mhi_sm_mmio_set_mhistatus(MHI_DEV_SYSERR_STATE);
|
||
|
|
||
|
/* Tell the host, device move to SYSERR state */
|
||
|
res = mhi_dev_send_state_change_event(mhi_sm_ctx->mhi_dev,
|
||
|
MHI_DEV_SYSERR_STATE);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed to send %s state change event to host\n",
|
||
|
mhi_sm_mstate_str(MHI_DEV_SYSERR_STATE));
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
if (!link_enabled)
|
||
|
MHI_SM_ERR("EP-PCIE Link is disable cannot set MMIO to %s\n",
|
||
|
mhi_sm_mstate_str(MHI_DEV_SYSERR_STATE));
|
||
|
|
||
|
MHI_SM_ERR("/n/n/nASSERT ON DEVICE !!!!/n/n/n");
|
||
|
BUG();
|
||
|
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* mhi_sm_dev_event_manager() - performs MHI state change
|
||
|
* @work: work_struct used by the work queue
|
||
|
*
|
||
|
* This function is called from mhi_sm_wq, and performs mhi state change
|
||
|
* if possible according to MHI state machine
|
||
|
*/
|
||
|
static void mhi_sm_dev_event_manager(struct work_struct *work)
|
||
|
{
|
||
|
int res;
|
||
|
struct mhi_sm_device_event *chg_event = container_of(work,
|
||
|
struct mhi_sm_device_event, work);
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
mutex_lock(&mhi_sm_ctx->mhi_state_lock);
|
||
|
MHI_SM_DBG("Start handling %s event, current states: %s & %s\n",
|
||
|
mhi_sm_dev_event_str(chg_event->event),
|
||
|
mhi_sm_mstate_str(mhi_sm_ctx->mhi_state),
|
||
|
mhi_sm_dstate_str(mhi_sm_ctx->d_state));
|
||
|
|
||
|
if (mhi_sm_ctx->syserr_occurred) {
|
||
|
MHI_SM_DBG("syserr occurred, Ignoring %s\n",
|
||
|
mhi_sm_dev_event_str(chg_event->event));
|
||
|
goto unlock_and_exit;
|
||
|
}
|
||
|
|
||
|
if (!mhi_sm_is_legal_event_on_state(mhi_sm_ctx->mhi_state,
|
||
|
chg_event->event)) {
|
||
|
MHI_SM_ERR("%s: illegal in current MHI state: %s and %s\n",
|
||
|
mhi_sm_dev_event_str(chg_event->event),
|
||
|
mhi_sm_mstate_str(mhi_sm_ctx->mhi_state),
|
||
|
mhi_sm_dstate_str(mhi_sm_ctx->d_state));
|
||
|
res = mhi_sm_handle_syserr();
|
||
|
if (res)
|
||
|
MHI_SM_ERR("Failed switching to SYSERR state\n");
|
||
|
goto unlock_and_exit;
|
||
|
}
|
||
|
|
||
|
switch (chg_event->event) {
|
||
|
case MHI_DEV_EVENT_M0_STATE:
|
||
|
res = mhi_sm_change_to_M0();
|
||
|
if (res)
|
||
|
MHI_SM_ERR("Failed switching to M0 state\n");
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_M3_STATE:
|
||
|
res = mhi_sm_change_to_M3();
|
||
|
if (res)
|
||
|
MHI_SM_ERR("Failed switching to M3 state\n");
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_HW_ACC_WAKEUP:
|
||
|
case MHI_DEV_EVENT_CORE_WAKEUP:
|
||
|
res = mhi_sm_wakeup_host(chg_event->event);
|
||
|
if (res)
|
||
|
MHI_SM_ERR("Failed to wakeup MHI host\n");
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_CTRL_TRIG:
|
||
|
case MHI_DEV_EVENT_M1_STATE:
|
||
|
case MHI_DEV_EVENT_M2_STATE:
|
||
|
MHI_SM_ERR("Error: %s event is not supported\n",
|
||
|
mhi_sm_dev_event_str(chg_event->event));
|
||
|
break;
|
||
|
default:
|
||
|
MHI_SM_ERR("Error: Invalid event, 0x%x", chg_event->event);
|
||
|
break;
|
||
|
}
|
||
|
unlock_and_exit:
|
||
|
mutex_unlock(&mhi_sm_ctx->mhi_state_lock);
|
||
|
atomic_dec(&mhi_sm_ctx->pending_device_events);
|
||
|
kfree(chg_event);
|
||
|
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* mhi_sm_pcie_event_manager() - performs EP-PCIe linke state change
|
||
|
* @work: work_struct used by the work queue
|
||
|
*
|
||
|
* This function is called from mhi_sm_wq, and performs ep-pcie link state
|
||
|
* change if possible according to current system state and MHI state machine
|
||
|
*/
|
||
|
static void mhi_sm_pcie_event_manager(struct work_struct *work)
|
||
|
{
|
||
|
int res;
|
||
|
enum mhi_sm_ep_pcie_state old_dstate;
|
||
|
struct mhi_sm_ep_pcie_event *chg_event = container_of(work,
|
||
|
struct mhi_sm_ep_pcie_event, work);
|
||
|
enum ep_pcie_event pcie_event = chg_event->event;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
mutex_lock(&mhi_sm_ctx->mhi_state_lock);
|
||
|
old_dstate = mhi_sm_ctx->d_state;
|
||
|
|
||
|
MHI_SM_DBG("Start handling %s event, current MHI state %s and %s\n",
|
||
|
mhi_sm_pcie_event_str(chg_event->event),
|
||
|
mhi_sm_mstate_str(mhi_sm_ctx->mhi_state),
|
||
|
mhi_sm_dstate_str(old_dstate));
|
||
|
|
||
|
if (mhi_sm_ctx->syserr_occurred &&
|
||
|
pcie_event != EP_PCIE_EVENT_LINKDOWN) {
|
||
|
MHI_SM_DBG("SYSERR occurred. Ignoring %s",
|
||
|
mhi_sm_pcie_event_str(pcie_event));
|
||
|
goto unlock_and_exit;
|
||
|
}
|
||
|
|
||
|
if (!mhi_sm_is_legal_pcie_event_on_state(mhi_sm_ctx->mhi_state,
|
||
|
old_dstate, pcie_event)) {
|
||
|
MHI_SM_ERR("%s: illegal in current MHI state: %s and %s\n",
|
||
|
mhi_sm_pcie_event_str(pcie_event),
|
||
|
mhi_sm_mstate_str(mhi_sm_ctx->mhi_state),
|
||
|
mhi_sm_dstate_str(old_dstate));
|
||
|
res = mhi_sm_handle_syserr();
|
||
|
if (res)
|
||
|
MHI_SM_ERR("Failed switching to SYSERR state\n");
|
||
|
goto unlock_and_exit;
|
||
|
}
|
||
|
|
||
|
switch (pcie_event) {
|
||
|
case EP_PCIE_EVENT_LINKUP:
|
||
|
if (mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_LINK_DISABLE)
|
||
|
mhi_sm_ctx->d_state = MHI_SM_EP_PCIE_D0_STATE;
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_LINKDOWN:
|
||
|
res = mhi_sm_handle_syserr();
|
||
|
if (res)
|
||
|
MHI_SM_ERR("Failed switching to SYSERR state\n");
|
||
|
goto unlock_and_exit;
|
||
|
case EP_PCIE_EVENT_PM_D3_HOT:
|
||
|
if (old_dstate == MHI_SM_EP_PCIE_D3_HOT_STATE) {
|
||
|
MHI_SM_DBG("cannot move to D3_HOT from D3_COLD\n");
|
||
|
break;
|
||
|
}
|
||
|
/* Backup MMIO is done on the callback function*/
|
||
|
mhi_sm_ctx->d_state = MHI_SM_EP_PCIE_D3_HOT_STATE;
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D3_COLD:
|
||
|
if (old_dstate == MHI_SM_EP_PCIE_D3_COLD_STATE) {
|
||
|
MHI_SM_DBG("Nothing to do, already in D3_COLD state\n");
|
||
|
break;
|
||
|
}
|
||
|
ep_pcie_disable_endpoint(mhi_sm_ctx->mhi_dev->phandle);
|
||
|
mhi_sm_ctx->d_state = MHI_SM_EP_PCIE_D3_COLD_STATE;
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_RST_DEAST:
|
||
|
if (old_dstate == MHI_SM_EP_PCIE_D0_STATE) {
|
||
|
MHI_SM_DBG("Nothing to do, already in D0 state\n");
|
||
|
break;
|
||
|
}
|
||
|
res = ep_pcie_enable_endpoint(mhi_sm_ctx->mhi_dev->phandle,
|
||
|
EP_PCIE_OPT_POWER_ON);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("Failed to power on ep_pcie, returned %d\n",
|
||
|
res);
|
||
|
goto unlock_and_exit;
|
||
|
}
|
||
|
|
||
|
mhi_dev_restore_mmio(mhi_sm_ctx->mhi_dev);
|
||
|
|
||
|
res = ep_pcie_enable_endpoint(mhi_sm_ctx->mhi_dev->phandle,
|
||
|
EP_PCIE_OPT_ENUM);
|
||
|
if (res) {
|
||
|
MHI_SM_ERR("ep-pcie failed to link train, return %d\n",
|
||
|
res);
|
||
|
goto unlock_and_exit;
|
||
|
}
|
||
|
mhi_sm_ctx->d_state = MHI_SM_EP_PCIE_D0_STATE;
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D0:
|
||
|
if (old_dstate == MHI_SM_EP_PCIE_D0_STATE) {
|
||
|
MHI_SM_DBG("Nothing to do, already in D0 state\n");
|
||
|
break;
|
||
|
}
|
||
|
mhi_sm_ctx->d_state = MHI_SM_EP_PCIE_D0_STATE;
|
||
|
break;
|
||
|
default:
|
||
|
MHI_SM_ERR("Invalid EP_PCIE event, received 0x%x\n",
|
||
|
pcie_event);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
unlock_and_exit:
|
||
|
mutex_unlock(&mhi_sm_ctx->mhi_state_lock);
|
||
|
atomic_dec(&mhi_sm_ctx->pending_pcie_events);
|
||
|
kfree(chg_event);
|
||
|
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* mhi_dev_sm_init() - Initialize MHI state machine.
|
||
|
* @mhi_dev: pointer to mhi device instance
|
||
|
*
|
||
|
* Assuming MHISTATUS register is in RESET state.
|
||
|
*
|
||
|
* Return: 0 success
|
||
|
* -EINVAL: invalid param
|
||
|
* -ENOMEM: allocating memory error
|
||
|
*/
|
||
|
int mhi_dev_sm_init(struct mhi_dev *mhi_dev)
|
||
|
{
|
||
|
int res;
|
||
|
enum ep_pcie_link_status link_state;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
if (!mhi_dev) {
|
||
|
MHI_SM_ERR("Fail: Null argument\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
mhi_sm_ctx = devm_kzalloc(mhi_dev->dev, sizeof(*mhi_sm_ctx),
|
||
|
GFP_KERNEL);
|
||
|
if (!mhi_sm_ctx) {
|
||
|
MHI_SM_ERR("devm_kzalloc err: mhi_sm_ctx\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
/*init debugfs*/
|
||
|
mhi_sm_debugfs_init();
|
||
|
mhi_sm_ctx->mhi_sm_wq = create_singlethread_workqueue("mhi_sm_wq");
|
||
|
if (!mhi_sm_ctx->mhi_sm_wq) {
|
||
|
MHI_SM_ERR("Failed to create singlethread_workqueue: sm_wq\n");
|
||
|
res = -ENOMEM;
|
||
|
goto fail_init_wq;
|
||
|
}
|
||
|
|
||
|
mutex_init(&mhi_sm_ctx->mhi_state_lock);
|
||
|
mhi_sm_ctx->mhi_dev = mhi_dev;
|
||
|
mhi_sm_ctx->mhi_state = MHI_DEV_RESET_STATE;
|
||
|
mhi_sm_ctx->syserr_occurred = false;
|
||
|
atomic_set(&mhi_sm_ctx->pending_device_events, 0);
|
||
|
atomic_set(&mhi_sm_ctx->pending_pcie_events, 0);
|
||
|
|
||
|
link_state = ep_pcie_get_linkstatus(mhi_sm_ctx->mhi_dev->phandle);
|
||
|
if (link_state == EP_PCIE_LINK_ENABLED)
|
||
|
mhi_sm_ctx->d_state = MHI_SM_EP_PCIE_D0_STATE;
|
||
|
else
|
||
|
mhi_sm_ctx->d_state = MHI_SM_EP_PCIE_LINK_DISABLE;
|
||
|
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
return 0;
|
||
|
|
||
|
fail_init_wq:
|
||
|
mhi_sm_ctx = NULL;
|
||
|
mhi_sm_debugfs_destroy();
|
||
|
return res;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mhi_dev_sm_init);
|
||
|
|
||
|
/**
|
||
|
* mhi_dev_sm_get_mhi_state() -Get current MHI state.
|
||
|
* @state: return param
|
||
|
*
|
||
|
* Returns the current MHI state of the state machine.
|
||
|
*
|
||
|
* Return: 0 success
|
||
|
* -EINVAL: invalid param
|
||
|
* -EFAULT: state machine isn't initialized
|
||
|
*/
|
||
|
int mhi_dev_sm_get_mhi_state(enum mhi_dev_state *state)
|
||
|
{
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
if (!state) {
|
||
|
MHI_SM_ERR("Fail: Null argument\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (!mhi_sm_ctx) {
|
||
|
MHI_SM_ERR("Fail: MHI SM is not initialized\n");
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
*state = mhi_sm_ctx->mhi_state;
|
||
|
MHI_SM_DBG("state machine states are: %s and %s\n",
|
||
|
mhi_sm_mstate_str(*state),
|
||
|
mhi_sm_dstate_str(mhi_sm_ctx->d_state));
|
||
|
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mhi_dev_sm_get_mhi_state);
|
||
|
|
||
|
/**
|
||
|
* mhi_dev_sm_set_ready() -Set MHI state to ready.
|
||
|
*
|
||
|
* Set MHISTATUS register in mmio to READY.
|
||
|
* Synchronic function.
|
||
|
*
|
||
|
* Return: 0: success
|
||
|
* EINVAL: mhi state manager is not initialized
|
||
|
* EPERM: Operation not permitted as EP PCIE link is desable.
|
||
|
* EFAULT: MHI state is not RESET
|
||
|
* negative: other failure
|
||
|
*/
|
||
|
int mhi_dev_sm_set_ready(void)
|
||
|
{
|
||
|
int res;
|
||
|
int is_ready;
|
||
|
enum mhi_dev_state state;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
if (!mhi_sm_ctx) {
|
||
|
MHI_SM_ERR("Failed, MHI SM isn't initialized\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&mhi_sm_ctx->mhi_state_lock);
|
||
|
if (mhi_sm_ctx->mhi_state != MHI_DEV_RESET_STATE) {
|
||
|
MHI_SM_ERR("Can not switch to READY state from %s state\n",
|
||
|
mhi_sm_mstate_str(mhi_sm_ctx->mhi_state));
|
||
|
res = -EFAULT;
|
||
|
goto unlock_and_exit;
|
||
|
}
|
||
|
|
||
|
if (mhi_sm_ctx->d_state != MHI_SM_EP_PCIE_D0_STATE) {
|
||
|
if (ep_pcie_get_linkstatus(mhi_sm_ctx->mhi_dev->phandle) ==
|
||
|
EP_PCIE_LINK_ENABLED) {
|
||
|
mhi_sm_ctx->d_state = MHI_SM_EP_PCIE_D0_STATE;
|
||
|
} else {
|
||
|
MHI_SM_ERR("ERROR: ep-pcie link is not enabled\n");
|
||
|
res = -EPERM;
|
||
|
goto unlock_and_exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* verify that MHISTATUS is configured to RESET*/
|
||
|
mhi_dev_mmio_masked_read(mhi_sm_ctx->mhi_dev,
|
||
|
MHISTATUS, MHISTATUS_MHISTATE_MASK,
|
||
|
MHISTATUS_MHISTATE_SHIFT, &state);
|
||
|
|
||
|
mhi_dev_mmio_masked_read(mhi_sm_ctx->mhi_dev, MHISTATUS,
|
||
|
MHISTATUS_READY_MASK,
|
||
|
MHISTATUS_READY_SHIFT, &is_ready);
|
||
|
|
||
|
if (state != MHI_DEV_RESET_STATE || is_ready) {
|
||
|
MHI_SM_ERR("Cannot switch to READY, MHI is not in RESET state");
|
||
|
MHI_SM_ERR("-MHISTATE: %s, READY bit: 0x%x\n",
|
||
|
mhi_sm_mstate_str(state), is_ready);
|
||
|
res = -EFAULT;
|
||
|
goto unlock_and_exit;
|
||
|
}
|
||
|
mhi_sm_mmio_set_mhistatus(MHI_DEV_READY_STATE);
|
||
|
|
||
|
unlock_and_exit:
|
||
|
mutex_unlock(&mhi_sm_ctx->mhi_state_lock);
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
return res;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mhi_dev_sm_set_ready);
|
||
|
|
||
|
/**
|
||
|
* mhi_dev_notify_sm_event() - MHI-core notify SM on trigger occurred
|
||
|
* @event - enum of the requierd operation.
|
||
|
*
|
||
|
* Asynchronic function.
|
||
|
* No trigger is sent after operation is done.
|
||
|
*
|
||
|
* Return: 0: success
|
||
|
* -EFAULT: SM isn't initialized or event isn't supported
|
||
|
* -ENOMEM: allocating memory error
|
||
|
* -EINVAL: invalied event
|
||
|
*/
|
||
|
int mhi_dev_notify_sm_event(enum mhi_dev_event event)
|
||
|
{
|
||
|
struct mhi_sm_device_event *state_change_event;
|
||
|
int res;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
if (!mhi_sm_ctx) {
|
||
|
MHI_SM_ERR("Failed, MHI SM is not initialized\n");
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
|
||
|
MHI_SM_DBG("received: %s\n",
|
||
|
mhi_sm_dev_event_str(event));
|
||
|
|
||
|
switch (event) {
|
||
|
case MHI_DEV_EVENT_M0_STATE:
|
||
|
mhi_sm_ctx->stats.m0_event_cnt++;
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_M3_STATE:
|
||
|
mhi_sm_ctx->stats.m3_event_cnt++;
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_HW_ACC_WAKEUP:
|
||
|
mhi_sm_ctx->stats.hw_acc_wakeup_event_cnt++;
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_CORE_WAKEUP:
|
||
|
mhi_sm_ctx->stats.mhi_core_wakeup_event_cnt++;
|
||
|
break;
|
||
|
case MHI_DEV_EVENT_CTRL_TRIG:
|
||
|
case MHI_DEV_EVENT_M1_STATE:
|
||
|
case MHI_DEV_EVENT_M2_STATE:
|
||
|
MHI_SM_ERR("Not supported event: %s\n",
|
||
|
mhi_sm_dev_event_str(event));
|
||
|
res = -EFAULT;
|
||
|
goto exit;
|
||
|
default:
|
||
|
MHI_SM_ERR("Invalid event, received: 0x%x event\n", event);
|
||
|
res = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
/*init work and push to queue*/
|
||
|
state_change_event = kzalloc(sizeof(*state_change_event), GFP_ATOMIC);
|
||
|
if (!state_change_event) {
|
||
|
MHI_SM_ERR("kzalloc error\n");
|
||
|
res = -ENOMEM;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
state_change_event->event = event;
|
||
|
INIT_WORK(&state_change_event->work, mhi_sm_dev_event_manager);
|
||
|
atomic_inc(&mhi_sm_ctx->pending_device_events);
|
||
|
queue_work(mhi_sm_ctx->mhi_sm_wq, &state_change_event->work);
|
||
|
res = 0;
|
||
|
|
||
|
exit:
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
return res;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mhi_dev_notify_sm_event);
|
||
|
|
||
|
/**
|
||
|
* mhi_dev_sm_pcie_handler() - handler of ep_pcie events
|
||
|
* @notify - pointer to structure contains the ep_pcie event
|
||
|
*
|
||
|
* Callback function, called by ep_pcie driver to notify on pcie state change
|
||
|
* Asynchronic function
|
||
|
*/
|
||
|
void mhi_dev_sm_pcie_handler(struct ep_pcie_notify *notify)
|
||
|
{
|
||
|
struct mhi_sm_ep_pcie_event *dstate_change_evt;
|
||
|
enum ep_pcie_event event;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
if (!notify) {
|
||
|
MHI_SM_ERR("Null argument - notify\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!mhi_sm_ctx) {
|
||
|
MHI_SM_ERR("Failed, MHI SM is not initialized\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
event = notify->event;
|
||
|
MHI_SM_DBG("received: %s\n",
|
||
|
mhi_sm_pcie_event_str(event));
|
||
|
|
||
|
dstate_change_evt = kzalloc(sizeof(*dstate_change_evt), GFP_ATOMIC);
|
||
|
if (!dstate_change_evt) {
|
||
|
MHI_SM_ERR("kzalloc error\n");
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
switch (event) {
|
||
|
case EP_PCIE_EVENT_LINKUP:
|
||
|
mhi_sm_ctx->stats.linkup_event_cnt++;
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D3_COLD:
|
||
|
mhi_sm_ctx->stats.d3_cold_event_cnt++;
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D3_HOT:
|
||
|
mhi_sm_ctx->stats.d3_hot_event_cnt++;
|
||
|
mhi_dev_backup_mmio(mhi_sm_ctx->mhi_dev);
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_RST_DEAST:
|
||
|
mhi_sm_ctx->stats.rst_deast_event_cnt++;
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_PM_D0:
|
||
|
mhi_sm_ctx->stats.d0_event_cnt++;
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_LINKDOWN:
|
||
|
mhi_sm_ctx->stats.linkdown_event_cnt++;
|
||
|
mhi_sm_ctx->syserr_occurred = true;
|
||
|
MHI_SM_ERR("got %s, ERROR occurred\n",
|
||
|
mhi_sm_pcie_event_str(event));
|
||
|
break;
|
||
|
case EP_PCIE_EVENT_MHI_A7:
|
||
|
ep_pcie_mask_irq_event(mhi_sm_ctx->mhi_dev->phandle,
|
||
|
EP_PCIE_INT_EVT_MHI_A7, false);
|
||
|
mhi_dev_notify_a7_event(mhi_sm_ctx->mhi_dev);
|
||
|
goto exit;
|
||
|
default:
|
||
|
MHI_SM_ERR("Invalid ep_pcie event, received 0x%x event\n",
|
||
|
event);
|
||
|
kfree(dstate_change_evt);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
dstate_change_evt->event = event;
|
||
|
INIT_WORK(&dstate_change_evt->work, mhi_sm_pcie_event_manager);
|
||
|
queue_work(mhi_sm_ctx->mhi_sm_wq, &dstate_change_evt->work);
|
||
|
atomic_inc(&mhi_sm_ctx->pending_pcie_events);
|
||
|
|
||
|
exit:
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
}
|
||
|
EXPORT_SYMBOL(mhi_dev_sm_pcie_handler);
|
||
|
|
||
|
/**
|
||
|
* mhi_dev_sm_syserr() - switch to system error state.
|
||
|
*
|
||
|
* Called on system error condition.
|
||
|
* Switch MHI to SYSERR state, notify MHI-host and ASSERT on the device.
|
||
|
* Synchronic function.
|
||
|
*
|
||
|
* Return: 0: success
|
||
|
* negative: failure
|
||
|
*/
|
||
|
int mhi_dev_sm_syserr(void)
|
||
|
{
|
||
|
int res;
|
||
|
|
||
|
MHI_SM_FUNC_ENTRY();
|
||
|
|
||
|
if (!mhi_sm_ctx) {
|
||
|
MHI_SM_ERR("Failed, MHI SM is not initialized\n");
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&mhi_sm_ctx->mhi_state_lock);
|
||
|
res = mhi_sm_handle_syserr();
|
||
|
if (res)
|
||
|
MHI_SM_ERR("mhi_sm_handle_syserr failed %d\n", res);
|
||
|
mutex_unlock(&mhi_sm_ctx->mhi_state_lock);
|
||
|
|
||
|
MHI_SM_FUNC_EXIT();
|
||
|
return res;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mhi_dev_sm_syserr);
|
||
|
|
||
|
static ssize_t mhi_sm_debugfs_read(struct file *file, char __user *ubuf,
|
||
|
size_t count, loff_t *ppos)
|
||
|
{
|
||
|
int nbytes = 0;
|
||
|
|
||
|
if (!mhi_sm_ctx) {
|
||
|
nbytes = scnprintf(dbg_buff, MHI_SM_MAX_MSG_LEN,
|
||
|
"Not initialized\n");
|
||
|
} else {
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"*************** MHI State machine status ***************\n");
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"D state: %s\n",
|
||
|
mhi_sm_dstate_str(mhi_sm_ctx->d_state));
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"M state: %s\n",
|
||
|
mhi_sm_mstate_str(mhi_sm_ctx->mhi_state));
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"pending device events: %d\n",
|
||
|
atomic_read(&mhi_sm_ctx->pending_device_events));
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"pending pcie events: %d\n",
|
||
|
atomic_read(&mhi_sm_ctx->pending_pcie_events));
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"*************** Statistics ***************\n");
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"M0 events: %d\n", mhi_sm_ctx->stats.m0_event_cnt);
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"M3 events: %d\n", mhi_sm_ctx->stats.m3_event_cnt);
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"HW_ACC wakeup events: %d\n",
|
||
|
mhi_sm_ctx->stats.hw_acc_wakeup_event_cnt);
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"CORE wakeup events: %d\n",
|
||
|
mhi_sm_ctx->stats.mhi_core_wakeup_event_cnt);
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"Linkup events: %d\n",
|
||
|
mhi_sm_ctx->stats.linkup_event_cnt);
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"De-assert PERST events: %d\n",
|
||
|
mhi_sm_ctx->stats.rst_deast_event_cnt);
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"D0 events: %d\n",
|
||
|
mhi_sm_ctx->stats.d0_event_cnt);
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"D3_HOT events: %d\n",
|
||
|
mhi_sm_ctx->stats.d3_hot_event_cnt);
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"D3_COLD events:%d\n",
|
||
|
mhi_sm_ctx->stats.d3_cold_event_cnt);
|
||
|
nbytes += scnprintf(dbg_buff + nbytes,
|
||
|
MHI_SM_MAX_MSG_LEN - nbytes,
|
||
|
"Linkdown events: %d\n",
|
||
|
mhi_sm_ctx->stats.linkdown_event_cnt);
|
||
|
}
|
||
|
|
||
|
return simple_read_from_buffer(ubuf, count, ppos, dbg_buff, nbytes);
|
||
|
}
|
||
|
|
||
|
static ssize_t mhi_sm_debugfs_write(struct file *file,
|
||
|
const char __user *ubuf,
|
||
|
size_t count,
|
||
|
loff_t *ppos)
|
||
|
{
|
||
|
unsigned long missing;
|
||
|
s8 in_num = 0;
|
||
|
|
||
|
if (!mhi_sm_ctx) {
|
||
|
MHI_SM_ERR("Not initialized\n");
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
|
||
|
if (sizeof(dbg_buff) < count + 1)
|
||
|
return -EFAULT;
|
||
|
|
||
|
missing = copy_from_user(dbg_buff, ubuf, count);
|
||
|
if (missing)
|
||
|
return -EFAULT;
|
||
|
|
||
|
dbg_buff[count] = '\0';
|
||
|
if (kstrtos8(dbg_buff, 0, &in_num))
|
||
|
return -EFAULT;
|
||
|
|
||
|
switch (in_num) {
|
||
|
case 0:
|
||
|
if (atomic_read(&mhi_sm_ctx->pending_device_events) ||
|
||
|
atomic_read(&mhi_sm_ctx->pending_pcie_events))
|
||
|
MHI_SM_DBG("Note, there are pending events in sm_wq\n");
|
||
|
|
||
|
memset(&mhi_sm_ctx->stats, 0, sizeof(struct mhi_sm_stats));
|
||
|
break;
|
||
|
default:
|
||
|
MHI_SM_ERR("invalid argument: To reset statistics echo 0\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|