/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
 * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/pm_wakeup.h>
#include <linux/rwsem.h>
#include <linux/suspend.h>
#include <linux/timer.h>
#include <linux/coresight.h>
#include <linux/remoteproc.h>
#include <linux/of_address.h>

#ifdef CONFIG_QCOM_SOCINFO
#include <soc/qcom/socinfo.h>
#endif
#include <linux/firmware.h>

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
#include <soc/qcom/subsystem_notif.h>
#endif

#ifdef KERNEL_SUPPORTS_QGIC2M
#include <soc/qcom/qgic2m.h>
#endif

#ifdef CONFIG_CNSS2_LEGACY_IRQ
#include "legacyirq/legacyirq.h"
#endif

#include "main.h"
#include "debug.h"
#include "pci.h"
#include "qmi.h"
#include "bus.h"
#include "genl.h"
#include "cnss_plat_ipc_qmi.h"
#ifdef CONFIG_CNSS2_KERNEL_5_15
#include <linux/devcoredump.h>
#include <linux/elf.h>
#include <linux/panic_notifier.h>
#else
#include <soc/qcom/ramdump.h>
#endif

#define CNSS_DUMP_FORMAT_VER		0x11
#define CNSS_DUMP_FORMAT_VER_V2		0x22
#define CNSS_DUMP_MAGIC_VER_V2		0x42445953
#define CNSS_DUMP_NAME			"CNSS_WLAN"
#define CNSS_DUMP_DESC_SIZE		0x1000
#define CNSS_MHI_SEG_LEN		SZ_512K
#define CNSS_DUMP_DESC_TOLERANCE	64
#define CNSS_DUMP_SEG_VER		0x1
#define CNSS_DUMP_SEG_VER_V2		0x2
#define WLAN_RECOVERY_DELAY		1000
#define FILE_SYSTEM_READY		1
#define FW_ASSERT_TIMEOUT		5000
#define CNSS_EVENT_PENDING		2989

#define CNSS_QUIRKS_DEFAULT		0
#ifdef CONFIG_CNSS_EMULATION
#define CNSS_MHI_TIMEOUT_DEFAULT	3600
#else
#define CNSS_MHI_TIMEOUT_DEFAULT	60
#endif
#define CNSS_QMI_TIMEOUT_DEFAULT	10000
#define CNSS_BDF_TYPE_DEFAULT		CNSS_BDF_ELF
#define CNSS_TIME_SYNC_PERIOD_DEFAULT	900000
#define DEFAULT_FW_FILE_NAME		"amss.bin"
#define DUALMAC_FW_FILE_NAME		"amss_dualmac.bin"

#define CNSS_INTX_SUPPORT_MASK          0xF
#define CNSS_INTX_SUPPORT_SHIFT         4

#define MAX_NUMBER_OF_SOCS 5
#define CNSS_PROBE_ORDER_MASK 0xF
#define CNSS_PROBE_ORDER_DEFAULT 0xFF
#define CNSS_PROBE_ORDER_SHIFT 4
#ifdef CONFIG_CNSS2_KERNEL_5_15
#define POWER_ON_RETRY_MAX_TIMES        4
#define POWER_ON_RETRY_DELAY_MS         500
#endif

struct cnss_plat_data *plat_env[MAX_NUMBER_OF_SOCS];
int plat_env_index;
struct cnss_mlo_group_info g_mlo_group_info[CNSS_MAX_MLO_GROUPS];
static DEFINE_SPINLOCK(plat_env_spinlock);
static DEFINE_SPINLOCK(rddm_spinlock);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0))
static DEFINE_MUTEX(rproc_list_mutex);
struct notifier_block panic_nb;
#endif

#ifdef CONFIG_CNSS2_PM
static DECLARE_RWSEM(cnss_pm_sem);
#endif

static unsigned int qmi_timeout;
module_param(qmi_timeout, uint, 0600);
MODULE_PARM_DESC(qmi_timeout, "Timeout for QMI message in milliseconds");

bool ramdump_enabled;
module_param(ramdump_enabled, bool, 0600);
MODULE_PARM_DESC(ramdump_enabled, "ramdump_enabled");

static int bdf_integrated;
module_param(bdf_integrated, int, 0644);
MODULE_PARM_DESC(bdf_integrated, "bdf_integrated");

static int bdf_pci0;
module_param(bdf_pci0, int, 0644);
MODULE_PARM_DESC(bdf_pci0, "bdf_pci0");

static int bdf_pci1;
module_param(bdf_pci1, int, 0644);
MODULE_PARM_DESC(bdf_pci1, "bdf_pci1");

static int bdf_pci2;
module_param(bdf_pci2, int, 0644);
MODULE_PARM_DESC(bdf_pci1, "bdf_pci2");

static int bdf_pci3;
module_param(bdf_pci3, int, 0644);
MODULE_PARM_DESC(bdf_pci1, "bdf_pci3");

int timeout_factor = 1;
module_param(timeout_factor, int, 0644);
MODULE_PARM_DESC(timeout_factor, "timeout_factor");

static unsigned int driver_mode;
module_param(driver_mode, uint, 0644);
MODULE_PARM_DESC(driver_mode, "Global driver mode");

static int skip_cnss;
module_param(skip_cnss, int, 0644);
MODULE_PARM_DESC(skip_cnss, "skip_cnss");

static int skip_radio_bmap;
module_param(skip_radio_bmap, int, 0644);
MODULE_PARM_DESC(skip_radio_bmap, "Bitmap to skip device probe");

static int disable_caldata_bmap;
module_param(disable_caldata_bmap, int, 0644);
MODULE_PARM_DESC(disable_caldata_bmap, "Bitmap to Disable Caldata download");

static int disable_regdb_bmap;
module_param(disable_regdb_bmap, int, 0644);
MODULE_PARM_DESC(disable_regdb_bmap, "Bitmap to Disable RegDB download");

/* probe_order needs to be defined in the format of hex.
 * The order of socX can be rearranged based on the given value.
 * For example, if default order is Soc0->Soc1->Soc2, then 0x213 will make
 * the order as Soc1->Soc0->Soc2.
 * If probe_order is 0 or not specified, then default order will be takes place.
 */
static unsigned int probe_order;
module_param(probe_order, uint, 0644);
MODULE_PARM_DESC(probe_order, "Probe order");

static int enable_intx_bmap;
module_param(enable_intx_bmap, int, 0644);
MODULE_PARM_DESC(enable_intx_bmap, "enable_intx_bmap");

#define FW_READY_DELAY	100  /* in msecs */

/* In platforms with low power CPU like IPQ5018 or SDX65, if CPU load
 * is high FW_READY might take longer than default value of 15s.
 * Increasing FW_READY timeout to 60s for IPQ5018 and SDX.
 */
#if defined(CONFIG_KASAN) || defined(CONFIG_IPQ_APSS_5018) || \
				defined(CONFIG_CNSS2_KERNEL_MSM)
static int fw_ready_timeout = 60;
static int cold_boot_cal_timeout = 180;
int rddm_done_timeout = 30;
#else
static int fw_ready_timeout = 15;
static int cold_boot_cal_timeout = 60;
int rddm_done_timeout = 15;
#endif
module_param(fw_ready_timeout, int, 0644);
MODULE_PARM_DESC(fw_ready_timeout, "fw ready timeout in seconds");

module_param(cold_boot_cal_timeout, int, 0644);
MODULE_PARM_DESC(cold_boot_cal_timeout, "Cold boot cal timeout in seconds");

module_param(rddm_done_timeout, int, 0644);
MODULE_PARM_DESC(rddm_done_timeout, "RDDM collection timeout in seconds");

static int soc_version_major;
module_param(soc_version_major, int, 0444);
MODULE_PARM_DESC(soc_version_major, "SOC Major Version");

static unsigned int enable_mlo_support = 1;
module_param(enable_mlo_support, uint, 0600);
MODULE_PARM_DESC(enable_mlo_support, "enable_mlo_support");

/* Temporary bootarg till driver ini changes are ready */
static unsigned int mlo_chip_bitmask = 0xFF;
module_param(mlo_chip_bitmask, uint, 0600);
MODULE_PARM_DESC(mlo_chip_bitmask, "mlo_chip_bitmask");

/* Experimental module param to avoid FW shutdown/power on after coldboot
 * calibration. Current FW does not support this and should not be enabled
 * without FW support for this feature
 */
static unsigned int soft_switch;
module_param(soft_switch, uint, 0600);
MODULE_PARM_DESC(soft_switch, "soft_switch");

static unsigned int probe_timeout = 200;
module_param(probe_timeout, uint, 0600);
MODULE_PARM_DESC(probe_timeout, "Timeout for cnss_wlan_probe_driver");

enum skip_cnss_options {
	CNSS_SKIP_NONE,
	CNSS_SKIP_ALL,
	CNSS_SKIP_AHB,
	CNSS_SKIP_PCI
};

#define SKIP_INTEGRATED		0x1
#define SKIP_PCI_0		0x2
#define SKIP_PCI_1		0x4
#define SKIP_PCI_2		0x8
#define SKIP_PCI_3		0x10

static struct cnss_fw_files FW_FILES_QCA6174_FW_3_0 = {
	"qwlan30.bin", "bdwlan30.bin", "otp30.bin", "utf30.bin",
	"utfbd30.bin", "epping30.bin", "evicted30.bin"
};

static struct cnss_fw_files FW_FILES_DEFAULT = {
	"qwlan.bin", "bdwlan.bin", "otp.bin", "utf.bin",
	"utfbd.bin", "epping.bin", "evicted.bin"
};

struct cnss_driver_event {
	struct list_head list;
	enum cnss_driver_event_type type;
	bool sync;
	struct completion complete;
	int ret;
	void *data;
};

/* M3 Dump related global structures/variables */
static int m3_dump_major;
static struct class *m3_dump_class;

atomic_t cal_in_progress_count;

#ifndef CONFIG_CNSS2_KERNEL_5_15
static int cnss_get_event(unsigned long subsys_event,
			  struct cnss_plat_data *plat_priv)
{
	int event = -EINVAL;

	switch (subsys_event) {
	case SUBSYS_BEFORE_SHUTDOWN:
		event = CNSS_BEFORE_SHUTDOWN;
		break;
	case SUBSYS_AFTER_SHUTDOWN:
		event = CNSS_AFTER_SHUTDOWN;
		break;
	case SUBSYS_BEFORE_POWERUP:
		event = CNSS_BEFORE_POWERUP;
		break;
	case SUBSYS_AFTER_POWERUP:
		event = CNSS_AFTER_POWERUP;
		break;
	case SUBSYS_RAMDUMP_NOTIFICATION:
		event = CNSS_RAMDUMP_NOTIFICATION;
		break;
	case SUBSYS_POWERUP_FAILURE:
		event = CNSS_POWERUP_FAILURE;
		break;
#ifdef CONFIG_CNSS2_KERNEL_IPQ
	case SUBSYS_PREPARE_FOR_FATAL_SHUTDOWN:
		event = CNSS_PREPARE_FOR_FATAL_SHUTDOWN;
		break;
#endif
	default:
		event = subsys_event;
		break;
	}
	return event;
}
#endif

void *cnss_get_pci_dev_by_device_id(int device_id)
{
	int i;

	for (i = 0; i < plat_env_index; i++) {
		if (plat_env[i]->device_id == device_id)
			return plat_env[i]->pci_dev;
	}
	return NULL;
}
EXPORT_SYMBOL(cnss_get_pci_dev_by_device_id);

void *cnss_get_plat_priv_dev_by_pci_dev(void *pci_dev)
{
	int i;

	for (i = 0; i < plat_env_index; i++) {
		if (plat_env[i]->pci_dev == pci_dev)
			return plat_env[i];
	}
	return NULL;
}

void *cnss_get_plat_dev_by_bus_dev(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return NULL;

	return plat_priv->plat_dev;
}
EXPORT_SYMBOL(cnss_get_plat_dev_by_bus_dev);

struct cnss_plat_data *cnss_get_plat_priv_by_qrtr_node_id(int node_id)
{
	int i;

	for (i = 0; i < plat_env_index; i++) {
		if (plat_env[i]->qrtr_node_id == node_id)
			return plat_env[i];
	}
	return NULL;
}


#ifdef CONFIG_CNSS2_PCI_NODT
struct cnss_plat_data *cnss_get_plat_priv_by_pci(unsigned char bus_number, unsigned int devfn)
{
	int i;

	for (i = 0; i < plat_env_index; i++) {
		if ((plat_env[i]->pci_bus_number == bus_number) && (plat_env[i]->pci_devfn == devfn))
			return plat_env[i];
	}
	return NULL;
}
#endif

struct cnss_plat_data *cnss_get_plat_priv_by_instance_id(int instance_id)
{
	int i;

	for (i = 0; i < plat_env_index; i++) {
		if (plat_env[i]->wlfw_service_instance_id == instance_id)
			return plat_env[i];
	}
	return NULL;
}
EXPORT_SYMBOL(cnss_get_plat_priv_by_instance_id);

void cnss_set_recovery_mode(struct device *dev, u8 recovery_mode)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return;

	cnss_pr_dbg("The recovery mode is %d\n", recovery_mode);
	plat_priv->recovery_mode = recovery_mode;

}
EXPORT_SYMBOL(cnss_set_recovery_mode);

void cnss_set_standby_mode(struct device *dev, u8 standby_mode)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return;

	cnss_pr_info("The standby mode is %d\n", standby_mode);
	plat_priv->standby_mode = standby_mode;

}
EXPORT_SYMBOL(cnss_set_standby_mode);

struct cnss_plat_data *cnss_get_plat_priv_by_device_id(int id)
{
	int i;

	for (i = 0; i < plat_env_index; i++) {
		if (plat_env[i]->device_id == id &&
		    !plat_env[i]->pci_dev)
			return plat_env[i];
	}
	return NULL;
}

struct cnss_plat_data *cnss_get_plat_priv(struct platform_device
						 *plat_dev)
{
	int i;

	if (!plat_dev)
		return NULL;

	for (i = 0; i < plat_env_index; i++) {
		if (plat_env[i]->plat_dev == plat_dev)
			return plat_env[i];
	}
	return NULL;
}

static void cnss_sort_probe_order(void)
{
	int i = 0;
	int j = 0;

	for (i = 0; i < plat_env_index; i++)
		for (j = i + 1; j < plat_env_index; j++)
			if (plat_env[i]->probe_order > plat_env[j]->probe_order)
				swap(plat_env[i], plat_env[j]);
}

struct cnss_plat_data *cnss_get_plat_priv_by_soc_id(int soc_id)
{
	if (soc_id >= MAX_NUMBER_OF_SOCS || soc_id >= plat_env_index)
		return NULL;

	cnss_sort_probe_order();
	return plat_env[soc_id];
}

int cnss_get_plat_env_index_from_plat_priv(struct cnss_plat_data *plat_priv)
{
	int i;

	if (!plat_priv)
		return -EINVAL;

	for (i = 0; i < plat_env_index; i++) {
		if (plat_env[i] == plat_priv)
			return i;
	}

	return -EINVAL;
}

const char *cnss_get_fw_path(struct cnss_plat_data *plat_priv)
{
	switch (plat_priv->device_id) {
	case QCA8074_DEVICE_ID:
	case QCA8074V2_DEVICE_ID:
		return "IPQ8074/";
	case QCA6018_DEVICE_ID:
		return "IPQ6018/";
	case QCA5018_DEVICE_ID:
		return "IPQ5018/";
	case QCA9574_DEVICE_ID:
		return "IPQ9574/";
	case QCA5332_DEVICE_ID:
		return "IPQ5332/";
	case QCN9000_DEVICE_ID:
		return "qcn9000/";
	case QCN6122_DEVICE_ID:
		return "qcn6122/";
	case QCN9160_DEVICE_ID:
		return "qcn9160/";
	case QCN9224_DEVICE_ID:
		return "qcn9224/";
	default:
		cnss_pr_err("No such device id 0x%lx\n", plat_priv->device_id);
	}

	return "UNKNOWN";
}

#ifdef CONFIG_CNSS2_PM
static int cnss_pm_notify(struct notifier_block *b,
			  unsigned long event, void *p)
{
	switch (event) {
	case PM_SUSPEND_PREPARE:
		down_write(&cnss_pm_sem);
		break;
	case PM_POST_SUSPEND:
		up_write(&cnss_pm_sem);
		break;
	}

	return NOTIFY_DONE;
}

static struct notifier_block cnss_pm_notifier = {
	.notifier_call = cnss_pm_notify,
};

static void cnss_pm_stay_awake(struct cnss_plat_data *plat_priv)
{
	if (atomic_inc_return(&plat_priv->pm_count) != 1)
		return;

	cnss_pr_dbg("PM stay awake, state: 0x%lx, count: %d\n",
		    plat_priv->driver_state,
		    atomic_read(&plat_priv->pm_count));
	pm_stay_awake(&plat_priv->plat_dev->dev);
}

static void cnss_pm_relax(struct cnss_plat_data *plat_priv)
{
	int r = atomic_dec_return(&plat_priv->pm_count);

	WARN_ON(r < 0);

	if (r != 0)
		return;

	cnss_pr_dbg("PM relax, state: 0x%lx, count: %d\n",
		    plat_priv->driver_state,
		    atomic_read(&plat_priv->pm_count));
	pm_relax(&plat_priv->plat_dev->dev);
}
void cnss_lock_pm_sem(struct device *dev)
{
	down_read(&cnss_pm_sem);
}
EXPORT_SYMBOL(cnss_lock_pm_sem);

void cnss_release_pm_sem(struct device *dev)
{
	up_read(&cnss_pm_sem);
}
EXPORT_SYMBOL(cnss_release_pm_sem);
#endif

int cnss_get_fw_files_for_target(struct device *dev,
				 struct cnss_fw_files *pfw_files,
				 u32 target_type, u32 target_version)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return -EINVAL;

	if (!pfw_files)
		return -ENODEV;

	switch (target_version) {
	case QCA6174_REV3_VERSION:
	case QCA6174_REV3_2_VERSION:
		memcpy(pfw_files, &FW_FILES_QCA6174_FW_3_0, sizeof(*pfw_files));
		break;
	default:
		memcpy(pfw_files, &FW_FILES_DEFAULT, sizeof(*pfw_files));
		cnss_pr_err("Unknown target version, type: 0x%X, version: 0x%X",
			    target_type, target_version);
		break;
	}

	return 0;
}
EXPORT_SYMBOL(cnss_get_fw_files_for_target);

int cnss_request_bus_bandwidth(struct device *dev, int bandwidth)
{
#ifdef CONFIG_MSM_PCI
	int ret;
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	struct cnss_bus_bw_info *bus_bw_info;

	if (!plat_priv)
		return -ENODEV;

	bus_bw_info = &plat_priv->bus_bw_info;
	if (!bus_bw_info->bus_client)
		return -EINVAL;

	switch (bandwidth) {
	case CNSS_BUS_WIDTH_NONE:
	case CNSS_BUS_WIDTH_IDLE:
	case CNSS_BUS_WIDTH_LOW:
	case CNSS_BUS_WIDTH_MEDIUM:
	case CNSS_BUS_WIDTH_HIGH:
	case CNSS_BUS_WIDTH_VERY_HIGH:
		ret = msm_bus_scale_client_update_request
			(bus_bw_info->bus_client, bandwidth);
		if (!ret)
			bus_bw_info->current_bw_vote = bandwidth;
		else
			cnss_pr_err("Could not set bus bandwidth: %d, err = %d\n",
				    bandwidth, ret);
		break;
	default:
		cnss_pr_err("Invalid bus bandwidth: %d", bandwidth);
		ret = -EINVAL;
	}

	return ret;
#endif
	return 0;
}
EXPORT_SYMBOL(cnss_request_bus_bandwidth);

int cnss_get_platform_cap(struct device *dev, struct cnss_platform_cap *cap)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return -ENODEV;

	if (cap)
		*cap = plat_priv->cap;

	return 0;
}
EXPORT_SYMBOL(cnss_get_platform_cap);

#ifndef CONFIG_CNSS2_KERNEL_5_15
void cnss_request_pm_qos(struct device *dev, u32 qos_val)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return;

	pm_qos_add_request(&plat_priv->qos_request, PM_QOS_CPU_DMA_LATENCY,
			   qos_val);
}
EXPORT_SYMBOL(cnss_request_pm_qos);

void cnss_remove_pm_qos(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return;

	pm_qos_remove_request(&plat_priv->qos_request);
}
EXPORT_SYMBOL(cnss_remove_pm_qos);
#endif

static int cnss_cal_db_mem_update(struct cnss_plat_data *plat_priv,
				  enum cnss_cal_db_op op, u32 *size)
{
	int ret = 0;
	char filename[50];

	u32 timeout = cnss_get_qmi_timeout(plat_priv)
		      + CNSS_DAEMON_CONNECT_TIMEOUT_MS;

	if (op >= CNSS_CAL_DB_INVALID_OP)
		return -EINVAL;

	if ((plat_priv->bus_type == CNSS_BUS_PCI) ||
	    (plat_priv->device_id == QCN6122_DEVICE_ID) ||
	    (plat_priv->device_id == QCN9160_DEVICE_ID))
		snprintf(filename, sizeof(filename),
			 CNSS_CAL_DB_FILE_PREFIX"_%s"CNSS_CAL_DB_FILE_SUFFIX,
			 plat_priv->device_name);
	else
		snprintf(filename, sizeof(filename),
			 CNSS_CAL_DB_FILE_PREFIX CNSS_CAL_DB_FILE_SUFFIX);

	cnss_pr_info("%s: File Operation %u size %u file name %s\n", __func__,
		     op, *size, filename);

	if (*size == 0) {
		cnss_pr_err("Invalid cal file size\n");
		return -EINVAL;
	}

	/* Ensure cnss-daemon is connected */
	if (!is_ipc_qmi_client_connected(CNSS_PLAT_IPC_DAEMON_QMI_CLIENT_V01,
					 timeout)) {
		cnss_pr_err("Daemon not yet connected\n");
		CNSS_ASSERT(0);
		return ret;
	}

	if (!plat_priv->cal_mem->va) {
		cnss_pr_err("CAL DB Memory not setup for FW\n");
		return -EINVAL;
	}

	/* Copy CAL DB file contents to/from CAL_TYPE_DDR mem allocated to FW */
	if (op == CNSS_CAL_DB_DOWNLOAD) {
		cnss_pr_dbg("Initiating Calibration file download to mem\n");
		ret = cnss_plat_ipc_qmi_file_download(
					 CNSS_PLAT_IPC_DAEMON_QMI_CLIENT_V01,
					 filename, plat_priv->cal_mem->va,
					 size);
	} else {
		cnss_pr_dbg("Initiating Calibration mem upload to file\n");
		ret = cnss_plat_ipc_qmi_file_upload(
					 CNSS_PLAT_IPC_DAEMON_QMI_CLIENT_V01,
					 filename, plat_priv->cal_mem->va,
					 *size);
	}

	if (ret)
		cnss_pr_err("Cal DB file %s %s failure\n",
			    filename,
			    op == CNSS_CAL_DB_DOWNLOAD ? "download" : "upload");
	else
		cnss_pr_dbg("Cal DB file %s %s size %d done\n",
			    filename,
			    op == CNSS_CAL_DB_DOWNLOAD ? "download" : "upload",
			    *size);

	return ret;
}

static int cnss_cal_mem_upload_to_file(struct cnss_plat_data *plat_priv)
{
	if (plat_priv->cal_file_size > plat_priv->cal_mem->size) {
		cnss_pr_err("Cal file size is larger than Cal DB Mem size\n");
		return -EINVAL;
	}
	return cnss_cal_db_mem_update(plat_priv, CNSS_CAL_DB_UPLOAD,
				      &plat_priv->cal_file_size);
}

int cnss_cal_file_download_to_mem(struct cnss_plat_data *plat_priv,
				  u32 *cal_file_size)
{
	/* To download pass the total size of cal DB mem allocated.
	 * After cal file is download to mem, its size is updated in
	 * return pointer
	 */
	*cal_file_size = plat_priv->cal_mem->size;
	return cnss_cal_db_mem_update(plat_priv, CNSS_CAL_DB_DOWNLOAD,
				      cal_file_size);
}
#ifdef CONFIG_CNSS2_KERNEL_5_15
static void cnss_hif_notifier(struct cnss_plat_data *plat_priv,
				enum cnss_notif_type code)
{
	struct cnss_wlan_driver *driver_ops = NULL;
	enum cnss_notif_type event_code = code;

	if (!plat_priv->cal_in_progress)
		driver_ops = plat_priv->driver_ops;

	if (event_code == CNSS_AFTER_POWERUP) {
		if (driver_ops) {
			driver_ops->probe((struct pci_dev *)plat_priv->plat_dev,
					  (const struct pci_device_id *)
					  plat_priv->plat_dev_id);
			set_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state);
		}
		clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state);
		clear_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state);
	} else if (event_code == CNSS_BEFORE_SHUTDOWN) {
		if (driver_ops) {
			driver_ops->remove(
					(struct pci_dev *)plat_priv->plat_dev);

			clear_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state);
			clear_bit(CNSS_DEV_ERR_NOTIFY,
					&plat_priv->driver_state);
		}
	} else if (event_code == CNSS_RAMDUMP_NOTIFICATION) {
		if (driver_ops) {
			driver_ops->reinit(
					(struct pci_dev *)plat_priv->plat_dev,
					(const struct pci_device_id *)
					plat_priv->plat_dev_id);

			clear_bit(CNSS_DRIVER_RECOVERY,
					&plat_priv->driver_state);
		}
	} else {
		if (driver_ops)
			driver_ops->update_status(
					(struct pci_dev *)plat_priv->plat_dev,
					(const struct pci_device_id *)
					plat_priv->plat_dev_id,
					(int)event_code);
	}
}

static int cnss_hif_power_up(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	if (!plat_priv)
		return -ENODEV;

	cnss_hif_notifier(plat_priv, CNSS_BEFORE_POWERUP);
	plat_priv->target_asserted = 0;
	plat_priv->target_assert_timestamp = 0;
	set_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state);
	ret = cnss_pci_probe(plat_priv->pci_dev,
			plat_priv->pci_dev_id,
			plat_priv);
	if (ret) {
		pr_err("ERROR : %s:%d ret %d\n", __func__, __LINE__, ret);
		return -ENODEV;
	}

	ret = cnss_bus_dev_powerup(plat_priv);
	if (ret) {
		cnss_pr_err("%s: cnss_bus_dev_powerup failed(%d)\n", __func__,
			ret);
		CNSS_ASSERT(0);
	}
	cnss_hif_notifier(plat_priv, CNSS_AFTER_POWERUP);

	return ret;
}


static int cnss_hif_shutdown(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	if (!plat_priv)
		return -ENODEV;

	cnss_hif_notifier(plat_priv, CNSS_BEFORE_SHUTDOWN);

	if (!plat_priv->driver_state) {
		cnss_pr_dbg("shutdown is ignored\n");
		return 0;
	}

#ifdef CONFIG_CNSS2_KERNEL_5_15
	if (plat_priv->bus_priv) {
#endif
	ret = cnss_bus_dev_shutdown(plat_priv);
	if (ret != 0) {
		cnss_pr_err("%s: cnss_bus_dev_shutdown failed(%d)\n", __func__,
			    ret);
	}

#ifdef CONFIG_CNSS2_KERNEL_5_15
	}
#endif
	cnss_hif_notifier(plat_priv, CNSS_AFTER_SHUTDOWN);

	return 0;
}

void *__cnss_hif_get(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	if (!plat_priv)
		return NULL;

	cnss_pr_info("%s: driver_state: 0x%lx\n", __func__,
		     plat_priv->driver_state);

	clear_bit(CNSS_RECOVERY_WAIT_FOR_DRIVER, &plat_priv->driver_state);
	ret = cnss_hif_power_up(plat_priv);
	if (ret) {
		cnss_pr_err("%s: cnss_hif_power_up failed %s\n", __func__,
			plat_priv->device_name);
		goto fail;
	}

	/* Return plat_priv for successful power up */
	return plat_priv;
fail:
	CNSS_ASSERT(0);
	return NULL;
}

void __cnss_hif_put(struct cnss_plat_data *plat_priv)
{

#if 1
	if (test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state)) {
		return;
	}
