From 931f63edf21ca3da4a0ae45c8eec60842397690d Mon Sep 17 00:00:00 2001 From: Lazarus Motha Date: Thu, 8 Sep 2022 23:21:57 -0700 Subject: [PATCH] bus: mhi: controllers: Add snapshot of MHI controller driver This is a snapshot of MHI controller driver from msm-5.15 commit c2abee477eae ("bus: mhi: Add snapshot of MHI QCOM controller"). Change-Id: I24b737a79a949d1de27ee1ab12bfe93e51e2c37e Signed-off-by: Lazarus Motha --- drivers/bus/mhi/controllers/Kconfig | 15 + drivers/bus/mhi/controllers/Makefile | 4 + drivers/bus/mhi/controllers/mhi_arch_qcom.c | 719 +++++++++++ drivers/bus/mhi/controllers/mhi_qcom.c | 1266 +++++++++++++++++++ drivers/bus/mhi/controllers/mhi_qcom.h | 156 +++ 5 files changed, 2160 insertions(+) create mode 100644 drivers/bus/mhi/controllers/Kconfig create mode 100644 drivers/bus/mhi/controllers/Makefile create mode 100644 drivers/bus/mhi/controllers/mhi_arch_qcom.c create mode 100644 drivers/bus/mhi/controllers/mhi_qcom.c create mode 100644 drivers/bus/mhi/controllers/mhi_qcom.h diff --git a/drivers/bus/mhi/controllers/Kconfig b/drivers/bus/mhi/controllers/Kconfig new file mode 100644 index 000000000000..a35b65518cb4 --- /dev/null +++ b/drivers/bus/mhi/controllers/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "MHI controllers" + +config MHI_QCOM + tristate "MHI QCOM" + depends on MHI_BUS && MHI_BUS_MISC && QCOM_ESOC_MDM_DRV + help + If you say yes to this option, MHI bus support for QCOM modem chipsets + will be enabled. QCOM PCIe based modems uses MHI as the communication + protocol. MHI control driver is the bus master for such modems. As the + bus master driver, it oversees power management operations such as + suspend, resume, powering on and off the device. + +endmenu diff --git a/drivers/bus/mhi/controllers/Makefile b/drivers/bus/mhi/controllers/Makefile new file mode 100644 index 000000000000..4c44987acf99 --- /dev/null +++ b/drivers/bus/mhi/controllers/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +mhi_cntrl_qcom-y := mhi_qcom.o mhi_arch_qcom.o +obj-$(CONFIG_MHI_QCOM) += mhi_cntrl_qcom.o diff --git a/drivers/bus/mhi/controllers/mhi_arch_qcom.c b/drivers/bus/mhi/controllers/mhi_arch_qcom.c new file mode 100644 index 000000000000..4dff834863f3 --- /dev/null +++ b/drivers/bus/mhi/controllers/mhi_arch_qcom.c @@ -0,0 +1,719 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mhi_qcom.h" + +/** + * struct mhi_bus_bw_cfg - Interconnect vote data + * @avg_bw: Vote for average bandwidth + * @peak_bw: Vote for peak bandwidth + */ +struct mhi_bus_bw_cfg { + u32 avg_bw; + u32 peak_bw; +}; + +struct arch_info { + struct mhi_qcom_priv *mhi_priv; + struct esoc_desc *esoc_client; + struct esoc_client_hook esoc_ops; + struct msm_pcie_register_event pcie_reg_event; + struct pci_saved_state *pcie_state; + struct notifier_block pm_notifier; + struct completion pm_completion; + struct icc_path *icc_path; + const char *icc_name; + struct mhi_bus_bw_cfg *bw_cfg_table; + /* bootup logger */ + void *boot_ipc_log; + struct mhi_device *boot_dev; + size_t boot_len; +}; + +/* IPC log markers for bootup logger */ +#define DLOG "Dev->Host: " +#define HLOG "Host: " + +#define MHI_BOOT_LOG_PAGES (25) +#define MHI_BOOT_DEFAULT_RX_LEN (0x1000) + +#define MHI_BUS_BW_CFG_COUNT (5) + +static int mhi_arch_pm_notifier(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct arch_info *arch_info = + container_of(nb, struct arch_info, pm_notifier); + + switch (event) { + case PM_SUSPEND_PREPARE: + reinit_completion(&arch_info->pm_completion); + break; + + case PM_POST_SUSPEND: + complete_all(&arch_info->pm_completion); + break; + } + + return NOTIFY_DONE; +} + +static int mhi_arch_set_bus_request(struct mhi_controller *mhi_cntrl, int index) +{ + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct arch_info *arch_info = mhi_priv->arch_info; + int ret; + + if (index >= MHI_BUS_BW_CFG_COUNT) + return -EINVAL; + + ret = icc_set_bw(arch_info->icc_path, + arch_info->bw_cfg_table[index].avg_bw, + arch_info->bw_cfg_table[index].peak_bw); + if (ret) { + MHI_CNTRL_ERR("Could not set BW cfg: %d (%d %d), ret: %d\n", + index, arch_info->bw_cfg_table[index].avg_bw, + arch_info->bw_cfg_table[index].peak_bw, ret); + return ret; + } + + return 0; +} + +static void mhi_arch_pci_link_state_cb(struct msm_pcie_notify *notify) +{ + struct mhi_controller *mhi_cntrl = notify->data; + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + + switch (notify->event) { + case MSM_PCIE_EVENT_WAKEUP: + MHI_CNTRL_LOG("Received PCIE_WAKE signal\n"); + + /* bring link out of d3cold */ + if (mhi_priv->powered_on) { + pm_runtime_get(mhi_cntrl->cntrl_dev); + pm_runtime_put_noidle(mhi_cntrl->cntrl_dev); + } + break; + case MSM_PCIE_EVENT_L1SS_TIMEOUT: + MHI_CNTRL_LOG("Received PCIE_L1SS_TIMEOUT signal\n"); + + pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); + pm_request_autosuspend(mhi_cntrl->cntrl_dev); + break; + default: + MHI_CNTRL_LOG("Unhandled event: 0x%x\n", notify->event); + } +} + +static int mhi_arch_esoc_ops_power_on(void *priv, unsigned int flags) +{ + struct mhi_controller *mhi_cntrl = priv; + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + int ret; + + mutex_lock(&mhi_cntrl->pm_mutex); + if (mhi_priv->powered_on) { + MHI_CNTRL_LOG("MHI still in active state\n"); + mutex_unlock(&mhi_cntrl->pm_mutex); + return 0; + } + + MHI_CNTRL_LOG("Enter: mdm_crashed: %d\n", flags & ESOC_HOOK_MDM_CRASH); + + /* reset rpm state */ + pm_runtime_set_active(mhi_cntrl->cntrl_dev); + pm_runtime_enable(mhi_cntrl->cntrl_dev); + mutex_unlock(&mhi_cntrl->pm_mutex); + pm_runtime_forbid(mhi_cntrl->cntrl_dev); + ret = pm_runtime_get_sync(mhi_cntrl->cntrl_dev); + if (ret < 0) { + MHI_CNTRL_ERR("Error with rpm resume, ret: %d\n", ret); + return ret; + } + + /* re-start the link & recover default cfg states */ + ret = msm_pcie_pm_control(MSM_PCIE_RESUME, pci_dev->bus->number, + pci_dev, NULL, 0); + if (ret) { + MHI_CNTRL_ERR("Failed to resume pcie bus ret: %d\n", ret); + return ret; + } + + mhi_priv->mdm_state = (flags & ESOC_HOOK_MDM_CRASH); + + return mhi_qcom_pci_probe(pci_dev, mhi_cntrl, mhi_priv); +} + +static void mhi_arch_link_off(struct mhi_controller *mhi_cntrl) +{ + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + + MHI_CNTRL_LOG("Entered\n"); + + pci_set_power_state(pci_dev, PCI_D3hot); + + /* release the resources */ + msm_pcie_pm_control(MSM_PCIE_SUSPEND, pci_dev->bus->number, pci_dev, + NULL, 0); + mhi_arch_set_bus_request(mhi_cntrl, 0); + + MHI_CNTRL_LOG("Exited\n"); +} + +static void mhi_arch_esoc_ops_power_off(void *priv, unsigned int flags) +{ + struct mhi_controller *mhi_cntrl = priv; + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + bool mdm_state = (flags & ESOC_HOOK_MDM_CRASH); + struct arch_info *arch_info = mhi_priv->arch_info; + + MHI_CNTRL_LOG("Enter: mdm_crashed: %d\n", mdm_state); + + /* + * Abort system suspend if system is preparing to go to suspend + * by grabbing wake source. + * If system is suspended, wait for pm notifier callback to notify + * that resume has occurred with PM_POST_SUSPEND event. + */ + pm_stay_awake(&mhi_cntrl->mhi_dev->dev); + wait_for_completion(&arch_info->pm_completion); + + /* if link is in suspend, wake it up */ + pm_runtime_get_sync(mhi_cntrl->cntrl_dev); + + mutex_lock(&mhi_cntrl->pm_mutex); + if (!mhi_priv->powered_on) { + MHI_CNTRL_LOG("Not in active state\n"); + mutex_unlock(&mhi_cntrl->pm_mutex); + pm_runtime_put_noidle(mhi_cntrl->cntrl_dev); + return; + } + mhi_priv->powered_on = false; + mhi_priv->driver_remove = false; + mutex_unlock(&mhi_cntrl->pm_mutex); + + pm_runtime_put_noidle(mhi_cntrl->cntrl_dev); + + MHI_CNTRL_LOG("Triggering shutdown process\n"); + mhi_power_down(mhi_cntrl, !mdm_state); + + /* turn the link off */ + mhi_arch_link_off(mhi_cntrl); + mhi_arch_pcie_deinit(mhi_cntrl); + mhi_deinit_pci_dev(to_pci_dev(mhi_cntrl->cntrl_dev), + mhi_priv->dev_info); + + pm_relax(&mhi_cntrl->mhi_dev->dev); +} + +static void mhi_arch_esoc_ops_mdm_error(void *priv) +{ + struct mhi_controller *mhi_cntrl = priv; + + /* transition MHI state into error state */ + MHI_CNTRL_LOG("Enter: mdm asserted: ret: %d\n", + mhi_report_error(mhi_cntrl)); +} + +void mhi_arch_mission_mode_enter(struct mhi_controller *mhi_cntrl) +{ + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + int ret; + + /* Set target PCIe link speed as maximum device/link is capable of */ + ret = msm_pcie_set_target_link_speed(pci_domain_nr(pci_dev->bus), 0, + true); + if (ret) { + MHI_CNTRL_ERR("Failed to set PCIe target link speed\n"); + return; + } + + if (dev_info->skip_forced_suspend && !dev_info->allow_m1) + msm_pcie_l1ss_timeout_enable(to_pci_dev(mhi_cntrl->cntrl_dev)); +} + +static int mhi_arch_pcie_scale_bw(struct mhi_controller *mhi_cntrl, + struct pci_dev *pci_dev, + struct mhi_link_info *link_info) +{ + int ret; + + ret = msm_pcie_set_link_bandwidth(pci_dev, link_info->target_link_speed, + link_info->target_link_width); + if (ret) + return ret; + + /* do a bus scale vote based on gen speeds */ + mhi_arch_set_bus_request(mhi_cntrl, link_info->target_link_speed); + + MHI_CNTRL_LOG("BW changed to speed:0x%x width:0x%x\n", + link_info->target_link_speed, + link_info->target_link_width); + + return 0; +} + +static int mhi_arch_bw_scale(struct mhi_controller *mhi_cntrl, + struct mhi_link_info *link_info) +{ + return mhi_arch_pcie_scale_bw(mhi_cntrl, to_pci_dev(mhi_cntrl->cntrl_dev), + link_info); +} + +static void mhi_bl_dl_cb(struct mhi_device *mhi_device, + struct mhi_result *mhi_result) +{ + struct mhi_controller *mhi_cntrl = mhi_device->mhi_cntrl; + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct arch_info *arch_info = mhi_priv->arch_info; + char *buf = mhi_result->buf_addr; + char *token, *delim = "\n"; + int ret; + + if (mhi_result->transaction_status == -ENOTCONN) { + kfree(buf); + return; + } + + /* force a null at last character */ + buf[mhi_result->bytes_xferd - 1] = 0; + + if (mhi_result->bytes_xferd >= MAX_MSG_SIZE) { + do { + token = strsep((char **)&buf, delim); + if (token) + ipc_log_string(arch_info->boot_ipc_log, "%s %s", + DLOG, token); + } while (token); + } else { + ipc_log_string(arch_info->boot_ipc_log, "%s %s", DLOG, buf); + } + + ret = mhi_queue_buf(mhi_device, DMA_FROM_DEVICE, buf, + arch_info->boot_len, MHI_EOT); + if (ret) { + kfree(buf); + MHI_CNTRL_ERR("Failed to recycle BL channel buffer: %d\n", ret); + return; + } +} + +static void mhi_bl_dummy_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ +} + +static void mhi_bl_remove(struct mhi_device *mhi_device) +{ + struct mhi_controller *mhi_cntrl = mhi_device->mhi_cntrl; + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct arch_info *arch_info = mhi_priv->arch_info; + + arch_info->boot_dev = NULL; + ipc_log_string(arch_info->boot_ipc_log, HLOG "Remove notification\n"); + + mhi_unprepare_from_transfer(mhi_device); +} + +int mhi_bl_queue_inbound(struct mhi_device *mhi_dev, size_t len) +{ + void *buf; + int nr_trbs, ret, i; + + nr_trbs = mhi_get_free_desc_count(mhi_dev, DMA_FROM_DEVICE); + for (i = 0; i < nr_trbs; i++) { + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, buf, len, + MHI_EOT); + if (ret) { + kfree(buf); + return -EIO; + } + } + + return 0; +} + +static int mhi_bl_probe(struct mhi_device *mhi_device, + const struct mhi_device_id *id) +{ + struct mhi_controller *mhi_cntrl = mhi_device->mhi_cntrl; + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct arch_info *arch_info = mhi_priv->arch_info; + int ret; + + arch_info->boot_dev = mhi_device; + arch_info->boot_ipc_log = + ipc_log_context_create(MHI_BOOT_LOG_PAGES, + dev_name(&mhi_device->dev), 0); + ipc_log_string(arch_info->boot_ipc_log, HLOG + "Entered SBL, Session ID:0x%x\n", mhi_cntrl->session_id); + + ret = mhi_prepare_for_transfer(mhi_device, 0); + if (ret) + return ret; + + arch_info->boot_len = id->driver_data; + if (!arch_info->boot_len) + arch_info->boot_len = MHI_BOOT_DEFAULT_RX_LEN; + + ret = mhi_bl_queue_inbound(mhi_device, arch_info->boot_len); + if (ret) { + mhi_unprepare_from_transfer(mhi_device); + return ret; + } + + return 0; +} + +static const struct mhi_device_id mhi_bl_match_table[] = { + { .chan = "BL", .driver_data = 0x1000 }, + {}, +}; + +static struct mhi_driver mhi_bl_driver = { + .id_table = mhi_bl_match_table, + .remove = mhi_bl_remove, + .probe = mhi_bl_probe, + .ul_xfer_cb = mhi_bl_dummy_cb, + .dl_xfer_cb = mhi_bl_dl_cb, + .driver = { + .name = "MHI_BL", + .owner = THIS_MODULE, + }, +}; + +void mhi_arch_pcie_deinit(struct mhi_controller *mhi_cntrl) +{ + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + struct arch_info *arch_info = mhi_priv->arch_info; + int ret; + + mhi_arch_set_bus_request(mhi_cntrl, 0); + + /* Reset target PCIe link speed to the original device tree entry */ + ret = msm_pcie_set_target_link_speed(pci_domain_nr(pci_dev->bus), 0, + false); + if (ret) + MHI_CNTRL_ERR("Failed to set PCIe target link speed\n"); + + if (!mhi_priv->driver_remove) + return; + + mhi_driver_unregister(&mhi_bl_driver); + esoc_unregister_client_hook(arch_info->esoc_client, + &arch_info->esoc_ops); + devm_unregister_esoc_client(mhi_cntrl->cntrl_dev, + arch_info->esoc_client); + complete_all(&arch_info->pm_completion); + unregister_pm_notifier(&arch_info->pm_notifier); + msm_pcie_deregister_event(&arch_info->pcie_reg_event); +} + +int mhi_arch_pcie_init(struct mhi_controller *mhi_cntrl) +{ + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct arch_info *arch_info = mhi_priv->arch_info; + const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + struct mhi_link_info *cur_link_info; + int ret; + u16 linkstat; + + if (!arch_info) { + struct msm_pcie_register_event *reg_event; + struct device_node *of_node = pci_dev->dev.of_node; + + arch_info = devm_kzalloc(mhi_cntrl->cntrl_dev, + sizeof(*arch_info), GFP_KERNEL); + if (!arch_info) + return -ENOMEM; + + mhi_priv->arch_info = arch_info; + arch_info->mhi_priv = mhi_priv; + + ret = of_property_read_string(of_node, "interconnect-names", + &arch_info->icc_name); + if (ret) { + MHI_CNTRL_ERR("No interconnect name specified\n"); + return -ENOENT; + } + + arch_info->icc_path = of_icc_get(mhi_cntrl->cntrl_dev, + arch_info->icc_name); + if (IS_ERR(arch_info->icc_path)) { + ret = PTR_ERR(arch_info->icc_path); + MHI_CNTRL_ERR("Interconnect path for %s not found, ret: %d\n", + arch_info->icc_name, ret); + return ret; + } + + arch_info->bw_cfg_table = devm_kzalloc(mhi_cntrl->cntrl_dev, + sizeof(*arch_info->bw_cfg_table) + * MHI_BUS_BW_CFG_COUNT, + GFP_KERNEL); + if (!arch_info->bw_cfg_table) + return -ENOMEM; + + ret = of_property_read_u32_array(of_node, "qcom,mhi-bus-bw-cfg", + (u32 *)arch_info->bw_cfg_table, + MHI_BUS_BW_CFG_COUNT * 2); + if (ret) { + MHI_CNTRL_ERR("Invalid bus BW config table\n"); + return ret; + } + + /* register with pcie rc for WAKE# or link state events */ + reg_event = &arch_info->pcie_reg_event; + reg_event->events = dev_info->allow_m1 ? + (MSM_PCIE_EVENT_WAKEUP) : + (MSM_PCIE_EVENT_WAKEUP | MSM_PCIE_EVENT_L1SS_TIMEOUT); + + reg_event->user = pci_dev; + reg_event->callback = mhi_arch_pci_link_state_cb; + reg_event->notify.data = mhi_cntrl; + ret = msm_pcie_register_event(reg_event); + if (ret) + MHI_CNTRL_ERR("Failed to register for link events\n"); + + init_completion(&arch_info->pm_completion); + + /* register PM notifier to get post resume events */ + arch_info->pm_notifier.notifier_call = mhi_arch_pm_notifier; + register_pm_notifier(&arch_info->pm_notifier); + + /* + * Mark as completed at initial boot-up to allow ESOC power on + * callback to proceed if system has not gone to suspend + */ + complete_all(&arch_info->pm_completion); + + arch_info->esoc_client = devm_register_esoc_client( + mhi_cntrl->cntrl_dev, "mdm"); + if (IS_ERR_OR_NULL(arch_info->esoc_client)) { + MHI_CNTRL_ERR("Failed to register esoc client\n"); + } else { + /* register for power on/off hooks */ + struct esoc_client_hook *esoc_ops = + &arch_info->esoc_ops; + + esoc_ops->priv = mhi_cntrl; + esoc_ops->prio = ESOC_MHI_HOOK; + esoc_ops->esoc_link_power_on = + mhi_arch_esoc_ops_power_on; + esoc_ops->esoc_link_power_off = + mhi_arch_esoc_ops_power_off; + esoc_ops->esoc_link_mdm_crash = + mhi_arch_esoc_ops_mdm_error; + + ret = esoc_register_client_hook(arch_info->esoc_client, + esoc_ops); + if (ret) + MHI_CNTRL_ERR("Failed to register esoc ops\n"); + } + + /* + * MHI host driver has full autonomy to manage power state. + * Disable all automatic power collapse features + */ + msm_pcie_pm_control(MSM_PCIE_DISABLE_PC, pci_dev->bus->number, + pci_dev, NULL, 0); + + mhi_controller_set_bw_scale_cb(mhi_cntrl, &mhi_arch_bw_scale); + + mhi_driver_register(&mhi_bl_driver); + } + + /* store the current bw info */ + ret = pcie_capability_read_word(pci_dev, PCI_EXP_LNKSTA, &linkstat); + if (ret) + return ret; + + cur_link_info = &mhi_cntrl->mhi_link_info; + cur_link_info->target_link_speed = linkstat & PCI_EXP_LNKSTA_CLS; + cur_link_info->target_link_width = (linkstat & PCI_EXP_LNKSTA_NLW) >> + PCI_EXP_LNKSTA_NLW_SHIFT; + + return mhi_arch_set_bus_request(mhi_cntrl, + cur_link_info->target_link_speed); +} + +int mhi_arch_link_suspend(struct mhi_controller *mhi_cntrl) +{ + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct arch_info *arch_info = mhi_priv->arch_info; + const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + int ret = 0; + + MHI_CNTRL_LOG("Entered with suspend_mode:%s\n", + TO_MHI_SUSPEND_MODE_STR(mhi_priv->suspend_mode)); + + /* disable inactivity timer */ + if (!dev_info->allow_m1) + msm_pcie_l1ss_timeout_disable(pci_dev); + + if (mhi_priv->disable_pci_lpm) + goto exit_suspend; + + switch (mhi_priv->suspend_mode) { + case MHI_DEFAULT_SUSPEND: + pci_clear_master(pci_dev); + ret = pci_save_state(pci_dev); + if (ret) { + MHI_CNTRL_ERR("Failed with pci_save_state, ret:%d\n", + ret); + goto exit_suspend; + } + + arch_info->pcie_state = pci_store_saved_state(pci_dev); + pci_disable_device(pci_dev); + + pci_set_power_state(pci_dev, PCI_D3hot); + + /* release the resources */ + msm_pcie_pm_control(MSM_PCIE_SUSPEND, pci_dev->bus->number, + pci_dev, NULL, 0); + mhi_arch_set_bus_request(mhi_cntrl, 0); + break; + case MHI_FAST_LINK_OFF: + case MHI_ACTIVE_STATE: + case MHI_FAST_LINK_ON:/* keeping link on do nothing */ + default: + break; + } + +exit_suspend: + if (ret && !dev_info->allow_m1) + msm_pcie_l1ss_timeout_enable(pci_dev); + + MHI_CNTRL_LOG("Exited with ret:%d\n", ret); + + return ret; +} + +static int __mhi_arch_link_resume(struct mhi_controller *mhi_cntrl) +{ + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct arch_info *arch_info = mhi_priv->arch_info; + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + struct mhi_link_info *cur_info = &mhi_cntrl->mhi_link_info; + int ret; + + /* request bus scale voting based on higher gen speed */ + ret = mhi_arch_set_bus_request(mhi_cntrl, + cur_info->target_link_speed); + if (ret) + MHI_CNTRL_LOG("Could not set bus frequency, ret:%d\n", ret); + + ret = msm_pcie_pm_control(MSM_PCIE_RESUME, pci_dev->bus->number, + pci_dev, NULL, 0); + if (ret) { + MHI_CNTRL_ERR("Link training failed, ret:%d\n", ret); + return ret; + } + + ret = pci_set_power_state(pci_dev, PCI_D0); + if (ret) { + MHI_CNTRL_ERR("Failed to set PCI_D0 state, ret:%d\n", ret); + return ret; + } + + ret = pci_enable_device(pci_dev); + if (ret) { + MHI_CNTRL_ERR("Failed to enable device, ret:%d\n", ret); + return ret; + } + + ret = pci_load_and_free_saved_state(pci_dev, &arch_info->pcie_state); + if (ret) + MHI_CNTRL_LOG("Failed to load saved cfg state\n"); + + pci_restore_state(pci_dev); + pci_set_master(pci_dev); + + return 0; +} + +int mhi_arch_link_resume(struct mhi_controller *mhi_cntrl) +{ + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; + int ret = 0; + + MHI_CNTRL_LOG("Entered with suspend_mode: %s\n", + TO_MHI_SUSPEND_MODE_STR(mhi_priv->suspend_mode)); + + if (mhi_priv->disable_pci_lpm) + goto resume_no_pci_lpm; + + switch (mhi_priv->suspend_mode) { + case MHI_DEFAULT_SUSPEND: + ret = __mhi_arch_link_resume(mhi_cntrl); + break; + case MHI_FAST_LINK_OFF: + case MHI_ACTIVE_STATE: + case MHI_FAST_LINK_ON: + default: + break; + } + + if (ret) { + MHI_CNTRL_ERR("Link training failed, ret: %d\n", ret); + return ret; + } + +resume_no_pci_lpm: + if (!dev_info->allow_m1) + msm_pcie_l1ss_timeout_enable(pci_dev); + + MHI_CNTRL_LOG("Exited with ret: %d\n", ret); + + return ret; +} + +u64 mhi_arch_time_get(struct mhi_controller *mhi_cntrl) +{ + return __arch_counter_get_cntvct(); +} + +int mhi_arch_link_lpm_disable(struct mhi_controller *mhi_cntrl) +{ + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + + return msm_pcie_prevent_l1(pci_dev); +} + +int mhi_arch_link_lpm_enable(struct mhi_controller *mhi_cntrl) +{ + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + + msm_pcie_allow_l1(pci_dev); + + return 0; +} diff --git a/drivers/bus/mhi/controllers/mhi_qcom.c b/drivers/bus/mhi/controllers/mhi_qcom.c new file mode 100644 index 000000000000..25c63994956b --- /dev/null +++ b/drivers/bus/mhi/controllers/mhi_qcom.c @@ -0,0 +1,1266 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mhi_qcom.h" + +#define MHI_CHANNEL_CONFIG_UL(ch_num, ch_name, elems, ev_ring, ee, \ + dbmode, lpm, poll, offload, modeswitch, \ + ch_type) \ + { \ + .dir = DMA_TO_DEVICE, \ + .num = ch_num, \ + .name = ch_name, \ + .num_elements = elems, \ + .event_ring = ev_ring, \ + .ee_mask = BIT(ee), \ + .pollcfg = poll, \ + .doorbell = dbmode, \ + .lpm_notify = lpm, \ + .offload_channel = offload, \ + .doorbell_mode_switch = modeswitch, \ + .wake_capable = false, \ + .auto_queue = false, \ + .local_elements = 0, \ + .type = ch_type, \ + } + +#define MHI_CHANNEL_CONFIG_DL(ch_num, ch_name, elems, ev_ring, ee, \ + dbmode, lpm, poll, offload, modeswitch, \ + wake, autoq, local_el, ch_type) \ + { \ + .dir = DMA_FROM_DEVICE, \ + .num = ch_num, \ + .name = ch_name, \ + .num_elements = elems, \ + .event_ring = ev_ring, \ + .ee_mask = BIT(ee), \ + .pollcfg = poll, \ + .doorbell = dbmode, \ + .lpm_notify = lpm, \ + .offload_channel = offload, \ + .doorbell_mode_switch = modeswitch, \ + .wake_capable = wake, \ + .auto_queue = autoq, \ + .local_elements = local_el, \ + .type = ch_type, \ + } + +#define MHI_EVENT_CONFIG(ev_ring, ev_irq, type, num_elems, int_mod, \ + prio, dbmode, hw, cl_manage, offload, ch_num) \ + { \ + .num_elements = num_elems, \ + .irq_moderation_ms = int_mod, \ + .irq = ev_irq, \ + .priority = prio, \ + .mode = dbmode, \ + .data_type = type, \ + .hardware_event = hw, \ + .client_managed = cl_manage, \ + .offload_channel = offload, \ + .channel = ch_num, \ + } + +static const struct mhi_channel_config modem_qcom_sdx65_mhi_channels[] = { + /* SBL channels */ + MHI_CHANNEL_CONFIG_UL(2, "SAHARA", 128, 1, MHI_EE_SBL, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(3, "SAHARA", 128, 1, MHI_EE_SBL, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_DL(25, "BL", 32, 1, MHI_EE_SBL, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + /* AMSS channels */ + MHI_CHANNEL_CONFIG_UL(0, "LOOPBACK", 64, 2, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(1, "LOOPBACK", 64, 2, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(4, "DIAG", 64, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(5, "DIAG", 64, 3, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(8, "QDSS", 64, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(9, "QDSS", 64, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(10, "EFS", 64, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + /* wake-capable */ + MHI_CHANNEL_CONFIG_DL(11, "EFS", 64, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + true, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(14, "QMI0", 64, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(15, "QMI0", 64, 2, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(16, "QMI1", 64, 3, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(17, "QMI1", 64, 3, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(18, "IP_CTRL", 64, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + /* auto-queue */ + MHI_CHANNEL_CONFIG_DL(19, "IP_CTRL", 64, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, true, 0, 0), + MHI_CHANNEL_CONFIG_UL(20, "IPCR", 32, 2, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + /* auto-queue */ + MHI_CHANNEL_CONFIG_DL(21, "IPCR", 32, 2, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, true, 0, 0), + MHI_CHANNEL_CONFIG_UL(26, "DCI", 64, 3, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(27, "DCI", 64, 3, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(32, "DUN", 64, 3, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(33, "DUN", 64, 3, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(80, "AUDIO_VOICE_0", 32, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(81, "AUDIO_VOICE_0", 32, 1, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(100, "IP_HW0", 512, 6, MHI_EE_AMSS, + MHI_DB_BRST_ENABLE, false, 0, false, true, 0), + MHI_CHANNEL_CONFIG_DL(101, "IP_HW0", 512, 7, MHI_EE_AMSS, + MHI_DB_BRST_ENABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_DL(102, "IP_HW_ADPL", 1, 8, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, true, 0, true, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_DL(103, "IP_HW_QDSS", 1, 9, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_DL(104, "IP_HW0_RSC", 512, 7, MHI_EE_AMSS, + MHI_DB_BRST_ENABLE, false, 0, false, false, + false, false, 3078, + MHI_CH_TYPE_INBOUND_COALESCED), + MHI_CHANNEL_CONFIG_UL(105, "RMNET_DATA_LL", 512, 10, MHI_EE_AMSS, + MHI_DB_BRST_ENABLE, false, 0, false, true, 0), + MHI_CHANNEL_CONFIG_DL(106, "RMNET_DATA_LL", 512, 10, MHI_EE_AMSS, + MHI_DB_BRST_ENABLE, false, 0, false, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(107, "IP_HW_MHIP_1", 1, 11, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, true, true, 0), + MHI_CHANNEL_CONFIG_DL(108, "IP_HW_MHIP_1", 1, 12, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, true, 0, true, false, + false, false, 0, 0), + MHI_CHANNEL_CONFIG_UL(109, "RMNET_CTL", 128, 13, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, 0), + MHI_CHANNEL_CONFIG_DL(110, "RMNET_CTL", 128, 14, MHI_EE_AMSS, + MHI_DB_BRST_DISABLE, false, 0, false, false, + false, false, 0, 0), +}; + +static struct mhi_event_config modem_qcom_sdx65_mhi_events[] = { + MHI_EVENT_CONFIG(0, 1, MHI_ER_CTRL, 64, 0, + MHI_ER_PRIORITY_HI_NOSLEEP, MHI_DB_BRST_DISABLE, false, + false, false, 0), + MHI_EVENT_CONFIG(1, 2, MHI_ER_DATA, 256, 0, + MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_DISABLE, + false, false, false, 0), + MHI_EVENT_CONFIG(2, 3, MHI_ER_DATA, 256, 0, + MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_DISABLE, + false, false, false, 0), + MHI_EVENT_CONFIG(3, 4, MHI_ER_DATA, 256, 0, + MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_DISABLE, + false, false, false, 0), + MHI_EVENT_CONFIG(4, 5, MHI_ER_BW_SCALE, 64, 0, + MHI_ER_PRIORITY_HI_SLEEP, MHI_DB_BRST_DISABLE, false, + false, false, 0), + MHI_EVENT_CONFIG(5, 6, MHI_ER_TIMESYNC, 64, 0, + MHI_ER_PRIORITY_HI_SLEEP, MHI_DB_BRST_DISABLE, false, + false, false, 0), + /* Hardware channels request dedicated hardware event rings */ + MHI_EVENT_CONFIG(6, 7, MHI_ER_DATA, 1024, 5, + MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_ENABLE, + true, false, false, 100), + MHI_EVENT_CONFIG(7, 7, MHI_ER_DATA, 2048, 5, + MHI_ER_PRIORITY_DEFAULT_NOSLEEP, + MHI_DB_BRST_ENABLE, true, true, false, 101), + MHI_EVENT_CONFIG(8, 8, MHI_ER_DATA, 0, 0, + MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_ENABLE, + true, true, true, 102), + MHI_EVENT_CONFIG(9, 9, MHI_ER_DATA, 1024, 5, + MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_DISABLE, + true, false, false, 103), + MHI_EVENT_CONFIG(10, 10, MHI_ER_DATA, 1024, 1, + MHI_ER_PRIORITY_HI_NOSLEEP, MHI_DB_BRST_ENABLE, true, + false, false, 0), + MHI_EVENT_CONFIG(11, 11, MHI_ER_DATA, 0, 0, + MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_ENABLE, + true, true, true, 107), + MHI_EVENT_CONFIG(12, 12, MHI_ER_DATA, 0, 0, + MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_ENABLE, + true, true, true, 108), + MHI_EVENT_CONFIG(13, 13, MHI_ER_DATA, 1024, 1, + MHI_ER_PRIORITY_HI_NOSLEEP, MHI_DB_BRST_DISABLE, true, + false, false, 109), + MHI_EVENT_CONFIG(14, 15, MHI_ER_DATA, 1024, 0, + MHI_ER_PRIORITY_HI_NOSLEEP, MHI_DB_BRST_DISABLE, true, + false, false, 110), +}; + +static const struct mhi_controller_config modem_qcom_sdx65_mhi_config = { + .max_channels = 128, + .timeout_ms = 2000, + .buf_len = 0x8000, + .num_channels = ARRAY_SIZE(modem_qcom_sdx65_mhi_channels), + .ch_cfg = modem_qcom_sdx65_mhi_channels, + .num_events = ARRAY_SIZE(modem_qcom_sdx65_mhi_events), + .event_cfg = modem_qcom_sdx65_mhi_events, +}; + +static const struct mhi_pci_dev_info mhi_qcom_sdx65_info = { + .device_id = 0x0308, + .name = "esoc0", + .fw_image = "sdx65m/xbl.elf", + .edl_image = "sdx65m/edl.mbn", + .config = &modem_qcom_sdx65_mhi_config, + .bar_num = MHI_PCI_BAR_NUM, + .dma_data_width = 64, + .allow_m1 = false, + .skip_forced_suspend = true, + .sfr_support = true, + .timesync = true, + .drv_support = false, +}; + +static const struct mhi_pci_dev_info mhi_qcom_debug_info = { + .device_id = MHI_PCIE_DEBUG_ID, + .name = "qcom-debug", + .fw_image = "debug.mbn", + .edl_image = "debug.mbn", + .config = &modem_qcom_sdx65_mhi_config, + .bar_num = MHI_PCI_BAR_NUM, + .dma_data_width = 64, + .allow_m1 = true, + .skip_forced_suspend = true, + .sfr_support = false, + .timesync = false, + .drv_support = false, +}; + +static const struct pci_device_id mhi_pcie_device_id[] = { + { PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0308), + .driver_data = (kernel_ulong_t) &mhi_qcom_sdx65_info }, + { PCI_DEVICE(MHI_PCIE_VENDOR_ID, MHI_PCIE_DEBUG_ID), + .driver_data = (kernel_ulong_t) &mhi_qcom_debug_info }, + { } +}; +MODULE_DEVICE_TABLE(pci, mhi_pcie_device_id); + +static enum mhi_debug_mode debug_mode; + +const char * const mhi_debug_mode_str[MHI_DEBUG_MODE_MAX] = { + [MHI_DEBUG_OFF] = "Debug mode OFF", + [MHI_DEBUG_ON] = "Debug mode ON", + [MHI_DEBUG_NO_LPM] = "Debug mode - no LPM", +}; + +const char * const mhi_suspend_mode_str[MHI_SUSPEND_MODE_MAX] = { + [MHI_ACTIVE_STATE] = "Active", + [MHI_DEFAULT_SUSPEND] = "Default", + [MHI_FAST_LINK_OFF] = "Fast Link Off", + [MHI_FAST_LINK_ON] = "Fast Link On", +}; + +static int mhi_qcom_power_up(struct mhi_controller *mhi_cntrl); + +static int mhi_link_status(struct mhi_controller *mhi_cntrl) +{ + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + u16 dev_id; + int ret; + + /* try reading device IDs, a mismatch could indicate a link down */ + ret = pci_read_config_word(pci_dev, PCI_DEVICE_ID, &dev_id); + + return (ret || dev_id != pci_dev->device) ? -EIO : 0; +} + +static int mhi_qcom_read_reg(struct mhi_controller *mhi_cntrl, + void __iomem *addr, u32 *out) +{ + u32 tmp = readl_relaxed(addr); + + if (PCI_INVALID_READ(tmp) && mhi_link_status(mhi_cntrl)) + return -EIO; + + *out = tmp; + + return 0; +} + +static void mhi_qcom_write_reg(struct mhi_controller *mhi_cntrl, + void __iomem *addr, u32 val) +{ + writel_relaxed(val, addr); +} + +static u64 mhi_qcom_time_get(struct mhi_controller *mhi_cntrl) +{ + return mhi_arch_time_get(mhi_cntrl); +} + +static int mhi_qcom_lpm_disable(struct mhi_controller *mhi_cntrl) +{ + return mhi_arch_link_lpm_disable(mhi_cntrl); +} + +static int mhi_qcom_lpm_enable(struct mhi_controller *mhi_cntrl) +{ + return mhi_arch_link_lpm_enable(mhi_cntrl); +} + +static int mhi_debugfs_power_up(void *data, u64 val) +{ + struct mhi_controller *mhi_cntrl = data; + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + int ret; + + if (!val || mhi_priv->powered_on) + return -EINVAL; + + MHI_CNTRL_LOG("Trigger power up from %s\n", + TO_MHI_DEBUG_MODE_STR(debug_mode)); + + ret = mhi_qcom_power_up(mhi_cntrl); + if (ret) { + MHI_CNTRL_ERR("Failed to power up MHI\n"); + return ret; + } + + mhi_priv->powered_on = true; + + return ret; +} +DEFINE_DEBUGFS_ATTRIBUTE(debugfs_power_up_fops, NULL, + mhi_debugfs_power_up, "%llu\n"); + +static int mhi_debugfs_trigger_m0(void *data, u64 val) +{ + struct mhi_controller *mhi_cntrl = data; + + MHI_CNTRL_LOG("Trigger M3 Exit\n"); + pm_runtime_get(mhi_cntrl->cntrl_dev); + pm_runtime_put(mhi_cntrl->cntrl_dev); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(debugfs_trigger_m0_fops, NULL, + mhi_debugfs_trigger_m0, "%llu\n"); + +static int mhi_debugfs_trigger_m3(void *data, u64 val) +{ + struct mhi_controller *mhi_cntrl = data; + + MHI_CNTRL_LOG("Trigger M3 Entry\n"); + pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); + pm_request_autosuspend(mhi_cntrl->cntrl_dev); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(debugfs_trigger_m3_fops, NULL, + mhi_debugfs_trigger_m3, "%llu\n"); + +static int mhi_debugfs_disable_pci_lpm_get(void *data, u64 *val) +{ + struct mhi_controller *mhi_cntrl = data; + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + + *val = mhi_priv->disable_pci_lpm; + + MHI_CNTRL_LOG("PCIe low power modes (D3 hot/cold) are %s\n", + mhi_priv->disable_pci_lpm ? "Disabled" : "Enabled"); + + return 0; +} + +static int mhi_debugfs_disable_pci_lpm_set(void *data, u64 val) +{ + struct mhi_controller *mhi_cntrl = data; + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + + mutex_lock(&mhi_cntrl->pm_mutex); + mhi_priv->disable_pci_lpm = val ? true : false; + mutex_unlock(&mhi_cntrl->pm_mutex); + + MHI_CNTRL_LOG("%s PCIe low power modes (D3 hot/cold)\n", + val ? "Disabled" : "Enabled"); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(debugfs_pci_lpm_fops, mhi_debugfs_disable_pci_lpm_get, + mhi_debugfs_disable_pci_lpm_set, "%llu\n"); + +void mhi_deinit_pci_dev(struct pci_dev *pci_dev, + const struct mhi_pci_dev_info *dev_info) +{ + struct mhi_controller *mhi_cntrl = dev_get_drvdata(&pci_dev->dev); + + if (!mhi_cntrl) + return; + + pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); + pm_runtime_dont_use_autosuspend(mhi_cntrl->cntrl_dev); + pm_runtime_disable(mhi_cntrl->cntrl_dev); + + pci_free_irq_vectors(pci_dev); + kfree(mhi_cntrl->irq); + mhi_cntrl->irq = NULL; + iounmap(mhi_cntrl->regs); + mhi_cntrl->regs = NULL; + mhi_cntrl->reg_len = 0; + mhi_cntrl->nr_irqs = 0; + pci_clear_master(pci_dev); + pci_release_region(pci_dev, dev_info->bar_num); + pci_disable_device(pci_dev); +} + +static int mhi_init_pci_dev(struct pci_dev *pci_dev, + const struct mhi_pci_dev_info *dev_info) +{ + struct mhi_controller *mhi_cntrl = dev_get_drvdata(&pci_dev->dev); + phys_addr_t base; + int ret; + int i; + + if (!mhi_cntrl) + return -ENODEV; + + ret = pci_assign_resource(pci_dev, dev_info->bar_num); + if (ret) { + MHI_CNTRL_ERR("Error assign pci resources, ret: %d\n", ret); + return ret; + } + + ret = pci_enable_device(pci_dev); + if (ret) { + MHI_CNTRL_ERR("Error enabling device, ret: %d\n", ret); + goto error_enable_device; + } + + ret = pci_request_region(pci_dev, dev_info->bar_num, "mhi"); + if (ret) { + MHI_CNTRL_ERR("Error pci_request_region, ret: %d\n", ret); + goto error_request_region; + } + + pci_set_master(pci_dev); + + base = pci_resource_start(pci_dev, dev_info->bar_num); + mhi_cntrl->reg_len = pci_resource_len(pci_dev, dev_info->bar_num); + mhi_cntrl->regs = ioremap(base, mhi_cntrl->reg_len); + if (!mhi_cntrl->regs) { + MHI_CNTRL_ERR("Error ioremap region\n"); + goto error_ioremap; + } + + /* reserved MSI for BHI plus one for each event ring */ + mhi_cntrl->nr_irqs = dev_info->config->num_events + 1; + + ret = pci_alloc_irq_vectors(pci_dev, mhi_cntrl->nr_irqs, + mhi_cntrl->nr_irqs, PCI_IRQ_MSI); + if (IS_ERR_VALUE((ulong)ret) || ret < mhi_cntrl->nr_irqs) { + MHI_CNTRL_ERR("Failed to enable MSI, ret: %d\n", ret); + goto error_req_msi; + } + + mhi_cntrl->irq = kmalloc_array(mhi_cntrl->nr_irqs, + sizeof(*mhi_cntrl->irq), GFP_KERNEL); + if (!mhi_cntrl->irq) { + ret = -ENOMEM; + goto error_alloc_msi_vec; + } + + for (i = 0; i < mhi_cntrl->nr_irqs; i++) { + mhi_cntrl->irq[i] = pci_irq_vector(pci_dev, i); + if (mhi_cntrl->irq[i] < 0) { + ret = mhi_cntrl->irq[i]; + goto error_get_irq_vec; + } + } + + /* configure runtime pm */ + pm_runtime_set_autosuspend_delay(mhi_cntrl->cntrl_dev, + MHI_RPM_SUSPEND_TMR_MS); + pm_runtime_use_autosuspend(mhi_cntrl->cntrl_dev); + pm_suspend_ignore_children(mhi_cntrl->cntrl_dev, true); + + /* + * pci framework will increment usage count (twice) before + * calling local device driver probe function. + * 1st pci.c pci_pm_init() calls pm_runtime_forbid + * 2nd pci-driver.c local_pci_probe calls pm_runtime_get_sync + * Framework expect pci device driver to call + * pm_runtime_put_noidle to decrement usage count after + * successful probe and call pm_runtime_allow to enable + * runtime suspend. + */ + pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); + pm_runtime_put_noidle(mhi_cntrl->cntrl_dev); + + return 0; + +error_get_irq_vec: + kfree(mhi_cntrl->irq); + mhi_cntrl->irq = NULL; + +error_alloc_msi_vec: + pci_free_irq_vectors(pci_dev); + +error_req_msi: + iounmap(mhi_cntrl->regs); + +error_ioremap: + pci_clear_master(pci_dev); + +error_request_region: + pci_disable_device(pci_dev); + +error_enable_device: + pci_release_region(pci_dev, dev_info->bar_num); + + return ret; +} + +static int mhi_runtime_suspend(struct device *dev) +{ + struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + int ret = 0; + + MHI_CNTRL_LOG("Entered\n"); + + mutex_lock(&mhi_cntrl->pm_mutex); + + if (!mhi_priv->powered_on) { + MHI_CNTRL_LOG("Not fully powered, return success\n"); + mutex_unlock(&mhi_cntrl->pm_mutex); + return 0; + } + + ret = mhi_pm_suspend(mhi_cntrl); + + if (ret) { + MHI_CNTRL_LOG("Abort due to ret: %d\n", ret); + mhi_priv->suspend_mode = MHI_ACTIVE_STATE; + goto exit_runtime_suspend; + } + + mhi_priv->suspend_mode = MHI_DEFAULT_SUSPEND; + + ret = mhi_arch_link_suspend(mhi_cntrl); + + /* failed suspending link abort mhi suspend */ + if (ret) { + MHI_CNTRL_LOG("Failed to suspend link, abort suspend\n"); + mhi_pm_resume(mhi_cntrl); + mhi_priv->suspend_mode = MHI_ACTIVE_STATE; + } + +exit_runtime_suspend: + mutex_unlock(&mhi_cntrl->pm_mutex); + MHI_CNTRL_LOG("Exited with ret: %d\n", ret); + + return (ret < 0) ? -EBUSY : 0; +} + +static int mhi_runtime_idle(struct device *dev) +{ + struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); + + MHI_CNTRL_LOG("Entered returning -EBUSY\n"); + + /* + * RPM framework during runtime resume always calls + * rpm_idle to see if device ready to suspend. + * If dev.power usage_count count is 0, rpm fw will call + * rpm_idle cb to see if device is ready to suspend. + * if cb return 0, or cb not defined the framework will + * assume device driver is ready to suspend; + * therefore, fw will schedule runtime suspend. + * In MHI power management, MHI host shall go to + * runtime suspend only after entering MHI State M2, even if + * usage count is 0. Return -EBUSY to disable automatic suspend. + */ + return -EBUSY; +} + +static int mhi_runtime_resume(struct device *dev) +{ + int ret = 0; + struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + + MHI_CNTRL_LOG("Entered\n"); + + mutex_lock(&mhi_cntrl->pm_mutex); + + if (!mhi_priv->powered_on) { + MHI_CNTRL_LOG("Not fully powered, return success\n"); + mutex_unlock(&mhi_cntrl->pm_mutex); + return 0; + } + + /* turn on link */ + ret = mhi_arch_link_resume(mhi_cntrl); + if (ret) + goto rpm_resume_exit; + + /* transition to M0 state */ + if (mhi_priv->suspend_mode == MHI_DEFAULT_SUSPEND) + ret = mhi_pm_resume(mhi_cntrl); + else + ret = mhi_pm_fast_resume(mhi_cntrl, MHI_FAST_LINK_ON); + + mhi_priv->suspend_mode = MHI_ACTIVE_STATE; + +rpm_resume_exit: + mutex_unlock(&mhi_cntrl->pm_mutex); + MHI_CNTRL_LOG("Exited with ret: %d\n", ret); + + return (ret < 0) ? -EBUSY : 0; +} + +static int mhi_system_resume(struct device *dev) +{ + return mhi_runtime_resume(dev); +} + +int mhi_system_suspend(struct device *dev) +{ + struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; + int ret; + + MHI_CNTRL_LOG("Entered\n"); + + /* No DRV support - use regular suspends */ + if (!dev_info->drv_support) + return mhi_runtime_suspend(dev); + + mutex_lock(&mhi_cntrl->pm_mutex); + + if (!mhi_priv->powered_on) { + MHI_CNTRL_LOG("Not fully powered, return success\n"); + mutex_unlock(&mhi_cntrl->pm_mutex); + return 0; + } + + /* + * pci framework always makes a dummy vote to rpm + * framework to resume before calling system suspend + * hence usage count is minimum one + */ + if (atomic_read(&dev->power.usage_count) > 1) { + /* + * clients have requested to keep link on, try + * fast suspend. No need to notify clients since + * we will not be turning off the pcie link + */ + ret = mhi_pm_fast_suspend(mhi_cntrl, false); + mhi_priv->suspend_mode = MHI_FAST_LINK_ON; + } else { + /* try normal suspend */ + mhi_priv->suspend_mode = MHI_DEFAULT_SUSPEND; + ret = mhi_pm_suspend(mhi_cntrl); + + /* + * normal suspend failed because we're busy, try + * fast suspend before aborting system suspend. + * this could happens if client has disabled + * device lpm but no active vote for PCIe from + * apps processor + */ + if (ret == -EBUSY) { + ret = mhi_pm_fast_suspend(mhi_cntrl, true); + mhi_priv->suspend_mode = MHI_FAST_LINK_ON; + } + } + + if (ret) { + MHI_CNTRL_LOG("Abort due to ret: %d\n", ret); + mhi_priv->suspend_mode = MHI_ACTIVE_STATE; + goto exit_system_suspend; + } + + ret = mhi_arch_link_suspend(mhi_cntrl); + + /* failed suspending link abort mhi suspend */ + if (ret) { + MHI_CNTRL_LOG("Failed to suspend link, abort suspend\n"); + if (mhi_priv->suspend_mode == MHI_DEFAULT_SUSPEND) + mhi_pm_resume(mhi_cntrl); + else + mhi_pm_fast_resume(mhi_cntrl, MHI_FAST_LINK_OFF); + + mhi_priv->suspend_mode = MHI_ACTIVE_STATE; + } + +exit_system_suspend: + mutex_unlock(&mhi_cntrl->pm_mutex); + + MHI_CNTRL_LOG("Exited with ret: %d\n", ret); + + return ret; +} + +static int mhi_suspend_noirq(struct device *dev) +{ + return 0; +} + +static int mhi_resume_noirq(struct device *dev) +{ + return 0; +} + +static int mhi_force_suspend(struct mhi_controller *mhi_cntrl) +{ + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + int itr = DIV_ROUND_UP(mhi_cntrl->timeout_ms, 100); + int ret = -EIO; + + MHI_CNTRL_LOG("Entered\n"); + + mutex_lock(&mhi_cntrl->pm_mutex); + + for (; itr; itr--) { + /* + * This function get called soon as device entered mission mode + * so most of the channels are still in disabled state. However, + * sbl channels are active and clients could be trying to close + * channels while we trying to suspend the link. So, we need to + * re-try if MHI is busy + */ + ret = mhi_pm_suspend(mhi_cntrl); + if (!ret || ret != -EBUSY) + break; + + MHI_CNTRL_LOG("MHI busy, sleeping and retry\n"); + msleep(100); + } + + if (ret) { + MHI_CNTRL_ERR("Force suspend ret:%d\n", ret); + goto exit_force_suspend; + } + + mhi_priv->suspend_mode = MHI_DEFAULT_SUSPEND; + ret = mhi_arch_link_suspend(mhi_cntrl); + +exit_force_suspend: + mutex_unlock(&mhi_cntrl->pm_mutex); + + return ret; +} + +static int mhi_qcom_power_up(struct mhi_controller *mhi_cntrl) +{ + int ret; + + /* when coming out of SSR, initial states are not valid */ + mhi_cntrl->ee = MHI_EE_MAX; + mhi_cntrl->dev_state = MHI_STATE_RESET; + + ret = mhi_prepare_for_power_up(mhi_cntrl); + if (ret) + return ret; + + ret = mhi_async_power_up(mhi_cntrl); + if (ret) { + mhi_unprepare_after_power_down(mhi_cntrl); + return ret; + } + + if (mhi_cntrl->debugfs_dentry) { + debugfs_create_file("m0", 0444, mhi_cntrl->debugfs_dentry, mhi_cntrl, + &debugfs_trigger_m0_fops); + debugfs_create_file("m3", 0444, mhi_cntrl->debugfs_dentry, mhi_cntrl, + &debugfs_trigger_m3_fops); + debugfs_create_file("disable_pci_lpm", 0644, mhi_cntrl->debugfs_dentry, + mhi_cntrl, &debugfs_pci_lpm_fops); + } + + return ret; +} + +static int mhi_runtime_get(struct mhi_controller *mhi_cntrl) +{ + return pm_runtime_get(mhi_cntrl->cntrl_dev); +} + +static void mhi_runtime_put(struct mhi_controller *mhi_cntrl) +{ + pm_runtime_put_noidle(mhi_cntrl->cntrl_dev); +} + +static void mhi_runtime_last_busy(struct mhi_controller *mhi_cntrl) +{ + pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); +} + +static void mhi_status_cb(struct mhi_controller *mhi_cntrl, + enum mhi_callback reason) +{ + struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; + struct device *dev = mhi_cntrl->cntrl_dev; + int ret; + + switch (reason) { + case MHI_CB_IDLE: + MHI_CNTRL_LOG("Schedule runtime suspend\n"); + pm_runtime_mark_last_busy(dev); + pm_request_autosuspend(dev); + break; + case MHI_CB_EE_MISSION_MODE: + MHI_CNTRL_LOG("Mission mode entry\n"); + if (debug_mode == MHI_DEBUG_NO_LPM) { + mhi_arch_mission_mode_enter(mhi_cntrl); + MHI_CNTRL_LOG("Exit due to: %s\n", + TO_MHI_DEBUG_MODE_STR(debug_mode)); + break; + } + /* + * we need to force a suspend so device can switch to + * mission mode pcie phy settings. + */ + if (!dev_info->skip_forced_suspend) { + pm_runtime_get(dev); + ret = mhi_force_suspend(mhi_cntrl); + if (!ret) { + MHI_CNTRL_LOG("Resume after forced suspend\n"); + mhi_runtime_resume(dev); + } + pm_runtime_put(dev); + } + mhi_arch_mission_mode_enter(mhi_cntrl); + pm_runtime_allow(dev); + break; + default: + MHI_CNTRL_LOG("Unhandled cb: 0x%x\n", reason); + } +} + +/* Setting to use this mhi_qcom_pm_domain ops will let PM framework override the + * ops from dev->bus->pm which is pci_dev_pm_ops from pci-driver.c. This ops + * has to take care everything device driver needed which is currently done + * from pci_dev_pm_ops. + */ +static struct dev_pm_domain mhi_qcom_pm_domain = { + .ops = { + SET_SYSTEM_SLEEP_PM_OPS(mhi_system_suspend, mhi_system_resume) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(mhi_suspend_noirq, + mhi_resume_noirq) + SET_RUNTIME_PM_OPS(mhi_runtime_suspend, + mhi_runtime_resume, + mhi_runtime_idle) + } +}; + +#ifdef CONFIG_MHI_BUS_DEBUG + +#define MHI_QCOM_DEBUG_LEVEL MHI_MSG_LVL_VERBOSE + +static struct dentry *mhi_qcom_debugfs; + +static int mhi_qcom_debugfs_debug_mode_show(struct seq_file *m, void *d) +{ + seq_printf(m, "%s\n", TO_MHI_DEBUG_MODE_STR(debug_mode)); + + return 0; +} + +static ssize_t mhi_qcom_debugfs_debug_mode_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *m = file->private_data; + u32 input; + + if (kstrtou32_from_user(ubuf, count, 0, &input)) + return -EINVAL; + + if (input >= MHI_DEBUG_MODE_MAX) + return -EINVAL; + + debug_mode = input; + + seq_printf(m, "Changed debug mode to: %s\n", + TO_MHI_DEBUG_MODE_STR(debug_mode)); + + return count; +} + +static int mhi_qcom_debugfs_debug_mode_open(struct inode *inode, struct file *p) +{ + return single_open(p, mhi_qcom_debugfs_debug_mode_show, + inode->i_private); +} + +static const struct file_operations debugfs_debug_mode_fops = { + .open = mhi_qcom_debugfs_debug_mode_open, + .write = mhi_qcom_debugfs_debug_mode_write, + .release = single_release, + .read = seq_read, +}; + +void mhi_qcom_debugfs_init(void) +{ + mhi_qcom_debugfs = debugfs_create_dir("mhi_qcom", NULL); + + debugfs_create_file("debug_mode", 0644, mhi_qcom_debugfs, NULL, + &debugfs_debug_mode_fops); +} + +void mhi_qcom_debugfs_exit(void) +{ + debugfs_remove_recursive(mhi_qcom_debugfs); + mhi_qcom_debugfs = NULL; +} + +#else + +#define MHI_QCOM_DEBUG_LEVEL MHI_MSG_LVL_ERROR + +static inline void mhi_qcom_debugfs_init(void) +{ + +} + +static inline void mhi_qcom_debugfs_exit(void) +{ + +} + +#endif + +static int mhi_qcom_register_controller(struct mhi_controller *mhi_cntrl, + struct mhi_qcom_priv *mhi_priv) +{ + const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; + const struct mhi_controller_config *mhi_cntrl_config = dev_info->config; + struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); + struct device_node *of_node = pci_dev->dev.of_node; + struct mhi_device *mhi_dev; + bool use_s1; + u32 addr_win[2]; + const char *iommu_dma_type; + int ret; + + mhi_cntrl->iova_start = 0; + mhi_cntrl->iova_stop = DMA_BIT_MASK(dev_info->dma_data_width); + + of_node = of_parse_phandle(of_node, "qcom,iommu-group", 0); + if (of_node) { + use_s1 = true; + + /* + * s1 translation can be in bypass or fastmap mode + * if "qcom,iommu-dma" property is missing, we assume s1 is + * enabled and in default (no fastmap/atomic) mode + */ + ret = of_property_read_string(of_node, "qcom,iommu-dma", + &iommu_dma_type); + if (!ret && !strcmp("bypass", iommu_dma_type)) + use_s1 = false; + + /* + * if s1 translation enabled pull iova addr from dt using + * iommu-dma-addr-pool property specified addresses + */ + if (use_s1) { + ret = of_property_read_u32_array(of_node, + "qcom,iommu-dma-addr-pool", + addr_win, 2); + if (ret) { + of_node_put(of_node); + return -EINVAL; + } + + /* + * If S1 is enabled, set MHI_CTRL start address to 0 + * so we can use low level mapping api to map buffers + * outside of smmu domain + */ + mhi_cntrl->iova_start = 0; + mhi_cntrl->iova_stop = addr_win[0] + addr_win[1]; + } + + of_node_put(of_node); + } + + /* setup power management apis */ + mhi_cntrl->status_cb = mhi_status_cb; + mhi_cntrl->runtime_get = mhi_runtime_get; + mhi_cntrl->runtime_put = mhi_runtime_put; + mhi_cntrl->runtime_last_busy = mhi_runtime_last_busy; + mhi_cntrl->read_reg = mhi_qcom_read_reg; + mhi_cntrl->write_reg = mhi_qcom_write_reg; + + ret = mhi_register_controller(mhi_cntrl, mhi_cntrl_config); + if (ret) + return ret; + + mhi_cntrl->fw_image = dev_info->fw_image; + mhi_cntrl->edl_image = dev_info->edl_image; + + mhi_controller_set_privdata(mhi_cntrl, mhi_priv); + mhi_controller_set_loglevel(mhi_cntrl, MHI_QCOM_DEBUG_LEVEL); + mhi_controller_set_base(mhi_cntrl, + pci_resource_start(pci_dev, dev_info->bar_num)); + + if (dev_info->sfr_support) { + ret = mhi_controller_set_sfr_support(mhi_cntrl, + MHI_MAX_SFR_LEN); + if (ret) + goto error_register; + } + + if (dev_info->timesync) { + ret = mhi_controller_setup_timesync(mhi_cntrl, + &mhi_qcom_time_get, + &mhi_qcom_lpm_disable, + &mhi_qcom_lpm_enable); + if (ret) + goto error_register; + } + + if (dev_info->drv_support) + pci_dev->dev.pm_domain = &mhi_qcom_pm_domain; + + /* set name based on PCIe BDF format */ + mhi_dev = mhi_cntrl->mhi_dev; + dev_set_name(&mhi_dev->dev, "mhi_%04x_%02u.%02u.%02u", pci_dev->device, + pci_domain_nr(pci_dev->bus), pci_dev->bus->number, + PCI_SLOT(pci_dev->devfn)); + mhi_dev->name = dev_name(&mhi_dev->dev); + + mhi_priv->cntrl_ipc_log = ipc_log_context_create(MHI_IPC_LOG_PAGES, + dev_info->name, 0); + + return 0; + +error_register: + mhi_unregister_controller(mhi_cntrl); + + return -EINVAL; +} + +int mhi_qcom_pci_probe(struct pci_dev *pci_dev, + struct mhi_controller *mhi_cntrl, + struct mhi_qcom_priv *mhi_priv) +{ + const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; + int ret; + + dev_set_drvdata(&pci_dev->dev, mhi_cntrl); + mhi_cntrl->cntrl_dev = &pci_dev->dev; + + ret = mhi_init_pci_dev(pci_dev, dev_info); + if (ret) + return ret; + + /* driver removal boolen set to true indicates initial probe */ + if (mhi_priv->driver_remove) { + ret = mhi_qcom_register_controller(mhi_cntrl, mhi_priv); + if (ret) + goto error_init_pci; + } + + mhi_priv->powered_on = true; + + ret = mhi_arch_pcie_init(mhi_cntrl); + if (ret) + goto error_init_pci; + + ret = dma_set_mask_and_coherent(mhi_cntrl->cntrl_dev, + DMA_BIT_MASK(dev_info->dma_data_width)); + if (ret) + goto error_init_pci; + + if (debug_mode) { + if (mhi_cntrl->debugfs_dentry) + debugfs_create_file("power_up", 0644, + mhi_cntrl->debugfs_dentry, + mhi_cntrl, &debugfs_power_up_fops); + mhi_priv->powered_on = false; + return 0; + } + + /* start power up sequence */ + ret = mhi_qcom_power_up(mhi_cntrl); + if (ret) { + MHI_CNTRL_ERR("Failed to power up MHI\n"); + mhi_priv->powered_on = false; + goto error_power_up; + } + + pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); + + return 0; + +error_power_up: + mhi_arch_pcie_deinit(mhi_cntrl); + +error_init_pci: + mhi_deinit_pci_dev(pci_dev, dev_info); + + dev_set_drvdata(&pci_dev->dev, NULL); + mhi_cntrl->cntrl_dev = NULL; + + return ret; +} + +int mhi_pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) +{ + const struct mhi_pci_dev_info *dev_info = + (struct mhi_pci_dev_info *) id->driver_data; + struct mhi_controller *mhi_cntrl; + struct mhi_qcom_priv *mhi_priv; + u32 domain = pci_domain_nr(pci_dev->bus); + u32 bus = pci_dev->bus->number; + u32 dev_id = pci_dev->device; + u32 slot = PCI_SLOT(pci_dev->devfn); + int ret; + + /* see if we already registered */ + mhi_cntrl = mhi_bdf_to_controller(domain, bus, slot, dev_id); + if (!mhi_cntrl) { + mhi_cntrl = mhi_alloc_controller(); + if (!mhi_cntrl) + return -ENOMEM; + } + + mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + if (!mhi_priv) { + mhi_priv = kzalloc(sizeof(*mhi_priv), GFP_KERNEL); + if (!mhi_priv) + return -ENOMEM; + } + + /* set as true to initiate clean-up after first probe fails */ + mhi_priv->driver_remove = true; + mhi_priv->dev_info = dev_info; + + ret = mhi_qcom_pci_probe(pci_dev, mhi_cntrl, mhi_priv); + if (ret) { + kfree(mhi_priv); + mhi_free_controller(mhi_cntrl); + } + + return ret; +} + +void mhi_pci_remove(struct pci_dev *pci_dev) +{ + struct mhi_controller *mhi_cntrl; + struct mhi_qcom_priv *mhi_priv; + u32 domain = pci_domain_nr(pci_dev->bus); + u32 bus = pci_dev->bus->number; + u32 dev_id = pci_dev->device; + u32 slot = PCI_SLOT(pci_dev->devfn); + + /* see if we already registered */ + mhi_cntrl = mhi_bdf_to_controller(domain, bus, slot, dev_id); + if (!mhi_cntrl) + return; + + mhi_priv = mhi_controller_get_privdata(mhi_cntrl); + if (!mhi_priv) + return; + + /* if link is in suspend, wake it up */ + pm_runtime_get_sync(mhi_cntrl->cntrl_dev); + + if (mhi_priv->powered_on) { + MHI_CNTRL_LOG("Triggering shutdown process\n"); + mhi_power_down(mhi_cntrl, false); + mhi_unprepare_after_power_down(mhi_cntrl); + } + mhi_priv->powered_on = false; + + pm_runtime_put_noidle(mhi_cntrl->cntrl_dev); + + /* allow arch driver to free memory and unregister esoc if set */ + mhi_priv->driver_remove = true; + mhi_arch_pcie_deinit(mhi_cntrl); + + /* turn the link off */ + mhi_deinit_pci_dev(pci_dev, mhi_priv->dev_info); + + mhi_unregister_controller(mhi_cntrl); + kfree(mhi_priv); + mhi_free_controller(mhi_cntrl); +} + +static const struct dev_pm_ops pm_ops = { + SET_RUNTIME_PM_OPS(mhi_runtime_suspend, + mhi_runtime_resume, + mhi_runtime_idle) + SET_SYSTEM_SLEEP_PM_OPS(mhi_system_suspend, mhi_system_resume) +}; + +static struct pci_driver mhi_pcie_driver = { + .name = "mhi", + .id_table = mhi_pcie_device_id, + .probe = mhi_pci_probe, + .remove = mhi_pci_remove, + .driver = { + .pm = &pm_ops + } +}; + +static int __init mhi_qcom_init(void) +{ + int ret = 0; + + mhi_qcom_debugfs_init(); + ret = pci_register_driver(&mhi_pcie_driver); + if (ret) { + mhi_qcom_debugfs_exit(); + return ret; + } + + return 0; +} + +static void __exit mhi_qcom_exit(void) +{ + pci_unregister_driver(&mhi_pcie_driver); + mhi_qcom_debugfs_exit(); +} + +module_init(mhi_qcom_init); +module_exit(mhi_qcom_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("MHI_CORE"); +MODULE_DESCRIPTION("MHI Host Driver"); diff --git a/drivers/bus/mhi/controllers/mhi_qcom.h b/drivers/bus/mhi/controllers/mhi_qcom.h new file mode 100644 index 000000000000..93598028bbb1 --- /dev/null +++ b/drivers/bus/mhi/controllers/mhi_qcom.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ + +#ifndef _MHI_QCOM_ +#define _MHI_QCOM_ + +#define MHI_PCIE_VENDOR_ID (0x17cb) +#define MHI_PCIE_DEBUG_ID (0xffff) + +/* runtime suspend timer */ +#define MHI_RPM_SUSPEND_TMR_MS (250) +#define MHI_PCI_BAR_NUM (0) +#define MHI_MAX_SFR_LEN (256) + +#define PCI_INVALID_READ(val) ((val) == U32_MAX) + +#define MHI_IPC_LOG_PAGES (100) + +#define MHI_CNTRL_LOG(fmt, ...) do { \ + struct mhi_qcom_priv *mhi_priv = \ + mhi_controller_get_privdata(mhi_cntrl); \ + ipc_log_string(mhi_priv->cntrl_ipc_log, "[I][%s] " fmt, __func__, \ + ##__VA_ARGS__); \ +} while (0) + +#define MHI_CNTRL_ERR(fmt, ...) do { \ + struct mhi_qcom_priv *mhi_priv = \ + mhi_controller_get_privdata(mhi_cntrl); \ + pr_err("[E][%s] " fmt, __func__, ##__VA_ARGS__); \ + ipc_log_string(mhi_priv->cntrl_ipc_log, "[E][%s] " fmt, __func__, \ + ##__VA_ARGS__); \ +} while (0) + +extern const char * const mhi_ee_str[MHI_EE_MAX]; +#define TO_MHI_EXEC_STR(ee) (ee >= MHI_EE_MAX ? "INVALID_EE" : mhi_ee_str[ee]) + +enum mhi_debug_mode { + MHI_DEBUG_OFF, + MHI_DEBUG_ON, /* delayed power up */ + MHI_DEBUG_NO_LPM, /* delayed power up, low power modes disabled */ + MHI_DEBUG_MODE_MAX, +}; + +extern const char * const mhi_debug_mode_str[MHI_DEBUG_MODE_MAX]; +#define TO_MHI_DEBUG_MODE_STR(mode) \ + (mode >= MHI_DEBUG_MODE_MAX ? "Invalid" : mhi_debug_mode_str[mode]) + +enum mhi_suspend_mode { + MHI_ACTIVE_STATE, + MHI_DEFAULT_SUSPEND, + MHI_FAST_LINK_OFF, + MHI_FAST_LINK_ON, + MHI_SUSPEND_MODE_MAX, +}; + +extern const char * const mhi_suspend_mode_str[MHI_SUSPEND_MODE_MAX]; +#define TO_MHI_SUSPEND_MODE_STR(mode) \ + (mode >= MHI_SUSPEND_MODE_MAX ? "Invalid" : mhi_suspend_mode_str[mode]) + +/** + * struct mhi_pci_dev_info - MHI PCI device specific information + * @config: MHI controller configuration + * @device_id: PCI device ID + * @name: name of the PCI module + * @fw_image: firmware path (if any) + * @edl_image: emergency download mode firmware path (if any) + * @bar_num: PCI base address register to use for MHI MMIO register space + * @dma_data_width: DMA transfer word size (32 or 64 bits) + */ +struct mhi_pci_dev_info { + const struct mhi_controller_config *config; + unsigned int device_id; + const char *name; + const char *fw_image; + const char *edl_image; + unsigned int bar_num; + unsigned int dma_data_width; + bool allow_m1; + bool skip_forced_suspend; + bool sfr_support; + bool timesync; + bool drv_support; +}; + +struct mhi_qcom_priv { + const struct mhi_pci_dev_info *dev_info; + struct work_struct fatal_worker; + void *cntrl_ipc_log; + void *arch_info; + bool powered_on; + bool mdm_state; + bool disable_pci_lpm; + enum mhi_suspend_mode suspend_mode; + bool driver_remove; +}; + +void mhi_deinit_pci_dev(struct pci_dev *pci_dev, + const struct mhi_pci_dev_info *dev_info); +int mhi_qcom_pci_probe(struct pci_dev *pci_dev, + struct mhi_controller *mhi_cntrl, + struct mhi_qcom_priv *mhi_priv); + +#ifdef CONFIG_ARCH_QCOM + +int mhi_arch_pcie_init(struct mhi_controller *mhi_cntrl); +void mhi_arch_pcie_deinit(struct mhi_controller *mhi_cntrl); +int mhi_arch_link_suspend(struct mhi_controller *mhi_cntrl); +int mhi_arch_link_resume(struct mhi_controller *mhi_cntrl); +void mhi_arch_mission_mode_enter(struct mhi_controller *mhi_cntrl); +u64 mhi_arch_time_get(struct mhi_controller *mhi_cntrl); +int mhi_arch_link_lpm_disable(struct mhi_controller *mhi_cntrl); +int mhi_arch_link_lpm_enable(struct mhi_controller *mhi_cntrl); + +#else + +static inline int mhi_arch_pcie_init(struct mhi_controller *mhi_cntrl) +{ + return 0; +} + +static inline void mhi_arch_pcie_deinit(struct mhi_controller *mhi_cntrl) +{ +} + +static inline int mhi_arch_link_suspend(struct mhi_controller *mhi_cntrl) +{ + return 0; +} + +static inline int mhi_arch_link_resume(struct mhi_controller *mhi_cntrl) +{ + return 0; +} + +static inline void mhi_arch_mission_mode_enter(struct mhi_controller *mhi_cntrl) +{ +} + +static inline u64 mhi_arch_time_get(struct mhi_controller *mhi_cntrl) +{ + return 0; +} + +static inline int mhi_arch_link_lpm_disable(struct mhi_controller *mhi_cntrl) +{ + return 0; +} + +static inline int mhi_arch_link_lpm_enable(struct mhi_controller *mhi_cntrl) +{ + return 0; +} + +#endif + +#endif /* _MHI_QCOM_ */