cnss2: Add debugfs for dynamic feature

Add a debugfs for dynamic feature. User can enable/disable certain
features in WLAN FW by writing to this debugfs.

CRs-Fixed: 2468610
Change-Id: Iefaa905c52011edcf9619d5ddbe8e356dfce04ba
Signed-off-by: Yuanyuan Liu <yuanliu@codeaurora.org>
diff --git a/drivers/net/wireless/cnss2/debug.c b/drivers/net/wireless/cnss2/debug.c
index 3e8ff87..89dcc2a 100644
--- a/drivers/net/wireless/cnss2/debug.c
+++ b/drivers/net/wireless/cnss2/debug.c
@@ -650,6 +650,51 @@
 	.llseek = seq_lseek,
 };
 
+static ssize_t cnss_dynamic_feature_write(struct file *fp,
+					  const char __user *user_buf,
+					  size_t count, loff_t *off)
+{
+	struct cnss_plat_data *plat_priv =
+		((struct seq_file *)fp->private_data)->private;
+	int ret = 0;
+	u64 val;
+
+	ret = kstrtou64_from_user(user_buf, count, 0, &val);
+	if (ret)
+		return ret;
+
+	plat_priv->dynamic_feature = val;
+	ret = cnss_wlfw_dynamic_feature_mask_send_sync(plat_priv);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static int cnss_dynamic_feature_show(struct seq_file *s, void *data)
+{
+	struct cnss_plat_data *cnss_priv = s->private;
+
+	seq_printf(s, "dynamic_feature: 0x%llx\n", cnss_priv->dynamic_feature);
+
+	return 0;
+}
+
+static int cnss_dynamic_feature_open(struct inode *inode,
+				     struct file *file)
+{
+	return single_open(file, cnss_dynamic_feature_show,
+			   inode->i_private);
+}
+
+static const struct file_operations cnss_dynamic_feature_fops = {
+	.read = seq_read,
+	.write = cnss_dynamic_feature_write,
+	.open = cnss_dynamic_feature_open,
+	.owner = THIS_MODULE,
+	.llseek = seq_lseek,
+};
+
 #ifdef CONFIG_CNSS2_DEBUG
 static int cnss_create_debug_only_node(struct cnss_plat_data *plat_priv)
 {
@@ -665,6 +710,8 @@
 			    &cnss_runtime_pm_debug_fops);
 	debugfs_create_file("control_params", 0600, root_dentry, plat_priv,
 			    &cnss_control_params_debug_fops);
+	debugfs_create_file("dynamic_feature", 0600, root_dentry, plat_priv,
+			    &cnss_dynamic_feature_fops);
 
 	return 0;
 }
diff --git a/drivers/net/wireless/cnss2/main.h b/drivers/net/wireless/cnss2/main.h
index 46c3b6f..4831fb0 100644
--- a/drivers/net/wireless/cnss2/main.h
+++ b/drivers/net/wireless/cnss2/main.h
@@ -339,6 +339,7 @@
 	struct qmi_handle coex_qmi;
 	struct qmi_handle ims_qmi;
 	struct qmi_txn txn;
+	u64 dynamic_feature;
 };
 
 #ifdef CONFIG_ARCH_QCOM
diff --git a/drivers/net/wireless/cnss2/qmi.c b/drivers/net/wireless/cnss2/qmi.c
index afae7c1..9840a52 100644
--- a/drivers/net/wireless/cnss2/qmi.c
+++ b/drivers/net/wireless/cnss2/qmi.c
@@ -1368,6 +1368,70 @@
 	return ret;
 }
 
+int cnss_wlfw_dynamic_feature_mask_send_sync(struct cnss_plat_data *plat_priv)
+{
+	struct wlfw_dynamic_feature_mask_req_msg_v01 *req;
+	struct wlfw_dynamic_feature_mask_resp_msg_v01 *resp;
+	struct qmi_txn txn;
+	int ret = 0;
+
+	cnss_pr_dbg("Sending dynamic feature mask 0x%llx, state: 0x%lx\n",
+		    plat_priv->dynamic_feature,
+		    plat_priv->driver_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 = plat_priv->dynamic_feature;
+
+	ret = qmi_txn_init(&plat_priv->qmi_wlfw, &txn,
+			   wlfw_dynamic_feature_mask_resp_msg_v01_ei, resp);
+	if (ret < 0) {
+		cnss_pr_err("Fail to initialize txn for dynamic feature mask request: err %d\n",
+			    ret);
+		goto out;
+	}
+
+	ret = qmi_send_request
+		(&plat_priv->qmi_wlfw, 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);
+		cnss_pr_err("Fail to send dynamic feature mask request: err %d\n",
+			    ret);
+		goto out;
+	}
+
+	ret = qmi_txn_wait(&txn, QMI_WLFW_TIMEOUT_JF);
+	if (ret < 0) {
+		cnss_pr_err("Fail to wait for response of dynamic feature mask request, err %d\n",
+			    ret);
+		goto out;
+	}
+
+	if (resp->resp.result != QMI_RESULT_SUCCESS_V01) {
+		cnss_pr_err("Dynamic feature mask request failed, result: %d, err: %d\n",
+			    resp->resp.result, resp->resp.error);
+		ret = -resp->resp.result;
+		goto out;
+	}
+
+out:
+	kfree(req);
+	kfree(resp);
+	return ret;
+}
+
 unsigned int cnss_get_qmi_timeout(struct cnss_plat_data *plat_priv)
 {
 	cnss_pr_dbg("QMI timeout is %u ms\n", QMI_WLFW_TIMEOUT_MS);
diff --git a/drivers/net/wireless/cnss2/qmi.h b/drivers/net/wireless/cnss2/qmi.h
index 4913aa0..a064660 100644
--- a/drivers/net/wireless/cnss2/qmi.h
+++ b/drivers/net/wireless/cnss2/qmi.h
@@ -58,6 +58,7 @@
 			    u8 fw_log_mode);
 int cnss_wlfw_antenna_switch_send_sync(struct cnss_plat_data *plat_priv);
 int cnss_wlfw_antenna_grant_send_sync(struct cnss_plat_data *plat_priv);
+int cnss_wlfw_dynamic_feature_mask_send_sync(struct cnss_plat_data *plat_priv);
 int cnss_register_coex_service(struct cnss_plat_data *plat_priv);
 void cnss_unregister_coex_service(struct cnss_plat_data *plat_priv);
 int coex_antenna_switch_to_wlan_send_sync_msg(struct cnss_plat_data *plat_priv);
@@ -167,6 +168,12 @@
 }
 
 static inline
+int cnss_wlfw_dynamic_feature_mask_send_sync(struct cnss_plat_data *plat_priv)
+{
+	return 0;
+}
+
+static inline
 int cnss_register_coex_service(struct cnss_plat_data *plat_priv)
 {
 	return 0;