#endif
	set_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state);

	if (!plat_priv)
		return;

	cnss_hif_shutdown(plat_priv);
	plat_priv->driver_state = 0;
}
#endif

int __cnss_wlan_enable(struct cnss_plat_data *plat_priv,
		       struct cnss_wlan_enable_cfg *config,
		       enum cnss_driver_mode mode,
		       const char *host_version)
{
	int ret;
	u32 cal_file_size = 0;

	if (!plat_priv)
		return 0;

	cal_file_size = plat_priv->cal_file_size;

	if (plat_priv->device_id == QCA6174_DEVICE_ID)
		return 0;

	if (test_bit(QMI_BYPASS, &plat_priv->ctrl_params.quirks))
		return 0;

	if (mode == CNSS_CALIBRATION || mode == CNSS_WALTEST ||
	    mode == CNSS_FTM_CALIBRATION)
		goto skip_cfg;

	if (!config || !host_version) {
		cnss_pr_err("Invalid config or host_version pointer\n");
		return -EINVAL;
	}

	/* Set wmi diag logging */
	if (!(plat_priv->device_id == QCA8074_DEVICE_ID ||
	      plat_priv->device_id == QCA8074V2_DEVICE_ID ||
	      plat_priv->device_id == QCA6018_DEVICE_ID))
		cnss_wlfw_ini_send_sync(plat_priv, 1);

	cnss_pr_dbg("Mode: %d, config: %pK, host_version: %s\n",
		    mode, config, host_version);

	if (mode == CNSS_WALTEST || mode == CNSS_CCPM)
		goto skip_cfg;

	if (mode != CNSS_CALIBRATION && mode != CNSS_FTM_CALIBRATION) {
		ret = cnss_wlfw_wlan_cfg_send_sync(plat_priv, config,
						   host_version);
		if (ret)
			goto out;
	}

skip_cfg:

	if (mode == CNSS_CALIBRATION || mode == CNSS_FTM_CALIBRATION) {
		set_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state);
		/* Only check whether cnss-daemon is connected.
		 * It is not required to wait until it gets connected here.
		 * Hence pass the timeout value as 0.
		 */
		if (is_ipc_qmi_client_connected
				(CNSS_PLAT_IPC_DAEMON_QMI_CLIENT_V01, 0)) {
			cnss_pr_dbg("%s: cal_file_size %u !\n", __func__,
				    cal_file_size);
			cnss_wlfw_cal_report_req_send_sync(plat_priv,
							   cal_file_size);
			plat_priv->cal_time = jiffies;
		}
	}

	ret = cnss_wlfw_wlan_mode_send_sync(plat_priv, mode);

	if (plat_priv->qdss_support & (1 << mode)) {
		cnss_pr_info("Starting QDSS for %s\n", plat_priv->device_name);
		cnss_wlfw_qdss_dnld_send_sync(plat_priv);
	}

out:
	return ret;
}

int cnss_wlan_enable(struct device *dev,
		     struct cnss_wlan_enable_cfg *config,
		     enum cnss_driver_mode mode,
		     const char *host_version)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	return __cnss_wlan_enable(plat_priv, config, mode, host_version);

}
EXPORT_SYMBOL(cnss_wlan_enable);

int cnss_wlan_disable(struct device *dev, enum cnss_driver_mode mode)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return 0;

	if (plat_priv->device_id == QCA6174_DEVICE_ID)
		return 0;

	if (test_bit(QMI_BYPASS, &plat_priv->ctrl_params.quirks))
		return 0;

	return cnss_wlfw_wlan_mode_send_sync(plat_priv, CNSS_OFF);
}
EXPORT_SYMBOL(cnss_wlan_disable);

void cnss_set_led_gpio(int led_gpio, unsigned int value, unsigned int flags)
{
	struct gpio_desc *led_gpio_desc;

	led_gpio_desc = gpio_to_desc(led_gpio);

	/* IPQ9574 has active-low GPIO for WiFi LED.
	   Hence check and set the active-low status
	   appropriately from the GPIO flags.
	 */
	if ((flags & OF_GPIO_ACTIVE_LOW) &&
	    !gpiod_is_active_low(led_gpio_desc)) {
		gpiod_toggle_active_low(led_gpio_desc);
	}
	gpiod_set_value(led_gpio_desc, value);
}
EXPORT_SYMBOL(cnss_set_led_gpio);

int cnss_athdiag_read(struct device *dev, u32 offset, u32 mem_type,
		      u32 data_len, u8 *output)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	int ret = 0;

	if (!plat_priv) {
		pr_err("plat_priv is NULL!\n");
		return -EINVAL;
	}

	if (plat_priv->device_id == QCA6174_DEVICE_ID)
		return 0;

	if (!output || data_len == 0 || data_len > QMI_WLFW_MAX_DATA_SIZE_V01) {
		cnss_pr_err("Inv param athdiag read: output %p, data_len %u\n",
			    output, data_len);
		ret = -EINVAL;
		goto out;
	}

	if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) {
		cnss_pr_err("Invalid state for athdiag read: 0x%lx\n",
			    plat_priv->driver_state);
		ret = -EINVAL;
		goto out;
	}

	ret = cnss_wlfw_athdiag_read_send_sync(plat_priv, offset, mem_type,
					       data_len, output);

out:
	return ret;
}
EXPORT_SYMBOL(cnss_athdiag_read);

int cnss_athdiag_write(struct device *dev, u32 offset, u32 mem_type,
		       u32 data_len, u8 *input)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	int ret = 0;

	if (!plat_priv) {
		pr_err("plat_priv is NULL!\n");
		return -EINVAL;
	}

	if (plat_priv->device_id == QCA6174_DEVICE_ID)
		return 0;

	if (!input || data_len == 0 || data_len > QMI_WLFW_MAX_DATA_SIZE_V01) {
		cnss_pr_err("Inv param athdiag write: input %p, data_len %u\n",
			    input, data_len);
		ret = -EINVAL;
		goto out;
	}

	if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) {
		cnss_pr_err("Invalid state for athdiag write: 0x%lx\n",
			    plat_priv->driver_state);
		ret = -EINVAL;
		goto out;
	}

	ret = cnss_wlfw_athdiag_write_send_sync(plat_priv, offset, mem_type,
						data_len, input);

out:
	return ret;
}
EXPORT_SYMBOL(cnss_athdiag_write);

/* Return 0 if device is a multi-pd target.
 * Else return -ENODEV.
 */
int cnss_check_multipd_target(struct cnss_plat_data *plat_priv)
{
	if (!plat_priv)
		return -ENODEV;

	switch (plat_priv->device_id) {
	case QCA5018_DEVICE_ID:
	case QCN6122_DEVICE_ID:
	case QCA5332_DEVICE_ID:
	case QCA9574_DEVICE_ID:
	case QCN9160_DEVICE_ID:
		return 0;
	default:
		break;
	}
	return -ENODEV;
}

/* Return 0 if device ID is valid.
 * Else, return -EINVAL.
 */
int cnss_check_device_id_valid(struct cnss_plat_data *plat_priv)
{
	if (!plat_priv)
		return -ENODEV;

	switch (plat_priv->device_id) {
	case QCA8074_DEVICE_ID:
	case QCA8074V2_DEVICE_ID:
	case QCA6018_DEVICE_ID:
	case QCA9574_DEVICE_ID:
	case QCN9000_DEVICE_ID:
	case QCN9224_DEVICE_ID:
	case QCA5018_DEVICE_ID:
	case QCN6122_DEVICE_ID:
	case QCN9160_DEVICE_ID:
	case QCA5332_DEVICE_ID:
		return 0;
	default:
		cnss_pr_err("Invalid device id 0x%lx\n", plat_priv->device_id);
		break;
	}
	return -EINVAL;
}

static int cnss_fw_mem_ready_hdlr(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	if (!plat_priv)
		return -ENODEV;

	set_bit(CNSS_FW_MEM_READY, &plat_priv->driver_state);

	ret = cnss_wlfw_tgt_cap_send_sync(plat_priv);
	if (ret)
		goto out;

	if (plat_priv->device_id == QCN6122_DEVICE_ID ||
	    plat_priv->device_id == QCN9160_DEVICE_ID) {
		ret = cnss_wlfw_device_info_send_sync(plat_priv);
		if (ret) {
			cnss_pr_err("Device info msg failed. ret %d\n", ret);
			goto out;
		}
	}

	if (plat_priv->hds_support) {
		ret = cnss_wlfw_bdf_dnld_send_sync(plat_priv, CNSS_BDF_HDS);
		if (ret) {
			cnss_pr_err("hds load failed. ret %d\n", ret);
			goto out;
		}
	}

	if (plat_priv->regdb_support) {
		ret = cnss_wlfw_bdf_dnld_send_sync(plat_priv,
						   CNSS_BDF_REGDB);
		if (ret) {
			cnss_pr_err("regdb load failed. ret %d\n", ret);
			goto out;
		}
	}

	if (plat_priv->rxgainlut_support) {
		ret = cnss_wlfw_bdf_dnld_send_sync(plat_priv,
						   CNSS_BDF_RXGAINLUT);
		if (ret) {
			cnss_pr_err("rxgainlut load failed. ret %d\n", ret);
			goto out;
		}
	}

	ret = cnss_wlfw_bdf_dnld_send_sync(plat_priv, CNSS_BDF_WIN);
	if (ret) {
		cnss_pr_err("bdf load failed. ret %d\n", ret);
		goto out;
	}

	if (plat_priv->caldata_support) {
		ret = cnss_wlfw_bdf_dnld_send_sync(plat_priv, CNSS_CALDATA_WIN);
		if (ret) {
			cnss_pr_err("caldata load failed. ret %d\n", ret);
			goto out;
		}
	}

	if (plat_priv->device_id == QCN9000_DEVICE_ID ||
	    plat_priv->device_id == QCN9224_DEVICE_ID) {
		ret = cnss_bus_load_m3(plat_priv);
		if (ret) {
			cnss_pr_err("m3 load failed. ret %d\n", ret);
			goto out;
		}
	}

	ret = cnss_wlfw_m3_dnld_send_sync(plat_priv);
	if (ret) {
		cnss_pr_err("m3 dnld failed. ret %d\n", ret);
		goto out;
	}

	return 0;
out:
	return ret;
}

void cnss_get_ramdump_device_name(struct device *dev,
				  char *ramdump_dev_name,
				  size_t ramdump_dev_name_len)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	bool multi_pd_arch = false;
	const char *subsys_name;

	if (!plat_priv || !ramdump_dev_name)
		return;

	switch (plat_priv->device_id) {
	case QCA8074_DEVICE_ID:
	case QCA8074V2_DEVICE_ID:
	case QCA6018_DEVICE_ID:
		snprintf(ramdump_dev_name, ramdump_dev_name_len, "q6mem");
		break;
	case QCN9000_DEVICE_ID:
	case QCN9224_DEVICE_ID:
		snprintf(ramdump_dev_name, ramdump_dev_name_len, "ramdump_%s",
			 plat_priv->device_name);
		break;
	case QCA5018_DEVICE_ID:
	case QCA5332_DEVICE_ID:
	case QCN6122_DEVICE_ID:
	case QCA9574_DEVICE_ID:
	case QCN9160_DEVICE_ID:
		multi_pd_arch = of_property_read_bool(dev->of_node,
						      "qcom,multipd_arch");
		if (multi_pd_arch) {
			of_property_read_string(dev->of_node,
						"qcom,userpd-subsys-name",
						&subsys_name);
			snprintf(ramdump_dev_name, ramdump_dev_name_len,
				 "%s_mem", subsys_name);
		} else {
			snprintf(ramdump_dev_name, ramdump_dev_name_len,
				 "q6mem");
		}
		break;
	default:
		cnss_pr_info("%s: Unknown device_id 0x%lx",
			     __func__, plat_priv->device_id);
	}
	cnss_pr_dbg("Ramdump device name %s for device 0x%lx\n",
		    ramdump_dev_name, plat_priv->device_id);
}
EXPORT_SYMBOL(cnss_get_ramdump_device_name);

bool cnss_get_global_mlo_support(void)
{
	struct cnss_plat_data *plat_priv = NULL;
	int i;

#if 1
	unsigned int cnt = 0;
#endif

	for (i = 0; i < plat_env_index; i++) {
		plat_priv = plat_env[i];
		switch (plat_priv->device_id) {
		case QCN9224_DEVICE_ID:

#if 1
			cnt++;
			if (plat_priv->firmware_type == CNSS_FW_DUAL_MAC)
				cnt++;
			break;
#endif
		case QCA5332_DEVICE_ID:

#if 0
			return true;
#else
			cnt++;
			break;
#endif
		}
	}


#if 0
	return false;
#else
	cnss_pr_info("Found %u radio%s; %s MLO", cnt, cnt == 1 ? "" : "s", cnt > 1 ? "enabling" : "disabling");

	return cnt > 1;
#endif
}
EXPORT_SYMBOL(cnss_get_global_mlo_support);

static void cnss_set_global_mlo_support(bool enable)
{
	struct cnss_plat_data *plat_priv = NULL;
	int i;

	cnss_pr_info("%s MLO support..\n", enable ? "Enabling" : "Disabling");
	enable_mlo_support = enable;

	for (i = 0; i < plat_env_index; i++) {
		plat_priv = plat_env[i];
		switch (plat_priv->device_id) {
		case QCN9224_DEVICE_ID:
		case QCA5332_DEVICE_ID:
			plat_priv->mlo_support = enable;
			break;
		default:
			cnss_pr_dbg("MLO not supported for %s",
				    plat_priv->device_name);
		}
	}
}

static int cnss_set_static_mlo_config(struct cnss_mlo_group_info *in_group_info,
			int num_groups)
{
	struct cnss_mlo_group_info *mlo_group_info;
	struct cnss_mlo_group_info *group_info;
	struct cnss_mlo_chip_info *chip_info;
	struct cnss_plat_data *plat_priv = NULL;
	int i, j, k;

	if (!enable_mlo_support) {
		cnss_pr_info("%s: MLO is disabled\n", __func__);
		return 0;
	}

	if (num_groups > CNSS_MAX_MLO_GROUPS) {
		cnss_pr_err("%s: num_groups %d greater than max %d",
			    __func__, num_groups, CNSS_MAX_MLO_GROUPS);
		return -EINVAL;
	}

	for (i = 0; i < num_groups; i++) {
		mlo_group_info = &g_mlo_group_info[i];
		group_info = &in_group_info[i];

		if (group_info->num_chips > CNSS_MAX_MLO_CHIPS) {
			cnss_pr_err("%s: num_chips %d greater than max %d",
				    __func__, group_info->num_chips,
				    CNSS_MAX_MLO_CHIPS);
			return -EINVAL;
		}

		memset(mlo_group_info, 0, sizeof(struct cnss_mlo_group_info));
		mlo_group_info->group_id = group_info->group_id;
		mlo_group_info->max_num_peers = group_info->max_num_peers;
		mlo_group_info->num_chips = group_info->num_chips;

		for (j = 0; j < group_info->num_chips; j++) {
			chip_info = &mlo_group_info->chip_info[j];

			chip_info->group_id =
				 group_info->chip_info[j].group_id;
			chip_info->soc_id =
				group_info->chip_info[j].soc_id;
			chip_info->chip_id =
				group_info->chip_info[j].chip_id;
			chip_info->num_local_links =
				group_info->chip_info[j].num_local_links;

			for (k = 0; k < CNSS_MAX_LINKS_PER_CHIP; k++) {
				chip_info->hw_link_ids[k] =
				    group_info->chip_info[j].hw_link_ids[k];
				chip_info->valid_link_ids[k] =
				    group_info->chip_info[j].valid_link_ids[k];
			}

			plat_priv =
				cnss_get_plat_priv_by_soc_id(chip_info->soc_id);
			if (!plat_priv || !plat_priv->mlo_support)
				continue;

			plat_priv->mlo_group_info = mlo_group_info;
			plat_priv->mlo_chip_info = chip_info;
			plat_priv->mlo_capable = 1;
			plat_priv->mlo_default_cfg = true;

			cnss_pr_info("%s: Default MLO Config updated for %s",
				     __func__, plat_priv->device_name);
		}
	}

	return 0;
}

void cnss_reset_mlo_config(void)
{
	struct cnss_plat_data *plat_priv = NULL;
	int i = 0;

	if (!enable_mlo_support)
		return;

	memset(&g_mlo_group_info, 0, sizeof(struct cnss_mlo_group_info) *
	       CNSS_MAX_MLO_GROUPS);

	for (i = 0; i < plat_env_index; i++) {
		plat_priv = cnss_get_plat_priv_by_soc_id(i);
		if (!plat_priv) {
			cnss_pr_err("%s: Failed to get plat_priv for soc_id: %d",
				    __func__, i);
			continue;
		}

		if (!plat_priv->mlo_support ||
		    ((plat_priv->bus_type == CNSS_BUS_PCI) &&
		     !plat_priv->pci_dev)) {
			continue;
		}

		plat_priv->mlo_capable = 0;
	}
}
EXPORT_SYMBOL(cnss_reset_mlo_config);

struct cnss_plat_data *cnss_get_plat_priv_by_chip_id(int chip_id)
{
	struct cnss_plat_data *plat_priv = NULL;
	int i = 0;

	for (i = 0; i < plat_env_index; i++) {
		plat_priv = cnss_get_plat_priv_by_soc_id(i);
		if (!plat_priv) {
			cnss_pr_err("%s: Failed to get plat_priv for soc_id: %d",
				    __func__, i);
			continue;
		}

		if (!plat_priv->mlo_support ||
		    ((plat_priv->bus_type == CNSS_BUS_PCI) &&
		     !plat_priv->pci_dev))
			continue;

		if (!plat_priv->mlo_capable || !plat_env[i]->mlo_chip_info)
			continue;

		if (plat_env[i]->mlo_chip_info->chip_id == chip_id)
			return plat_env[i];
	}

	cnss_pr_err("plat_env is not found for chip %d", chip_id);
	return NULL;
}

static void cnss_set_adj_mlo_chips(struct cnss_mlo_group_info *mlo_group_info)
{
	struct cnss_plat_data *plat_priv = NULL;
	struct cnss_plat_data *adj_plat_priv = NULL;
	struct cnss_mlo_chip_info *chip_info;
	int mlo_chip_num = mlo_group_info->num_chips;
	int i = 0, k = 0, chip_idx = 0;

	for (i = 0; i < mlo_chip_num; i++) {
		chip_info = &mlo_group_info->chip_info[i];
		plat_priv = cnss_get_plat_priv_by_soc_id(chip_info->soc_id);

		if (!plat_priv)
			continue;

		for (k = 0; k < chip_info->num_adj_chips; k++) {
			chip_idx = chip_info->adj_chip_ids[k];
			adj_plat_priv = cnss_get_plat_priv_by_chip_id(chip_idx);
			if (adj_plat_priv)
				plat_priv->adj_mlo_chip_info[k] =
					adj_plat_priv->mlo_chip_info;
		}
	}
}

int cnss_set_mlo_config(struct cnss_module_param *modparam,
			struct cnss_mlo_group_info *in_mlo_config)
{
	struct cnss_mlo_group_info *mlo_group_info;
	struct cnss_mlo_group_info *mlo_config;
	struct cnss_mlo_chip_info *chip_info;
	struct cnss_plat_data *plat_priv = NULL;
	int num_chip = 0;
	int i, j, k;
	int prev_dual_count = 0;
	int link_id = 0;

	if (!enable_mlo_support) {
		cnss_pr_info("%s: MLO is disabled\n", __func__);
		return 0;
	}

	if (modparam->mlo_max_groups > CNSS_MAX_MLO_GROUPS) {
		cnss_pr_err("%s: num_groups %d greater than max %d",
			     __func__, modparam->mlo_max_groups,
			     CNSS_MAX_MLO_GROUPS);
		return -EINVAL;
	}

	for (i = 0; i < modparam->mlo_max_groups; i++) {
		mlo_group_info = &g_mlo_group_info[i];
		mlo_config = &in_mlo_config[i];

		if (mlo_config->num_chips > CNSS_MAX_MLO_CHIPS) {
			cnss_pr_err("%s: num_chips %d greater than max %d",
				    __func__, mlo_config->num_chips,
				    CNSS_MAX_MLO_CHIPS);
			return -EINVAL;
		}

		memset(mlo_group_info, 0, sizeof(struct cnss_mlo_group_info));
		mlo_group_info->group_id = i;
		mlo_group_info->max_num_peers = mlo_config->max_num_peers;
		mlo_group_info->num_chips = mlo_config->num_chips;
		mlo_group_info->soc_chip_bitmap = mlo_config->soc_chip_bitmap;
		num_chip = 0;

		for (j = 0; j < plat_env_index; j++) {
			plat_priv = cnss_get_plat_priv_by_soc_id(j);
			if (!plat_priv) {
				cnss_pr_err("%s: Failed to get plat_priv for soc_id: %d",
					    __func__, j);
				return -EINVAL;
			}

			if (!plat_priv->mlo_support ||
			    ((plat_priv->bus_type == CNSS_BUS_PCI) &&
			     !plat_priv->pci_dev)) {
				continue;
			}

			if (!(mlo_config->soc_chip_bitmap & (1 << j)))
				continue;

			chip_info = &mlo_group_info->chip_info[num_chip];
			chip_info->group_id = i;
			chip_info->soc_id = j;
			chip_info->chip_id = num_chip;

			if (plat_priv->firmware_type == CNSS_FW_DUAL_MAC) {
				chip_info->num_local_links = 2;
				prev_dual_count++;
				for (k = 0; k < CNSS_MAX_LINKS_PER_CHIP; k++) {
					chip_info->hw_link_ids[k] = link_id++;
					chip_info->valid_link_ids[k] = 1;
				}
			} else {
				chip_info->num_local_links = 1;
				chip_info->hw_link_ids[0] = link_id++;
				chip_info->valid_link_ids[0] = 1;
				chip_info->valid_link_ids[1] = 0;
			}

			chip_info->num_adj_chips =
				mlo_config->chip_info[num_chip].num_adj_chips;
			for (k = 0; k < chip_info->num_adj_chips; k++) {
				chip_info->adj_chip_ids[k] =
				mlo_config->chip_info[num_chip].adj_chip_ids[k];
			}

			plat_priv->mlo_group_info = mlo_group_info;
			plat_priv->mlo_chip_info = chip_info;
			plat_priv->mlo_capable = 1;
			plat_priv->mlo_default_cfg = false;
			num_chip++;

			cnss_pr_info("%s: Dynamic MLO Config updated for %s",
				     __func__, plat_priv->device_name);
			if (mlo_group_info->num_chips == num_chip)
				break;
		}
		cnss_set_adj_mlo_chips(mlo_group_info);
	}

	return 0;
}
EXPORT_SYMBOL(cnss_set_mlo_config);

static void cnss_print_chip_info(struct cnss_mlo_chip_info *chip_info)
{
	int i;

	pr_err("\nsoc_id: %u\n\t\tchip_id: %u\n\t\tgroup_id: %u\n\t\tnum_local_links: %u\n\t\tnum_adj_chips: %u\n",
	       chip_info->soc_id,
	       chip_info->chip_id,
	       chip_info->group_id,
	       chip_info->num_local_links,
	       chip_info->num_adj_chips);

	for (i = 0; i < CNSS_MAX_LINKS_PER_CHIP; i++)
		pr_err("\t\thw_link_ids[%d]: %u\n",
		       i, chip_info->hw_link_ids[i]);

	for (i = 0; i < CNSS_MAX_LINKS_PER_CHIP; i++)
		pr_err("\t\tvalid_link_ids[%d]: %u\n",
		       i, chip_info->valid_link_ids[i]);

	for (i = 0; i < CNSS_MAX_LINKS_PER_CHIP; i++)
		pr_err("\t\tadj_chip_ids[%d]: %u\n",
		       i, chip_info->adj_chip_ids[i]);
}

void cnss_print_mlo_config(void)
{
	struct cnss_mlo_group_info *mlo_group_info;
	struct cnss_mlo_chip_info *chip_info;
	int i, j;

	if (!enable_mlo_support) {
		pr_err("MLO is disabled!\n");
		return;
	}

	pr_err("\n****** CNSS MLO CONFIG ******\n");
	for (i = 0; i < CNSS_MAX_MLO_GROUPS; i++) {
		mlo_group_info = &g_mlo_group_info[i];

		pr_err("\ngroup_id: %u\nmax_num_peers: %u\nnum_chips: %u\nsoc_chip_bitmap: 0x%x\n",
		       mlo_group_info->group_id,
		       mlo_group_info->max_num_peers,
		       mlo_group_info->num_chips,
		       mlo_group_info->soc_chip_bitmap);

		for (j = 0; j < mlo_group_info->num_chips; j++) {
			chip_info = &mlo_group_info->chip_info[j];
			cnss_print_chip_info(chip_info);
		}
	}
}
EXPORT_SYMBOL(cnss_print_mlo_config);

int cnss_get_mlo_chip_id(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv || !plat_priv->mlo_support)
		return -EINVAL;

	if (!plat_priv->mlo_capable || !plat_priv->mlo_chip_info)
		return -EINVAL;

	return plat_priv->mlo_chip_info->chip_id;
}
EXPORT_SYMBOL(cnss_get_mlo_chip_id);

bool cnss_get_mlo_capable(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv || !plat_priv->mlo_support)
		return false;

	return plat_priv->mlo_capable;
}
EXPORT_SYMBOL(cnss_get_mlo_capable);

bool cnss_is_mlo_default_cfg_enabled(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv || !plat_priv->mlo_support)
		return false;

	return plat_priv->mlo_default_cfg;
}
EXPORT_SYMBOL(cnss_is_mlo_default_cfg_enabled);

int cnss_get_mlo_global_config_region_info(struct device *dev,
					   void **bar,
					   int *num_bytes)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	struct cnss_fw_mem *fw_mem;
	int i;

	if (!plat_priv || !plat_priv->mlo_support || !plat_priv->mlo_capable)
		return -EINVAL;

	*bar = 0;
	fw_mem = plat_priv->fw_mem;
	for (i = 0; i < plat_priv->fw_mem_seg_len; i++) {
		if (fw_mem[i].va) {
			if (fw_mem[i].type == QMI_WLFW_MLO_GLOBAL_MEM_V01) {
				*bar = plat_priv->fw_mem[i].va;
				*num_bytes = plat_priv->fw_mem[i].size;
			}
		}
	}

	if (!*bar) {
		cnss_pr_err("%s: no mlo mem found", __func__);
		return -EINVAL;
	}

	return 0;
}
EXPORT_SYMBOL(cnss_get_mlo_global_config_region_info);

int cnss_get_num_mlo_links(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv || !plat_priv->mlo_support)
		return -EINVAL;

	if (!plat_priv->mlo_capable || !plat_priv->mlo_chip_info)
		return -EINVAL;

	return plat_priv->mlo_chip_info->num_local_links;
}
EXPORT_SYMBOL(cnss_get_num_mlo_links);

int cnss_get_mlo_chip_info(struct device *dev,
			   struct cnss_mlo_chip_info **chip_info)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv || !plat_priv->mlo_support)
		return -EINVAL;

	if (!plat_priv->mlo_capable || !plat_priv->mlo_chip_info)
		return -EINVAL;

	*chip_info = plat_priv->mlo_chip_info;
	return 0;
}
EXPORT_SYMBOL(cnss_get_mlo_chip_info);

int cnss_get_num_mlo_capable_devices(unsigned int *device_id, int num_elements)
{
	struct cnss_plat_data *plat_priv = NULL;
	int num_capable = 0;
	int i;
	int device_count = 0;

	if (!enable_mlo_support)
		return -EINVAL;

	if (!device_id)
		return -EINVAL;

	for (i = 0; i < plat_env_index; i++) {
		plat_priv = plat_env[i];

		if (plat_priv && plat_priv->mlo_capable) {
			num_capable++;
			if (device_count < num_elements)
				device_id[device_count++] =
					plat_priv->device_id;
		}
	}

	return num_capable;
}
EXPORT_SYMBOL(cnss_get_num_mlo_capable_devices);

