Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cd2915d4 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "ath10k: add qmi service for snoc wlan enable and disable"

parents d8d96f71 165003b6
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ ath10k_pci-y += pci.o \
obj-$(CONFIG_ATH10K_TARGET_SNOC) += ath10k_snoc.o
ath10k_snoc-y += snoc.o \
		qmi.o \
		wcn3990_qmi_service_v01.o \
		ce.o

ath10k_pci-$(CONFIG_ATH10K_AHB) += ahb.o
+636 −1
Original line number Diff line number Diff line
@@ -12,10 +12,14 @@
#include <soc/qcom/subsystem_notif.h>
#include <soc/qcom/subsystem_restart.h>
#include <soc/qcom/service-notifier.h>
#include <soc/qcom/msm_qmi_interface.h>
#include <soc/qcom/service-locator.h>
#include "core.h"
#include "qmi.h"
#include "snoc.h"
#include <soc/qcom/icnss.h>
#include "wcn3990_qmi_service_v01.h"

static DECLARE_WAIT_QUEUE_HEAD(ath10k_fw_ready_wait_event);

static int
ath10k_snoc_service_notifier_notify(struct notifier_block *nb,
@@ -228,3 +232,634 @@ int ath10k_snoc_modem_ssr_unregister_notifier(struct ath10k *ar)
	return 0;
}

static char *
ath10k_snoc_driver_event_to_str(enum ath10k_snoc_driver_event_type type)
{
	switch (type) {
	case ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE:
		return "SERVER_ARRIVE";
	case ATH10K_SNOC_DRIVER_EVENT_SERVER_EXIT:
		return "SERVER_EXIT";
	case ATH10K_SNOC_DRIVER_EVENT_FW_READY_IND:
		return "FW_READY";
	case ATH10K_SNOC_DRIVER_EVENT_MAX:
		return "EVENT_MAX";
	}

	return "UNKNOWN";
};

static int
ath10k_snoc_driver_event_post(enum ath10k_snoc_driver_event_type type,
			      u32 flags, void *data)
{
	int ret = 0;
	int i = 0;
	struct ath10k *ar = (struct ath10k *)data;
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "Posting event: %s type: %d\n",
		   ath10k_snoc_driver_event_to_str(type), type);

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

	spin_lock_bh(&qmi_cfg->event_lock);

	for (i = 0; i < ATH10K_SNOC_DRIVER_EVENT_MAX; i++) {
		if (atomic_read(&qmi_cfg->qmi_ev_list[i].event_handled)) {
			qmi_cfg->qmi_ev_list[i].type = type;
			qmi_cfg->qmi_ev_list[i].data = data;
			init_completion(&qmi_cfg->qmi_ev_list[i].complete);
			qmi_cfg->qmi_ev_list[i].ret =
					ATH10K_SNOC_EVENT_PENDING;
			qmi_cfg->qmi_ev_list[i].sync =
					!!(flags & ATH10K_SNOC_EVENT_SYNC);
			atomic_set(&qmi_cfg->qmi_ev_list[i].event_handled, 0);
			list_add_tail(&qmi_cfg->qmi_ev_list[i].list,
				      &qmi_cfg->event_list);
			break;
		}
	}

	if (i >= ATH10K_SNOC_DRIVER_EVENT_MAX)
		i = ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE;

	spin_unlock_bh(&qmi_cfg->event_lock);

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

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

	if (flags & ATH10K_SNOC_EVENT_UNINTERRUPTIBLE)
		wait_for_completion(&qmi_cfg->qmi_ev_list[i].complete);
	else
		ret = wait_for_completion_interruptible(
			&qmi_cfg->qmi_ev_list[i].complete);

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "Completed event: %s(%d)\n",
		   ath10k_snoc_driver_event_to_str(type), type);

	spin_lock_bh(&qmi_cfg->event_lock);
	if (ret == -ERESTARTSYS &&
	    qmi_cfg->qmi_ev_list[i].ret == ATH10K_SNOC_EVENT_PENDING) {
		qmi_cfg->qmi_ev_list[i].sync = false;
		atomic_set(&qmi_cfg->qmi_ev_list[i].event_handled, 1);
		spin_unlock_bh(&qmi_cfg->event_lock);
		ret = -EINTR;
		goto out;
	}
	spin_unlock_bh(&qmi_cfg->event_lock);

