mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 03:27:03 +02:00
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 <quic_lmotha@quicinc.com>
This commit is contained in:
parent
03de227439
commit
931f63edf2
15
drivers/bus/mhi/controllers/Kconfig
Normal file
15
drivers/bus/mhi/controllers/Kconfig
Normal file
|
|
@ -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
|
||||
4
drivers/bus/mhi/controllers/Makefile
Normal file
4
drivers/bus/mhi/controllers/Makefile
Normal file
|
|
@ -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
|
||||
719
drivers/bus/mhi/controllers/mhi_arch_qcom.c
Normal file
719
drivers/bus/mhi/controllers/mhi_arch_qcom.c
Normal file
|
|
@ -0,0 +1,719 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
|
||||
#include <asm/arch_timer.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-direction.h>
|
||||
#include <linux/esoc_client.h>
|
||||
#include <linux/interconnect.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/memblock.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/msm_pcie.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/mhi.h>
|
||||
#include <linux/mhi_misc.h>
|
||||
#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;
|
||||
}
|
||||
1266
drivers/bus/mhi/controllers/mhi_qcom.c
Normal file
1266
drivers/bus/mhi/controllers/mhi_qcom.c
Normal file
File diff suppressed because it is too large
Load Diff
156
drivers/bus/mhi/controllers/mhi_qcom.h
Normal file
156
drivers/bus/mhi/controllers/mhi_qcom.h
Normal file
|
|
@ -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_ */
|
||||
Loading…
Reference in New Issue
Block a user