int cnss_get_num_mlo_groups(void)
{
	struct cnss_plat_data *plat_priv = NULL;
	int num_mlo_grp = 0;
	int i;
	int group_count = 0;

	if (!enable_mlo_support)
		return 0;

	for (i = 0; i < plat_env_index; i++) {
		plat_priv = plat_env[i];

		if (!plat_priv) {
			cnss_pr_err("%s: Failed to get plat_priv for soc_id %d",
				    __func__, i);
			continue;
		}

		if (!plat_priv->mlo_capable ||
		    ((plat_priv->bus_type == CNSS_BUS_PCI) &&
		     !plat_priv->pci_dev)) {
			continue;
		}

		group_count = plat_priv->mlo_group_info->group_id;
		if (group_count > num_mlo_grp)
			num_mlo_grp = group_count;
	}

	return ++num_mlo_grp;
}
EXPORT_SYMBOL(cnss_get_num_mlo_groups);

int cnss_get_mlo_group_id(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv || !plat_priv->mlo_support)
		return -EINVAL;

	if (!plat_priv->mlo_capable || !plat_priv->mlo_chip_info)
		return -EINVAL;

	return plat_priv->mlo_chip_info->group_id;
}
EXPORT_SYMBOL(cnss_get_mlo_group_id);

bool cnss_get_mlo_group_info(uint8_t grp_id,
			struct cnss_mlo_group_info *grp_info)
{
	if (grp_id < 0 || grp_id >= CNSS_MAX_MLO_GROUPS)
		return false;
	memcpy(grp_info, &g_mlo_group_info[grp_id],
		sizeof(struct cnss_mlo_group_info));

	return true;
}
EXPORT_SYMBOL(cnss_get_mlo_group_info);

int cnss_get_dev_link_ids(struct device *dev, u8 *link_ids, int max_elements)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	int i;

	if (!link_ids) {
		cnss_pr_err("link_ids buffer is null\n");
		return -ENOMEM;
	}

	if (max_elements < CNSS_MAX_LINKS_PER_CHIP) {
		cnss_pr_err("link ids size is less %d\n", max_elements);
		return -EINVAL;
	}

	if (!plat_priv || !plat_priv->mlo_support)
		return -EINVAL;

	if (!plat_priv->mlo_capable || !plat_priv->mlo_chip_info)
		return -EINVAL;

	memset(link_ids, 0, max_elements);

	for (i = 0; i < max_elements; i++)
		link_ids[i] = plat_priv->mlo_chip_info->hw_link_ids[i];

	return i;
}
EXPORT_SYMBOL(cnss_get_dev_link_ids);

static int cnss_get_group_id(struct cnss_plat_data *plat_priv)
{
	struct device *dev = &plat_priv->plat_dev->dev;
	int group_id = 0;

	if (of_property_read_u32(dev->of_node, "group_id", &group_id)) {
		cnss_pr_dbg("%s: Group ID not specified in the DTS. Setting the default group ID 0\n",
			    __func__);
		group_id = 0;
	}

	return group_id;
}

/* Temporary API to set default MLO config, will be removed once driver starts
 * setting MLO config via PLD.
 */
void cnss_set_default_mlo_config(void)
{
	struct cnss_mlo_group_info mlo_group_info[CNSS_MAX_MLO_GROUPS];
	struct cnss_mlo_chip_info *ch_info = NULL;
	struct cnss_plat_data *plat_priv = NULL;
	int num_chip = 0, i = 0, link_id = 0, group_id = 0;
	int grp_chip_id[CNSS_MAX_MLO_GROUPS] = {0};
	int grp_link_id[CNSS_MAX_MLO_GROUPS] = {0};
	int k = 0;

	if (!enable_mlo_support)
		return;

	memset(&mlo_group_info, 0, sizeof(struct cnss_mlo_group_info));

	for (i = 0; i < plat_env_index; i++) {
		plat_priv = cnss_get_plat_priv_by_soc_id(i);
		if (!plat_priv) {
			cnss_pr_err("%s: Failed to get plat_priv for soc_id: %d",
				    __func__, i);
			return;
		}

		if (!plat_priv->mlo_support ||
		    ((plat_priv->bus_type == CNSS_BUS_PCI) &&
		     !plat_priv->pci_dev))
			continue;

		group_id = cnss_get_group_id(plat_priv);
		if (group_id < 0 && group_id >= CNSS_MAX_MLO_GROUPS) {
			cnss_pr_err("%s: Invalid group id: %d", __func__,
				    group_id);
			return;
		}
		mlo_group_info[group_id].group_id = group_id;
		mlo_group_info[group_id].max_num_peers = 256;
		if (mlo_chip_bitmask & (1 << i)) {
			/*Temporarily Hard coding group id as 0 */
			num_chip = grp_chip_id[group_id];
			link_id = grp_link_id[group_id];

			ch_info = &mlo_group_info[group_id].chip_info[num_chip];

			ch_info->group_id = group_id;
			ch_info->soc_id = i;
			ch_info->chip_id = num_chip;

			if (plat_priv->firmware_type == CNSS_FW_DUAL_MAC) {
				ch_info->num_local_links = 2;
				for (k = 0; k < CNSS_MAX_LINKS_PER_CHIP; k++) {
					ch_info->hw_link_ids[k] = link_id + k;
					ch_info->valid_link_ids[k] = 1;
				}
				grp_link_id[group_id] = grp_link_id[group_id] +
							CNSS_MAX_LINKS_PER_CHIP;
			} else {
				ch_info->num_local_links = 1;
				ch_info->hw_link_ids[0] = link_id;
				ch_info->valid_link_ids[0] = 1;
				ch_info->valid_link_ids[1] = 0;
				grp_link_id[group_id] = grp_link_id[group_id] +
									1;
			}

			grp_chip_id[group_id] = grp_chip_id[group_id] + 1;
		}

		mlo_group_info[group_id].num_chips = grp_chip_id[group_id];
	}


	cnss_set_static_mlo_config(&mlo_group_info[0], group_id + 1);
	cnss_pr_info("Default MLO configuration is set!");
}
EXPORT_SYMBOL(cnss_set_default_mlo_config);

void __cnss_wait_for_fw_ready(struct cnss_plat_data *plat_priv)
{
	int count = 0;

	if (!cnss_check_device_id_valid(plat_priv)) {
		/* Device ID is valid */
		cnss_pr_info("Waiting for FW ready. Device: 0x%lx, FW ready timeout: %d seconds\n",
			     plat_priv->device_id, fw_ready_timeout);
		while (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) {
			msleep(FW_READY_DELAY);
			if (count++ > fw_ready_timeout * 10) {
				cnss_pr_err("FW ready timed-out %d seconds\n",
					    fw_ready_timeout);
				CNSS_ASSERT(0);
			}
		}
		cnss_pr_info("FW ready received for device 0x%lx\n",
			     plat_priv->device_id);
	}
}

void cnss_wait_for_fw_ready(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return;

	__cnss_wait_for_fw_ready(plat_priv);
}
EXPORT_SYMBOL(cnss_wait_for_fw_ready);

void cnss_wait_for_cold_boot_cal_done(struct cnss_plat_data *plat_priv)
{
	int count = 0;

	if (!plat_priv)
		return;

	if (!cnss_check_device_id_valid(plat_priv)) {
		/* Device ID is valid.
		 * Cold boot Calibration is done parallely for multiple devices
		 * Check if this device has already completed cold boot cal
		 * If already completed, we need not wait
		 */
		if (!test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state)) {
			cnss_pr_dbg("%s: Device already completed cold boot cal!\n",
				    __func__);
			return;
		}

		cnss_pr_info("Coldboot Calbration wait started for Device: 0x%lx, timeout: %d seconds\n",
			     plat_priv->device_id, cold_boot_cal_timeout);
		while (test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state)) {
			msleep(FW_READY_DELAY);
			if (count++ > cold_boot_cal_timeout * 10) {
				cnss_pr_err("Coldboot calibration timed out %d seconds\n",
					    cold_boot_cal_timeout);
				/* Collect the FW dump when there is no target
				 * assert instead coldboot timeout happens and
				 * host asserted.
				 */
				if (ramdump_enabled) {
					cnss_bus_collect_dump_info(plat_priv,
								   true);
					cnss_bus_dev_ramdump(plat_priv);
				}
				CNSS_ASSERT(0);
			}
		}
		cnss_pr_info("Coldboot Calibration wait ended for device 0x%lx\n",
			     plat_priv->device_id);
	}
}

void cnss_set_ramdump_enabled(struct device *dev, bool enabled)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv) {
		pr_err("%s: Failed to get plat_priv", __func__);
		return;
	}

	/* This is temporarily same as cnss_set_recovery_enabled until the
	 * wifi driver switches to use cnss_set_recovery_enabled.
	 */
	plat_priv->recovery_enabled = enabled;
	cnss_pr_dbg("Setting recovery_enabled to %d for %s\n",
		    plat_priv->recovery_enabled,
		    plat_priv->device_name);
}
EXPORT_SYMBOL(cnss_set_ramdump_enabled);

void cnss_set_recovery_enabled(struct device *dev, bool enabled)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv) {
		pr_err("%s: Failed to get plat_priv", __func__);
		return;
	}

	plat_priv->recovery_enabled = enabled;
	cnss_pr_dbg("Setting recovery_enabled to %d for %s\n",
		    plat_priv->recovery_enabled,
		    plat_priv->device_name);
}
EXPORT_SYMBOL(cnss_set_recovery_enabled);

static int cnss_fw_ready_hdlr(struct cnss_plat_data *plat_priv)
{
	if (!plat_priv)
		return -ENODEV;

	cnss_pr_dbg("%s:%d FW ready received for %s\n", __func__, __LINE__,
		    plat_priv->device_name);

	del_timer(&plat_priv->fw_boot_timer);
	set_bit(CNSS_FW_READY, &plat_priv->driver_state);
	clear_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state);

	if (test_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state)) {
		clear_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state);
		clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state);
	}

	return 0;
}

static char *cnss_driver_event_to_str(enum cnss_driver_event_type type)
{
	switch (type) {
	case CNSS_DRIVER_EVENT_SERVER_ARRIVE:
		return "SERVER_ARRIVE";
	case CNSS_DRIVER_EVENT_SERVER_EXIT:
		return "SERVER_EXIT";
	case CNSS_DRIVER_EVENT_REQUEST_MEM:
		return "REQUEST_MEM";
	case CNSS_DRIVER_EVENT_FW_MEM_READY:
		return "FW_MEM_READY";
	case CNSS_DRIVER_EVENT_FW_READY:
		return "FW_READY";
	case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START:
		return "COLD_BOOT_CAL_START";
	case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE:
		return "COLD_BOOT_CAL_DONE";
	case CNSS_DRIVER_EVENT_REGISTER_DRIVER:
		return "REGISTER_DRIVER";
	case CNSS_DRIVER_EVENT_UNREGISTER_DRIVER:
		return "UNREGISTER_DRIVER";
	case CNSS_DRIVER_EVENT_RECOVERY:
		return "RECOVERY";
	case CNSS_DRIVER_EVENT_FORCE_FW_ASSERT:
		return "FORCE_FW_ASSERT";
	case CNSS_DRIVER_EVENT_POWER_UP:
		return "POWER_UP";
	case CNSS_DRIVER_EVENT_POWER_DOWN:
		return "POWER_DOWN";
	case CNSS_DRIVER_EVENT_IDLE_RESTART:
		return "IDLE_RESTART";
	case CNSS_DRIVER_EVENT_IDLE_SHUTDOWN:
		return "IDLE_SHUTDOWN";
	case CNSS_DRIVER_EVENT_QDSS_TRACE_REQ_MEM:
		return "QDSS_TRACE_REQ_MEM";
	case CNSS_DRIVER_EVENT_QDSS_TRACE_SAVE:
		return "QDSS_TRACE_SAVE";
	case CNSS_DRIVER_EVENT_QDSS_TRACE_FREE:
		return "QDSS_TRACE_FREE";
	case CNSS_DRIVER_EVENT_QDSS_MEM_READY:
		return "QDSS_MEM_READY";
	case CNSS_DRIVER_EVENT_M3_DUMP_UPLOAD_REQ:
		return "M3_DUMP_UPLOAD_REQ";
	case CNSS_DRIVER_EVENT_QDSS_TRACE_REQ_DATA:
		return "QDSS_TRACE_REQ_DATA";
	case CNSS_DRIVER_EVENT_RAMDUMP_DONE:
		return "RAMDUMP_DONE";
	case CNSS_DRIVER_EVENT_MAX:
		return "EVENT_MAX";
	}

	return "UNKNOWN";
};

int cnss_driver_event_post(struct cnss_plat_data *plat_priv,
			   enum cnss_driver_event_type type,
			   u32 flags, void *data)
{
	struct cnss_driver_event *event;
	unsigned long irq_flags;
	int gfp = GFP_KERNEL;
	int ret = 0;

	if (!plat_priv)
		return -ENODEV;

	cnss_pr_dbg("Posting event[%p]: %s(%d)%s, state: 0x%lx flags: 0x%0x\n",
		    plat_priv,
		    cnss_driver_event_to_str(type), type,
		    flags ? "-sync" : "", plat_priv->driver_state, flags);

	if (type >= CNSS_DRIVER_EVENT_MAX) {
		cnss_pr_err("Invalid Event type: %d, can't post", type);
		return -EINVAL;
	}

	if (in_interrupt() || irqs_disabled())
		gfp = GFP_ATOMIC;

	event = kzalloc(sizeof(*event), gfp);
	if (!event)
		return -ENOMEM;

#ifdef CONFIG_CNSS2_PM
	cnss_pm_stay_awake(plat_priv);
#endif

	event->type = type;
	event->data = data;
	init_completion(&event->complete);
	event->ret = CNSS_EVENT_PENDING;
	event->sync = !!(flags & CNSS_EVENT_SYNC);

	spin_lock_irqsave(&plat_priv->event_lock, irq_flags);
	list_add_tail(&event->list, &plat_priv->event_list);
	spin_unlock_irqrestore(&plat_priv->event_lock, irq_flags);

	queue_work(plat_priv->event_wq, &plat_priv->event_work);

	if (!(flags & CNSS_EVENT_SYNC))
		goto out;

	printk(KERN_INFO "Waiting for Event(%s) to complete\n",
		cnss_driver_event_to_str(type));

	if (flags & CNSS_EVENT_UNINTERRUPTIBLE)
		wait_for_completion(&event->complete);
	else
		ret = wait_for_completion_interruptible(&event->complete);

	cnss_pr_dbg("Completed event: %s(%d), state: 0x%lx, ret: %d/%d\n",
		    cnss_driver_event_to_str(type), type,
		    plat_priv->driver_state, ret, event->ret);
	spin_lock_irqsave(&plat_priv->event_lock, irq_flags);
	if (ret == -ERESTARTSYS && event->ret == CNSS_EVENT_PENDING) {
		event->sync = false;
		spin_unlock_irqrestore(&plat_priv->event_lock, irq_flags);
		ret = -EINTR;
		goto out;
	}
	spin_unlock_irqrestore(&plat_priv->event_lock, irq_flags);

	ret = event->ret;
	kfree(event);

out:
#ifdef CONFIG_CNSS2_PM
	cnss_pm_relax(plat_priv);
#endif
	return ret;
}

unsigned int cnss_get_driver_mode(void)
{
	return driver_mode;
}
EXPORT_SYMBOL(cnss_get_driver_mode);

int cnss_set_driver_mode(unsigned int mode)
{
	switch (mode) {
	/* Fall through for all valid modes */
	case CNSS_MISSION:
	case CNSS_FTM:
	case CNSS_EPPING:
	case CNSS_WALTEST:
	case CNSS_OFF:
	case CNSS_CCPM:
	case CNSS_QVIT:
	case CNSS_CALIBRATION:
	case CNSS_FTM_CALIBRATION:
		driver_mode = mode;
		break;
	default:
		pr_err("%s: Invalid driver mode %d", __func__, mode);
		return -EINVAL;
	}

	/* MLO support needs to be enabled only for Mission mode */
	if (mode != CNSS_MISSION)
		cnss_set_global_mlo_support(false);

	return 0;
}
EXPORT_SYMBOL(cnss_set_driver_mode);

unsigned int cnss_get_boot_timeout(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL\n");
		return 0;
	}

	return cnss_get_qmi_timeout(plat_priv);
}
EXPORT_SYMBOL(cnss_get_boot_timeout);

int cnss_power_up(struct device *dev)
{
	int ret = 0;
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	unsigned int timeout;

	if (!plat_priv) {
		printk(KERN_ERR "plat_priv is NULL\n");
		return -ENODEV;
	}

	cnss_pr_dbg("Powering up device\n");

	ret = cnss_driver_event_post(plat_priv,
				     CNSS_DRIVER_EVENT_POWER_UP,
				     CNSS_EVENT_SYNC, NULL);
	if (ret)
		goto out;

	if (plat_priv->device_id == QCA6174_DEVICE_ID)
		goto out;

	timeout = cnss_get_boot_timeout(dev);

	reinit_completion(&plat_priv->power_up_complete);
	ret = wait_for_completion_timeout(&plat_priv->power_up_complete,
					  msecs_to_jiffies(timeout) << 2);
	if (!ret) {
		cnss_pr_err("Timeout waiting for power up to complete\n");
		ret = -EAGAIN;
		goto out;
	}

	return 0;

out:
	return ret;
}
EXPORT_SYMBOL(cnss_power_up);

int cnss_power_down(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv) {
		printk(KERN_ERR "plat_priv is NULL\n");
		return -ENODEV;
	}

	cnss_pr_dbg("Powering down device\n");

	return cnss_driver_event_post(plat_priv,
				      CNSS_DRIVER_EVENT_POWER_DOWN,
				      CNSS_EVENT_SYNC, NULL);
}
EXPORT_SYMBOL(cnss_power_down);

int cnss_idle_restart(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	unsigned int timeout;
	int ret = 0;

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL\n");
		return -ENODEV;
	}

	cnss_pr_dbg("Doing idle restart\n");

	ret = cnss_driver_event_post(plat_priv,
				     CNSS_DRIVER_EVENT_IDLE_RESTART,
				     CNSS_EVENT_SYNC_UNINTERRUPTIBLE, NULL);
	if (ret)
		goto out;

	if (plat_priv->device_id == QCA6174_DEVICE_ID) {
		ret = cnss_bus_call_driver_probe(plat_priv);
		goto out;
	}

	timeout = cnss_get_boot_timeout(dev);

	reinit_completion(&plat_priv->power_up_complete);
	ret = wait_for_completion_timeout(&plat_priv->power_up_complete,
					  msecs_to_jiffies(timeout) << 2);
	if (!ret) {
		cnss_pr_err("Timeout waiting for idle restart to complete\n");
		ret = -EAGAIN;
		goto out;
	}

	return 0;

out:
	return ret;
}
EXPORT_SYMBOL(cnss_idle_restart);

int cnss_idle_shutdown(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	int ret;

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL\n");
		return -ENODEV;
	}

	if (test_bit(CNSS_IN_SUSPEND_RESUME, &plat_priv->driver_state)) {
		cnss_pr_dbg("System suspend or resume in progress, ignore idle shutdown\n");
		return -EAGAIN;
	}

	cnss_pr_dbg("Doing idle shutdown\n");

	if (!test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state) &&
	    !test_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state))
		goto skip_wait;

	reinit_completion(&plat_priv->recovery_complete);
	ret = wait_for_completion_timeout(&plat_priv->recovery_complete,
					  RECOVERY_TIMEOUT);
	if (!ret) {
		cnss_pr_err("Timeout waiting for recovery to complete\n");
		CNSS_ASSERT(0);
	}

skip_wait:
	return cnss_driver_event_post(plat_priv,
				      CNSS_DRIVER_EVENT_IDLE_SHUTDOWN,
				      CNSS_EVENT_SYNC_UNINTERRUPTIBLE, NULL);
}
EXPORT_SYMBOL(cnss_idle_shutdown);

static int cnss_get_resources(struct cnss_plat_data *plat_priv)
{
#ifdef CNSS2_NOCLOCK
	int ret;

	ret = cnss_get_vreg_type(plat_priv, CNSS_VREG_PRIM);
	if (ret) {
		cnss_pr_err("Failed to get vreg, err = %d\n", ret);
		goto out;
	}

	ret = cnss_get_clk(plat_priv);
	if (ret) {
		cnss_pr_err("Failed to get clocks, err = %d\n", ret);
		goto put_vreg;
	}

	ret = cnss_get_pinctrl(plat_priv);
	if (ret) {
		cnss_pr_err("Failed to get pinctrl, err = %d\n", ret);
		goto put_clk;
	}

	return 0;

put_clk:
	cnss_put_clk(plat_priv);
put_vreg:
	cnss_put_vreg_type(plat_priv, CNSS_VREG_PRIM);
out:
	return ret;
#endif
	return 0;
}

static void cnss_put_resources(struct cnss_plat_data *plat_priv)
{
#ifdef CNSS2_NOCLOCK
	cnss_put_clk(plat_priv);
	cnss_put_vreg_type(plat_priv, CNSS_VREG_PRIM);
#endif
}

static int cnss_set_ssr_recovery_type(struct cnss_plat_data *plat_priv)
{
	if (!plat_priv)
		return -ENODEV;

	switch (plat_priv->device_id) {
	case QCA8074_DEVICE_ID:
	case QCA8074V2_DEVICE_ID:
	case QCA6018_DEVICE_ID:
	case QCA5018_DEVICE_ID:
	case QCN6122_DEVICE_ID:
	case QCA9574_DEVICE_ID:
	case QCN9000_DEVICE_ID:
		plat_priv->recovery_type = CNSS_ASYNC_RECOVERY;
		break;
	default:
		plat_priv->recovery_type = CNSS_SYNC_RECOVERY;
	}

	return 0;
}

#ifdef CONFIG_CNSS2_KERNEL_IPQ
static int cnss_qcn9000_notifier_atomic_nb(struct notifier_block *nb,
					   unsigned long code,
					   void *ss_handle)
{
	/* Fatal Notification to driver already sent as soon as
	 * MHI FATAL_ERR or SYS_ERR is received in cnss_mhi_notify_status
	 */
	return NOTIFY_OK;
}

static int cnss_qca8074_notifier_atomic_nb(struct notifier_block *nb,
	unsigned long code,
	void *ss_handle)
{
	struct cnss_plat_data *plat_priv =
		container_of(nb, struct cnss_plat_data, modem_atomic_nb);
	struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info;
	struct rproc *rproc;
	struct cnss_wlan_driver *driver_ops;
	int event_code = cnss_get_event(code, plat_priv);
	enum cnss_recovery_reason cnss_reason;
	driver_ops = plat_priv->driver_ops;

	if (event_code < 0)
		return NOTIFY_OK;

	if (event_code == CNSS_PREPARE_FOR_FATAL_SHUTDOWN) {
		cnss_pr_err("XXX TARGET ASSERTED XXX\n");
		cnss_pr_err("XXX TARGET %s instance_id 0x%x plat_env idx %d XXX\n",
			    plat_priv->device_name,
			    plat_priv->wlfw_service_instance_id,
			    cnss_get_plat_env_index_from_plat_priv(plat_priv));
		plat_priv->target_asserted = 1;
		plat_priv->target_assert_timestamp = ktime_to_ms(ktime_get());
		if (plat_priv->recovery_type == CNSS_SYNC_RECOVERY) {
			rproc = subsys_info->subsys_handle;
			if (rproc) {
				rproc->state = RPROC_CRASHED;
				cnss_reason = CNSS_REASON_FATAL_SHUTDOWN;
			       cnss_schedule_recovery(&plat_priv->plat_dev->dev,
							cnss_reason);
			}
		} else {
			driver_ops->fatal((struct pci_dev *)plat_priv->plat_dev,
					  (const struct pci_device_id *)
					  plat_priv->plat_dev_id);
		}
	}

	return NOTIFY_OK;
}
#endif

static int cnss_qcn9000_notifier_nb(struct notifier_block *nb,
				    unsigned long code,
				    void *ss_handle)
{
	struct cnss_plat_data *plat_priv =
		container_of(nb, struct cnss_plat_data, modem_nb);
	struct cnss_wlan_driver *driver_ops = NULL;
#ifndef CONFIG_CNSS2_KERNEL_5_15
	int event_code = cnss_get_event(code, plat_priv);
#else
	int event_code = code;
#endif

	if (!plat_priv->cal_in_progress)
		driver_ops = plat_priv->driver_ops;

	if (event_code < 0)
		return NOTIFY_OK;

	if (event_code == CNSS_AFTER_POWERUP) {
		if (driver_ops)
			driver_ops->probe((struct pci_dev *)plat_priv->plat_dev,
					  (const struct pci_device_id *)
					  plat_priv->plat_dev_id);
		clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state);
		clear_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state);
		set_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state);
	} else if (event_code == CNSS_BEFORE_SHUTDOWN) {
		if (driver_ops)
			driver_ops->remove(
					(struct pci_dev *)plat_priv->plat_dev);

		clear_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state);
		clear_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state);
	} else if (event_code == CNSS_RAMDUMP_NOTIFICATION) {
		if (driver_ops)
			driver_ops->reinit(
					(struct pci_dev *)plat_priv->plat_dev,
					(const struct pci_device_id *)
					plat_priv->plat_dev_id);

		clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state);
		return NOTIFY_DONE;
	} else {
		if (driver_ops)
			driver_ops->update_status(
					(struct pci_dev *)plat_priv->plat_dev,
					(const struct pci_device_id *)
					plat_priv->plat_dev_id, event_code);
	}

	return NOTIFY_OK;
}
#ifndef CONFIG_CNSS2_KERNEL_5_15
static int cnss_qca8074_notifier_nb(struct notifier_block *nb,
				  unsigned long code,
				  void *ss_handle)
{
	struct cnss_plat_data *plat_priv =
		container_of(nb, struct cnss_plat_data, modem_nb);
	struct cnss_wlan_driver *driver_ops = NULL;
	int event_code = cnss_get_event(code, plat_priv);

	if (!plat_priv->cal_in_progress)
		driver_ops = plat_priv->driver_ops;

	if (event_code < 0)
		return NOTIFY_OK;

	if (event_code == CNSS_AFTER_POWERUP) {
		if (driver_ops)
			driver_ops->probe((struct pci_dev *)plat_priv->plat_dev,
					  (const struct pci_device_id *)
					  plat_priv->plat_dev_id);
	} else if (event_code == CNSS_BEFORE_SHUTDOWN) {
		if (driver_ops)
			driver_ops->remove(
					(struct pci_dev *)plat_priv->plat_dev);
	} else if (event_code == CNSS_RAMDUMP_NOTIFICATION) {
#ifdef CONFIG_CNSS2_KERNEL_IPQ
		coresight_abort();
#endif
		if (driver_ops)
			driver_ops->reinit(
					(struct pci_dev *)plat_priv->plat_dev,
					(const struct pci_device_id *)
					plat_priv->plat_dev_id);
		return NOTIFY_DONE;
	} else {
		if (event_code == CNSS_AFTER_SHUTDOWN) {
			clear_bit(CNSS_FW_READY, &plat_priv->driver_state);
			clear_bit(CNSS_FW_MEM_READY, &plat_priv->driver_state);
			/* FW handles coresight settings for QDSS for all
			 * targets from 11be family onwards. Hence, clear QDSS
			 * state to get it started automatically after
			 * SSR recovery.
			 */
			if (plat_priv->device_id == QCA5332_DEVICE_ID)
				clear_bit(CNSS_QDSS_STARTED,
					  &plat_priv->driver_state);
			cnss_bus_free_fw_mem(plat_priv);
			cnss_bus_free_qdss_mem(plat_priv);
		}
		if (driver_ops)
			driver_ops->update_status(
					(struct pci_dev *)plat_priv->plat_dev,
					(const struct pci_device_id *)
					plat_priv->plat_dev_id, event_code);
		cnss_free_soc_info(plat_priv);
	}

	return NOTIFY_OK;
}