out:
	return ret;
}

static int
ath10k_snoc_wlan_mode_send_sync_msg(struct ath10k *ar,
				    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 msg_desc req_desc, resp_desc;
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	if (!qmi_cfg || !qmi_cfg->wlfw_clnt)
		return -ENODEV;

	ath10k_dbg(ar, ATH10K_DBG_SNOC,
		   "Sending Mode request, mode: %d\n", mode);

	memset(&req, 0, sizeof(req));
	memset(&resp, 0, sizeof(resp));

	req.mode = mode;
	req.hw_debug_valid = 1;
	req.hw_debug = 0;

	req_desc.max_msg_len = WLFW_WLAN_MODE_REQ_MSG_V01_MAX_MSG_LEN;
	req_desc.msg_id = QMI_WLFW_WLAN_MODE_REQ_V01;
	req_desc.ei_array = wlfw_wlan_mode_req_msg_v01_ei;

	resp_desc.max_msg_len = WLFW_WLAN_MODE_RESP_MSG_V01_MAX_MSG_LEN;
	resp_desc.msg_id = QMI_WLFW_WLAN_MODE_RESP_V01;
	resp_desc.ei_array = wlfw_wlan_mode_resp_msg_v01_ei;

	ret = qmi_send_req_wait(qmi_cfg->wlfw_clnt,
				&req_desc, &req, sizeof(req),
				&resp_desc, &resp, sizeof(resp),
				WLFW_TIMEOUT_MS);
	if (ret < 0) {
		ath10k_err(ar, "Send mode req failed, mode: %d ret: %d\n",
			   mode, ret);
		return ret;
	}

	if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
		ath10k_err(ar, "QMI mode request rejected:");
		ath10k_err(ar, "mode:%d result:%d error:%d\n",
			   mode, resp.resp.result, resp.resp.error);
		ret = resp.resp.result;
		return ret;
	}

	ath10k_dbg(ar, ATH10K_DBG_SNOC,
		   "wlan Mode request send success, mode: %d\n", mode);
	return 0;
}

static int
ath10k_snoc_wlan_cfg_send_sync_msg(struct ath10k *ar,
				   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 msg_desc req_desc, resp_desc;
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	if (!qmi_cfg || !qmi_cfg->wlfw_clnt)
		return -ENODEV;

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "Sending config request\n");

	memset(&req, 0, sizeof(req));
	memset(&resp, 0, sizeof(resp));
	memcpy(&req, data, sizeof(req));

	req_desc.max_msg_len = WLFW_WLAN_CFG_REQ_MSG_V01_MAX_MSG_LEN;
	req_desc.msg_id = QMI_WLFW_WLAN_CFG_REQ_V01;
	req_desc.ei_array = wlfw_wlan_cfg_req_msg_v01_ei;

	resp_desc.max_msg_len = WLFW_WLAN_CFG_RESP_MSG_V01_MAX_MSG_LEN;
	resp_desc.msg_id = QMI_WLFW_WLAN_CFG_RESP_V01;
	resp_desc.ei_array = wlfw_wlan_cfg_resp_msg_v01_ei;

	ret = qmi_send_req_wait(qmi_cfg->wlfw_clnt,
				&req_desc, &req, sizeof(req),
				&resp_desc, &resp, sizeof(resp),
				WLFW_TIMEOUT_MS);
	if (ret < 0) {
		ath10k_err(ar, "Send config req failed %d\n", ret);
		return ret;
	}

	if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
		ath10k_err(ar, "QMI config request rejected:");
		ath10k_err(ar, "result:%d error:%d\n",
			   resp.resp.result, resp.resp.error);
		ret = resp.resp.result;
		return ret;
	}

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "wlan config request success..\n");
	return 0;
}

