| /* Copyright (c) 2015-2018, 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_mhi.h> |
| #include "mhi_hwio.h" |
| #include "mhi_sm.h" |
| #include <linux/interrupt.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 = 0666; |
| |
| 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_mstate == MHI_DEV_READY_STATE || |
| curr_mstate == MHI_DEV_RESET_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 || |
| curr_dstate == MHI_SM_EP_PCIE_D0_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_HOT_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 = -EINVAL; |
| |
| 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 */ |
| if (old_state == MHI_DEV_M3_STATE) { |
| if (mhi_sm_ctx->mhi_dev->use_ipa) { |
| res = ipa_dma_enable(); |
| if (res) { |
| MHI_SM_ERR("IPA enable failed\n"); |
| return res; |
| } |
| } |
| |
| 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 = 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; |
| } |
| } |
| 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; |
| } |
| |
| if (mhi_sm_ctx->mhi_dev->use_ipa) { |
| res = ipa_dma_disable(); |
| if (res) { |
| MHI_SM_ERR("IPA disable failed\n"); |
| return res; |
| } |
| } |
| |
| 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/nError ON DEVICE !!!!/n/n/n"); |
| WARN_ON(1); |
| |
| 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; |
| unsigned long flags; |
| |
| 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); |
| |
| spin_lock_irqsave(&mhi_sm_ctx->mhi_dev->lock, flags); |
| if ((mhi_sm_ctx->mhi_dev->mhi_int) && |
| (!mhi_sm_ctx->mhi_dev->mhi_int_en)) { |
| enable_irq(mhi_sm_ctx->mhi_dev->mhi_irq); |
| mhi_sm_ctx->mhi_dev->mhi_int_en = true; |
| MHI_SM_DBG("Enable MHI IRQ during PCIe DEAST"); |
| } |
| spin_unlock_irqrestore(&mhi_sm_ctx->mhi_dev->lock, flags); |
| |
| 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); |
| |
| int mhi_dev_sm_exit(struct mhi_dev *mhi_dev) |
| { |
| MHI_SM_FUNC_ENTRY(); |
| |
| atomic_set(&mhi_sm_ctx->pending_device_events, 0); |
| atomic_set(&mhi_sm_ctx->pending_pcie_events, 0); |
| mhi_sm_debugfs_destroy(); |
| flush_workqueue(mhi_sm_ctx->mhi_sm_wq); |
| destroy_workqueue(mhi_sm_ctx->mhi_sm_wq); |
| ipa_dma_destroy(); |
| mutex_destroy(&mhi_sm_ctx->mhi_state_lock); |
| devm_kfree(mhi_dev->dev, mhi_sm_ctx); |
| mhi_sm_ctx = NULL; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(mhi_dev_sm_exit); |
| |
| /** |
| * 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 = 0; |
| 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); |
| res = 0; |
| |
| 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; |
| unsigned long flags; |
| |
| 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++; |
| |
| spin_lock_irqsave(&mhi_sm_ctx->mhi_dev->lock, flags); |
| if ((mhi_sm_ctx->mhi_dev->mhi_int) && |
| (mhi_sm_ctx->mhi_dev->mhi_int_en)) { |
| disable_irq(mhi_sm_ctx->mhi_dev->mhi_irq); |
| mhi_sm_ctx->mhi_dev->mhi_int_en = false; |
| MHI_SM_DBG("Disable MHI IRQ during D3 HOT"); |
| } |
| spin_unlock_irqrestore(&mhi_sm_ctx->mhi_dev->lock, flags); |
| |
| 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++; |
| |
| spin_lock_irqsave(&mhi_sm_ctx->mhi_dev->lock, flags); |
| if ((mhi_sm_ctx->mhi_dev->mhi_int) && |
| (!mhi_sm_ctx->mhi_dev->mhi_int_en)) { |
| enable_irq(mhi_sm_ctx->mhi_dev->mhi_irq); |
| mhi_sm_ctx->mhi_dev->mhi_int_en = true; |
| MHI_SM_DBG("Enable MHI IRQ during D0"); |
| } |
| spin_unlock_irqrestore(&mhi_sm_ctx->mhi_dev->lock, flags); |
| 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); |
| kfree(dstate_change_evt); |
| 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; |
| } |