static int cnss_qca8074_rpd_notifier_atomic_nb(struct notifier_block *nb,
	unsigned long code,
	void *ss_handle)
{
	struct cnss_plat_data *plat_priv =
		container_of(nb, struct cnss_plat_data, rpd_atomic_nb);
	struct cnss_wlan_driver *driver_ops;
	int event_code = cnss_get_event(code, plat_priv);
	struct rproc *rproc_rpd;
	enum cnss_recovery_reason cnss_reason;

	driver_ops = plat_priv->driver_ops;

	if (event_code < 0)
		return NOTIFY_OK;

	if (event_code == CNSS_PREPARE_FOR_FATAL_SHUTDOWN) {
		cnss_pr_err("XXX TARGET ASSERTED XXX\n");
		cnss_pr_err("XXX TARGET %s instance_id 0x%x plat_env idx %d XXX\n",
			    plat_priv->device_name,
			    plat_priv->wlfw_service_instance_id,
			    cnss_get_plat_env_index_from_plat_priv(plat_priv));
		plat_priv->target_asserted = 1;
		plat_priv->target_assert_timestamp = ktime_to_ms(ktime_get());
		rproc_rpd = plat_priv->rproc_rpd_handle;
		if (rproc_rpd) {
			rproc_rpd->state = RPROC_CRASHED;
			cnss_reason = CNSS_REASON_FATAL_SHUTDOWN;
			cnss_schedule_recovery(&plat_priv->plat_dev->dev,
						cnss_reason);
		}
	}

	return NOTIFY_OK;

}

static int cnss_qca8074_rpd_notifier_nb(struct notifier_block *nb,
				  unsigned long code,
				  void *ss_handle)
{
	return NOTIFY_OK;
}

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
void *cnss_register_qcn9000_cb(struct cnss_plat_data *plat_priv)
{
	struct cnss_subsys_info *subsys_info;
	void *ss_handle = NULL;

	subsys_info = &plat_priv->subsys_info;
	subsys_info->subsys_desc.name = plat_priv->device_name;
	plat_priv->modem_nb.notifier_call = cnss_qcn9000_notifier_nb;
	ss_handle = subsys_notif_register_notifier(
		subsys_info->subsys_desc.name, &plat_priv->modem_nb);

	return ss_handle;
}

void *cnss_register_qca8074_cb(struct cnss_plat_data *plat_priv)
{
	struct cnss_subsys_info *subsys_info;
	void *ss_handle = NULL;

	subsys_info = &plat_priv->subsys_info;
	plat_priv->modem_nb.notifier_call = cnss_qca8074_notifier_nb;
	ss_handle = subsys_notif_register_notifier(
		subsys_info->subsys_desc.name, &plat_priv->modem_nb);

	return ss_handle;
}

int cnss_unregister_qca8074_cb(struct cnss_plat_data *plat_priv)
{
	void *handler = plat_priv->esoc_info.modem_notify_handler;

	if (handler) {
		subsys_notif_unregister_notifier(handler, &plat_priv->modem_nb);
		memset(&plat_priv->modem_nb, 0, sizeof(struct notifier_block));
		plat_priv->esoc_info.modem_notify_handler = NULL;
	}
	return 0;
}

int cnss_unregister_qcn9000_cb(struct cnss_plat_data *plat_priv)
{
	void *handler = plat_priv->esoc_info.modem_notify_handler;

	if (handler) {
		subsys_notif_unregister_notifier(handler, &plat_priv->modem_nb);
		memset(&plat_priv->modem_nb, 0, sizeof(struct notifier_block));
		plat_priv->esoc_info.modem_notify_handler = NULL;
	}
	return 0;
}

#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */

void *cnss_register_qcn9000_cb(struct cnss_plat_data *plat_priv)
{
	struct cnss_subsys_info *subsys_info;
	void *ss_handle = NULL;
	int ret = 0;

	subsys_info = &plat_priv->subsys_info;
	subsys_info->subsys_desc.name = plat_priv->device_name;

	plat_priv->modem_nb.notifier_call = cnss_qcn9000_notifier_nb;
	plat_priv->modem_atomic_nb.notifier_call =
				cnss_qcn9000_notifier_atomic_nb;
	ret = rproc_register_subsys_notifier(subsys_info->subsys_desc.name,
			&plat_priv->modem_nb, &plat_priv->modem_atomic_nb);
	if (ret) {
		cnss_pr_err("%s: failed to register rproc ret %d\n",
			    __func__, ret);
		return NULL;
	}
	ss_handle = subsys_info;

	return ss_handle;
}

void *cnss_register_qca8074_cb(struct cnss_plat_data *plat_priv)
{
	struct cnss_subsys_info *subsys_info;
	void *ss_handle = NULL;
	struct rproc *rproc_rpd;
	int ret = 0;

	subsys_info = &plat_priv->subsys_info;
	plat_priv->modem_nb.notifier_call = cnss_qca8074_notifier_nb;
	plat_priv->modem_atomic_nb.notifier_call =
					cnss_qca8074_notifier_atomic_nb;
	ret = rproc_register_subsys_notifier(subsys_info->subsys_desc.name,
			&plat_priv->modem_nb, &plat_priv->modem_atomic_nb);
	if (ret) {
		cnss_pr_err("%s: failed to register rproc ret %d\n",
			    __func__, ret);
		return NULL;
	}

	rproc_rpd = plat_priv->rproc_rpd_handle;
	if (rproc_rpd) {
		plat_priv->rpd_nb.notifier_call = cnss_qca8074_rpd_notifier_nb;
		plat_priv->rpd_atomic_nb.notifier_call =
			cnss_qca8074_rpd_notifier_atomic_nb;
		ret = rproc_register_subsys_notifier(rproc_rpd->name,
				&plat_priv->rpd_nb, &plat_priv->rpd_atomic_nb);
	}

	ss_handle = subsys_info;
	return ss_handle;
}

int cnss_unregister_qca8074_cb(struct cnss_plat_data *plat_priv)
{
	int ret = 0;
	struct rproc *rproc_rpd;

	if (plat_priv->modem_nb.notifier_call) {
		ret = rproc_unregister_subsys_notifier(
				plat_priv->subsys_info.subsys_desc.name,
				&plat_priv->modem_nb,
				&plat_priv->modem_atomic_nb);
		if (ret) {
			cnss_pr_err("%s: failed to unregister ret %d\n",
				    __func__, ret);
			return ret;
		}
		memset(&plat_priv->modem_nb, 0, sizeof(struct notifier_block));
		memset(&plat_priv->modem_atomic_nb, 0,
		       sizeof(struct notifier_block));
	}

	rproc_rpd = plat_priv->rproc_rpd_handle;
	if (rproc_rpd) {
		if (plat_priv->rpd_nb.notifier_call) {
			ret = rproc_unregister_subsys_notifier(
					rproc_rpd->name,
					&plat_priv->rpd_nb,
					&plat_priv->rpd_atomic_nb);
			if (ret) {
				cnss_pr_err("%s: failed to unregister rootpd ret %d\n",
						__func__, ret);
				return ret;
			}
			memset(&plat_priv->rpd_nb, 0,
					sizeof(struct notifier_block));
			memset(&plat_priv->rpd_atomic_nb, 0,
					sizeof(struct notifier_block));
		}
	}

	return 0;
}

int cnss_unregister_qcn9000_cb(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	if (plat_priv->modem_nb.notifier_call) {
		ret = rproc_unregister_subsys_notifier(
				plat_priv->subsys_info.subsys_desc.name,
				&plat_priv->modem_nb,
				&plat_priv->modem_atomic_nb);
		if (ret) {
			cnss_pr_err("%s: failed to unregister ret %d\n",
				    __func__, ret);
			return ret;
		}
		memset(&plat_priv->modem_nb, 0, sizeof(struct notifier_block));
		memset(&plat_priv->modem_atomic_nb, 0,
		       sizeof(struct notifier_block));
	}

	return 0;
}
#endif
#endif

void *cnss_register_notifier_cb(struct cnss_plat_data *plat_priv)
{
	switch (plat_priv->bus_type) {
	case CNSS_BUS_PCI:
#ifndef CONFIG_CNSS2_KERNEL_5_15
		return cnss_register_qcn9000_cb(plat_priv);
#else
		return 0;
#endif
#ifndef CONFIG_CNSS2_KERNEL_5_15
	case CNSS_BUS_AHB:
		return cnss_register_qca8074_cb(plat_priv);
#endif
	default:
		cnss_pr_err("Invalid bus type for %s", plat_priv->device_name);
	}
	return NULL;
}

int cnss_unregister_notifier_cb(struct cnss_plat_data *plat_priv)
{
	switch (plat_priv->bus_type) {
	case CNSS_BUS_PCI:
#ifndef CONFIG_CNSS2_KERNEL_5_15
		return cnss_unregister_qcn9000_cb(plat_priv);
#else
		return 0;
#endif
#ifndef CONFIG_CNSS2_KERNEL_5_15
	case CNSS_BUS_AHB:
		return cnss_unregister_qca8074_cb(plat_priv);
#endif
	default:
		cnss_pr_err("Invalid bus type for %s", plat_priv->device_name);
	}
	return 0;
}

int cnss_wlan_probe_driver(void)
{
	int ret;
	int i;
	int count = 0;
	struct cnss_plat_data *plat_priv = NULL;
	enum cnss_driver_mode cal_mode;

	cnss_sort_probe_order();
	for (i = 0; i < plat_env_index; i++) {
		plat_priv = plat_env[i];

		if (!plat_priv)
			continue;

		plat_priv->target_asserted = 0;
		plat_priv->target_assert_timestamp = 0;
		plat_priv->driver_status = CNSS_LOAD_UNLOAD;

		if (plat_priv->bus_type == CNSS_BUS_PCI) {
			/* If plat_priv->pci_dev is NULL, the PCI device is not
			 * enumerated, set driver status and skip that device
			 * so that other devices can continue to boot.
			 */
			if (!plat_priv->pci_dev) {
				plat_priv->driver_status = CNSS_INITIALIZED;
				continue;
			}
			cnss_pci_init(plat_priv);
			set_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state);
		}

		if (plat_priv->cold_boot_support && !plat_priv->cal_done)
			plat_priv->cal_in_progress = true;

		ret = cnss_register_subsys(plat_priv);
		if (ret)
			goto reset_ctx;


		if (plat_priv->cal_in_progress) {
			if (driver_mode == CNSS_FTM)
				cal_mode = CNSS_FTM_CALIBRATION;
			else
				cal_mode = CNSS_CALIBRATION;

			__cnss_wait_for_fw_ready(plat_priv);
			__cnss_wlan_enable(plat_priv, NULL, cal_mode, "WIN");

			schedule_work(&plat_priv->cal_work);
			atomic_inc(&cal_in_progress_count);
		} else {
			plat_priv->driver_status = CNSS_INITIALIZED;
		}
	}


	while (atomic_read(&cal_in_progress_count)) {
		msleep(FW_READY_DELAY);
		if (count++ > probe_timeout * 10) {
			cnss_pr_err("CNSS Driver probe timed out\n");
			CNSS_ASSERT(0);
		}
	}
	return 0;

reset_ctx:
	cnss_pr_err("Failed to get subsystem, err = %d\n", ret);
	plat_priv->driver_status = CNSS_UNINITIALIZED;
	plat_priv->driver_ops = NULL;
	return ret;
}
EXPORT_SYMBOL(cnss_wlan_probe_driver);


#ifdef CONFIG_CNSS2_LEGACY_IRQ
static int cnss_assign_lvirq(struct cnss_plat_data *plat_priv)
{
	plat_priv->lvirq = cnss_get_lvirq_by_qrtr_id(plat_priv->qrtr_node_id);
	if (!plat_priv->lvirq) {
		cnss_pr_err("lvirq pointer is NULL");
		CNSS_ASSERT(0);
		return -EINVAL;
	}
	return 0;
}
#endif

int cnss_wlan_register_driver_ops(struct cnss_wlan_driver *driver_ops)
{
	int i;
	struct cnss_plat_data *plat_priv;

	for (i = 0; i < plat_env_index; i++) {
		plat_priv = plat_env[i];

		if (!plat_priv)
			continue;

		switch (plat_priv->bus_type) {
		case CNSS_BUS_AHB:
			if (strcmp(driver_ops->name, "pld_ahb") == 0)
				plat_priv->driver_ops = driver_ops;
			break;
		case CNSS_BUS_PCI:
			if (strcmp(driver_ops->name, "pld_pcie") == 0) {
				plat_priv->driver_ops = driver_ops;

#ifdef CONFIG_CNSS2_LEGACY_IRQ
				if (plat_priv->enable_intx) {
					if (cnss_assign_lvirq(plat_priv))
						return -EINVAL;
				}
#endif
			}
			break;
		default:
			cnss_pr_err("%s: Invalid bus type for device 0x%lx\n",
				    __func__, plat_priv->device_id);
			break;
		}
	}

	return 0;
}
EXPORT_SYMBOL(cnss_wlan_register_driver_ops);

bool cnss_is_dev_initialized(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return NULL;

	return (plat_priv->driver_status == CNSS_INITIALIZED) ? 1 : 0;
}
EXPORT_SYMBOL(cnss_is_dev_initialized);

void *cnss_get_pci_dev_from_plat_dev(void *pdev)
{
	struct platform_device *plat_dev = (struct platform_device *)pdev;
	struct cnss_plat_data *plat_priv;

	if (!plat_dev)
		return NULL;

	plat_priv = platform_get_drvdata(plat_dev);

	if (!plat_priv)
		return NULL;

	return plat_priv->pci_dev;
}
EXPORT_SYMBOL(cnss_get_pci_dev_from_plat_dev);

void *cnss_get_pci_dev_id_from_plat_dev(void *pdev)
{
	struct platform_device *plat_dev = (struct platform_device *)pdev;
	struct cnss_plat_data *plat_priv;

	if (!plat_dev)
		return NULL;

	plat_priv = platform_get_drvdata(plat_dev);

	if (!plat_priv)
		return NULL;

	return plat_priv->pci_dev_id;
}
EXPORT_SYMBOL(cnss_get_pci_dev_id_from_plat_dev);

void cnss_wlan_unregister_driver(struct cnss_wlan_driver *driver_ops)
{
	struct cnss_plat_data *plat_priv;
	struct cnss_subsys_info *subsys_info;
	struct cnss_wlan_driver *ops;
	int i;

	for (i = 0; i < plat_env_index; i++) {
		plat_priv = plat_env[i];

		if (!plat_priv) {
			printk(KERN_ERR "%s plat_priv is NULL!\n", __func__);
			return;
		}

		plat_priv->driver_status = CNSS_LOAD_UNLOAD;
		ops = plat_priv->driver_ops;

		if ((plat_priv->bus_type == CNSS_BUS_AHB) && ops &&
		    (strcmp(driver_ops->name, "pld_ahb") == 0)) {
			subsys_info = &plat_priv->subsys_info;
			if (subsys_info->subsys_handle &&
			    !subsys_info->subsystem_put_in_progress) {
				subsys_info->subsystem_put_in_progress = true;
#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
				subsystem_put(subsys_info->subsys_handle);
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
				rproc_shutdown(subsys_info->subsys_handle);
#endif
				subsys_info->subsystem_put_in_progress = false;
			} else {
				ops->remove((struct pci_dev *)
					    plat_priv->plat_dev);
			}

			subsys_info->subsys_handle = NULL;
			cnss_unregister_notifier_cb(plat_priv);
			plat_priv->driver_ops = NULL;
			plat_priv->driver_status = CNSS_UNINITIALIZED;
			plat_priv->driver_state = 0;
		}

		if ((plat_priv->bus_type == CNSS_BUS_PCI) && ops &&
		    (strcmp(driver_ops->name, "pld_pcie") == 0)) {
			set_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state);
#ifndef CONFIG_CNSS2_KERNEL_5_15
			subsys_info = &plat_priv->subsys_info;
			if (subsys_info->subsys_handle &&
			    !subsys_info->subsystem_put_in_progress) {
				subsys_info->subsystem_put_in_progress = true;
#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
				subsystem_put(subsys_info->subsys_handle);
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
				rproc_shutdown(subsys_info->subsys_handle);
#endif
				subsys_info->subsys_handle = NULL;
				subsys_info->subsystem_put_in_progress = false;
			} else {
				if (plat_priv->pci_dev)
					ops->remove((struct pci_dev *)plat_priv->plat_dev);
			}
			cnss_unregister_subsys(plat_priv);
			cnss_unregister_notifier_cb(plat_priv);
#else
			cnss_hif_shutdown(plat_priv);
#endif
			plat_priv->driver_ops = NULL;
			plat_priv->driver_status = CNSS_UNINITIALIZED;
			plat_priv->driver_state = 0;
		}
	}
}
EXPORT_SYMBOL(cnss_wlan_unregister_driver);

#ifndef CONFIG_CNSS2_KERNEL_5_15
static int cnss_rproc_recovery(struct cnss_plat_data *plat_priv)
{
	struct cnss_subsys_info *subsys_info =
		&plat_priv->subsys_info;
	struct rproc *rproc_rpd;
	struct rproc *rproc = subsys_info->subsys_handle;
	int ret;

	cnss_bus_update_status(plat_priv, CNSS_FW_DOWN);
	if (rproc) {
		rproc->state = RPROC_CRASHED;
		ret = rproc_stop(rproc, true);
		if (ret < 0) {
			cnss_pr_err("User pd rproc_stop failed\n");
			return ret;
		}
		rproc->state = RPROC_SUSPENDED;
	}
	rproc_rpd = plat_priv->rproc_rpd_handle;
	if (rproc_rpd) {
		rproc_rpd->state = RPROC_RUNNING;
		ret = rproc_stop(rproc_rpd, true);
		if (ret < 0) {
			cnss_pr_err("Root pd rproc_stop failed\n");
			return ret;
		}
		rproc_rpd->ops->coredump(rproc_rpd);
	} else {
		if (rproc) {
			cnss_qca8074_notifier_nb(&plat_priv->modem_nb,
					CNSS_RAMDUMP_NOTIFICATION, NULL);
			rproc->ops->coredump(rproc);
		}
	}

	return 0;
}

static int cnss_rproc_start(struct cnss_plat_data *plat_priv)
{
	struct rproc *rproc_rpd;
	const struct firmware *firmware_p = NULL;
	struct rproc *rproc;
	struct device *dev;
	int ret;

	rproc = plat_priv->rproc_handle;
	rproc_rpd = plat_priv->rproc_rpd_handle;
	if (rproc_rpd) {
		dev = &rproc_rpd->dev;
		ret = request_firmware(&firmware_p, rproc_rpd->firmware, dev);
		if (ret < 0) {
			cnss_pr_err("request_firmware failed: %d\n", ret);
			return ret;
		}
		ret = rproc_start(rproc_rpd, firmware_p);
		if (ret < 0) {
			cnss_pr_err("Root pd rproc_start failed\n");
			return ret;
		}
	} else {
		if (rproc) {
			dev = &rproc->dev;
			ret = request_firmware(&firmware_p,
						rproc->firmware, dev);
			if (ret < 0) {
				cnss_pr_err("request_firmware failed: %d\n",
						ret);
				return ret;
			}
			ret = rproc_start(rproc, firmware_p);
			if (ret < 0) {
				cnss_pr_err("User pd rproc_start failed\n");
				return ret;
			}
		}
	}
	return 0;
}
#endif

void  *__cnss_subsystem_get(struct cnss_plat_data *plat_priv)
{
	struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info;
#ifndef CONFIG_CNSS2_KERNEL_5_15
	bool boot_after_recovery = false;
#endif

	plat_priv->target_asserted = 0;
	plat_priv->target_assert_timestamp = 0;

	cnss_pr_info("%s: driver_state: 0x%lx\n", __func__,
		     plat_priv->driver_state);

#ifndef CONFIG_CNSS2_KERNEL_5_15
	if (test_bit(CNSS_RECOVERY_WAIT_FOR_DRIVER, &plat_priv->driver_state))
		boot_after_recovery = true;
#endif

	if (subsys_info->subsys_handle &&
	    !test_bit(CNSS_RECOVERY_WAIT_FOR_DRIVER,
		      &plat_priv->driver_state)) {
		cnss_pr_err("%s: error: subsys handle %pK is not NULL\n",
			    __func__, subsys_info->subsys_handle);
		return NULL;
	}
	clear_bit(CNSS_RECOVERY_WAIT_FOR_DRIVER, &plat_priv->driver_state);

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
	subsys_info->subsys_handle =
				subsystem_get(subsys_info->subsys_desc.name);
	if (!subsys_info->subsys_handle) {
		cnss_pr_err("Failed to get subsys_handle!\n");
		goto fail;
	} else if (IS_ERR(subsys_info->subsys_handle)) {
		cnss_pr_err("Failed to do subsystem_get, err = %ld\n",
			    PTR_ERR(subsys_info->subsys_handle));
		goto fail;
	}
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
	if (!plat_priv->rproc_handle) {
		cnss_pr_err("%s: rproc_handle is NULL for %s\n",
			    __func__, plat_priv->device_name);
		return NULL;
	}

#ifndef CONFIG_CNSS2_KERNEL_5_15
	if (plat_priv->recovery_enabled && boot_after_recovery &&
	    (plat_priv->recovery_type == CNSS_SYNC_RECOVERY) &&
	    (plat_priv->bus_type == CNSS_BUS_AHB)) {
		/* In this case, rproc_stop was done and not rproc_shutdown,
		 * hence rproc_start has to be done after SSR recovery
		 * instead of rproc_boot. This would be applicable for AHB
		 * radios from IPQ53xx onwards.
		 */
		cnss_rproc_start(plat_priv);

	} else {
		subsys_info->subsys_handle = plat_priv->rproc_handle;
		if (rproc_boot(subsys_info->subsys_handle)) {
			cnss_pr_err("%s: error: rproc_boot failed for %s\n",
					__func__, plat_priv->device_name);
			goto fail;
		}
	}
#else
		subsys_info->subsys_handle = plat_priv->rproc_handle;
		if (rproc_boot(subsys_info->subsys_handle)) {
			cnss_pr_err("%s: error: rproc_boot failed for %s\n",
					__func__, plat_priv->device_name);
			goto fail;
		}

#endif
#endif
	return subsys_info->subsys_handle;

fail:
	CNSS_ASSERT(0);
	return NULL;
}

void  *cnss_subsystem_get(struct device *dev, int device_id)
{
	struct cnss_plat_data *plat_priv;
	struct pci_dev *pcidev;

	if (cnss_get_bus_type(device_id) == CNSS_BUS_AHB) {
		plat_priv = cnss_bus_dev_to_plat_priv(dev);
	} else {
		pcidev = container_of(dev, struct pci_dev, dev);
		plat_priv = cnss_get_plat_priv_dev_by_pci_dev(pcidev);
	}

	if (!plat_priv)
		return NULL;

#ifndef CONFIG_CNSS2_KERNEL_5_15
	return __cnss_subsystem_get(plat_priv);
#else
	return __cnss_hif_get(plat_priv);
#endif
}
EXPORT_SYMBOL(cnss_subsystem_get);

void __cnss_subsystem_put(struct cnss_plat_data *plat_priv)
{
	struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info;

	if (!subsys_info->subsys_handle) {
		cnss_pr_err("%s: error: subsys handle is NULL", __func__);
		return;
	}

	set_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state);

	if (!subsys_info->subsystem_put_in_progress) {
		subsys_info->subsystem_put_in_progress = true;
#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
		subsystem_put(subsys_info->subsys_handle);
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
		rproc_shutdown(subsys_info->subsys_handle);
#endif
		subsys_info->subsystem_put_in_progress = false;
		subsys_info->subsys_handle = NULL;
		plat_priv->driver_state = 0;
	}
}

void cnss_subsystem_put(struct device *dev)
{
	struct cnss_plat_data *plat_priv;

	plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv)
		return;

#ifndef CONFIG_CNSS2_KERNEL_5_15
	__cnss_subsystem_put(plat_priv);
#else
	__cnss_hif_put(plat_priv);
#endif
}
EXPORT_SYMBOL(cnss_subsystem_put);

#ifdef CONFIG_CNSS2_PM
static int cnss_modem_notifier_nb(struct notifier_block *nb,
				  unsigned long code,
				  void *ss_handle)
{
	struct cnss_plat_data *plat_priv =
		container_of(nb, struct cnss_plat_data, modem_nb);
	struct cnss_esoc_info *esoc_info;
	int event_code = cnss_get_event(code, plat_priv);
	cnss_pr_dbg("Modem notifier: event %lu\n", event_code);

	if (!plat_priv)
		return NOTIFY_DONE;

	if (event_code)
		return NOTIFY_OK;

	esoc_info = &plat_priv->esoc_info;

	if (event_code == CNSS_AFTER_POWERUP)
		esoc_info->modem_current_status = 1;
	else if (event_code == CNSS_BEFORE_SHUTDOWN)
		esoc_info->modem_current_status = 0;
	else
		return NOTIFY_DONE;

	if (!cnss_bus_call_driver_modem_status(plat_priv,
					       esoc_info->modem_current_status))
		return NOTIFY_DONE;

	return NOTIFY_OK;
}

static int cnss_register_esoc(struct cnss_plat_data *plat_priv)
{
	int ret = 0;
	struct device *dev;
	struct cnss_esoc_info *esoc_info;
	struct esoc_desc *esoc_desc;
	const char *client_desc;

	dev = &plat_priv->plat_dev->dev;
	esoc_info = &plat_priv->esoc_info;

	esoc_info->notify_modem_status =
		of_property_read_bool(dev->of_node,
				      "qcom,notify-modem-status");

	if (!esoc_info->notify_modem_status)
		goto out;

	ret = of_property_read_string_index(dev->of_node, "esoc-names", 0,
					    &client_desc);
	if (ret) {
		cnss_pr_dbg("esoc-names is not defined in DT, skip!\n");
	} else {
		esoc_desc = devm_register_esoc_client(dev, client_desc);
		if (IS_ERR_OR_NULL(esoc_desc)) {
			ret = PTR_RET(esoc_desc);
			cnss_pr_err("Failed to register esoc_desc, err = %d\n",
				    ret);
			goto out;
		}
		esoc_info->esoc_desc = esoc_desc;
	}

	plat_priv->modem_nb.notifier_call = cnss_modem_notifier_nb;
	esoc_info->modem_current_status = 0;
#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
	esoc_info->modem_notify_handler =
		subsys_notif_register_notifier(esoc_info->esoc_desc ?
					       esoc_info->esoc_desc->name :
					       "modem", &plat_priv->modem_nb);
	if (IS_ERR(esoc_info->modem_notify_handler)) {
		ret = PTR_ERR(esoc_info->modem_notify_handler);
		cnss_pr_err("Failed to register esoc notifier, err = %d\n",
			    ret);
		goto unreg_esoc;
	}
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
	ret = rproc_register_subsys_notifier(esoc_info->esoc_desc ?
					 esoc_info->esoc_desc->name :
					 "modem", &plat_priv->modem_nb, NULL);
	if (ret) {
		cnss_pr_err("%s: Failed register rproc. ret %d\n", __func__,
			     ret);
		return ret
	}
#endif

	return 0;
unreg_esoc:
	if (esoc_info->esoc_desc)
		devm_unregister_esoc_client(dev, esoc_info->esoc_desc);
out:
	return ret;
}

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
static void cnss_unregister_esoc(struct cnss_plat_data *plat_priv)
{
	struct device *dev;
	struct cnss_esoc_info *esoc_info;

	dev = &plat_priv->plat_dev->dev;
	esoc_info = &plat_priv->esoc_info;

	if (esoc_info->notify_modem_status)
		subsys_notif_unregister_notifier
		(esoc_info->modem_notify_handler,
		 &plat_priv->modem_nb);
	if (esoc_info->esoc_desc)
		devm_unregister_esoc_client(dev, esoc_info->esoc_desc);
}
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
static void cnss_unregister_esoc(struct cnss_plat_data *plat_priv)
{
	struct cnss_esoc_info *esoc_info;
	int ret = 0;

	esoc_info = &plat_priv->esoc_info;
	ret = rproc_unregister_subsys_notifier("modem",
					       &plat_priv->modem_nb, NULL);
	if (ret) {
		cnss_pr_err("%s: Failed to unregister rproc. ret %d\n",
			    __func__, ret);
		return;
	}
}
#endif
#endif