int ath10k_snoc_qmi_wlan_enable(struct ath10k *ar,
				struct ath10k_wlan_enable_cfg *config,
				enum ath10k_driver_mode mode,
				const char *host_version)
{
	struct wlfw_wlan_cfg_req_msg_v01 req;
	u32 i;
	int ret;
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	ath10k_dbg(ar, ATH10K_DBG_SNOC,
		   "Mode: %d, config: %p, host_version: %s\n",
		   mode, config, host_version);

	memset(&req, 0, sizeof(req));
	if (!config || !host_version) {
		ath10k_err(ar, "WLAN_EN Config Invalid:%p: host_version:%p\n",
			   config, host_version);
		ret = -EINVAL;
		return ret;
	}

	wait_event_timeout(ath10k_fw_ready_wait_event,
			   (atomic_read(&qmi_cfg->fw_ready) &&
			    atomic_read(&qmi_cfg->server_connected)),
			   msecs_to_jiffies(ATH10K_SNOC_WLAN_FW_READY_TIMEOUT));

	req.host_version_valid = 1;
	strlcpy(req.host_version, host_version,
		QMI_WLFW_MAX_STR_LEN_V01 + 1);

	req.tgt_cfg_valid = 1;
	if (config->num_ce_tgt_cfg > QMI_WLFW_MAX_NUM_CE_V01)
		req.tgt_cfg_len = QMI_WLFW_MAX_NUM_CE_V01;
	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 > QMI_WLFW_MAX_NUM_SVC_V01)
		req.svc_cfg_len = QMI_WLFW_MAX_NUM_SVC_V01;
	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;
	}

	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_shadow_reg_cfg_s_v01) * req.shadow_reg_len);

	ret = ath10k_snoc_wlan_cfg_send_sync_msg(ar, &req);
	if (ret) {
		ath10k_err(ar, "WLAN config send failed\n");
		return ret;
	}

	ret = ath10k_snoc_wlan_mode_send_sync_msg(ar, mode);
	if (ret) {
		ath10k_err(ar, "WLAN mode send failed\n");
		return ret;
	}

	return 0;
}

int ath10k_snoc_qmi_wlan_disable(struct ath10k *ar)
{
	return ath10k_snoc_wlan_mode_send_sync_msg(ar, QMI_WLFW_OFF_V01);
}

static int ath10k_snoc_ind_register_send_sync_msg(struct ath10k *ar)
{
	int ret;
	struct wlfw_ind_register_req_msg_v01 req;
	struct wlfw_ind_register_resp_msg_v01 resp;
	struct msg_desc req_desc, resp_desc;
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	ath10k_dbg(ar, ATH10K_DBG_SNOC,
		   "Sending indication register message,\n");

	memset(&req, 0, sizeof(req));
	memset(&resp, 0, sizeof(resp));

	req.client_id_valid = 1;
	req.client_id = WLFW_CLIENT_ID;
	req.fw_ready_enable_valid = 1;
	req.fw_ready_enable = 1;
	req.msa_ready_enable_valid = 1;
	req.msa_ready_enable = 1;

	req_desc.max_msg_len = WLFW_IND_REGISTER_REQ_MSG_V01_MAX_MSG_LEN;
	req_desc.msg_id = QMI_WLFW_IND_REGISTER_REQ_V01;
	req_desc.ei_array = wlfw_ind_register_req_msg_v01_ei;

	resp_desc.max_msg_len = WLFW_IND_REGISTER_RESP_MSG_V01_MAX_MSG_LEN;
	resp_desc.msg_id = QMI_WLFW_IND_REGISTER_RESP_V01;
	resp_desc.ei_array = wlfw_ind_register_resp_msg_v01_ei;

	ret = qmi_send_req_wait(qmi_cfg->wlfw_clnt,
				&req_desc, &req, sizeof(req),
				&resp_desc, &resp, sizeof(resp),
				WLFW_TIMEOUT_MS);
	if (ret < 0) {
		ath10k_err(ar, "Send indication register req failed %d\n", ret);
		return ret;
	}

	if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
		ath10k_err(ar, "QMI indication register request rejected:");
		ath10k_err(ar, "resut:%d error:%d\n",
			   resp.resp.result, resp.resp.error);
		ret = resp.resp.result;
		return ret;
	}

	return 0;
}

