// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.*/

#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-direction.h>
#include <linux/list.h>
#include <linux/of.h>
#include <linux/memblock.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/mhi.h>
#include <linux/mhi_misc.h>
#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_ENABLE, false, 0, false, true, 0),
	MHI_CHANNEL_CONFIG_DL(110, "RMNET_CTL", 128, 14, MHI_EE_AMSS,
			      MHI_DB_BRST_ENABLE, false, 0, false, false,
			      false, false, 0, 0),
};

static const 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 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_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)
		}
};

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->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_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)
};

#ifdef CONFIG_MHI_BUS_DEBUG
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

static inline void mhi_qcom_debugfs_init(void)
{

}

static inline void mhi_qcom_debugfs_exit(void)
{

}

#endif

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 v2");
MODULE_ALIAS("MHI_CORE");
MODULE_DESCRIPTION("MHI Host Driver");