#ifndef CONFIG_CNSS2_KERNEL_5_15
#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
static int cnss_subsys_powerup(const struct subsys_desc *subsys_desc)
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
static int cnss_subsys_powerup(struct rproc *subsys_desc)
#endif
{
	int ret = 0;
	struct cnss_plat_data *plat_priv;
#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
	if (!subsys_desc->dev) {
		printk(KERN_ERR "dev from subsys_desc is NULL\n");
		return -ENODEV;
	}

	plat_priv = dev_get_drvdata(subsys_desc->dev);
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
	plat_priv = dev_get_drvdata(subsys_desc->dev.parent);
#endif
	if (!plat_priv)
		return -ENODEV;

	plat_priv->target_asserted = 0;
	plat_priv->target_assert_timestamp = 0;
	set_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state);
	ret = cnss_pci_probe(plat_priv->pci_dev,
			     plat_priv->pci_dev_id,
			     plat_priv);
	if (ret) {
		pr_err("ERROR : %s:%d ret %d\n", __func__, __LINE__, ret);
		return -ENODEV;
	}

	if (!plat_priv->driver_state) {
		cnss_pr_dbg("Powerup is ignored\n");
		return 0;
	}

	return cnss_bus_dev_powerup(plat_priv);
}

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
static int cnss_subsys_shutdown(const struct subsys_desc *subsys_desc,
				bool force_stop)
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
static int cnss_subsys_shutdown(struct rproc *subsys_desc)
#endif
{
	struct cnss_plat_data *plat_priv;

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
	if (!subsys_desc->dev) {
		pr_err("dev from subsys_desc is NULL\n");
		return -ENODEV;
	}

	plat_priv = dev_get_drvdata(subsys_desc->dev);
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
	plat_priv = dev_get_drvdata(subsys_desc->dev.parent);
#endif
	if (!plat_priv) {
		printk(KERN_ERR "plat_priv is NULL!\n");
		return -ENODEV;
	}

	if (!plat_priv->driver_state) {
		cnss_pr_dbg("shutdown is ignored\n");
		return 0;
	}

	return cnss_bus_dev_shutdown(plat_priv);
}

#ifdef CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK
static int cnss_subsys_dummy_load(struct rproc *subsys_desc,
				  const struct firmware *fw)
{
	/*no firmware load it will be taken care by pci and mhi*/
	return 0;
}
#endif
#endif

void cnss_device_crashed(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	struct cnss_subsys_info *subsys_info;

	if (!plat_priv)
		return;

	subsys_info = &plat_priv->subsys_info;
#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
	if (subsys_info->subsys_device) {
		set_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state);
		subsys_set_crash_status(subsys_info->subsys_device, true);
		subsystem_restart_dev(subsys_info->subsys_device);
	}
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
	if (subsys_info->subsys_handle) {
		set_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state);
		rproc_report_crash(subsys_info->subsys_handle,
						RPROC_FATAL_ERROR);
	}
#endif
}
EXPORT_SYMBOL(cnss_device_crashed);

#ifndef CONFIG_CNSS2_KERNEL_5_15
#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
static void cnss_subsys_crash_shutdown(const struct subsys_desc *subsys_desc)
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
static void cnss_subsys_crash_shutdown(struct rproc *subsys_desc)
#endif
{
#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
	struct cnss_plat_data *plat_priv = dev_get_drvdata(subsys_desc->dev);
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
	struct cnss_plat_data *plat_priv =
				dev_get_drvdata(subsys_desc->dev.parent);
#endif

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL\n");
		return;
	}

	cnss_bus_dev_crash_shutdown(plat_priv);
}

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
static int cnss_subsys_ramdump(int enable,
			       const struct subsys_desc *subsys_desc)
{
	struct cnss_plat_data *plat_priv = dev_get_drvdata(subsys_desc->dev);

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL\n");
		return -ENODEV;
	}

	return cnss_bus_dev_ramdump(plat_priv);
}
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
static void cnss_subsys_ramdump(struct rproc *subsys_desc,
				struct rproc_dump_segment *segment,
				void  *dest)
{
	struct cnss_plat_data *plat_priv;

	plat_priv = dev_get_drvdata(subsys_desc->dev.parent);

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL\n");
		return;
	}

	cnss_bus_dev_ramdump(plat_priv);
}
#endif

#ifdef CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK
static int cnss_subsys_add_ramdump_callback(struct rproc *subsys_desc,
		const struct firmware *firmware)
{
	struct cnss_plat_data *plat_priv;
	int ret = 0;

	plat_priv = dev_get_drvdata(subsys_desc->dev.parent);
	if (!plat_priv) {
		cnss_pr_err("%s: plat_priv is NULL\n", __func__);
		return -1;
	}
	ret = rproc_coredump_add_custom_segment(subsys_desc, 0, 0,
						cnss_subsys_ramdump, NULL);
	if (ret) {
		cnss_pr_err("%s: Failed to add custom segment ret %d\n",
			    __func__, ret);
		return ret;
	}
	return ret;
}
#endif
#endif

void *cnss_get_virt_ramdump_mem(struct device *dev, unsigned long *size)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	struct cnss_ramdump_info *ramdump_info;

	if (!plat_priv)
		return NULL;

	ramdump_info = &plat_priv->ramdump_info;
	*size = ramdump_info->ramdump_size;

	return ramdump_info->ramdump_va;
}
EXPORT_SYMBOL(cnss_get_virt_ramdump_mem);

static const char *cnss_recovery_reason_to_str(enum cnss_recovery_reason reason)
{
	switch (reason) {
	case CNSS_REASON_DEFAULT:
		return "DEFAULT";
	case CNSS_REASON_LINK_DOWN:
		return "LINK_DOWN";
	case CNSS_REASON_RDDM:
		return "RDDM";
	case CNSS_REASON_TIMEOUT:
		return "TIMEOUT";
	case CNSS_REASON_FATAL_SHUTDOWN:
		return "FATAL_SHUTDOWN";
	}

	return "UNKNOWN";
};

static int cnss_do_recovery(struct cnss_plat_data *plat_priv,
			    enum cnss_recovery_reason reason)
{
#ifndef CONFIG_CNSS2_KERNEL_5_15
	struct cnss_subsys_info *subsys_info =
		&plat_priv->subsys_info;
#endif
	unsigned long rddm_lock;
	struct cnss_pci_data *pci_priv = NULL;
	struct cnss_mlo_group_info *group_info = plat_priv->mlo_group_info;
	int ret = 0;

	plat_priv->recovery_count++;

	if (plat_priv->device_id == QCA6174_DEVICE_ID)
		goto self_recovery;

	if (test_bit(SKIP_RECOVERY, &plat_priv->ctrl_params.quirks)) {
		cnss_pr_dbg("Skip device recovery\n");
		return 0;
	}

	switch (reason) {
	case CNSS_REASON_LINK_DOWN:
		if (test_bit(LINK_DOWN_SELF_RECOVERY,
			     &plat_priv->ctrl_params.quirks))
			goto self_recovery;
		break;
	case CNSS_REASON_RDDM:
	case CNSS_REASON_FATAL_SHUTDOWN:
		if (plat_priv->bus_type == CNSS_BUS_PCI)
			cnss_bus_collect_dump_info(plat_priv, false);
		if (plat_priv->mlo_support && !plat_priv->recovery_enabled &&
				group_info != NULL) {
			spin_lock_irqsave(&rddm_spinlock, rddm_lock);
			group_info->rddm_dump_all++;
			spin_unlock_irqrestore(&rddm_spinlock, rddm_lock);
		}
		break;
	case CNSS_REASON_DEFAULT:
	case CNSS_REASON_TIMEOUT:
		break;
	default:
		cnss_pr_err("Unsupported recovery reason: %s(%d)\n",
			    cnss_recovery_reason_to_str(reason), reason);
		break;
	}

	/* If recovery is enabled, fatal notification is sent to wifi driver
	 * as soon as MHI notifies error.
	 * If recovery is disabled, send fatal notification here after RDDM
	 * is done so that we have the RDDM information before the assert
	 * is triggered from the wifi driver.
	 */
	if (!plat_priv->recovery_enabled) {
		/* This is a special case where ramdump file is uploaded to
		 * TFTP and then host assert happens. It is disabled by default.
		 */
		if (ramdump_enabled)
			cnss_bus_dev_ramdump(plat_priv);
		if (plat_priv->mlo_support && group_info != NULL &&
		    plat_priv->recovery_mode != MODE_1_RECOVERY_MODE &&
		    !plat_priv->standby_mode) {
			if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state))
				cnss_pr_info("FW_READY not received for the device, so early assert\n");
			else if (group_info->num_chips != group_info->rddm_dump_all)
				return 0;
		}
		ret = cnss_bus_update_status(plat_priv, CNSS_FW_DOWN);
		if (ret) {
			/* Call CNSS_ASSERT if fatal call is missed in down
			 * path. Target assert can happen in down path and
			 * fatal is not called since the driver_ops is NULL.
			 */
			pci_priv = plat_priv->bus_priv;
			if ((plat_priv->bus_type == CNSS_BUS_PCI) && pci_priv)
				plat_priv = pci_priv->plat_priv;

			CNSS_ASSERT(0);
		}
	}

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
	if (!subsys_info->subsys_device)
		return 0;

	subsys_set_crash_status(subsys_info->subsys_device, true);
	subsystem_restart_dev(subsys_info->subsys_device);
#else
#ifndef CONFIG_CNSS2_KERNEL_5_15
	if (!subsys_info->subsys_handle)
		return 0;
#endif
	if (plat_priv->mlo_capable) {
		/* For MLO supported targets, power off the target and collect
		 * dump. The power up would be handled by driver to ensure
		 * multiple targets in the MLO group are all powered up in the
		 * correct sequence
		 */
#ifdef CONFIG_CNSS2_KERNEL_5_15
		cnss_hif_shutdown(plat_priv);
		cnss_hif_notifier(plat_priv, CNSS_RAMDUMP_NOTIFICATION);
		cnss_bus_dev_ramdump(plat_priv);
		set_bit(CNSS_RECOVERY_WAIT_FOR_DRIVER,
			&plat_priv->driver_state);
		cnss_hif_notifier(plat_priv,
				  CNSS_RAMDUMP_DONE);
#else
		if (plat_priv->bus_type == CNSS_BUS_PCI) {
			rproc_shutdown(subsys_info->subsys_handle);
			cnss_qcn9000_notifier_nb(&plat_priv->modem_nb,
					CNSS_RAMDUMP_NOTIFICATION, NULL);
			cnss_subsys_ramdump(subsys_info->subsys_handle,
								NULL, NULL);
		} else
			cnss_rproc_recovery(plat_priv);

		set_bit(CNSS_RECOVERY_WAIT_FOR_DRIVER,
			&plat_priv->driver_state);
		cnss_qcn9000_notifier_nb(&plat_priv->modem_nb,
				CNSS_RAMDUMP_DONE, NULL);
#endif
	} else {
#ifdef CONFIG_CNSS2_KERNEL_5_15
		schedule_work(&plat_priv->crash_work);
#else
		if (plat_priv->bus_type == CNSS_BUS_PCI)
			rproc_report_crash(subsys_info->subsys_handle,
					RPROC_FATAL_ERROR);
		else {
			cnss_rproc_recovery(plat_priv);
			cnss_rproc_start(plat_priv);
		}
#endif
	}

#endif
	return 0;

self_recovery:
	cnss_bus_dev_shutdown(plat_priv);
	cnss_bus_dev_powerup(plat_priv);

	return 0;
}

static int cnss_driver_recovery_hdlr(struct cnss_plat_data *plat_priv,
				     void *data)
{
	struct cnss_recovery_data *recovery_data = data;
	int ret = 0;

	cnss_pr_dbg("Driver recovery is triggered with reason: %s(%d)\n",
		    cnss_recovery_reason_to_str(recovery_data->reason),
		    recovery_data->reason);

	if (!plat_priv->driver_state) {
		cnss_pr_err("Improper driver state, ignore recovery\n");
		ret = -EINVAL;
		goto out;
	}

	if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) {
		cnss_pr_err("Recovery is already in progress, state 0x%lx\n",
			    plat_priv->driver_state);
		ret = -EINVAL;
		goto out;
	}

	if (test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state) ||
	    test_bit(CNSS_DRIVER_IDLE_SHUTDOWN, &plat_priv->driver_state)) {
		cnss_pr_err("Driver unload or idle shutdown is in progress, ignore recovery\n");
		BUG_ON(1);
		ret = -EINVAL;
		goto out;
	}

	switch (plat_priv->device_id) {
	case QCA6174_DEVICE_ID:
		if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state) ||
		    test_bit(CNSS_DRIVER_IDLE_RESTART,
			     &plat_priv->driver_state)) {
			cnss_pr_err("Driver load or idle restart is in progress, ignore recovery\n");
			ret = -EINVAL;
			goto out;
		}
		break;
	default:
		if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) {
			set_bit(CNSS_FW_BOOT_RECOVERY,
				&plat_priv->driver_state);
		}
		break;
	}

	set_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state);
	ret = cnss_do_recovery(plat_priv, recovery_data->reason);

out:
	kfree(data);
	return ret;
}

int cnss_self_recovery(struct device *dev,
		       enum cnss_recovery_reason reason)
{
	cnss_schedule_recovery(dev, reason);
	return 0;
}
EXPORT_SYMBOL(cnss_self_recovery);

void cnss_schedule_recovery(struct device *dev,
			    enum cnss_recovery_reason reason)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv) {
		pr_err("plat_priv is NULL\n");
		return;
	}

	if (test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state) ||
	    test_bit(CNSS_DRIVER_IDLE_SHUTDOWN, &plat_priv->driver_state)) {
		cnss_pr_err("Driver unload or idle shutdown is in progress, ignore schedule recovery\n");
		BUG_ON(1);
		return;
	}

	plat_priv->reason = reason;
	queue_work(plat_priv->recovery_wq, &plat_priv->recovery_work);
}
EXPORT_SYMBOL(cnss_schedule_recovery);

int cnss_force_fw_assert(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);

	if (!plat_priv) {
		printk(KERN_ERR "plat_priv is NULL\n");
		return -ENODEV;
	}

	if (plat_priv->device_id == QCA6174_DEVICE_ID) {
		cnss_pr_info("Forced FW assert is not supported\n");
		return -EOPNOTSUPP;
	}

	if (cnss_pci_is_device_down(dev)) {
		cnss_pr_info("Device is already in bad state, ignore force assert\n");
		return 0;
	}

	if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) {
		cnss_pr_info("Recovery is already in progress, ignore forced FW assert\n");
		return 0;
	}

	cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_FORCE_FW_ASSERT, 0,
			       NULL);

	return 0;
}
EXPORT_SYMBOL(cnss_force_fw_assert);

int cnss_force_collect_rddm(struct device *dev)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	int ret = 0;

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL\n");
		return -ENODEV;
	}

	if (plat_priv->device_id == QCA6174_DEVICE_ID) {
		cnss_pr_info("Force collect rddm is not supported\n");
		return -EOPNOTSUPP;
	}

	if (cnss_pci_is_device_down(dev)) {
		cnss_pr_info("Device is already in bad state, ignore force collect rddm\n");
		return 0;
	}

	if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) {
		cnss_pr_info("Recovery is already in progress, ignore forced collect rddm\n");
		return 0;
	}

	if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state) ||
	    test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state) ||
	    test_bit(CNSS_DRIVER_IDLE_RESTART, &plat_priv->driver_state) ||
	    test_bit(CNSS_DRIVER_IDLE_SHUTDOWN, &plat_priv->driver_state)) {
		cnss_pr_info("Loading/Unloading/idle restart/shutdown is in progress, ignore forced collect rddm\n");
		return 0;
	}

	ret = cnss_bus_force_fw_assert_hdlr(plat_priv);
	if (ret)
		return ret;

	reinit_completion(&plat_priv->rddm_complete);
	ret = wait_for_completion_timeout
		(&plat_priv->rddm_complete,
		 msecs_to_jiffies(CNSS_RDDM_TIMEOUT_MS));
	if (!ret)
		ret = -ETIMEDOUT;

	return ret;
}
EXPORT_SYMBOL(cnss_force_collect_rddm);

static int cnss_cold_boot_cal_start_hdlr(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	if (test_bit(CNSS_FW_READY, &plat_priv->driver_state) ||
	    test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state) ||
	    test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) {
		cnss_pr_dbg("Device is already active, ignore calibration\n");
		goto out;
	}

	set_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state);
	reinit_completion(&plat_priv->cal_complete);
	ret = cnss_bus_dev_powerup(plat_priv);
	if (ret) {
		complete(&plat_priv->cal_complete);
		clear_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state);
	}

out:
	return ret;
}

static int cnss_cold_boot_cal_done_hdlr(struct cnss_plat_data *plat_priv,
					void *data)
{
	struct cnss_cal_info *cal_info = data;

	if (!test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state)) {
		cnss_pr_dbg("%s: Driver not in cold boot cal state, returning!\n",
			    __func__);
		goto out;
	}

	switch (cal_info->cal_status) {
	case CNSS_CAL_DONE:
		cnss_pr_info("Coldboot Calibration completed successfully for device 0x%lx\n",
			     plat_priv->device_id);
		/* Send cal upload req to cnss-daemon after confirming that
		 * it is connected to cnss2 over QMI.
		 */
		if (is_ipc_qmi_client_connected
				(CNSS_PLAT_IPC_DAEMON_QMI_CLIENT_V01, 0))
			cnss_cal_mem_upload_to_file(plat_priv);
		plat_priv->cal_done = true;
		break;
	case CNSS_CAL_TIMEOUT:
		cnss_pr_dbg("Calibration timed out!\n");
		break;
	default:
		cnss_pr_err("Unknown calibration status: %u\n",
			    cal_info->cal_status);
		break;
	}

	complete(&plat_priv->cal_complete);
	clear_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state);

out:
	kfree(data);
	return 0;
}

static int cnss_power_up_hdlr(struct cnss_plat_data *plat_priv)
{
	int ret;

	ret = cnss_bus_dev_powerup(plat_priv);
	if (ret)
		clear_bit(CNSS_DRIVER_IDLE_RESTART, &plat_priv->driver_state);

	return ret;
}

static int cnss_power_down_hdlr(struct cnss_plat_data *plat_priv)
{
	cnss_bus_dev_shutdown(plat_priv);

	return 0;
}

static int cnss_qdss_trace_req_mem_hdlr(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	ret = cnss_bus_alloc_qdss_mem(plat_priv);
	if (ret < 0)
		return ret;

	return cnss_wlfw_qdss_trace_mem_info_send_sync(plat_priv);
}

void *cnss_qdss_trace_pa_to_va(struct cnss_plat_data *plat_priv,
			       u64 pa, u32 size, int *seg_id)
{
	int i = 0;
	struct cnss_fw_mem *qdss_mem = plat_priv->qdss_mem;
	u64 offset = 0;
	void *va = NULL;
	u64 local_pa;
	u32 local_size;

	for (i = 0; i < plat_priv->qdss_mem_seg_len; i++) {
		local_pa = (u64)qdss_mem[i].pa;
		local_size = (u32)qdss_mem[i].size;
		if (pa == local_pa && size <= local_size) {
			va = qdss_mem[i].va;
			break;
		}
		if (pa > local_pa &&
		    pa < local_pa + local_size &&
		    pa + size <= local_pa + local_size) {
			offset = pa - local_pa;
			va = qdss_mem[i].va + offset;
			break;
		}
	}

	*seg_id = i;
	return va;
}

static void get_updated_qdss_trace_filename(struct cnss_plat_data *plat_priv,
					    char *raw_file_name,
					    char *file_name, size_t size)
{
	char *file_suffix = NULL;
	char file_prefix[QDSS_TRACE_FILE_NAME_MAX] = {};

	file_suffix = strnstr(raw_file_name, ".bin",
			      QDSS_TRACE_FILE_NAME_MAX);

	if (file_suffix) {
		strlcpy(file_prefix, raw_file_name,
			(file_suffix - &raw_file_name[0]) + 1);

		snprintf(file_name, size, "%s_%s%s",
			 file_prefix, plat_priv->device_name, file_suffix);
	} else {
		snprintf(file_name, size, "%s_%s",
			 raw_file_name, plat_priv->device_name);
	}
}

static int cnss_qdss_trace_save_hdlr(struct cnss_plat_data *plat_priv,
				     void *data)
{
	struct cnss_qmi_event_qdss_trace_save_data *event_data = data;
	struct cnss_fw_mem *qdss_mem = plat_priv->qdss_mem;
	int ret = 0;
	int i;
	char file_name[CNSS_GENL_STR_LEN_MAX];

	if (!plat_priv->qdss_mem_seg_len) {
		cnss_pr_err("Memory for QDSS trace is not available\n");
		return -ENOMEM;
	}

	get_updated_qdss_trace_filename(plat_priv,
					event_data->file_name, file_name,
					sizeof(file_name));

	if (event_data->mem_seg_len == 0) {
		for (i = 0; i < plat_priv->qdss_mem_seg_len; i++) {
			ret = cnss_genl_send_msg(qdss_mem[i].va,
						 CNSS_GENL_MSG_TYPE_QDSS,
						 file_name,
						 qdss_mem[i].size);
			if (ret < 0) {
				cnss_pr_err("Fail to save QDSS data: %d\n",
					    ret);
				break;
			}
		}
	} else if ((event_data->mem_seg_len == 1) &&
		   (event_data->mem_seg[0].addr == qdss_mem[0].pa) &&
		   (event_data->mem_seg[0].size <= qdss_mem[0].size)) {
		ret = cnss_genl_send_msg(qdss_mem[0].va,
					 CNSS_GENL_MSG_TYPE_QDSS,
					 file_name,
					 event_data->mem_seg[0].size);
		if (ret < 0) {
			cnss_pr_err("Fail to save QDSS data: %d\n", ret);
			goto out;
		}
		cnss_pr_info("QDSS Data saved in /data/vendor/wifi/%s",
			     file_name);
	} else if (event_data->mem_seg_len == 2) {
		/* FW sends the 2 segments in below format, we need to send
		 * segment 0 first then segment 1
		 *
		 *  QDSS ETR Memory - 1MB
		 * +---------------------+
		 * |   segment 1 start   |
		 * |                     |
		 * |                     |
		 * |                     |
		 * |   segment 1 end     |
		 * +---------------------+
		 * |   segment 0 start   |
		 * |                     |
		 * |                     |
		 * |   segment 0 end     |
		 * +---------------------+
		 */
		if (event_data->mem_seg[1].addr != qdss_mem[0].pa) {
			cnss_pr_err("Invalid seg 0 addr 0x%llx",
				    event_data->mem_seg[1].addr);
			goto out;
		}
		if (event_data->mem_seg[0].size + event_data->mem_seg[1].size !=
		    qdss_mem[0].size) {
			cnss_pr_err("Invalid total size 0x%x 0x%x",
				    event_data->mem_seg[0].size,
				    event_data->mem_seg[1].size);
			goto out;
		}

		ret = cnss_genl_send_msg((qdss_mem[0].va +
					 event_data->mem_seg[1].size),
					 CNSS_GENL_MSG_TYPE_QDSS,
					 file_name,
					 event_data->mem_seg[0].size);
		if (ret < 0) {
			cnss_pr_err("Fail to save QDSS data 0: %d\n", ret);
			goto out;
		}
		ret = cnss_genl_send_msg((qdss_mem[0].va),
					 CNSS_GENL_MSG_TYPE_QDSS,
					 file_name,
					 event_data->mem_seg[1].size);
		if (ret < 0) {
			cnss_pr_err("Fail to save QDSS data 1: %d\n", ret);
			goto out;
		}
		cnss_pr_info("QDSS Data saved in /data/vendor/wifi/%s",
			     file_name);
	} else {
		cnss_pr_err("Inavalid mem seg len %d", event_data->mem_seg_len);
	}

out:
	kfree(data);
	return ret;
}

static int cnss_qdss_trace_free_hdlr(struct cnss_plat_data *plat_priv)
{
	cnss_bus_free_qdss_mem(plat_priv);

	return 0;
}

static int cnss_qdss_mem_ready_hdlr(struct cnss_plat_data *plat_priv)
{
	return cnss_wlfw_send_qdss_trace_mode_req(plat_priv,
						  QMI_WLFW_QDSS_TRACE_ON_V01,
						  0);
}

static void m3_dump_open_timeout_func(struct timer_list *timer)
{
	struct m3_dump *m3_dump_data =
			from_timer(m3_dump_data, timer, open_timer);

	if (!m3_dump_data) {
		pr_err("%s: Invalid m3_dump_data from timer\n", __func__);
		return;
	}

	atomic_set(&m3_dump_data->open_timedout, 1);
	complete(&m3_dump_data->open_complete);
	pr_err("M3 dump open failed\n");
}

static void m3_dump_read_timeout_func(struct timer_list *timer)
{
	struct m3_dump *m3_dump_data =
			from_timer(m3_dump_data, timer, read_timer);

	if (!m3_dump_data) {
		pr_err("%s: Invalid m3_dump_data from timer\n", __func__);
		return;
	}

	if (m3_dump_data->task)
		send_sig(SIGKILL, m3_dump_data->task, 0);

	atomic_set(&m3_dump_data->read_timedout, 1);
	complete(&m3_dump_data->read_complete);
	pr_err("M3 dump collection failed\n");
}

static int m3_dump_open(struct inode *inode, struct file *file)
{
	struct cnss_plat_data *plat_priv;
	struct m3_dump *m3_dump_data;

	plat_priv = cnss_get_plat_priv_by_instance_id(iminor(inode));

	if (!plat_priv) {
		cnss_pr_err("%s: Failed to get plat_priv for instance_id 0x%x",
			    __func__, iminor(inode));
		return -ENODEV;
	}

	m3_dump_data = &plat_priv->m3_dump_data;
	if (m3_dump_data->file_open)
		return -EBUSY;

	del_timer_sync(&m3_dump_data->open_timer);
	if (atomic_read(&m3_dump_data->open_timedout) == 1)
		return -ENODEV;

	m3_dump_data->file_open = true;
	m3_dump_data->task = current;
	file->private_data = m3_dump_data;
	nonseekable_open(inode, file);

	init_completion(&m3_dump_data->read_complete);
	timer_setup(&m3_dump_data->read_timer, m3_dump_read_timeout_func, 0);
	mod_timer(&m3_dump_data->read_timer,
		  jiffies + msecs_to_jiffies(M3_DUMP_READ_TIMER_TIMEOUT));

	complete(&m3_dump_data->open_complete);
	return 0;
}

static ssize_t m3_dump_read(struct file *file, char __user *data, size_t len,
			    loff_t *ppos)
{
	struct m3_dump *m3_dump_data = (struct m3_dump *)file->private_data;
	char *bufp;

	if (!m3_dump_data) {
		pr_err("%s: m3_dump_data invalid", __func__);
		return -EFAULT;
	}

	bufp = (char *)m3_dump_data->dump_addr + *ppos;
	mod_timer(&m3_dump_data->read_timer,
		  jiffies + msecs_to_jiffies(M3_DUMP_READ_TIMER_TIMEOUT));

	if (*ppos + len > m3_dump_data->size)
		len = m3_dump_data->size - *ppos;

	if (copy_to_user(data, bufp, len)) {
		pr_err("%s: copy_to_user failed\n", __func__);
		return -EFAULT;
	}

	*ppos += len;

	return len;
}

static int m3_dump_release(struct inode *inode, struct file *file)
{
	struct m3_dump *m3_dump_data = (struct m3_dump *)file->private_data;
	int dump_minor = iminor(inode);
	int dump_major = imajor(inode);

	if (!m3_dump_data) {
		pr_err("%s: m3_dump_data invalid", __func__);
		return -EFAULT;
	}

	file->private_data = NULL;
	device_destroy(m3_dump_class, MKDEV(dump_major, dump_minor));

	complete(&m3_dump_data->read_complete);
	m3_dump_data->file_open = false;
	m3_dump_data->task = 0;
	return 0;
}

static const struct file_operations m3_dump_fops = {
	.owner          = THIS_MODULE,
	.open		= m3_dump_open,
	.read		= m3_dump_read,
	.release	= m3_dump_release,
};