static void ath10k_snoc_qmi_wlfw_clnt_notify_work(struct work_struct *work)
{
	int ret;
	struct ath10k_snoc_qmi_config *qmi_cfg =
		container_of(work, struct ath10k_snoc_qmi_config,
			     qmi_recv_msg_work);
	struct ath10k_snoc *ar_snoc =
		container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg);
	struct ath10k *ar = ar_snoc->ar;

	ath10k_dbg(ar, ATH10K_DBG_SNOC,
		   "Receiving Event in work queue context\n");

	do {
	} while ((ret = qmi_recv_msg(qmi_cfg->wlfw_clnt)) == 0);

	if (ret != -ENOMSG)
		ath10k_err(ar, "Error receiving message: %d\n", ret);

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "Receiving Event completed\n");
}

static void
ath10k_snoc_qmi_wlfw_clnt_notify(struct qmi_handle *handle,
				 enum qmi_event_type event,
				 void *notify_priv)
{
	struct ath10k_snoc_qmi_config *qmi_cfg =
		(struct ath10k_snoc_qmi_config *)notify_priv;
	struct ath10k_snoc *ar_snoc =
		container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg);
	struct ath10k *ar = ar_snoc->ar;

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "QMI client notify: %d\n", event);

	if (!qmi_cfg || !qmi_cfg->wlfw_clnt)
		return;

	switch (event) {
	case QMI_RECV_MSG:
		schedule_work(&qmi_cfg->qmi_recv_msg_work);
		break;
	default:
		ath10k_dbg(ar, ATH10K_DBG_SNOC, "Unknown Event: %d\n", event);
		break;
	}
}

static void
ath10k_snoc_qmi_wlfw_clnt_ind(struct qmi_handle *handle,
			      unsigned int msg_id, void *msg,
			      unsigned int msg_len, void *ind_cb_priv)
{
	struct ath10k_snoc_qmi_config *qmi_cfg =
		(struct ath10k_snoc_qmi_config *)ind_cb_priv;
	struct ath10k_snoc *ar_snoc =
		container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg);
	struct ath10k *ar = ar_snoc->ar;

	ath10k_dbg(ar, ATH10K_DBG_SNOC,
		   "Received Ind 0x%x, msg_len: %d\n", msg_id, msg_len);
	switch (msg_id) {
	case QMI_WLFW_FW_READY_IND_V01:
		ath10k_snoc_driver_event_post(
			ATH10K_SNOC_DRIVER_EVENT_FW_READY_IND, 0, ar);
		break;
	case QMI_WLFW_MSA_READY_IND_V01:
		qmi_cfg->msa_ready = true;
		ath10k_dbg(ar, ATH10K_DBG_SNOC,
			   "Received MSA Ready, ind = 0x%x\n", msg_id);
		break;
	default:
		ath10k_err(ar, "Invalid msg_id 0x%x\n", msg_id);
		break;
	}
}

static int ath10k_snoc_driver_event_server_arrive(struct ath10k *ar)
{
	int ret = 0;
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	if (!qmi_cfg)
		return -ENODEV;

	qmi_cfg->wlfw_clnt = qmi_handle_create(
			ath10k_snoc_qmi_wlfw_clnt_notify, qmi_cfg);
	if (!qmi_cfg->wlfw_clnt) {
		ath10k_dbg(ar, ATH10K_DBG_SNOC,
			   "QMI client handle create failed\n");
		return -ENOMEM;
	}

	ret = qmi_connect_to_service(qmi_cfg->wlfw_clnt,
				     WLFW_SERVICE_ID_V01,
				     WLFW_SERVICE_VERS_V01,
				     WLFW_SERVICE_INS_ID_V01);
	if (ret < 0) {
		ath10k_err(ar, "QMI WLAN Service not found : %d\n", ret);
		goto err_qmi_config;
	}

	ret = qmi_register_ind_cb(qmi_cfg->wlfw_clnt,
				  ath10k_snoc_qmi_wlfw_clnt_ind, qmi_cfg);
	if (ret < 0) {
		ath10k_err(ar, "Failed to register indication callback: %d\n",
			   ret);
		goto err_qmi_config;
	}

	ret = ath10k_snoc_ind_register_send_sync_msg(ar);
	if (ret) {
		ath10k_err(ar, "Failed to config qmi ind register\n");
		goto err_qmi_config;
	}

	atomic_set(&qmi_cfg->server_connected, 1);
	wake_up_all(&ath10k_fw_ready_wait_event);
	ath10k_dbg(ar, ATH10K_DBG_SNOC,
		   "QMI Server Arrive Configuration Success\n");
	return 0;

err_qmi_config:
	qmi_handle_destroy(qmi_cfg->wlfw_clnt);
	qmi_cfg->wlfw_clnt = NULL;
	return ret;
}

