From 3983bc45032260eb80e410a84e69687d09e0aa1e Mon Sep 17 00:00:00 2001 From: Naman Padhiar Date: Mon, 14 Feb 2022 13:58:53 +0530 Subject: [PATCH] wlan_platform: Bring initial files for ICNSS family drivers Bring ICNSS family drivers from msm-5.10 kernel as of commit 'cb298739ee51 ("icnss2: Download bdf file for helium targets")' to WLAN platform project. Updated wlan_firmware_service files and makefiles to support ICNSS2 compilation. Change-Id: I02cf792b61772f07ac0607be7bb6b3bfda7815e4 --- Android.mk | 9 + Kbuild | 9 + Makefile | 3 + cnss_utils/wlan_firmware_service_v01.c | 219 ++ cnss_utils/wlan_firmware_service_v01.h | 59 +- icnss2/Kconfig | 38 + icnss2/Makefile | 16 + icnss2/debug.c | 891 +++++ icnss2/debug.h | 108 + icnss2/genl.c | 213 ++ icnss2/genl.h | 17 + icnss2/main.c | 4559 ++++++++++++++++++++++++ icnss2/main.h | 512 +++ icnss2/power.c | 942 +++++ icnss2/power.h | 19 + icnss2/qmi.c | 3469 ++++++++++++++++++ icnss2/qmi.h | 264 ++ inc/icnss2.h | 207 ++ 18 files changed, 11552 insertions(+), 2 deletions(-) create mode 100644 icnss2/Kconfig create mode 100644 icnss2/Makefile create mode 100644 icnss2/debug.c create mode 100644 icnss2/debug.h create mode 100644 icnss2/genl.c create mode 100644 icnss2/genl.h create mode 100644 icnss2/main.c create mode 100644 icnss2/main.h create mode 100644 icnss2/power.c create mode 100644 icnss2/power.h create mode 100644 icnss2/qmi.c create mode 100644 icnss2/qmi.h create mode 100644 inc/icnss2.h diff --git a/Android.mk b/Android.mk index eee4c069d4..9e0839dc5d 100644 --- a/Android.mk +++ b/Android.mk @@ -93,3 +93,12 @@ LOCAL_MODULE_TAGS := optional LOCAL_MODULE_DEBUG_ENABLE := true LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) include $(DLKM_DIR)/Build_external_kernelmodule.mk +################################ icnss2 ################################ +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(CNSS_SRC_FILES) +LOCAL_MODULE := icnss2.ko +LOCAL_MODULE_KBUILD_NAME := icnss2/icnss2.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_DEBUG_ENABLE := true +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/Build_external_kernelmodule.mk diff --git a/Kbuild b/Kbuild index 515c2956a1..399b921e0d 100644 --- a/Kbuild +++ b/Kbuild @@ -10,6 +10,14 @@ ifeq ($(CONFIG_CNSS2_QMI),y) KBUILD_CPPFLAGS += -DCONFIG_CNSS2_QMI endif +ifeq ($(CONFIG_ICNSS2_DEBUG),y) +KBUILD_CPPFLAGS += -DCONFIG_ICNSS2_DEBUG +endif + +ifeq ($(CONFIG_ICNSS2_QMI),y) +KBUILD_CPPFLAGS += -DCONFIG_ICNSS2_QMI +endif + # CONFIG_CNSS_PLAT_IPC_QMI_SVC should never be "y" here since it # can be only compiled as a module from out-of-kernel-tree source. ifeq ($(CONFIG_CNSS_PLAT_IPC_QMI_SVC),m) @@ -17,6 +25,7 @@ KBUILD_CPPFLAGS += -DCONFIG_CNSS_PLAT_IPC_QMI_SVC endif obj-$(CONFIG_CNSS2) += cnss2/ +obj-$(CONFIG_ICNSS2) += icnss2/ obj-$(CONFIG_CNSS_GENL) += cnss_genl/ obj-$(CONFIG_WCNSS_MEM_PRE_ALLOC) += cnss_prealloc/ obj-y += cnss_utils/ diff --git a/Makefile b/Makefile index 6d709f918d..d97d1a009c 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,11 @@ WLAN_PLATFORM_ROOT = $(shell pwd) KBUILD_OPTIONS := WLAN_PLATFORM_ROOT=$(WLAN_PLATFORM_ROOT) KBUILD_OPTIONS += CONFIG_CNSS_OUT_OF_TREE=y KBUILD_OPTIONS += CONFIG_CNSS2=m +KBUILD_OPTIONS += CONFIG_ICNSS2=m KBUILD_OPTIONS += CONFIG_CNSS2_QMI=y +KBUILD_OPTIONS += CONFIG_ICNSS2_QMI=y KBUILD_OPTIONS += CONFIG_CNSS2_DEBUG=y +KBUILD_OPTIONS += CONFIG_ICNSS2_DEBUG=y KBUILD_OPTIONS += CONFIG_CNSS_QMI_SVC=m KBUILD_OPTIONS += CONFIG_CNSS_PLAT_IPC_QMI_SVC=m KBUILD_OPTIONS += CONFIG_CNSS_GENL=m diff --git a/cnss_utils/wlan_firmware_service_v01.c b/cnss_utils/wlan_firmware_service_v01.c index 93a6d59eec..e37c6eaea1 100644 --- a/cnss_utils/wlan_firmware_service_v01.c +++ b/cnss_utils/wlan_firmware_service_v01.c @@ -1766,6 +1766,106 @@ struct qmi_elem_info wlfw_cap_resp_msg_v01_ei[] = { dev_mem_info), .ei_array = wlfw_dev_mem_info_s_v01_ei, }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1D, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + foundry_name_valid), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_WLFW_MAX_STR_LEN_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x1D, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + foundry_name), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1E, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + hang_data_addr_offset_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x1E, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + hang_data_addr_offset), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1F, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + hang_data_length_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .array_type = NO_ARRAY, + .tlv_type = 0x1F, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + hang_data_length), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x20, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + bdf_dnld_method_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_bdf_dnld_method_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x20, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + bdf_dnld_method), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x21, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + hwid_bitmap_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x21, + .offset = offsetof(struct + wlfw_cap_resp_msg_v01, + hwid_bitmap), + }, { .data_type = QMI_EOTI, .array_type = NO_ARRAY, @@ -3475,6 +3575,76 @@ struct qmi_elem_info wlfw_host_cap_req_msg_v01_ei[] = { wlfw_host_cap_req_msg_v01, wake_msi_addr), }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x2B, + .offset = offsetof(struct + wlfw_host_cap_req_msg_v01, + wlan_enable_delay_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x2B, + .offset = offsetof(struct + wlfw_host_cap_req_msg_v01, + wlan_enable_delay), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x2C, + .offset = offsetof(struct + wlfw_host_cap_req_msg_v01, + ddr_type_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x2C, + .offset = offsetof(struct + wlfw_host_cap_req_msg_v01, + ddr_type), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x2D, + .offset = offsetof(struct + wlfw_host_cap_req_msg_v01, + gpio_info_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x2D, + .offset = offsetof(struct + wlfw_host_cap_req_msg_v01, + gpio_info_len), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = QMI_WLFW_MAX_NUM_GPIO_INFO_V01, + .elem_size = sizeof(u32), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x2D, + .offset = offsetof(struct + wlfw_host_cap_req_msg_v01, + gpio_info), + }, { .data_type = QMI_EOTI, .array_type = NO_ARRAY, @@ -5458,6 +5628,55 @@ struct qmi_elem_info wlfw_m3_dump_upload_segments_req_ind_msg_v01_ei[] = { }; EXPORT_SYMBOL(wlfw_m3_dump_upload_segments_req_ind_msg_v01_ei); +struct qmi_elem_info wlfw_subsys_restart_level_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_subsys_restart_level_req_msg_v01, + restart_level_type_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_subsys_restart_level_req_msg_v01, + restart_level_type), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; +EXPORT_SYMBOL(wlfw_subsys_restart_level_req_msg_v01_ei); + +struct qmi_elem_info wlfw_subsys_restart_level_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + wlfw_subsys_restart_level_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; +EXPORT_SYMBOL(wlfw_subsys_restart_level_resp_msg_v01_ei); + /** * wlfw_is_valid_dt_node_found - Check if valid device tree node present * diff --git a/cnss_utils/wlan_firmware_service_v01.h b/cnss_utils/wlan_firmware_service_v01.h index 458f4726e9..9653d947e1 100644 --- a/cnss_utils/wlan_firmware_service_v01.h +++ b/cnss_utils/wlan_firmware_service_v01.h @@ -9,6 +9,8 @@ #define WLFW_SERVICE_ID_V01 0x45 #define WLFW_SERVICE_VERS_V01 0x01 +#define QMI_WLFW_SUBSYS_RESTART_LEVEL_RESP_V01 0x0055 +#define QMI_WLFW_SUBSYS_RESTART_LEVEL_REQ_V01 0x0055 #define QMI_WLFW_POWER_SAVE_RESP_V01 0x0050 #define QMI_WLFW_CAP_REQ_V01 0x0024 #define QMI_WLFW_CAL_REPORT_REQ_V01 0x0026 @@ -107,6 +109,7 @@ #define QMI_WLFW_MAX_NUM_SVC_V01 24 #define QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01 2 #define QMI_WLFW_MAC_ADDR_SIZE_V01 6 +#define QMI_WLFW_MAX_NUM_GPIO_INFO_V01 20 #define QMI_WLFW_MAX_NUM_MEM_CFG_V01 2 #define QMI_WLFW_MAX_NUM_MEM_SEG_V01 52 #define QMI_WLFW_MAX_WFC_CALL_STATUS_DATA_SIZE_V01 256 @@ -166,6 +169,7 @@ enum wlfw_mem_type_enum_v01 { QMI_WLFW_MEM_HANG_DATA_V01 = 7, QMI_WLFW_MLO_GLOBAL_MEM_V01 = 8, QMI_WLFW_PAGEABLE_MEM_V01 = 9, + QMI_WLFW_AFC_MEM_V01 = 10, WLFW_MEM_TYPE_ENUM_MAX_VAL_V01 = INT_MAX, }; @@ -248,10 +252,28 @@ enum cnss_feature_v01 { CNSS_FEATURE_MIN_VAL_V01 = INT_MIN, BOOTSTRAP_CLOCK_SELECT_V01 = 0, CNSS_DRV_SUPPORT_V01 = 1, + CNSS_WLAN_EN_SUPPORT_V01 = 2, CNSS_MAX_FEATURE_V01 = 64, CNSS_FEATURE_MAX_VAL_V01 = INT_MAX, }; +enum wlfw_bdf_dnld_method_v01 { + WLFW_BDF_DNLD_METHOD_MIN_VAL_V01 = INT_MIN, + WLFW_DIRECT_BDF_COPY_V01 = 0, + WLFW_SEND_BDF_OVER_QMI_V01 = 1, + WLFW_BDF_DNLD_METHOD_MAX_VAL_V01 = INT_MAX, +}; + +enum wlfw_gpio_info_type_v01 { + WLFW_GPIO_INFO_TYPE_MIN_VAL_V01 = INT_MIN, + WLAN_EN_GPIO_V01 = 0, + BT_EN_GPIO_V01 = 1, + HOST_SOL_GPIO_V01 = 2, + TARGET_SOL_GPIO_V01 = 3, + GPIO_TYPE_MAX_V01 = 4, + WLFW_GPIO_INFO_TYPE_MAX_VAL_V01 = INT_MAX, +}; + #define QMI_WLFW_CE_ATTR_FLAGS_V01 ((u32)0x00) #define QMI_WLFW_CE_ATTR_NO_SNOOP_V01 ((u32)0x01) #define QMI_WLFW_CE_ATTR_BYTE_SWAP_DATA_V01 ((u32)0x02) @@ -530,8 +552,19 @@ struct wlfw_cap_resp_msg_v01 { enum wlfw_rd_card_chain_cap_v01 rd_card_chain_cap; u8 dev_mem_info_valid; struct wlfw_dev_mem_info_s_v01 dev_mem_info[QMI_WLFW_MAX_DEV_MEM_NUM_V01]; + u8 foundry_name_valid; + char foundry_name[QMI_WLFW_MAX_STR_LEN_V01 + 1]; + u8 hang_data_addr_offset_valid; + u32 hang_data_addr_offset; + u8 hang_data_length_valid; + u16 hang_data_length; + u8 bdf_dnld_method_valid; + enum wlfw_bdf_dnld_method_v01 bdf_dnld_method; + u8 hwid_bitmap_valid; + u8 hwid_bitmap; }; -#define WLFW_CAP_RESP_MSG_V01_MAX_MSG_LEN 320 + +#define WLFW_CAP_RESP_MSG_V01_MAX_MSG_LEN 362 extern struct qmi_elem_info wlfw_cap_resp_msg_v01_ei[]; struct wlfw_bdf_download_req_msg_v01 { @@ -803,9 +836,16 @@ struct wlfw_host_cap_req_msg_v01 { u8 num_wlan_vaps; u8 wake_msi_addr_valid; u32 wake_msi_addr; + u8 wlan_enable_delay_valid; + u32 wlan_enable_delay; + u8 ddr_type_valid; + u32 ddr_type; + u8 gpio_info_valid; + u32 gpio_info_len; + u32 gpio_info[QMI_WLFW_MAX_NUM_GPIO_INFO_V01]; }; -#define WLFW_HOST_CAP_REQ_MSG_V01_MAX_MSG_LEN 389 +#define WLFW_HOST_CAP_REQ_MSG_V01_MAX_MSG_LEN 487 extern struct qmi_elem_info wlfw_host_cap_req_msg_v01_ei[]; struct wlfw_host_cap_resp_msg_v01 { @@ -1268,4 +1308,19 @@ struct wlfw_m3_dump_upload_segments_req_ind_msg_v01 { #define WLFW_M3_DUMP_UPLOAD_SEGMENTS_REQ_IND_MSG_V01_MAX_MSG_LEN 387 extern struct qmi_elem_info wlfw_m3_dump_upload_segments_req_ind_msg_v01_ei[]; +struct wlfw_subsys_restart_level_req_msg_v01 { + u8 restart_level_type_valid; + u8 restart_level_type; +}; + +#define WLFW_SUBSYS_RESTART_LEVEL_REQ_MSG_V01_MAX_MSG_LEN 4 +extern struct qmi_elem_info wlfw_subsys_restart_level_req_msg_v01_ei[]; + +struct wlfw_subsys_restart_level_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_SUBSYS_RESTART_LEVEL_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct qmi_elem_info wlfw_subsys_restart_level_resp_msg_v01_ei[]; + #endif diff --git a/icnss2/Kconfig b/icnss2/Kconfig new file mode 100644 index 0000000000..829db44f2c --- /dev/null +++ b/icnss2/Kconfig @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0-only +config ICNSS2 + tristate "Platform driver for Wi-Fi Module module" + select CNSS_UTILS + help + This module adds support for Q6 integrated WLAN connectivity + subsystem with iWCN architecture. This module is responsible for + communicating WLAN on/off control messages to FW over QMI channel. + It is also responsible for handling WLAN PD restart notifications. + +config ICNSS2_DEBUG + bool "ICNSS2 Platform Driver Debug Support" + depends on ICNSS2 + help + Say 'Y' here to enable ICNSS driver debug support. Debug support + primarily consists of logs consisting of information related to + hardware register access and enabling BUG_ON for certain cases to aid + the debugging. + +config ICNSS2_QMI + bool "ICNSS2 Platform Driver QMI support" + depends on ICNSS2 + select CNSS_QMI_SVC + help + Say 'Y' here to enable ICNSS QMI support. ICNSS driver will use + QMI framework to communicate with WLAN FW. It will send coldboot + handshake messages to WLAN FW, which includes hardware capabilities + and configurations. It also send WLAN on/off control message to FW + over QMI channel. + +config CNSS_QCA6750 + bool "Enable ICNSS QCA6750 chipset specific changes" + depends on ICNSS2 + help + This enables the changes from WLAN host driver that are specific to + CNSS QCA6750 chipset. + These changes are needed to support the new hardware architecture + for CNSS QCA6750 chipset. diff --git a/icnss2/Makefile b/icnss2/Makefile new file mode 100644 index 0000000000..bc4c293c77 --- /dev/null +++ b/icnss2/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only + +ifeq ($(CONFIG_CNSS_OUT_OF_TREE),y) +ccflags-y += -I$(WLAN_PLATFORM_ROOT)/cnss_utils +ccflags-y += -I$(WLAN_PLATFORM_ROOT)/inc +else +ccflags-y += -I$(srctree)/drivers/net/wireless/cnss_utils/ +endif + +obj-$(CONFIG_ICNSS2) += icnss2.o + +icnss2-y := main.o +icnss2-y += debug.o +icnss2-y += power.o +icnss2-y += genl.o +icnss2-$(CONFIG_ICNSS2_QMI) += qmi.o diff --git a/icnss2/debug.c b/icnss2/debug.c new file mode 100644 index 0000000000..e12cee7dfc --- /dev/null +++ b/icnss2/debug.c @@ -0,0 +1,891 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + */ +#include +#include +#include +#include +#include +#include "main.h" +#include "debug.h" +#include "qmi.h" +#include "power.h" + +void *icnss_ipc_log_context; +void *icnss_ipc_log_long_context; +void *icnss_ipc_log_smp2p_context; +void *icnss_ipc_soc_wake_context; + +static ssize_t icnss_regwrite_write(struct file *fp, + const char __user *user_buf, + size_t count, loff_t *off) +{ + struct icnss_priv *priv = + ((struct seq_file *)fp->private_data)->private; + char buf[64]; + char *sptr, *token; + unsigned int len = 0; + uint32_t reg_offset, mem_type, reg_val; + const char *delim = " "; + int ret = 0; + + if (!test_bit(ICNSS_FW_READY, &priv->state) || + !test_bit(ICNSS_POWER_ON, &priv->state)) + return -EINVAL; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, &mem_type)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, ®_offset)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (kstrtou32(token, 0, ®_val)) + return -EINVAL; + + ret = wlfw_athdiag_write_send_sync_msg(priv, reg_offset, mem_type, + sizeof(uint32_t), + (uint8_t *)®_val); + if (ret) + return ret; + + return count; +} + +static int icnss_regwrite_show(struct seq_file *s, void *data) +{ + struct icnss_priv *priv = s->private; + + seq_puts(s, "Usage: echo > /icnss/reg_write\n"); + + if (!test_bit(ICNSS_FW_READY, &priv->state)) + seq_puts(s, "Firmware is not ready yet!, wait for FW READY\n"); + + return 0; +} + +static int icnss_regwrite_open(struct inode *inode, struct file *file) +{ + return single_open(file, icnss_regwrite_show, inode->i_private); +} + +static const struct file_operations icnss_regwrite_fops = { + .read = seq_read, + .write = icnss_regwrite_write, + .open = icnss_regwrite_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static int icnss_regread_show(struct seq_file *s, void *data) +{ + struct icnss_priv *priv = s->private; + + mutex_lock(&priv->dev_lock); + if (!priv->diag_reg_read_buf) { + seq_puts(s, "Usage: echo > /icnss/reg_read\n"); + + if (!test_bit(ICNSS_FW_READY, &priv->state)) + seq_puts(s, "Firmware is not ready yet!, wait for FW READY\n"); + + mutex_unlock(&priv->dev_lock); + return 0; + } + + seq_printf(s, "REGREAD: Addr 0x%x Type 0x%x Length 0x%x\n", + priv->diag_reg_read_addr, priv->diag_reg_read_mem_type, + priv->diag_reg_read_len); + + seq_hex_dump(s, "", DUMP_PREFIX_OFFSET, 32, 4, priv->diag_reg_read_buf, + priv->diag_reg_read_len, false); + + priv->diag_reg_read_len = 0; + kfree(priv->diag_reg_read_buf); + priv->diag_reg_read_buf = NULL; + mutex_unlock(&priv->dev_lock); + + return 0; +} + +static int icnss_regread_open(struct inode *inode, struct file *file) +{ + return single_open(file, icnss_regread_show, inode->i_private); +} + +static ssize_t icnss_reg_parse(const char __user *user_buf, size_t count, + struct icnss_reg_info *reg_info_ptr) +{ + char buf[64] = {0}; + char *sptr = NULL, *token = NULL; + const char *delim = " "; + unsigned int len = 0; + + if (user_buf == NULL) + return -EFAULT; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, ®_info_ptr->mem_type)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, ®_info_ptr->reg_offset)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (kstrtou32(token, 0, ®_info_ptr->data_len)) + return -EINVAL; + + if (reg_info_ptr->data_len == 0 || + reg_info_ptr->data_len > WLFW_MAX_DATA_SIZE) + return -EINVAL; + + return 0; +} + +static ssize_t icnss_regread_write(struct file *fp, const char __user *user_buf, + size_t count, loff_t *off) +{ + struct icnss_priv *priv = + ((struct seq_file *)fp->private_data)->private; + uint8_t *reg_buf = NULL; + int ret = 0; + struct icnss_reg_info reg_info; + + if (!test_bit(ICNSS_FW_READY, &priv->state) || + !test_bit(ICNSS_POWER_ON, &priv->state)) + return -EINVAL; + + ret = icnss_reg_parse(user_buf, count, ®_info); + if (ret) + return ret; + + mutex_lock(&priv->dev_lock); + kfree(priv->diag_reg_read_buf); + priv->diag_reg_read_buf = NULL; + + reg_buf = kzalloc(reg_info.data_len, GFP_KERNEL); + if (!reg_buf) { + mutex_unlock(&priv->dev_lock); + return -ENOMEM; + } + + ret = wlfw_athdiag_read_send_sync_msg(priv, reg_info.reg_offset, + reg_info.mem_type, + reg_info.data_len, + reg_buf); + if (ret) { + kfree(reg_buf); + mutex_unlock(&priv->dev_lock); + return ret; + } + + priv->diag_reg_read_addr = reg_info.reg_offset; + priv->diag_reg_read_mem_type = reg_info.mem_type; + priv->diag_reg_read_len = reg_info.data_len; + priv->diag_reg_read_buf = reg_buf; + mutex_unlock(&priv->dev_lock); + + return count; +} + +static const struct file_operations icnss_regread_fops = { + .read = seq_read, + .write = icnss_regread_write, + .open = icnss_regread_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static ssize_t icnss_stats_write(struct file *fp, const char __user *buf, + size_t count, loff_t *off) +{ + struct icnss_priv *priv = + ((struct seq_file *)fp->private_data)->private; + int ret; + u32 val; + + ret = kstrtou32_from_user(buf, count, 0, &val); + if (ret) + return ret; + + if (ret == 0) + memset(&priv->stats, 0, sizeof(priv->stats)); + + return count; +} + +static int icnss_stats_show_rejuvenate_info(struct seq_file *s, + struct icnss_priv *priv) +{ + if (priv->stats.rejuvenate_ind) { + seq_puts(s, "\n<---------------- Rejuvenate Info ----------------->\n"); + seq_printf(s, "Number of Rejuvenations: %u\n", + priv->stats.rejuvenate_ind); + seq_printf(s, "Cause for Rejuvenation: 0x%x\n", + priv->cause_for_rejuvenation); + seq_printf(s, "Requesting Sub-System: 0x%x\n", + priv->requesting_sub_system); + seq_printf(s, "Line Number: %u\n", + priv->line_number); + seq_printf(s, "Function Name: %s\n", + priv->function_name); + } + + return 0; +} + +static int icnss_stats_show_irqs(struct seq_file *s, struct icnss_priv *priv) +{ + int i; + + seq_puts(s, "\n<------------------ IRQ stats ------------------->\n"); + seq_printf(s, "%4s %4s %8s %8s %8s %8s\n", "CE_ID", "IRQ", "Request", + "Free", "Enable", "Disable"); + for (i = 0; i < ICNSS_MAX_IRQ_REGISTRATIONS; i++) + seq_printf(s, "%4d: %4u %8u %8u %8u %8u\n", i, + priv->ce_irqs[i], priv->stats.ce_irqs[i].request, + priv->stats.ce_irqs[i].free, + priv->stats.ce_irqs[i].enable, + priv->stats.ce_irqs[i].disable); + + return 0; +} + +static int icnss_stats_show_capability(struct seq_file *s, + struct icnss_priv *priv) +{ + if (test_bit(ICNSS_FW_READY, &priv->state)) { + seq_puts(s, "\n<---------------- FW Capability ----------------->\n"); + seq_printf(s, "Chip ID: 0x%x\n", priv->chip_info.chip_id); + seq_printf(s, "Chip family: 0x%x\n", + priv->chip_info.chip_family); + seq_printf(s, "Board ID: 0x%x\n", priv->board_id); + seq_printf(s, "SOC Info: 0x%x\n", priv->soc_id); + seq_printf(s, "Firmware Version: 0x%x\n", + priv->fw_version_info.fw_version); + seq_printf(s, "Firmware Build Timestamp: %s\n", + priv->fw_version_info.fw_build_timestamp); + seq_printf(s, "Firmware Build ID: %s\n", + priv->fw_build_id); + } + + return 0; +} + +static int icnss_stats_show_events(struct seq_file *s, struct icnss_priv *priv) +{ + int i; + + seq_puts(s, "\n<----------------- Events stats ------------------->\n"); + seq_printf(s, "%24s %16s %16s\n", "Events", "Posted", "Processed"); + for (i = 0; i < ICNSS_DRIVER_EVENT_MAX; i++) + seq_printf(s, "%24s %16u %16u\n", + icnss_driver_event_to_str(i), + priv->stats.events[i].posted, + priv->stats.events[i].processed); + + return 0; +} + +static int icnss_stats_show_state(struct seq_file *s, struct icnss_priv *priv) +{ + enum icnss_driver_state i; + int skip = 0; + unsigned long state; + + seq_printf(s, "\nState: 0x%lx(", priv->state); + for (i = 0, state = priv->state; state != 0; state >>= 1, i++) { + + if (!(state & 0x1)) + continue; + + if (skip++) + seq_puts(s, " | "); + + switch (i) { + case ICNSS_WLFW_CONNECTED: + seq_puts(s, "FW CONN"); + continue; + case ICNSS_POWER_ON: + seq_puts(s, "POWER ON"); + continue; + case ICNSS_FW_READY: + seq_puts(s, "FW READY"); + continue; + case ICNSS_DRIVER_PROBED: + seq_puts(s, "DRIVER PROBED"); + continue; + case ICNSS_FW_TEST_MODE: + seq_puts(s, "FW TEST MODE"); + continue; + case ICNSS_PM_SUSPEND: + seq_puts(s, "PM SUSPEND"); + continue; + case ICNSS_PM_SUSPEND_NOIRQ: + seq_puts(s, "PM SUSPEND NOIRQ"); + continue; + case ICNSS_SSR_REGISTERED: + seq_puts(s, "SSR REGISTERED"); + continue; + case ICNSS_PDR_REGISTERED: + seq_puts(s, "PDR REGISTERED"); + continue; + case ICNSS_PD_RESTART: + seq_puts(s, "PD RESTART"); + continue; + case ICNSS_WLFW_EXISTS: + seq_puts(s, "WLAN FW EXISTS"); + continue; + case ICNSS_SHUTDOWN_DONE: + seq_puts(s, "SHUTDOWN DONE"); + continue; + case ICNSS_HOST_TRIGGERED_PDR: + seq_puts(s, "HOST TRIGGERED PDR"); + continue; + case ICNSS_FW_DOWN: + seq_puts(s, "FW DOWN"); + continue; + case ICNSS_DRIVER_UNLOADING: + seq_puts(s, "DRIVER UNLOADING"); + continue; + case ICNSS_REJUVENATE: + seq_puts(s, "FW REJUVENATE"); + continue; + case ICNSS_MODE_ON: + seq_puts(s, "MODE ON DONE"); + continue; + case ICNSS_BLOCK_SHUTDOWN: + seq_puts(s, "BLOCK SHUTDOWN"); + continue; + case ICNSS_PDR: + seq_puts(s, "PDR TRIGGERED"); + continue; + case ICNSS_DEL_SERVER: + seq_puts(s, "DEL SERVER"); + continue; + case ICNSS_COLD_BOOT_CAL: + seq_puts(s, "COLD BOOT CALIBRATION"); + continue; + case ICNSS_QMI_DMS_CONNECTED: + seq_puts(s, "DMS_CONNECTED"); + } + + seq_printf(s, "UNKNOWN-%d", i); + } + seq_puts(s, ")\n"); + + return 0; +} + +#define ICNSS_STATS_DUMP(_s, _priv, _x) \ + seq_printf(_s, "%24s: %u\n", #_x, _priv->stats._x) + +static int icnss_stats_show(struct seq_file *s, void *data) +{ + + struct icnss_priv *priv = s->private; + + ICNSS_STATS_DUMP(s, priv, ind_register_req); + ICNSS_STATS_DUMP(s, priv, ind_register_resp); + ICNSS_STATS_DUMP(s, priv, ind_register_err); + ICNSS_STATS_DUMP(s, priv, cap_req); + ICNSS_STATS_DUMP(s, priv, cap_resp); + ICNSS_STATS_DUMP(s, priv, cap_err); + ICNSS_STATS_DUMP(s, priv, pin_connect_result); + ICNSS_STATS_DUMP(s, priv, cfg_req); + ICNSS_STATS_DUMP(s, priv, cfg_resp); + ICNSS_STATS_DUMP(s, priv, cfg_req_err); + ICNSS_STATS_DUMP(s, priv, mode_req); + ICNSS_STATS_DUMP(s, priv, mode_resp); + ICNSS_STATS_DUMP(s, priv, mode_req_err); + ICNSS_STATS_DUMP(s, priv, ini_req); + ICNSS_STATS_DUMP(s, priv, ini_resp); + ICNSS_STATS_DUMP(s, priv, ini_req_err); + ICNSS_STATS_DUMP(s, priv, recovery.pdr_fw_crash); + ICNSS_STATS_DUMP(s, priv, recovery.pdr_host_error); + ICNSS_STATS_DUMP(s, priv, recovery.root_pd_crash); + ICNSS_STATS_DUMP(s, priv, recovery.root_pd_shutdown); + + seq_puts(s, "\n<------------------ PM stats ------------------->\n"); + ICNSS_STATS_DUMP(s, priv, pm_suspend); + ICNSS_STATS_DUMP(s, priv, pm_suspend_err); + ICNSS_STATS_DUMP(s, priv, pm_resume); + ICNSS_STATS_DUMP(s, priv, pm_resume_err); + ICNSS_STATS_DUMP(s, priv, pm_suspend_noirq); + ICNSS_STATS_DUMP(s, priv, pm_suspend_noirq_err); + ICNSS_STATS_DUMP(s, priv, pm_resume_noirq); + ICNSS_STATS_DUMP(s, priv, pm_resume_noirq_err); + ICNSS_STATS_DUMP(s, priv, pm_stay_awake); + ICNSS_STATS_DUMP(s, priv, pm_relax); + + if (priv->device_id != WCN6750_DEVICE_ID) { + seq_puts(s, "\n<------------------ MSA stats ------------------->\n"); + ICNSS_STATS_DUMP(s, priv, msa_info_req); + ICNSS_STATS_DUMP(s, priv, msa_info_resp); + ICNSS_STATS_DUMP(s, priv, msa_info_err); + ICNSS_STATS_DUMP(s, priv, msa_ready_req); + ICNSS_STATS_DUMP(s, priv, msa_ready_resp); + ICNSS_STATS_DUMP(s, priv, msa_ready_err); + ICNSS_STATS_DUMP(s, priv, msa_ready_ind); + + seq_puts(s, "\n<------------------ Rejuvenate stats ------------------->\n"); + ICNSS_STATS_DUMP(s, priv, rejuvenate_ind); + ICNSS_STATS_DUMP(s, priv, rejuvenate_ack_req); + ICNSS_STATS_DUMP(s, priv, rejuvenate_ack_resp); + ICNSS_STATS_DUMP(s, priv, rejuvenate_ack_err); + icnss_stats_show_rejuvenate_info(s, priv); + + } + + icnss_stats_show_irqs(s, priv); + + icnss_stats_show_capability(s, priv); + + icnss_stats_show_events(s, priv); + + icnss_stats_show_state(s, priv); + + return 0; +} + +static int icnss_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, icnss_stats_show, inode->i_private); +} + +static const struct file_operations icnss_stats_fops = { + .read = seq_read, + .write = icnss_stats_write, + .release = single_release, + .open = icnss_stats_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static int icnss_fw_debug_show(struct seq_file *s, void *data) +{ + struct icnss_priv *priv = s->private; + + seq_puts(s, "\nUsage: echo > /icnss/fw_debug\n"); + + seq_puts(s, "\nCMD: test_mode\n"); + seq_puts(s, " VAL: 0 (Test mode disable)\n"); + seq_puts(s, " VAL: 1 (WLAN FW test)\n"); + seq_puts(s, " VAL: 2 (CCPM test)\n"); + seq_puts(s, " VAL: 3 (Trigger Recovery)\n"); + seq_puts(s, " VAL: 4 (allow recursive recovery)\n"); + seq_puts(s, " VAL: 3 (Disallow recursive recovery)\n"); + + seq_puts(s, "\nCMD: dynamic_feature_mask\n"); + seq_puts(s, " VAL: (64 bit feature mask)\n"); + + if (!test_bit(ICNSS_FW_READY, &priv->state)) { + seq_puts(s, "Firmware is not ready yet, can't run test_mode!\n"); + goto out; + } + + if (test_bit(ICNSS_DRIVER_PROBED, &priv->state)) { + seq_puts(s, "Machine mode is running, can't run test_mode!\n"); + goto out; + } + + if (test_bit(ICNSS_FW_TEST_MODE, &priv->state)) { + seq_puts(s, "test_mode is running, can't run test_mode!\n"); + goto out; + } + +out: + seq_puts(s, "\n"); + return 0; +} + +static int icnss_test_mode_fw_test_off(struct icnss_priv *priv) +{ + int ret; + + if (!test_bit(ICNSS_FW_READY, &priv->state)) { + icnss_pr_err("Firmware is not ready yet!, wait for FW READY: state: 0x%lx\n", + priv->state); + ret = -ENODEV; + goto out; + } + + if (test_bit(ICNSS_DRIVER_PROBED, &priv->state)) { + icnss_pr_err("Machine mode is running, can't run test mode: state: 0x%lx\n", + priv->state); + ret = -EINVAL; + goto out; + } + + if (!test_bit(ICNSS_FW_TEST_MODE, &priv->state)) { + icnss_pr_err("Test mode not started, state: 0x%lx\n", + priv->state); + ret = -EINVAL; + goto out; + } + + icnss_wlan_disable(&priv->pdev->dev, ICNSS_OFF); + + ret = icnss_hw_power_off(priv); + + clear_bit(ICNSS_FW_TEST_MODE, &priv->state); + +out: + return ret; +} + +static int icnss_test_mode_fw_test(struct icnss_priv *priv, + enum icnss_driver_mode mode) +{ + int ret; + + if (!test_bit(ICNSS_FW_READY, &priv->state)) { + icnss_pr_err("Firmware is not ready yet!, wait for FW READY, state: 0x%lx\n", + priv->state); + ret = -ENODEV; + goto out; + } + + if (test_bit(ICNSS_DRIVER_PROBED, &priv->state)) { + icnss_pr_err("Machine mode is running, can't run test mode, state: 0x%lx\n", + priv->state); + ret = -EINVAL; + goto out; + } + + if (test_bit(ICNSS_FW_TEST_MODE, &priv->state)) { + icnss_pr_err("Test mode already started, state: 0x%lx\n", + priv->state); + ret = -EBUSY; + goto out; + } + + ret = icnss_hw_power_on(priv); + if (ret) + goto out; + + set_bit(ICNSS_FW_TEST_MODE, &priv->state); + + ret = icnss_wlan_enable(&priv->pdev->dev, NULL, mode, NULL); + if (ret) + goto power_off; + + return 0; + +power_off: + icnss_hw_power_off(priv); + clear_bit(ICNSS_FW_TEST_MODE, &priv->state); + +out: + return ret; +} + + +static ssize_t icnss_fw_debug_write(struct file *fp, + const char __user *user_buf, + size_t count, loff_t *off) +{ + struct icnss_priv *priv = + ((struct seq_file *)fp->private_data)->private; + char buf[64]; + char *sptr, *token; + unsigned int len = 0; + char *cmd; + uint64_t val; + const char *delim = " "; + int ret = 0; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EINVAL; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + if (!sptr) + return -EINVAL; + cmd = token; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + if (kstrtou64(token, 0, &val)) + return -EINVAL; + + if (strcmp(cmd, "test_mode") == 0) { + switch (val) { + case 0: + ret = icnss_test_mode_fw_test_off(priv); + break; + case 1: + ret = icnss_test_mode_fw_test(priv, ICNSS_WALTEST); + break; + case 2: + ret = icnss_test_mode_fw_test(priv, ICNSS_CCPM); + break; + case 3: + ret = icnss_trigger_recovery(&priv->pdev->dev); + break; + case 4: + icnss_allow_recursive_recovery(&priv->pdev->dev); + break; + case 5: + icnss_disallow_recursive_recovery(&priv->pdev->dev); + break; + default: + return -EINVAL; + } + } else if (strcmp(cmd, "dynamic_feature_mask") == 0) { + ret = wlfw_dynamic_feature_mask_send_sync_msg(priv, val); + } else { + return -EINVAL; + } + + if (ret) + return ret; + + return count; +} + +static int icnss_fw_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, icnss_fw_debug_show, inode->i_private); +} + +static const struct file_operations icnss_fw_debug_fops = { + .read = seq_read, + .write = icnss_fw_debug_write, + .release = single_release, + .open = icnss_fw_debug_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static ssize_t icnss_control_params_debug_write(struct file *fp, + const char __user *user_buf, + size_t count, loff_t *off) +{ + struct icnss_priv *priv = + ((struct seq_file *)fp->private_data)->private; + + char buf[64]; + char *sptr, *token; + char *cmd; + u32 val; + unsigned int len = 0; + const char *delim = " "; + + if (!priv) + return -ENODEV; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EINVAL; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + if (!sptr) + return -EINVAL; + cmd = token; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val)) + return -EINVAL; + + if (strcmp(cmd, "qmi_timeout") == 0) + priv->ctrl_params.qmi_timeout = msecs_to_jiffies(val); + else + return -EINVAL; + + return count; +} + +static int icnss_control_params_debug_show(struct seq_file *s, void *data) +{ + struct icnss_priv *priv = s->private; + + seq_puts(s, "\nUsage: echo > /icnss/control_params\n"); + seq_puts(s, " can be from below:\n"); + seq_puts(s, "qmi_timeout: Timeout for QMI message in milliseconds\n"); + + seq_puts(s, "\nCurrent value:\n"); + + seq_printf(s, "qmi_timeout: %u\n", jiffies_to_msecs(priv->ctrl_params.qmi_timeout)); + + return 0; +} + +static int icnss_control_params_debug_open(struct inode *inode, + struct file *file) +{ + return single_open(file, icnss_control_params_debug_show, + inode->i_private); +} + +static const struct file_operations icnss_control_params_debug_fops = { + .read = seq_read, + .write = icnss_control_params_debug_write, + .release = single_release, + .open = icnss_control_params_debug_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +#ifdef CONFIG_ICNSS2_DEBUG +int icnss_debugfs_create(struct icnss_priv *priv) +{ + int ret = 0; + struct dentry *root_dentry; + + root_dentry = debugfs_create_dir("icnss", NULL); + + if (IS_ERR(root_dentry)) { + ret = PTR_ERR(root_dentry); + icnss_pr_err("Unable to create debugfs %d\n", ret); + goto out; + } + + priv->root_dentry = root_dentry; + + debugfs_create_file("fw_debug", 0600, root_dentry, priv, + &icnss_fw_debug_fops); + debugfs_create_file("stats", 0600, root_dentry, priv, + &icnss_stats_fops); + debugfs_create_file("reg_read", 0600, root_dentry, priv, + &icnss_regread_fops); + debugfs_create_file("reg_write", 0600, root_dentry, priv, + &icnss_regwrite_fops); + debugfs_create_file("control_params", 0600, root_dentry, priv, + &icnss_control_params_debug_fops); +out: + return ret; +} +#else +int icnss_debugfs_create(struct icnss_priv *priv) +{ + int ret = 0; + struct dentry *root_dentry; + + root_dentry = debugfs_create_dir("icnss", NULL); + + if (IS_ERR(root_dentry)) { + ret = PTR_ERR(root_dentry); + icnss_pr_err("Unable to create debugfs %d\n", ret); + return ret; + } + + priv->root_dentry = root_dentry; + + debugfs_create_file("stats", 0600, root_dentry, priv, + &icnss_stats_fops); + return 0; +} +#endif + +void icnss_debugfs_destroy(struct icnss_priv *priv) +{ + debugfs_remove_recursive(priv->root_dentry); +} + +void icnss_debug_init(void) +{ + icnss_ipc_log_context = ipc_log_context_create(NUM_LOG_PAGES, + "icnss", 0); + if (!icnss_ipc_log_context) + icnss_pr_err("Unable to create log context\n"); + + icnss_ipc_log_long_context = ipc_log_context_create(NUM_LOG_LONG_PAGES, + "icnss_long", 0); + if (!icnss_ipc_log_long_context) + icnss_pr_err("Unable to create log long context\n"); + + icnss_ipc_log_smp2p_context = ipc_log_context_create(NUM_LOG_LONG_PAGES, + "icnss_smp2p", 0); + if (!icnss_ipc_log_smp2p_context) + icnss_pr_err("Unable to create log smp2p context\n"); + + icnss_ipc_soc_wake_context = ipc_log_context_create(NUM_LOG_LONG_PAGES, + "icnss_soc_wake", 0); + if (!icnss_ipc_soc_wake_context) + icnss_pr_err("Unable to create log soc_wake context\n"); + +} + +void icnss_debug_deinit(void) +{ + if (icnss_ipc_log_context) { + ipc_log_context_destroy(icnss_ipc_log_context); + icnss_ipc_log_context = NULL; + } + + if (icnss_ipc_log_long_context) { + ipc_log_context_destroy(icnss_ipc_log_long_context); + icnss_ipc_log_long_context = NULL; + } + + if (icnss_ipc_log_smp2p_context) { + ipc_log_context_destroy(icnss_ipc_log_smp2p_context); + icnss_ipc_log_smp2p_context = NULL; + } + + if (icnss_ipc_soc_wake_context) { + ipc_log_context_destroy(icnss_ipc_soc_wake_context); + icnss_ipc_soc_wake_context = NULL; + } +} diff --git a/icnss2/debug.h b/icnss2/debug.h new file mode 100644 index 0000000000..e7f2daf227 --- /dev/null +++ b/icnss2/debug.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef _ICNSS_DEBUG_H +#define _ICNSS_DEBUG_H + +#include +#include + +#define NUM_LOG_PAGES 10 +#define NUM_LOG_LONG_PAGES 4 + +extern void *icnss_ipc_log_context; +extern void *icnss_ipc_log_long_context; +extern void *icnss_ipc_log_smp2p_context; +extern void *icnss_ipc_soc_wake_context; + +#if IS_ENABLED(CONFIG_IPC_LOGGING) +#define icnss_ipc_log_string(_x...) \ + ipc_log_string(icnss_ipc_log_context, _x) + +#define icnss_ipc_log_long_string(_x...) \ + ipc_log_string(icnss_ipc_log_long_context, _x) + +#define icnss_ipc_log_smp2p_string(_x...) \ + ipc_log_string(icnss_ipc_log_smp2p_context, _x) + +#define icnss_ipc_soc_wake_string(_x...) \ + ipc_log_string(icnss_ipc_soc_wake_context, _x) +#else +#define icnss_ipc_log_string(_x...) + +#define icnss_ipc_log_long_string(_x...) + +#define icnss_ipc_log_smp2p_string(_x...) + +#define icnss_ipc_soc_wake_string(_x...) +#endif + +#define icnss_pr_err(_fmt, ...) do { \ + printk("%s" pr_fmt(_fmt), KERN_ERR, ##__VA_ARGS__); \ + icnss_ipc_log_string("%s" pr_fmt(_fmt), "", \ + ##__VA_ARGS__); \ + } while (0) + +#define icnss_pr_warn(_fmt, ...) do { \ + printk("%s" pr_fmt(_fmt), KERN_WARNING, ##__VA_ARGS__); \ + icnss_ipc_log_string("%s" pr_fmt(_fmt), "", \ + ##__VA_ARGS__); \ + } while (0) + +#define icnss_pr_info(_fmt, ...) do { \ + printk("%s" pr_fmt(_fmt), KERN_INFO, ##__VA_ARGS__); \ + icnss_ipc_log_string("%s" pr_fmt(_fmt), "", \ + ##__VA_ARGS__); \ + } while (0) + +#define icnss_pr_dbg(_fmt, ...) do { \ + pr_debug(_fmt, ##__VA_ARGS__); \ + icnss_ipc_log_string(pr_fmt(_fmt), ##__VA_ARGS__); \ + } while (0) + +#define icnss_pr_vdbg(_fmt, ...) do { \ + pr_debug(_fmt, ##__VA_ARGS__); \ + icnss_ipc_log_long_string(pr_fmt(_fmt), ##__VA_ARGS__); \ + } while (0) + +#define icnss_pr_smp2p(_fmt, ...) do { \ + pr_debug(_fmt, ##__VA_ARGS__); \ + icnss_ipc_log_smp2p_string(pr_fmt(_fmt), ##__VA_ARGS__); \ + } while (0) + +#define icnss_pr_soc_wake(_fmt, ...) do { \ + pr_debug(_fmt, ##__VA_ARGS__); \ + icnss_ipc_soc_wake_string(pr_fmt(_fmt), ##__VA_ARGS__); \ + } while (0) + +#ifdef CONFIG_ICNSS2_DEBUG +#define ICNSS_ASSERT(_condition) do { \ + if (!(_condition)) { \ + icnss_pr_err("ASSERT at line %d\n", __LINE__); \ + BUG(); \ + } \ + } while (0) +#else +#define ICNSS_ASSERT(_condition) do { } while (0) +#endif + +#define icnss_fatal_err(_fmt, ...) \ + icnss_pr_err("fatal: "_fmt, ##__VA_ARGS__) + +enum icnss_debug_quirks { + HW_ALWAYS_ON, + HW_DEBUG_ENABLE, + SKIP_QMI, + RECOVERY_DISABLE, + SSR_ONLY, + PDR_ONLY, + FW_REJUVENATE_ENABLE, +}; + +void icnss_debug_init(void); +void icnss_debug_deinit(void); +int icnss_debugfs_create(struct icnss_priv *priv); +void icnss_debugfs_destroy(struct icnss_priv *priv); +#endif /* _ICNSS_DEBUG_H */ diff --git a/icnss2/genl.c b/icnss2/genl.c new file mode 100644 index 0000000000..556a298a61 --- /dev/null +++ b/icnss2/genl.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */ + +#define pr_fmt(fmt) "cnss_genl: " fmt + +#include +#include +#include +#include +#include + +#include "main.h" +#include "debug.h" + +#define ICNSS_GENL_FAMILY_NAME "cnss-genl" +#define ICNSS_GENL_MCAST_GROUP_NAME "cnss-genl-grp" +#define ICNSS_GENL_VERSION 1 +#define ICNSS_GENL_DATA_LEN_MAX (15 * 1024) +#define ICNSS_GENL_STR_LEN_MAX 16 + +enum { + ICNSS_GENL_ATTR_MSG_UNSPEC, + ICNSS_GENL_ATTR_MSG_TYPE, + ICNSS_GENL_ATTR_MSG_FILE_NAME, + ICNSS_GENL_ATTR_MSG_TOTAL_SIZE, + ICNSS_GENL_ATTR_MSG_SEG_ID, + ICNSS_GENL_ATTR_MSG_END, + ICNSS_GENL_ATTR_MSG_DATA_LEN, + ICNSS_GENL_ATTR_MSG_DATA, + __ICNSS_GENL_ATTR_MAX, +}; + +#define ICNSS_GENL_ATTR_MAX (__ICNSS_GENL_ATTR_MAX - 1) + +enum { + ICNSS_GENL_CMD_UNSPEC, + ICNSS_GENL_CMD_MSG, + __ICNSS_GENL_CMD_MAX, +}; + +#define ICNSS_GENL_CMD_MAX (__ICNSS_GENL_CMD_MAX - 1) + +static struct nla_policy icnss_genl_msg_policy[ICNSS_GENL_ATTR_MAX + 1] = { + [ICNSS_GENL_ATTR_MSG_TYPE] = { .type = NLA_U8 }, + [ICNSS_GENL_ATTR_MSG_FILE_NAME] = { .type = NLA_NUL_STRING, + .len = ICNSS_GENL_STR_LEN_MAX }, + [ICNSS_GENL_ATTR_MSG_TOTAL_SIZE] = { .type = NLA_U32 }, + [ICNSS_GENL_ATTR_MSG_SEG_ID] = { .type = NLA_U32 }, + [ICNSS_GENL_ATTR_MSG_END] = { .type = NLA_U8 }, + [ICNSS_GENL_ATTR_MSG_DATA_LEN] = { .type = NLA_U32 }, + [ICNSS_GENL_ATTR_MSG_DATA] = { .type = NLA_BINARY, + .len = ICNSS_GENL_DATA_LEN_MAX }, +}; + +static int icnss_genl_process_msg(struct sk_buff *skb, struct genl_info *info) +{ + return 0; +} + +static struct genl_ops icnss_genl_ops[] = { + { + .cmd = ICNSS_GENL_CMD_MSG, + .doit = icnss_genl_process_msg, + }, +}; + +static struct genl_multicast_group icnss_genl_mcast_grp[] = { + { + .name = ICNSS_GENL_MCAST_GROUP_NAME, + }, +}; + +static struct genl_family icnss_genl_family = { + .id = 0, + .hdrsize = 0, + .name = ICNSS_GENL_FAMILY_NAME, + .version = ICNSS_GENL_VERSION, + .maxattr = ICNSS_GENL_ATTR_MAX, + .policy = icnss_genl_msg_policy, + .module = THIS_MODULE, + .ops = icnss_genl_ops, + .n_ops = ARRAY_SIZE(icnss_genl_ops), + .mcgrps = icnss_genl_mcast_grp, + .n_mcgrps = ARRAY_SIZE(icnss_genl_mcast_grp), +}; + +static int icnss_genl_send_data(u8 type, char *file_name, u32 total_size, + u32 seg_id, u8 end, u32 data_len, u8 *msg_buff) +{ + struct sk_buff *skb = NULL; + void *msg_header = NULL; + int ret = 0; + char filename[ICNSS_GENL_STR_LEN_MAX + 1]; + + icnss_pr_dbg("type: %u, file_name %s, total_size: %x, seg_id %u, end %u, data_len %u\n", + type, file_name, total_size, seg_id, end, data_len); + + if (!file_name) + strlcpy(filename, "default", sizeof(filename)); + else + strlcpy(filename, file_name, sizeof(filename)); + + skb = genlmsg_new(NLMSG_HDRLEN + + nla_total_size(sizeof(type)) + + nla_total_size(strlen(filename) + 1) + + nla_total_size(sizeof(total_size)) + + nla_total_size(sizeof(seg_id)) + + nla_total_size(sizeof(end)) + + nla_total_size(sizeof(data_len)) + + nla_total_size(data_len), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + msg_header = genlmsg_put(skb, 0, 0, + &icnss_genl_family, 0, + ICNSS_GENL_CMD_MSG); + if (!msg_header) { + ret = -ENOMEM; + goto fail; + } + + ret = nla_put_u8(skb, ICNSS_GENL_ATTR_MSG_TYPE, type); + if (ret < 0) + goto fail; + ret = nla_put_string(skb, ICNSS_GENL_ATTR_MSG_FILE_NAME, filename); + if (ret < 0) + goto fail; + ret = nla_put_u32(skb, ICNSS_GENL_ATTR_MSG_TOTAL_SIZE, total_size); + if (ret < 0) + goto fail; + ret = nla_put_u32(skb, ICNSS_GENL_ATTR_MSG_SEG_ID, seg_id); + if (ret < 0) + goto fail; + ret = nla_put_u8(skb, ICNSS_GENL_ATTR_MSG_END, end); + if (ret < 0) + goto fail; + ret = nla_put_u32(skb, ICNSS_GENL_ATTR_MSG_DATA_LEN, data_len); + if (ret < 0) + goto fail; + ret = nla_put(skb, ICNSS_GENL_ATTR_MSG_DATA, data_len, msg_buff); + if (ret < 0) + goto fail; + + genlmsg_end(skb, msg_header); + ret = genlmsg_multicast(&icnss_genl_family, skb, 0, 0, GFP_KERNEL); + if (ret < 0) + icnss_pr_err("Fail to send genl msg: %d\n", ret); + + return ret; +fail: + icnss_pr_err("Fail to generate genl msg: %d\n", ret); + if (skb) + nlmsg_free(skb); + return ret; +} + +int icnss_genl_send_msg(void *buff, u8 type, char *file_name, u32 total_size) +{ + int ret = 0; + u8 *msg_buff = buff; + u32 remaining = total_size; + u32 seg_id = 0; + u32 data_len = 0; + u8 end = 0; + u8 retry; + + icnss_pr_dbg("type: %u, total_size: %x\n", type, total_size); + + while (remaining) { + if (remaining > ICNSS_GENL_DATA_LEN_MAX) { + data_len = ICNSS_GENL_DATA_LEN_MAX; + } else { + data_len = remaining; + end = 1; + } + + for (retry = 0; retry < 2; retry++) { + ret = icnss_genl_send_data(type, file_name, total_size, + seg_id, end, data_len, + msg_buff); + if (ret >= 0) + break; + msleep(100); + } + + if (ret < 0) { + icnss_pr_err("fail to send genl data, ret %d\n", ret); + return ret; + } + + remaining -= data_len; + msg_buff += data_len; + seg_id++; + } + + return ret; +} + +int icnss_genl_init(void) +{ + int ret = 0; + + ret = genl_register_family(&icnss_genl_family); + if (ret != 0) + icnss_pr_err("genl_register_family fail: %d\n", ret); + + return ret; +} + +void icnss_genl_exit(void) +{ + genl_unregister_family(&icnss_genl_family); +} diff --git a/icnss2/genl.h b/icnss2/genl.h new file mode 100644 index 0000000000..6cc04c9fcb --- /dev/null +++ b/icnss2/genl.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. */ + +#ifndef __ICNSS_GENL_H__ +#define __ICNSS_GENL_H__ + +enum icnss_genl_msg_type { + ICNSS_GENL_MSG_TYPE_UNSPEC, + ICNSS_GENL_MSG_TYPE_QDSS, +}; + +int icnss_genl_init(void); +void icnss_genl_exit(void); +int icnss_genl_send_msg(void *buff, u8 type, + char *file_name, u32 total_size); + +#endif diff --git a/icnss2/main.c b/icnss2/main.c new file mode 100644 index 0000000000..62eab7937f --- /dev/null +++ b/icnss2/main.c @@ -0,0 +1,4559 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2020, 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "icnss2: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "main.h" +#include "qmi.h" +#include "debug.h" +#include "power.h" +#include "genl.h" + +#define MAX_PROP_SIZE 32 +#define NUM_LOG_PAGES 10 +#define NUM_LOG_LONG_PAGES 4 +#define ICNSS_MAGIC 0x5abc5abc + +#define ICNSS_WLAN_SERVICE_NAME "wlan/fw" +#define ICNSS_WLANPD_NAME "msm/modem/wlan_pd" +#define ICNSS_DEFAULT_FEATURE_MASK 0x01 + +#define ICNSS_M3_SEGMENT(segment) "wcnss_"segment +#define ICNSS_M3_SEGMENT_PHYAREG "phyareg" +#define ICNSS_M3_SEGMENT_PHYA "phydbg" +#define ICNSS_M3_SEGMENT_WMACREG "wmac0reg" +#define ICNSS_M3_SEGMENT_WCSSDBG "WCSSDBG" +#define ICNSS_M3_SEGMENT_PHYAM3 "PHYAPDMEM" + +#define ICNSS_QUIRKS_DEFAULT BIT(FW_REJUVENATE_ENABLE) +#define ICNSS_MAX_PROBE_CNT 2 + +#define ICNSS_BDF_TYPE_DEFAULT ICNSS_BDF_ELF + +#define PROBE_TIMEOUT 15000 +#define SMP2P_SOC_WAKE_TIMEOUT 500 +#ifdef CONFIG_ICNSS2_DEBUG +static unsigned long qmi_timeout = 3000; +module_param(qmi_timeout, ulong, 0600); +#define WLFW_TIMEOUT msecs_to_jiffies(qmi_timeout) +#else +#define WLFW_TIMEOUT msecs_to_jiffies(3000) +#endif + +static struct icnss_priv *penv; +static struct work_struct wpss_loader; +uint64_t dynamic_feature_mask = ICNSS_DEFAULT_FEATURE_MASK; + +#define ICNSS_EVENT_PENDING 2989 + +#define ICNSS_EVENT_SYNC BIT(0) +#define ICNSS_EVENT_UNINTERRUPTIBLE BIT(1) +#define ICNSS_EVENT_SYNC_UNINTERRUPTIBLE (ICNSS_EVENT_UNINTERRUPTIBLE | \ + ICNSS_EVENT_SYNC) +#define ICNSS_DMS_QMI_CONNECTION_WAIT_MS 50 +#define ICNSS_DMS_QMI_CONNECTION_WAIT_RETRY 200 + +#define SMP2P_GET_MAX_RETRY 4 +#define SMP2P_GET_RETRY_DELAY_MS 500 + +#define RAMDUMP_NUM_DEVICES 256 +#define ICNSS_RAMDUMP_NAME "icnss_ramdump" + +#define ICNSS_RPROC_LEN 10 +static DEFINE_IDA(rd_minor_id); + +enum icnss_pdr_cause_index { + ICNSS_FW_CRASH, + ICNSS_ROOT_PD_CRASH, + ICNSS_ROOT_PD_SHUTDOWN, + ICNSS_HOST_ERROR, +}; + +static const char * const icnss_pdr_cause[] = { + [ICNSS_FW_CRASH] = "FW crash", + [ICNSS_ROOT_PD_CRASH] = "Root PD crashed", + [ICNSS_ROOT_PD_SHUTDOWN] = "Root PD shutdown", + [ICNSS_HOST_ERROR] = "Host error", +}; + +static void icnss_set_plat_priv(struct icnss_priv *priv) +{ + penv = priv; +} + +static struct icnss_priv *icnss_get_plat_priv() +{ + return penv; +} + +static ssize_t icnss_sysfs_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct icnss_priv *priv = icnss_get_plat_priv(); + + atomic_set(&priv->is_shutdown, true); + icnss_pr_dbg("Received shutdown indication"); + return count; +} + +static struct kobj_attribute icnss_sysfs_attribute = +__ATTR(shutdown, 0660, NULL, icnss_sysfs_store); + +static void icnss_pm_stay_awake(struct icnss_priv *priv) +{ + if (atomic_inc_return(&priv->pm_count) != 1) + return; + + icnss_pr_vdbg("PM stay awake, state: 0x%lx, count: %d\n", priv->state, + atomic_read(&priv->pm_count)); + + pm_stay_awake(&priv->pdev->dev); + + priv->stats.pm_stay_awake++; +} + +static void icnss_pm_relax(struct icnss_priv *priv) +{ + int r = atomic_dec_return(&priv->pm_count); + + WARN_ON(r < 0); + + if (r != 0) + return; + + icnss_pr_vdbg("PM relax, state: 0x%lx, count: %d\n", priv->state, + atomic_read(&priv->pm_count)); + + pm_relax(&priv->pdev->dev); + priv->stats.pm_relax++; +} + +char *icnss_driver_event_to_str(enum icnss_driver_event_type type) +{ + switch (type) { + case ICNSS_DRIVER_EVENT_SERVER_ARRIVE: + return "SERVER_ARRIVE"; + case ICNSS_DRIVER_EVENT_SERVER_EXIT: + return "SERVER_EXIT"; + case ICNSS_DRIVER_EVENT_FW_READY_IND: + return "FW_READY"; + case ICNSS_DRIVER_EVENT_REGISTER_DRIVER: + return "REGISTER_DRIVER"; + case ICNSS_DRIVER_EVENT_UNREGISTER_DRIVER: + return "UNREGISTER_DRIVER"; + case ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN: + return "PD_SERVICE_DOWN"; + case ICNSS_DRIVER_EVENT_FW_EARLY_CRASH_IND: + return "FW_EARLY_CRASH_IND"; + case ICNSS_DRIVER_EVENT_IDLE_SHUTDOWN: + return "IDLE_SHUTDOWN"; + case ICNSS_DRIVER_EVENT_IDLE_RESTART: + return "IDLE_RESTART"; + case ICNSS_DRIVER_EVENT_FW_INIT_DONE_IND: + return "FW_INIT_DONE"; + case ICNSS_DRIVER_EVENT_QDSS_TRACE_REQ_MEM: + return "QDSS_TRACE_REQ_MEM"; + case ICNSS_DRIVER_EVENT_QDSS_TRACE_SAVE: + return "QDSS_TRACE_SAVE"; + case ICNSS_DRIVER_EVENT_QDSS_TRACE_FREE: + return "QDSS_TRACE_FREE"; + case ICNSS_DRIVER_EVENT_M3_DUMP_UPLOAD_REQ: + return "M3_DUMP_UPLOAD"; + case ICNSS_DRIVER_EVENT_QDSS_TRACE_REQ_DATA: + return "QDSS_TRACE_REQ_DATA"; + case ICNSS_DRIVER_EVENT_SUBSYS_RESTART_LEVEL: + return "SUBSYS_RESTART_LEVEL"; + case ICNSS_DRIVER_EVENT_MAX: + return "EVENT_MAX"; + } + + return "UNKNOWN"; +}; + +char *icnss_soc_wake_event_to_str(enum icnss_soc_wake_event_type type) +{ + switch (type) { + case ICNSS_SOC_WAKE_REQUEST_EVENT: + return "SOC_WAKE_REQUEST"; + case ICNSS_SOC_WAKE_RELEASE_EVENT: + return "SOC_WAKE_RELEASE"; + case ICNSS_SOC_WAKE_EVENT_MAX: + return "SOC_EVENT_MAX"; + } + + return "UNKNOWN"; +}; + +int icnss_driver_event_post(struct icnss_priv *priv, + enum icnss_driver_event_type type, + u32 flags, void *data) +{ + struct icnss_driver_event *event; + unsigned long irq_flags; + int gfp = GFP_KERNEL; + int ret = 0; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Posting event: %s(%d), %s, flags: 0x%x, state: 0x%lx\n", + icnss_driver_event_to_str(type), type, current->comm, + flags, priv->state); + + if (type >= ICNSS_DRIVER_EVENT_MAX) { + icnss_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 == NULL) + return -ENOMEM; + + icnss_pm_stay_awake(priv); + + event->type = type; + event->data = data; + init_completion(&event->complete); + event->ret = ICNSS_EVENT_PENDING; + event->sync = !!(flags & ICNSS_EVENT_SYNC); + + spin_lock_irqsave(&priv->event_lock, irq_flags); + list_add_tail(&event->list, &priv->event_list); + spin_unlock_irqrestore(&priv->event_lock, irq_flags); + + priv->stats.events[type].posted++; + queue_work(priv->event_wq, &priv->event_work); + + if (!(flags & ICNSS_EVENT_SYNC)) + goto out; + + if (flags & ICNSS_EVENT_UNINTERRUPTIBLE) + wait_for_completion(&event->complete); + else + ret = wait_for_completion_interruptible(&event->complete); + + icnss_pr_dbg("Completed event: %s(%d), state: 0x%lx, ret: %d/%d\n", + icnss_driver_event_to_str(type), type, priv->state, ret, + event->ret); + + spin_lock_irqsave(&priv->event_lock, irq_flags); + if (ret == -ERESTARTSYS && event->ret == ICNSS_EVENT_PENDING) { + event->sync = false; + spin_unlock_irqrestore(&priv->event_lock, irq_flags); + ret = -EINTR; + goto out; + } + spin_unlock_irqrestore(&priv->event_lock, irq_flags); + + ret = event->ret; + kfree(event); + +out: + icnss_pm_relax(priv); + return ret; +} + +int icnss_soc_wake_event_post(struct icnss_priv *priv, + enum icnss_soc_wake_event_type type, + u32 flags, void *data) +{ + struct icnss_soc_wake_event *event; + unsigned long irq_flags; + int gfp = GFP_KERNEL; + int ret = 0; + + if (!priv) + return -ENODEV; + + icnss_pr_soc_wake("Posting event: %s(%d), %s, flags: 0x%x, state: 0x%lx\n", + icnss_soc_wake_event_to_str(type), + type, current->comm, flags, priv->state); + + if (type >= ICNSS_SOC_WAKE_EVENT_MAX) { + icnss_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; + + icnss_pm_stay_awake(priv); + + event->type = type; + event->data = data; + init_completion(&event->complete); + event->ret = ICNSS_EVENT_PENDING; + event->sync = !!(flags & ICNSS_EVENT_SYNC); + + spin_lock_irqsave(&priv->soc_wake_msg_lock, irq_flags); + list_add_tail(&event->list, &priv->soc_wake_msg_list); + spin_unlock_irqrestore(&priv->soc_wake_msg_lock, irq_flags); + + priv->stats.soc_wake_events[type].posted++; + queue_work(priv->soc_wake_wq, &priv->soc_wake_msg_work); + + if (!(flags & ICNSS_EVENT_SYNC)) + goto out; + + if (flags & ICNSS_EVENT_UNINTERRUPTIBLE) + wait_for_completion(&event->complete); + else + ret = wait_for_completion_interruptible(&event->complete); + + icnss_pr_soc_wake("Completed event: %s(%d), state: 0x%lx, ret: %d/%d\n", + icnss_soc_wake_event_to_str(type), + type, priv->state, ret, event->ret); + + spin_lock_irqsave(&priv->soc_wake_msg_lock, irq_flags); + if (ret == -ERESTARTSYS && event->ret == ICNSS_EVENT_PENDING) { + event->sync = false; + spin_unlock_irqrestore(&priv->soc_wake_msg_lock, irq_flags); + ret = -EINTR; + goto out; + } + spin_unlock_irqrestore(&priv->soc_wake_msg_lock, irq_flags); + + ret = event->ret; + kfree(event); + +out: + icnss_pm_relax(priv); + return ret; +} + +bool icnss_is_fw_ready(void) +{ + if (!penv) + return false; + else + return test_bit(ICNSS_FW_READY, &penv->state); +} +EXPORT_SYMBOL(icnss_is_fw_ready); + +void icnss_block_shutdown(bool status) +{ + if (!penv) + return; + + if (status) { + set_bit(ICNSS_BLOCK_SHUTDOWN, &penv->state); + reinit_completion(&penv->unblock_shutdown); + } else { + clear_bit(ICNSS_BLOCK_SHUTDOWN, &penv->state); + complete(&penv->unblock_shutdown); + } +} +EXPORT_SYMBOL(icnss_block_shutdown); + +bool icnss_is_fw_down(void) +{ + + struct icnss_priv *priv = icnss_get_plat_priv(); + + if (!priv) + return false; + + return test_bit(ICNSS_FW_DOWN, &priv->state) || + test_bit(ICNSS_PD_RESTART, &priv->state) || + test_bit(ICNSS_REJUVENATE, &priv->state); +} +EXPORT_SYMBOL(icnss_is_fw_down); + +bool icnss_is_rejuvenate(void) +{ + if (!penv) + return false; + else + return test_bit(ICNSS_REJUVENATE, &penv->state); +} +EXPORT_SYMBOL(icnss_is_rejuvenate); + +bool icnss_is_pdr(void) +{ + if (!penv) + return false; + else + return test_bit(ICNSS_PDR, &penv->state); +} +EXPORT_SYMBOL(icnss_is_pdr); + +static int icnss_send_smp2p(struct icnss_priv *priv, + enum icnss_smp2p_msg_id msg_id, + enum smp2p_out_entry smp2p_entry) +{ + unsigned int value = 0; + int ret; + + if (IS_ERR(priv->smp2p_info[smp2p_entry].smem_state)) + return -EINVAL; + + /* No Need to check FW_DOWN for ICNSS_RESET_MSG */ + if (msg_id == ICNSS_RESET_MSG) { + priv->smp2p_info[smp2p_entry].seq = 0; + ret = qcom_smem_state_update_bits( + priv->smp2p_info[smp2p_entry].smem_state, + ICNSS_SMEM_VALUE_MASK, + 0); + if (ret) + icnss_pr_err("Error in SMP2P sent. ret: %d, %s\n", + ret, icnss_smp2p_str[smp2p_entry]); + + return ret; + } + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) + return -ENODEV; + + value |= priv->smp2p_info[smp2p_entry].seq++; + value <<= ICNSS_SMEM_SEQ_NO_POS; + value |= msg_id; + + icnss_pr_smp2p("Sending SMP2P value: 0x%X\n", value); + + if (msg_id == ICNSS_SOC_WAKE_REQ || msg_id == ICNSS_SOC_WAKE_REL) + reinit_completion(&penv->smp2p_soc_wake_wait); + + ret = qcom_smem_state_update_bits( + priv->smp2p_info[smp2p_entry].smem_state, + ICNSS_SMEM_VALUE_MASK, + value); + if (ret) { + icnss_pr_smp2p("Error in SMP2P send ret: %d, %s\n", ret, + icnss_smp2p_str[smp2p_entry]); + } else { + if (msg_id == ICNSS_SOC_WAKE_REQ || + msg_id == ICNSS_SOC_WAKE_REL) { + if (!wait_for_completion_timeout( + &priv->smp2p_soc_wake_wait, + msecs_to_jiffies(SMP2P_SOC_WAKE_TIMEOUT))) { + icnss_pr_err("SMP2P Soc Wake timeout msg %d, %s\n", msg_id, + icnss_smp2p_str[smp2p_entry]); + ICNSS_ASSERT(0); + } + } + } + + return ret; +} + +static irqreturn_t fw_error_fatal_handler(int irq, void *ctx) +{ + struct icnss_priv *priv = ctx; + + if (priv) + priv->force_err_fatal = true; + + icnss_pr_err("Received force error fatal request from FW\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t fw_crash_indication_handler(int irq, void *ctx) +{ + struct icnss_priv *priv = ctx; + struct icnss_uevent_fw_down_data fw_down_data = {0}; + + icnss_pr_err("Received early crash indication from FW\n"); + + if (priv) { + set_bit(ICNSS_FW_DOWN, &priv->state); + icnss_ignore_fw_timeout(true); + + if (test_bit(ICNSS_FW_READY, &priv->state)) { + clear_bit(ICNSS_FW_READY, &priv->state); + fw_down_data.crashed = true; + icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_DOWN, + &fw_down_data); + } + } + + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_FW_EARLY_CRASH_IND, + 0, NULL); + + return IRQ_HANDLED; +} + +static void register_fw_error_notifications(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + struct device_node *dev_node; + int irq = 0, ret = 0; + + if (!priv) + return; + + dev_node = of_find_node_by_name(NULL, "qcom,smp2p_map_wlan_1_in"); + if (!dev_node) { + icnss_pr_err("Failed to get smp2p node for force-fatal-error\n"); + return; + } + + icnss_pr_dbg("smp2p node->name=%s\n", dev_node->name); + + if (strcmp("qcom,smp2p_map_wlan_1_in", dev_node->name) == 0) { + ret = irq = of_irq_get_byname(dev_node, + "qcom,smp2p-force-fatal-error"); + if (ret < 0) { + icnss_pr_err("Unable to get force-fatal-error irq %d\n", + irq); + return; + } + } + + ret = devm_request_threaded_irq(dev, irq, NULL, fw_error_fatal_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "wlanfw-err", priv); + if (ret < 0) { + icnss_pr_err("Unable to register for error fatal IRQ handler %d ret = %d", + irq, ret); + return; + } + icnss_pr_dbg("FW force error fatal handler registered irq = %d\n", irq); + priv->fw_error_fatal_irq = irq; +} + +static void register_early_crash_notifications(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + struct device_node *dev_node; + int irq = 0, ret = 0; + + if (!priv) + return; + + dev_node = of_find_node_by_name(NULL, "qcom,smp2p_map_wlan_1_in"); + if (!dev_node) { + icnss_pr_err("Failed to get smp2p node for early-crash-ind\n"); + return; + } + + icnss_pr_dbg("smp2p node->name=%s\n", dev_node->name); + + if (strcmp("qcom,smp2p_map_wlan_1_in", dev_node->name) == 0) { + ret = irq = of_irq_get_byname(dev_node, + "qcom,smp2p-early-crash-ind"); + if (ret < 0) { + icnss_pr_err("Unable to get early-crash-ind irq %d\n", + irq); + return; + } + } + + ret = devm_request_threaded_irq(dev, irq, NULL, + fw_crash_indication_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "wlanfw-early-crash-ind", priv); + if (ret < 0) { + icnss_pr_err("Unable to register for early crash indication IRQ handler %d ret = %d", + irq, ret); + return; + } + icnss_pr_dbg("FW crash indication handler registered irq = %d\n", irq); + priv->fw_early_crash_irq = irq; +} + +static irqreturn_t fw_soc_wake_ack_handler(int irq, void *ctx) +{ + struct icnss_priv *priv = ctx; + + if (priv) + complete(&priv->smp2p_soc_wake_wait); + + return IRQ_HANDLED; +} + +static void register_soc_wake_notif(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + struct device_node *dev_node; + int irq = 0, ret = 0; + + if (!priv) + return; + + dev_node = of_find_node_by_name(NULL, "qcom,smp2p_map_wlan_2_in"); + if (!dev_node) { + icnss_pr_err("Failed to get smp2p node for soc-wake-ack\n"); + return; + } + + icnss_pr_dbg("smp2p node->name=%s\n", dev_node->name); + + if (strcmp("qcom,smp2p_map_wlan_2_in", dev_node->name) == 0) { + ret = irq = of_irq_get_byname(dev_node, + "qcom,smp2p-soc-wake-ack"); + if (ret < 0) { + icnss_pr_err("Unable to get soc wake ack irq %d\n", + irq); + return; + } + } + + ret = devm_request_threaded_irq(dev, irq, NULL, + fw_soc_wake_ack_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "wlanfw-soc-wake-ack", priv); + if (ret < 0) { + icnss_pr_err("Unable to register for SOC Wake ACK IRQ handler %d ret = %d", + irq, ret); + return; + } + icnss_pr_dbg("FW SOC Wake ACK handler registered irq = %d\n", irq); + priv->fw_soc_wake_ack_irq = irq; +} + + +int icnss_call_driver_uevent(struct icnss_priv *priv, + enum icnss_uevent uevent, void *data) +{ + struct icnss_uevent_data uevent_data; + + if (!priv->ops || !priv->ops->uevent) + return 0; + + icnss_pr_dbg("Calling driver uevent state: 0x%lx, uevent: %d\n", + priv->state, uevent); + + uevent_data.uevent = uevent; + uevent_data.data = data; + + return priv->ops->uevent(&priv->pdev->dev, &uevent_data); +} + +static int icnss_setup_dms_mac(struct icnss_priv *priv) +{ + int i; + int ret = 0; + + ret = icnss_qmi_get_dms_mac(priv); + if (ret == 0 && priv->dms.mac_valid) + goto qmi_send; + + /* DTSI property use-nv-mac is used to force DMS MAC address for WLAN. + * Thus assert on failure to get MAC from DMS even after retries + */ + if (priv->use_nv_mac) { + for (i = 0; i < ICNSS_DMS_QMI_CONNECTION_WAIT_RETRY; i++) { + if (priv->dms.mac_valid) + break; + + ret = icnss_qmi_get_dms_mac(priv); + if (ret != -EAGAIN) + break; + msleep(ICNSS_DMS_QMI_CONNECTION_WAIT_MS); + } + if (!priv->dms.nv_mac_not_prov && !priv->dms.mac_valid) { + icnss_pr_err("Unable to get MAC from DMS after retries\n"); + ICNSS_ASSERT(0); + return -EINVAL; + } + } +qmi_send: + if (priv->dms.mac_valid) + ret = + icnss_wlfw_wlan_mac_req_send_sync(priv, priv->dms.mac, + ARRAY_SIZE(priv->dms.mac)); + return ret; +} + +static void icnss_get_smp2p_info(struct icnss_priv *priv, + enum smp2p_out_entry smp2p_entry) +{ + int retry = 0; + int error; + + if (priv->smp2p_info[smp2p_entry].smem_state) + return; +retry: + priv->smp2p_info[smp2p_entry].smem_state = + qcom_smem_state_get(&priv->pdev->dev, + icnss_smp2p_str[smp2p_entry], + &priv->smp2p_info[smp2p_entry].smem_bit); + if (IS_ERR(priv->smp2p_info[smp2p_entry].smem_state)) { + if (retry++ < SMP2P_GET_MAX_RETRY) { + error = PTR_ERR(priv->smp2p_info[smp2p_entry].smem_state); + icnss_pr_err("Failed to get smem state, ret: %d Entry: %s", + error, icnss_smp2p_str[smp2p_entry]); + msleep(SMP2P_GET_RETRY_DELAY_MS); + goto retry; + } + ICNSS_ASSERT(0); + return; + } + + icnss_pr_dbg("smem state, Entry: %s", icnss_smp2p_str[smp2p_entry]); +} + +static int icnss_driver_event_server_arrive(struct icnss_priv *priv, + void *data) +{ + int ret = 0; + bool ignore_assert = false; + + if (!priv) + return -ENODEV; + + set_bit(ICNSS_WLFW_EXISTS, &priv->state); + clear_bit(ICNSS_FW_DOWN, &priv->state); + clear_bit(ICNSS_FW_READY, &priv->state); + + icnss_ignore_fw_timeout(false); + + if (test_bit(ICNSS_WLFW_CONNECTED, &priv->state)) { + icnss_pr_err("QMI Server already in Connected State\n"); + ICNSS_ASSERT(0); + } + + ret = icnss_connect_to_fw_server(priv, data); + if (ret) + goto fail; + + set_bit(ICNSS_WLFW_CONNECTED, &priv->state); + + ret = wlfw_ind_register_send_sync_msg(priv); + if (ret < 0) { + if (ret == -EALREADY) { + ret = 0; + goto qmi_registered; + } + ignore_assert = true; + goto fail; + } + + if (priv->device_id == WCN6750_DEVICE_ID) { + ret = wlfw_host_cap_send_sync(priv); + if (ret < 0) + goto fail; + } + + if (priv->device_id == ADRASTEA_DEVICE_ID) { + if (!priv->msa_va) { + icnss_pr_err("Invalid MSA address\n"); + ret = -EINVAL; + goto fail; + } + + ret = wlfw_msa_mem_info_send_sync_msg(priv); + if (ret < 0) { + ignore_assert = true; + goto fail; + } + + ret = wlfw_msa_ready_send_sync_msg(priv); + if (ret < 0) { + ignore_assert = true; + goto fail; + } + } + + ret = wlfw_cap_send_sync_msg(priv); + if (ret < 0) { + ignore_assert = true; + goto fail; + } + + ret = icnss_hw_power_on(priv); + if (ret) + goto fail; + + if (priv->device_id == WCN6750_DEVICE_ID) { + ret = wlfw_device_info_send_msg(priv); + if (ret < 0) { + ignore_assert = true; + goto device_info_failure; + } + + priv->mem_base_va = devm_ioremap(&priv->pdev->dev, + priv->mem_base_pa, + priv->mem_base_size); + if (!priv->mem_base_va) { + icnss_pr_err("Ioremap failed for bar address\n"); + goto device_info_failure; + } + + icnss_pr_dbg("Non-Secured Bar Address pa: %pa, va: 0x%pK\n", + &priv->mem_base_pa, + priv->mem_base_va); + + if (priv->mhi_state_info_pa) + priv->mhi_state_info_va = devm_ioremap(&priv->pdev->dev, + priv->mhi_state_info_pa, + PAGE_SIZE); + if (!priv->mhi_state_info_va) + icnss_pr_err("Ioremap failed for MHI info address\n"); + + icnss_pr_dbg("MHI state info Address pa: %pa, va: 0x%pK\n", + &priv->mhi_state_info_pa, + priv->mhi_state_info_va); + } + + if (priv->bdf_download_support) { + icnss_wlfw_bdf_dnld_send_sync(priv, ICNSS_BDF_REGDB); + + ret = icnss_wlfw_bdf_dnld_send_sync(priv, + priv->ctrl_params.bdf_type); + if (ret < 0) + goto device_info_failure; + } + + if (priv->device_id == WCN6750_DEVICE_ID) { + if (!priv->fw_soc_wake_ack_irq) + register_soc_wake_notif(&priv->pdev->dev); + + icnss_get_smp2p_info(priv, ICNSS_SMP2P_OUT_POWER_SAVE); + icnss_get_smp2p_info(priv, ICNSS_SMP2P_OUT_SOC_WAKE); + icnss_get_smp2p_info(priv, ICNSS_SMP2P_OUT_EP_POWER_SAVE); + } + + if (priv->device_id == ADRASTEA_DEVICE_ID) { + if (priv->bdf_download_support) { + ret = wlfw_cal_report_req(priv); + if (ret < 0) + goto device_info_failure; + } + + wlfw_dynamic_feature_mask_send_sync_msg(priv, + dynamic_feature_mask); + } + + if (!priv->fw_error_fatal_irq) + register_fw_error_notifications(&priv->pdev->dev); + + if (!priv->fw_early_crash_irq) + register_early_crash_notifications(&priv->pdev->dev); + + if (priv->vbatt_supported) + icnss_init_vph_monitor(priv); + + return ret; + +device_info_failure: + icnss_hw_power_off(priv); +fail: + ICNSS_ASSERT(ignore_assert); +qmi_registered: + return ret; +} + +static int icnss_driver_event_server_exit(struct icnss_priv *priv) +{ + if (!priv) + return -ENODEV; + + icnss_pr_info("WLAN FW Service Disconnected: 0x%lx\n", priv->state); + + icnss_clear_server(priv); + + if (priv->adc_tm_dev && priv->vbatt_supported) + adc_tm_disable_chan_meas(priv->adc_tm_dev, + &priv->vph_monitor_params); + + return 0; +} + +static int icnss_call_driver_probe(struct icnss_priv *priv) +{ + int ret = 0; + int probe_cnt = 0; + + if (!priv->ops || !priv->ops->probe) + return 0; + + if (test_bit(ICNSS_DRIVER_PROBED, &priv->state)) + return -EINVAL; + + icnss_pr_dbg("Calling driver probe state: 0x%lx\n", priv->state); + + icnss_hw_power_on(priv); + + icnss_block_shutdown(true); + while (probe_cnt < ICNSS_MAX_PROBE_CNT) { + ret = priv->ops->probe(&priv->pdev->dev); + probe_cnt++; + if (ret != -EPROBE_DEFER) + break; + } + if (ret < 0) { + icnss_pr_err("Driver probe failed: %d, state: 0x%lx, probe_cnt: %d\n", + ret, priv->state, probe_cnt); + icnss_block_shutdown(false); + goto out; + } + + icnss_block_shutdown(false); + set_bit(ICNSS_DRIVER_PROBED, &priv->state); + + return 0; + +out: + icnss_hw_power_off(priv); + return ret; +} + +static int icnss_call_driver_shutdown(struct icnss_priv *priv) +{ + if (!test_bit(ICNSS_DRIVER_PROBED, &priv->state)) + goto out; + + if (!priv->ops || !priv->ops->shutdown) + goto out; + + if (test_bit(ICNSS_SHUTDOWN_DONE, &priv->state)) + goto out; + + icnss_pr_dbg("Calling driver shutdown state: 0x%lx\n", priv->state); + + priv->ops->shutdown(&priv->pdev->dev); + set_bit(ICNSS_SHUTDOWN_DONE, &priv->state); + +out: + return 0; +} + +static int icnss_pd_restart_complete(struct icnss_priv *priv) +{ + int ret = 0; + + icnss_pm_relax(priv); + + icnss_call_driver_shutdown(priv); + + clear_bit(ICNSS_PDR, &priv->state); + clear_bit(ICNSS_REJUVENATE, &priv->state); + clear_bit(ICNSS_PD_RESTART, &priv->state); + priv->early_crash_ind = false; + priv->is_ssr = false; + + if (!priv->ops || !priv->ops->reinit) + goto out; + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) { + icnss_pr_err("FW is in bad state, state: 0x%lx\n", + priv->state); + goto out; + } + + if (!test_bit(ICNSS_DRIVER_PROBED, &priv->state)) + goto call_probe; + + icnss_pr_dbg("Calling driver reinit state: 0x%lx\n", priv->state); + + icnss_hw_power_on(priv); + + icnss_block_shutdown(true); + + ret = priv->ops->reinit(&priv->pdev->dev); + if (ret < 0) { + icnss_fatal_err("Driver reinit failed: %d, state: 0x%lx\n", + ret, priv->state); + if (!priv->allow_recursive_recovery) + ICNSS_ASSERT(false); + icnss_block_shutdown(false); + goto out_power_off; + } + + icnss_block_shutdown(false); + clear_bit(ICNSS_SHUTDOWN_DONE, &priv->state); + return 0; + +call_probe: + return icnss_call_driver_probe(priv); + +out_power_off: + icnss_hw_power_off(priv); + +out: + return ret; +} + + +static int icnss_driver_event_fw_ready_ind(struct icnss_priv *priv, void *data) +{ + int ret = 0; + + if (!priv) + return -ENODEV; + + set_bit(ICNSS_FW_READY, &priv->state); + clear_bit(ICNSS_MODE_ON, &priv->state); + atomic_set(&priv->soc_wake_ref_count, 0); + + if (priv->device_id == WCN6750_DEVICE_ID) + icnss_free_qdss_mem(priv); + + icnss_pr_info("WLAN FW is ready: 0x%lx\n", priv->state); + + icnss_hw_power_off(priv); + + if (!priv->pdev) { + icnss_pr_err("Device is not ready\n"); + ret = -ENODEV; + goto out; + } + + if (test_bit(ICNSS_PD_RESTART, &priv->state)) { + ret = icnss_pd_restart_complete(priv); + } else { + if (priv->device_id == WCN6750_DEVICE_ID) + icnss_setup_dms_mac(priv); + ret = icnss_call_driver_probe(priv); + } + + icnss_vreg_unvote(priv); + +out: + return ret; +} + +static int icnss_driver_event_fw_init_done(struct icnss_priv *priv, void *data) +{ + int ret = 0; + + if (!priv) + return -ENODEV; + + icnss_pr_info("WLAN FW Initialization done: 0x%lx\n", priv->state); + + if (icnss_wlfw_qdss_dnld_send_sync(priv)) + icnss_pr_info("Failed to download qdss configuration file"); + + if (test_bit(ICNSS_COLD_BOOT_CAL, &priv->state)) + ret = wlfw_wlan_mode_send_sync_msg(priv, + (enum wlfw_driver_mode_enum_v01)ICNSS_CALIBRATION); + else + icnss_driver_event_fw_ready_ind(priv, NULL); + + return ret; +} + +int icnss_alloc_qdss_mem(struct icnss_priv *priv) +{ + struct platform_device *pdev = priv->pdev; + struct icnss_fw_mem *qdss_mem = priv->qdss_mem; + int i, j; + + for (i = 0; i < priv->qdss_mem_seg_len; i++) { + if (!qdss_mem[i].va && qdss_mem[i].size) { + qdss_mem[i].va = + dma_alloc_coherent(&pdev->dev, + qdss_mem[i].size, + &qdss_mem[i].pa, + GFP_KERNEL); + if (!qdss_mem[i].va) { + icnss_pr_err("Failed to allocate QDSS memory for FW, size: 0x%zx, type: %u, chuck-ID: %d\n", + qdss_mem[i].size, + qdss_mem[i].type, i); + break; + } + } + } + + /* Best-effort allocation for QDSS trace */ + if (i < priv->qdss_mem_seg_len) { + for (j = i; j < priv->qdss_mem_seg_len; j++) { + qdss_mem[j].type = 0; + qdss_mem[j].size = 0; + } + priv->qdss_mem_seg_len = i; + } + + return 0; +} + +void icnss_free_qdss_mem(struct icnss_priv *priv) +{ + struct platform_device *pdev = priv->pdev; + struct icnss_fw_mem *qdss_mem = priv->qdss_mem; + int i; + + for (i = 0; i < priv->qdss_mem_seg_len; i++) { + if (qdss_mem[i].va && qdss_mem[i].size) { + icnss_pr_dbg("Freeing memory for QDSS: pa: %pa, size: 0x%zx, type: %u\n", + &qdss_mem[i].pa, qdss_mem[i].size, + qdss_mem[i].type); + dma_free_coherent(&pdev->dev, + qdss_mem[i].size, qdss_mem[i].va, + qdss_mem[i].pa); + qdss_mem[i].va = NULL; + qdss_mem[i].pa = 0; + qdss_mem[i].size = 0; + qdss_mem[i].type = 0; + } + } + priv->qdss_mem_seg_len = 0; +} + +static int icnss_qdss_trace_req_mem_hdlr(struct icnss_priv *priv) +{ + int ret = 0; + + ret = icnss_alloc_qdss_mem(priv); + if (ret < 0) + return ret; + + return wlfw_qdss_trace_mem_info_send_sync(priv); +} + +static void *icnss_qdss_trace_pa_to_va(struct icnss_priv *priv, + u64 pa, u32 size, int *seg_id) +{ + int i = 0; + struct icnss_fw_mem *qdss_mem = priv->qdss_mem; + u64 offset = 0; + void *va = NULL; + u64 local_pa; + u32 local_size; + + for (i = 0; i < 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 int icnss_qdss_trace_save_hdlr(struct icnss_priv *priv, + void *data) +{ + struct icnss_qmi_event_qdss_trace_save_data *event_data = data; + struct icnss_fw_mem *qdss_mem = priv->qdss_mem; + int ret = 0; + int i; + void *va = NULL; + u64 pa; + u32 size; + int seg_id = 0; + + if (!priv->qdss_mem_seg_len) { + icnss_pr_err("Memory for QDSS trace is not available\n"); + return -ENOMEM; + } + + if (event_data->mem_seg_len == 0) { + for (i = 0; i < priv->qdss_mem_seg_len; i++) { + ret = icnss_genl_send_msg(qdss_mem[i].va, + ICNSS_GENL_MSG_TYPE_QDSS, + event_data->file_name, + qdss_mem[i].size); + if (ret < 0) { + icnss_pr_err("Fail to save QDSS data: %d\n", + ret); + break; + } + } + } else { + for (i = 0; i < event_data->mem_seg_len; i++) { + pa = event_data->mem_seg[i].addr; + size = event_data->mem_seg[i].size; + va = icnss_qdss_trace_pa_to_va(priv, pa, + size, &seg_id); + if (!va) { + icnss_pr_err("Fail to find matching va for pa %pa\n", + &pa); + ret = -EINVAL; + break; + } + ret = icnss_genl_send_msg(va, ICNSS_GENL_MSG_TYPE_QDSS, + event_data->file_name, size); + if (ret < 0) { + icnss_pr_err("Fail to save QDSS data: %d\n", + ret); + break; + } + } + } + + kfree(data); + return ret; +} + +static inline int icnss_atomic_dec_if_greater_one(atomic_t *v) +{ + int dec, c = atomic_read(v); + + do { + dec = c - 1; + if (unlikely(dec < 1)) + break; + } while (!atomic_try_cmpxchg(v, &c, dec)); + + return dec; +} + +static int icnss_qdss_trace_req_data_hdlr(struct icnss_priv *priv, + void *data) +{ + int ret = 0; + struct icnss_qmi_event_qdss_trace_save_data *event_data = data; + + if (!priv) + return -ENODEV; + + if (!data) + return -EINVAL; + + ret = icnss_wlfw_qdss_data_send_sync(priv, event_data->file_name, + event_data->total_size); + + kfree(data); + return ret; +} + +static int icnss_event_soc_wake_request(struct icnss_priv *priv, void *data) +{ + int ret = 0; + + if (!priv) + return -ENODEV; + + if (atomic_inc_not_zero(&priv->soc_wake_ref_count)) { + icnss_pr_soc_wake("SOC awake after posting work, Ref count: %d", + atomic_read(&priv->soc_wake_ref_count)); + return 0; + } + + ret = icnss_send_smp2p(priv, ICNSS_SOC_WAKE_REQ, + ICNSS_SMP2P_OUT_SOC_WAKE); + if (!ret) + atomic_inc(&priv->soc_wake_ref_count); + + return ret; +} + +static int icnss_event_soc_wake_release(struct icnss_priv *priv, void *data) +{ + int ret = 0; + + if (!priv) + return -ENODEV; + + if (atomic_dec_if_positive(&priv->soc_wake_ref_count)) { + icnss_pr_soc_wake("Wake release not called. Ref count: %d", + priv->soc_wake_ref_count); + return 0; + } + + ret = icnss_send_smp2p(priv, ICNSS_SOC_WAKE_REL, + ICNSS_SMP2P_OUT_SOC_WAKE); + return ret; +} + +static int icnss_driver_event_register_driver(struct icnss_priv *priv, + void *data) +{ + int ret = 0; + int probe_cnt = 0; + + if (priv->ops) + return -EEXIST; + + priv->ops = data; + + if (test_bit(SKIP_QMI, &priv->ctrl_params.quirks)) + set_bit(ICNSS_FW_READY, &priv->state); + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) { + icnss_pr_err("FW is in bad state, state: 0x%lx\n", + priv->state); + return -ENODEV; + } + + if (!test_bit(ICNSS_FW_READY, &priv->state)) { + icnss_pr_dbg("FW is not ready yet, state: 0x%lx\n", + priv->state); + goto out; + } + + ret = icnss_hw_power_on(priv); + if (ret) + goto out; + + icnss_block_shutdown(true); + while (probe_cnt < ICNSS_MAX_PROBE_CNT) { + ret = priv->ops->probe(&priv->pdev->dev); + probe_cnt++; + if (ret != -EPROBE_DEFER) + break; + } + if (ret) { + icnss_pr_err("Driver probe failed: %d, state: 0x%lx, probe_cnt: %d\n", + ret, priv->state, probe_cnt); + icnss_block_shutdown(false); + goto power_off; + } + + icnss_block_shutdown(false); + set_bit(ICNSS_DRIVER_PROBED, &priv->state); + + return 0; + +power_off: + icnss_hw_power_off(priv); +out: + return ret; +} + +static int icnss_driver_event_unregister_driver(struct icnss_priv *priv, + void *data) +{ + if (!test_bit(ICNSS_DRIVER_PROBED, &priv->state)) { + priv->ops = NULL; + goto out; + } + + set_bit(ICNSS_DRIVER_UNLOADING, &priv->state); + + icnss_block_shutdown(true); + + if (priv->ops) + priv->ops->remove(&priv->pdev->dev); + + icnss_block_shutdown(false); + + clear_bit(ICNSS_DRIVER_UNLOADING, &priv->state); + clear_bit(ICNSS_DRIVER_PROBED, &priv->state); + + priv->ops = NULL; + + icnss_hw_power_off(priv); + +out: + return 0; +} + +static int icnss_fw_crashed(struct icnss_priv *priv, + struct icnss_event_pd_service_down_data *event_data) +{ + icnss_pr_dbg("FW crashed, state: 0x%lx\n", priv->state); + + set_bit(ICNSS_PD_RESTART, &priv->state); + clear_bit(ICNSS_FW_READY, &priv->state); + + icnss_pm_stay_awake(priv); + + if (test_bit(ICNSS_DRIVER_PROBED, &priv->state)) + icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_CRASHED, NULL); + + if (event_data && event_data->fw_rejuvenate) + wlfw_rejuvenate_ack_send_sync_msg(priv); + + return 0; +} + +int icnss_update_hang_event_data(struct icnss_priv *priv, + struct icnss_uevent_hang_data *hang_data) +{ + if (!priv->hang_event_data_va) + return -EINVAL; + + priv->hang_event_data = kmemdup(priv->hang_event_data_va, + priv->hang_event_data_len, + GFP_ATOMIC); + if (!priv->hang_event_data) + return -ENOMEM; + + // Update the hang event params + hang_data->hang_event_data = priv->hang_event_data; + hang_data->hang_event_data_len = priv->hang_event_data_len; + + return 0; +} + +int icnss_send_hang_event_data(struct icnss_priv *priv) +{ + struct icnss_uevent_hang_data hang_data = {0}; + int ret = 0xFF; + + if (priv->early_crash_ind) { + ret = icnss_update_hang_event_data(priv, &hang_data); + if (ret) + icnss_pr_err("Unable to allocate memory for Hang event data\n"); + } + icnss_call_driver_uevent(priv, ICNSS_UEVENT_HANG_DATA, + &hang_data); + + if (!ret) { + kfree(priv->hang_event_data); + priv->hang_event_data = NULL; + } + + return 0; +} + +static int icnss_driver_event_pd_service_down(struct icnss_priv *priv, + void *data) +{ + struct icnss_event_pd_service_down_data *event_data = data; + + if (!test_bit(ICNSS_WLFW_EXISTS, &priv->state)) { + icnss_ignore_fw_timeout(false); + goto out; + } + + if (priv->force_err_fatal) + ICNSS_ASSERT(0); + + if (priv->device_id == WCN6750_DEVICE_ID) { + icnss_send_smp2p(priv, ICNSS_RESET_MSG, + ICNSS_SMP2P_OUT_POWER_SAVE); + icnss_send_smp2p(priv, ICNSS_RESET_MSG, + ICNSS_SMP2P_OUT_SOC_WAKE); + icnss_send_smp2p(priv, ICNSS_RESET_MSG, + ICNSS_SMP2P_OUT_EP_POWER_SAVE); + } + + icnss_send_hang_event_data(priv); + + if (priv->early_crash_ind) { + icnss_pr_dbg("PD Down ignored as early indication is processed: %d, state: 0x%lx\n", + event_data->crashed, priv->state); + goto out; + } + + if (test_bit(ICNSS_PD_RESTART, &priv->state) && event_data->crashed) { + icnss_fatal_err("PD Down while recovery inprogress, crashed: %d, state: 0x%lx\n", + event_data->crashed, priv->state); + if (!priv->allow_recursive_recovery) + ICNSS_ASSERT(0); + goto out; + } + + if (!test_bit(ICNSS_PD_RESTART, &priv->state)) + icnss_fw_crashed(priv, event_data); + +out: + kfree(data); + + return 0; +} + +static int icnss_driver_event_early_crash_ind(struct icnss_priv *priv, + void *data) +{ + if (!test_bit(ICNSS_WLFW_EXISTS, &priv->state)) { + icnss_ignore_fw_timeout(false); + goto out; + } + + priv->early_crash_ind = true; + icnss_fw_crashed(priv, NULL); + +out: + kfree(data); + + return 0; +} + +static int icnss_driver_event_idle_shutdown(struct icnss_priv *priv, + void *data) +{ + int ret = 0; + + if (!priv->ops || !priv->ops->idle_shutdown) + return 0; + + if (priv->is_ssr || test_bit(ICNSS_PDR, &priv->state) || + test_bit(ICNSS_REJUVENATE, &priv->state)) { + icnss_pr_err("SSR/PDR is already in-progress during idle shutdown callback\n"); + ret = -EBUSY; + } else { + icnss_pr_dbg("Calling driver idle shutdown, state: 0x%lx\n", + priv->state); + icnss_block_shutdown(true); + ret = priv->ops->idle_shutdown(&priv->pdev->dev); + icnss_block_shutdown(false); + } + + return ret; +} + +static int icnss_driver_event_idle_restart(struct icnss_priv *priv, + void *data) +{ + int ret = 0; + + if (!priv->ops || !priv->ops->idle_restart) + return 0; + + if (priv->is_ssr || test_bit(ICNSS_PDR, &priv->state) || + test_bit(ICNSS_REJUVENATE, &priv->state)) { + icnss_pr_err("SSR/PDR is already in-progress during idle restart callback\n"); + ret = -EBUSY; + } else { + icnss_pr_dbg("Calling driver idle restart, state: 0x%lx\n", + priv->state); + icnss_block_shutdown(true); + ret = priv->ops->idle_restart(&priv->pdev->dev); + icnss_block_shutdown(false); + } + + return ret; +} + +static int icnss_qdss_trace_free_hdlr(struct icnss_priv *priv) +{ + icnss_free_qdss_mem(priv); + + return 0; +} + +static int icnss_m3_dump_upload_req_hdlr(struct icnss_priv *priv, + void *data) +{ + struct icnss_m3_upload_segments_req_data *event_data = data; + struct qcom_dump_segment segment; + int i, status = 0, ret = 0; + struct list_head head; + + if (!dump_enabled()) { + icnss_pr_info("Dump collection is not enabled\n"); + return ret; + } + + INIT_LIST_HEAD(&head); + + for (i = 0; i < event_data->no_of_valid_segments; i++) { + memset(&segment, 0, sizeof(segment)); + + segment.va = devm_ioremap(&priv->pdev->dev, + event_data->m3_segment[i].addr, + event_data->m3_segment[i].size); + if (!segment.va) { + icnss_pr_err("Failed to ioremap M3 Dump region"); + ret = -ENOMEM; + goto send_resp; + } + + segment.size = event_data->m3_segment[i].size; + + list_add(&segment.node, &head); + icnss_pr_dbg("Started Dump colletcion for %s segment", + event_data->m3_segment[i].name); + + switch (event_data->m3_segment[i].type) { + case QMI_M3_SEGMENT_PHYAREG_V01: + ret = qcom_dump(&head, priv->m3_dump_phyareg->dev); + break; + case QMI_M3_SEGMENT_PHYDBG_V01: + ret = qcom_dump(&head, priv->m3_dump_phydbg->dev); + break; + case QMI_M3_SEGMENT_WMAC0_REG_V01: + ret = qcom_dump(&head, priv->m3_dump_wmac0reg->dev); + break; + case QMI_M3_SEGMENT_WCSSDBG_V01: + ret = qcom_dump(&head, priv->m3_dump_wcssdbg->dev); + break; + case QMI_M3_SEGMENT_PHYAPDMEM_V01: + ret = qcom_dump(&head, priv->m3_dump_phyapdmem->dev); + break; + default: + icnss_pr_err("Invalid Segment type: %d", + event_data->m3_segment[i].type); + } + + if (ret) { + status = ret; + icnss_pr_err("Failed to dump m3 %s segment, err = %d\n", + event_data->m3_segment[i].name, ret); + } + list_del(&segment.node); + } +send_resp: + icnss_wlfw_m3_dump_upload_done_send_sync(priv, event_data->pdev_id, + status); + + return ret; +} + +static int icnss_subsys_restart_level(struct icnss_priv *priv, void *data) +{ + int ret = 0; + struct icnss_subsys_restart_level_data *event_data = data; + + if (!priv) + return -ENODEV; + + if (!data) + return -EINVAL; + + ret = wlfw_subsys_restart_level_msg(priv, event_data->restart_level); + + kfree(data); + + return ret; +} + +static void icnss_driver_event_work(struct work_struct *work) +{ + struct icnss_priv *priv = + container_of(work, struct icnss_priv, event_work); + struct icnss_driver_event *event; + unsigned long flags; + int ret; + + icnss_pm_stay_awake(priv); + + spin_lock_irqsave(&priv->event_lock, flags); + + while (!list_empty(&priv->event_list)) { + event = list_first_entry(&priv->event_list, + struct icnss_driver_event, list); + list_del(&event->list); + spin_unlock_irqrestore(&priv->event_lock, flags); + + icnss_pr_dbg("Processing event: %s%s(%d), state: 0x%lx\n", + icnss_driver_event_to_str(event->type), + event->sync ? "-sync" : "", event->type, + priv->state); + + switch (event->type) { + case ICNSS_DRIVER_EVENT_SERVER_ARRIVE: + ret = icnss_driver_event_server_arrive(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_SERVER_EXIT: + ret = icnss_driver_event_server_exit(priv); + break; + case ICNSS_DRIVER_EVENT_FW_READY_IND: + ret = icnss_driver_event_fw_ready_ind(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_REGISTER_DRIVER: + ret = icnss_driver_event_register_driver(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_UNREGISTER_DRIVER: + ret = icnss_driver_event_unregister_driver(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN: + ret = icnss_driver_event_pd_service_down(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_FW_EARLY_CRASH_IND: + ret = icnss_driver_event_early_crash_ind(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_IDLE_SHUTDOWN: + ret = icnss_driver_event_idle_shutdown(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_IDLE_RESTART: + ret = icnss_driver_event_idle_restart(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_FW_INIT_DONE_IND: + ret = icnss_driver_event_fw_init_done(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_QDSS_TRACE_REQ_MEM: + ret = icnss_qdss_trace_req_mem_hdlr(priv); + break; + case ICNSS_DRIVER_EVENT_QDSS_TRACE_SAVE: + ret = icnss_qdss_trace_save_hdlr(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_QDSS_TRACE_FREE: + ret = icnss_qdss_trace_free_hdlr(priv); + break; + case ICNSS_DRIVER_EVENT_M3_DUMP_UPLOAD_REQ: + ret = icnss_m3_dump_upload_req_hdlr(priv, event->data); + break; + case ICNSS_DRIVER_EVENT_QDSS_TRACE_REQ_DATA: + ret = icnss_qdss_trace_req_data_hdlr(priv, + event->data); + break; + case ICNSS_DRIVER_EVENT_SUBSYS_RESTART_LEVEL: + ret = icnss_subsys_restart_level(priv, event->data); + break; + default: + icnss_pr_err("Invalid Event type: %d", event->type); + kfree(event); + continue; + } + + priv->stats.events[event->type].processed++; + + icnss_pr_dbg("Event Processed: %s%s(%d), ret: %d, state: 0x%lx\n", + icnss_driver_event_to_str(event->type), + event->sync ? "-sync" : "", event->type, ret, + priv->state); + + spin_lock_irqsave(&priv->event_lock, flags); + if (event->sync) { + event->ret = ret; + complete(&event->complete); + continue; + } + spin_unlock_irqrestore(&priv->event_lock, flags); + + kfree(event); + + spin_lock_irqsave(&priv->event_lock, flags); + } + spin_unlock_irqrestore(&priv->event_lock, flags); + + icnss_pm_relax(priv); +} + +static void icnss_soc_wake_msg_work(struct work_struct *work) +{ + struct icnss_priv *priv = + container_of(work, struct icnss_priv, soc_wake_msg_work); + struct icnss_soc_wake_event *event; + unsigned long flags; + int ret; + + icnss_pm_stay_awake(priv); + + spin_lock_irqsave(&priv->soc_wake_msg_lock, flags); + + while (!list_empty(&priv->soc_wake_msg_list)) { + event = list_first_entry(&priv->soc_wake_msg_list, + struct icnss_soc_wake_event, list); + list_del(&event->list); + spin_unlock_irqrestore(&priv->soc_wake_msg_lock, flags); + + icnss_pr_soc_wake("Processing event: %s%s(%d), state: 0x%lx\n", + icnss_soc_wake_event_to_str(event->type), + event->sync ? "-sync" : "", event->type, + priv->state); + + switch (event->type) { + case ICNSS_SOC_WAKE_REQUEST_EVENT: + ret = icnss_event_soc_wake_request(priv, + event->data); + break; + case ICNSS_SOC_WAKE_RELEASE_EVENT: + ret = icnss_event_soc_wake_release(priv, + event->data); + break; + default: + icnss_pr_err("Invalid Event type: %d", event->type); + kfree(event); + continue; + } + + priv->stats.soc_wake_events[event->type].processed++; + + icnss_pr_soc_wake("Event Processed: %s%s(%d), ret: %d, state: 0x%lx\n", + icnss_soc_wake_event_to_str(event->type), + event->sync ? "-sync" : "", event->type, ret, + priv->state); + + spin_lock_irqsave(&priv->soc_wake_msg_lock, flags); + if (event->sync) { + event->ret = ret; + complete(&event->complete); + continue; + } + spin_unlock_irqrestore(&priv->soc_wake_msg_lock, flags); + + kfree(event); + + spin_lock_irqsave(&priv->soc_wake_msg_lock, flags); + } + spin_unlock_irqrestore(&priv->soc_wake_msg_lock, flags); + + icnss_pm_relax(priv); +} + +static int icnss_msa0_ramdump(struct icnss_priv *priv) +{ + int ret = 0; + struct qcom_dump_segment segment; + struct icnss_ramdump_info *msa0_dump_dev = priv->msa0_dump_dev; + struct list_head head; + + if (!dump_enabled()) { + icnss_pr_info("Dump collection is not enabled\n"); + return ret; + } + + INIT_LIST_HEAD(&head); + + memset(&segment, 0, sizeof(segment)); + + segment.va = priv->msa_va; + segment.size = priv->msa_mem_size; + + list_add(&segment.node, &head); + + if (!msa0_dump_dev->dev) { + icnss_pr_err("Created Dump Device not found\n"); + return 0; + } + + ret = qcom_dump(&head, msa0_dump_dev->dev); + if (ret) { + icnss_pr_err("Failed to dump msa0, err = %d\n", ret); + return ret; + } + + list_del(&segment.node); + return ret; +} + +static void icnss_update_state_send_modem_shutdown(struct icnss_priv *priv, + void *data) +{ + struct qcom_ssr_notify_data *notif = data; + int ret = 0; + + if (!notif->crashed) { + if (atomic_read(&priv->is_shutdown)) { + atomic_set(&priv->is_shutdown, false); + if (!test_bit(ICNSS_PD_RESTART, &priv->state) && + !test_bit(ICNSS_SHUTDOWN_DONE, &priv->state) && + !test_bit(ICNSS_BLOCK_SHUTDOWN, &priv->state)) { + clear_bit(ICNSS_FW_READY, &priv->state); + icnss_driver_event_post(priv, + ICNSS_DRIVER_EVENT_UNREGISTER_DRIVER, + ICNSS_EVENT_SYNC_UNINTERRUPTIBLE, + NULL); + } + } + + if (test_bit(ICNSS_BLOCK_SHUTDOWN, &priv->state)) { + if (!wait_for_completion_timeout( + &priv->unblock_shutdown, + msecs_to_jiffies(PROBE_TIMEOUT))) + icnss_pr_err("modem block shutdown timeout\n"); + } + + ret = wlfw_send_modem_shutdown_msg(priv); + if (ret < 0) + icnss_pr_err("Fail to send modem shutdown Indication %d\n", + ret); + } +} + +static char *icnss_qcom_ssr_notify_state_to_str(enum qcom_ssr_notify_type code) +{ + switch (code) { + case QCOM_SSR_BEFORE_POWERUP: + return "BEFORE_POWERUP"; + case QCOM_SSR_AFTER_POWERUP: + return "AFTER_POWERUP"; + case QCOM_SSR_BEFORE_SHUTDOWN: + return "BEFORE_SHUTDOWN"; + case QCOM_SSR_AFTER_SHUTDOWN: + return "AFTER_SHUTDOWN"; + default: + return "UNKNOWN"; + } +}; + +static int icnss_wpss_notifier_nb(struct notifier_block *nb, + unsigned long code, + void *data) +{ + struct icnss_event_pd_service_down_data *event_data; + struct qcom_ssr_notify_data *notif = data; + struct icnss_priv *priv = container_of(nb, struct icnss_priv, + wpss_ssr_nb); + struct icnss_uevent_fw_down_data fw_down_data = {0}; + + icnss_pr_vdbg("WPSS-Notify: event %s(%lu)\n", + icnss_qcom_ssr_notify_state_to_str(code), code); + + if (code == QCOM_SSR_AFTER_SHUTDOWN) { + icnss_pr_info("Collecting msa0 segment dump\n"); + icnss_msa0_ramdump(priv); + goto out; + } + + if (code != QCOM_SSR_BEFORE_SHUTDOWN) + goto out; + + priv->is_ssr = true; + + icnss_pr_info("WPSS went down, state: 0x%lx, crashed: %d\n", + priv->state, notif->crashed); + + set_bit(ICNSS_FW_DOWN, &priv->state); + + if (notif->crashed) + priv->stats.recovery.root_pd_crash++; + else + priv->stats.recovery.root_pd_shutdown++; + + icnss_ignore_fw_timeout(true); + + event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); + + if (event_data == NULL) + return notifier_from_errno(-ENOMEM); + + event_data->crashed = notif->crashed; + + fw_down_data.crashed = !!notif->crashed; + if (test_bit(ICNSS_FW_READY, &priv->state)) { + clear_bit(ICNSS_FW_READY, &priv->state); + fw_down_data.crashed = !!notif->crashed; + icnss_call_driver_uevent(priv, + ICNSS_UEVENT_FW_DOWN, + &fw_down_data); + } + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN, + ICNSS_EVENT_SYNC, event_data); +out: + icnss_pr_vdbg("Exit %s,state: 0x%lx\n", __func__, priv->state); + return NOTIFY_OK; +} + +static int icnss_modem_notifier_nb(struct notifier_block *nb, + unsigned long code, + void *data) +{ + struct icnss_event_pd_service_down_data *event_data; + struct qcom_ssr_notify_data *notif = data; + struct icnss_priv *priv = container_of(nb, struct icnss_priv, + modem_ssr_nb); + struct icnss_uevent_fw_down_data fw_down_data = {0}; + + icnss_pr_vdbg("Modem-Notify: event %s(%lu)\n", + icnss_qcom_ssr_notify_state_to_str(code), code); + + if (code == QCOM_SSR_AFTER_SHUTDOWN) { + icnss_pr_info("Collecting msa0 segment dump\n"); + icnss_msa0_ramdump(priv); + goto out; + } + + if (code != QCOM_SSR_BEFORE_SHUTDOWN) + goto out; + + priv->is_ssr = true; + + if (notif->crashed) { + priv->stats.recovery.root_pd_crash++; + priv->root_pd_shutdown = false; + } else { + priv->stats.recovery.root_pd_shutdown++; + priv->root_pd_shutdown = true; + } + + icnss_update_state_send_modem_shutdown(priv, data); + + if (test_bit(ICNSS_PDR_REGISTERED, &priv->state)) { + set_bit(ICNSS_FW_DOWN, &priv->state); + icnss_ignore_fw_timeout(true); + + if (test_bit(ICNSS_FW_READY, &priv->state)) { + clear_bit(ICNSS_FW_READY, &priv->state); + fw_down_data.crashed = !!notif->crashed; + icnss_call_driver_uevent(priv, + ICNSS_UEVENT_FW_DOWN, + &fw_down_data); + } + goto out; + } + + icnss_pr_info("Modem went down, state: 0x%lx, crashed: %d\n", + priv->state, notif->crashed); + + set_bit(ICNSS_FW_DOWN, &priv->state); + + icnss_ignore_fw_timeout(true); + + event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); + + if (event_data == NULL) + return notifier_from_errno(-ENOMEM); + + event_data->crashed = notif->crashed; + + fw_down_data.crashed = !!notif->crashed; + if (test_bit(ICNSS_FW_READY, &priv->state)) { + clear_bit(ICNSS_FW_READY, &priv->state); + fw_down_data.crashed = !!notif->crashed; + icnss_call_driver_uevent(priv, + ICNSS_UEVENT_FW_DOWN, + &fw_down_data); + } + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN, + ICNSS_EVENT_SYNC, event_data); +out: + icnss_pr_vdbg("Exit %s,state: 0x%lx\n", __func__, priv->state); + return NOTIFY_OK; +} + +static int icnss_wpss_ssr_register_notifier(struct icnss_priv *priv) +{ + int ret = 0; + + priv->wpss_ssr_nb.notifier_call = icnss_wpss_notifier_nb; + /* + * Assign priority of icnss wpss notifier callback over IPA + * modem notifier callback which is 0 + */ + priv->wpss_ssr_nb.priority = 1; + + priv->wpss_notify_handler = + qcom_register_ssr_notifier("wpss", &priv->wpss_ssr_nb); + + if (IS_ERR(priv->wpss_notify_handler)) { + ret = PTR_ERR(priv->wpss_notify_handler); + icnss_pr_err("WPSS register notifier failed: %d\n", ret); + } + + set_bit(ICNSS_SSR_REGISTERED, &priv->state); + + return ret; +} + +static int icnss_modem_ssr_register_notifier(struct icnss_priv *priv) +{ + int ret = 0; + + priv->modem_ssr_nb.notifier_call = icnss_modem_notifier_nb; + /* + * Assign priority of icnss modem notifier callback over IPA + * modem notifier callback which is 0 + */ + priv->modem_ssr_nb.priority = 1; + + priv->modem_notify_handler = + qcom_register_ssr_notifier("modem", &priv->modem_ssr_nb); + + if (IS_ERR(priv->modem_notify_handler)) { + ret = PTR_ERR(priv->modem_notify_handler); + icnss_pr_err("Modem register notifier failed: %d\n", ret); + } + + set_bit(ICNSS_SSR_REGISTERED, &priv->state); + + return ret; +} + +static int icnss_wpss_ssr_unregister_notifier(struct icnss_priv *priv) +{ + if (!test_and_clear_bit(ICNSS_SSR_REGISTERED, &priv->state)) + return 0; + + qcom_unregister_ssr_notifier(priv->wpss_notify_handler, + &priv->wpss_ssr_nb); + priv->wpss_notify_handler = NULL; + + return 0; +} + +static int icnss_modem_ssr_unregister_notifier(struct icnss_priv *priv) +{ + if (!test_and_clear_bit(ICNSS_SSR_REGISTERED, &priv->state)) + return 0; + + qcom_unregister_ssr_notifier(priv->modem_notify_handler, + &priv->modem_ssr_nb); + priv->modem_notify_handler = NULL; + + return 0; +} + +static void icnss_pdr_notifier_cb(int state, char *service_path, void *priv_cb) +{ + struct icnss_priv *priv = priv_cb; + struct icnss_event_pd_service_down_data *event_data; + struct icnss_uevent_fw_down_data fw_down_data = {0}; + enum icnss_pdr_cause_index cause = ICNSS_ROOT_PD_CRASH; + + icnss_pr_dbg("PD service notification: 0x%lx state: 0x%lx\n", + state, priv->state); + + switch (state) { + case SERVREG_SERVICE_STATE_DOWN: + event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); + + if (!event_data) + return; + + event_data->crashed = true; + + if (!priv->is_ssr) { + set_bit(ICNSS_PDR, &penv->state); + if (test_bit(ICNSS_HOST_TRIGGERED_PDR, &priv->state)) { + cause = ICNSS_HOST_ERROR; + priv->stats.recovery.pdr_host_error++; + } else { + cause = ICNSS_FW_CRASH; + priv->stats.recovery.pdr_fw_crash++; + } + } else if (priv->root_pd_shutdown) { + cause = ICNSS_ROOT_PD_SHUTDOWN; + event_data->crashed = false; + } + + icnss_pr_info("PD service down, state: 0x%lx: cause: %s\n", + priv->state, icnss_pdr_cause[cause]); + + if (!test_bit(ICNSS_FW_DOWN, &priv->state)) { + set_bit(ICNSS_FW_DOWN, &priv->state); + icnss_ignore_fw_timeout(true); + + if (test_bit(ICNSS_FW_READY, &priv->state)) { + clear_bit(ICNSS_FW_READY, &priv->state); + fw_down_data.crashed = event_data->crashed; + icnss_call_driver_uevent(priv, + ICNSS_UEVENT_FW_DOWN, + &fw_down_data); + } + } + clear_bit(ICNSS_HOST_TRIGGERED_PDR, &priv->state); + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN, + ICNSS_EVENT_SYNC, event_data); + break; + case SERVREG_SERVICE_STATE_UP: + clear_bit(ICNSS_FW_DOWN, &priv->state); + break; + default: + break; + } + return; +} + +static int icnss_pd_restart_enable(struct icnss_priv *priv) +{ + struct pdr_handle *handle = NULL; + struct pdr_service *service = NULL; + int err = 0; + + handle = pdr_handle_alloc(icnss_pdr_notifier_cb, priv); + if (IS_ERR_OR_NULL(handle)) { + err = PTR_ERR(handle); + icnss_pr_err("Failed to alloc pdr handle, err %d", err); + goto out; + } + service = pdr_add_lookup(handle, ICNSS_WLAN_SERVICE_NAME, ICNSS_WLANPD_NAME); + if (IS_ERR_OR_NULL(service)) { + err = PTR_ERR(service); + icnss_pr_err("Failed to add lookup, err %d", err); + goto out; + } + priv->pdr_handle = handle; + priv->pdr_service = service; + set_bit(ICNSS_PDR_REGISTERED, &priv->state); + + icnss_pr_info("PDR registration happened"); +out: + return err; +} + +static void icnss_pdr_unregister_notifier(struct icnss_priv *priv) +{ + if (!test_and_clear_bit(ICNSS_PDR_REGISTERED, &priv->state)) + return; + + pdr_handle_release(priv->pdr_handle); +} + +static int icnss_ramdump_devnode_init(struct icnss_priv *priv) +{ + int ret = 0; + + priv->icnss_ramdump_class = class_create(THIS_MODULE, ICNSS_RAMDUMP_NAME); + if (IS_ERR_OR_NULL(priv->icnss_ramdump_class)) { + ret = PTR_ERR(priv->icnss_ramdump_class); + icnss_pr_err("%s:Class create failed for ramdump devices (%d)\n", __func__, ret); + return ret; + } + + ret = alloc_chrdev_region(&priv->icnss_ramdump_dev, 0, RAMDUMP_NUM_DEVICES, + ICNSS_RAMDUMP_NAME); + if (ret < 0) { + icnss_pr_err("%s: Unable to allocate major\n", __func__); + goto fail_alloc_major; + } + return 0; + +fail_alloc_major: + class_destroy(priv->icnss_ramdump_class); + return ret; +} + +void *icnss_create_ramdump_device(struct icnss_priv *priv, const char *dev_name) +{ + int ret = 0; + struct icnss_ramdump_info *ramdump_info; + + ramdump_info = kzalloc(sizeof(*ramdump_info), GFP_KERNEL); + + if (!dev_name) { + icnss_pr_err("%s: Invalid device name.\n", __func__); + return NULL; + } + + snprintf(ramdump_info->name, ARRAY_SIZE(ramdump_info->name), "icnss_%s", dev_name); + + ramdump_info->minor = ida_simple_get(&rd_minor_id, 0, RAMDUMP_NUM_DEVICES, GFP_KERNEL); + if (ramdump_info->minor < 0) { + icnss_pr_err("%s: No more minor numbers left! rc:%d\n", __func__, + ramdump_info->minor); + ret = -ENODEV; + goto fail_out_of_minors; + } + + ramdump_info->dev = device_create(priv->icnss_ramdump_class, NULL, + MKDEV(MAJOR(priv->icnss_ramdump_dev), + ramdump_info->minor), + ramdump_info, ramdump_info->name); + if (IS_ERR_OR_NULL(ramdump_info->dev)) { + ret = PTR_ERR(ramdump_info->dev); + icnss_pr_err("%s: Device create failed for %s (%d)\n", __func__, + ramdump_info->name, ret); + goto fail_device_create; + } + return (void *)ramdump_info; + +fail_device_create: + ida_simple_remove(&rd_minor_id, ramdump_info->minor); +fail_out_of_minors: + kfree(ramdump_info); + return ERR_PTR(ret); +} + +static int icnss_register_ramdump_devices(struct icnss_priv *priv) +{ + int ret = 0; + + if (!priv || !priv->pdev) { + icnss_pr_err("Platform priv or pdev is NULL\n"); + return -EINVAL; + } + + ret = icnss_ramdump_devnode_init(priv); + if (ret) + return ret; + + priv->msa0_dump_dev = icnss_create_ramdump_device(priv, "wcss_msa0"); + + if (!priv->msa0_dump_dev->dev) { + icnss_pr_err("Failed to create msa0 dump device!"); + return -ENOMEM; + } + + if (priv->device_id == WCN6750_DEVICE_ID) { + priv->m3_dump_phyareg = icnss_create_ramdump_device(priv, + ICNSS_M3_SEGMENT( + ICNSS_M3_SEGMENT_PHYAREG)); + + if (!priv->m3_dump_phyareg->dev) { + icnss_pr_err("Failed to create m3 dump for Phyareg segment device!"); + return -ENOMEM; + } + + priv->m3_dump_phydbg = icnss_create_ramdump_device(priv, + ICNSS_M3_SEGMENT( + ICNSS_M3_SEGMENT_PHYA)); + + if (!priv->m3_dump_phydbg->dev) { + icnss_pr_err("Failed to create m3 dump for Phydbg segment device!"); + return -ENOMEM; + } + + priv->m3_dump_wmac0reg = icnss_create_ramdump_device(priv, + ICNSS_M3_SEGMENT( + ICNSS_M3_SEGMENT_WMACREG)); + + if (!priv->m3_dump_wmac0reg->dev) { + icnss_pr_err("Failed to create m3 dump for Wmac0reg segment device!"); + return -ENOMEM; + } + + priv->m3_dump_wcssdbg = icnss_create_ramdump_device(priv, + ICNSS_M3_SEGMENT( + ICNSS_M3_SEGMENT_WCSSDBG)); + + if (!priv->m3_dump_wcssdbg->dev) { + icnss_pr_err("Failed to create m3 dump for Wcssdbg segment device!"); + return -ENOMEM; + } + + priv->m3_dump_phyapdmem = icnss_create_ramdump_device(priv, + ICNSS_M3_SEGMENT( + ICNSS_M3_SEGMENT_PHYAM3)); + + if (!priv->m3_dump_phyapdmem->dev) { + icnss_pr_err("Failed to create m3 dump for Phyapdmem segment device!"); + return -ENOMEM; + } + } + + return 0; +} + +static int icnss_enable_recovery(struct icnss_priv *priv) +{ + int ret; + + if (test_bit(RECOVERY_DISABLE, &priv->ctrl_params.quirks)) { + icnss_pr_dbg("Recovery disabled through module parameter\n"); + return 0; + } + + if (test_bit(PDR_ONLY, &priv->ctrl_params.quirks)) { + icnss_pr_dbg("SSR disabled through module parameter\n"); + goto enable_pdr; + } + + ret = icnss_register_ramdump_devices(priv); + if (ret) + return ret; + + if (priv->device_id == WCN6750_DEVICE_ID) { + icnss_wpss_ssr_register_notifier(priv); + return 0; + } + + icnss_modem_ssr_register_notifier(priv); + if (test_bit(SSR_ONLY, &priv->ctrl_params.quirks)) { + icnss_pr_dbg("PDR disabled through module parameter\n"); + return 0; + } + +enable_pdr: + ret = icnss_pd_restart_enable(priv); + + if (ret) + return ret; + + return 0; +} + +static int icnss_dev_id_match(struct icnss_priv *priv, + struct device_info *dev_info) +{ + if (!dev_info) { + icnss_pr_info("WLAN driver devinfo is null, Continue driver loading"); + return 1; + } + + while (dev_info->device_id) { + if (priv->device_id == dev_info->device_id) + return 1; + dev_info++; + } + return 0; +} + +static int icnss_tcdev_get_max_state(struct thermal_cooling_device *tcdev, + unsigned long *thermal_state) +{ + struct icnss_thermal_cdev *icnss_tcdev = tcdev->devdata; + + *thermal_state = icnss_tcdev->max_thermal_state; + + return 0; +} + +static int icnss_tcdev_get_cur_state(struct thermal_cooling_device *tcdev, + unsigned long *thermal_state) +{ + struct icnss_thermal_cdev *icnss_tcdev = tcdev->devdata; + + *thermal_state = icnss_tcdev->curr_thermal_state; + + return 0; +} + +static int icnss_tcdev_set_cur_state(struct thermal_cooling_device *tcdev, + unsigned long thermal_state) +{ + struct icnss_thermal_cdev *icnss_tcdev = tcdev->devdata; + struct device *dev = &penv->pdev->dev; + int ret = 0; + + + if (!penv->ops || !penv->ops->set_therm_cdev_state) + return 0; + + if (thermal_state > icnss_tcdev->max_thermal_state) + return -EINVAL; + + icnss_pr_vdbg("Cooling device set current state: %ld,for cdev id %d", + thermal_state, icnss_tcdev->tcdev_id); + + mutex_lock(&penv->tcdev_lock); + ret = penv->ops->set_therm_cdev_state(dev, thermal_state, + icnss_tcdev->tcdev_id); + if (!ret) + icnss_tcdev->curr_thermal_state = thermal_state; + mutex_unlock(&penv->tcdev_lock); + if (ret) { + icnss_pr_err("Setting Current Thermal State Failed: %d,for cdev id %d", + ret, icnss_tcdev->tcdev_id); + return ret; + } + + return 0; +} + +static struct thermal_cooling_device_ops icnss_cooling_ops = { + .get_max_state = icnss_tcdev_get_max_state, + .get_cur_state = icnss_tcdev_get_cur_state, + .set_cur_state = icnss_tcdev_set_cur_state, +}; + +int icnss_thermal_cdev_register(struct device *dev, unsigned long max_state, + int tcdev_id) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + struct icnss_thermal_cdev *icnss_tcdev = NULL; + char cdev_node_name[THERMAL_NAME_LENGTH] = ""; + struct device_node *dev_node; + int ret = 0; + + icnss_tcdev = kzalloc(sizeof(*icnss_tcdev), GFP_KERNEL); + if (!icnss_tcdev) + return -ENOMEM; + + icnss_tcdev->tcdev_id = tcdev_id; + icnss_tcdev->max_thermal_state = max_state; + + snprintf(cdev_node_name, THERMAL_NAME_LENGTH, + "qcom,icnss_cdev%d", tcdev_id); + + dev_node = of_find_node_by_name(NULL, cdev_node_name); + if (!dev_node) { + icnss_pr_err("Failed to get cooling device node\n"); + return -EINVAL; + } + + icnss_pr_dbg("tcdev node->name=%s\n", dev_node->name); + + if (of_find_property(dev_node, "#cooling-cells", NULL)) { + icnss_tcdev->tcdev = thermal_of_cooling_device_register( + dev_node, + cdev_node_name, icnss_tcdev, + &icnss_cooling_ops); + if (IS_ERR_OR_NULL(icnss_tcdev->tcdev)) { + ret = PTR_ERR(icnss_tcdev->tcdev); + icnss_pr_err("Cooling device register failed: %d, for cdev id %d\n", + ret, icnss_tcdev->tcdev_id); + } else { + icnss_pr_dbg("Cooling device registered for cdev id %d", + icnss_tcdev->tcdev_id); + list_add(&icnss_tcdev->tcdev_list, + &priv->icnss_tcdev_list); + } + } else { + icnss_pr_dbg("Cooling device registration not supported"); + ret = -EOPNOTSUPP; + } + + return ret; +} +EXPORT_SYMBOL(icnss_thermal_cdev_register); + +void icnss_thermal_cdev_unregister(struct device *dev, int tcdev_id) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + struct icnss_thermal_cdev *icnss_tcdev = NULL; + + while (!list_empty(&priv->icnss_tcdev_list)) { + icnss_tcdev = list_first_entry(&priv->icnss_tcdev_list, + struct icnss_thermal_cdev, + tcdev_list); + thermal_cooling_device_unregister(icnss_tcdev->tcdev); + list_del(&icnss_tcdev->tcdev_list); + kfree(icnss_tcdev); + } +} +EXPORT_SYMBOL(icnss_thermal_cdev_unregister); + +int icnss_get_curr_therm_cdev_state(struct device *dev, + unsigned long *thermal_state, + int tcdev_id) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + struct icnss_thermal_cdev *icnss_tcdev = NULL; + + mutex_lock(&priv->tcdev_lock); + list_for_each_entry(icnss_tcdev, &priv->icnss_tcdev_list, tcdev_list) { + if (icnss_tcdev->tcdev_id != tcdev_id) + continue; + + *thermal_state = icnss_tcdev->curr_thermal_state; + mutex_unlock(&priv->tcdev_lock); + icnss_pr_dbg("Cooling device current state: %ld, for cdev id %d", + icnss_tcdev->curr_thermal_state, tcdev_id); + return 0; + } + mutex_unlock(&priv->tcdev_lock); + icnss_pr_dbg("Cooling device ID not found: %d", tcdev_id); + return -EINVAL; +} +EXPORT_SYMBOL(icnss_get_curr_therm_cdev_state); + +int icnss_qmi_send(struct device *dev, int type, void *cmd, + int cmd_len, void *cb_ctx, + int (*cb)(void *ctx, void *event, int event_len)) +{ + struct icnss_priv *priv = icnss_get_plat_priv(); + int ret; + + if (!priv) + return -ENODEV; + + if (!test_bit(ICNSS_WLFW_CONNECTED, &priv->state)) + return -EINVAL; + + priv->get_info_cb = cb; + priv->get_info_cb_ctx = cb_ctx; + + ret = icnss_wlfw_get_info_send_sync(priv, type, cmd, cmd_len); + if (ret) { + priv->get_info_cb = NULL; + priv->get_info_cb_ctx = NULL; + } + + return ret; +} +EXPORT_SYMBOL(icnss_qmi_send); + +int __icnss_register_driver(struct icnss_driver_ops *ops, + struct module *owner, const char *mod_name) +{ + int ret = 0; + struct icnss_priv *priv = icnss_get_plat_priv(); + + if (!priv || !priv->pdev) { + ret = -ENODEV; + goto out; + } + + icnss_pr_dbg("Registering driver, state: 0x%lx\n", priv->state); + + if (priv->ops) { + icnss_pr_err("Driver already registered\n"); + ret = -EEXIST; + goto out; + } + + if (!icnss_dev_id_match(priv, ops->dev_info)) { + icnss_pr_err("WLAN driver dev name is %s, not supported by platform driver\n", + ops->dev_info->name); + return -ENODEV; + } + + if (!ops->probe || !ops->remove) { + ret = -EINVAL; + goto out; + } + + ret = icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_REGISTER_DRIVER, + 0, ops); + + if (ret == -EINTR) + ret = 0; + +out: + return ret; +} +EXPORT_SYMBOL(__icnss_register_driver); + +int icnss_unregister_driver(struct icnss_driver_ops *ops) +{ + int ret; + struct icnss_priv *priv = icnss_get_plat_priv(); + + if (!priv || !priv->pdev) { + ret = -ENODEV; + goto out; + } + + icnss_pr_dbg("Unregistering driver, state: 0x%lx\n", priv->state); + + if (!priv->ops) { + icnss_pr_err("Driver not registered\n"); + ret = -ENOENT; + goto out; + } + + ret = icnss_driver_event_post(priv, + ICNSS_DRIVER_EVENT_UNREGISTER_DRIVER, + ICNSS_EVENT_SYNC_UNINTERRUPTIBLE, NULL); +out: + return ret; +} +EXPORT_SYMBOL(icnss_unregister_driver); + +static struct icnss_msi_config msi_config = { + .total_vectors = 28, + .total_users = 2, + .users = (struct icnss_msi_user[]) { + { .name = "CE", .num_vectors = 10, .base_vector = 0 }, + { .name = "DP", .num_vectors = 18, .base_vector = 10 }, + }, +}; + +static int icnss_get_msi_assignment(struct icnss_priv *priv) +{ + priv->msi_config = &msi_config; + + return 0; +} + +int icnss_get_user_msi_assignment(struct device *dev, char *user_name, + int *num_vectors, u32 *user_base_data, + u32 *base_vector) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + struct icnss_msi_config *msi_config; + int idx; + + if (!priv) + return -ENODEV; + + msi_config = priv->msi_config; + if (!msi_config) { + icnss_pr_err("MSI is not supported.\n"); + return -EINVAL; + } + + for (idx = 0; idx < msi_config->total_users; idx++) { + if (strcmp(user_name, msi_config->users[idx].name) == 0) { + *num_vectors = msi_config->users[idx].num_vectors; + *user_base_data = msi_config->users[idx].base_vector + + priv->msi_base_data; + *base_vector = msi_config->users[idx].base_vector; + + icnss_pr_dbg("Assign MSI to user: %s, num_vectors: %d, user_base_data: %u, base_vector: %u\n", + user_name, *num_vectors, *user_base_data, + *base_vector); + + return 0; + } + } + + icnss_pr_err("Failed to find MSI assignment for %s!\n", user_name); + + return -EINVAL; +} +EXPORT_SYMBOL(icnss_get_user_msi_assignment); + +int icnss_get_msi_irq(struct device *dev, unsigned int vector) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int irq_num; + + irq_num = priv->srng_irqs[vector]; + icnss_pr_dbg("Get IRQ number %d for vector index %d\n", + irq_num, vector); + + return irq_num; +} +EXPORT_SYMBOL(icnss_get_msi_irq); + +void icnss_get_msi_address(struct device *dev, u32 *msi_addr_low, + u32 *msi_addr_high) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + *msi_addr_low = lower_32_bits(priv->msi_addr_iova); + *msi_addr_high = upper_32_bits(priv->msi_addr_iova); + +} +EXPORT_SYMBOL(icnss_get_msi_address); + +int icnss_ce_request_irq(struct device *dev, unsigned int ce_id, + irqreturn_t (*handler)(int, void *), + unsigned long flags, const char *name, void *ctx) +{ + int ret = 0; + unsigned int irq; + struct ce_irq_list *irq_entry; + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv || !priv->pdev) { + ret = -ENODEV; + goto out; + } + + icnss_pr_vdbg("CE request IRQ: %d, state: 0x%lx\n", ce_id, priv->state); + + if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { + icnss_pr_err("Invalid CE ID, ce_id: %d\n", ce_id); + ret = -EINVAL; + goto out; + } + irq = priv->ce_irqs[ce_id]; + irq_entry = &priv->ce_irq_list[ce_id]; + + if (irq_entry->handler || irq_entry->irq) { + icnss_pr_err("IRQ already requested: %d, ce_id: %d\n", + irq, ce_id); + ret = -EEXIST; + goto out; + } + + ret = request_irq(irq, handler, flags, name, ctx); + if (ret) { + icnss_pr_err("IRQ request failed: %d, ce_id: %d, ret: %d\n", + irq, ce_id, ret); + goto out; + } + irq_entry->irq = irq; + irq_entry->handler = handler; + + icnss_pr_vdbg("IRQ requested: %d, ce_id: %d\n", irq, ce_id); + + penv->stats.ce_irqs[ce_id].request++; +out: + return ret; +} +EXPORT_SYMBOL(icnss_ce_request_irq); + +int icnss_ce_free_irq(struct device *dev, unsigned int ce_id, void *ctx) +{ + int ret = 0; + unsigned int irq; + struct ce_irq_list *irq_entry; + + if (!penv || !penv->pdev || !dev) { + ret = -ENODEV; + goto out; + } + + icnss_pr_vdbg("CE free IRQ: %d, state: 0x%lx\n", ce_id, penv->state); + + if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { + icnss_pr_err("Invalid CE ID to free, ce_id: %d\n", ce_id); + ret = -EINVAL; + goto out; + } + + irq = penv->ce_irqs[ce_id]; + irq_entry = &penv->ce_irq_list[ce_id]; + if (!irq_entry->handler || !irq_entry->irq) { + icnss_pr_err("IRQ not requested: %d, ce_id: %d\n", irq, ce_id); + ret = -EEXIST; + goto out; + } + free_irq(irq, ctx); + irq_entry->irq = 0; + irq_entry->handler = NULL; + + penv->stats.ce_irqs[ce_id].free++; +out: + return ret; +} +EXPORT_SYMBOL(icnss_ce_free_irq); + +void icnss_enable_irq(struct device *dev, unsigned int ce_id) +{ + unsigned int irq; + + if (!penv || !penv->pdev || !dev) { + icnss_pr_err("Platform driver not initialized\n"); + return; + } + + icnss_pr_vdbg("Enable IRQ: ce_id: %d, state: 0x%lx\n", ce_id, + penv->state); + + if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { + icnss_pr_err("Invalid CE ID to enable IRQ, ce_id: %d\n", ce_id); + return; + } + + penv->stats.ce_irqs[ce_id].enable++; + + irq = penv->ce_irqs[ce_id]; + enable_irq(irq); +} +EXPORT_SYMBOL(icnss_enable_irq); + +void icnss_disable_irq(struct device *dev, unsigned int ce_id) +{ + unsigned int irq; + + if (!penv || !penv->pdev || !dev) { + icnss_pr_err("Platform driver not initialized\n"); + return; + } + + icnss_pr_vdbg("Disable IRQ: ce_id: %d, state: 0x%lx\n", ce_id, + penv->state); + + if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { + icnss_pr_err("Invalid CE ID to disable IRQ, ce_id: %d\n", + ce_id); + return; + } + + irq = penv->ce_irqs[ce_id]; + disable_irq(irq); + + penv->stats.ce_irqs[ce_id].disable++; +} +EXPORT_SYMBOL(icnss_disable_irq); + +int icnss_get_soc_info(struct device *dev, struct icnss_soc_info *info) +{ + char *fw_build_timestamp = NULL; + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Platform driver not initialized\n"); + return -EINVAL; + } + + info->v_addr = priv->mem_base_va; + info->p_addr = priv->mem_base_pa; + info->chip_id = priv->chip_info.chip_id; + info->chip_family = priv->chip_info.chip_family; + info->board_id = priv->board_id; + info->soc_id = priv->soc_id; + info->fw_version = priv->fw_version_info.fw_version; + fw_build_timestamp = priv->fw_version_info.fw_build_timestamp; + fw_build_timestamp[WLFW_MAX_TIMESTAMP_LEN] = '\0'; + strlcpy(info->fw_build_timestamp, + priv->fw_version_info.fw_build_timestamp, + WLFW_MAX_TIMESTAMP_LEN + 1); + + return 0; +} +EXPORT_SYMBOL(icnss_get_soc_info); + +int icnss_get_mhi_state(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Platform driver not initialized\n"); + return -EINVAL; + } + + if (!priv->mhi_state_info_va) + return -ENOMEM; + + return ioread32(priv->mhi_state_info_va); +} +EXPORT_SYMBOL(icnss_get_mhi_state); + +int icnss_set_fw_log_mode(struct device *dev, uint8_t fw_log_mode) +{ + int ret; + struct icnss_priv *priv; + + if (!dev) + return -ENODEV; + + priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Platform driver not initialized\n"); + return -EINVAL; + } + + if (test_bit(ICNSS_FW_DOWN, &penv->state) || + !test_bit(ICNSS_FW_READY, &penv->state)) { + icnss_pr_err("FW down, ignoring fw_log_mode state: 0x%lx\n", + priv->state); + return -EINVAL; + } + + icnss_pr_dbg("FW log mode: %u\n", fw_log_mode); + + ret = wlfw_ini_send_sync_msg(priv, fw_log_mode); + if (ret) + icnss_pr_err("Fail to send ini, ret = %d, fw_log_mode: %u\n", + ret, fw_log_mode); + return ret; +} +EXPORT_SYMBOL(icnss_set_fw_log_mode); + +int icnss_force_wake_request(struct device *dev) +{ + struct icnss_priv *priv; + + if (!dev) + return -ENODEV; + + priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Platform driver not initialized\n"); + return -EINVAL; + } + + if (atomic_inc_not_zero(&priv->soc_wake_ref_count)) { + icnss_pr_soc_wake("SOC already awake, Ref count: %d", + atomic_read(&priv->soc_wake_ref_count)); + return 0; + } + + icnss_pr_soc_wake("Calling SOC Wake request"); + + icnss_soc_wake_event_post(priv, ICNSS_SOC_WAKE_REQUEST_EVENT, + 0, NULL); + + return 0; +} +EXPORT_SYMBOL(icnss_force_wake_request); + +int icnss_force_wake_release(struct device *dev) +{ + struct icnss_priv *priv; + + if (!dev) + return -ENODEV; + + priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Platform driver not initialized\n"); + return -EINVAL; + } + + icnss_pr_soc_wake("Calling SOC Wake response"); + + if (atomic_read(&priv->soc_wake_ref_count) && + icnss_atomic_dec_if_greater_one(&priv->soc_wake_ref_count)) { + icnss_pr_soc_wake("SOC previous release pending, Ref count: %d", + atomic_read(&priv->soc_wake_ref_count)); + return 0; + } + + icnss_soc_wake_event_post(priv, ICNSS_SOC_WAKE_RELEASE_EVENT, + 0, NULL); + + return 0; +} +EXPORT_SYMBOL(icnss_force_wake_release); + +int icnss_is_device_awake(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Platform driver not initialized\n"); + return -EINVAL; + } + + return atomic_read(&priv->soc_wake_ref_count); +} +EXPORT_SYMBOL(icnss_is_device_awake); + +int icnss_is_pci_ep_awake(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Platform driver not initialized\n"); + return -EINVAL; + } + + if (!priv->mhi_state_info_va) + return -ENOMEM; + + return ioread32(priv->mhi_state_info_va + ICNSS_PCI_EP_WAKE_OFFSET); +} +EXPORT_SYMBOL(icnss_is_pci_ep_awake); + +int icnss_athdiag_read(struct device *dev, uint32_t offset, + uint32_t mem_type, uint32_t data_len, + uint8_t *output) +{ + int ret = 0; + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for diag read: dev %pK, data %pK, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + if (!output || data_len == 0 + || data_len > WLFW_MAX_DATA_SIZE) { + icnss_pr_err("Invalid parameters for diag read: output %pK, data_len %u\n", + output, data_len); + ret = -EINVAL; + goto out; + } + + if (!test_bit(ICNSS_FW_READY, &priv->state) || + !test_bit(ICNSS_POWER_ON, &priv->state)) { + icnss_pr_err("Invalid state for diag read: 0x%lx\n", + priv->state); + ret = -EINVAL; + goto out; + } + + ret = wlfw_athdiag_read_send_sync_msg(priv, offset, mem_type, + data_len, output); +out: + return ret; +} +EXPORT_SYMBOL(icnss_athdiag_read); + +int icnss_athdiag_write(struct device *dev, uint32_t offset, + uint32_t mem_type, uint32_t data_len, + uint8_t *input) +{ + int ret = 0; + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for diag write: dev %pK, data %pK, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + if (!input || data_len == 0 + || data_len > WLFW_MAX_DATA_SIZE) { + icnss_pr_err("Invalid parameters for diag write: input %pK, data_len %u\n", + input, data_len); + ret = -EINVAL; + goto out; + } + + if (!test_bit(ICNSS_FW_READY, &priv->state) || + !test_bit(ICNSS_POWER_ON, &priv->state)) { + icnss_pr_err("Invalid state for diag write: 0x%lx\n", + priv->state); + ret = -EINVAL; + goto out; + } + + ret = wlfw_athdiag_write_send_sync_msg(priv, offset, mem_type, + data_len, input); +out: + return ret; +} +EXPORT_SYMBOL(icnss_athdiag_write); + +int icnss_wlan_enable(struct device *dev, struct icnss_wlan_enable_cfg *config, + enum icnss_driver_mode mode, + const char *host_version) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (test_bit(ICNSS_FW_DOWN, &priv->state) || + !test_bit(ICNSS_FW_READY, &priv->state)) { + icnss_pr_err("FW down, ignoring wlan_enable state: 0x%lx\n", + priv->state); + return -EINVAL; + } + + if (test_bit(ICNSS_MODE_ON, &priv->state)) { + icnss_pr_err("Already Mode on, ignoring wlan_enable state: 0x%lx\n", + priv->state); + return -EINVAL; + } + + if (priv->device_id == WCN6750_DEVICE_ID && + !priv->dms.nv_mac_not_prov && !priv->dms.mac_valid) + icnss_setup_dms_mac(priv); + + return icnss_send_wlan_enable_to_fw(priv, config, mode, host_version); +} +EXPORT_SYMBOL(icnss_wlan_enable); + +int icnss_wlan_disable(struct device *dev, enum icnss_driver_mode mode) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) { + icnss_pr_dbg("FW down, ignoring wlan_disable state: 0x%lx\n", + priv->state); + return 0; + } + + return icnss_send_wlan_disable_to_fw(priv); +} +EXPORT_SYMBOL(icnss_wlan_disable); + +bool icnss_is_qmi_disable(struct device *dev) +{ + return test_bit(SKIP_QMI, &penv->ctrl_params.quirks) ? true : false; +} +EXPORT_SYMBOL(icnss_is_qmi_disable); + +int icnss_get_ce_id(struct device *dev, int irq) +{ + int i; + + if (!penv || !penv->pdev || !dev) + return -ENODEV; + + for (i = 0; i < ICNSS_MAX_IRQ_REGISTRATIONS; i++) { + if (penv->ce_irqs[i] == irq) + return i; + } + + icnss_pr_err("No matching CE id for irq %d\n", irq); + + return -EINVAL; +} +EXPORT_SYMBOL(icnss_get_ce_id); + +int icnss_get_irq(struct device *dev, int ce_id) +{ + int irq; + + if (!penv || !penv->pdev || !dev) + return -ENODEV; + + if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) + return -EINVAL; + + irq = penv->ce_irqs[ce_id]; + + return irq; +} +EXPORT_SYMBOL(icnss_get_irq); + +struct iommu_domain *icnss_smmu_get_domain(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Invalid drvdata: dev %pK\n", dev); + return NULL; + } + return priv->iommu_domain; +} +EXPORT_SYMBOL(icnss_smmu_get_domain); + +int icnss_smmu_map(struct device *dev, + phys_addr_t paddr, uint32_t *iova_addr, size_t size) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int flag = IOMMU_READ | IOMMU_WRITE; + bool dma_coherent = false; + unsigned long iova; + int prop_len = 0; + size_t len; + int ret = 0; + + if (!priv) { + icnss_pr_err("Invalid drvdata: dev %pK, data %pK\n", + dev, priv); + return -EINVAL; + } + + if (!iova_addr) { + icnss_pr_err("iova_addr is NULL, paddr %pa, size %zu\n", + &paddr, size); + return -EINVAL; + } + + len = roundup(size + paddr - rounddown(paddr, PAGE_SIZE), PAGE_SIZE); + iova = roundup(priv->smmu_iova_ipa_current, PAGE_SIZE); + + if (of_get_property(dev->of_node, "qcom,iommu-geometry", &prop_len) && + iova >= priv->smmu_iova_ipa_start + priv->smmu_iova_ipa_len) { + icnss_pr_err("No IOVA space to map, iova %lx, smmu_iova_ipa_start %pad, smmu_iova_ipa_len %zu\n", + iova, + &priv->smmu_iova_ipa_start, + priv->smmu_iova_ipa_len); + return -ENOMEM; + } + + dma_coherent = of_property_read_bool(dev->of_node, "dma-coherent"); + icnss_pr_dbg("dma-coherent is %s\n", + dma_coherent ? "enabled" : "disabled"); + if (dma_coherent) + flag |= IOMMU_CACHE; + + icnss_pr_dbg("IOMMU Map: iova %lx, len %zu\n", iova, len); + + ret = iommu_map(priv->iommu_domain, iova, + rounddown(paddr, PAGE_SIZE), len, + flag); + if (ret) { + icnss_pr_err("PA to IOVA mapping failed, ret %d\n", ret); + return ret; + } + + priv->smmu_iova_ipa_current = iova + len; + *iova_addr = (uint32_t)(iova + paddr - rounddown(paddr, PAGE_SIZE)); + + icnss_pr_dbg("IOVA addr mapped to physical addr %lx\n", *iova_addr); + return 0; +} +EXPORT_SYMBOL(icnss_smmu_map); + +int icnss_smmu_unmap(struct device *dev, + uint32_t iova_addr, size_t size) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + unsigned long iova; + size_t len, unmapped_len; + + if (!priv) { + icnss_pr_err("Invalid drvdata: dev %pK, data %pK\n", + dev, priv); + return -EINVAL; + } + + if (!iova_addr) { + icnss_pr_err("iova_addr is NULL, size %zu\n", + size); + return -EINVAL; + } + + len = roundup(size + iova_addr - rounddown(iova_addr, PAGE_SIZE), + PAGE_SIZE); + iova = rounddown(iova_addr, PAGE_SIZE); + + if (iova >= priv->smmu_iova_ipa_start + priv->smmu_iova_ipa_len) { + icnss_pr_err("Out of IOVA space during unmap, iova %lx, smmu_iova_ipa_start %pad, smmu_iova_ipa_len %zu\n", + iova, + &priv->smmu_iova_ipa_start, + priv->smmu_iova_ipa_len); + return -ENOMEM; + } + + icnss_pr_dbg("IOMMU Unmap: iova %lx, len %zu\n", + iova, len); + + unmapped_len = iommu_unmap(priv->iommu_domain, iova, len); + if (unmapped_len != len) { + icnss_pr_err("Failed to unmap, %zu\n", unmapped_len); + return -EINVAL; + } + + priv->smmu_iova_ipa_current = iova; + return 0; +} +EXPORT_SYMBOL(icnss_smmu_unmap); + +unsigned int icnss_socinfo_get_serial_number(struct device *dev) +{ + return socinfo_get_serial_number(); +} +EXPORT_SYMBOL(icnss_socinfo_get_serial_number); + +int icnss_trigger_recovery(struct device *dev) +{ + int ret = 0; + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata: magic 0x%x\n", priv->magic); + ret = -EINVAL; + goto out; + } + + if (test_bit(ICNSS_PD_RESTART, &priv->state)) { + icnss_pr_err("PD recovery already in progress: state: 0x%lx\n", + priv->state); + ret = -EPERM; + goto out; + } + + if (priv->device_id == WCN6750_DEVICE_ID) { + icnss_pr_vdbg("Initiate Root PD restart"); + ret = icnss_send_smp2p(priv, ICNSS_TRIGGER_SSR, + ICNSS_SMP2P_OUT_POWER_SAVE); + if (!ret) + set_bit(ICNSS_HOST_TRIGGERED_PDR, &priv->state); + return ret; + } + + if (!test_bit(ICNSS_PDR_REGISTERED, &priv->state)) { + icnss_pr_err("PD restart not enabled to trigger recovery: state: 0x%lx\n", + priv->state); + ret = -EOPNOTSUPP; + goto out; + } + + icnss_pr_warn("Initiate PD restart at WLAN FW, state: 0x%lx\n", + priv->state); + + ret = pdr_restart_pd(priv->pdr_handle, priv->pdr_service); + + if (!ret) + set_bit(ICNSS_HOST_TRIGGERED_PDR, &priv->state); + +out: + return ret; +} +EXPORT_SYMBOL(icnss_trigger_recovery); + +int icnss_idle_shutdown(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Invalid drvdata: dev %pK", dev); + return -EINVAL; + } + + if (priv->is_ssr || test_bit(ICNSS_PDR, &priv->state) || + test_bit(ICNSS_REJUVENATE, &priv->state)) { + icnss_pr_err("SSR/PDR is already in-progress during idle shutdown\n"); + return -EBUSY; + } + + return icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_IDLE_SHUTDOWN, + ICNSS_EVENT_SYNC_UNINTERRUPTIBLE, NULL); +} +EXPORT_SYMBOL(icnss_idle_shutdown); + +int icnss_idle_restart(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Invalid drvdata: dev %pK", dev); + return -EINVAL; + } + + if (priv->is_ssr || test_bit(ICNSS_PDR, &priv->state) || + test_bit(ICNSS_REJUVENATE, &priv->state)) { + icnss_pr_err("SSR/PDR is already in-progress during idle restart\n"); + return -EBUSY; + } + + return icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_IDLE_RESTART, + ICNSS_EVENT_SYNC_UNINTERRUPTIBLE, NULL); +} +EXPORT_SYMBOL(icnss_idle_restart); + +int icnss_exit_power_save(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + icnss_pr_vdbg("Calling Exit Power Save\n"); + + if (test_bit(ICNSS_PD_RESTART, &priv->state) || + !test_bit(ICNSS_MODE_ON, &priv->state)) + return 0; + + return icnss_send_smp2p(priv, ICNSS_POWER_SAVE_EXIT, + ICNSS_SMP2P_OUT_POWER_SAVE); +} +EXPORT_SYMBOL(icnss_exit_power_save); + +int icnss_prevent_l1(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (test_bit(ICNSS_PD_RESTART, &priv->state) || + !test_bit(ICNSS_MODE_ON, &priv->state)) + return 0; + + return icnss_send_smp2p(priv, ICNSS_PCI_EP_POWER_SAVE_EXIT, + ICNSS_SMP2P_OUT_EP_POWER_SAVE); +} +EXPORT_SYMBOL(icnss_prevent_l1); + +void icnss_allow_l1(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (test_bit(ICNSS_PD_RESTART, &priv->state) || + !test_bit(ICNSS_MODE_ON, &priv->state)) + return; + + icnss_send_smp2p(priv, ICNSS_PCI_EP_POWER_SAVE_ENTER, + ICNSS_SMP2P_OUT_EP_POWER_SAVE); +} +EXPORT_SYMBOL(icnss_allow_l1); + +void icnss_allow_recursive_recovery(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + priv->allow_recursive_recovery = true; + + icnss_pr_info("Recursive recovery allowed for WLAN\n"); +} + +void icnss_disallow_recursive_recovery(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + priv->allow_recursive_recovery = false; + + icnss_pr_info("Recursive recovery disallowed for WLAN\n"); +} + +static int icnss_create_shutdown_sysfs(struct icnss_priv *priv) +{ + struct kobject *icnss_kobject; + int ret = 0; + + atomic_set(&priv->is_shutdown, false); + + icnss_kobject = kobject_create_and_add("shutdown_wlan", kernel_kobj); + if (!icnss_kobject) { + icnss_pr_err("Unable to create shutdown_wlan kernel object"); + return -EINVAL; + } + + priv->icnss_kobject = icnss_kobject; + + ret = sysfs_create_file(icnss_kobject, &icnss_sysfs_attribute.attr); + if (ret) { + icnss_pr_err("Unable to create icnss sysfs file err:%d", ret); + return ret; + } + + return ret; +} + +static void icnss_destroy_shutdown_sysfs(struct icnss_priv *priv) +{ + struct kobject *icnss_kobject; + + icnss_kobject = priv->icnss_kobject; + if (icnss_kobject) + kobject_put(icnss_kobject); +} + +static ssize_t qdss_tr_start_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + wlfw_qdss_trace_start(priv); + icnss_pr_dbg("Received QDSS start command\n"); + return count; +} + +static ssize_t qdss_tr_stop_store(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + u32 option = 0; + + if (sscanf(user_buf, "%du", &option) != 1) + return -EINVAL; + + wlfw_qdss_trace_stop(priv, option); + icnss_pr_dbg("Received QDSS stop command\n"); + return count; +} + +static ssize_t qdss_conf_download_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + icnss_wlfw_qdss_dnld_send_sync(priv); + icnss_pr_dbg("Received QDSS download config command\n"); + return count; +} + +static ssize_t hw_trc_override_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int tmp = 0; + + if (sscanf(buf, "%du", &tmp) != 1) + return -EINVAL; + + priv->hw_trc_override = tmp; + icnss_pr_dbg("Received QDSS hw_trc_override indication\n"); + return count; +} + +static void icnss_wpss_load(struct work_struct *wpss_load_work) +{ + struct icnss_priv *priv = icnss_get_plat_priv(); + phandle rproc_phandle; + int ret; + + if (of_property_read_u32(priv->pdev->dev.of_node, "qcom,rproc-handle", + &rproc_phandle)) { + icnss_pr_err("error reading rproc phandle\n"); + return; + } + + priv->rproc = rproc_get_by_phandle(rproc_phandle); + if (IS_ERR_OR_NULL(priv->rproc)) { + icnss_pr_err("rproc not found"); + return; + } + + ret = rproc_boot(priv->rproc); + if (ret) { + icnss_pr_err("Failed to boot wpss rproc, ret: %d", ret); + rproc_put(priv->rproc); + } +} + +static inline void icnss_wpss_unload(struct icnss_priv *priv) +{ + if (priv && priv->rproc) { + rproc_shutdown(priv->rproc); + rproc_put(priv->rproc); + priv->rproc = NULL; + } +} + +static ssize_t wpss_boot_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int wpss_rproc = 0; + + if (priv->device_id != WCN6750_DEVICE_ID) + return count; + + if (sscanf(buf, "%du", &wpss_rproc) != 1) { + icnss_pr_err("Failed to read wpss rproc info"); + return -EINVAL; + } + + icnss_pr_dbg("WPSS Remote Processor: %s", wpss_rproc ? "GET" : "PUT"); + + if (wpss_rproc == 1) + schedule_work(&wpss_loader); + else if (wpss_rproc == 0) + icnss_wpss_unload(priv); + + return count; +} + +static ssize_t wlan_en_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + uint32_t wlan_en_delay = 0; + + if (priv->device_id != WCN6750_DEVICE_ID) + return count; + + if (sscanf(buf, "%du", &wlan_en_delay) != 1) { + icnss_pr_err("Failed to read wlan_en_delay"); + return -EINVAL; + } + + icnss_pr_dbg("WLAN_EN delay: %dms", wlan_en_delay); + priv->wlan_en_delay_ms = wlan_en_delay; + + return count; +} + +static DEVICE_ATTR_WO(qdss_tr_start); +static DEVICE_ATTR_WO(qdss_tr_stop); +static DEVICE_ATTR_WO(qdss_conf_download); +static DEVICE_ATTR_WO(hw_trc_override); +static DEVICE_ATTR_WO(wpss_boot); +static DEVICE_ATTR_WO(wlan_en_delay); + +static struct attribute *icnss_attrs[] = { + &dev_attr_qdss_tr_start.attr, + &dev_attr_qdss_tr_stop.attr, + &dev_attr_qdss_conf_download.attr, + &dev_attr_hw_trc_override.attr, + &dev_attr_wpss_boot.attr, + &dev_attr_wlan_en_delay.attr, + NULL, +}; + +static struct attribute_group icnss_attr_group = { + .attrs = icnss_attrs, +}; + +static int icnss_create_sysfs_link(struct icnss_priv *priv) +{ + struct device *dev = &priv->pdev->dev; + int ret; + + ret = sysfs_create_link(kernel_kobj, &dev->kobj, "icnss"); + if (ret) { + icnss_pr_err("Failed to create icnss link, err = %d\n", + ret); + goto out; + } + + return 0; +out: + return ret; +} + +static void icnss_remove_sysfs_link(struct icnss_priv *priv) +{ + sysfs_remove_link(kernel_kobj, "icnss"); +} + +static int icnss_sysfs_create(struct icnss_priv *priv) +{ + int ret = 0; + + ret = devm_device_add_group(&priv->pdev->dev, + &icnss_attr_group); + if (ret) { + icnss_pr_err("Failed to create icnss device group, err = %d\n", + ret); + goto out; + } + + icnss_create_sysfs_link(priv); + + ret = icnss_create_shutdown_sysfs(priv); + if (ret) + goto remove_icnss_group; + + return 0; +remove_icnss_group: + devm_device_remove_group(&priv->pdev->dev, &icnss_attr_group); +out: + return ret; +} + +static void icnss_sysfs_destroy(struct icnss_priv *priv) +{ + icnss_destroy_shutdown_sysfs(priv); + icnss_remove_sysfs_link(priv); + devm_device_remove_group(&priv->pdev->dev, &icnss_attr_group); +} + +static int icnss_get_vbatt_info(struct icnss_priv *priv) +{ + struct adc_tm_chip *adc_tm_dev = NULL; + struct iio_channel *channel = NULL; + int ret = 0; + + adc_tm_dev = get_adc_tm(&priv->pdev->dev, "icnss"); + if (PTR_ERR(adc_tm_dev) == -EPROBE_DEFER) { + icnss_pr_err("adc_tm_dev probe defer\n"); + return -EPROBE_DEFER; + } + + if (IS_ERR(adc_tm_dev)) { + ret = PTR_ERR(adc_tm_dev); + icnss_pr_err("Not able to get ADC dev, VBATT monitoring is disabled: %d\n", + ret); + return ret; + } + + channel = devm_iio_channel_get(&priv->pdev->dev, "icnss"); + if (PTR_ERR(channel) == -EPROBE_DEFER) { + icnss_pr_err("channel probe defer\n"); + return -EPROBE_DEFER; + } + + if (IS_ERR(channel)) { + ret = PTR_ERR(channel); + icnss_pr_err("Not able to get VADC dev, VBATT monitoring is disabled: %d\n", + ret); + return ret; + } + + priv->adc_tm_dev = adc_tm_dev; + priv->channel = channel; + + return 0; +} + +static int icnss_resource_parse(struct icnss_priv *priv) +{ + int ret = 0, i = 0; + struct platform_device *pdev = priv->pdev; + struct device *dev = &pdev->dev; + struct resource *res; + u32 int_prop; + + if (of_property_read_bool(pdev->dev.of_node, "qcom,icnss-adc_tm")) { + ret = icnss_get_vbatt_info(priv); + if (ret == -EPROBE_DEFER) + goto out; + priv->vbatt_supported = true; + } + + ret = icnss_get_vreg(priv); + if (ret) { + icnss_pr_err("Failed to get vreg, err = %d\n", ret); + goto out; + } + + ret = icnss_get_clk(priv); + if (ret) { + icnss_pr_err("Failed to get clocks, err = %d\n", ret); + goto put_vreg; + } + + if (priv->device_id == ADRASTEA_DEVICE_ID) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "membase"); + if (!res) { + icnss_pr_err("Memory base not found in DT\n"); + ret = -EINVAL; + goto put_clk; + } + + priv->mem_base_pa = res->start; + priv->mem_base_va = devm_ioremap(dev, priv->mem_base_pa, + resource_size(res)); + if (!priv->mem_base_va) { + icnss_pr_err("Memory base ioremap failed: phy addr: %pa\n", + &priv->mem_base_pa); + ret = -EINVAL; + goto put_clk; + } + icnss_pr_dbg("MEM_BASE pa: %pa, va: 0x%pK\n", + &priv->mem_base_pa, + priv->mem_base_va); + + for (i = 0; i < ICNSS_MAX_IRQ_REGISTRATIONS; i++) { + res = platform_get_resource(priv->pdev, + IORESOURCE_IRQ, i); + if (!res) { + icnss_pr_err("Fail to get IRQ-%d\n", i); + ret = -ENODEV; + goto put_clk; + } else { + priv->ce_irqs[i] = res->start; + } + } + } else if (priv->device_id == WCN6750_DEVICE_ID) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "msi_addr"); + if (!res) { + icnss_pr_err("MSI address not found in DT\n"); + ret = -EINVAL; + goto put_clk; + } + + priv->msi_addr_pa = res->start; + priv->msi_addr_iova = dma_map_resource(dev, priv->msi_addr_pa, + PAGE_SIZE, + DMA_FROM_DEVICE, 0); + if (dma_mapping_error(dev, priv->msi_addr_iova)) { + icnss_pr_err("MSI: failed to map msi address\n"); + priv->msi_addr_iova = 0; + ret = -ENOMEM; + goto put_clk; + } + icnss_pr_dbg("MSI Addr pa: %pa, iova: 0x%pK\n", + &priv->msi_addr_pa, + priv->msi_addr_iova); + + ret = of_property_read_u32_index(dev->of_node, + "interrupts", + 1, + &int_prop); + if (ret) { + icnss_pr_dbg("Read interrupt prop failed"); + goto put_clk; + } + + priv->msi_base_data = int_prop + 32; + icnss_pr_dbg(" MSI Base Data: %d, IRQ Index: %d\n", + priv->msi_base_data, int_prop); + + icnss_get_msi_assignment(priv); + for (i = 0; i < msi_config.total_vectors; i++) { + res = platform_get_resource(priv->pdev, + IORESOURCE_IRQ, i); + if (!res) { + icnss_pr_err("Fail to get IRQ-%d\n", i); + ret = -ENODEV; + goto put_clk; + } else { + priv->srng_irqs[i] = res->start; + } + } + } + + return 0; + +put_clk: + icnss_put_clk(priv); +put_vreg: + icnss_put_vreg(priv); +out: + return ret; +} + +static int icnss_msa_dt_parse(struct icnss_priv *priv) +{ + int ret = 0; + struct platform_device *pdev = priv->pdev; + struct device *dev = &pdev->dev; + struct device_node *np = NULL; + u64 prop_size = 0; + const __be32 *addrp = NULL; + + np = of_parse_phandle(dev->of_node, + "qcom,wlan-msa-fixed-region", 0); + if (np) { + addrp = of_get_address(np, 0, &prop_size, NULL); + if (!addrp) { + icnss_pr_err("Failed to get assigned-addresses or property\n"); + ret = -EINVAL; + of_node_put(np); + goto out; + } + + priv->msa_pa = of_translate_address(np, addrp); + if (priv->msa_pa == OF_BAD_ADDR) { + icnss_pr_err("Failed to translate MSA PA from device-tree\n"); + ret = -EINVAL; + of_node_put(np); + goto out; + } + + of_node_put(np); + + priv->msa_va = memremap(priv->msa_pa, + (unsigned long)prop_size, MEMREMAP_WT); + if (!priv->msa_va) { + icnss_pr_err("MSA PA ioremap failed: phy addr: %pa\n", + &priv->msa_pa); + ret = -EINVAL; + goto out; + } + priv->msa_mem_size = prop_size; + } else { + ret = of_property_read_u32(dev->of_node, "qcom,wlan-msa-memory", + &priv->msa_mem_size); + if (ret || priv->msa_mem_size == 0) { + icnss_pr_err("Fail to get MSA Memory Size: %u ret: %d\n", + priv->msa_mem_size, ret); + goto out; + } + + priv->msa_va = dmam_alloc_coherent(&pdev->dev, + priv->msa_mem_size, &priv->msa_pa, GFP_KERNEL); + + if (!priv->msa_va) { + icnss_pr_err("DMA alloc failed for MSA\n"); + ret = -ENOMEM; + goto out; + } + } + + icnss_pr_dbg("MSA pa: %pa, MSA va: 0x%pK MSA Memory Size: 0x%x\n", + &priv->msa_pa, (void *)priv->msa_va, priv->msa_mem_size); + + priv->use_prefix_path = of_property_read_bool(priv->pdev->dev.of_node, + "qcom,fw-prefix"); + return 0; + +out: + return ret; +} + +static int icnss_smmu_fault_handler(struct iommu_domain *domain, + struct device *dev, unsigned long iova, + int flags, void *handler_token) +{ + struct icnss_priv *priv = handler_token; + struct icnss_uevent_fw_down_data fw_down_data = {0}; + + icnss_fatal_err("SMMU fault happened with IOVA 0x%lx\n", iova); + + if (!priv) { + icnss_pr_err("priv is NULL\n"); + return -ENODEV; + } + + if (test_bit(ICNSS_FW_READY, &priv->state)) { + fw_down_data.crashed = true; + icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_DOWN, + &fw_down_data); + } + + icnss_trigger_recovery(&priv->pdev->dev); + + /* IOMMU driver requires non-zero return value to print debug info. */ + return -EINVAL; +} + +static int icnss_smmu_dt_parse(struct icnss_priv *priv) +{ + int ret = 0; + struct platform_device *pdev = priv->pdev; + struct device *dev = &pdev->dev; + const char *iommu_dma_type; + struct resource *res; + u32 addr_win[2]; + + ret = of_property_read_u32_array(dev->of_node, + "qcom,iommu-dma-addr-pool", + addr_win, + ARRAY_SIZE(addr_win)); + + if (ret) { + icnss_pr_err("SMMU IOVA base not found\n"); + } else { + priv->smmu_iova_start = addr_win[0]; + priv->smmu_iova_len = addr_win[1]; + icnss_pr_dbg("SMMU IOVA start: %pa, len: %zx\n", + &priv->smmu_iova_start, + priv->smmu_iova_len); + + priv->iommu_domain = + iommu_get_domain_for_dev(&pdev->dev); + + ret = of_property_read_string(dev->of_node, "qcom,iommu-dma", + &iommu_dma_type); + if (!ret && !strcmp("fastmap", iommu_dma_type)) { + icnss_pr_dbg("SMMU S1 stage enabled\n"); + priv->smmu_s1_enable = true; + if (priv->device_id == WCN6750_DEVICE_ID) + iommu_set_fault_handler(priv->iommu_domain, + icnss_smmu_fault_handler, + priv); + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "smmu_iova_ipa"); + if (!res) { + icnss_pr_err("SMMU IOVA IPA not found\n"); + } else { + priv->smmu_iova_ipa_start = res->start; + priv->smmu_iova_ipa_current = res->start; + priv->smmu_iova_ipa_len = resource_size(res); + icnss_pr_dbg("SMMU IOVA IPA start: %pa, len: %zx\n", + &priv->smmu_iova_ipa_start, + priv->smmu_iova_ipa_len); + } + } + + return 0; +} + +int icnss_get_iova(struct icnss_priv *priv, u64 *addr, u64 *size) +{ + if (!priv) + return -ENODEV; + + if (!priv->smmu_iova_len) + return -EINVAL; + + *addr = priv->smmu_iova_start; + *size = priv->smmu_iova_len; + + return 0; +} + +int icnss_get_iova_ipa(struct icnss_priv *priv, u64 *addr, u64 *size) +{ + if (!priv) + return -ENODEV; + + if (!priv->smmu_iova_ipa_len) + return -EINVAL; + + *addr = priv->smmu_iova_ipa_start; + *size = priv->smmu_iova_ipa_len; + + return 0; +} + +void icnss_add_fw_prefix_name(struct icnss_priv *priv, char *prefix_name, + char *name) +{ + if (!priv) + return; + + if (!priv->use_prefix_path) { + scnprintf(prefix_name, ICNSS_MAX_FILE_NAME, "%s", name); + return; + } + + scnprintf(prefix_name, ICNSS_MAX_FILE_NAME, + QCA6750_PATH_PREFIX "%s", name); + + icnss_pr_dbg("File added with prefix: %s\n", prefix_name); +} + +static const struct platform_device_id icnss_platform_id_table[] = { + { .name = "wcn6750", .driver_data = WCN6750_DEVICE_ID, }, + { .name = "adrastea", .driver_data = ADRASTEA_DEVICE_ID, }, + { }, +}; + +static const struct of_device_id icnss_dt_match[] = { + { + .compatible = "qcom,wcn6750", + .data = (void *)&icnss_platform_id_table[0]}, + { + .compatible = "qcom,icnss", + .data = (void *)&icnss_platform_id_table[1]}, + { }, +}; + +MODULE_DEVICE_TABLE(of, icnss_dt_match); + +static void icnss_init_control_params(struct icnss_priv *priv) +{ + priv->ctrl_params.qmi_timeout = WLFW_TIMEOUT; + priv->ctrl_params.quirks = ICNSS_QUIRKS_DEFAULT; + priv->ctrl_params.bdf_type = ICNSS_BDF_TYPE_DEFAULT; + + if (of_property_read_bool(priv->pdev->dev.of_node, + "bdf-download-support")) + priv->bdf_download_support = true; + + if (priv->bdf_download_support && priv->device_id == ADRASTEA_DEVICE_ID) + priv->ctrl_params.bdf_type = ICNSS_BDF_BIN; +} + +static inline void icnss_runtime_pm_init(struct icnss_priv *priv) +{ + pm_runtime_get_sync(&priv->pdev->dev); + pm_runtime_forbid(&priv->pdev->dev); + pm_runtime_set_active(&priv->pdev->dev); + pm_runtime_enable(&priv->pdev->dev); +} + +static inline void icnss_runtime_pm_deinit(struct icnss_priv *priv) +{ + pm_runtime_disable(&priv->pdev->dev); + pm_runtime_allow(&priv->pdev->dev); + pm_runtime_put_sync(&priv->pdev->dev); +} + +static inline bool icnss_use_nv_mac(struct icnss_priv *priv) +{ + return of_property_read_bool(priv->pdev->dev.of_node, + "use-nv-mac"); +} + +static void rproc_restart_level_notifier(void *data, struct rproc *rproc) +{ + struct icnss_subsys_restart_level_data *restart_level_data; + + icnss_pr_info("rproc name: %s recovery disable: %d", + rproc->name, rproc->recovery_disabled); + + restart_level_data = kzalloc(sizeof(*restart_level_data), GFP_ATOMIC); + if (!restart_level_data) + return; + + if (strnstr(rproc->name, "wpss", ICNSS_RPROC_LEN)) { + if (rproc->recovery_disabled) + restart_level_data->restart_level = ICNSS_DISABLE_M3_SSR; + else + restart_level_data->restart_level = ICNSS_ENABLE_M3_SSR; + + icnss_driver_event_post(penv, ICNSS_DRIVER_EVENT_SUBSYS_RESTART_LEVEL, + 0, restart_level_data); + } +} + +static int icnss_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *dev = &pdev->dev; + struct icnss_priv *priv; + const struct of_device_id *of_id; + const struct platform_device_id *device_id; + + if (dev_get_drvdata(dev)) { + icnss_pr_err("Driver is already initialized\n"); + return -EEXIST; + } + + of_id = of_match_device(icnss_dt_match, &pdev->dev); + if (!of_id || !of_id->data) { + icnss_pr_err("Failed to find of match device!\n"); + ret = -ENODEV; + goto out_reset_drvdata; + } + + device_id = of_id->data; + + icnss_pr_dbg("Platform driver probe\n"); + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->magic = ICNSS_MAGIC; + dev_set_drvdata(dev, priv); + + priv->pdev = pdev; + priv->device_id = device_id->driver_data; + priv->is_chain1_supported = true; + INIT_LIST_HEAD(&priv->vreg_list); + INIT_LIST_HEAD(&priv->clk_list); + icnss_allow_recursive_recovery(dev); + + icnss_init_control_params(priv); + + ret = icnss_resource_parse(priv); + if (ret) + goto out_reset_drvdata; + + ret = icnss_msa_dt_parse(priv); + if (ret) + goto out_free_resources; + + ret = icnss_smmu_dt_parse(priv); + if (ret) + goto out_free_resources; + + spin_lock_init(&priv->event_lock); + spin_lock_init(&priv->on_off_lock); + spin_lock_init(&priv->soc_wake_msg_lock); + mutex_init(&priv->dev_lock); + mutex_init(&priv->tcdev_lock); + + priv->event_wq = alloc_workqueue("icnss_driver_event", WQ_UNBOUND, 1); + if (!priv->event_wq) { + icnss_pr_err("Workqueue creation failed\n"); + ret = -EFAULT; + goto smmu_cleanup; + } + + INIT_WORK(&priv->event_work, icnss_driver_event_work); + INIT_LIST_HEAD(&priv->event_list); + + priv->soc_wake_wq = alloc_workqueue("icnss_soc_wake_event", + WQ_UNBOUND|WQ_HIGHPRI, 1); + if (!priv->soc_wake_wq) { + icnss_pr_err("Soc wake Workqueue creation failed\n"); + ret = -EFAULT; + goto out_destroy_wq; + } + + INIT_WORK(&priv->soc_wake_msg_work, icnss_soc_wake_msg_work); + INIT_LIST_HEAD(&priv->soc_wake_msg_list); + + ret = icnss_register_fw_service(priv); + if (ret < 0) { + icnss_pr_err("fw service registration failed: %d\n", ret); + goto out_destroy_soc_wq; + } + + icnss_enable_recovery(priv); + + icnss_debugfs_create(priv); + + icnss_sysfs_create(priv); + + ret = device_init_wakeup(&priv->pdev->dev, true); + if (ret) + icnss_pr_err("Failed to init platform device wakeup source, err = %d\n", + ret); + + icnss_set_plat_priv(priv); + + init_completion(&priv->unblock_shutdown); + + if (priv->device_id == WCN6750_DEVICE_ID) { + ret = icnss_dms_init(priv); + if (ret) + icnss_pr_err("ICNSS DMS init failed %d\n", ret); + ret = icnss_genl_init(); + if (ret < 0) + icnss_pr_err("ICNSS genl init failed %d\n", ret); + + init_completion(&priv->smp2p_soc_wake_wait); + icnss_runtime_pm_init(priv); + icnss_aop_mbox_init(priv); + set_bit(ICNSS_COLD_BOOT_CAL, &priv->state); + priv->bdf_download_support = true; + priv->use_nv_mac = icnss_use_nv_mac(priv); + icnss_pr_dbg("NV MAC feature is %s\n", + priv->use_nv_mac ? "Mandatory":"Not Mandatory"); + INIT_WORK(&wpss_loader, icnss_wpss_load); + register_trace_android_vh_rproc_recovery_set(rproc_restart_level_notifier, NULL); + } + + INIT_LIST_HEAD(&priv->icnss_tcdev_list); + + icnss_pr_info("Platform driver probed successfully\n"); + + return 0; + +out_destroy_soc_wq: + destroy_workqueue(priv->soc_wake_wq); +out_destroy_wq: + destroy_workqueue(priv->event_wq); +smmu_cleanup: + priv->iommu_domain = NULL; +out_free_resources: + icnss_put_resources(priv); +out_reset_drvdata: + dev_set_drvdata(dev, NULL); + return ret; +} + +void icnss_destroy_ramdump_device(struct icnss_ramdump_info *ramdump_info) +{ + device_unregister(ramdump_info->dev); + + ida_simple_remove(&rd_minor_id, ramdump_info->minor); + + kfree(ramdump_info); +} + +static int icnss_remove(struct platform_device *pdev) +{ + struct icnss_priv *priv = dev_get_drvdata(&pdev->dev); + + icnss_pr_info("Removing driver: state: 0x%lx\n", priv->state); + + if (priv->device_id == WCN6750_DEVICE_ID) { + icnss_dms_deinit(priv); + icnss_genl_exit(); + icnss_runtime_pm_deinit(priv); + if (!IS_ERR_OR_NULL(priv->mbox_chan)) + mbox_free_channel(priv->mbox_chan); + unregister_trace_android_vh_rproc_recovery_set(rproc_restart_level_notifier, NULL); + complete_all(&priv->smp2p_soc_wake_wait); + } + + device_init_wakeup(&priv->pdev->dev, false); + + icnss_debugfs_destroy(priv); + + icnss_sysfs_destroy(priv); + + complete_all(&priv->unblock_shutdown); + + icnss_destroy_ramdump_device(priv->msa0_dump_dev); + + if (priv->device_id == WCN6750_DEVICE_ID) { + icnss_wpss_ssr_unregister_notifier(priv); + rproc_put(priv->rproc); + icnss_destroy_ramdump_device(priv->m3_dump_phyareg); + icnss_destroy_ramdump_device(priv->m3_dump_phydbg); + icnss_destroy_ramdump_device(priv->m3_dump_wmac0reg); + icnss_destroy_ramdump_device(priv->m3_dump_wcssdbg); + icnss_destroy_ramdump_device(priv->m3_dump_phyapdmem); + } else { + icnss_modem_ssr_unregister_notifier(priv); + icnss_pdr_unregister_notifier(priv); + } + + class_destroy(priv->icnss_ramdump_class); + unregister_chrdev_region(priv->icnss_ramdump_dev, RAMDUMP_NUM_DEVICES); + + icnss_unregister_fw_service(priv); + if (priv->event_wq) + destroy_workqueue(priv->event_wq); + + if (priv->soc_wake_wq) + destroy_workqueue(priv->soc_wake_wq); + + priv->iommu_domain = NULL; + + icnss_hw_power_off(priv); + + icnss_put_resources(priv); + + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int icnss_pm_suspend(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int ret = 0; + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for pm suspend: dev %pK, data %pK, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + icnss_pr_vdbg("PM Suspend, state: 0x%lx\n", priv->state); + + if (!priv->ops || !priv->ops->pm_suspend || + IS_ERR(priv->smp2p_info[ICNSS_SMP2P_OUT_POWER_SAVE].smem_state) || + !test_bit(ICNSS_DRIVER_PROBED, &priv->state)) + return 0; + + ret = priv->ops->pm_suspend(dev); + + if (ret == 0) { + if (priv->device_id == WCN6750_DEVICE_ID) { + if (test_bit(ICNSS_PD_RESTART, &priv->state) || + !test_bit(ICNSS_MODE_ON, &priv->state)) + return 0; + + ret = icnss_send_smp2p(priv, ICNSS_POWER_SAVE_ENTER, + ICNSS_SMP2P_OUT_POWER_SAVE); + } + priv->stats.pm_suspend++; + set_bit(ICNSS_PM_SUSPEND, &priv->state); + } else { + priv->stats.pm_suspend_err++; + } + return ret; +} + +static int icnss_pm_resume(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int ret = 0; + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for pm resume: dev %pK, data %pK, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + icnss_pr_vdbg("PM resume, state: 0x%lx\n", priv->state); + + if (!priv->ops || !priv->ops->pm_resume || + IS_ERR(priv->smp2p_info[ICNSS_SMP2P_OUT_POWER_SAVE].smem_state) || + !test_bit(ICNSS_DRIVER_PROBED, &priv->state)) + goto out; + + ret = priv->ops->pm_resume(dev); + +out: + if (ret == 0) { + priv->stats.pm_resume++; + clear_bit(ICNSS_PM_SUSPEND, &priv->state); + } else { + priv->stats.pm_resume_err++; + } + return ret; +} + +static int icnss_pm_suspend_noirq(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int ret = 0; + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for pm suspend_noirq: dev %pK, data %pK, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + icnss_pr_vdbg("PM suspend_noirq, state: 0x%lx\n", priv->state); + + if (!priv->ops || !priv->ops->suspend_noirq || + !test_bit(ICNSS_DRIVER_PROBED, &priv->state)) + goto out; + + ret = priv->ops->suspend_noirq(dev); + +out: + if (ret == 0) { + priv->stats.pm_suspend_noirq++; + set_bit(ICNSS_PM_SUSPEND_NOIRQ, &priv->state); + } else { + priv->stats.pm_suspend_noirq_err++; + } + return ret; +} + +static int icnss_pm_resume_noirq(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int ret = 0; + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for pm resume_noirq: dev %pK, data %pK, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + icnss_pr_vdbg("PM resume_noirq, state: 0x%lx\n", priv->state); + + if (!priv->ops || !priv->ops->resume_noirq || + !test_bit(ICNSS_DRIVER_PROBED, &priv->state)) + goto out; + + ret = priv->ops->resume_noirq(dev); + +out: + if (ret == 0) { + priv->stats.pm_resume_noirq++; + clear_bit(ICNSS_PM_SUSPEND_NOIRQ, &priv->state); + } else { + priv->stats.pm_resume_noirq_err++; + } + return ret; +} + +static int icnss_pm_runtime_suspend(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int ret = 0; + + if (priv->device_id != WCN6750_DEVICE_ID) { + icnss_pr_err("Ignore runtime suspend:\n"); + goto out; + } + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for runtime suspend: dev %pK, data %pK, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + if (!priv->ops || !priv->ops->runtime_suspend || + IS_ERR(priv->smp2p_info[ICNSS_SMP2P_OUT_POWER_SAVE].smem_state)) + goto out; + + icnss_pr_vdbg("Runtime suspend\n"); + ret = priv->ops->runtime_suspend(dev); + if (!ret) { + if (test_bit(ICNSS_PD_RESTART, &priv->state) || + !test_bit(ICNSS_MODE_ON, &priv->state)) + return 0; + + ret = icnss_send_smp2p(priv, ICNSS_POWER_SAVE_ENTER, + ICNSS_SMP2P_OUT_POWER_SAVE); + } +out: + return ret; +} + +static int icnss_pm_runtime_resume(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + int ret = 0; + + if (priv->device_id != WCN6750_DEVICE_ID) { + icnss_pr_err("Ignore runtime resume:\n"); + goto out; + } + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for runtime resume: dev %pK, data %pK, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + if (!priv->ops || !priv->ops->runtime_resume || + IS_ERR(priv->smp2p_info[ICNSS_SMP2P_OUT_POWER_SAVE].smem_state)) + goto out; + + icnss_pr_vdbg("Runtime resume, state: 0x%lx\n", priv->state); + + ret = priv->ops->runtime_resume(dev); + +out: + return ret; +} + +static int icnss_pm_runtime_idle(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (priv->device_id != WCN6750_DEVICE_ID) { + icnss_pr_err("Ignore runtime idle:\n"); + goto out; + } + + icnss_pr_vdbg("Runtime idle\n"); + + pm_request_autosuspend(dev); + +out: + return -EBUSY; +} +#endif + +static const struct dev_pm_ops icnss_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(icnss_pm_suspend, + icnss_pm_resume) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(icnss_pm_suspend_noirq, + icnss_pm_resume_noirq) + SET_RUNTIME_PM_OPS(icnss_pm_runtime_suspend, icnss_pm_runtime_resume, + icnss_pm_runtime_idle) +}; + +static struct platform_driver icnss_driver = { + .probe = icnss_probe, + .remove = icnss_remove, + .driver = { + .name = "icnss2", + .pm = &icnss_pm_ops, + .of_match_table = icnss_dt_match, + }, +}; + +static int __init icnss_initialize(void) +{ + icnss_debug_init(); + return platform_driver_register(&icnss_driver); +} + +static void __exit icnss_exit(void) +{ + platform_driver_unregister(&icnss_driver); + icnss_debug_deinit(); +} + + +module_init(icnss_initialize); +module_exit(icnss_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("iWCN CORE platform driver"); diff --git a/icnss2/main.h b/icnss2/main.h new file mode 100644 index 0000000000..e865bec1de --- /dev/null +++ b/icnss2/main.h @@ -0,0 +1,512 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2020, 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __MAIN_H__ +#define __MAIN_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlan_firmware_service_v01.h" +#include + +#define WCN6750_DEVICE_ID 0x6750 +#define ADRASTEA_DEVICE_ID 0xabcd +#define QMI_WLFW_MAX_NUM_MEM_SEG 32 +#define THERMAL_NAME_LENGTH 20 +#define ICNSS_SMEM_VALUE_MASK 0xFFFFFFFF +#define ICNSS_SMEM_SEQ_NO_POS 16 +#define QCA6750_PATH_PREFIX "qca6750/" +#define ICNSS_MAX_FILE_NAME 35 +#define ICNSS_PCI_EP_WAKE_OFFSET 4 +#define ICNSS_DISABLE_M3_SSR 0 +#define ICNSS_ENABLE_M3_SSR 1 + +extern uint64_t dynamic_feature_mask; + +enum icnss_bdf_type { + ICNSS_BDF_BIN, + ICNSS_BDF_ELF, + ICNSS_BDF_REGDB = 4, + ICNSS_BDF_DUMMY = 255, +}; + +struct icnss_control_params { + unsigned long quirks; + unsigned int qmi_timeout; + unsigned int bdf_type; +}; + +enum icnss_driver_event_type { + ICNSS_DRIVER_EVENT_SERVER_ARRIVE, + ICNSS_DRIVER_EVENT_SERVER_EXIT, + ICNSS_DRIVER_EVENT_FW_READY_IND, + ICNSS_DRIVER_EVENT_REGISTER_DRIVER, + ICNSS_DRIVER_EVENT_UNREGISTER_DRIVER, + ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN, + ICNSS_DRIVER_EVENT_FW_EARLY_CRASH_IND, + ICNSS_DRIVER_EVENT_IDLE_SHUTDOWN, + ICNSS_DRIVER_EVENT_IDLE_RESTART, + ICNSS_DRIVER_EVENT_FW_INIT_DONE_IND, + ICNSS_DRIVER_EVENT_QDSS_TRACE_REQ_MEM, + ICNSS_DRIVER_EVENT_QDSS_TRACE_SAVE, + ICNSS_DRIVER_EVENT_QDSS_TRACE_FREE, + ICNSS_DRIVER_EVENT_M3_DUMP_UPLOAD_REQ, + ICNSS_DRIVER_EVENT_QDSS_TRACE_REQ_DATA, + ICNSS_DRIVER_EVENT_SUBSYS_RESTART_LEVEL, + ICNSS_DRIVER_EVENT_MAX, +}; + +enum icnss_soc_wake_event_type { + ICNSS_SOC_WAKE_REQUEST_EVENT, + ICNSS_SOC_WAKE_RELEASE_EVENT, + ICNSS_SOC_WAKE_EVENT_MAX, +}; + +struct icnss_event_server_arrive_data { + unsigned int node; + unsigned int port; +}; + +struct icnss_event_pd_service_down_data { + bool crashed; + bool fw_rejuvenate; +}; + +struct icnss_driver_event { + struct list_head list; + enum icnss_driver_event_type type; + bool sync; + struct completion complete; + int ret; + void *data; +}; + +struct icnss_soc_wake_event { + struct list_head list; + enum icnss_soc_wake_event_type type; + bool sync; + struct completion complete; + int ret; + void *data; +}; + +enum icnss_driver_state { + ICNSS_WLFW_CONNECTED, + ICNSS_POWER_ON, + ICNSS_FW_READY, + ICNSS_DRIVER_PROBED, + ICNSS_FW_TEST_MODE, + ICNSS_PM_SUSPEND, + ICNSS_PM_SUSPEND_NOIRQ, + ICNSS_SSR_REGISTERED, + ICNSS_PDR_REGISTERED, + ICNSS_PD_RESTART, + ICNSS_WLFW_EXISTS, + ICNSS_SHUTDOWN_DONE, + ICNSS_HOST_TRIGGERED_PDR, + ICNSS_FW_DOWN, + ICNSS_DRIVER_UNLOADING, + ICNSS_REJUVENATE, + ICNSS_MODE_ON, + ICNSS_BLOCK_SHUTDOWN, + ICNSS_PDR, + ICNSS_DEL_SERVER, + ICNSS_COLD_BOOT_CAL, + ICNSS_QMI_DMS_CONNECTED, +}; + +struct ce_irq_list { + int irq; + irqreturn_t (*handler)(int irq, void *priv); +}; + +struct icnss_vreg_cfg { + const char *name; + u32 min_uv; + u32 max_uv; + u32 load_ua; + u32 delay_us; + u32 need_unvote; + bool required; + bool is_supported; +}; + +struct icnss_vreg_info { + struct list_head list; + struct regulator *reg; + struct icnss_vreg_cfg cfg; + u32 enabled; +}; + +struct icnss_cpr_info { + const char *vreg_ol_cpr; + u32 voltage; +}; + +enum icnss_vreg_type { + ICNSS_VREG_PRIM, +}; +struct icnss_clk_cfg { + const char *name; + u32 freq; + u32 required; +}; + +struct icnss_clk_info { + struct list_head list; + struct clk *clk; + struct icnss_clk_cfg cfg; + u32 enabled; +}; + +struct icnss_fw_mem { + size_t size; + void *va; + phys_addr_t pa; + u8 valid; + u32 type; + unsigned long attrs; +}; + +enum icnss_smp2p_msg_id { + ICNSS_RESET_MSG, + ICNSS_POWER_SAVE_ENTER, + ICNSS_POWER_SAVE_EXIT, + ICNSS_TRIGGER_SSR, + ICNSS_SOC_WAKE_REQ, + ICNSS_SOC_WAKE_REL, + ICNSS_PCI_EP_POWER_SAVE_ENTER, + ICNSS_PCI_EP_POWER_SAVE_EXIT, +}; + +struct icnss_subsys_restart_level_data { + uint8_t restart_level; +}; + +struct icnss_stats { + struct { + uint32_t posted; + uint32_t processed; + } events[ICNSS_DRIVER_EVENT_MAX]; + + struct { + u32 posted; + u32 processed; + } soc_wake_events[ICNSS_SOC_WAKE_EVENT_MAX]; + + struct { + uint32_t request; + uint32_t free; + uint32_t enable; + uint32_t disable; + } ce_irqs[ICNSS_MAX_IRQ_REGISTRATIONS]; + + struct { + uint32_t pdr_fw_crash; + uint32_t pdr_host_error; + uint32_t root_pd_crash; + uint32_t root_pd_shutdown; + } recovery; + + uint32_t pm_suspend; + uint32_t pm_suspend_err; + uint32_t pm_resume; + uint32_t pm_resume_err; + uint32_t pm_suspend_noirq; + uint32_t pm_suspend_noirq_err; + uint32_t pm_resume_noirq; + uint32_t pm_resume_noirq_err; + uint32_t pm_stay_awake; + uint32_t pm_relax; + + uint32_t ind_register_req; + uint32_t ind_register_resp; + uint32_t ind_register_err; + uint32_t msa_info_req; + uint32_t msa_info_resp; + uint32_t msa_info_err; + uint32_t msa_ready_req; + uint32_t msa_ready_resp; + uint32_t msa_ready_err; + uint32_t msa_ready_ind; + uint32_t cap_req; + uint32_t cap_resp; + uint32_t cap_err; + uint32_t pin_connect_result; + uint32_t cfg_req; + uint32_t cfg_resp; + uint32_t cfg_req_err; + uint32_t mode_req; + uint32_t mode_resp; + uint32_t mode_req_err; + uint32_t ini_req; + uint32_t ini_resp; + uint32_t ini_req_err; + u32 rejuvenate_ind; + uint32_t rejuvenate_ack_req; + uint32_t rejuvenate_ack_resp; + uint32_t rejuvenate_ack_err; + uint32_t vbatt_req; + uint32_t vbatt_resp; + uint32_t vbatt_req_err; + uint32_t device_info_req; + uint32_t device_info_resp; + uint32_t device_info_err; + u32 exit_power_save_req; + u32 exit_power_save_resp; + u32 exit_power_save_err; + u32 enter_power_save_req; + u32 enter_power_save_resp; + u32 enter_power_save_err; + u32 soc_wake_req; + u32 soc_wake_resp; + u32 soc_wake_err; + u32 restart_level_req; + u32 restart_level_resp; + u32 restart_level_err; +}; + +#define WLFW_MAX_TIMESTAMP_LEN 32 +#define WLFW_MAX_BUILD_ID_LEN 128 +#define WLFW_MAX_NUM_MEMORY_REGIONS 2 +#define WLFW_FUNCTION_NAME_LEN 129 +#define WLFW_MAX_DATA_SIZE 6144 +#define WLFW_MAX_STR_LEN 16 +#define WLFW_MAX_NUM_CE 12 +#define WLFW_MAX_NUM_SVC 24 +#define WLFW_MAX_NUM_SHADOW_REG 24 +#define WLFW_MAX_HANG_EVENT_DATA_SIZE 400 + +struct wlfw_rf_chip_info { + uint32_t chip_id; + uint32_t chip_family; +}; + +struct wlfw_rf_board_info { + uint32_t board_id; +}; + +struct wlfw_fw_version_info { + uint32_t fw_version; + char fw_build_timestamp[WLFW_MAX_TIMESTAMP_LEN + 1]; +}; + +struct icnss_mem_region_info { + uint64_t reg_addr; + uint32_t size; + uint8_t secure_flag; +}; + +struct icnss_msi_user { + char *name; + int num_vectors; + u32 base_vector; +}; + +struct icnss_msi_config { + int total_vectors; + int total_users; + struct icnss_msi_user *users; +}; + +struct icnss_thermal_cdev { + struct list_head tcdev_list; + int tcdev_id; + unsigned long curr_thermal_state; + unsigned long max_thermal_state; + struct device_node *dev_node; + struct thermal_cooling_device *tcdev; +}; + +enum smp2p_out_entry { + ICNSS_SMP2P_OUT_POWER_SAVE, + ICNSS_SMP2P_OUT_SOC_WAKE, + ICNSS_SMP2P_OUT_EP_POWER_SAVE, + ICNSS_SMP2P_OUT_MAX +}; + +static const char * const icnss_smp2p_str[] = { + [ICNSS_SMP2P_OUT_POWER_SAVE] = "wlan-smp2p-out", + [ICNSS_SMP2P_OUT_SOC_WAKE] = "wlan-soc-wake-smp2p-out", + [ICNSS_SMP2P_OUT_EP_POWER_SAVE] = "wlan-ep-powersave-smp2p-out", +}; + +struct smp2p_out_info { + unsigned short seq; + unsigned int smem_bit; + struct qcom_smem_state *smem_state; +}; + +struct icnss_dms_data { + u8 mac_valid; + u8 nv_mac_not_prov; + u8 mac[QMI_WLFW_MAC_ADDR_SIZE_V01]; +}; + +struct icnss_ramdump_info { + int minor; + char name[32]; + struct device *dev; +}; + +struct icnss_priv { + uint32_t magic; + struct platform_device *pdev; + struct icnss_driver_ops *ops; + struct ce_irq_list ce_irq_list[ICNSS_MAX_IRQ_REGISTRATIONS]; + struct list_head vreg_list; + struct list_head clk_list; + struct icnss_cpr_info cpr_info; + unsigned long device_id; + struct icnss_msi_config *msi_config; + u32 msi_base_data; + struct icnss_control_params ctrl_params; + u8 cal_done; + u8 use_prefix_path; + u32 ce_irqs[ICNSS_MAX_IRQ_REGISTRATIONS]; + u32 srng_irqs[IWCN_MAX_IRQ_REGISTRATIONS]; + phys_addr_t mem_base_pa; + void __iomem *mem_base_va; + u32 mem_base_size; + phys_addr_t mhi_state_info_pa; + void __iomem *mhi_state_info_va; + u32 mhi_state_info_size; + struct iommu_domain *iommu_domain; + dma_addr_t smmu_iova_start; + size_t smmu_iova_len; + dma_addr_t smmu_iova_ipa_start; + dma_addr_t smmu_iova_ipa_current; + size_t smmu_iova_ipa_len; + struct qmi_handle qmi; + struct qmi_handle qmi_dms; + struct list_head event_list; + struct list_head soc_wake_msg_list; + spinlock_t event_lock; + spinlock_t soc_wake_msg_lock; + struct work_struct event_work; + struct work_struct fw_recv_msg_work; + struct work_struct soc_wake_msg_work; + struct workqueue_struct *event_wq; + struct workqueue_struct *soc_wake_wq; + phys_addr_t msa_pa; + phys_addr_t msi_addr_pa; + dma_addr_t msi_addr_iova; + uint32_t msa_mem_size; + void *msa_va; + unsigned long state; + struct wlfw_rf_chip_info chip_info; + uint32_t board_id; + uint32_t soc_id; + struct wlfw_fw_version_info fw_version_info; + char fw_build_id[WLFW_MAX_BUILD_ID_LEN + 1]; + u32 pwr_pin_result; + u32 phy_io_pin_result; + u32 rf_pin_result; + uint32_t nr_mem_region; + struct icnss_mem_region_info + mem_region[WLFW_MAX_NUM_MEMORY_REGIONS]; + struct dentry *root_dentry; + spinlock_t on_off_lock; + struct icnss_stats stats; + void *modem_notify_handler; + void *wpss_notify_handler; + struct notifier_block modem_ssr_nb; + struct notifier_block wpss_ssr_nb; + uint32_t diag_reg_read_addr; + uint32_t diag_reg_read_mem_type; + uint32_t diag_reg_read_len; + uint8_t *diag_reg_read_buf; + atomic_t pm_count; + struct icnss_ramdump_info *msa0_dump_dev; + struct icnss_ramdump_info *m3_dump_phyareg; + struct icnss_ramdump_info *m3_dump_phydbg; + struct icnss_ramdump_info *m3_dump_wmac0reg; + struct icnss_ramdump_info *m3_dump_wcssdbg; + struct icnss_ramdump_info *m3_dump_phyapdmem; + bool force_err_fatal; + bool allow_recursive_recovery; + bool early_crash_ind; + u8 cause_for_rejuvenation; + u8 requesting_sub_system; + u16 line_number; + struct mutex dev_lock; + uint32_t fw_error_fatal_irq; + uint32_t fw_early_crash_irq; + struct smp2p_out_info smp2p_info[ICNSS_SMP2P_OUT_MAX]; + struct completion unblock_shutdown; + struct adc_tm_param vph_monitor_params; + struct adc_tm_chip *adc_tm_dev; + struct iio_channel *channel; + uint64_t vph_pwr; + bool vbatt_supported; + char function_name[WLFW_FUNCTION_NAME_LEN + 1]; + bool is_ssr; + bool smmu_s1_enable; + struct kobject *icnss_kobject; + struct rproc *rproc; + atomic_t is_shutdown; + u32 qdss_mem_seg_len; + struct icnss_fw_mem qdss_mem[QMI_WLFW_MAX_NUM_MEM_SEG]; + void *get_info_cb_ctx; + int (*get_info_cb)(void *ctx, void *event, int event_len); + atomic_t soc_wake_ref_count; + phys_addr_t hang_event_data_pa; + void __iomem *hang_event_data_va; + uint16_t hang_event_data_len; + void *hang_event_data; + struct list_head icnss_tcdev_list; + struct mutex tcdev_lock; + bool is_chain1_supported; + bool chain_reg_info_updated; + u32 hw_trc_override; + struct icnss_dms_data dms; + u8 use_nv_mac; + struct pdr_handle *pdr_handle; + struct pdr_service *pdr_service; + bool root_pd_shutdown; + struct mbox_client mbox_client_data; + struct mbox_chan *mbox_chan; + u32 wlan_en_delay_ms; + struct class *icnss_ramdump_class; + dev_t icnss_ramdump_dev; + struct completion smp2p_soc_wake_wait; + uint32_t fw_soc_wake_ack_irq; + char foundry_name; + bool bdf_download_support; +}; + +struct icnss_reg_info { + uint32_t mem_type; + uint32_t reg_offset; + uint32_t data_len; +}; + +void icnss_free_qdss_mem(struct icnss_priv *priv); +char *icnss_driver_event_to_str(enum icnss_driver_event_type type); +int icnss_call_driver_uevent(struct icnss_priv *priv, + enum icnss_uevent uevent, void *data); +int icnss_driver_event_post(struct icnss_priv *priv, + enum icnss_driver_event_type type, + u32 flags, void *data); +void icnss_allow_recursive_recovery(struct device *dev); +void icnss_disallow_recursive_recovery(struct device *dev); +char *icnss_soc_wake_event_to_str(enum icnss_soc_wake_event_type type); +int icnss_soc_wake_event_post(struct icnss_priv *priv, + enum icnss_soc_wake_event_type type, + u32 flags, void *data); +int icnss_get_iova(struct icnss_priv *priv, u64 *addr, u64 *size); +int icnss_get_iova_ipa(struct icnss_priv *priv, u64 *addr, u64 *size); +int icnss_update_cpr_info(struct icnss_priv *priv); +void icnss_add_fw_prefix_name(struct icnss_priv *priv, char *prefix_name, + char *name); +int icnss_aop_mbox_init(struct icnss_priv *priv); +#endif + diff --git a/icnss2/power.c b/icnss2/power.c new file mode 100644 index 0000000000..5bc308b18d --- /dev/null +++ b/icnss2/power.c @@ -0,0 +1,942 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + */ +#include +#include +#if IS_ENABLED(CONFIG_MSM_QMP) +#include +#endif +#include +#include +#include +#include +#include "main.h" +#include "qmi.h" +#include "debug.h" +#include "power.h" + +static struct icnss_vreg_cfg icnss_wcn6750_vreg_list[] = { + {"vdd-cx-mx", 824000, 952000, 0, 0, 0, false, true}, + {"vdd-1.8-xo", 1872000, 1872000, 0, 0, 0, false, true}, + {"vdd-1.3-rfa", 1256000, 1352000, 0, 0, 0, false, true}, +}; + +static struct icnss_vreg_cfg icnss_adrestea_vreg_list[] = { + {"vdd-cx-mx", 752000, 752000, 0, 0, 0, false, true}, + {"vdd-1.8-xo", 1800000, 1800000, 0, 0, 0, false, true}, + {"vdd-1.3-rfa", 1304000, 1304000, 0, 0, 0, false, true}, + {"vdd-3.3-ch1", 3312000, 3312000, 0, 0, 0, false, true}, + {"vdd-3.3-ch0", 3312000, 3312000, 0, 0, 0, false, true}, +}; + +static struct icnss_clk_cfg icnss_clk_list[] = { + {"rf_clk", 0, 0}, +}; + +static struct icnss_clk_cfg icnss_adrestea_clk_list[] = { + {"cxo_ref_clk_pin", 0, 0}, +}; + +#define ICNSS_VREG_LIST_SIZE ARRAY_SIZE(icnss_wcn6750_vreg_list) +#define ICNSS_VREG_ADRESTEA_LIST_SIZE ARRAY_SIZE(icnss_adrestea_vreg_list) +#define ICNSS_CLK_LIST_SIZE ARRAY_SIZE(icnss_clk_list) +#define ICNSS_CLK_ADRESTEA_LIST_SIZE ARRAY_SIZE(icnss_adrestea_clk_list) + +#define ICNSS_CHAIN1_REGULATOR "vdd-3.3-ch1" +#define MAX_PROP_SIZE 32 +#define ICNSS_THRESHOLD_HIGH 3600000 +#define ICNSS_THRESHOLD_LOW 3450000 +#define ICNSS_THRESHOLD_GUARD 20000 + +#define BT_CXMX_VOLTAGE_MV 950 +#define ICNSS_MBOX_MSG_MAX_LEN 64 +#define ICNSS_MBOX_TIMEOUT_MS 1000 + +/** + * enum icnss_vreg_param: Voltage regulator TCS param + * @ICNSS_VREG_VOLTAGE: Provides voltage level to be configured in TCS + * @ICNSS_VREG_MODE: Regulator mode + * @ICNSS_VREG_ENABLE: Set Voltage regulator enable config in TCS + */ +enum icnss_vreg_param { + ICNSS_VREG_VOLTAGE, + ICNSS_VREG_MODE, + ICNSS_VREG_ENABLE, +}; + +/** + * enum icnss_tcs_seq: TCS sequence ID for trigger + * ICNSS_TCS_UP_SEQ: TCS Sequence based on up trigger / Wake TCS + * ICNSS_TCS_DOWN_SEQ: TCS Sequence based on down trigger / Sleep TCS + * ICNSS_TCS_ALL_SEQ: Update for both up and down triggers + */ +enum icnss_tcs_seq { + ICNSS_TCS_UP_SEQ, + ICNSS_TCS_DOWN_SEQ, + ICNSS_TCS_ALL_SEQ, +}; + +static int icnss_get_vreg_single(struct icnss_priv *priv, + struct icnss_vreg_info *vreg) +{ + int ret = 0; + struct device *dev = NULL; + struct regulator *reg = NULL; + const __be32 *prop = NULL; + char prop_name[MAX_PROP_SIZE] = {0}; + int len = 0; + int i; + + dev = &priv->pdev->dev; + + reg = devm_regulator_get_optional(dev, vreg->cfg.name); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + if (ret == -ENODEV) { + return ret; + } else if (ret == -EPROBE_DEFER) { + icnss_pr_info("EPROBE_DEFER for regulator: %s\n", + vreg->cfg.name); + goto out; + } else if (priv->device_id == ADRASTEA_DEVICE_ID) { + if (vreg->cfg.required) { + icnss_pr_err("Regulator %s doesn't exist: %d\n", + vreg->cfg.name, ret); + goto out; + } else { + icnss_pr_dbg("Optional regulator %s doesn't exist: %d\n", + vreg->cfg.name, ret); + goto done; + } + } else { + icnss_pr_err("Failed to get regulator %s, err = %d\n", + vreg->cfg.name, ret); + goto out; + } + } + + vreg->reg = reg; + + snprintf(prop_name, MAX_PROP_SIZE, "qcom,%s-config", + vreg->cfg.name); + + prop = of_get_property(dev->of_node, prop_name, &len); + + icnss_pr_dbg("Got regulator config, prop: %s, len: %d\n", + prop_name, len); + + if (!prop || len < (2 * sizeof(__be32))) { + icnss_pr_dbg("Property %s %s, use default\n", prop_name, + prop ? "invalid format" : "doesn't exist"); + goto done; + } + + for (i = 0; (i * sizeof(__be32)) < len; i++) { + switch (i) { + case 0: + vreg->cfg.min_uv = be32_to_cpup(&prop[0]); + break; + case 1: + vreg->cfg.max_uv = be32_to_cpup(&prop[1]); + break; + case 2: + vreg->cfg.load_ua = be32_to_cpup(&prop[2]); + break; + case 3: + vreg->cfg.delay_us = be32_to_cpup(&prop[3]); + break; + case 4: + if (priv->device_id == WCN6750_DEVICE_ID) + vreg->cfg.need_unvote = be32_to_cpup(&prop[4]); + else + vreg->cfg.need_unvote = 0; + break; + default: + icnss_pr_dbg("Property %s, ignoring value at %d\n", + prop_name, i); + break; + } + } + +done: + icnss_pr_dbg("Got regulator: %s, min_uv: %u, max_uv: %u, load_ua: %u, delay_us: %u, need_unvote: %u\n", + vreg->cfg.name, vreg->cfg.min_uv, + vreg->cfg.max_uv, vreg->cfg.load_ua, + vreg->cfg.delay_us, vreg->cfg.need_unvote); + + return 0; + +out: + return ret; +} + +static int icnss_vreg_on_single(struct icnss_vreg_info *vreg) +{ + int ret = 0; + + if (vreg->enabled) { + icnss_pr_dbg("Regulator %s is already enabled\n", + vreg->cfg.name); + return 0; + } + + icnss_pr_dbg("Regulator %s is being enabled\n", vreg->cfg.name); + + if (vreg->cfg.min_uv != 0 && vreg->cfg.max_uv != 0) { + ret = regulator_set_voltage(vreg->reg, + vreg->cfg.min_uv, + vreg->cfg.max_uv); + + if (ret) { + icnss_pr_err("Failed to set voltage for regulator %s, min_uv: %u, max_uv: %u, err = %d\n", + vreg->cfg.name, vreg->cfg.min_uv, + vreg->cfg.max_uv, ret); + goto out; + } + } + + if (vreg->cfg.load_ua) { + ret = regulator_set_load(vreg->reg, + vreg->cfg.load_ua); + + if (ret < 0) { + icnss_pr_err("Failed to set load for regulator %s, load: %u, err = %d\n", + vreg->cfg.name, vreg->cfg.load_ua, + ret); + goto out; + } + } + + if (vreg->cfg.delay_us) + udelay(vreg->cfg.delay_us); + + ret = regulator_enable(vreg->reg); + if (ret) { + icnss_pr_err("Failed to enable regulator %s, err = %d\n", + vreg->cfg.name, ret); + goto out; + } + + vreg->enabled = true; + +out: + return ret; +} + +static int icnss_vreg_unvote_single(struct icnss_vreg_info *vreg) +{ + int ret = 0; + + if (!vreg->enabled) { + icnss_pr_dbg("Regulator %s is already disabled\n", + vreg->cfg.name); + return 0; + } + + icnss_pr_dbg("Removing vote for Regulator %s\n", vreg->cfg.name); + + if (vreg->cfg.load_ua) { + ret = regulator_set_load(vreg->reg, 0); + if (ret < 0) + icnss_pr_err("Failed to set load for regulator %s, err = %d\n", + vreg->cfg.name, ret); + } + + if (vreg->cfg.min_uv != 0 && vreg->cfg.max_uv != 0) { + ret = regulator_set_voltage(vreg->reg, 0, + vreg->cfg.max_uv); + if (ret) + icnss_pr_err("Failed to set voltage for regulator %s, err = %d\n", + vreg->cfg.name, ret); + } + + return ret; +} + +static int icnss_vreg_off_single(struct icnss_vreg_info *vreg) +{ + int ret = 0; + + if (!vreg->enabled) { + icnss_pr_dbg("Regulator %s is already disabled\n", + vreg->cfg.name); + return 0; + } + + icnss_pr_dbg("Regulator %s is being disabled\n", + vreg->cfg.name); + + ret = regulator_disable(vreg->reg); + if (ret) + icnss_pr_err("Failed to disable regulator %s, err = %d\n", + vreg->cfg.name, ret); + + if (vreg->cfg.load_ua) { + ret = regulator_set_load(vreg->reg, 0); + if (ret < 0) + icnss_pr_err("Failed to set load for regulator %s, err = %d\n", + vreg->cfg.name, ret); + } + + if (vreg->cfg.min_uv != 0 && vreg->cfg.max_uv != 0) { + ret = regulator_set_voltage(vreg->reg, 0, + vreg->cfg.max_uv); + if (ret) + icnss_pr_err("Failed to set voltage for regulator %s, err = %d\n", + vreg->cfg.name, ret); + } + vreg->enabled = false; + + return ret; +} + +static struct icnss_vreg_cfg *get_vreg_list(u32 *vreg_list_size, + unsigned long device_id) +{ + switch (device_id) { + case WCN6750_DEVICE_ID: + *vreg_list_size = ICNSS_VREG_LIST_SIZE; + return icnss_wcn6750_vreg_list; + + case ADRASTEA_DEVICE_ID: + *vreg_list_size = ICNSS_VREG_ADRESTEA_LIST_SIZE; + return icnss_adrestea_vreg_list; + + default: + icnss_pr_err("Unsupported device_id 0x%x\n", device_id); + *vreg_list_size = 0; + return NULL; + } +} + +int icnss_get_vreg(struct icnss_priv *priv) +{ + int ret = 0; + int i; + struct icnss_vreg_info *vreg; + struct icnss_vreg_cfg *vreg_cfg = NULL; + struct list_head *vreg_list = &priv->vreg_list; + struct device *dev = &priv->pdev->dev; + u32 vreg_list_size = 0; + + vreg_cfg = get_vreg_list(&vreg_list_size, priv->device_id); + if (!vreg_cfg) + return -EINVAL; + + for (i = 0; i < vreg_list_size; i++) { + vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL); + if (!vreg) + return -ENOMEM; + + memcpy(&vreg->cfg, &vreg_cfg[i], sizeof(vreg->cfg)); + ret = icnss_get_vreg_single(priv, vreg); + if (ret != 0) { + if (ret == -ENODEV) + continue; + else + return ret; + } + list_add_tail(&vreg->list, vreg_list); + } + + return 0; +} + +void icnss_put_vreg(struct icnss_priv *priv) +{ + struct list_head *vreg_list = &priv->vreg_list; + struct icnss_vreg_info *vreg = NULL; + + while (!list_empty(vreg_list)) { + vreg = list_first_entry(vreg_list, + struct icnss_vreg_info, list); + list_del(&vreg->list); + } +} + +static int icnss_vreg_on(struct icnss_priv *priv) +{ + struct list_head *vreg_list = &priv->vreg_list; + struct icnss_vreg_info *vreg = NULL; + int ret = 0; + + list_for_each_entry(vreg, vreg_list, list) { + if (IS_ERR_OR_NULL(vreg->reg) || !vreg->cfg.is_supported) + continue; + if (!priv->chain_reg_info_updated && + !strcmp(ICNSS_CHAIN1_REGULATOR, vreg->cfg.name)) { + priv->chain_reg_info_updated = true; + if (!priv->is_chain1_supported) { + vreg->cfg.is_supported = false; + continue; + } + } + + ret = icnss_vreg_on_single(vreg); + if (ret) + break; + } + + if (!ret) + return 0; + + list_for_each_entry_continue_reverse(vreg, vreg_list, list) { + if (IS_ERR_OR_NULL(vreg->reg) || !vreg->enabled) + continue; + + icnss_vreg_off_single(vreg); + } + + return ret; +} + +static int icnss_vreg_off(struct icnss_priv *priv) +{ + struct list_head *vreg_list = &priv->vreg_list; + struct icnss_vreg_info *vreg = NULL; + + list_for_each_entry_reverse(vreg, vreg_list, list) { + if (IS_ERR_OR_NULL(vreg->reg)) + continue; + + icnss_vreg_off_single(vreg); + } + + return 0; +} + +int icnss_vreg_unvote(struct icnss_priv *priv) +{ + struct list_head *vreg_list = &priv->vreg_list; + struct icnss_vreg_info *vreg = NULL; + + list_for_each_entry_reverse(vreg, vreg_list, list) { + if (IS_ERR_OR_NULL(vreg->reg)) + continue; + + if (vreg->cfg.need_unvote) + icnss_vreg_unvote_single(vreg); + } + + return 0; +} + +int icnss_get_clk_single(struct icnss_priv *priv, + struct icnss_clk_info *clk_info) +{ + struct device *dev = &priv->pdev->dev; + struct clk *clk; + int ret; + + clk = devm_clk_get(dev, clk_info->cfg.name); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + if (clk_info->cfg.required) + icnss_pr_err("Failed to get clock %s, err = %d\n", + clk_info->cfg.name, ret); + else + icnss_pr_dbg("Failed to get optional clock %s, err = %d\n", + clk_info->cfg.name, ret); + return ret; + } + + clk_info->clk = clk; + icnss_pr_dbg("Got clock: %s, freq: %u\n", + clk_info->cfg.name, clk_info->cfg.freq); + + return 0; +} + +static int icnss_clk_on_single(struct icnss_clk_info *clk_info) +{ + int ret; + + if (clk_info->enabled) { + icnss_pr_dbg("Clock %s is already enabled\n", + clk_info->cfg.name); + return 0; + } + + icnss_pr_dbg("Clock %s is being enabled\n", clk_info->cfg.name); + + if (clk_info->cfg.freq) { + ret = clk_set_rate(clk_info->clk, clk_info->cfg.freq); + if (ret) { + icnss_pr_err("Failed to set frequency %u for clock %s, err = %d\n", + clk_info->cfg.freq, clk_info->cfg.name, + ret); + return ret; + } + } + + ret = clk_prepare_enable(clk_info->clk); + if (ret) { + icnss_pr_err("Failed to enable clock %s, err = %d\n", + clk_info->cfg.name, ret); + return ret; + } + + clk_info->enabled = true; + + return 0; +} + +static int icnss_clk_off_single(struct icnss_clk_info *clk_info) +{ + if (!clk_info->enabled) { + icnss_pr_dbg("Clock %s is already disabled\n", + clk_info->cfg.name); + return 0; + } + + icnss_pr_dbg("Clock %s is being disabled\n", clk_info->cfg.name); + + clk_disable_unprepare(clk_info->clk); + clk_info->enabled = false; + + return 0; +} + +int icnss_get_clk(struct icnss_priv *priv) +{ + struct device *dev; + struct list_head *clk_list; + struct icnss_clk_info *clk_info; + struct icnss_clk_cfg *clk_cfg; + int ret, i; + u32 clk_list_size = 0; + + if (!priv) + return -ENODEV; + + dev = &priv->pdev->dev; + clk_list = &priv->clk_list; + + if (priv->device_id == ADRASTEA_DEVICE_ID) { + clk_cfg = icnss_adrestea_clk_list; + clk_list_size = ICNSS_CLK_ADRESTEA_LIST_SIZE; + } else if (priv->device_id == WCN6750_DEVICE_ID) { + clk_cfg = icnss_clk_list; + clk_list_size = ICNSS_CLK_LIST_SIZE; + } + + if (!list_empty(clk_list)) { + icnss_pr_dbg("Clocks have already been updated\n"); + return 0; + } + + for (i = 0; i < clk_list_size; i++) { + clk_info = devm_kzalloc(dev, sizeof(*clk_info), GFP_KERNEL); + if (!clk_info) { + ret = -ENOMEM; + goto cleanup; + } + + memcpy(&clk_info->cfg, &clk_cfg[i], + sizeof(clk_info->cfg)); + ret = icnss_get_clk_single(priv, clk_info); + if (ret != 0) { + if (clk_info->cfg.required) + goto cleanup; + else + continue; + } + list_add_tail(&clk_info->list, clk_list); + } + + return 0; + +cleanup: + while (!list_empty(clk_list)) { + clk_info = list_first_entry(clk_list, struct icnss_clk_info, + list); + list_del(&clk_info->list); + } + + return ret; +} + +void icnss_put_clk(struct icnss_priv *priv) +{ + struct device *dev; + struct list_head *clk_list; + struct icnss_clk_info *clk_info; + + if (!priv) + return; + + dev = &priv->pdev->dev; + clk_list = &priv->clk_list; + + while (!list_empty(clk_list)) { + clk_info = list_first_entry(clk_list, struct icnss_clk_info, + list); + list_del(&clk_info->list); + } +} + +static int icnss_clk_on(struct list_head *clk_list) +{ + struct icnss_clk_info *clk_info; + int ret = 0; + + list_for_each_entry(clk_info, clk_list, list) { + if (IS_ERR_OR_NULL(clk_info->clk)) + continue; + ret = icnss_clk_on_single(clk_info); + if (ret) + break; + } + + if (!ret) + return 0; + + list_for_each_entry_continue_reverse(clk_info, clk_list, list) { + if (IS_ERR_OR_NULL(clk_info->clk)) + continue; + + icnss_clk_off_single(clk_info); + } + + return ret; +} + +static int icnss_clk_off(struct list_head *clk_list) +{ + struct icnss_clk_info *clk_info; + + list_for_each_entry_reverse(clk_info, clk_list, list) { + if (IS_ERR_OR_NULL(clk_info->clk)) + continue; + + icnss_clk_off_single(clk_info); + } + + return 0; +} + +int icnss_hw_power_on(struct icnss_priv *priv) +{ + int ret = 0; + + icnss_pr_dbg("HW Power on: state: 0x%lx\n", priv->state); + + spin_lock(&priv->on_off_lock); + if (test_bit(ICNSS_POWER_ON, &priv->state)) { + spin_unlock(&priv->on_off_lock); + return ret; + } + set_bit(ICNSS_POWER_ON, &priv->state); + spin_unlock(&priv->on_off_lock); + + ret = icnss_vreg_on(priv); + if (ret) { + icnss_pr_err("Failed to turn on vreg, err = %d\n", ret); + goto out; + } + + ret = icnss_clk_on(&priv->clk_list); + if (ret) + goto vreg_off; + + return ret; + +vreg_off: + icnss_vreg_off(priv); +out: + clear_bit(ICNSS_POWER_ON, &priv->state); + return ret; +} + +int icnss_hw_power_off(struct icnss_priv *priv) +{ + int ret = 0; + + if (test_bit(HW_ALWAYS_ON, &priv->ctrl_params.quirks)) + return 0; + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) + return 0; + + icnss_pr_dbg("HW Power off: 0x%lx\n", priv->state); + + spin_lock(&priv->on_off_lock); + if (!test_bit(ICNSS_POWER_ON, &priv->state)) { + spin_unlock(&priv->on_off_lock); + return ret; + } + clear_bit(ICNSS_POWER_ON, &priv->state); + spin_unlock(&priv->on_off_lock); + + icnss_clk_off(&priv->clk_list); + + ret = icnss_vreg_off(priv); + + return ret; +} + +int icnss_power_on(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Invalid drvdata: dev %pK, data %pK\n", + dev, priv); + return -EINVAL; + } + + icnss_pr_dbg("Power On: 0x%lx\n", priv->state); + + return icnss_hw_power_on(priv); +} +EXPORT_SYMBOL(icnss_power_on); + +int icnss_power_off(struct device *dev) +{ + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Invalid drvdata: dev %pK, data %pK\n", + dev, priv); + return -EINVAL; + } + + icnss_pr_dbg("Power Off: 0x%lx\n", priv->state); + + return icnss_hw_power_off(priv); +} +EXPORT_SYMBOL(icnss_power_off); + +void icnss_put_resources(struct icnss_priv *priv) +{ + icnss_put_clk(priv); + icnss_put_vreg(priv); +} + +static int icnss_get_phone_power(struct icnss_priv *priv, uint64_t *result_uv) +{ + int ret = 0; + int result; + + if (!priv->channel) { + icnss_pr_err("Channel doesn't exists\n"); + ret = -EINVAL; + goto out; + } + + ret = iio_read_channel_processed(priv->channel, &result); + if (ret < 0) { + icnss_pr_err("Error reading channel, ret = %d\n", ret); + goto out; + } + + *result_uv = (uint64_t)result; +out: + return ret; +} + +static void icnss_vph_notify(enum adc_tm_state state, void *ctx) +{ + struct icnss_priv *priv = ctx; + u64 vph_pwr = 0; + u64 vph_pwr_prev; + int ret = 0; + bool update = true; + + if (!priv) { + icnss_pr_err("Priv pointer is NULL\n"); + return; + } + + vph_pwr_prev = priv->vph_pwr; + + ret = icnss_get_phone_power(priv, &vph_pwr); + if (ret < 0) + return; + + if (vph_pwr < ICNSS_THRESHOLD_LOW) { + if (vph_pwr_prev < ICNSS_THRESHOLD_LOW) + update = false; + priv->vph_monitor_params.state_request = + ADC_TM_HIGH_THR_ENABLE; + priv->vph_monitor_params.high_thr = ICNSS_THRESHOLD_LOW + + ICNSS_THRESHOLD_GUARD; + priv->vph_monitor_params.low_thr = 0; + } else if (vph_pwr > ICNSS_THRESHOLD_HIGH) { + if (vph_pwr_prev > ICNSS_THRESHOLD_HIGH) + update = false; + priv->vph_monitor_params.state_request = + ADC_TM_LOW_THR_ENABLE; + priv->vph_monitor_params.low_thr = ICNSS_THRESHOLD_HIGH - + ICNSS_THRESHOLD_GUARD; + priv->vph_monitor_params.high_thr = 0; + } else { + if (vph_pwr_prev > ICNSS_THRESHOLD_LOW && + vph_pwr_prev < ICNSS_THRESHOLD_HIGH) + update = false; + priv->vph_monitor_params.state_request = + ADC_TM_HIGH_LOW_THR_ENABLE; + priv->vph_monitor_params.low_thr = ICNSS_THRESHOLD_LOW; + priv->vph_monitor_params.high_thr = ICNSS_THRESHOLD_HIGH; + } + + priv->vph_pwr = vph_pwr; + + if (update) { + icnss_send_vbatt_update(priv, vph_pwr); + icnss_pr_dbg("set low threshold to %d, high threshold to %d Phone power=%llu\n", + priv->vph_monitor_params.low_thr, + priv->vph_monitor_params.high_thr, vph_pwr); + } + + ret = adc_tm_channel_measure(priv->adc_tm_dev, + &priv->vph_monitor_params); + if (ret) + icnss_pr_err("TM channel setup failed %d\n", ret); +} + +static int icnss_setup_vph_monitor(struct icnss_priv *priv) +{ + int ret = 0; + + if (!priv->adc_tm_dev) { + icnss_pr_err("ADC TM handler is NULL\n"); + ret = -EINVAL; + goto out; + } + + priv->vph_monitor_params.low_thr = ICNSS_THRESHOLD_LOW; + priv->vph_monitor_params.high_thr = ICNSS_THRESHOLD_HIGH; + priv->vph_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; + priv->vph_monitor_params.channel = ADC5_VBAT_SNS; + priv->vph_monitor_params.btm_ctx = priv; + priv->vph_monitor_params.threshold_notification = &icnss_vph_notify; + icnss_pr_dbg("Set low threshold to %d, high threshold to %d\n", + priv->vph_monitor_params.low_thr, + priv->vph_monitor_params.high_thr); + + ret = adc_tm_channel_measure(priv->adc_tm_dev, + &priv->vph_monitor_params); + if (ret) + icnss_pr_err("TM channel setup failed %d\n", ret); +out: + return ret; +} + +int icnss_init_vph_monitor(struct icnss_priv *priv) +{ + int ret = 0; + + ret = icnss_get_phone_power(priv, &priv->vph_pwr); + if (ret < 0) + goto out; + + icnss_pr_dbg("Phone power=%llu\n", priv->vph_pwr); + + icnss_send_vbatt_update(priv, priv->vph_pwr); + + ret = icnss_setup_vph_monitor(priv); + if (ret) + goto out; +out: + return ret; +} + +int icnss_aop_mbox_init(struct icnss_priv *priv) +{ + struct mbox_client *mbox = &priv->mbox_client_data; + struct mbox_chan *chan; + int ret = 0; + + ret = of_property_read_string(priv->pdev->dev.of_node, + "qcom,vreg_ol_cpr", + &priv->cpr_info.vreg_ol_cpr); + if (ret) { + icnss_pr_dbg("Vreg for OL CPR not configured\n"); + return -EINVAL; + } + + mbox->dev = &priv->pdev->dev; + mbox->tx_block = true; + mbox->tx_tout = ICNSS_MBOX_TIMEOUT_MS; + mbox->knows_txdone = false; + + priv->mbox_chan = NULL; + chan = mbox_request_channel(mbox, 0); + if (IS_ERR(chan)) { + ret = PTR_ERR(chan); + icnss_pr_err("Failed to get mbox channel with err %d\n", ret); + return ret; + } + priv->mbox_chan = chan; + + icnss_pr_dbg("Mbox channel initialized\n"); + return 0; +} + +#if IS_ENABLED(CONFIG_MSM_QMP) +static int icnss_aop_set_vreg_param(struct icnss_priv *priv, + const char *vreg_name, + enum icnss_vreg_param param, + enum icnss_tcs_seq seq, int val) +{ + struct qmp_pkt pkt; + char mbox_msg[ICNSS_MBOX_MSG_MAX_LEN]; + static const char * const vreg_param_str[] = {"v", "m", "e"}; + static const char *const tcs_seq_str[] = {"upval", "dwnval", "enable"}; + int ret = 0; + + if (param > ICNSS_VREG_ENABLE || seq > ICNSS_TCS_ALL_SEQ || !vreg_name) + return -EINVAL; + + snprintf(mbox_msg, ICNSS_MBOX_MSG_MAX_LEN, + "{class: wlan_pdc, res: %s.%s, %s: %d}", vreg_name, + vreg_param_str[param], tcs_seq_str[seq], val); + + icnss_pr_dbg("Sending AOP Mbox msg: %s\n", mbox_msg); + pkt.size = ICNSS_MBOX_MSG_MAX_LEN; + pkt.data = mbox_msg; + + ret = mbox_send_message(priv->mbox_chan, &pkt); + if (ret < 0) + icnss_pr_err("Failed to send AOP mbox msg: %s,ret: %d\n", + mbox_msg, ret); + else + ret = 0; + + return ret; +} +#else +static int icnss_aop_set_vreg_param(struct icnss_priv *priv, + const char *vreg_name, + enum icnss_vreg_param param, + enum icnss_tcs_seq seq, int val) +{ + return 0; +} +#endif + +int icnss_update_cpr_info(struct icnss_priv *priv) +{ + struct icnss_cpr_info *cpr_info = &priv->cpr_info; + + if (!cpr_info->vreg_ol_cpr || !priv->mbox_chan) { + icnss_pr_dbg("Mbox channel / OL CPR Vreg not configured\n"); + return 0; + } + + if (cpr_info->voltage == 0) { + icnss_pr_err("Voltage %dmV is not valid\n", cpr_info->voltage); + return -EINVAL; + } + + cpr_info->voltage = cpr_info->voltage > BT_CXMX_VOLTAGE_MV ? + cpr_info->voltage : BT_CXMX_VOLTAGE_MV; + + return icnss_aop_set_vreg_param(priv, + cpr_info->vreg_ol_cpr, + ICNSS_VREG_VOLTAGE, + ICNSS_TCS_UP_SEQ, + cpr_info->voltage); +} diff --git a/icnss2/power.h b/icnss2/power.h new file mode 100644 index 0000000000..e6832e6368 --- /dev/null +++ b/icnss2/power.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __ICNSS_POWER_H__ +#define __ICNSS_POWER_H__ + +int icnss_hw_power_on(struct icnss_priv *priv); +int icnss_hw_power_off(struct icnss_priv *priv); +int icnss_get_clk(struct icnss_priv *priv); +int icnss_get_vreg(struct icnss_priv *priv); +int icnss_init_vph_monitor(struct icnss_priv *priv); +void icnss_put_resources(struct icnss_priv *priv); +void icnss_put_vreg(struct icnss_priv *priv); +void icnss_put_clk(struct icnss_priv *priv); +int icnss_vreg_unvote(struct icnss_priv *priv); + +#endif diff --git a/icnss2/qmi.c b/icnss2/qmi.c new file mode 100644 index 0000000000..d76128a2cb --- /dev/null +++ b/icnss2/qmi.c @@ -0,0 +1,3469 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "icnss2_qmi: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlan_firmware_service_v01.h" +#include "main.h" +#include "qmi.h" +#include "debug.h" +#include "genl.h" + +#define WLFW_SERVICE_WCN_INS_ID_V01 3 +#define WLFW_SERVICE_INS_ID_V01 0 +#define WLFW_CLIENT_ID 0x4b4e454c +#define QMI_ERR_PLAT_CCPM_CLK_INIT_FAILED 0x77 + +#define BDF_FILE_NAME_PREFIX "bdwlan" +#define ELF_BDF_FILE_NAME "bdwlan.elf" +#define ELF_BDF_FILE_NAME_PREFIX "bdwlan.e" +#define BIN_BDF_FILE_NAME "bdwlan.bin" +#define BIN_BDF_FILE_NAME_PREFIX "bdwlan." +#define REGDB_FILE_NAME "regdb.bin" +#define DUMMY_BDF_FILE_NAME "bdwlan.dmy" + +#define QDSS_TRACE_CONFIG_FILE "qdss_trace_config.cfg" + +#define WLAN_BOARD_ID_INDEX 0x100 +#define DEVICE_BAR_SIZE 0x200000 +#define M3_SEGMENT_ADDR_MASK 0xFFFFFFFF +#define DMS_QMI_MAX_MSG_LEN SZ_256 +#define DMS_MAC_NOT_PROVISIONED 16 +#define BDWLAN_SIZE 6 +#define UMC_CHIP_ID 0x4320 + +#ifdef CONFIG_ICNSS2_DEBUG +bool ignore_fw_timeout; +#define ICNSS_QMI_ASSERT() ICNSS_ASSERT(ignore_fw_timeout) +#else +#define ICNSS_QMI_ASSERT() do { } while (0) +#endif + +#ifdef CONFIG_ICNSS2_DEBUG +void icnss_ignore_fw_timeout(bool ignore) +{ + ignore_fw_timeout = ignore; +} +#else +void icnss_ignore_fw_timeout(bool ignore) { } +#endif + +#define icnss_qmi_fatal_err(_fmt, ...) do { \ + icnss_pr_err("fatal: "_fmt, ##__VA_ARGS__); \ + ICNSS_QMI_ASSERT(); \ + } while (0) + +int wlfw_msa_mem_info_send_sync_msg(struct icnss_priv *priv) +{ + int ret; + int i; + struct wlfw_msa_info_req_msg_v01 *req; + struct wlfw_msa_info_resp_msg_v01 *resp; + struct qmi_txn txn; + uint64_t max_mapped_addr; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Sending MSA mem info, state: 0x%lx\n", priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->msa_addr = priv->msa_pa; + req->size = priv->msa_mem_size; + + priv->stats.msa_info_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_msa_info_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err( + "Fail to init txn for MSA Mem info resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_MSA_INFO_REQ_V01, + WLFW_MSA_INFO_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_msa_info_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send MSA Mem info req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_qmi_fatal_err("MSA Mem info resp wait failed ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "QMI MSA Mem info request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + icnss_pr_dbg("Receive mem_region_info_len: %d\n", + resp->mem_region_info_len); + + if (resp->mem_region_info_len > QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01) { + icnss_qmi_fatal_err( + "Invalid memory region length received: %d\n", + resp->mem_region_info_len); + ret = -EINVAL; + goto out; + } + + max_mapped_addr = priv->msa_pa + priv->msa_mem_size; + priv->stats.msa_info_resp++; + priv->nr_mem_region = resp->mem_region_info_len; + for (i = 0; i < resp->mem_region_info_len; i++) { + + if (resp->mem_region_info[i].size > priv->msa_mem_size || + resp->mem_region_info[i].region_addr >= max_mapped_addr || + resp->mem_region_info[i].region_addr < priv->msa_pa || + resp->mem_region_info[i].size + + resp->mem_region_info[i].region_addr > max_mapped_addr) { + icnss_pr_dbg("Received out of range Addr: 0x%llx Size: 0x%x\n", + resp->mem_region_info[i].region_addr, + resp->mem_region_info[i].size); + ret = -EINVAL; + goto fail_unwind; + } + + priv->mem_region[i].reg_addr = + resp->mem_region_info[i].region_addr; + priv->mem_region[i].size = + resp->mem_region_info[i].size; + priv->mem_region[i].secure_flag = + resp->mem_region_info[i].secure_flag; + icnss_pr_dbg("Memory Region: %d Addr: 0x%llx Size: 0x%x Flag: 0x%08x\n", + i, priv->mem_region[i].reg_addr, + priv->mem_region[i].size, + priv->mem_region[i].secure_flag); + } + + kfree(resp); + kfree(req); + return 0; + +fail_unwind: + memset(&priv->mem_region[0], 0, sizeof(priv->mem_region[0]) * i); +out: + kfree(resp); + kfree(req); + priv->stats.msa_info_err++; + return ret; +} + +int wlfw_msa_ready_send_sync_msg(struct icnss_priv *priv) +{ + int ret; + struct wlfw_msa_ready_req_msg_v01 *req; + struct wlfw_msa_ready_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Sending MSA ready request message, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + priv->stats.msa_ready_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_msa_ready_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err( + "Fail to init txn for MSA Mem Ready resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_MSA_READY_REQ_V01, + WLFW_MSA_READY_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_msa_ready_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send MSA Mem Ready req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_qmi_fatal_err( + "MSA Mem Ready resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "QMI MSA Mem Ready request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.msa_ready_resp++; + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(resp); + kfree(req); + priv->stats.msa_ready_err++; + return ret; +} + +int wlfw_device_info_send_msg(struct icnss_priv *priv) +{ + int ret; + struct wlfw_device_info_req_msg_v01 *req; + struct wlfw_device_info_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Sending Device Info request message, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + priv->stats.device_info_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_device_info_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err( + "Fail to init txn for Device Info resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_DEVICE_INFO_REQ_V01, + WLFW_DEVICE_INFO_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_device_info_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send device info req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_qmi_fatal_err( + "Device Info resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "QMI Device info request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.device_info_resp++; + + if (resp->bar_addr_valid) + priv->mem_base_pa = resp->bar_addr; + + if (resp->bar_size_valid) + priv->mem_base_size = resp->bar_size; + + if (!priv->mem_base_pa) { + ret = -EINVAL; + icnss_qmi_fatal_err("Fail to get bar address\n"); + goto out; + } + + if (priv->mem_base_size < DEVICE_BAR_SIZE) { + ret = -EINVAL; + icnss_qmi_fatal_err("Bar size is not proper 0x%x\n", + priv->mem_base_size); + goto out; + } + + if (resp->mhi_state_info_addr_valid) + priv->mhi_state_info_pa = resp->mhi_state_info_addr; + + if (resp->mhi_state_info_size_valid) + priv->mhi_state_info_size = resp->mhi_state_info_size; + + if (!priv->mhi_state_info_pa) + icnss_pr_err("Fail to get MHI info address\n"); + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(resp); + kfree(req); + priv->stats.device_info_err++; + return ret; +} + +int wlfw_power_save_send_msg(struct icnss_priv *priv, + enum wlfw_power_save_mode_v01 mode) +{ + int ret; + struct wlfw_power_save_req_msg_v01 *req; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) + return -EINVAL; + + if (test_bit(ICNSS_PD_RESTART, &priv->state) || + !test_bit(ICNSS_MODE_ON, &priv->state)) + return 0; + + icnss_pr_dbg("Sending power save mode: %d, state: 0x%lx\n", + mode, priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->power_save_mode_valid = 1; + req->power_save_mode = mode; + + if (mode == WLFW_POWER_SAVE_EXIT_V01) + priv->stats.exit_power_save_req++; + else + priv->stats.enter_power_save_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, + NULL, NULL); + if (ret < 0) { + icnss_qmi_fatal_err("Fail to init txn for exit power save%d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_POWER_SAVE_REQ_V01, + WLFW_POWER_SAVE_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_power_save_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send exit power save req %d\n", + ret); + goto out; + } + + qmi_txn_cancel(&txn); + + if (mode == WLFW_POWER_SAVE_EXIT_V01) + priv->stats.exit_power_save_resp++; + else + priv->stats.enter_power_save_resp++; + + kfree(req); + return 0; + +out: + kfree(req); + + if (mode == WLFW_POWER_SAVE_EXIT_V01) + priv->stats.exit_power_save_err++; + else + priv->stats.enter_power_save_err++; + return ret; +} + +int wlfw_send_soc_wake_msg(struct icnss_priv *priv, + enum wlfw_soc_wake_enum_v01 type) +{ + int ret; + struct wlfw_soc_wake_req_msg_v01 *req; + struct wlfw_soc_wake_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) + return -EINVAL; + + icnss_pr_soc_wake("Sending soc wake msg, type: 0x%x\n", + type); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + req->wake_valid = 1; + req->wake = type; + + priv->stats.soc_wake_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_soc_wake_resp_msg_v01_ei, resp); + + if (ret < 0) { + icnss_pr_err("Fail to init txn for wake msg resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_SOC_WAKE_REQ_V01, + WLFW_SOC_WAKE_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_soc_wake_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send soc wake msg %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_qmi_fatal_err("SOC wake timed out with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "SOC wake request rejected,result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.soc_wake_resp++; + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(req); + kfree(resp); + priv->stats.soc_wake_err++; + return ret; +} + +int wlfw_ind_register_send_sync_msg(struct icnss_priv *priv) +{ + int ret; + struct wlfw_ind_register_req_msg_v01 *req; + struct wlfw_ind_register_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Sending indication register message, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->client_id_valid = 1; + req->client_id = WLFW_CLIENT_ID; + req->fw_ready_enable_valid = 1; + req->fw_ready_enable = 1; + req->pin_connect_result_enable_valid = 1; + req->pin_connect_result_enable = 1; + + if (priv->device_id == ADRASTEA_DEVICE_ID) { + req->msa_ready_enable_valid = 1; + req->msa_ready_enable = 1; + if (test_bit(FW_REJUVENATE_ENABLE, + &priv->ctrl_params.quirks)) { + req->rejuvenate_enable_valid = 1; + req->rejuvenate_enable = 1; + } + } else if (priv->device_id == WCN6750_DEVICE_ID) { + req->fw_init_done_enable_valid = 1; + req->fw_init_done_enable = 1; + req->cal_done_enable_valid = 1; + req->cal_done_enable = 1; + req->qdss_trace_req_mem_enable_valid = 1; + req->qdss_trace_req_mem_enable = 1; + req->qdss_trace_save_enable_valid = 1; + req->qdss_trace_save_enable = 1; + req->qdss_trace_free_enable_valid = 1; + req->qdss_trace_free_enable = 1; + req->respond_get_info_enable_valid = 1; + req->respond_get_info_enable = 1; + req->m3_dump_upload_segments_req_enable_valid = 1; + req->m3_dump_upload_segments_req_enable = 1; + } + + priv->stats.ind_register_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_ind_register_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err( + "Fail to init txn for Ind Register resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_IND_REGISTER_REQ_V01, + WLFW_IND_REGISTER_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_ind_register_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send Ind Register req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_qmi_fatal_err( + "Ind Register resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "QMI Ind Register request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.ind_register_resp++; + + if (resp->fw_status_valid && + (resp->fw_status & QMI_WLFW_ALREADY_REGISTERED_V01)) { + ret = -EALREADY; + icnss_pr_dbg("WLFW already registered\n"); + goto qmi_registered; + } + + kfree(resp); + kfree(req); + + return 0; + +out: + priv->stats.ind_register_err++; +qmi_registered: + kfree(resp); + kfree(req); + return ret; +} + +int wlfw_cal_report_req(struct icnss_priv *priv) +{ + int ret; + struct wlfw_cal_report_req_msg_v01 *req; + struct wlfw_cal_report_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) + return -EINVAL; + + icnss_pr_info("Sending cal report request, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + req->meta_data_len = 0; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_cal_report_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err("Fail to init txn for cal report req %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_CAL_REPORT_REQ_V01, + WLFW_CAL_REPORT_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_cal_report_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send cal report req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, + priv->ctrl_params.qmi_timeout); + + if (ret < 0) { + icnss_qmi_fatal_err("Cal report wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err("QMI cal report request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + kfree(resp); + kfree(req); + + return 0; + +out: + return ret; +} + +int wlfw_cap_send_sync_msg(struct icnss_priv *priv) +{ + int ret; + struct wlfw_cap_req_msg_v01 *req; + struct wlfw_cap_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Sending target capability message, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + priv->stats.cap_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, wlfw_cap_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err("Fail to init txn for Capability resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_CAP_REQ_V01, + WLFW_CAP_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_cap_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send Capability req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, + priv->ctrl_params.qmi_timeout + + msecs_to_jiffies(priv->wlan_en_delay_ms)); + if (ret < 0) { + icnss_qmi_fatal_err("Capability resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + ret = -resp->resp.result; + if (resp->resp.error == QMI_ERR_PLAT_CCPM_CLK_INIT_FAILED) { + icnss_pr_err("RF card not present\n"); + goto out; + } + icnss_qmi_fatal_err( + "QMI Capability request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + goto out; + } + + priv->stats.cap_resp++; + + if (resp->chip_info_valid) { + priv->chip_info.chip_id = resp->chip_info.chip_id; + priv->chip_info.chip_family = resp->chip_info.chip_family; + } + if (resp->board_info_valid) + priv->board_id = resp->board_info.board_id; + else + priv->board_id = 0xFF; + if (resp->soc_info_valid) + priv->soc_id = resp->soc_info.soc_id; + if (resp->fw_version_info_valid) { + priv->fw_version_info.fw_version = + resp->fw_version_info.fw_version; + strlcpy(priv->fw_version_info.fw_build_timestamp, + resp->fw_version_info.fw_build_timestamp, + WLFW_MAX_TIMESTAMP_LEN + 1); + } + + if (resp->voltage_mv_valid) { + priv->cpr_info.voltage = resp->voltage_mv; + icnss_pr_dbg("Voltage for CPR: %dmV\n", + priv->cpr_info.voltage); + icnss_update_cpr_info(priv); + } + + if (resp->fw_build_id_valid) + strlcpy(priv->fw_build_id, resp->fw_build_id, + QMI_WLFW_MAX_BUILD_ID_LEN_V01 + 1); + + if (resp->rd_card_chain_cap_valid && + resp->rd_card_chain_cap == WLFW_RD_CARD_CHAIN_CAP_1x1_V01) + priv->is_chain1_supported = false; + + if (resp->foundry_name_valid) + priv->foundry_name = resp->foundry_name[0]; + else if (resp->chip_info_valid && priv->chip_info.chip_id == UMC_CHIP_ID) + priv->foundry_name = 'u'; + + icnss_pr_dbg("Capability, chip_id: 0x%x, chip_family: 0x%x, board_id: 0x%x, soc_id: 0x%x", + priv->chip_info.chip_id, priv->chip_info.chip_family, + priv->board_id, priv->soc_id); + + icnss_pr_dbg("fw_version: 0x%x, fw_build_timestamp: %s, fw_build_id: %s", + priv->fw_version_info.fw_version, + priv->fw_version_info.fw_build_timestamp, + priv->fw_build_id); + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(resp); + kfree(req); + priv->stats.cap_err++; + return ret; +} + +int icnss_qmi_get_dms_mac(struct icnss_priv *priv) +{ + struct dms_get_mac_address_req_msg_v01 req; + struct dms_get_mac_address_resp_msg_v01 resp; + struct qmi_txn txn; + int ret = 0; + + if (!test_bit(ICNSS_QMI_DMS_CONNECTED, &priv->state)) { + icnss_pr_err("DMS QMI connection not established\n"); + return -EAGAIN; + } + icnss_pr_dbg("Requesting DMS MAC address"); + + memset(&resp, 0, sizeof(resp)); + ret = qmi_txn_init(&priv->qmi_dms, &txn, + dms_get_mac_address_resp_msg_v01_ei, &resp); + if (ret < 0) { + icnss_pr_err("Failed to initialize txn for dms, err: %d\n", + ret); + goto out; + } + req.device = DMS_DEVICE_MAC_WLAN_V01; + ret = qmi_send_request(&priv->qmi_dms, NULL, &txn, + QMI_DMS_GET_MAC_ADDRESS_REQ_V01, + DMS_GET_MAC_ADDRESS_REQ_MSG_V01_MAX_MSG_LEN, + dms_get_mac_address_req_msg_v01_ei, &req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Failed to send QMI_DMS_GET_MAC_ADDRESS_REQ_V01, err: %d\n", + ret); + goto out; + } + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Failed to wait for QMI_DMS_GET_MAC_ADDRESS_RESP_V01, err: %d\n", + ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + if (resp.resp.error == DMS_MAC_NOT_PROVISIONED) { + icnss_pr_err("NV MAC address is not provisioned"); + priv->dms.nv_mac_not_prov = 1; + } else { + icnss_pr_err("QMI_DMS_GET_MAC_ADDRESS_REQ_V01 failed, result: %d, err: %d\n", + resp.resp.result, resp.resp.error); + } + ret = -resp.resp.result; + goto out; + } + if (!resp.mac_address_valid || + resp.mac_address_len != QMI_WLFW_MAC_ADDR_SIZE_V01) { + icnss_pr_err("Invalid MAC address received from DMS\n"); + priv->dms.mac_valid = false; + goto out; + } + priv->dms.mac_valid = true; + memcpy(priv->dms.mac, resp.mac_address, QMI_WLFW_MAC_ADDR_SIZE_V01); + icnss_pr_info("Received DMS MAC: [%pM]\n", priv->dms.mac); +out: + return ret; +} + +int icnss_wlfw_wlan_mac_req_send_sync(struct icnss_priv *priv, + u8 *mac, u32 mac_len) +{ + struct wlfw_mac_addr_req_msg_v01 req; + struct wlfw_mac_addr_resp_msg_v01 resp = {0}; + struct qmi_txn txn; + int ret; + + if (!priv || !mac || mac_len != QMI_WLFW_MAC_ADDR_SIZE_V01) + return -EINVAL; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_mac_addr_resp_msg_v01_ei, &resp); + if (ret < 0) { + icnss_pr_err("Failed to initialize txn for mac req, err: %d\n", + ret); + ret = -EIO; + goto out; + } + + icnss_pr_dbg("Sending WLAN mac req [%pM], state: 0x%lx\n", + mac, priv->state); + memcpy(req.mac_addr, mac, mac_len); + req.mac_addr_valid = 1; + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_MAC_ADDR_REQ_V01, + WLFW_MAC_ADDR_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_mac_addr_req_msg_v01_ei, &req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Failed to send mac req, err: %d\n", ret); + ret = -EIO; + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Failed to wait for resp of mac req, err: %d\n", + ret); + ret = -EIO; + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("WLAN mac req failed, result: %d, err: %d\n", + resp.resp.result); + ret = -resp.resp.result; + } +out: + return ret; +} + +static int icnss_dms_connect_to_server(struct icnss_priv *priv, + unsigned int node, unsigned int port) +{ + struct qmi_handle *qmi_dms = &priv->qmi_dms; + struct sockaddr_qrtr sq = {0}; + int ret = 0; + + sq.sq_family = AF_QIPCRTR; + sq.sq_node = node; + sq.sq_port = port; + + ret = kernel_connect(qmi_dms->sock, (struct sockaddr *)&sq, + sizeof(sq), 0); + if (ret < 0) { + icnss_pr_err("Failed to connect to QMI DMS remote service Node: %d Port: %d\n", + node, port); + goto out; + } + + set_bit(ICNSS_QMI_DMS_CONNECTED, &priv->state); + icnss_pr_info("QMI DMS service connected, state: 0x%lx\n", + priv->state); +out: + return ret; +} + +static int dms_new_server(struct qmi_handle *qmi_dms, + struct qmi_service *service) +{ + struct icnss_priv *priv = + container_of(qmi_dms, struct icnss_priv, qmi_dms); + + if (!service) + return -EINVAL; + + return icnss_dms_connect_to_server(priv, service->node, + service->port); +} + +static void dms_del_server(struct qmi_handle *qmi_dms, + struct qmi_service *service) +{ + struct icnss_priv *priv = + container_of(qmi_dms, struct icnss_priv, qmi_dms); + + clear_bit(ICNSS_QMI_DMS_CONNECTED, &priv->state); + icnss_pr_info("QMI DMS service disconnected, state: 0x%lx\n", + priv->state); +} + +static struct qmi_ops qmi_dms_ops = { + .new_server = dms_new_server, + .del_server = dms_del_server, +}; + +int icnss_dms_init(struct icnss_priv *priv) +{ + int ret = 0; + + ret = qmi_handle_init(&priv->qmi_dms, DMS_QMI_MAX_MSG_LEN, + &qmi_dms_ops, NULL); + if (ret < 0) { + icnss_pr_err("Failed to initialize DMS handle, err: %d\n", ret); + goto out; + } + + ret = qmi_add_lookup(&priv->qmi_dms, DMS_SERVICE_ID_V01, + DMS_SERVICE_VERS_V01, 0); + if (ret < 0) + icnss_pr_err("Failed to add DMS lookup, err: %d\n", ret); +out: + return ret; +} + +void icnss_dms_deinit(struct icnss_priv *priv) +{ + qmi_handle_release(&priv->qmi_dms); +} + +static int icnss_get_bdf_file_name(struct icnss_priv *priv, + u32 bdf_type, char *filename, + u32 filename_len) +{ + char filename_tmp[ICNSS_MAX_FILE_NAME]; + char foundry_specific_filename[ICNSS_MAX_FILE_NAME]; + int ret = 0; + + switch (bdf_type) { + case ICNSS_BDF_ELF: + if (priv->board_id == 0xFF) + snprintf(filename_tmp, filename_len, ELF_BDF_FILE_NAME); + else if (priv->board_id < 0xFF) + snprintf(filename_tmp, filename_len, + ELF_BDF_FILE_NAME_PREFIX "%02x", + priv->board_id); + else + snprintf(filename_tmp, filename_len, + BDF_FILE_NAME_PREFIX "%02x.e%02x", + priv->board_id >> 8 & 0xFF, + priv->board_id & 0xFF); + break; + case ICNSS_BDF_BIN: + if (priv->board_id == 0xFF) + snprintf(filename_tmp, filename_len, BIN_BDF_FILE_NAME); + else if (priv->board_id >= WLAN_BOARD_ID_INDEX) + snprintf(filename_tmp, filename_len, + BIN_BDF_FILE_NAME_PREFIX "%03x", + priv->board_id); + else + snprintf(filename_tmp, filename_len, + BIN_BDF_FILE_NAME_PREFIX "b%02x", + priv->board_id); + if (priv->foundry_name) { + strlcpy(foundry_specific_filename, filename_tmp, ICNSS_MAX_FILE_NAME); + memmove(foundry_specific_filename + BDWLAN_SIZE + 1, + foundry_specific_filename + BDWLAN_SIZE, + BDWLAN_SIZE - 1); + foundry_specific_filename[BDWLAN_SIZE] = priv->foundry_name; + foundry_specific_filename[ICNSS_MAX_FILE_NAME - 1] = '\0'; + strlcpy(filename_tmp, foundry_specific_filename, ICNSS_MAX_FILE_NAME); + } + break; + case ICNSS_BDF_REGDB: + snprintf(filename_tmp, filename_len, REGDB_FILE_NAME); + break; + case ICNSS_BDF_DUMMY: + icnss_pr_dbg("CNSS_BDF_DUMMY is set, sending dummy BDF\n"); + snprintf(filename_tmp, filename_len, DUMMY_BDF_FILE_NAME); + ret = ICNSS_MAX_FILE_NAME; + break; + default: + icnss_pr_err("Invalid BDF type: %d\n", + priv->ctrl_params.bdf_type); + ret = -EINVAL; + break; + } + + if (ret >= 0) + icnss_add_fw_prefix_name(priv, filename, filename_tmp); + + return ret; +} + +static char *icnss_bdf_type_to_str(enum icnss_bdf_type bdf_type) +{ + switch (bdf_type) { + case ICNSS_BDF_BIN: + return "BDF"; + case ICNSS_BDF_ELF: + return "BDF"; + case ICNSS_BDF_REGDB: + return "REGDB"; + case ICNSS_BDF_DUMMY: + return "BDF"; + default: + return "UNKNOWN"; + } +}; + +int icnss_wlfw_bdf_dnld_send_sync(struct icnss_priv *priv, u32 bdf_type) +{ + struct wlfw_bdf_download_req_msg_v01 *req; + struct wlfw_bdf_download_resp_msg_v01 *resp; + struct qmi_txn txn; + char filename[ICNSS_MAX_FILE_NAME]; + const struct firmware *fw_entry = NULL; + const u8 *temp; + unsigned int remaining; + int ret = 0; + + icnss_pr_dbg("Sending %s download message, state: 0x%lx, type: %d\n", + icnss_bdf_type_to_str(bdf_type), priv->state, bdf_type); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + ret = icnss_get_bdf_file_name(priv, bdf_type, + filename, sizeof(filename)); + if (ret > 0) { + temp = DUMMY_BDF_FILE_NAME; + remaining = ICNSS_MAX_FILE_NAME; + goto bypass_bdf; + } else if (ret < 0) { + goto err_req_fw; + } + + ret = request_firmware(&fw_entry, filename, &priv->pdev->dev); + if (ret) { + icnss_pr_err("Failed to load %s: %s ret:%d\n", + icnss_bdf_type_to_str(bdf_type), filename, ret); + goto err_req_fw; + } + + temp = fw_entry->data; + remaining = fw_entry->size; + +bypass_bdf: + icnss_pr_dbg("Downloading %s: %s, size: %u\n", + icnss_bdf_type_to_str(bdf_type), filename, remaining); + + while (remaining) { + req->valid = 1; + req->file_id_valid = 1; + req->file_id = priv->board_id; + req->total_size_valid = 1; + req->total_size = fw_entry->size; + req->seg_id_valid = 1; + req->data_valid = 1; + req->end_valid = 1; + req->bdf_type_valid = 1; + req->bdf_type = bdf_type; + + if (remaining > QMI_WLFW_MAX_DATA_SIZE_V01) { + req->data_len = QMI_WLFW_MAX_DATA_SIZE_V01; + } else { + req->data_len = remaining; + req->end = 1; + } + + memcpy(req->data, temp, req->data_len); + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_bdf_download_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Failed to initialize txn for %s download request, err: %d\n", + icnss_bdf_type_to_str(bdf_type), ret); + goto err_send; + } + + ret = qmi_send_request + (&priv->qmi, NULL, &txn, + QMI_WLFW_BDF_DOWNLOAD_REQ_V01, + WLFW_BDF_DOWNLOAD_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_bdf_download_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Failed to send respond %s download request, err: %d\n", + icnss_bdf_type_to_str(bdf_type), ret); + goto err_send; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Failed to wait for response of %s download request, err: %d\n", + icnss_bdf_type_to_str(bdf_type), ret); + goto err_send; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("%s download request failed, result: %d, err: %d\n", + icnss_bdf_type_to_str(bdf_type), resp->resp.result, + resp->resp.error); + ret = -resp->resp.result; + goto err_send; + } + + remaining -= req->data_len; + temp += req->data_len; + req->seg_id++; + } + + if (bdf_type != ICNSS_BDF_DUMMY) + release_firmware(fw_entry); + + kfree(req); + kfree(resp); + return 0; + +err_send: + if (bdf_type != ICNSS_BDF_DUMMY) + release_firmware(fw_entry); +err_req_fw: + if (bdf_type != ICNSS_BDF_REGDB) + ICNSS_QMI_ASSERT(); + kfree(req); + kfree(resp); + return ret; +} + +int icnss_wlfw_qdss_data_send_sync(struct icnss_priv *priv, char *file_name, + u32 total_size) +{ + int ret = 0; + struct wlfw_qdss_trace_data_req_msg_v01 *req; + struct wlfw_qdss_trace_data_resp_msg_v01 *resp; + unsigned char *p_qdss_trace_data_temp, *p_qdss_trace_data = NULL; + unsigned int remaining; + struct qmi_txn txn; + + icnss_pr_dbg("%s", __func__); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + p_qdss_trace_data = kzalloc(total_size, GFP_KERNEL); + if (!p_qdss_trace_data) { + ret = ENOMEM; + goto end; + } + + remaining = total_size; + p_qdss_trace_data_temp = p_qdss_trace_data; + while (remaining && resp->end == 0) { + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_qdss_trace_data_resp_msg_v01_ei, resp); + + if (ret < 0) { + icnss_pr_err("Fail to init txn for QDSS trace resp %d\n", + ret); + goto fail; + } + + ret = qmi_send_request + (&priv->qmi, NULL, &txn, + QMI_WLFW_QDSS_TRACE_DATA_REQ_V01, + WLFW_QDSS_TRACE_DATA_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_qdss_trace_data_req_msg_v01_ei, req); + + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send QDSS trace data req %d\n", + ret); + goto fail; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + + if (ret < 0) { + icnss_pr_err("QDSS trace resp wait failed with rc %d\n", + ret); + goto fail; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI QDSS trace request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto fail; + } else { + ret = 0; + } + + icnss_pr_dbg("%s: response total size %d data len %d", + __func__, resp->total_size, resp->data_len); + + if ((resp->total_size_valid == 1 && + resp->total_size == total_size) && + (resp->seg_id_valid == 1 && resp->seg_id == req->seg_id) && + (resp->data_valid == 1 && + resp->data_len <= QMI_WLFW_MAX_DATA_SIZE_V01)) { + memcpy(p_qdss_trace_data_temp, + resp->data, resp->data_len); + } else { + icnss_pr_err("%s: Unmatched qdss trace data, Expect total_size %u, seg_id %u, Recv total_size_valid %u, total_size %u, seg_id_valid %u, seg_id %u, data_len_valid %u, data_len %u", + __func__, + total_size, req->seg_id, + resp->total_size_valid, + resp->total_size, + resp->seg_id_valid, + resp->seg_id, + resp->data_valid, + resp->data_len); + ret = -EINVAL; + goto fail; + } + + remaining -= resp->data_len; + p_qdss_trace_data_temp += resp->data_len; + req->seg_id++; + } + + if (remaining == 0 && (resp->end_valid && resp->end)) { + ret = icnss_genl_send_msg(p_qdss_trace_data, + ICNSS_GENL_MSG_TYPE_QDSS, file_name, + total_size); + if (ret < 0) { + icnss_pr_err("Fail to save QDSS trace data: %d\n", + ret); + ret = -EINVAL; + } + } else { + icnss_pr_err("%s: QDSS trace file corrupted: remaining %u, end_valid %u, end %u", + __func__, + remaining, resp->end_valid, resp->end); + ret = -EINVAL; + } + +fail: + kfree(p_qdss_trace_data); + +end: + kfree(req); + kfree(resp); + return ret; +} + +int icnss_wlfw_qdss_dnld_send_sync(struct icnss_priv *priv) +{ + struct wlfw_qdss_trace_config_download_req_msg_v01 *req; + struct wlfw_qdss_trace_config_download_resp_msg_v01 *resp; + struct qmi_txn txn; + char filename[ICNSS_MAX_FILE_NAME]; + const struct firmware *fw_entry = NULL; + const u8 *temp; + unsigned int remaining; + int ret = 0; + + icnss_pr_dbg("Sending QDSS config download message, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + icnss_add_fw_prefix_name(priv, filename, QDSS_TRACE_CONFIG_FILE); + ret = request_firmware(&fw_entry, filename, + &priv->pdev->dev); + if (ret) { + icnss_pr_err("Failed to load QDSS: %s ret:%d\n", + filename, ret); + goto err_req_fw; + } + + temp = fw_entry->data; + remaining = fw_entry->size; + + icnss_pr_dbg("Downloading QDSS: %s, size: %u\n", + filename, remaining); + + while (remaining) { + req->total_size_valid = 1; + req->total_size = remaining; + req->seg_id_valid = 1; + req->data_valid = 1; + req->end_valid = 1; + + if (remaining > QMI_WLFW_MAX_DATA_SIZE_V01) { + req->data_len = QMI_WLFW_MAX_DATA_SIZE_V01; + } else { + req->data_len = remaining; + req->end = 1; + } + + memcpy(req->data, temp, req->data_len); + + ret = qmi_txn_init + (&priv->qmi, &txn, + wlfw_qdss_trace_config_download_resp_msg_v01_ei, + resp); + if (ret < 0) { + icnss_pr_err("Failed to initialize txn for QDSS download request, err: %d\n", + ret); + goto err_send; + } + + ret = qmi_send_request + (&priv->qmi, NULL, &txn, + QMI_WLFW_QDSS_TRACE_CONFIG_DOWNLOAD_REQ_V01, + WLFW_QDSS_TRACE_CONFIG_DOWNLOAD_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_qdss_trace_config_download_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Failed to send respond QDSS download request, err: %d\n", + ret); + goto err_send; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Failed to wait for response of QDSS download request, err: %d\n", + ret); + goto err_send; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QDSS download request failed, result: %d, err: %d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto err_send; + } + + remaining -= req->data_len; + temp += req->data_len; + req->seg_id++; + } + + release_firmware(fw_entry); + kfree(req); + kfree(resp); + return 0; + +err_send: + release_firmware(fw_entry); +err_req_fw: + + kfree(req); + kfree(resp); + return ret; +} + +int wlfw_wlan_mode_send_sync_msg(struct icnss_priv *priv, + enum wlfw_driver_mode_enum_v01 mode) +{ + int ret; + struct wlfw_wlan_mode_req_msg_v01 *req; + struct wlfw_wlan_mode_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + /* During recovery do not send mode request for WLAN OFF as + * FW not able to process it. + */ + if (test_bit(ICNSS_PD_RESTART, &priv->state) && + mode == QMI_WLFW_OFF_V01) + return 0; + + if (!test_bit(ICNSS_MODE_ON, &priv->state) && + mode == QMI_WLFW_OFF_V01) + return 0; + + icnss_pr_dbg("Sending Mode request, state: 0x%lx, mode: %d\n", + priv->state, mode); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->mode = mode; + req->hw_debug_valid = 1; + req->hw_debug = !!test_bit(HW_DEBUG_ENABLE, &priv->ctrl_params.quirks); + + priv->stats.mode_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_wlan_mode_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err("Fail to init txn for Mode resp %d\n", ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_WLAN_MODE_REQ_V01, + WLFW_WLAN_MODE_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_wlan_mode_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send Mode req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, + priv->ctrl_params.qmi_timeout + + msecs_to_jiffies(priv->wlan_en_delay_ms)); + if (ret < 0) { + icnss_qmi_fatal_err("Mode resp wait failed with ret %d\n", ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "QMI Mode request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.mode_resp++; + + if (mode == QMI_WLFW_OFF_V01) { + icnss_pr_dbg("Clear mode on 0x%lx, mode: %d\n", + priv->state, mode); + clear_bit(ICNSS_MODE_ON, &priv->state); + } else { + icnss_pr_dbg("Set mode on 0x%lx, mode: %d\n", + priv->state, mode); + set_bit(ICNSS_MODE_ON, &priv->state); + } + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(resp); + kfree(req); + priv->stats.mode_req_err++; + return ret; +} + +static int wlfw_send_qdss_trace_mode_req + (struct icnss_priv *priv, + enum wlfw_qdss_trace_mode_enum_v01 mode, + unsigned long long option) +{ + int rc = 0; + int tmp = 0; + struct wlfw_qdss_trace_mode_req_msg_v01 *req; + struct wlfw_qdss_trace_mode_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->mode_valid = 1; + req->mode = mode; + req->option_valid = 1; + req->option = option; + + tmp = priv->hw_trc_override; + + req->hw_trc_disable_override_valid = 1; + req->hw_trc_disable_override = + (tmp > QMI_PARAM_DISABLE_V01 ? QMI_PARAM_DISABLE_V01 : + (tmp < 0 ? QMI_PARAM_INVALID_V01 : tmp)); + + icnss_pr_dbg("%s: mode %u, option %llu, hw_trc_disable_override: %u", + __func__, mode, option, req->hw_trc_disable_override); + + rc = qmi_txn_init(&priv->qmi, &txn, + wlfw_qdss_trace_mode_resp_msg_v01_ei, resp); + if (rc < 0) { + icnss_qmi_fatal_err("Fail to init txn for QDSS Mode resp %d\n", + rc); + goto out; + } + + rc = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_QDSS_TRACE_MODE_REQ_V01, + WLFW_QDSS_TRACE_MODE_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_qdss_trace_mode_req_msg_v01_ei, req); + if (rc < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send QDSS Mode req %d\n", rc); + goto out; + } + + rc = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (rc < 0) { + icnss_qmi_fatal_err("QDSS Mode resp wait failed with rc %d\n", + rc); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "QMI QDSS Mode request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + rc = -resp->resp.result; + goto out; + } + +out: + kfree(resp); + kfree(req); + return rc; +} + +int wlfw_qdss_trace_start(struct icnss_priv *priv) +{ + return wlfw_send_qdss_trace_mode_req(priv, + QMI_WLFW_QDSS_TRACE_ON_V01, 0); +} + +int wlfw_qdss_trace_stop(struct icnss_priv *priv, unsigned long long option) +{ + return wlfw_send_qdss_trace_mode_req(priv, QMI_WLFW_QDSS_TRACE_OFF_V01, + option); +} + +int wlfw_wlan_cfg_send_sync_msg(struct icnss_priv *priv, + struct wlfw_wlan_cfg_req_msg_v01 *data) +{ + int ret; + struct wlfw_wlan_cfg_req_msg_v01 *req; + struct wlfw_wlan_cfg_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Sending config request, state: 0x%lx\n", priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + memcpy(req, data, sizeof(*req)); + + priv->stats.cfg_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_wlan_cfg_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err("Fail to init txn for Config resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_WLAN_CFG_REQ_V01, + WLFW_WLAN_CFG_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_wlan_cfg_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send Config req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_qmi_fatal_err("Config resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "QMI Config request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.cfg_resp++; + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(resp); + kfree(req); + priv->stats.cfg_req_err++; + return ret; +} + +int wlfw_send_modem_shutdown_msg(struct icnss_priv *priv) +{ + int ret; + struct wlfw_shutdown_req_msg_v01 *req; + struct wlfw_shutdown_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) + return -EINVAL; + + icnss_pr_dbg("Sending modem shutdown request, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->shutdown_valid = 1; + req->shutdown = 1; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_shutdown_resp_msg_v01_ei, resp); + + if (ret < 0) { + icnss_pr_err("Fail to init txn for shutdown resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_SHUTDOWN_REQ_V01, + WLFW_SHUTDOWN_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_shutdown_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send Shutdown req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Shutdown resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI modem shutdown request rejected result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + +out: + kfree(resp); + kfree(req); + return ret; +} + +int wlfw_ini_send_sync_msg(struct icnss_priv *priv, uint8_t fw_log_mode) +{ + int ret; + struct wlfw_ini_req_msg_v01 *req; + struct wlfw_ini_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Sending ini sync request, state: 0x%lx, fw_log_mode: %d\n", + priv->state, fw_log_mode); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->enablefwlog_valid = 1; + req->enablefwlog = fw_log_mode; + + priv->stats.ini_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, wlfw_ini_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err("Fail to init txn for INI resp %d\n", ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_INI_REQ_V01, + WLFW_INI_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_ini_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send INI req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_qmi_fatal_err("INI resp wait failed with ret %d\n", ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "QMI INI request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.ini_resp++; + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(resp); + kfree(req); + priv->stats.ini_req_err++; + return ret; +} + +int wlfw_athdiag_read_send_sync_msg(struct icnss_priv *priv, + uint32_t offset, uint32_t mem_type, + uint32_t data_len, uint8_t *data) +{ + int ret; + struct wlfw_athdiag_read_req_msg_v01 *req; + struct wlfw_athdiag_read_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Diag read: state 0x%lx, offset %x, mem_type %x, data_len %u\n", + priv->state, offset, mem_type, data_len); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->offset = offset; + req->mem_type = mem_type; + req->data_len = data_len; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_athdiag_read_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Fail to init txn for Athdiag Read resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_ATHDIAG_READ_REQ_V01, + WLFW_ATHDIAG_READ_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_athdiag_read_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send Athdiag Read req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Athdaig Read resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI Athdiag Read request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } else { + ret = 0; + } + + if (!resp->data_valid || resp->data_len < data_len) { + icnss_pr_err("Athdiag read data is invalid, data_valid = %u, data_len = %u\n", + resp->data_valid, resp->data_len); + ret = -EINVAL; + goto out; + } + + memcpy(data, resp->data, resp->data_len); + +out: + kfree(resp); + kfree(req); + return ret; +} + +int wlfw_athdiag_write_send_sync_msg(struct icnss_priv *priv, + uint32_t offset, uint32_t mem_type, + uint32_t data_len, uint8_t *data) +{ + int ret; + struct wlfw_athdiag_write_req_msg_v01 *req; + struct wlfw_athdiag_write_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Diag write: state 0x%lx, offset %x, mem_type %x, data_len %u, data %pK\n", + priv->state, offset, mem_type, data_len, data); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->offset = offset; + req->mem_type = mem_type; + req->data_len = data_len; + memcpy(req->data, data, data_len); + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_athdiag_write_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Fail to init txn for Athdiag Write resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_ATHDIAG_WRITE_REQ_V01, + WLFW_ATHDIAG_WRITE_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_athdiag_write_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send Athdiag Write req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Athdiag Write resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI Athdiag Write request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } else { + ret = 0; + } + +out: + kfree(resp); + kfree(req); + return ret; +} + +int wlfw_rejuvenate_ack_send_sync_msg(struct icnss_priv *priv) +{ + int ret; + struct wlfw_rejuvenate_ack_req_msg_v01 *req; + struct wlfw_rejuvenate_ack_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + icnss_pr_dbg("Sending rejuvenate ack request, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + priv->stats.rejuvenate_ack_req++; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_rejuvenate_ack_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_qmi_fatal_err( + "Fail to init txn for Rejuvenate Ack resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_REJUVENATE_ACK_REQ_V01, + WLFW_REJUVENATE_ACK_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_rejuvenate_ack_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_qmi_fatal_err("Fail to send Rejuvenate Ack req %d\n", + ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_qmi_fatal_err( + "Rejuvenate Ack resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_qmi_fatal_err( + "QMI Rejuvenate Ack request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.rejuvenate_ack_resp++; + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(resp); + kfree(req); + priv->stats.rejuvenate_ack_err++; + return ret; +} + +int wlfw_dynamic_feature_mask_send_sync_msg(struct icnss_priv *priv, + uint64_t dynamic_feature_mask) +{ + int ret; + struct wlfw_dynamic_feature_mask_req_msg_v01 *req; + struct wlfw_dynamic_feature_mask_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + if (!test_bit(ICNSS_WLFW_CONNECTED, &priv->state)) { + icnss_pr_err("Invalid state for dynamic feature: 0x%lx\n", + priv->state); + return -EINVAL; + } + + if (!test_bit(FW_REJUVENATE_ENABLE, &priv->ctrl_params.quirks)) { + icnss_pr_dbg("FW rejuvenate is disabled from quirks\n"); + return 0; + } + + icnss_pr_dbg("Sending dynamic feature mask request, val 0x%llx, state: 0x%lx\n", + dynamic_feature_mask, priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->mask_valid = 1; + req->mask = dynamic_feature_mask; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_dynamic_feature_mask_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Fail to init txn for Dynamic Feature Mask resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_DYNAMIC_FEATURE_MASK_REQ_V01, + WLFW_DYNAMIC_FEATURE_MASK_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_dynamic_feature_mask_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send Dynamic Feature Mask req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Dynamic Feature Mask resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI Dynamic Feature Mask request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + icnss_pr_dbg("prev_mask_valid %u, prev_mask 0x%llx, curr_maks_valid %u, curr_mask 0x%llx\n", + resp->prev_mask_valid, resp->prev_mask, + resp->curr_mask_valid, resp->curr_mask); + +out: + kfree(resp); + kfree(req); + return ret; +} + +void icnss_handle_rejuvenate(struct icnss_priv *priv) +{ + struct icnss_event_pd_service_down_data *event_data; + struct icnss_uevent_fw_down_data fw_down_data = {0}; + + event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); + if (event_data == NULL) + return; + + event_data->crashed = true; + event_data->fw_rejuvenate = true; + fw_down_data.crashed = true; + set_bit(ICNSS_REJUVENATE, &priv->state); + + icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_DOWN, + &fw_down_data); + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN, + 0, event_data); +} + +int wlfw_qdss_trace_mem_info_send_sync(struct icnss_priv *priv) +{ + struct wlfw_qdss_trace_mem_info_req_msg_v01 *req; + struct wlfw_qdss_trace_mem_info_resp_msg_v01 *resp; + struct qmi_txn txn; + struct icnss_fw_mem *qdss_mem = priv->qdss_mem; + int ret = 0; + int i; + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) + return -EINVAL; + + icnss_pr_dbg("Sending QDSS trace mem info, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->mem_seg_len = priv->qdss_mem_seg_len; + + if (priv->qdss_mem_seg_len > QMI_WLFW_MAX_NUM_MEM_SEG) { + icnss_pr_err("Invalid seg len %u\n", + priv->qdss_mem_seg_len); + ret = -EINVAL; + goto out; + } + + for (i = 0; i < req->mem_seg_len; i++) { + icnss_pr_dbg("Memory for FW, va: 0x%pK, pa: %pa, size: 0x%zx, type: %u\n", + qdss_mem[i].va, &qdss_mem[i].pa, + qdss_mem[i].size, qdss_mem[i].type); + + req->mem_seg[i].addr = qdss_mem[i].pa; + req->mem_seg[i].size = qdss_mem[i].size; + req->mem_seg[i].type = qdss_mem[i].type; + } + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_qdss_trace_mem_info_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Fail to initialize txn for QDSS trace mem request: err %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_QDSS_TRACE_MEM_INFO_REQ_V01, + WLFW_QDSS_TRACE_MEM_INFO_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_qdss_trace_mem_info_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send QDSS trace mem info request: err %d\n", + ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Fail to wait for response of QDSS trace mem info request, err %d\n", + ret); + goto out; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QDSS trace mem info request failed, result: %d, err: %d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + kfree(req); + kfree(resp); + return 0; + +out: + kfree(req); + kfree(resp); + return ret; +} + +int icnss_wlfw_m3_dump_upload_done_send_sync(struct icnss_priv *priv, + u32 pdev_id, int status) +{ + struct wlfw_m3_dump_upload_done_req_msg_v01 *req; + struct wlfw_m3_dump_upload_done_resp_msg_v01 *resp; + struct qmi_txn txn; + int ret = 0; + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + icnss_pr_dbg("Sending M3 Upload done req, pdev %d, status %d\n", + pdev_id, status); + + req->pdev_id = pdev_id; + req->status = status; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_m3_dump_upload_done_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Fail to initialize txn for M3 dump upload done req: err %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_M3_DUMP_UPLOAD_DONE_REQ_V01, + WLFW_M3_DUMP_UPLOAD_DONE_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_m3_dump_upload_done_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send M3 dump upload done request: err %d\n", + ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Fail to wait for response of M3 dump upload done request, err %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("M3 Dump Upload Done Req failed, result: %d, err: 0x%X\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + +out: + kfree(req); + kfree(resp); + return ret; +} + +static void fw_ready_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct icnss_priv *priv = + container_of(qmi, struct icnss_priv, qmi); + + icnss_pr_dbg("Received FW Ready Indication\n"); + + if (!txn) { + pr_err("spurious indication\n"); + return; + } + + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_FW_READY_IND, + 0, NULL); +} + +static void msa_ready_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct icnss_priv *priv = container_of(qmi, struct icnss_priv, qmi); + struct device *dev = &priv->pdev->dev; + const struct wlfw_msa_ready_ind_msg_v01 *ind_msg = data; + uint64_t msa_base_addr = priv->msa_pa; + phys_addr_t hang_data_phy_addr; + + icnss_pr_dbg("Received MSA Ready Indication\n"); + + if (!txn) { + pr_err("spurious indication\n"); + return; + } + + priv->stats.msa_ready_ind++; + + /* Check if the length is valid & + * the length should not be 0 and + * should be <= WLFW_MAX_HANG_EVENT_DATA_SIZE(400) + */ + + if (ind_msg->hang_data_length_valid && + ind_msg->hang_data_length && + ind_msg->hang_data_length <= WLFW_MAX_HANG_EVENT_DATA_SIZE) + priv->hang_event_data_len = ind_msg->hang_data_length; + else + goto out; + + /* Check if the offset is valid & + * the offset should be in range of 0 to msa_mem_size-hang_data_length + */ + + if (ind_msg->hang_data_addr_offset_valid && + (ind_msg->hang_data_addr_offset <= (priv->msa_mem_size - + ind_msg->hang_data_length))) + hang_data_phy_addr = msa_base_addr + + ind_msg->hang_data_addr_offset; + else + goto out; + + if (priv->hang_event_data_pa == hang_data_phy_addr) + goto exit; + + priv->hang_event_data_pa = hang_data_phy_addr; + priv->hang_event_data_va = devm_ioremap(dev, priv->hang_event_data_pa, + ind_msg->hang_data_length); + + if (!priv->hang_event_data_va) { + icnss_pr_err("Hang Data ioremap failed: phy addr: %pa\n", + &priv->hang_event_data_pa); + goto fail; + } +exit: + icnss_pr_dbg("Hang Event Data details,Offset:0x%x, Length:0x%x,va_addr: 0x%pK\n", + ind_msg->hang_data_addr_offset, + ind_msg->hang_data_length, + priv->hang_event_data_va); + + return; + +out: + icnss_pr_err("Invalid Hang Data details, Offset:0x%x, Length:0x%x", + ind_msg->hang_data_addr_offset, + ind_msg->hang_data_length); +fail: + priv->hang_event_data_va = NULL; + priv->hang_event_data_pa = 0; + priv->hang_event_data_len = 0; +} + +static void pin_connect_result_ind_cb(struct qmi_handle *qmi, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct icnss_priv *priv = container_of(qmi, struct icnss_priv, qmi); + const struct wlfw_pin_connect_result_ind_msg_v01 *ind_msg = data; + + icnss_pr_dbg("Received Pin Connect Result Indication\n"); + + if (!txn) { + pr_err("spurious indication\n"); + return; + } + + if (ind_msg->pwr_pin_result_valid) + priv->pwr_pin_result = ind_msg->pwr_pin_result; + if (ind_msg->phy_io_pin_result_valid) + priv->phy_io_pin_result = ind_msg->phy_io_pin_result; + if (ind_msg->rf_pin_result_valid) + priv->rf_pin_result = ind_msg->rf_pin_result; + + icnss_pr_dbg("Pin connect Result: pwr_pin: 0x%x phy_io_pin: 0x%x rf_io_pin: 0x%x\n", + ind_msg->pwr_pin_result, ind_msg->phy_io_pin_result, + ind_msg->rf_pin_result); + priv->stats.pin_connect_result++; +} + +static void rejuvenate_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct icnss_priv *priv = container_of(qmi, struct icnss_priv, qmi); + const struct wlfw_rejuvenate_ind_msg_v01 *ind_msg = data; + + icnss_pr_dbg("Received Rejuvenate Indication\n"); + + if (!txn) { + pr_err("spurious indication\n"); + return; + } + + icnss_ignore_fw_timeout(true); + + if (ind_msg->cause_for_rejuvenation_valid) + priv->cause_for_rejuvenation = ind_msg->cause_for_rejuvenation; + else + priv->cause_for_rejuvenation = 0; + if (ind_msg->requesting_sub_system_valid) + priv->requesting_sub_system = ind_msg->requesting_sub_system; + else + priv->requesting_sub_system = 0; + if (ind_msg->line_number_valid) + priv->line_number = ind_msg->line_number; + else + priv->line_number = 0; + if (ind_msg->function_name_valid) + memcpy(priv->function_name, ind_msg->function_name, + QMI_WLFW_FUNCTION_NAME_LEN_V01 + 1); + else + memset(priv->function_name, 0, + QMI_WLFW_FUNCTION_NAME_LEN_V01 + 1); + + icnss_pr_info("Cause for rejuvenation: 0x%x, requesting sub-system: 0x%x, line number: %u, function name: %s\n", + priv->cause_for_rejuvenation, + priv->requesting_sub_system, + priv->line_number, + priv->function_name); + + priv->stats.rejuvenate_ind++; + + icnss_handle_rejuvenate(priv); +} + +static void cal_done_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct icnss_priv *priv = container_of(qmi, struct icnss_priv, qmi); + + icnss_pr_dbg("Received QMI WLFW calibration done indication\n"); + + if (!txn) { + icnss_pr_err("Spurious indication\n"); + return; + } + + priv->cal_done = true; + clear_bit(ICNSS_COLD_BOOT_CAL, &priv->state); +} + +static void fw_init_done_ind_cb(struct qmi_handle *qmi, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct icnss_priv *priv = container_of(qmi, struct icnss_priv, qmi); + struct device *dev = &priv->pdev->dev; + const struct wlfw_fw_init_done_ind_msg_v01 *ind_msg = data; + uint64_t msa_base_addr = priv->msa_pa; + phys_addr_t hang_data_phy_addr; + + icnss_pr_dbg("Received QMI WLFW FW initialization done indication\n"); + + if (!txn) { + icnss_pr_err("Spurious indication\n"); + return; + } + + /* Check if the length is valid & + * the length should not be 0 and + * should be <= WLFW_MAX_HANG_EVENT_DATA_SIZE(400) + */ + + if (ind_msg->hang_data_length_valid && + ind_msg->hang_data_length && + ind_msg->hang_data_length <= WLFW_MAX_HANG_EVENT_DATA_SIZE) + priv->hang_event_data_len = ind_msg->hang_data_length; + else + goto out; + + /* Check if the offset is valid & + * the offset should be in range of 0 to msa_mem_size-hang_data_length + */ + + if (ind_msg->hang_data_addr_offset_valid && + (ind_msg->hang_data_addr_offset <= (priv->msa_mem_size - + ind_msg->hang_data_length))) + hang_data_phy_addr = msa_base_addr + + ind_msg->hang_data_addr_offset; + else + goto out; + + if (priv->hang_event_data_pa == hang_data_phy_addr) + goto exit; + + priv->hang_event_data_pa = hang_data_phy_addr; + priv->hang_event_data_va = devm_ioremap(dev, priv->hang_event_data_pa, + ind_msg->hang_data_length); + + if (!priv->hang_event_data_va) { + icnss_pr_err("Hang Data ioremap failed: phy addr: %pa\n", + &priv->hang_event_data_pa); + goto fail; + } + +exit: + icnss_pr_dbg("Hang Event Data details,Offset:0x%x, Length:0x%x,va_addr: 0x%pK\n", + ind_msg->hang_data_addr_offset, + ind_msg->hang_data_length, + priv->hang_event_data_va); + + goto post; + +out: + icnss_pr_err("Invalid Hang Data details, Offset:0x%x, Length:0x%x", + ind_msg->hang_data_addr_offset, + ind_msg->hang_data_length); +fail: + priv->hang_event_data_va = NULL; + priv->hang_event_data_pa = 0; + priv->hang_event_data_len = 0; +post: + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_FW_INIT_DONE_IND, + 0, NULL); + +} + +static void wlfw_qdss_trace_req_mem_ind_cb(struct qmi_handle *qmi, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, + const void *data) +{ + struct icnss_priv *priv = + container_of(qmi, struct icnss_priv, qmi); + const struct wlfw_qdss_trace_req_mem_ind_msg_v01 *ind_msg = data; + int i; + + icnss_pr_dbg("Received QMI WLFW QDSS trace request mem indication\n"); + + if (!txn) { + icnss_pr_err("Spurious indication\n"); + return; + } + + if (priv->qdss_mem_seg_len) { + icnss_pr_err("Ignore double allocation for QDSS trace, current len %u\n", + priv->qdss_mem_seg_len); + return; + } + + priv->qdss_mem_seg_len = ind_msg->mem_seg_len; + + if (priv->qdss_mem_seg_len > QMI_WLFW_MAX_NUM_MEM_SEG) { + icnss_pr_err("Invalid seg len %u\n", + priv->qdss_mem_seg_len); + return; + } + + for (i = 0; i < priv->qdss_mem_seg_len; i++) { + icnss_pr_dbg("QDSS requests for memory, size: 0x%x, type: %u\n", + ind_msg->mem_seg[i].size, + ind_msg->mem_seg[i].type); + priv->qdss_mem[i].type = ind_msg->mem_seg[i].type; + priv->qdss_mem[i].size = ind_msg->mem_seg[i].size; + } + + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_QDSS_TRACE_REQ_MEM, + 0, NULL); +} + +static void wlfw_qdss_trace_save_ind_cb(struct qmi_handle *qmi, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, + const void *data) +{ + struct icnss_priv *priv = + container_of(qmi, struct icnss_priv, qmi); + const struct wlfw_qdss_trace_save_ind_msg_v01 *ind_msg = data; + struct icnss_qmi_event_qdss_trace_save_data *event_data; + int i = 0; + + icnss_pr_dbg("Received QMI WLFW QDSS trace save indication\n"); + + if (!txn) { + icnss_pr_err("Spurious indication\n"); + return; + } + + icnss_pr_dbg("QDSS_trace_save info: source %u, total_size %u, file_name_valid %u, file_name %s\n", + ind_msg->source, ind_msg->total_size, + ind_msg->file_name_valid, ind_msg->file_name); + + event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); + if (!event_data) + return; + + if (ind_msg->mem_seg_valid) { + if (ind_msg->mem_seg_len > QDSS_TRACE_SEG_LEN_MAX) { + icnss_pr_err("Invalid seg len %u\n", + ind_msg->mem_seg_len); + goto free_event_data; + } + icnss_pr_dbg("QDSS_trace_save seg len %u\n", + ind_msg->mem_seg_len); + event_data->mem_seg_len = ind_msg->mem_seg_len; + for (i = 0; i < ind_msg->mem_seg_len; i++) { + event_data->mem_seg[i].addr = ind_msg->mem_seg[i].addr; + event_data->mem_seg[i].size = ind_msg->mem_seg[i].size; + icnss_pr_dbg("seg-%d: addr 0x%llx size 0x%x\n", + i, ind_msg->mem_seg[i].addr, + ind_msg->mem_seg[i].size); + } + } + + event_data->total_size = ind_msg->total_size; + + if (ind_msg->file_name_valid) + strlcpy(event_data->file_name, ind_msg->file_name, + QDSS_TRACE_FILE_NAME_MAX + 1); + + if (ind_msg->source == 1) { + if (!ind_msg->file_name_valid) + strlcpy(event_data->file_name, "qdss_trace_wcss_etb", + QDSS_TRACE_FILE_NAME_MAX + 1); + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_QDSS_TRACE_REQ_DATA, + 0, event_data); + } else { + if (!ind_msg->file_name_valid) + strlcpy(event_data->file_name, "qdss_trace_ddr", + QDSS_TRACE_FILE_NAME_MAX + 1); + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_QDSS_TRACE_SAVE, + 0, event_data); + } + + return; + +free_event_data: + kfree(event_data); +} + +static void wlfw_qdss_trace_free_ind_cb(struct qmi_handle *qmi, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, + const void *data) +{ + struct icnss_priv *priv = + container_of(qmi, struct icnss_priv, qmi); + + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_QDSS_TRACE_FREE, + 0, NULL); +} + +static void icnss_wlfw_respond_get_info_ind_cb(struct qmi_handle *qmi, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, + const void *data) +{ + struct icnss_priv *priv = container_of(qmi, struct icnss_priv, qmi); + const struct wlfw_respond_get_info_ind_msg_v01 *ind_msg = data; + + if (!txn) { + icnss_pr_err("Spurious indication\n"); + return; + } + + icnss_pr_vdbg("Extract message with event length: %d, type: %d, is last: %d, seq no: %d\n", + ind_msg->data_len, ind_msg->type, + ind_msg->is_last, ind_msg->seq_no); + + if (priv->get_info_cb_ctx && priv->get_info_cb) + priv->get_info_cb(priv->get_info_cb_ctx, + (void *)ind_msg->data, + ind_msg->data_len); +} + +static void icnss_wlfw_m3_dump_upload_segs_req_ind_cb(struct qmi_handle *qmi, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, + const void *d) +{ + struct icnss_priv *priv = container_of(qmi, struct icnss_priv, qmi); + const struct wlfw_m3_dump_upload_segments_req_ind_msg_v01 *ind_msg = d; + struct icnss_m3_upload_segments_req_data *event_data = NULL; + u64 max_mapped_addr = 0; + u64 segment_addr = 0; + int i = 0; + + icnss_pr_dbg("Received QMI WLFW M3 dump upload sigments indication\n"); + + if (!txn) { + icnss_pr_err("Spurious indication\n"); + return; + } + + icnss_pr_dbg("M3 Dump upload info: pdev_id: %d no_of_segments: %d\n", + ind_msg->pdev_id, ind_msg->no_of_valid_segments); + + if (ind_msg->no_of_valid_segments > QMI_WLFW_MAX_M3_SEGMENTS_SIZE_V01) + return; + + event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); + if (!event_data) + return; + + event_data->pdev_id = ind_msg->pdev_id; + event_data->no_of_valid_segments = ind_msg->no_of_valid_segments; + max_mapped_addr = priv->msa_pa + priv->msa_mem_size; + + for (i = 0; i < ind_msg->no_of_valid_segments; i++) { + segment_addr = ind_msg->m3_segment[i].addr & + M3_SEGMENT_ADDR_MASK; + + if (ind_msg->m3_segment[i].size > priv->msa_mem_size || + segment_addr >= max_mapped_addr || + segment_addr < priv->msa_pa || + ind_msg->m3_segment[i].size + + segment_addr > max_mapped_addr) { + icnss_pr_dbg("Received out of range Segment %d Addr: 0x%llx Size: 0x%x, Name: %s, type: %d\n", + (i + 1), segment_addr, + ind_msg->m3_segment[i].size, + ind_msg->m3_segment[i].name, + ind_msg->m3_segment[i].type); + goto out; + } + + event_data->m3_segment[i].addr = segment_addr; + event_data->m3_segment[i].size = ind_msg->m3_segment[i].size; + event_data->m3_segment[i].type = ind_msg->m3_segment[i].type; + strlcpy(event_data->m3_segment[i].name, + ind_msg->m3_segment[i].name, + WLFW_MAX_STR_LEN + 1); + + icnss_pr_dbg("Received Segment %d Addr: 0x%llx Size: 0x%x, Name: %s, type: %d\n", + (i + 1), segment_addr, + ind_msg->m3_segment[i].size, + ind_msg->m3_segment[i].name, + ind_msg->m3_segment[i].type); + } + + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_M3_DUMP_UPLOAD_REQ, + 0, event_data); + + return; +out: + kfree(event_data); +} + +static struct qmi_msg_handler wlfw_msg_handlers[] = { + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_FW_READY_IND_V01, + .ei = wlfw_fw_ready_ind_msg_v01_ei, + .decoded_size = sizeof(struct wlfw_fw_ready_ind_msg_v01), + .fn = fw_ready_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_MSA_READY_IND_V01, + .ei = wlfw_msa_ready_ind_msg_v01_ei, + .decoded_size = sizeof(struct wlfw_msa_ready_ind_msg_v01), + .fn = msa_ready_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_PIN_CONNECT_RESULT_IND_V01, + .ei = wlfw_pin_connect_result_ind_msg_v01_ei, + .decoded_size = + sizeof(struct wlfw_pin_connect_result_ind_msg_v01), + .fn = pin_connect_result_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_REJUVENATE_IND_V01, + .ei = wlfw_rejuvenate_ind_msg_v01_ei, + .decoded_size = sizeof(struct wlfw_rejuvenate_ind_msg_v01), + .fn = rejuvenate_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_CAL_DONE_IND_V01, + .ei = wlfw_cal_done_ind_msg_v01_ei, + .decoded_size = sizeof(struct wlfw_cal_done_ind_msg_v01), + .fn = cal_done_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_FW_INIT_DONE_IND_V01, + .ei = wlfw_fw_init_done_ind_msg_v01_ei, + .decoded_size = sizeof(struct wlfw_fw_init_done_ind_msg_v01), + .fn = fw_init_done_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_QDSS_TRACE_REQ_MEM_IND_V01, + .ei = wlfw_qdss_trace_req_mem_ind_msg_v01_ei, + .decoded_size = + sizeof(struct wlfw_qdss_trace_req_mem_ind_msg_v01), + .fn = wlfw_qdss_trace_req_mem_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_QDSS_TRACE_SAVE_IND_V01, + .ei = wlfw_qdss_trace_save_ind_msg_v01_ei, + .decoded_size = + sizeof(struct wlfw_qdss_trace_save_ind_msg_v01), + .fn = wlfw_qdss_trace_save_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_QDSS_TRACE_FREE_IND_V01, + .ei = wlfw_qdss_trace_free_ind_msg_v01_ei, + .decoded_size = + sizeof(struct wlfw_qdss_trace_free_ind_msg_v01), + .fn = wlfw_qdss_trace_free_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_RESPOND_GET_INFO_IND_V01, + .ei = wlfw_respond_get_info_ind_msg_v01_ei, + .decoded_size = + sizeof(struct wlfw_respond_get_info_ind_msg_v01), + .fn = icnss_wlfw_respond_get_info_ind_cb + }, + { + .type = QMI_INDICATION, + .msg_id = QMI_WLFW_M3_DUMP_UPLOAD_SEGMENTS_REQ_IND_V01, + .ei = wlfw_m3_dump_upload_segments_req_ind_msg_v01_ei, + .decoded_size = + sizeof(struct wlfw_m3_dump_upload_segments_req_ind_msg_v01), + .fn = icnss_wlfw_m3_dump_upload_segs_req_ind_cb + }, + {} +}; + +int icnss_connect_to_fw_server(struct icnss_priv *priv, void *data) +{ + struct icnss_event_server_arrive_data *event_data = data; + struct qmi_handle *qmi = &priv->qmi; + struct sockaddr_qrtr sq = { 0 }; + int ret = 0; + + if (!priv) { + ret = -ENODEV; + goto out; + } + + sq.sq_family = AF_QIPCRTR; + sq.sq_node = event_data->node; + sq.sq_port = event_data->port; + ret = kernel_connect(qmi->sock, (struct sockaddr *)&sq, sizeof(sq), 0); + if (ret < 0) { + icnss_pr_err("Fail to connect to remote service port\n"); + goto out; + } + + icnss_pr_info("QMI Server Connected: state: 0x%lx\n", priv->state); + + kfree(data); + return 0; + +out: + kfree(data); + ICNSS_ASSERT(0); + return ret; +} + +int icnss_clear_server(struct icnss_priv *priv) +{ + int ret; + + if (!priv) + return -ENODEV; + + icnss_pr_info("QMI Service Disconnected: 0x%lx\n", priv->state); + clear_bit(ICNSS_WLFW_CONNECTED, &priv->state); + + icnss_unregister_fw_service(priv); + + clear_bit(ICNSS_DEL_SERVER, &priv->state); + + ret = icnss_register_fw_service(priv); + if (ret < 0) { + icnss_pr_err("WLFW server registration failed\n"); + ICNSS_ASSERT(0); + } + + return 0; +} + +static int wlfw_new_server(struct qmi_handle *qmi, + struct qmi_service *service) +{ + struct icnss_priv *priv = + container_of(qmi, struct icnss_priv, qmi); + struct icnss_event_server_arrive_data *event_data; + + if (priv && test_bit(ICNSS_DEL_SERVER, &priv->state)) { + icnss_pr_info("WLFW server delete in progress, Ignore server arrive: 0x%lx\n", + priv->state); + return 0; + } + + icnss_pr_dbg("WLFW server arrive: node %u port %u\n", + service->node, service->port); + + event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); + if (event_data == NULL) + return -ENOMEM; + + event_data->node = service->node; + event_data->port = service->port; + + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_SERVER_ARRIVE, + 0, event_data); + + return 0; +} + +static void wlfw_del_server(struct qmi_handle *qmi, + struct qmi_service *service) +{ + struct icnss_priv *priv = container_of(qmi, struct icnss_priv, qmi); + + if (priv && test_bit(ICNSS_DEL_SERVER, &priv->state)) { + icnss_pr_info("WLFW server delete in progress, Ignore server delete: 0x%lx\n", + priv->state); + return; + } + + icnss_pr_dbg("WLFW server delete\n"); + + if (priv) { + set_bit(ICNSS_DEL_SERVER, &priv->state); + set_bit(ICNSS_FW_DOWN, &priv->state); + icnss_ignore_fw_timeout(true); + } + + icnss_driver_event_post(priv, ICNSS_DRIVER_EVENT_SERVER_EXIT, + 0, NULL); +} + +static struct qmi_ops wlfw_qmi_ops = { + .new_server = wlfw_new_server, + .del_server = wlfw_del_server, +}; + +int icnss_register_fw_service(struct icnss_priv *priv) +{ + int ret; + + ret = qmi_handle_init(&priv->qmi, + WLFW_BDF_DOWNLOAD_REQ_MSG_V01_MAX_MSG_LEN, + &wlfw_qmi_ops, wlfw_msg_handlers); + if (ret < 0) + return ret; + + if (priv->device_id == WCN6750_DEVICE_ID) + ret = qmi_add_lookup(&priv->qmi, WLFW_SERVICE_ID_V01, + WLFW_SERVICE_VERS_V01, + WLFW_SERVICE_WCN_INS_ID_V01); + else + ret = qmi_add_lookup(&priv->qmi, WLFW_SERVICE_ID_V01, + WLFW_SERVICE_VERS_V01, + WLFW_SERVICE_INS_ID_V01); + return ret; +} + +void icnss_unregister_fw_service(struct icnss_priv *priv) +{ + qmi_handle_release(&priv->qmi); +} + +int icnss_send_wlan_enable_to_fw(struct icnss_priv *priv, + struct icnss_wlan_enable_cfg *config, + enum icnss_driver_mode mode, + const char *host_version) +{ + struct wlfw_wlan_cfg_req_msg_v01 req; + u32 i; + int ret; + + icnss_pr_dbg("Mode: %d, config: %pK, host_version: %s\n", + mode, config, host_version); + + memset(&req, 0, sizeof(req)); + + if (mode == ICNSS_WALTEST || mode == ICNSS_CCPM) + goto skip; + + if (!config || !host_version) { + icnss_pr_err("Invalid cfg pointer, config: %pK, host_version: %pK\n", + config, host_version); + ret = -EINVAL; + goto out; + } + + req.host_version_valid = 1; + strlcpy(req.host_version, host_version, + WLFW_MAX_STR_LEN + 1); + + req.tgt_cfg_valid = 1; + if (config->num_ce_tgt_cfg > WLFW_MAX_NUM_CE) + req.tgt_cfg_len = WLFW_MAX_NUM_CE; + else + req.tgt_cfg_len = config->num_ce_tgt_cfg; + for (i = 0; i < req.tgt_cfg_len; i++) { + req.tgt_cfg[i].pipe_num = config->ce_tgt_cfg[i].pipe_num; + req.tgt_cfg[i].pipe_dir = config->ce_tgt_cfg[i].pipe_dir; + req.tgt_cfg[i].nentries = config->ce_tgt_cfg[i].nentries; + req.tgt_cfg[i].nbytes_max = config->ce_tgt_cfg[i].nbytes_max; + req.tgt_cfg[i].flags = config->ce_tgt_cfg[i].flags; + } + + req.svc_cfg_valid = 1; + if (config->num_ce_svc_pipe_cfg > WLFW_MAX_NUM_SVC) + req.svc_cfg_len = WLFW_MAX_NUM_SVC; + else + req.svc_cfg_len = config->num_ce_svc_pipe_cfg; + for (i = 0; i < req.svc_cfg_len; i++) { + req.svc_cfg[i].service_id = config->ce_svc_cfg[i].service_id; + req.svc_cfg[i].pipe_dir = config->ce_svc_cfg[i].pipe_dir; + req.svc_cfg[i].pipe_num = config->ce_svc_cfg[i].pipe_num; + } + + if (priv->device_id == WCN6750_DEVICE_ID) { + req.shadow_reg_v2_valid = 1; + if (config->num_shadow_reg_v2_cfg > + QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01) + req.shadow_reg_v2_len = + QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01; + else + req.shadow_reg_v2_len = config->num_shadow_reg_v2_cfg; + + memcpy(req.shadow_reg_v2, config->shadow_reg_v2_cfg, + sizeof(struct wlfw_shadow_reg_v2_cfg_s_v01) * + req.shadow_reg_v2_len); + } else if (priv->device_id == ADRASTEA_DEVICE_ID) { + req.shadow_reg_valid = 1; + if (config->num_shadow_reg_cfg > + QMI_WLFW_MAX_NUM_SHADOW_REG_V01) + req.shadow_reg_len = QMI_WLFW_MAX_NUM_SHADOW_REG_V01; + else + req.shadow_reg_len = config->num_shadow_reg_cfg; + + memcpy(req.shadow_reg, config->shadow_reg_cfg, + sizeof(struct wlfw_msi_cfg_s_v01) * req.shadow_reg_len); + } + + ret = wlfw_wlan_cfg_send_sync_msg(priv, &req); + if (ret) + goto out; +skip: + ret = wlfw_wlan_mode_send_sync_msg(priv, + (enum wlfw_driver_mode_enum_v01)mode); +out: + if (test_bit(SKIP_QMI, &priv->ctrl_params.quirks)) + ret = 0; + + return ret; +} + +int icnss_send_wlan_disable_to_fw(struct icnss_priv *priv) +{ + enum wlfw_driver_mode_enum_v01 mode = QMI_WLFW_OFF_V01; + + return wlfw_wlan_mode_send_sync_msg(priv, mode); +} + +int icnss_send_vbatt_update(struct icnss_priv *priv, uint64_t voltage_uv) +{ + int ret; + struct wlfw_vbatt_req_msg_v01 *req; + struct wlfw_vbatt_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) + return -EINVAL; + + icnss_pr_dbg("Sending Vbatt message, state: 0x%lx\n", priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + priv->stats.vbatt_req++; + + req->voltage_uv = voltage_uv; + + ret = qmi_txn_init(&priv->qmi, &txn, wlfw_vbatt_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Fail to init txn for Vbatt message resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_VBATT_REQ_V01, + WLFW_VBATT_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_vbatt_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send Vbatt message req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("VBATT message resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI Vbatt message request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.vbatt_resp++; + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(resp); + kfree(req); + priv->stats.vbatt_req_err++; + return ret; +} + +#ifdef CONFIG_ICNSS2_DEBUG +static inline u32 icnss_get_host_build_type(void) +{ + return QMI_HOST_BUILD_TYPE_PRIMARY_V01; +} +#else +static inline u32 icnss_get_host_build_type(void) +{ + return QMI_HOST_BUILD_TYPE_SECONDARY_V01; +} +#endif + +int wlfw_host_cap_send_sync(struct icnss_priv *priv) +{ + struct wlfw_host_cap_req_msg_v01 *req; + struct wlfw_host_cap_resp_msg_v01 *resp; + struct qmi_txn txn; + int ddr_type; + u32 gpio; + int ret = 0; + u64 iova_start = 0, iova_size = 0, + iova_ipa_start = 0, iova_ipa_size = 0; + + icnss_pr_dbg("Sending host capability message, state: 0x%lx\n", + priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->num_clients_valid = 1; + req->num_clients = 1; + + req->bdf_support_valid = 1; + req->bdf_support = 1; + + req->cal_done_valid = 1; + req->cal_done = priv->cal_done; + icnss_pr_dbg("Calibration done is %d\n", priv->cal_done); + + if (priv->smmu_s1_enable && + !icnss_get_iova(priv, &iova_start, &iova_size) && + !icnss_get_iova_ipa(priv, &iova_ipa_start, + &iova_ipa_size)) { + req->ddr_range_valid = 1; + req->ddr_range[0].start = iova_start; + req->ddr_range[0].size = iova_size + iova_ipa_size; + req->ddr_range[1].start = priv->msa_pa; + req->ddr_range[1].size = priv->msa_mem_size; + icnss_pr_dbg("Sending iova starting 0x%llx with size 0x%llx\n", + req->ddr_range[0].start, req->ddr_range[0].size); + icnss_pr_dbg("Sending msa starting 0x%llx with size 0x%llx\n", + req->ddr_range[1].start, req->ddr_range[1].size); + } + + req->host_build_type_valid = 1; + req->host_build_type = icnss_get_host_build_type(); + + if (priv->wlan_en_delay_ms >= 100) { + icnss_pr_dbg("Setting WLAN_EN delay: %d ms\n", + priv->wlan_en_delay_ms); + req->wlan_enable_delay_valid = 1; + req->wlan_enable_delay = priv->wlan_en_delay_ms; + } + + /* ddr_type = 7(LPDDR4) and 8(LPDDR5) */ + ddr_type = of_fdt_get_ddrtype(); + if (ddr_type > 0) { + icnss_pr_dbg("DDR Type: %d\n", ddr_type); + req->ddr_type_valid = 1; + req->ddr_type = ddr_type; + } + + ret = of_property_read_u32(priv->pdev->dev.of_node, "wlan-en-gpio", + &gpio); + if (!ret) { + icnss_pr_dbg("WLAN_EN_GPIO modified through DT: %d\n", gpio); + req->gpio_info_valid = 1; + req->gpio_info[WLAN_EN_GPIO_V01] = gpio; + } else { + req->gpio_info[WLAN_EN_GPIO_V01] = 0xFFFF; + } + + ret = of_property_read_u32(priv->pdev->dev.of_node, "bt-en-gpio", + &gpio); + if (!ret) { + icnss_pr_dbg("BT_EN_GPIO modified through DT: %d\n", gpio); + req->gpio_info_valid = 1; + req->gpio_info[BT_EN_GPIO_V01] = gpio; + } else { + req->gpio_info[BT_EN_GPIO_V01] = 0xFFFF; + } + + ret = of_property_read_u32(priv->pdev->dev.of_node, "host-sol-gpio", + &gpio); + if (!ret) { + icnss_pr_dbg("HOST_SOL_GPIO modified through DT: %d\n", gpio); + req->gpio_info_valid = 1; + req->gpio_info[HOST_SOL_GPIO_V01] = gpio; + } else { + req->gpio_info[HOST_SOL_GPIO_V01] = 0xFFFF; + } + + ret = of_property_read_u32(priv->pdev->dev.of_node, "target-sol-gpio", + &gpio); + if (!ret) { + icnss_pr_dbg("TARGET_SOL_GPIO modified through DT: %d\n", gpio); + req->gpio_info_valid = 1; + req->gpio_info[TARGET_SOL_GPIO_V01] = gpio; + } else { + req->gpio_info[TARGET_SOL_GPIO_V01] = 0xFFFF; + } + + req->gpio_info_len = GPIO_TYPE_MAX_V01; + + ret = qmi_txn_init(&priv->qmi, &txn, + wlfw_host_cap_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Failed to initialize txn for host capability request, err: %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_HOST_CAP_REQ_V01, + WLFW_HOST_CAP_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_host_cap_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Failed to send host capability request, err: %d\n", + ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Failed to wait for response of host capability request, err: %d\n", + ret); + goto out; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("Host capability request failed, result: %d, err: %d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + kfree(req); + kfree(resp); + return 0; + +out: + ICNSS_QMI_ASSERT(); + kfree(req); + kfree(resp); + return ret; +} + +int icnss_wlfw_get_info_send_sync(struct icnss_priv *plat_priv, int type, + void *cmd, int cmd_len) +{ + struct wlfw_get_info_req_msg_v01 *req; + struct wlfw_get_info_resp_msg_v01 *resp; + struct qmi_txn txn; + int ret = 0; + + if (cmd_len > QMI_WLFW_MAX_DATA_SIZE_V01) + return -EINVAL; + + if (test_bit(ICNSS_FW_DOWN, &plat_priv->state)) + return -EINVAL; + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->type = type; + req->data_len = cmd_len; + memcpy(req->data, cmd, req->data_len); + + ret = qmi_txn_init(&plat_priv->qmi, &txn, + wlfw_get_info_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Failed to initialize txn for get info request, err: %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&plat_priv->qmi, NULL, &txn, + QMI_WLFW_GET_INFO_REQ_V01, + WLFW_GET_INFO_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_get_info_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Failed to send get info request, err: %d\n", + ret); + goto out; + } + + ret = qmi_txn_wait(&txn, plat_priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Failed to wait for response of get info request, err: %d\n", + ret); + goto out; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("Get info request failed, result: %d, err: %d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + kfree(req); + kfree(resp); + return 0; + +out: + kfree(req); + kfree(resp); + return ret; +} + +int wlfw_subsys_restart_level_msg(struct icnss_priv *penv, uint8_t restart_level) +{ + int ret; + struct wlfw_subsys_restart_level_req_msg_v01 *req; + struct wlfw_subsys_restart_level_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!penv) + return -ENODEV; + + if (test_bit(ICNSS_FW_DOWN, &penv->state)) + return -EINVAL; + + icnss_pr_dbg("Sending subsystem restart level: 0x%x\n", restart_level); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->restart_level_type_valid = 1; + req->restart_level_type = restart_level; + + penv->stats.restart_level_req++; + + ret = qmi_txn_init(&penv->qmi, &txn, + wlfw_subsys_restart_level_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Fail to init txn for subsystem restart level, resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&penv->qmi, NULL, &txn, + QMI_WLFW_SUBSYS_RESTART_LEVEL_REQ_V01, + WLFW_SUBSYS_RESTART_LEVEL_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_subsys_restart_level_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send subsystem restart level %d\n", + ret); + goto out; + } + + ret = qmi_txn_wait(&txn, penv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("Subsystem restart level timed out with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("Subsystem restart level request rejected,result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + penv->stats.restart_level_resp++; + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(req); + kfree(resp); + penv->stats.restart_level_err++; + return ret; +} diff --git a/icnss2/qmi.h b/icnss2/qmi.h new file mode 100644 index 0000000000..c99595a470 --- /dev/null +++ b/icnss2/qmi.h @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __ICNSS_QMI_H__ +#define __ICNSS_QMI_H__ + +#include "device_management_service_v01.h" + +#define QDSS_TRACE_SEG_LEN_MAX 32 +#define QDSS_TRACE_FILE_NAME_MAX 16 +#define M3_SEGMENTS_SIZE_MAX 10 +#define M3_SEGMENT_NAME_LEN_MAX 16 + +struct icnss_mem_seg { + u64 addr; + u32 size; +}; + +struct icnss_qmi_event_qdss_trace_save_data { + u32 total_size; + u32 mem_seg_len; + struct icnss_mem_seg mem_seg[QDSS_TRACE_SEG_LEN_MAX]; + char file_name[QDSS_TRACE_FILE_NAME_MAX + 1]; +}; + +struct icnss_m3_segment { + u32 type; + u64 addr; + u64 size; + char name[M3_SEGMENT_NAME_LEN_MAX + 1]; +}; + +struct icnss_m3_upload_segments_req_data { + u32 pdev_id; + u32 no_of_valid_segments; + struct icnss_m3_segment m3_segment[M3_SEGMENTS_SIZE_MAX]; +}; + +struct icnss_qmi_event_qdss_trace_req_data { + u32 total_size; + char file_name[QDSS_TRACE_FILE_NAME_MAX + 1]; +}; + +#ifndef CONFIG_ICNSS2_QMI + +static inline int wlfw_ind_register_send_sync_msg(struct icnss_priv *priv) +{ + return 0; +} +static inline int icnss_connect_to_fw_server(struct icnss_priv *priv, + void *data) +{ + return 0; +} +static inline int wlfw_msa_mem_info_send_sync_msg(struct icnss_priv *priv) +{ + return 0; +} +static inline int wlfw_msa_ready_send_sync_msg(struct icnss_priv *priv) +{ + return 0; +} +static inline int wlfw_cap_send_sync_msg(struct icnss_priv *priv) +{ + return 0; +} +static inline int wlfw_dynamic_feature_mask_send_sync_msg( + struct icnss_priv *priv, uint64_t dynamic_feature_mask) +{ + return 0; +} +static inline int icnss_clear_server(struct icnss_priv *priv) +{ + return 0; +} +static inline int wlfw_rejuvenate_ack_send_sync_msg(struct icnss_priv *priv) +{ + return 0; +} +static inline void icnss_ignore_fw_timeout(bool ignore) {} +static int wlfw_send_modem_shutdown_msg(struct icnss_priv *priv) +{ + return 0; +} +static inline int wlfw_ini_send_sync_msg(struct icnss_priv *priv, + uint8_t fw_log_mode) +{ + return 0; +} +static inline int wlfw_athdiag_read_send_sync_msg(struct icnss_priv *priv, + uint32_t offset, uint32_t mem_type, + uint32_t data_len, uint8_t *data) +{ + return 0; +} +static inline int wlfw_athdiag_write_send_sync_msg(struct icnss_priv *priv, + uint32_t offset, uint32_t mem_type, + uint32_t data_len, uint8_t *data) +{ + return 0; +} +static inline int wlfw_wlan_mode_send_sync_msg(struct icnss_priv *priv, + enum icnss_driver_mode mode) +{ + return 0; +} +static int wlfw_host_cap_send_sync(struct icnss_priv *priv) +{ + return 0; +} +static inline int icnss_send_wlan_enable_to_fw(struct icnss_priv *priv, + struct icnss_wlan_enable_cfg *config, + enum icnss_driver_mode mode, + const char *host_version) +{ + return 0; +} +static inline int icnss_send_wlan_disable_to_fw(struct icnss_priv *priv) +{ + return 0; +} +static inline int icnss_register_fw_service(struct icnss_priv *priv) +{ + return 0; +} +static inline void icnss_unregister_fw_service(struct icnss_priv *priv) {} +static inline int icnss_send_vbatt_update(struct icnss_priv *priv, + uint64_t voltage_uv) +{ + return 0; +} + +static inline int wlfw_device_info_send_msg(struct icnss_priv *priv) +{ + return 0; +} +int wlfw_wlan_mode_send_sync_msg(struct icnss_priv *priv, + enum wlfw_driver_mode_enum_v01 mode) +{ + return 0; +} +int icnss_wlfw_bdf_dnld_send_sync(struct icnss_priv *priv, u32 bdf_type) +{ + return 0; +} + +int wlfw_qdss_trace_mem_info_send_sync(struct icnss_priv *priv) +{ + return 0; +} + +int wlfw_power_save_send_msg(struct icnss_priv *priv, + enum wlfw_power_save_mode_v01 mode) +{ + return 0; +} + +int icnss_wlfw_get_info_send_sync(struct icnss_priv *priv, int type, + void *cmd, int cmd_len) +{ + return 0; +} + +int wlfw_send_soc_wake_msg(struct icnss_priv *priv, + enum wlfw_soc_wake_enum_v01 type) +{ + return 0; +} + +int icnss_wlfw_m3_dump_upload_done_send_sync(struct icnss_priv *priv, + u32 pdev_id, int status) +{ + return 0; +} + +int icnss_qmi_get_dms_mac(struct icnss_priv *priv) +{ + return 0; +} + +int icnss_wlfw_wlan_mac_req_send_sync(struct icnss_priv *priv, + u8 *mac, u32 mac_len) +{ + return 0; +} + +int icnss_dms_init(struct icns_priv *priv) +{ + return 0; +} + +void icnss_dms_deinit(struct icnss_priv *priv) +{ +} + +int wlfw_subsys_restart_level_msg(struct icnss_priv *penv, uint8_t restart_level) +{ + return 0; +} + +static inline int wlfw_cal_report_req(struct icnss_priv *priv) +{ + return 0; +} +#else +int wlfw_ind_register_send_sync_msg(struct icnss_priv *priv); +int icnss_connect_to_fw_server(struct icnss_priv *priv, void *data); +int wlfw_msa_mem_info_send_sync_msg(struct icnss_priv *priv); +int wlfw_msa_ready_send_sync_msg(struct icnss_priv *priv); +int wlfw_cap_send_sync_msg(struct icnss_priv *priv); +int icnss_qmi_pin_connect_result_ind(struct icnss_priv *priv, + void *msg, unsigned int msg_len); +int wlfw_dynamic_feature_mask_send_sync_msg(struct icnss_priv *priv, + uint64_t dynamic_feature_mask); +int icnss_clear_server(struct icnss_priv *priv); +int wlfw_rejuvenate_ack_send_sync_msg(struct icnss_priv *priv); +void icnss_ignore_fw_timeout(bool ignore); +int wlfw_send_modem_shutdown_msg(struct icnss_priv *priv); +int wlfw_ini_send_sync_msg(struct icnss_priv *priv, uint8_t fw_log_mode); +int wlfw_athdiag_read_send_sync_msg(struct icnss_priv *priv, + uint32_t offset, uint32_t mem_type, + uint32_t data_len, uint8_t *data); +int wlfw_athdiag_write_send_sync_msg(struct icnss_priv *priv, + uint32_t offset, uint32_t mem_type, + uint32_t data_len, uint8_t *data); +int icnss_send_wlan_enable_to_fw(struct icnss_priv *priv, + struct icnss_wlan_enable_cfg *config, + enum icnss_driver_mode mode, + const char *host_version); +int icnss_send_wlan_disable_to_fw(struct icnss_priv *priv); +int icnss_register_fw_service(struct icnss_priv *priv); +void icnss_unregister_fw_service(struct icnss_priv *priv); +int icnss_send_vbatt_update(struct icnss_priv *priv, uint64_t voltage_uv); +int wlfw_host_cap_send_sync(struct icnss_priv *priv); +int wlfw_device_info_send_msg(struct icnss_priv *priv); +int wlfw_wlan_mode_send_sync_msg(struct icnss_priv *priv, + enum wlfw_driver_mode_enum_v01 mode); +int icnss_wlfw_bdf_dnld_send_sync(struct icnss_priv *priv, u32 bdf_type); +int icnss_wlfw_qdss_dnld_send_sync(struct icnss_priv *priv); +int icnss_wlfw_qdss_data_send_sync(struct icnss_priv *priv, char *file_name, + u32 total_size); +int wlfw_qdss_trace_start(struct icnss_priv *priv); +int wlfw_qdss_trace_stop(struct icnss_priv *priv, unsigned long long option); +int wlfw_qdss_trace_mem_info_send_sync(struct icnss_priv *priv); +int wlfw_power_save_send_msg(struct icnss_priv *priv, + enum wlfw_power_save_mode_v01 mode); +int icnss_wlfw_get_info_send_sync(struct icnss_priv *priv, int type, + void *cmd, int cmd_len); +int wlfw_send_soc_wake_msg(struct icnss_priv *priv, + enum wlfw_soc_wake_enum_v01 type); +int icnss_wlfw_m3_dump_upload_done_send_sync(struct icnss_priv *priv, + u32 pdev_id, int status); +int icnss_qmi_get_dms_mac(struct icnss_priv *priv); +int icnss_wlfw_wlan_mac_req_send_sync(struct icnss_priv *priv, + u8 *mac, u32 mac_len); +int icnss_dms_init(struct icnss_priv *priv); +void icnss_dms_deinit(struct icnss_priv *priv); +int wlfw_subsys_restart_level_msg(struct icnss_priv *penv, uint8_t restart_level); +int wlfw_cal_report_req(struct icnss_priv *priv); +#endif + +#endif /* __ICNSS_QMI_H__*/ diff --git a/inc/icnss2.h b/inc/icnss2.h new file mode 100644 index 0000000000..98e51e6041 --- /dev/null +++ b/inc/icnss2.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + */ +#ifndef _ICNSS_WLAN_H_ +#define _ICNSS_WLAN_H_ + +#include +#include + +#define ICNSS_MAX_IRQ_REGISTRATIONS 12 +#define IWCN_MAX_IRQ_REGISTRATIONS 32 +#define ICNSS_MAX_TIMESTAMP_LEN 32 + +#ifndef ICNSS_API_WITH_DEV +#define ICNSS_API_WITH_DEV +#endif + +#define DEVICE_NAME_MAX 10 +enum icnss_uevent { + ICNSS_UEVENT_FW_CRASHED, + ICNSS_UEVENT_FW_DOWN, + ICNSS_UEVENT_HANG_DATA, + ICNSS_UEVENT_SMMU_FAULT, +}; + +struct icnss_uevent_hang_data { + void *hang_event_data; + uint16_t hang_event_data_len; +}; + +struct icnss_uevent_fw_down_data { + bool crashed; +}; + +struct icnss_uevent_data { + enum icnss_uevent uevent; + void *data; +}; + +/* Device information like supported device ids, etc*/ +struct device_info { + char name[DEVICE_NAME_MAX]; + uint16_t device_id; +}; + +struct icnss_driver_ops { + char *name; + struct device_info *dev_info; + unsigned long drv_state; + struct device_driver driver; + int (*probe)(struct device *dev); + void (*remove)(struct device *dev); + void (*shutdown)(struct device *dev); + int (*reinit)(struct device *dev); + void (*crash_shutdown)(void *pdev); + int (*pm_suspend)(struct device *dev); + int (*pm_resume)(struct device *dev); + int (*suspend_noirq)(struct device *dev); + int (*resume_noirq)(struct device *dev); + int (*runtime_suspend)(struct device *dev); + int (*runtime_resume)(struct device *dev); + int (*uevent)(struct device *dev, struct icnss_uevent_data *uevent); + int (*idle_shutdown)(struct device *dev); + int (*idle_restart)(struct device *dev); + int (*set_therm_cdev_state)(struct device *dev, + unsigned long thermal_state, + int tcdev_id); +}; + + +struct ce_tgt_pipe_cfg { + u32 pipe_num; + u32 pipe_dir; + u32 nentries; + u32 nbytes_max; + u32 flags; + u32 reserved; +}; + +struct ce_svc_pipe_cfg { + u32 service_id; + u32 pipe_dir; + u32 pipe_num; +}; + +struct icnss_shadow_reg_cfg { + u16 ce_id; + u16 reg_offset; +}; + +struct icnss_shadow_reg_v2_cfg { + u32 addr; +}; + +struct icnss_rri_over_ddr_cfg { + u32 base_addr_low; + u32 base_addr_high; +}; +/* CE configuration to target */ +struct icnss_wlan_enable_cfg { + u32 num_ce_tgt_cfg; + struct ce_tgt_pipe_cfg *ce_tgt_cfg; + u32 num_ce_svc_pipe_cfg; + struct ce_svc_pipe_cfg *ce_svc_cfg; + u32 num_shadow_reg_cfg; + struct icnss_shadow_reg_cfg *shadow_reg_cfg; + u32 num_shadow_reg_v2_cfg; + struct icnss_shadow_reg_v2_cfg *shadow_reg_v2_cfg; + bool rri_over_ddr_cfg_valid; + struct icnss_rri_over_ddr_cfg rri_over_ddr_cfg; +}; + +/* driver modes */ +enum icnss_driver_mode { + ICNSS_MISSION, + ICNSS_FTM, + ICNSS_EPPING, + ICNSS_WALTEST, + ICNSS_OFF, + ICNSS_CCPM, + ICNSS_QVIT, + ICNSS_CALIBRATION, +}; + +struct icnss_soc_info { + void __iomem *v_addr; + phys_addr_t p_addr; + uint32_t chip_id; + uint32_t chip_family; + uint32_t board_id; + uint32_t soc_id; + uint32_t fw_version; + char fw_build_timestamp[ICNSS_MAX_TIMESTAMP_LEN + 1]; +}; + +#define icnss_register_driver(ops) \ + __icnss_register_driver(ops, THIS_MODULE, KBUILD_MODNAME) +extern int __icnss_register_driver(struct icnss_driver_ops *ops, + struct module *owner, const char *mod_name); + +extern int icnss_unregister_driver(struct icnss_driver_ops *ops); + +extern int icnss_wlan_enable(struct device *dev, + struct icnss_wlan_enable_cfg *config, + enum icnss_driver_mode mode, + const char *host_version); +extern int icnss_wlan_disable(struct device *dev, enum icnss_driver_mode mode); +extern void icnss_enable_irq(struct device *dev, unsigned int ce_id); +extern void icnss_disable_irq(struct device *dev, unsigned int ce_id); +extern int icnss_get_soc_info(struct device *dev, struct icnss_soc_info *info); +extern int icnss_ce_free_irq(struct device *dev, unsigned int ce_id, void *ctx); +extern int icnss_ce_request_irq(struct device *dev, unsigned int ce_id, + irqreturn_t (*handler)(int, void *), + unsigned long flags, const char *name, void *ctx); +extern int icnss_get_ce_id(struct device *dev, int irq); +extern int icnss_set_fw_log_mode(struct device *dev, uint8_t fw_log_mode); +extern int icnss_athdiag_read(struct device *dev, uint32_t offset, + uint32_t mem_type, uint32_t data_len, + uint8_t *output); +extern int icnss_athdiag_write(struct device *dev, uint32_t offset, + uint32_t mem_type, uint32_t data_len, + uint8_t *input); +extern int icnss_get_irq(struct device *dev, int ce_id); +extern int icnss_power_on(struct device *dev); +extern int icnss_power_off(struct device *dev); +extern struct dma_iommu_mapping *icnss_smmu_get_mapping(struct device *dev); +extern struct iommu_domain *icnss_smmu_get_domain(struct device *dev); +extern int icnss_smmu_map(struct device *dev, phys_addr_t paddr, + uint32_t *iova_addr, size_t size); +extern int icnss_smmu_unmap(struct device *dev, + uint32_t iova_addr, size_t size); +extern unsigned int icnss_socinfo_get_serial_number(struct device *dev); +extern bool icnss_is_qmi_disable(struct device *dev); +extern bool icnss_is_fw_ready(void); +extern bool icnss_is_fw_down(void); +extern bool icnss_is_rejuvenate(void); +extern int icnss_trigger_recovery(struct device *dev); +extern void icnss_block_shutdown(bool status); +extern bool icnss_is_pdr(void); +extern int icnss_idle_restart(struct device *dev); +extern int icnss_idle_shutdown(struct device *dev); +extern int icnss_get_user_msi_assignment(struct device *dev, char *user_name, + int *num_vectors, u32 *user_base_data, + u32 *base_vector); +extern int icnss_get_msi_irq(struct device *dev, unsigned int vector); +extern void icnss_get_msi_address(struct device *dev, u32 *msi_addr_low, + u32 *msi_addr_high); +extern int icnss_qmi_send(struct device *dev, int type, void *cmd, + int cmd_len, void *cb_ctx, + int (*cb)(void *ctx, void *event, int event_len)); +extern int icnss_force_wake_request(struct device *dev); +extern int icnss_force_wake_release(struct device *dev); +extern int icnss_is_device_awake(struct device *dev); +extern int icnss_thermal_cdev_register(struct device *dev, + unsigned long max_state, + int tcdev_id); +extern void icnss_thermal_cdev_unregister(struct device *dev, int tcdev_id); +extern int icnss_get_curr_therm_cdev_state(struct device *dev, + unsigned long *thermal_state, + int tcdev_id); +extern int icnss_exit_power_save(struct device *dev); +extern int icnss_prevent_l1(struct device *dev); +extern void icnss_allow_l1(struct device *dev); +extern int icnss_get_mhi_state(struct device *dev); +extern int icnss_is_pci_ep_awake(struct device *dev); +#endif /* _ICNSS_WLAN_H_ */