static int cnss_init_m3_dump_class(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	if (m3_dump_class) {
		cnss_pr_dbg("m3_dump_class already initialized");
		goto out;
	}

	m3_dump_major = register_chrdev(UNNAMED_MAJOR, "dump",
					&m3_dump_fops);
	if (m3_dump_major < 0) {
		cnss_pr_err("%s: Unable to allocate a major number err = %d",
			    __func__, m3_dump_major);
		ret = m3_dump_major;
		goto out;
	}

	m3_dump_class = class_create(THIS_MODULE, "dump");
	if (IS_ERR(m3_dump_class)) {
		cnss_pr_err("%s: Unable to create class = %ld",
			    __func__, PTR_ERR(m3_dump_class));
		unregister_chrdev(m3_dump_major, "dump");
		m3_dump_major = 0;
		m3_dump_class = NULL;
		ret = -ENODEV;
	}

out:
	return ret;
}

static void cnss_deinit_m3_dump_class(void)
{
	if (m3_dump_class)
		class_destroy(m3_dump_class);

	if (m3_dump_major)
		unregister_chrdev(m3_dump_major, "dump");

	m3_dump_class = NULL;
	m3_dump_major = 0;
}

static int cnss_do_m3_dump_upload(struct cnss_plat_data *plat_priv,
				  const char *dump_file_name)
{
	int ret = 0;
	struct device *dump_dev = NULL;
	struct m3_dump *m3_dump_data = &plat_priv->m3_dump_data;

	init_completion(&m3_dump_data->open_complete);
	atomic_set(&m3_dump_data->open_timedout, 0);
	atomic_set(&m3_dump_data->read_timedout, 0);

	if (!m3_dump_class) {
		cnss_pr_err("M3 dump class not initialized.");
		return -ENODEV;
	}

	dump_dev = device_create(m3_dump_class, NULL,
				 MKDEV(m3_dump_major,
				       plat_priv->wlfw_service_instance_id),
				 NULL, dump_file_name);
	if (IS_ERR(dump_dev)) {
		ret = PTR_ERR(dump_dev);
		cnss_pr_err("%s: Unable to create device = %d",
			    __func__, ret);
		return ret;
	}

	/* This avoids race condition between the scheduled timer and the opened
	 * file discriptor during delay in user space app execution.
	 */
	timer_setup(&m3_dump_data->open_timer, m3_dump_open_timeout_func, 0);

	mod_timer(&m3_dump_data->open_timer,
		  jiffies + msecs_to_jiffies(M3_DUMP_OPEN_TIMEOUT));

	ret = wait_for_completion_timeout(&m3_dump_data->open_complete,
					  msecs_to_jiffies(
					  M3_DUMP_OPEN_COMPLETION_TIMEOUT));
	if (!ret || (atomic_read(&m3_dump_data->open_timedout) == 1)) {
		ret = -ETIMEDOUT;
		cnss_pr_err("%s: Failed to open M3 dump", __func__);
		goto dump_dev_failed;
	}

	ret = wait_for_completion_timeout(&m3_dump_data->read_complete,
					  msecs_to_jiffies(
					  M3_DUMP_COMPLETION_TIMEOUT));
	if (!ret || (atomic_read(&m3_dump_data->read_timedout) == 1)) {
		ret = -ETIMEDOUT;
		cnss_pr_err("%s: Failed to collect M3 dump", __func__);
	} else {
		/* completed before timeout */
		ret = 0;
	}

	del_timer_sync(&m3_dump_data->read_timer);
	return ret;

dump_dev_failed:
	device_destroy(m3_dump_class,
		       MKDEV(m3_dump_major,
			     plat_priv->wlfw_service_instance_id));

	return ret;
}

static int cnss_m3_dump_upload_req_hdlr(struct cnss_plat_data *plat_priv,
					void *data)
{
	struct cnss_qmi_event_m3_dump_upload_req_data *event_data = data;
	struct cnss_fw_mem *m3_mem = NULL;
	char dump_file_name[30];
	struct m3_dump *m3_dump_data = &plat_priv->m3_dump_data;
	int i, ret = 0;

	cnss_pr_dbg("%s: %d pdev_id %d addr 0x%llx size %llu",
		    __func__, __LINE__,
		    event_data->pdev_id, event_data->addr, event_data->size);

	for (i = 0; i < plat_priv->fw_mem_seg_len; i++) {
		if (plat_priv->fw_mem[i].type == M3_DUMP_REGION_TYPE) {
			m3_mem = &plat_priv->fw_mem[i];
			break;
		}
	}

	if (!m3_mem) {
		cnss_pr_err("M3 dump memory not allocated\n");
		ret = -ENOMEM;
		goto send_resp;
	}

	if ((event_data->addr != m3_mem->pa) ||
	    (event_data->size > m3_mem->size)) {
		cnss_pr_err("Invalid M3 dump info from FW: addr: %llx, size: %lld; in plat_priv: addr:%pa size: %zd\n",
			    event_data->addr, event_data->size,
			    &m3_mem->pa, m3_mem->size);
		ret = -EINVAL;
		goto send_resp;
	}

	if (!m3_mem->va) {
		cnss_pr_err("M3 mem not remapped!\n");
		ret = -ENOMEM;
		goto send_resp;
	}

	memset(m3_dump_data, 0, sizeof(struct m3_dump));

	m3_dump_data->dump_addr = m3_mem->va;
	m3_dump_data->size = event_data->size;
	m3_dump_data->pdev_id = event_data->pdev_id;
	m3_dump_data->timestamp = ktime_to_ms(ktime_get());
	cnss_pr_dbg("%s: %d: pdev_id: %d va 0x%p size %d\n",
		    __func__, __LINE__,
		    m3_dump_data->pdev_id, m3_dump_data->dump_addr,
		    m3_dump_data->size);

	if (plat_priv->bus_type == CNSS_BUS_PCI)
		snprintf(dump_file_name, sizeof(dump_file_name),
			 "m3_dump_%s.bin", plat_priv->device_name);
	else
		snprintf(dump_file_name, sizeof(dump_file_name),
			 "m3_dump_wifi%d.bin", m3_dump_data->pdev_id);

	ret = cnss_do_m3_dump_upload(plat_priv, (const char *)dump_file_name);
	if (ret)
		cnss_pr_err("M3 Dump upload failed with ret %d", ret);

send_resp:
	cnss_wlfw_m3_dump_upload_done_send_sync(plat_priv,
						event_data->pdev_id,
						ret);

	return ret;
}

static int cnss_qdss_trace_req_data_hdlr(struct cnss_plat_data *plat_priv,
					 void *data)
{
	int ret = 0;
	struct cnss_qmi_event_qdss_trace_save_data *event_data = data;
	char file_name[CNSS_GENL_STR_LEN_MAX];

	if (!plat_priv)
		return -ENODEV;

	get_updated_qdss_trace_filename(plat_priv, event_data->file_name,
					file_name, sizeof(file_name));

	ret = cnss_wlfw_qdss_data_send_sync(plat_priv, file_name,
					    event_data->total_size);

	kfree(data);
	return ret;
}

static void cnss_driver_recovery_work(struct work_struct *work)
{
	struct cnss_plat_data *plat_priv =
		container_of(work, struct cnss_plat_data, recovery_work);

	if (!plat_priv) {
		pr_err("plat_priv is NULL!\n");
		return;
	}
	cnss_do_recovery(plat_priv, plat_priv->reason);
}

static void cnss_driver_event_work(struct work_struct *work)
{
	struct cnss_plat_data *plat_priv =
		container_of(work, struct cnss_plat_data, event_work);
	struct cnss_driver_event *event;
	unsigned long flags;
	int ret = 0;

	if (!plat_priv) {
		printk(KERN_ERR "plat_priv is NULL!\n");
		return;
	}

#ifdef CONFIG_CNSS2_PM
	cnss_pm_stay_awake(plat_priv);
#endif

	spin_lock_irqsave(&plat_priv->event_lock, flags);

	while (!list_empty(&plat_priv->event_list)) {
		event = list_first_entry(&plat_priv->event_list,
					 struct cnss_driver_event, list);
		list_del(&event->list);
		spin_unlock_irqrestore(&plat_priv->event_lock, flags);

		cnss_pr_dbg("Processing driver event: %s%s(%d), state: 0x%lx\n",
			    cnss_driver_event_to_str(event->type),
			    event->sync ? "-sync" : "", event->type,
			    plat_priv->driver_state);

		switch (event->type) {
		case CNSS_DRIVER_EVENT_SERVER_ARRIVE:
			ret = cnss_wlfw_server_arrive(plat_priv, event->data);
			break;
		case CNSS_DRIVER_EVENT_SERVER_EXIT:
			ret = cnss_wlfw_server_exit(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_REQUEST_MEM:
			ret = cnss_bus_alloc_fw_mem(plat_priv);
			if (ret)
				break;
			ret = cnss_wlfw_respond_mem_send_sync(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_FW_MEM_READY:
			ret = cnss_fw_mem_ready_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_FW_READY:
			ret = cnss_fw_ready_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START:
			ret = cnss_cold_boot_cal_start_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE:
			ret = cnss_cold_boot_cal_done_hdlr(plat_priv,
							   event->data);
			break;
		case CNSS_DRIVER_EVENT_REGISTER_DRIVER:
			ret = cnss_bus_register_driver_hdlr(plat_priv,
							    event->data);
			break;
		case CNSS_DRIVER_EVENT_UNREGISTER_DRIVER:
			ret = cnss_bus_unregister_driver_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_RECOVERY:
			ret = cnss_driver_recovery_hdlr(plat_priv,
							event->data);
			break;
		case CNSS_DRIVER_EVENT_FORCE_FW_ASSERT:
			ret = cnss_bus_force_fw_assert_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_IDLE_RESTART:
			set_bit(CNSS_DRIVER_IDLE_RESTART,
				&plat_priv->driver_state);
			/* fall through */
		case CNSS_DRIVER_EVENT_POWER_UP:
			ret = cnss_power_up_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_IDLE_SHUTDOWN:
			set_bit(CNSS_DRIVER_IDLE_SHUTDOWN,
				&plat_priv->driver_state);
			/* fall through */
		case CNSS_DRIVER_EVENT_POWER_DOWN:
			ret = cnss_power_down_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_QDSS_TRACE_REQ_MEM:
			ret = cnss_qdss_trace_req_mem_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_QDSS_TRACE_SAVE:
			ret = cnss_qdss_trace_save_hdlr(plat_priv,
							event->data);
			break;
		case CNSS_DRIVER_EVENT_QDSS_TRACE_FREE:
			ret = cnss_qdss_trace_free_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_QDSS_MEM_READY:
			ret = cnss_qdss_mem_ready_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_M3_DUMP_UPLOAD_REQ:
			ret = cnss_m3_dump_upload_req_hdlr(plat_priv,
							   event->data);
			break;
		case CNSS_DRIVER_EVENT_QDSS_TRACE_REQ_DATA:
			ret = cnss_qdss_trace_req_data_hdlr(plat_priv,
							    event->data);
			break;
		case CNSS_DRIVER_EVENT_RAMDUMP_DONE:
#ifndef CONFIG_CNSS2_KERNEL_5_15
			if (plat_priv->bus_type == CNSS_BUS_AHB)
				ret = cnss_qca8074_notifier_nb(
						&plat_priv->modem_nb,
						CNSS_RAMDUMP_DONE, NULL);
			else
				ret = cnss_qcn9000_notifier_nb(
						&plat_priv->modem_nb,
						CNSS_RAMDUMP_DONE, NULL);
#else
			ret = cnss_qcn9000_notifier_nb(
					&plat_priv->modem_nb,
					CNSS_RAMDUMP_DONE, NULL);
#endif

			break;
		default:
			cnss_pr_err("Invalid driver event type: %d",
				    event->type);
			kfree(event);
			spin_lock_irqsave(&plat_priv->event_lock, flags);
			continue;
		}

		spin_lock_irqsave(&plat_priv->event_lock, flags);
		if (event->sync) {
			event->ret = ret;
			complete(&event->complete);
			continue;
		}
		spin_unlock_irqrestore(&plat_priv->event_lock, flags);

		kfree(event);

		spin_lock_irqsave(&plat_priv->event_lock, flags);
	}
	spin_unlock_irqrestore(&plat_priv->event_lock, flags);

#ifdef CONFIG_CNSS2_PM
	cnss_pm_relax(plat_priv);
#endif
}

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
int cnss_register_subsys(struct cnss_plat_data *plat_priv)
{
	bool multi_pd_arch = false;
	int ret = 0;
	struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info;
	struct device *dev = &plat_priv->plat_dev->dev;
	struct cnss_esoc_info *esoc_info = esoc_info = &plat_priv->esoc_info;

	esoc_info->modem_notify_handler = cnss_register_notifier_cb(plat_priv);

	switch (plat_priv->bus_type) {
	case CNSS_BUS_AHB:
		multi_pd_arch = of_property_read_bool(dev->of_node,
						      "qcom,multipd_arch");
		if (multi_pd_arch &&
		    (!cnss_check_multipd_target(plat_priv)))
			/* Device is multi-pd */
			of_property_read_string(dev->of_node,
						"qcom,userpd-subsys-name",
						&subsys_info->subsys_desc.name);
		else
			subsys_info->subsys_desc.name = "qcom_q6v5_wcss";

		subsys_info->subsys_handle =
			subsystem_get(subsys_info->subsys_desc.name);
		if (!subsys_info->subsys_handle)
			ret = -EINVAL;
		else if (IS_ERR(subsys_info->subsys_handle))
			ret = PTR_ERR(subsys_info->subsys_handle);

		return ret;
	case CNSS_BUS_PCI:
		/* Handled below */
		break;
	default:
		cnss_pr_err("Unknown device ID: 0x%lx\n", plat_priv->device_id);
		ret = -ENODEV;
		goto out;
	}

	if (!plat_priv->pci_dev) {
		ret = -ENODEV;
		goto out;
	}

	subsys_info->subsys_desc.name = plat_priv->device_name;

	subsys_info->subsys_desc.owner = THIS_MODULE;
	subsys_info->subsys_desc.powerup = cnss_subsys_powerup;
	subsys_info->subsys_desc.shutdown = cnss_subsys_shutdown;
	subsys_info->subsys_desc.ramdump = cnss_subsys_ramdump;
	subsys_info->subsys_desc.crash_shutdown = cnss_subsys_crash_shutdown;
	subsys_info->subsys_desc.dev = &plat_priv->plat_dev->dev;

	subsys_info->subsys_device = subsys_register(&subsys_info->subsys_desc);
	if (IS_ERR(subsys_info->subsys_device)) {
		ret = PTR_ERR(subsys_info->subsys_device);
		cnss_pr_err("Failed to register subsys, err = %d\n", ret);
		goto out;
	}
	subsys_info->subsys_handle =
		subsystem_get(subsys_info->subsys_desc.name);
	if (!subsys_info->subsys_handle) {
		cnss_pr_err("Failed to get subsys_handle!\n");
		ret = -EINVAL;
		goto unregister_subsys;
	} else if (IS_ERR(subsys_info->subsys_handle)) {
		ret = PTR_ERR(subsys_info->subsys_handle);
		cnss_pr_err("Failed to do subsystem_get, err = %d\n", ret);
		goto unregister_subsys;
	}
	return 0;

unregister_subsys:
	CNSS_ASSERT(0);
	subsys_unregister(subsys_info->subsys_device);
out:
	return ret;
}
#else
int cnss_register_subsys(struct cnss_plat_data *plat_priv)
{
#ifndef CONFIG_CNSS2_KERNEL_5_15
	struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info;
	struct device *dev = &plat_priv->plat_dev->dev;
	struct rproc *rproc_handle;
	phandle rproc_node;
	struct rproc *rproc_rpd_handle;
	phandle rproc_rpd_node;
#endif
	int ret = 0;

	switch (plat_priv->bus_type) {
	case CNSS_BUS_AHB:
#ifndef CONFIG_CNSS2_KERNEL_5_15
		if (!plat_priv->rproc_handle) {
			/* rproc_handle for AHB targets are allocated inside
			 * remoteproc driver and is never freed.
			 * So get the rproc_handle once during first wifi load
			 * and reuse it for every subsequent loads
			 */
			if (of_property_read_u32(dev->of_node, "qcom,rproc",
						 &rproc_node)) {
				return -ENODEV;
			}

			plat_priv->rproc_handle =
				rproc_get_by_phandle(rproc_node);
			if (IS_ERR_OR_NULL(plat_priv->rproc_handle)) {
				cnss_pr_err("%s: Failed to get rproc handle %ld for device %s\n",
					    __func__,
					    PTR_ERR(plat_priv->rproc_handle),
					    plat_priv->device_name);
				return -EINVAL;
			}

			of_property_read_u32(dev->of_node, "qcom,rproc_rpd",
						 &rproc_rpd_node);
			plat_priv->rproc_rpd_handle =
				rproc_get_by_phandle(rproc_rpd_node);
			if (IS_ERR_OR_NULL(plat_priv->rproc_rpd_handle)) {
				cnss_pr_err("%s: Failed to get rproc handle \
					    %ld for device %s\n", __func__,
					    PTR_ERR(
					    plat_priv->rproc_rpd_handle),
					    plat_priv->device_name);
			}
		}

		rproc_handle = plat_priv->rproc_handle;
		rproc_rpd_handle = plat_priv->rproc_rpd_handle;
		subsys_info->subsys_desc.name = rproc_handle->name;
#endif
		break;
	case CNSS_BUS_PCI:
#ifdef CONFIG_CNSS2_KERNEL_5_15
		ret = cnss_hif_power_up(plat_priv);
		if (ret != 0) {
			cnss_pr_err("%s: cnss_hif_power_up failed(%d)\n",
				    __func__, ret);
		}
#else
		if (!plat_priv->rproc_handle) {
			/* Should never happen as rproc_handle is allocated
			 * during cnss_probe for PCI targets
			 */
			cnss_pr_err("Invalid rproc_handle for %s",
				    plat_priv->device_name);
			return -ENODEV;
		}
		subsys_info->subsys_desc.name = plat_priv->device_name;
#endif
		break;
	default:
		cnss_pr_err("%s: Invalid bus type for device 0x%lx\n",
			    __func__, plat_priv->device_id);
		return -ENODEV;

	}

	/* plat_priv->rproc_handle is never freed but subsys_handle is set here
	 * and is reset to NULL everytime target is shutdown.
	 */
#ifndef CONFIG_CNSS2_KERNEL_5_15
	subsys_info->subsys_handle = plat_priv->rproc_handle;
	plat_priv->esoc_info.modem_notify_handler =
				cnss_register_notifier_cb(plat_priv);

	ret = rproc_boot(subsys_info->subsys_handle);
	if (ret) {
		cnss_pr_err("%s: Failed to boot device %s (%d)\n",
			    __func__, plat_priv->device_name, ret);
		CNSS_ASSERT(0);
		cnss_unregister_notifier_cb(plat_priv);
	}
#endif

	return ret;
}
#endif

void cnss_unregister_subsys(struct cnss_plat_data *plat_priv)
{
	struct cnss_subsys_info *subsys_info;

	if (plat_priv->bus_type == CNSS_BUS_AHB)
		return;

	subsys_info = &plat_priv->subsys_info;

#ifdef CONFIG_CNSS2_KERNEL_SSR_FRAMEWORK
	if (subsys_info->subsys_handle)
		subsystem_put(subsys_info->subsys_handle);

	subsys_unregister(subsys_info->subsys_device);
	subsys_info->subsys_device = NULL;
#else /* CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK */
	if (subsys_info->subsys_handle)
		rproc_shutdown(subsys_info->subsys_handle);
#endif
	subsys_info->subsys_handle = NULL;
}

#ifdef CONFIG_CNSS2_KERNEL_5_15
int cnss_register_ramdump(struct cnss_plat_data *plat_priv)
{
	struct cnss_ramdump_info_v2 *info_v2 = &plat_priv->ramdump_info_v2;
	struct cnss_dump_data *dump_data = dump_data = &info_v2->dump_data;
	struct device *dev = &plat_priv->plat_dev->dev;
	int gfp = GFP_KERNEL;
	u32 ramdump_size = 0;

	if (in_interrupt() || irqs_disabled())
		gfp = GFP_ATOMIC;

	if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic",
				 &ramdump_size) == 0)
		info_v2->ramdump_size = ramdump_size;

	cnss_pr_dbg("Ramdump size 0x%lx\n", info_v2->ramdump_size);

	/* Dump data allocated during previous register needs to be freed
	  * before allocating again
	  */
	info_v2->ramdump_dev = NULL;
	kfree(info_v2->dump_data_vaddr);
	info_v2->dump_data_vaddr = NULL;
	info_v2->dump_data_valid = false;

	info_v2->dump_data_vaddr = kzalloc(CNSS_DUMP_DESC_SIZE, gfp);
	if (!info_v2->dump_data_vaddr)
		return -ENOMEM;

	dump_data->paddr = virt_to_phys(info_v2->dump_data_vaddr);
	dump_data->version = CNSS_DUMP_FORMAT_VER_V2;
	dump_data->magic = CNSS_DUMP_MAGIC_VER_V2;
	dump_data->seg_version = CNSS_DUMP_SEG_VER;
	strlcpy(dump_data->name, CNSS_DUMP_NAME,
		sizeof(dump_data->name));

	info_v2->ramdump_dev = dev;

	return 0;
}

void cnss_unregister_ramdump(struct cnss_plat_data *plat_priv)
{
	struct cnss_ramdump_info_v2 *info_v2 = &plat_priv->ramdump_info_v2;

	info_v2->ramdump_dev = NULL;
	kfree(info_v2->dump_data_vaddr);
	info_v2->dump_data_vaddr = NULL;
	info_v2->dump_data_valid = false;
}
#else /* !CONFIG_CNSS2_KERNEL_5_15 */
static int cnss_init_dump_entry(struct cnss_plat_data *plat_priv)
{
	struct cnss_ramdump_info *ramdump_info;
	struct msm_dump_entry dump_entry;

	ramdump_info = &plat_priv->ramdump_info;
	ramdump_info->dump_data.addr = ramdump_info->ramdump_pa;
	ramdump_info->dump_data.len = ramdump_info->ramdump_size;
	ramdump_info->dump_data.version = CNSS_DUMP_FORMAT_VER;
	ramdump_info->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2;
	strlcpy(ramdump_info->dump_data.name, CNSS_DUMP_NAME,
		sizeof(ramdump_info->dump_data.name));
	dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN;
	dump_entry.addr = virt_to_phys(&ramdump_info->dump_data);

#ifdef NOMINIDUMP
	return msm_dump_data_register_nominidump(MSM_DUMP_TABLE_APPS,
						&dump_entry);
#else
	return 0;
#endif
}

static int cnss_register_ramdump_v1(struct cnss_plat_data *plat_priv)
{
	int ret = 0;
	struct device *dev;
	struct cnss_subsys_info *subsys_info;
	struct cnss_ramdump_info *ramdump_info;
	u32 ramdump_size = 0;

	dev = &plat_priv->plat_dev->dev;
	subsys_info = &plat_priv->subsys_info;
	ramdump_info = &plat_priv->ramdump_info;

	if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic",
				 &ramdump_size) == 0) {
		ramdump_info->ramdump_va =
			dma_alloc_coherent(dev, ramdump_size,
					   &ramdump_info->ramdump_pa,
					   GFP_KERNEL);

		if (ramdump_info->ramdump_va)
			ramdump_info->ramdump_size = ramdump_size;
	}

	cnss_pr_dbg("ramdump va: %pK, pa: %pa\n",
		    ramdump_info->ramdump_va, &ramdump_info->ramdump_pa);

	if (ramdump_info->ramdump_size == 0) {
		cnss_pr_info("Ramdump will not be collected");
		goto out;
	}

	ret = cnss_init_dump_entry(plat_priv);
	if (ret) {
		cnss_pr_err("Failed to setup dump table, err = %d\n", ret);
		goto free_ramdump;
	}

	ramdump_info->ramdump_dev =
		create_ramdump_device(subsys_info->subsys_desc.name,
				      subsys_info->subsys_desc.dev);
	if (!ramdump_info->ramdump_dev) {
		cnss_pr_err("Failed to create ramdump device!");
		ret = -ENOMEM;
		goto free_ramdump;
	}

	return 0;
free_ramdump:
	dma_free_coherent(dev, ramdump_info->ramdump_size,
			  ramdump_info->ramdump_va, ramdump_info->ramdump_pa);
out:
	return ret;
}

static void cnss_unregister_ramdump_v1(struct cnss_plat_data *plat_priv)
{
	struct device *dev;
	struct cnss_ramdump_info *ramdump_info;

	dev = &plat_priv->plat_dev->dev;
	ramdump_info = &plat_priv->ramdump_info;

	if (ramdump_info->ramdump_dev)
		destroy_ramdump_device(ramdump_info->ramdump_dev);

	if (ramdump_info->ramdump_va)
		dma_free_coherent(dev, ramdump_info->ramdump_size,
				  ramdump_info->ramdump_va,
				  ramdump_info->ramdump_pa);
}

static u32 cnss_get_dump_desc_size(struct cnss_plat_data *plat_priv)
{
	u32 descriptor_size = 0;
	u32 segment_len = CNSS_MHI_SEG_LEN;
	u32 wlan_sram_size = plat_priv->ramdump_info_v2.ramdump_size;
	struct pci_dev *pci_dev = plat_priv->pci_dev;

	of_property_read_u32(pci_dev->dev.of_node, "qti,rddm-seg-len",
			     &segment_len);

	descriptor_size = (((wlan_sram_size / segment_len) +
			    CNSS_DUMP_DESC_TOLERANCE) *
			    sizeof(struct cnss_dump_seg));

	return descriptor_size;
}

static int cnss_register_ramdump_v2(struct cnss_plat_data *plat_priv)
{
	int ret = 0;
	struct cnss_subsys_info *subsys_info;
	struct cnss_ramdump_info_v2 *info_v2;
	struct cnss_dump_data *dump_data;
	struct msm_dump_entry dump_entry;
	struct device *dev = &plat_priv->plat_dev->dev;
#ifdef CONFIG_CNSS2_KERNEL_IPQ
	char ramdump_dev_name[CNSS_RAMDUMP_FILE_NAME_MAX_LEN] = {0};
#endif
	u32 ramdump_size = 0;

	subsys_info = &plat_priv->subsys_info;
	info_v2 = &plat_priv->ramdump_info_v2;
	dump_data = &info_v2->dump_data;

	if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic",
				 &ramdump_size) == 0)
		info_v2->ramdump_size = ramdump_size;

	cnss_pr_dbg("Ramdump size 0x%lx\n", info_v2->ramdump_size);

	/* Dump data allocated during previous register needs to be freed
	 * before allocating again
	 */
	kfree(info_v2->dump_data_vaddr);
	info_v2->dump_data_vaddr = NULL;
	info_v2->dump_data_valid = false;

	info_v2->dump_data_vaddr = kzalloc(cnss_get_dump_desc_size(plat_priv),
					   GFP_KERNEL);
	if (!info_v2->dump_data_vaddr)
		return -ENOMEM;

	dump_data->paddr = virt_to_phys(info_v2->dump_data_vaddr);
	dump_data->version = CNSS_DUMP_FORMAT_VER_V2;
	dump_data->magic = CNSS_DUMP_MAGIC_VER_V2;
	dump_data->seg_version = CNSS_DUMP_SEG_VER_V2;
	strlcpy(dump_data->name, CNSS_DUMP_NAME,
		sizeof(dump_data->name));
	dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN;
	dump_entry.addr = virt_to_phys(dump_data);

#ifdef NOMINIDUMP
	ret = msm_dump_data_register_nominidump(MSM_DUMP_TABLE_APPS,
						&dump_entry);
#else
	ret = 0;
#endif
	if (ret) {
		cnss_pr_err("Failed to setup dump table, err = %d\n", ret);
		goto free_ramdump;
	}
#ifdef CONFIG_CNSS2_KERNEL_IPQ
	snprintf(ramdump_dev_name, sizeof(ramdump_dev_name), "ramdump_%s",
		 plat_priv->device_name);
	info_v2->ramdump_dev =
		create_ramdump_device((const char *)ramdump_dev_name,
				      subsys_info->subsys_desc.dev);
#else
	/*
	 * WAR to avoid memory corruption where freed memory of ramdump_device
	 * is getting accessed inside kernel and getting crashed.
	 *
	 * ramdump_device created is not freed during cnss_pci_remove and the
	 * same will be used the next time cnss_pci_probe is called
	 */
	if (plat_priv->rd_dev_present) {
		cnss_pr_info("Skipping rd_dev creation for %s ",
			      plat_priv->device_name);
		return 0;
	}
	info_v2->ramdump_dev =
		create_ramdump_device(subsys_info->subsys_desc.name,
				      subsys_info->subsys_desc.dev);
	plat_priv->rd_dev_present = true;