static int ath10k_snoc_driver_event_server_exit(struct ath10k *ar)
{
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "QMI Server Exit event received\n");
	atomic_set(&qmi_cfg->fw_ready, 0);
	qmi_cfg->msa_ready = false;
	atomic_set(&qmi_cfg->server_connected, 0);
	return 0;
}

static int ath10k_snoc_driver_event_fw_ready_ind(struct ath10k *ar)
{
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "FW Ready event received.\n");
	atomic_set(&qmi_cfg->fw_ready, 1);
	wake_up_all(&ath10k_fw_ready_wait_event);

	return 0;
}

static void ath10k_snoc_driver_event_work(struct work_struct *work)
{
	struct ath10k_snoc_qmi_driver_event *event;
	int ret;
	struct ath10k_snoc_qmi_config *qmi_cfg =
		container_of(work, struct ath10k_snoc_qmi_config, event_work);
	struct ath10k_snoc *ar_snoc =
		container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg);
	struct ath10k *ar = ar_snoc->ar;

	spin_lock_bh(&qmi_cfg->event_lock);

	while (!list_empty(&qmi_cfg->event_list)) {
		event = list_first_entry(&qmi_cfg->event_list,
					 struct ath10k_snoc_qmi_driver_event,
					 list);
		list_del(&event->list);
		spin_unlock_bh(&qmi_cfg->event_lock);

		ath10k_dbg(ar, ATH10K_DBG_SNOC, "Processing event: %s%s(%d)\n",
			   ath10k_snoc_driver_event_to_str(event->type),
			   event->sync ? "-sync" : "", event->type);

		switch (event->type) {
		case ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE:
			ret = ath10k_snoc_driver_event_server_arrive(ar);
			break;
		case ATH10K_SNOC_DRIVER_EVENT_SERVER_EXIT:
			ret = ath10k_snoc_driver_event_server_exit(ar);
			break;
		case ATH10K_SNOC_DRIVER_EVENT_FW_READY_IND:
			ret = ath10k_snoc_driver_event_fw_ready_ind(ar);
			break;
		default:
			ath10k_err(ar, "Invalid Event type: %d", event->type);
			kfree(event);
			continue;
		}

		atomic_set(&event->event_handled, 1);
		ath10k_dbg(ar, ATH10K_DBG_SNOC,
			   "Event Processed: %s%s(%d), ret: %d\n",
			   ath10k_snoc_driver_event_to_str(event->type),
			   event->sync ? "-sync" : "", event->type, ret);
		spin_lock_bh(&qmi_cfg->event_lock);
		if (event->sync) {
			event->ret = ret;
			complete(&event->complete);
			continue;
		}
		spin_unlock_bh(&qmi_cfg->event_lock);
		spin_lock_bh(&qmi_cfg->event_lock);
	}

	spin_unlock_bh(&qmi_cfg->event_lock);
}

static int
ath10k_snoc_qmi_wlfw_clnt_svc_event_notify(struct notifier_block *this,
					   unsigned long code,
					   void *_cmd)
{
	int ret = 0;
	struct ath10k_snoc_qmi_config *qmi_cfg =
		container_of(this, struct ath10k_snoc_qmi_config, wlfw_clnt_nb);
	struct ath10k_snoc *ar_snoc =
			container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg);
	struct ath10k *ar = ar_snoc->ar;

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "Event Notify: code: %ld", code);

	switch (code) {
	case QMI_SERVER_ARRIVE:
		ret = ath10k_snoc_driver_event_post(
			ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE, 0, ar);
		break;
	case QMI_SERVER_EXIT:
		ret = ath10k_snoc_driver_event_post(
			ATH10K_SNOC_DRIVER_EVENT_SERVER_EXIT, 0, ar);
		break;
	default:
		ath10k_err(ar, "Invalid code: %ld", code);
		break;
	}

	return ret;
}

int ath10k_snoc_start_qmi_service(struct ath10k *ar)
{
	int ret;
	int i;
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	qmi_cfg->event_wq = alloc_workqueue("ath10k_snoc_driver_event",
					    WQ_UNBOUND, 1);
	if (!qmi_cfg->event_wq) {
		ath10k_err(ar, "Workqueue creation failed\n");
		return -EFAULT;
	}

	spin_lock_init(&qmi_cfg->event_lock);
	atomic_set(&qmi_cfg->fw_ready, 0);
	atomic_set(&qmi_cfg->server_connected, 0);

	INIT_WORK(&qmi_cfg->event_work, ath10k_snoc_driver_event_work);
	INIT_WORK(&qmi_cfg->qmi_recv_msg_work,
		  ath10k_snoc_qmi_wlfw_clnt_notify_work);
	INIT_LIST_HEAD(&qmi_cfg->event_list);

	for (i = 0; i < ATH10K_SNOC_DRIVER_EVENT_MAX; i++)
		atomic_set(&qmi_cfg->qmi_ev_list[i].event_handled, 1);

	qmi_cfg->wlfw_clnt_nb.notifier_call =
		ath10k_snoc_qmi_wlfw_clnt_svc_event_notify;
	ret = qmi_svc_event_notifier_register(WLFW_SERVICE_ID_V01,
					      WLFW_SERVICE_VERS_V01,
					      WLFW_SERVICE_INS_ID_V01,
					      &qmi_cfg->wlfw_clnt_nb);
	if (ret < 0) {
		ath10k_err(ar, "Notifier register failed: %d\n", ret);
		ret = -EFAULT;
		goto out_destroy_wq;
	}

	atomic_set(&qmi_cfg->fw_ready, 1);
	ath10k_dbg(ar, ATH10K_DBG_SNOC, "QMI service started successfully\n");
	return 0;

out_destroy_wq:
	destroy_workqueue(qmi_cfg->event_wq);
	return ret;
}

void ath10k_snoc_stop_qmi_service(struct ath10k *ar)
{
	struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
	struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg;

	ath10k_dbg(ar, ATH10K_DBG_SNOC, "Removing QMI service..\n");

	qmi_svc_event_notifier_unregister(WLFW_SERVICE_ID_V01,
					  WLFW_SERVICE_VERS_V01,
					  WLFW_SERVICE_INS_ID_V01,
					  &qmi_cfg->wlfw_clnt_nb);

	wake_up_all(&ath10k_fw_ready_wait_event);
	destroy_workqueue(qmi_cfg->event_wq);
	qmi_cfg = NULL;
}
+138 −1
Original line number Diff line number Diff line
@@ -11,9 +11,146 @@
 */
#ifndef _QMI_H_
#define _QMI_H_

#define ATH10K_SNOC_EVENT_PENDING		2989
#define ATH10K_SNOC_EVENT_SYNC			BIT(0)
#define ATH10K_SNOC_EVENT_UNINTERRUPTIBLE	BIT(1)
#define ATH10K_SNOC_WLAN_FW_READY_TIMEOUT	8000

#define WLFW_SERVICE_INS_ID_V01		0
#define WLFW_CLIENT_ID			0x4b4e454c
#define WLFW_TIMEOUT_MS			20000

enum ath10k_snoc_driver_event_type {
	ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE,
	ATH10K_SNOC_DRIVER_EVENT_SERVER_EXIT,
	ATH10K_SNOC_DRIVER_EVENT_FW_READY_IND,
	ATH10K_SNOC_DRIVER_EVENT_MAX,
};