#endif
	if (!info_v2->ramdump_dev) {
		cnss_pr_err("Failed to create ramdump device!\n");
		ret = -ENOMEM;
		goto free_ramdump;
	}

	return 0;

free_ramdump:
	kfree(info_v2->dump_data_vaddr);
	plat_priv->rd_dev_present = false;
	info_v2->dump_data_vaddr = NULL;
	return ret;
}

static void cnss_unregister_ramdump_v2(struct cnss_plat_data *plat_priv)
{
	struct cnss_ramdump_info_v2 *info_v2;

	info_v2 = &plat_priv->ramdump_info_v2;

	if (info_v2->ramdump_dev)
		destroy_ramdump_device(info_v2->ramdump_dev);

	/* Freeing the dump data here causes loss of valid dump data in the
	 * below scenario
	 *  1. wifi down is in progress and target asserts after sending
	 *     mode OFF message
	 *  2. MHI notifies target assert, RDDM dump collection happens and
	 *     dump_data_vaddr has valid dump data
	 *  3. cnss_pci_remove is called as part of wifi down and dump data
	 *     with valid contents is now freed.
	 *
	 * To Avoid this scenario, skip freeing dump_data_vaddr here and free
	 * as part of cnss_register_ramdump_v2
	 *
	 * kfree(info_v2->dump_data_vaddr);
	 * info_v2->dump_data_vaddr = NULL;
	 * info_v2->dump_data_valid = false;
	 */
}

int cnss_register_ramdump(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	switch (plat_priv->device_id) {
	case QCA6174_DEVICE_ID:
		ret = cnss_register_ramdump_v1(plat_priv);
		break;
	case QCN9000_EMULATION_DEVICE_ID:
	case QCN9000_DEVICE_ID:
	case QCN9224_DEVICE_ID:
	case QCA6390_DEVICE_ID:
	case QCA6490_DEVICE_ID:
		ret = cnss_register_ramdump_v2(plat_priv);
		break;
	default:
		cnss_pr_err("Unknown device ID: 0x%lx\n", plat_priv->device_id);
		ret = -ENODEV;
		break;
	}
	return ret;
}

void cnss_unregister_ramdump(struct cnss_plat_data *plat_priv)
{
	switch (plat_priv->device_id) {
	case QCA6174_DEVICE_ID:
		cnss_unregister_ramdump_v1(plat_priv);
		break;
	case QCN9000_EMULATION_DEVICE_ID:
	case QCN9000_DEVICE_ID:
	case QCN9224_DEVICE_ID:
	case QCA6390_DEVICE_ID:
	case QCA6490_DEVICE_ID:
		cnss_unregister_ramdump_v2(plat_priv);
		break;
	default:
		cnss_pr_err("Unknown device ID: 0x%lx\n", plat_priv->device_id);
		break;
	}
}
#endif /* !CONFIG_CNSS2_KERNEL_5_15 */

#ifdef CONFIG_CNSS2_PM
static int cnss_register_bus_scale(struct cnss_plat_data *plat_priv)
{
	int ret = 0;
	struct cnss_bus_bw_info *bus_bw_info;

	bus_bw_info = &plat_priv->bus_bw_info;

	bus_bw_info->bus_scale_table =
		msm_bus_cl_get_pdata(plat_priv->plat_dev);
	if (bus_bw_info->bus_scale_table)  {
		bus_bw_info->bus_client =
			msm_bus_scale_register_client
			(bus_bw_info->bus_scale_table);
		if (!bus_bw_info->bus_client) {
			cnss_pr_err("Failed to register bus scale client!\n");
			ret = -EINVAL;
			goto out;
		}
	}

	return 0;
out:
	return ret;
}

static void cnss_unregister_bus_scale(struct cnss_plat_data *plat_priv)
{
	struct cnss_bus_bw_info *bus_bw_info;

	bus_bw_info = &plat_priv->bus_bw_info;

	if (bus_bw_info->bus_client)
		msm_bus_scale_unregister_client(bus_bw_info->bus_client);
}
#endif