/* enum ath10k_driver_mode: ath10k driver mode
 * @ATH10K_MISSION: mission mode
 * @ATH10K_FTM: ftm mode
 * @ATH10K_EPPING: epping mode
 * @ATH10K_OFF: off mode
 */
enum ath10k_driver_mode {
	ATH10K_MISSION,
	ATH10K_FTM,
	ATH10K_EPPING,
	ATH10K_OFF
};

/* struct ath10k_ce_tgt_pipe_cfg: target pipe configuration
 * @pipe_num: pipe number
 * @pipe_dir: pipe direction
 * @nentries: entries in pipe
 * @nbytes_max: pipe max size
 * @flags: pipe flags
 * @reserved: reserved
 */
struct ath10k_ce_tgt_pipe_cfg {
	u32 pipe_num;
	u32 pipe_dir;
	u32 nentries;
	u32 nbytes_max;
	u32 flags;
	u32 reserved;
};

/* struct ath10k_ce_svc_pipe_cfg: service pipe configuration
 * @service_id: target version
 * @pipe_dir: pipe direction
 * @pipe_num: pipe number
 */
struct ath10k_ce_svc_pipe_cfg {
	u32 service_id;
	u32 pipe_dir;
	u32 pipe_num;
};

/* struct ath10k_shadow_reg_cfg: shadow register configuration
 * @ce_id: copy engine id
 * @reg_offset: offset to copy engine
 */
struct ath10k_shadow_reg_cfg {
	u16 ce_id;
	u16 reg_offset;
};

/* struct ath10k_wlan_enable_cfg: wlan enable configuration
 * @num_ce_tgt_cfg: no of ce target configuration
 * @ce_tgt_cfg: target ce configuration
 * @num_ce_svc_pipe_cfg: no of ce service configuration
 * @ce_svc_cfg: ce service configuration
 * @num_shadow_reg_cfg: no of shadow registers
 * @shadow_reg_cfg: shadow register configuration
 */
struct ath10k_wlan_enable_cfg {
	u32 num_ce_tgt_cfg;
	struct ath10k_ce_tgt_pipe_cfg *ce_tgt_cfg;
	u32 num_ce_svc_pipe_cfg;
	struct ath10k_ce_svc_pipe_cfg *ce_svc_cfg;
	u32 num_shadow_reg_cfg;
	struct ath10k_shadow_reg_cfg *shadow_reg_cfg;
};

/* struct ath10k_snoc_qmi_driver_event: qmi driver event
 * event_handled: event handled by event work handler
 * sync: event synced
 * ret: event received return value
 * list: list to queue qmi event for process
 * type: driver event type
 * complete: completion for event handle complete
 * data: encapsulate driver data for event handler callback
 */
struct ath10k_snoc_qmi_driver_event {
	atomic_t event_handled;
	bool sync;
	int ret;
	struct list_head list;
	enum ath10k_snoc_driver_event_type type;
	struct completion complete;
	void *data;
};

/* struct ath10k_snoc_qmi_config: qmi service configuration
 * fw_ready: wlan firmware ready for wlan operation
 * msa_ready: wlan firmware msa memory ready for board data download
 * server_connected: qmi server connected
 * event_work: QMI event work
 * event_list: QMI event list
 * qmi_recv_msg_work: QMI message receive work
 * event_wq: QMI event work queue
 * wlfw_clnt_nb: WLAN firmware indication callback
 * wlfw_clnt: QMI notifier handler for wlan firmware
 * qmi_ev_list: QMI event list
 * event_lock: spinlock for qmi event work queue
 */
struct ath10k_snoc_qmi_config {
	atomic_t fw_ready;
	bool msa_ready;
	atomic_t server_connected;
	struct work_struct event_work;
	struct list_head event_list;
	struct work_struct qmi_recv_msg_work;
	struct workqueue_struct *event_wq;
	struct notifier_block wlfw_clnt_nb;
	struct qmi_handle *wlfw_clnt;
	struct ath10k_snoc_qmi_driver_event
			qmi_ev_list[ATH10K_SNOC_DRIVER_EVENT_MAX];
	spinlock_t event_lock; /* spinlock for qmi event work queue */
};