void cnss_config_param_update_cb(uint32_t instance_id,
			     enum cnss_plat_ipc_qmi_config_param_type_v01 param,
			     uint64_t value)
{
	struct cnss_plat_data *plat_priv = NULL;

	cnss_pr_info("%s: Instance ID: 0x%x Param %d Value: %llu\n", __func__,
		     instance_id, param, value);

	plat_priv = cnss_get_plat_priv_by_instance_id(instance_id);

	if (!plat_priv) {
		cnss_pr_err("Failed to get plat_priv for instance_id 0x%x\n",
			    instance_id);
		return;
	}

	switch (param) {
	case CNSS_PLAT_IPC_PARAM_TYPE_DAEMON_SUPPORT_V01:
		plat_priv->daemon_support = value;
		cnss_pr_info("Setting daemon_support=%llu for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_PLAT_IPC_PARAM_TYPE_COLD_BOOT_SUPPORT_V01:
		plat_priv->cold_boot_support = value;
		cnss_pr_info("Setting cold_boot_support=%llu for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_PLAT_IPC_PARAM_TYPE_HDS_SUPPORT_V01:
		plat_priv->hds_support = value;
		cnss_pr_info("Setting hds_support=%llu for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_PLAT_IPC_PARAM_TYPE_REGDB_SUPPORT_V01:
		plat_priv->regdb_support = value;
		cnss_pr_info("Setting regdb_support=%llu for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_PLAT_IPC_PARAM_TYPE_QDSS_SUPPORT_V01:
		plat_priv->qdss_support = value;
		cnss_pr_info("Setting qdss_support=%llu for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_PLAT_IPC_PARAM_TYPE_QDSS_START_V01:
		plat_priv->qdss_etr_sg_mode = value;
		cnss_pr_info("Starting QDSS for %s", plat_priv->device_name);
		cnss_wlfw_qdss_dnld_send_sync(plat_priv);
		break;
	case CNSS_PLAT_IPC_PARAM_TYPE_QDSS_STOP_V01:
		cnss_pr_info("Stopping QDSS for %s", plat_priv->device_name);
		cnss_wlfw_send_qdss_trace_mode_req(plat_priv,
						   QMI_WLFW_QDSS_TRACE_OFF_V01,
						   value);
		break;
	default:
		cnss_pr_err("Unknown config param type %d\n", param);
		break;
	}
}

void cnss_daemon_connection_update_cb(void *cb_ctx, bool status)
{
	int i;
	struct cnss_plat_data *plat_priv = NULL;

	cnss_pr_dbg("%s: Connection status %u\n", __func__, status);

	for (i = 0; i < plat_env_index; i++) {
		if (status)
			set_bit(CNSS_DAEMON_CONNECTED,
				&plat_env[i]->driver_state);
		else
			clear_bit(CNSS_DAEMON_CONNECTED,
				  &plat_env[i]->driver_state);
	}

}

static ssize_t fs_ready_store(struct device *dev,
			      struct device_attribute *attr,
			      const char *buf, size_t count)
{
	int fs_ready = 0;
	struct cnss_plat_data *plat_priv = dev_get_drvdata(dev);

	if (sscanf(buf, "%du", &fs_ready) != 1)
		return -EINVAL;

	cnss_pr_dbg("File system is ready, fs_ready is %d, count is %zu\n",
		    fs_ready, count);

	if (test_bit(QMI_BYPASS, &plat_priv->ctrl_params.quirks)) {
		printk(KERN_INFO "QMI is bypassed.\n");
		return count;
	}

	if (!plat_priv) {
		printk(KERN_ERR "plat_priv is NULL!\n");
		return count;
	}

	switch (plat_priv->device_id) {
	case QCN9000_EMULATION_DEVICE_ID:
	case QCN9000_DEVICE_ID:
	case QCN9224_DEVICE_ID:
	case QCA6390_DEVICE_ID:
	case QCA6490_DEVICE_ID:
		break;
	default:
		cnss_pr_err("Not supported for device ID 0x%lx\n",
			    plat_priv->device_id);
		return count;
	}

	if (fs_ready == FILE_SYSTEM_READY) {
		cnss_driver_event_post(plat_priv,
				       CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START,
				       CNSS_EVENT_SYNC, NULL);
	}

	return count;
}

static DEVICE_ATTR_WO(fs_ready);

static int cnss_create_sysfs(struct cnss_plat_data *plat_priv)
{
	int ret = 0;

	ret = device_create_file(&plat_priv->plat_dev->dev, &dev_attr_fs_ready);
	if (ret) {
		cnss_pr_err("Failed to create device file, err = %d\n", ret);
		goto out;
	}

	return 0;
out:
	return ret;
}

static void cnss_remove_sysfs(struct cnss_plat_data *plat_priv)
{
	device_remove_file(&plat_priv->plat_dev->dev, &dev_attr_fs_ready);
}

static int cnss_event_work_init(struct cnss_plat_data *plat_priv)
{
	spin_lock_init(&plat_priv->event_lock);
	plat_priv->event_wq = alloc_workqueue("cnss_driver_event",
					      WQ_UNBOUND, 1);
	if (!plat_priv->event_wq) {
		cnss_pr_err("Failed to create event workqueue!\n");
		return -EFAULT;
	}

	INIT_WORK(&plat_priv->event_work, cnss_driver_event_work);
	INIT_LIST_HEAD(&plat_priv->event_list);

	return 0;
}

static int cnss_recovery_work_init(struct cnss_plat_data *plat_priv)
{
	spin_lock_init(&plat_priv->recovery_lock);
	plat_priv->recovery_wq = alloc_workqueue("cnss_driver_recovery",
					      WQ_UNBOUND, 1);
	if (!plat_priv->recovery_wq) {
		cnss_pr_err("Failed to create event workqueue!\n");
		return -EFAULT;
	}

	INIT_WORK(&plat_priv->recovery_work, cnss_driver_recovery_work);

	return 0;
}

static void cnss_event_work_deinit(struct cnss_plat_data *plat_priv)
{
	if (plat_priv->event_wq)
		destroy_workqueue(plat_priv->event_wq);
}

static void cnss_recovery_work_deinit(struct cnss_plat_data *plat_priv)
{
	if (plat_priv->recovery_wq)
		destroy_workqueue(plat_priv->recovery_wq);
}

#ifdef CONFIG_CNSS2_KERNEL_5_15
static void cnss_report_crash_work(struct work_struct *work)
{
	int index;
	struct cnss_plat_data *plat_priv =
		container_of(work, struct cnss_plat_data, crash_work);

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL!\n");
		return;
	}

	index = cnss_get_plat_env_index_from_plat_priv(plat_priv);
	if (index < 0) {
		cnss_pr_err("Invalid plat_env index for %s",
			    plat_priv->device_name);
		return;
	}
	cnss_hif_shutdown(plat_priv);
	cnss_hif_notifier(plat_priv, CNSS_RAMDUMP_NOTIFICATION);
	cnss_bus_dev_ramdump(plat_priv);
	cnss_hif_power_up(plat_priv);
}
#endif

static void cnss_driver_cal_work(struct work_struct *work)
{
	int ret, index, count = 0;
	struct cnss_plat_data *plat_priv =
		container_of(work, struct cnss_plat_data, cal_work);
	struct cnss_plat_data *prev_plat_priv;

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL!\n");
		return;
	}

	index = cnss_get_plat_env_index_from_plat_priv(plat_priv);
	if (index < 0) {
		cnss_pr_err("Invalid plat_env index for %s",
			    plat_priv->device_name);
		return;
	}

	cnss_wait_for_cold_boot_cal_done(plat_priv);

	ret = cnss_wlfw_wlan_mode_send_sync(plat_priv, CNSS_OFF);
	if (ret) {
		cnss_pr_err("Failed to send Mode OFF for %s. Ret: %d",
			    plat_priv->device_name, ret);
		return;
	}

	if (soft_switch) {
		plat_priv->cal_in_progress = false;
		plat_priv->driver_ops->probe(
				(struct pci_dev *)plat_priv->plat_dev,
				(const struct pci_device_id *)
				plat_priv->plat_dev_id);
	} else {
#ifndef CONFIG_CNSS2_KERNEL_5_15
		__cnss_subsystem_put(plat_priv);
#else
		__cnss_hif_put(plat_priv);
#endif
		plat_priv->cal_in_progress = false;

		/* Temporary change to preserve probe order */
		if (index > 0) {
			prev_plat_priv = plat_env[index - 1];
			while (prev_plat_priv->driver_status !=
					CNSS_INITIALIZED) {
				cnss_pr_dbg("Waiting for prev target to probe\n");
				msleep(FW_READY_DELAY);
				if (count++ > probe_timeout * 10) {
					cnss_pr_err("CNSS Driver probe timed out\n");
					CNSS_ASSERT(0);
				}
			}
			cnss_pr_info("Previous target is probed\n");
		}
#ifndef CONFIG_CNSS2_KERNEL_5_15
		(void)__cnss_subsystem_get(plat_priv);
#else
		(void)__cnss_hif_get(plat_priv);
#endif
	}

	plat_priv->driver_status = CNSS_INITIALIZED;
	atomic_dec(&cal_in_progress_count);
}

static void cnss_cal_work_init(struct cnss_plat_data *plat_priv)
{
	INIT_WORK(&plat_priv->cal_work, cnss_driver_cal_work);
}

#ifdef CONFIG_CNSS2_KERNEL_5_15
static void cnss_crash_work_init(struct cnss_plat_data *plat_priv)
{
	INIT_WORK(&plat_priv->crash_work, cnss_report_crash_work);
}
#endif

static int cnss_misc_init(struct cnss_plat_data *plat_priv)
{
	int ret;

	timer_setup(&plat_priv->fw_boot_timer,
		    cnss_bus_fw_boot_timeout_hdlr, 0);
#ifdef CONFIG_CNSS2_PM
	register_pm_notifier(&cnss_pm_notifier);
#endif

	ret = device_init_wakeup(&plat_priv->plat_dev->dev, true);
	if (ret)
		cnss_pr_err("Failed to init platform device wakeup source, err = %d\n",
			    ret);

	init_completion(&plat_priv->power_up_complete);
	init_completion(&plat_priv->cal_complete);
	init_completion(&plat_priv->rddm_complete);
	init_completion(&plat_priv->recovery_complete);
	init_completion(&plat_priv->soc_reset_request_complete);
	mutex_init(&plat_priv->dev_lock);

	return 0;
}

static void cnss_misc_deinit(struct cnss_plat_data *plat_priv)
{
	complete_all(&plat_priv->soc_reset_request_complete);
	complete_all(&plat_priv->recovery_complete);
	complete_all(&plat_priv->rddm_complete);
	complete_all(&plat_priv->cal_complete);
	complete_all(&plat_priv->power_up_complete);
	device_init_wakeup(&plat_priv->plat_dev->dev, false);
#ifdef CONFIG_CNSS2_PM
	unregister_pm_notifier(&cnss_pm_notifier);
#endif
	del_timer(&plat_priv->fw_boot_timer);
}

static void cnss_init_control_params(struct cnss_plat_data *plat_priv)
{
	plat_priv->ctrl_params.quirks = CNSS_QUIRKS_DEFAULT;
	plat_priv->ctrl_params.mhi_timeout =
		CNSS_MHI_TIMEOUT_DEFAULT * timeout_factor;

	if (qmi_timeout)
		plat_priv->ctrl_params.qmi_timeout =
			qmi_timeout * timeout_factor;
	else
		plat_priv->ctrl_params.qmi_timeout =
			CNSS_QMI_TIMEOUT_DEFAULT * timeout_factor;

	plat_priv->ctrl_params.bdf_type = 0;
	plat_priv->ctrl_params.time_sync_period = CNSS_TIME_SYNC_PERIOD_DEFAULT;
}

static const struct platform_device_id cnss_platform_id_table[] = {
	{ .name = "qca6174", .driver_data = QCA6174_DEVICE_ID, },
	{ .name = "qcn9000", .driver_data = QCN9000_DEVICE_ID, },
	{ .name = "qca8074", .driver_data = QCA8074_DEVICE_ID, },
	{ .name = "qca8074v2", .driver_data = QCA8074V2_DEVICE_ID, },
	{ .name = "qca6018", .driver_data = QCA6018_DEVICE_ID, },
	{ .name = "qca5018", .driver_data = QCA5018_DEVICE_ID, },
	{ .name = "qcn6122", .driver_data = QCN6122_DEVICE_ID, },
	{ .name = "qcn9224", .driver_data = QCN9224_DEVICE_ID, },
	{ .name = "qca9574", .driver_data = QCA9574_DEVICE_ID, },
	{ .name = "qca5332", .driver_data = QCA5332_DEVICE_ID, },
	{ .name = "qcn9160", .driver_data = QCN9160_DEVICE_ID, },
};

static const struct of_device_id cnss_of_match_table[] = {
	{
		.compatible = "qcom,cnss",
		.data = (void *)&cnss_platform_id_table[0]},
	{
		.compatible = "qcom,cnss-qcn9000",
		.data = (void *)&cnss_platform_id_table[1]},
	{
		.compatible = "qcom,cnss-qca8074",
		.data = (void *)&cnss_platform_id_table[2]},
	{
		.compatible = "qcom,cnss-qca8074v2",
		.data = (void *)&cnss_platform_id_table[3]},
	{
		.compatible = "qcom,cnss-qca6018",
		.data = (void *)&cnss_platform_id_table[4]},
	{
		.compatible = "qcom,cnss-qca5018",
		.data = (void *)&cnss_platform_id_table[5]},
	{
		.compatible = "qcom,cnss-qcn6122",
		.data = (void *)&cnss_platform_id_table[6]},
	{
		.compatible = "qcom,cnss-qcn9224",
		.data = (void *)&cnss_platform_id_table[7]},
	{
		.compatible = "qcom,cnss-qca9574",
		.data = (void *)&cnss_platform_id_table[8]},
	{
		.compatible = "qcom,cnss-qca5332",
		.data = (void *)&cnss_platform_id_table[9]},
	{
		.compatible = "qcom,cnss-qcn9160",
		.data = (void *)&cnss_platform_id_table[10]},
	{ },
};
MODULE_DEVICE_TABLE(of, cnss_of_match_table);

static const char *
cnss_module_param_feature_to_str(enum cnss_module_param_feature feature)
{
	switch (feature) {
	case CALDATA:
		return "caldata";
	case REGDB:
		return "regdb";
	default:
		return "unknown";
	}
}

static void
cnss_set_mod_param_feature_support(struct cnss_plat_data *plat_priv,
				   enum cnss_module_param_feature feature)
{
	int bmap = 0x0;
	bool *ptr;
	const char *fname = cnss_module_param_feature_to_str(feature);

	switch (feature) {
	case CALDATA:
		bmap = disable_caldata_bmap;
		/* By default caldata support is enabled */
		plat_priv->caldata_support = 1;
		ptr = &plat_priv->caldata_support;
		break;
	case REGDB:
		bmap = disable_regdb_bmap;
		/* By default regdb support should be enabled.
		 * But for now, it is disabled through module param.
		 */
		plat_priv->regdb_support = 1;
		ptr = &plat_priv->regdb_support;
		break;
	default:
		cnss_pr_err("%s: Unknown feature %u %s", __func__, feature,
			    fname);
		return;
	}

	switch (plat_priv->device_id) {
	case QCA8074_DEVICE_ID:
	case QCA8074V2_DEVICE_ID:
	case QCA6018_DEVICE_ID:
	case QCA5018_DEVICE_ID:
	case QCA5332_DEVICE_ID:
	case QCA9574_DEVICE_ID:
		if (bmap & SKIP_INTEGRATED) {
			cnss_pr_info("Disabling %s support for %s", fname,
				     plat_priv->device_name);
			*ptr = 0;
		}
		break;
	case QCN9000_DEVICE_ID:
		if ((plat_priv->qrtr_node_id == QCN9000_0 &&
		     (bmap & SKIP_PCI_0)) ||
		    (plat_priv->qrtr_node_id == QCN9000_1 &&
		     (bmap & SKIP_PCI_1)) ||
		    (plat_priv->qrtr_node_id == QCN9000_2 &&
		     (bmap & SKIP_PCI_2)) ||
		    (plat_priv->qrtr_node_id == QCN9000_3 &&
		     (bmap & SKIP_PCI_3))) {
			*ptr = 0;
			cnss_pr_info("Disabling %s support for %s", fname,
				     plat_priv->device_name);
		}
		break;
	case QCN9160_DEVICE_ID:
	case QCN6122_DEVICE_ID:
		if ((plat_priv->userpd_id == USERPD_0 &&
		     (bmap & SKIP_PCI_0)) ||
		    (plat_priv->userpd_id == USERPD_1 &&
		     (bmap & SKIP_PCI_1))) {
			*ptr = 0;
			cnss_pr_info("Disabling %s support for %s", fname,
				     plat_priv->device_name);
		}
		break;
	case QCN9224_DEVICE_ID:
		if ((plat_priv->qrtr_node_id == QCN9224_0 &&
		     (bmap & SKIP_PCI_0)) ||
		    (plat_priv->qrtr_node_id == QCN9224_1 &&
		     (bmap & SKIP_PCI_1)) ||
		    (plat_priv->qrtr_node_id == QCN9224_2 &&
		     (bmap & SKIP_PCI_2)) ||
		    (plat_priv->qrtr_node_id == QCN9224_3 &&
		     (bmap & SKIP_PCI_3))) {
			*ptr = 0;
			cnss_pr_info("Disabling %s support for %s", fname,
				     plat_priv->device_name);
		}
		break;
	default:
		cnss_pr_err("%s: UNKNOWN DEVICE ID", __func__);
		break;
	}
	return;
}

static int cnss_set_device_name(struct cnss_plat_data *plat_priv)
{

	switch (plat_priv->device_id) {
	case QCN9000_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCN9000_PCI%d", plat_priv->pci_slot_id);
		break;
	case QCN9224_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCN9224_PCI%d", plat_priv->pci_slot_id);
		break;
	case QCA8074_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCA8074");
		break;
	case QCA8074V2_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCA8074v2");
		break;
	case QCA6018_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCA6018");
		break;
	case QCA5018_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCA5018");
		break;
	case QCN6122_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCN6122_%d", plat_priv->userpd_id);
		break;
	case QCN9160_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCN9160_%d", plat_priv->userpd_id);
		break;
	case QCA9574_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCA9574");
		break;
	case QCA5332_DEVICE_ID:
		snprintf(plat_priv->device_name, sizeof(plat_priv->device_name),
			 "QCA5332");
		break;
	default:
		cnss_pr_err("No such device id 0x%lx\n", plat_priv->device_id);
		return -ENODEV;
	}

	return 0;
}

void cnss_update_platform_feature_support(u8 type, u32 instance_id, u32 value)
{
	struct cnss_plat_data *plat_priv;

	plat_priv = cnss_get_plat_priv_by_instance_id(instance_id);
	if (!plat_priv) {
		cnss_pr_err("Failed to get plat_priv for instance_id 0x%x\n",
			    instance_id);
		return;
	}

	switch (type) {
	case CNSS_GENL_MSG_TYPE_DAEMON_SUPPORT:
		plat_priv->daemon_support = value;
		cnss_pr_info("Setting daemon_support=%d for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_GENL_MSG_TYPE_COLD_BOOT_SUPPORT:
		plat_priv->cold_boot_support = value;
		cnss_pr_info("Setting cold_boot_support=%d for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_GENL_MSG_TYPE_HDS_SUPPORT:
		plat_priv->hds_support = value;
		cnss_pr_info("Setting hds_support=%d for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_GENL_MSG_TYPE_REGDB_SUPPORT:
		plat_priv->regdb_support = value;
		cnss_pr_info("Setting regdb_support=%d for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_GENL_MSG_TYPE_QDSS_SUPPORT:
		plat_priv->qdss_support = value;
		cnss_pr_info("Setting qdss_support=%d for instance_id 0x%x\n",
			     value, instance_id);
		break;
	case CNSS_GENL_MSG_TYPE_QDSS_START:
		plat_priv->qdss_etr_sg_mode = value;
		cnss_pr_info("Starting QDSS for %s", plat_priv->device_name);
		cnss_wlfw_qdss_dnld_send_sync(plat_priv);
		break;
	case CNSS_GENL_MSG_TYPE_QDSS_STOP:
		cnss_pr_info("Stopping QDSS for %s", plat_priv->device_name);
		cnss_wlfw_send_qdss_trace_mode_req(plat_priv,
						   QMI_WLFW_QDSS_TRACE_OFF_V01,
						   value);
		break;
	default:
		cnss_pr_err("Unknown type %d\n", type);
		break;
	}
}

static int platform_get_userpd_id(struct platform_device *plat_dev,
					  uint32_t *userpd_id)
{
	int ret = 0;
	const char *subsys_name;

	ret = of_property_read_string(plat_dev->dev.of_node,
				      "qcom,userpd-subsys-name",
				      &subsys_name);
	if (ret) {
		pr_err("subsys name get failed");
		return -EINVAL;
	}

	if (strcmp(subsys_name, "q6v5_wcss_userpd2") == 0) {
		*userpd_id = USERPD_0;
	} else if (strcmp(subsys_name, "q6v5_wcss_userpd3") == 0) {
		*userpd_id = USERPD_1;
	} else {
		pr_err("subsys name %s not found", subsys_name);
		ret = -EINVAL;
	}

	return ret;
}

static bool
cnss_check_skip_target_probe(const struct platform_device_id *device_id,
			     u32 userpd_id, u32 node_id)
{
	/* skip_cnss based skip target checks */
	if (skip_cnss == CNSS_SKIP_ALL) {
		pr_err("Skipping cnss_probe for device 0x%lx\n",
		       device_id->driver_data);
		return true;
	} else if (skip_cnss == CNSS_SKIP_PCI &&
		   (device_id->driver_data == QCN9000_DEVICE_ID ||
		    device_id->driver_data == QCN9224_DEVICE_ID)) {
		pr_err("Skipping cnss_probe for device 0x%lx\n",
		       device_id->driver_data);
		return true;
	} else if (skip_cnss == CNSS_SKIP_AHB &&
		   (device_id->driver_data == QCA8074_DEVICE_ID ||
		   device_id->driver_data == QCA8074V2_DEVICE_ID ||
		   device_id->driver_data == QCA6018_DEVICE_ID ||
		   device_id->driver_data == QCN6122_DEVICE_ID ||
		   device_id->driver_data == QCN9160_DEVICE_ID ||
		   device_id->driver_data == QCA5018_DEVICE_ID ||
		   device_id->driver_data == QCA5332_DEVICE_ID ||
		   device_id->driver_data == QCA9574_DEVICE_ID)) {
		pr_err("Skipping cnss_probe for device 0x%lx\n",
		       device_id->driver_data);
		return true;
	}

	/* skip_radio_bmap based skip target checks
	 * SKIP_INTEGRATED - skip integrated radios ie. 5018,8074,8074v2,6018
	 * SKIP_PCI_0 - skip PCI_0 radios ie. first qcn9000/qcn6122 radios
	 * SKIP_PCI_1 - skip PCI_1 radios ie. second qcn9000/qcn6122 radios
	 */
	if ((skip_radio_bmap & SKIP_INTEGRATED) &&
	    ((device_id->driver_data == QCA5018_DEVICE_ID) ||
	    (device_id->driver_data == QCA8074_DEVICE_ID) ||
	    (device_id->driver_data == QCA8074V2_DEVICE_ID) ||
	    (device_id->driver_data == QCA6018_DEVICE_ID) ||
	    (device_id->driver_data == QCA5332_DEVICE_ID) ||
	    (device_id->driver_data == QCA9574_DEVICE_ID))) {
		pr_err("Skipping cnss_probe for device 0x%lx\n",
		       device_id->driver_data);
		return true;
	} else if ((skip_radio_bmap & SKIP_PCI_0) &&
		   (((userpd_id == USERPD_0) &&
		   ((device_id->driver_data == QCN6122_DEVICE_ID) ||
		   (device_id->driver_data == QCN9160_DEVICE_ID))) ||
		   ((node_id == QCN9000_0 || node_id == QCN9224_0) &&
		   (device_id->driver_data == QCN9000_DEVICE_ID ||
		    device_id->driver_data == QCN9224_DEVICE_ID)))) {
		pr_err("Skipping cnss_probe for PCI_0 device 0x%lx\n",
		       device_id->driver_data);
		return true;
	} else if ((skip_radio_bmap & SKIP_PCI_1) &&
		   (((userpd_id == USERPD_1) &&
		   ((device_id->driver_data == QCN6122_DEVICE_ID) ||
		   (device_id->driver_data == QCN9160_DEVICE_ID))) ||
		   ((node_id == QCN9000_1 || node_id == QCN9224_1) &&
		   (device_id->driver_data == QCN9000_DEVICE_ID ||
		    device_id->driver_data == QCN9224_DEVICE_ID)))) {
		pr_err("Skipping cnss_probe for PCI_1 device 0x%lx\n",
		       device_id->driver_data);
		return true;
	} else if ((skip_radio_bmap & SKIP_PCI_2) &&
		   ((node_id == QCN9000_2 || node_id == QCN9224_2) &&
		   (device_id->driver_data == QCN9000_DEVICE_ID ||
		    device_id->driver_data == QCN9224_DEVICE_ID))) {
		pr_err("Skipping cnss_probe for PCI_2 device 0x%lx\n",
		       device_id->driver_data);
		return true;
	} else if ((skip_radio_bmap & SKIP_PCI_3) &&
		   ((node_id == QCN9000_3 || node_id == QCN9224_3) &&
		   (device_id->driver_data == QCN9000_DEVICE_ID ||
		    device_id->driver_data == QCN9224_DEVICE_ID))) {
		pr_err("Skipping cnss_probe for PCI_3 device 0x%lx\n",
		       device_id->driver_data);
		return true;
	}

	return false;
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0))
static int cnss_panic_handler(struct notifier_block *this,
                                unsigned long event, void *ptr)
{
	int i;
	struct cnss_plat_data *plat_priv = NULL;

	mutex_lock(&rproc_list_mutex);
	for (i = 0; i < plat_env_index; i++) {
		if (plat_env[i]->target_asserted == 1) {
			mutex_unlock(&rproc_list_mutex);
			return 0;
		}
	}

	for (i = 0; i < plat_env_index; i++) {
		cnss_pr_dbg("cnss_panic_handler for plat_env %d\n", i);
		cnss_bus_dev_crash_shutdown(plat_env[i]);
	}

	mutex_unlock(&rproc_list_mutex);
	return 0;
}

static void cnss_panic_notifier_register(void)
{
	int ret;
	struct cnss_plat_data *plat_priv = NULL;
	panic_nb.notifier_call = cnss_panic_handler;
	panic_nb.priority = 100;

	ret = atomic_notifier_chain_register(&panic_notifier_list, &panic_nb);
	if(ret)
		cnss_pr_err("Err(%d): register panic notifier failed.\n", ret);
	else
		cnss_pr_dbg("%s: atomic_notifier_chain_register success.\n", __func__);

	return;
}
#endif

#ifdef CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK
const struct rproc_ops cnss_rproc_ops = {
	.start = cnss_subsys_powerup,
	.stop = cnss_subsys_shutdown,
	.load = cnss_subsys_dummy_load,
	.parse_fw = cnss_subsys_add_ramdump_callback,
	.report_panic = cnss_subsys_crash_shutdown,
};

static int cnss_rproc_register(struct cnss_plat_data *plat_priv)
{
	struct device *dev = &plat_priv->plat_dev->dev;
	struct rproc *rproc_handle = NULL;

	switch (plat_priv->bus_type) {
	case CNSS_BUS_AHB:
		/* rproc alloc is done from qcom_q6v5_wcss code for AHB
		 * plat_priv->rproc_handle will be filled during the first
		 * wifi load from cnss_register_subsys
		 */
		return 0;
	case CNSS_BUS_PCI:
		rproc_handle = rproc_alloc(dev, plat_priv->device_name,
					   &cnss_rproc_ops,
					   plat_priv->firmware_name, 0);

		if (IS_ERR_OR_NULL(rproc_handle)) {
			cnss_pr_err("%s: Failed to register rproc %ld for device 0x%s\n",
				    __func__, PTR_ERR(rproc_handle),
				    plat_priv->device_name);
			return -ENODEV;
		}

		rproc_handle->auto_boot = false;
		if (rproc_add(rproc_handle)) {
			cnss_pr_err("%s: Failed to add rproc for device %s\n",
				    __func__, plat_priv->device_name);
			rproc_free(rproc_handle);
			return -EINVAL;
		}

		plat_priv->rproc_handle = rproc_handle;
		break;
	default:
		cnss_pr_err("Invalid bus type for device %s\n",
			    plat_priv->device_name);
		return -EINVAL;
	}
	return 0;
}

static void cnss_rproc_unregister(struct cnss_plat_data *plat_priv)
{
	if (plat_priv->rproc_handle) {
		/* For AHB targets, ref count for rproc is taken during first
		 * wifi load from cnss_register_subsys, put the ref count here.
		 * For PCI targets, rproc_handle is allocated during cnss_prove,
		 * delete and free the proc_handle here.
		 */
		if (plat_priv->bus_type == CNSS_BUS_AHB) {
			rproc_put(plat_priv->rproc_handle);
		} else if (plat_priv->bus_type == CNSS_BUS_PCI) {
			rproc_del(plat_priv->rproc_handle);
			rproc_free(plat_priv->rproc_handle);
		}
	}
}
#endif

static void cnss_fill_probe_order(struct cnss_plat_data *plat_priv)
{
	u32 prb_order = 0;

	if (probe_order) {
		plat_priv->probe_order = (probe_order & CNSS_PROBE_ORDER_MASK);
		probe_order >>= CNSS_PROBE_ORDER_SHIFT;
	} else if (!of_property_read_u32(plat_priv->plat_dev->dev.of_node,
					 "probe-order", &prb_order)) {
		plat_priv->probe_order = prb_order;
	} else {
		plat_priv->probe_order = CNSS_PROBE_ORDER_DEFAULT;
	}
}


#ifdef CONFIG_CNSS2_LEGACY_IRQ
static void cnss_get_legacy_intx_support(struct cnss_plat_data *plat_priv)
{
	int enable_intx;

	if (enable_intx_bmap) {
		enable_intx = (enable_intx_bmap & CNSS_INTX_SUPPORT_MASK);
		enable_intx_bmap >>= CNSS_INTX_SUPPORT_SHIFT;
		if (enable_intx)
			plat_priv->enable_intx = true;
	} else if (of_property_read_bool(plat_priv->plat_dev->dev.of_node,
					 "enable-intx")) {
		plat_priv->enable_intx = true;
	} else {
		plat_priv->enable_intx = false;
	}
}
#endif

static u32 cnss_get_bdf_mod_param(int slot_id)
{
	u32 ret = 0;

	switch (slot_id) {
	case 0:
		ret = (u32)bdf_pci0;
		break;
	case 1:
		ret = (u32)bdf_pci1;
		break;
	case 2:
		ret = (u32)bdf_pci2;
		break;
	case 3:
		ret = (u32)bdf_pci3;
		break;
	default:
		break;
	}
	return ret;
}

/* Set board_id_override field in plat_priv based on the below priority order
 * Bootargs -- bdf_integrated, bdf_pciX...
 * DTS entry
 *
 * If both these are not present, board_id_override would be 0 and board_id
 * from OTP register or target capabilities would be used.
 */
static void cnss_set_board_id(struct cnss_plat_data *plat_priv)
{
	struct wlfw_rf_board_info *board_info = &plat_priv->board_info;
	struct device *dev = &plat_priv->plat_dev->dev;
	const char *board_id_str;

	switch (plat_priv->device_id) {
	case QCA8074_DEVICE_ID:
	case QCA8074V2_DEVICE_ID:
	case QCA5018_DEVICE_ID:
	case QCA6018_DEVICE_ID:
	case QCA9574_DEVICE_ID:
	case QCA5332_DEVICE_ID:
		board_id_str = "qcom,board_id";
		board_info->num_bytes = 1;
		board_info->board_id_override = (u32)bdf_integrated;
		break;
	case QCN9160_DEVICE_ID:
	case QCN6122_DEVICE_ID:
		board_id_str = "qcom,board_id";
		board_info->num_bytes = 1;
		board_info->board_id_override =
			cnss_get_bdf_mod_param(plat_priv->userpd_id - 1);
		break;
	case QCN9000_DEVICE_ID:
		board_id_str = "board_id";
		board_info->num_bytes = 1;
		board_info->board_id_override =
			cnss_get_bdf_mod_param(plat_priv->pci_slot_id);
		break;
	case QCN9224_DEVICE_ID:
		board_id_str = "board_id";
		board_info->num_bytes = 2;
		board_info->board_id_override =
			cnss_get_bdf_mod_param(plat_priv->pci_slot_id);
		break;
	default:
		cnss_pr_err("No such device id 0x%lx\n", plat_priv->device_id);
		return;
	}

	if (!board_info->board_id_override) {
		if (of_property_read_u32(dev->of_node, board_id_str,
					 &board_info->board_id_override))
			cnss_pr_info("No board_id in device tree for %s\n",
					plat_priv->device_name);
	}

	cnss_pr_dbg("%s: board_id_override 0x%x for device %s\n", __func__,
		    board_info->board_id_override,
		    plat_priv->device_name);
}

int cnss_set_fw_type_and_name(struct cnss_plat_data *plat_priv)
{
	const char *firmware_name = NULL;
	struct device *dev = &plat_priv->plat_dev->dev;
	u32 firmware_name_len;
	u32 board_id = plat_priv->board_info.board_id_override;

	/* AHB devices use PIL binaries, firmware_name is applicable
	 * only for PCI devices.
	 */
	if (plat_priv->bus_type == CNSS_BUS_AHB)
		return 0;

	plat_priv->firmware_type =
		(board_id & CNSS_FW_TYPE_MASK) >> CNSS_FW_TYPE_SHIFT;

	firmware_name_len = strlen(cnss_get_fw_path(plat_priv));
	firmware_name = of_get_property(dev->of_node, "firmware_name", NULL);

	/* If firmware_name not defined in DTS, use default FW name */
	if (!firmware_name) {
		switch (plat_priv->firmware_type) {
		case CNSS_FW_DUAL_MAC:
			firmware_name = DUALMAC_FW_FILE_NAME;
			break;
		case CNSS_FW_DEFAULT:
			/* Fall Through */
		default:
			firmware_name = DEFAULT_FW_FILE_NAME;
		}
	}

	firmware_name_len += strlen(firmware_name);
	if (firmware_name_len > PATH_MAX) {
		cnss_pr_err("firmware_name_len too long %d",
			    firmware_name_len);
		return -EINVAL;
	}

	if (!plat_priv->firmware_name) {
		plat_priv->firmware_name =
				kzalloc(firmware_name_len + 1, GFP_KERNEL);
		if (!plat_priv->firmware_name)
			return -ENOMEM;
	} else {
		memset(plat_priv->firmware_name, 0, firmware_name_len + 1);
	}

	snprintf(plat_priv->firmware_name, firmware_name_len + 1,
		 "%s%s", cnss_get_fw_path(plat_priv), firmware_name);

	return 0;
}

static int cnss_probe(struct platform_device *plat_dev)
{
	int ret = 0;
	struct cnss_plat_data *plat_priv = NULL;
	const struct of_device_id *of_id;
	const struct platform_device_id *device_id;
	u32 node_id = 0, userpd_id = 0, node_id_base;

#ifdef CONFIG_CNSS2_PCI_NODT
	u32 pci_bus;
#endif
	unsigned long flags;
#ifdef CONFIG_CNSS2_KERNEL_IPQ
	const int *soc_version;
#endif
	if (cnss_get_plat_priv(plat_dev)) {
		pr_err("Driver is already initialized!\n");
		ret = -EEXIST;
		goto out;
	}
	of_id = of_match_device(cnss_of_match_table, &plat_dev->dev);
	if (!of_id || !of_id->data) {
		pr_err("Failed to find of match device!\n");
		ret = -ENODEV;
		goto out;
	}

	device_id = (const struct platform_device_id *)of_id->data;

	if ((device_id->driver_data == QCN6122_DEVICE_ID ||
	     device_id->driver_data == QCN9160_DEVICE_ID) &&
	    (platform_get_userpd_id(plat_dev, &userpd_id))) {
		pr_err("Error: No userpd_id in device_tree\n");
		CNSS_ASSERT(0);
		ret = -ENODEV;
		goto out;
	}

	if ((device_id->driver_data == QCN9000_DEVICE_ID ||
	     device_id->driver_data == QCN9224_DEVICE_ID) &&
	    (of_property_read_u32(plat_dev->dev.of_node,
				  "qrtr_node_id", &node_id))) {
		pr_err("Error: No qrtr_node_id in device_tree\n");
		CNSS_ASSERT(0);
		ret = -ENODEV;
		goto out;
	}

#ifdef CONFIG_CNSS2_KERNEL_IPQ
	/* Check for QCA9574 here and skip probe accordingly */
	if (device_id->driver_data == QCA9574_DEVICE_ID &&
	    !cpu_is_internal_wifi_enabled()) {
		pr_err("Skipping probe for device 0x%lx\n",
		       device_id->driver_data);
		ret = -ENODEV;
		goto out;
	}
#endif
	if (cnss_check_skip_target_probe(device_id, userpd_id, node_id))
		goto out;

#ifdef CONFIG_CNSS_QCN9000
	if (device_id->driver_data == QCA6174_DEVICE_ID) {
		pr_err("No probe for QCA6174\n");
		ret = -EINVAL;
		goto out;
	}
#else
	if (device_id->driver_data == QCN9000_DEVICE_ID) {
		pr_err("No probe for QCN9000\n");
		ret = -EINVAL;
		goto out;
	}
#endif

#ifdef CONFIG_CNSS2_KERNEL_IPQ
	soc_version = of_get_property(of_find_node_by_path("/"),
				      "soc_version_major", NULL);
	if (!soc_version) {
		pr_err("Failed to get soc_version_major from device tree");
		CNSS_ASSERT(0);
		ret = -EINVAL;
		goto out;
	}

	soc_version_major = *soc_version;
	soc_version_major = le32_to_cpu(soc_version_major);

	if (device_id->driver_data == QCA8074_DEVICE_ID) {
		if (soc_version_major == 2) {
			pr_err("Skip QCA8074V1 in V2 platform\n");
			ret = -ENODEV;
			goto out;
		}
	}

	if (device_id->driver_data == QCA8074V2_DEVICE_ID) {
		if (soc_version_major == 1) {
			pr_err("Skip QCA8074V2 in V1 platform\n");
			ret = -ENODEV;
			goto out;
		}
	}
#endif
	if (plat_env_index >= MAX_NUMBER_OF_SOCS) {
		pr_err("cnss: plat_env_index %d greater than MAX_NUMBER_OF_SOCS\n",
			plat_env_index);
		ret = -ENOMEM;
		goto out;
	}

	plat_priv = devm_kzalloc(&plat_dev->dev, sizeof(*plat_priv),
				 GFP_KERNEL);
	if (!plat_priv) {
		ret = -ENOMEM;
		goto out;
	}

	plat_priv->plat_dev = plat_dev;
	plat_priv->device_id = device_id->driver_data;
	plat_priv->plat_dev_id = (struct platform_device_id *)device_id;
	plat_priv->service_id = WLFW_SERVICE_ID_V01;


#ifdef CONFIG_CNSS2_PCI_NODT
	if (of_property_read_u32(plat_dev->dev.of_node, "avm,pci_bus", &pci_bus)) {
		pr_warn("cnss: Failed to read pci bus number\n");
	} else {
		plat_priv->pci_bus_number = (uint8_t)pci_bus;
	}
	if (of_property_read_u32(plat_dev->dev.of_node, "avm,pci_devfn", &plat_priv->pci_devfn)) {
		pr_warn("cnss: Failed to read pci device function\n");
	}
	plat_priv->dma_alloc_supported = true;
#endif

#ifdef CONFIG_CNSS2_DMA_ALLOC
	plat_priv->dma_alloc_supported = true;
#endif

	switch (plat_priv->device_id) {
	case QCN9224_DEVICE_ID:
		plat_priv->mlo_support = !!enable_mlo_support;
		/* Fall Through */
	case QCN9000_DEVICE_ID:
		plat_priv->bus_type = CNSS_BUS_PCI;
		plat_priv->bdf_dnld_method = WLFW_SEND_BDF_OVER_QMI_V01;
		plat_priv->qrtr_node_id = node_id;
		plat_priv->wlfw_service_instance_id = node_id + FW_ID_BASE;

		if (plat_priv->device_id == QCN9224_DEVICE_ID)
			node_id_base = QCN9224_NODE_ID_BASE;
		else
			node_id_base = QCN9000_NODE_ID_BASE;

		plat_priv->pci_slot_id = plat_priv->wlfw_service_instance_id -
						node_id_base;
		break;
	case QCA5332_DEVICE_ID:
		plat_priv->mlo_support = !!enable_mlo_support;
		/* Fall Through */
	case QCA8074_DEVICE_ID:
	case QCA8074V2_DEVICE_ID:
	case QCA5018_DEVICE_ID:
	case QCA6018_DEVICE_ID:
	case QCA9574_DEVICE_ID:
		plat_priv->bus_type = CNSS_BUS_AHB;
		plat_priv->bdf_dnld_method = WLFW_DIRECT_BDF_COPY_V01;
		plat_priv->wlfw_service_instance_id =
			WLFW_SERVICE_INS_ID_V01_QCA8074;
		break;
	case QCN6122_DEVICE_ID:
	case QCN9160_DEVICE_ID:
		plat_priv->bus_type = CNSS_BUS_AHB;
		plat_priv->bdf_dnld_method = WLFW_DIRECT_BDF_COPY_V01;
		plat_priv->userpd_id = userpd_id;
		if (plat_priv->device_id == QCN9160_DEVICE_ID)
			plat_priv->wlfw_service_instance_id =
				WLFW_SERVICE_INS_ID_V01_QCN9160 + userpd_id;
		else if (plat_priv->device_id == QCN6122_DEVICE_ID)
			plat_priv->wlfw_service_instance_id =
				WLFW_SERVICE_INS_ID_V01_QCN6122 + userpd_id;

		if (userpd_id == USERPD_0)
			plat_priv->board_info.board_id_override = bdf_pci0;
		else if (userpd_id == USERPD_1)
			plat_priv->board_info.board_id_override = bdf_pci1;

#ifdef CONFIG_CNSS2_QGIC2M
		plat_priv->tgt_data.qgic2_msi =
			cnss_qgic2_enable_msi(plat_priv);
		if (!plat_priv->tgt_data.qgic2_msi) {
			cnss_pr_err("qgic2_msi fails: dev 0x%lx userpd id %d\n",
				    plat_priv->device_id, userpd_id);
			ret = -ENODEV;
			goto out;
		}
#endif
		break;
	default:
		cnss_pr_err("No such device id %p\n", device_id);
		return -ENODEV;
	}

	ret = cnss_set_device_name(plat_priv);
	if (ret)
		return -ENODEV;

	cnss_set_board_id(plat_priv);

	ret = cnss_set_fw_type_and_name(plat_priv);
	if (ret)
		return -ENODEV;
	cnss_set_ssr_recovery_type(plat_priv);

#ifdef CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK
	ret = cnss_rproc_register(plat_priv);
	if (ret)
		goto out;
#endif

	/*
	 * plat_env is initialized only at the end of cnss_probe.
	 * plat_env should not be referred to in any functions within cnss_probe
	 * till it is initialized.
	 */
	cnss_fill_probe_order(plat_priv);

#ifdef CONFIG_CNSS2_LEGACY_IRQ
	cnss_get_legacy_intx_support(plat_priv);
#endif
	cnss_set_mod_param_feature_support(plat_priv, CALDATA);
	cnss_set_mod_param_feature_support(plat_priv, REGDB);
	platform_set_drvdata(plat_dev, plat_priv);
	memset(&qmi_log, 0, sizeof(struct qmi_history) * QMI_HISTORY_SIZE);
	INIT_LIST_HEAD(&plat_priv->vreg_list);
	INIT_LIST_HEAD(&plat_priv->clk_list);

	cnss_init_control_params(plat_priv);

	ret = cnss_get_resources(plat_priv);
	if (ret)
		goto reset_ctx;

	if (!test_bit(SKIP_DEVICE_BOOT, &plat_priv->ctrl_params.quirks)) {
		ret = cnss_power_on_device(plat_priv, plat_priv->device_id);
		if (ret)
			goto free_res;
	}

#ifdef CONFIG_CNSS2_PM
	ret = cnss_register_esoc(plat_priv);
	if (ret)
		goto deinit_bus;

	ret = cnss_register_bus_scale(plat_priv);
	if (ret)
		goto unreg_esoc;
#endif

	ret = cnss_create_sysfs(plat_priv);
	if (ret)
		goto unreg_bus_scale;

	ret = cnss_event_work_init(plat_priv);
	if (ret)
		goto remove_sysfs;

	ret = cnss_qmi_init(plat_priv);
	if (ret)
		goto deinit_event_work;

	ret = cnss_debugfs_create(plat_priv);
	if (ret)
		goto deinit_qmi;

	ret = cnss_misc_init(plat_priv);
	if (ret)
		goto destroy_debugfs;
#if defined(CNSS2_COEX) || defined(CNSS2_IMS)
	cnss_register_coex_service(plat_priv);
	cnss_register_ims_service(plat_priv);
#endif

	ret = cnss_genl_init();
	if (ret < 0)
		cnss_pr_err("CNSS genl init failed %d\n", ret);

	ret = cnss_init_m3_dump_class(plat_priv);
	if (ret)
		goto deinit_genl;

	ret = cnss_recovery_work_init(plat_priv);
	if (ret)
		goto deinit_genl;
	cnss_cal_work_init(plat_priv);
#ifdef CONFIG_CNSS2_KERNEL_5_15
	cnss_crash_work_init(plat_priv);
#endif

	/* Incrementing plat_env_index only after the probe for the device
	 * is completed
	 */
	spin_lock_irqsave(&plat_env_spinlock, flags);
	plat_env[plat_env_index++] = plat_priv;
	spin_unlock_irqrestore(&plat_env_spinlock, flags);
	cnss_pr_info("Platform driver probed successfully. plat 0x%pK tgt 0x%lx\n",
		     plat_priv, plat_priv->device_id);

	return 0;

deinit_genl:
	cnss_genl_exit();
destroy_debugfs:
	cnss_debugfs_destroy(plat_priv);
deinit_qmi:
	cnss_qmi_deinit(plat_priv);
deinit_event_work:
	cnss_event_work_deinit(plat_priv);
remove_sysfs:
	cnss_remove_sysfs(plat_priv);
unreg_bus_scale:
#ifdef CONFIG_CNSS2_PM
	cnss_unregister_bus_scale(plat_priv);
unreg_esoc:
	cnss_unregister_esoc(plat_priv);
deinit_bus:
#endif
	if (!test_bit(SKIP_DEVICE_BOOT, &plat_priv->ctrl_params.quirks))
		cnss_bus_deinit(plat_priv);
	if (!test_bit(SKIP_DEVICE_BOOT, &plat_priv->ctrl_params.quirks))
		cnss_power_off_device(plat_priv, plat_priv->device_id);
free_res:
	cnss_put_resources(plat_priv);
reset_ctx:
	platform_set_drvdata(plat_dev, NULL);
	plat_env[plat_env_index] = NULL;
#ifdef CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK
	cnss_rproc_unregister(plat_priv);
#endif
out:
	return ret;
}

static int cnss_remove(struct platform_device *plat_dev)
{
	int i = 0;
	unsigned long flags = 0;
	struct cnss_plat_data *plat_priv = platform_get_drvdata(plat_dev);

	/* For platforms that support dma_alloc, FW memory is allocated during
	 * first wifi load and not freed during wifi down, so we are freeing
	 * here during rmmod of cnss2
	 */
	if (plat_priv->dma_alloc_supported) {
		cnss_bus_free_fw_mem(plat_priv);
		cnss_bus_free_qdss_mem(plat_priv);
	}
	cnss_deinit_m3_dump_class();
#if defined(CONFIG_CNSS2_KERNEL_MSM) || defined(CONFIG_CNSS2_KERNEL_5_15)
	/*
	 * ramdump_device allocated during pci_probe is not freed during
	 * pci_remove. So we are freeing in cnss2 rmmod.
	 */
	if (plat_priv->rd_dev_present)
		cnss_unregister_ramdump(plat_priv);
#endif

#ifdef CONFIG_CNSS2_QGIC2M
	cnss_qgic2_disable_msi(plat_priv);
#endif
	cnss_genl_exit();
#if defined(CNSS2_COEX) || defined(CNSS2_IMS)
	cnss_unregister_ims_service(plat_priv);
	cnss_unregister_coex_service(plat_priv);
#endif
	cnss_misc_deinit(plat_priv);
	cnss_debugfs_destroy(plat_priv);
	cnss_qmi_deinit(plat_priv);
	cnss_event_work_deinit(plat_priv);
	cnss_recovery_work_deinit(plat_priv);
	cnss_remove_sysfs(plat_priv);
#ifdef CONFIG_CNSS2_PM
	cnss_unregister_bus_scale(plat_priv);
	cnss_unregister_esoc(plat_priv);
#endif
	cnss_bus_deinit(plat_priv);
	cnss_put_resources(plat_priv);

#ifdef CONFIG_CNSS2_KERNEL_RPROC_FRAMEWORK
	cnss_rproc_unregister(plat_priv);
#endif
	platform_set_drvdata(plat_dev, NULL);
	kfree(plat_priv->firmware_name);

	for (i = 0; i < MAX_NUMBER_OF_SOCS; i++) {
		if (plat_env[i] == plat_priv) {
			spin_lock_irqsave(&plat_env_spinlock, flags);
			plat_env[i] = NULL;
			plat_env_index--;
			spin_unlock_irqrestore(&plat_env_spinlock, flags);
			break;
		}
	}
	return 0;
}

static struct platform_driver cnss_platform_driver = {
	.probe  = cnss_probe,
	.remove = cnss_remove,
	.driver = {
		.name = "cnss2",
		.of_match_table = cnss_of_match_table,
		.probe_type = PROBE_FORCE_SYNCHRONOUS,
	},
};

static void cnss_init_ipc_qmi_cb(struct cnss_plat_ipc_qmi_cb *ipc_qmi_cb)
{
	ipc_qmi_cb->connection_update_cb = cnss_daemon_connection_update_cb;
	ipc_qmi_cb->config_param_cb = cnss_config_param_update_cb;
}

static int __init cnss_initialize(void)
{
	int ret = 0;
	struct cnss_plat_ipc_qmi_cb ipc_qmi_callbacks;

	cnss_debug_init();
	ret = platform_driver_register(&cnss_platform_driver);
	if (ret) {
		cnss_debug_deinit();
		return ret;
	}

#ifdef CONFIG_CNSS2_LEGACY_IRQ
	ret = cnss_legacy_irq_init();
	if (ret) {
		platform_driver_unregister(&cnss_platform_driver);
		cnss_debug_deinit();
		return ret;
	}
#endif

	cnss_bus_init_by_type(CNSS_BUS_PCI);
	cnss_plat_ipc_qmi_svc_init();
	cnss_init_ipc_qmi_cb(&ipc_qmi_callbacks);
	cnss_plat_ipc_register(CNSS_PLAT_IPC_DAEMON_QMI_CLIENT_V01,
			       &ipc_qmi_callbacks, NULL);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0))
	cnss_panic_notifier_register();
#endif

	return ret;
}

static void __exit cnss_exit(void)
{
	cnss_plat_ipc_unregister(CNSS_PLAT_IPC_DAEMON_QMI_CLIENT_V01, NULL);
	cnss_plat_ipc_qmi_svc_exit();

#ifdef CONFIG_CNSS2_LEGACY_IRQ
	cnss_legacy_irq_deinit();
#endif
	platform_driver_unregister(&cnss_platform_driver);
	cnss_debug_deinit();
}

module_init(cnss_initialize);
module_exit(cnss_exit);

MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("CNSS2 Platform Driver");