int ath10k_snoc_pd_restart_enable(struct ath10k *ar);
int ath10k_snoc_modem_ssr_register_notifier(struct ath10k *ar);
int ath10k_snoc_modem_ssr_unregister_notifier(struct ath10k *ar);
int ath10k_snoc_pdr_unregister_notifier(struct ath10k *ar);

int ath10k_snoc_start_qmi_service(struct ath10k *ar);
void ath10k_snoc_stop_qmi_service(struct ath10k *ar);
int ath10k_snoc_qmi_wlan_enable(struct ath10k *ar,
				struct ath10k_wlan_enable_cfg *config,
				enum ath10k_driver_mode mode,
				const char *host_version);
int ath10k_snoc_qmi_wlan_disable(struct ath10k *ar);
#endif
+19 −15

File changed.

Preview size limit exceeded, changes collapsed.

+2 −67
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include "hw.h"
#include "ce.h"
#include "pci.h"
#include "qmi.h"
#include <soc/qcom/service-locator.h>
#define ATH10K_SNOC_RX_POST_RETRY_MS 50
#define CE_POLL_PIPE 4
@@ -143,60 +144,7 @@ struct ath10k_snoc {
	int total_domains;
	struct notifier_block get_service_nb;
	atomic_t fw_crashed;
};

/* struct ath10k_ce_tgt_pipe_cfg: target pipe configuration
 * @pipe_num: pipe number
 * @pipe_dir: pipe direction
 * @nentries: entries in pipe
 * @nbytes_max: pipe max size
 * @flags: pipe flags
 * @reserved: reserved
 */
struct ath10k_ce_tgt_pipe_cfg {
	u32 pipe_num;
	u32 pipe_dir;
	u32 nentries;
	u32 nbytes_max;
	u32 flags;
	u32 reserved;
};

/* struct ath10k_ce_svc_pipe_cfg: service pipe configuration
 * @service_id: target version
 * @pipe_dir: pipe direction
 * @pipe_num: pipe number
 */
struct ath10k_ce_svc_pipe_cfg {
	u32 service_id;
	u32 pipe_dir;
	u32 pipe_num;
};

/* struct ath10k_shadow_reg_cfg: shadow register configuration
 * @ce_id: copy engine id
 * @reg_offset: offset to copy engine
 */
struct ath10k_shadow_reg_cfg {
	u16 ce_id;
	u16 reg_offset;
};

/* struct ath10k_wlan_enable_cfg: wlan enable configuration
 * @num_ce_tgt_cfg: no of ce target configuration
 * @ce_tgt_cfg: target ce configuration
 * @num_ce_svc_pipe_cfg: no of ce service configuration
 * @ce_svc_cfg: ce service configuration
 * @num_shadow_reg_cfg: no of shadow registers
 * @shadow_reg_cfg: shadow register configuration
 */
struct ath10k_wlan_enable_cfg {
	u32 num_ce_tgt_cfg;
	struct ath10k_ce_tgt_pipe_cfg *ce_tgt_cfg;
	u32 num_ce_svc_pipe_cfg;
	struct ath10k_ce_svc_pipe_cfg *ce_svc_cfg;
	u32 num_shadow_reg_cfg;
	struct ath10k_shadow_reg_cfg *shadow_reg_cfg;
	struct ath10k_snoc_qmi_config qmi_cfg;
};

struct ath10k_event_pd_down_data {
@@ -204,19 +152,6 @@ struct ath10k_event_pd_down_data {
	bool fw_rejuvenate;
};

/* enum ath10k_driver_mode: ath10k driver mode
 * @ATH10K_MISSION: mission mode
 * @ATH10K_FTM: ftm mode
 * @ATH10K_EPPING: epping mode
 * @ATH10K_OFF: off mode
 */
enum ath10k_driver_mode {
	ATH10K_MISSION,
	ATH10K_FTM,
	ATH10K_EPPING,
	ATH10K_OFF
};

static inline struct ath10k_snoc *ath10k_snoc_priv(struct ath10k *ar)
{
	return (struct ath10k_snoc *)ar->drv_priv;
Loading