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

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

Merge "cnss2: Ignore mode off failure when QMI service is already disconnected"

parents ea9e6709 4c8768a5
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -4,4 +4,6 @@ cnss2-y := main.o
cnss2-y += debug.o
cnss2-y += pci.o
cnss2-y += power.o
cnss2-y += qmi.o
cnss2-y += utils.o
cnss2-y += wlan_firmware_service_v01.o
+403 −2
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@
#define CNSS_DUMP_NAME			"CNSS_WLAN"
#define WLAN_RECOVERY_DELAY		1000
#define FILE_SYSTEM_READY		1
#define FW_READY_TIMEOUT		5000
#define CNSS_EVENT_PENDING		2989

static struct cnss_plat_data *plat_env;

@@ -41,6 +43,18 @@ MODULE_PARM_DESC(fbc_bypass,
		 "Bypass firmware download when loading WLAN driver");
#endif

static bool qmi_bypass;
#ifdef CONFIG_CNSS2_DEBUG
module_param(qmi_bypass, bool, S_IRUSR | S_IWUSR);
MODULE_PARM_DESC(qmi_bypass, "Bypass QMI from platform driver");
#endif

static bool enable_waltest;
#ifdef CONFIG_CNSS2_DEBUG
module_param(enable_waltest, bool, S_IRUSR | S_IWUSR);
MODULE_PARM_DESC(enable_waltest, "Enable to handle firmware waltest");
#endif

static struct cnss_fw_files FW_FILES_QCA6174_FW_3_0 = {
	"qwlan30.bin", "bdwlan30.bin", "otp30.bin", "utf30.bin",
	"utfbd30.bin", "epping30.bin", "evicted30.bin"
@@ -51,6 +65,15 @@ static struct cnss_fw_files FW_FILES_DEFAULT = {
	"utfbd.bin", "epping.bin", "evicted.bin"
};

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

static enum cnss_dev_bus_type cnss_get_dev_bus_type(struct device *dev)
{
	if (!dev)
@@ -287,6 +310,314 @@ out:
}
EXPORT_SYMBOL(cnss_common_get_wlan_mac_address);

int cnss_wlan_enable(struct device *dev,
		     struct cnss_wlan_enable_cfg *config,
		     enum cnss_driver_mode mode,
		     const char *host_version)
{
	struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
	struct wlfw_wlan_cfg_req_msg_v01 req;
	u32 i;
	int ret = 0;

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

	if (qmi_bypass)
		return 0;

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

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

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

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

	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 = cnss_wlfw_wlan_cfg_send_sync(plat_priv, &req);
	if (ret)
		goto out;

skip_cfg:
	ret = cnss_wlfw_wlan_mode_send_sync(plat_priv, mode);
out:
	return ret;
}
EXPORT_SYMBOL(cnss_wlan_enable);

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

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

	if (qmi_bypass)
		return 0;

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

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

	if (!plat_priv)
		return -ENODEV;

	set_bit(CNSS_FW_MEM_READY, &plat_priv->driver_state);

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

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

	return 0;
out:
	return ret;
}

static int cnss_fw_ready_hdlr(struct cnss_plat_data *plat_priv)
{
	struct cnss_pci_data *pci_priv;
	int ret = 0;

	if (!plat_priv)
		return -ENODEV;

	set_bit(CNSS_FW_READY, &plat_priv->driver_state);

	pci_priv = plat_priv->bus_priv;
	if (!pci_priv)
		return -ENODEV;

	if (plat_priv->driver_status == CNSS_LOAD_UNLOAD)
		complete(&plat_priv->fw_ready_event);
	else
		ret = cnss_wlfw_wlan_mode_send_sync(plat_priv,
						    QMI_WLFW_CALIBRATION_V01);

	return ret;
}

static int cnss_cold_boot_cal_done_hdlr(struct cnss_plat_data *plat_priv)
{
	struct cnss_pci_data *pci_priv;

	if (!plat_priv)
		return -ENODEV;

	set_bit(CNSS_COLD_BOOT_CAL_DONE, &plat_priv->driver_state);

	pci_priv = plat_priv->bus_priv;
	if (!pci_priv)
		return -ENODEV;

	cnss_wlfw_wlan_mode_send_sync(plat_priv, QMI_WLFW_OFF_V01);
	cnss_pci_stop_mhi(pci_priv);
	cnss_suspend_pci_link(pci_priv);
	cnss_power_off_device(plat_priv);

	return 0;
}

static char *cnss_driver_event_to_str(enum cnss_driver_event_type type)
{
	switch (type) {
	case CNSS_DRIVER_EVENT_SERVER_ARRIVE:
		return "SERVER_ARRIVE";
	case CNSS_DRIVER_EVENT_SERVER_EXIT:
		return "SERVER_EXIT";
	case CNSS_DRIVER_EVENT_REQUEST_MEM:
		return "REQUEST_MEM";
	case CNSS_DRIVER_EVENT_FW_MEM_READY:
		return "FW_MEM_READY";
	case CNSS_DRIVER_EVENT_FW_READY:
		return "FW_READY";
	case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE:
		return "COLD_BOOT_CAL_DONE";
	case CNSS_DRIVER_EVENT_MAX:
		return "EVENT_MAX";
	}

	return "UNKNOWN";
};

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

	if (!plat_priv)
		return;

	spin_lock_irqsave(&plat_priv->event_lock, flags);

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

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

		switch (event->type) {
		case CNSS_DRIVER_EVENT_SERVER_ARRIVE:
			ret = cnss_wlfw_server_arrive(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_SERVER_EXIT:
			ret = cnss_wlfw_server_exit(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_REQUEST_MEM:
			ret = cnss_wlfw_respond_mem_send_sync(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_FW_MEM_READY:
			ret = cnss_fw_mem_ready_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_FW_READY:
			ret = cnss_fw_ready_hdlr(plat_priv);
			break;
		case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE:
			ret = cnss_cold_boot_cal_done_hdlr(plat_priv);
			break;
		default:
			cnss_pr_err("Invalid driver event type: %d",
				    event->type);
			kfree(event);
			continue;
		}

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

		kfree(event);

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

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

	if (!plat_priv)
		return -ENODEV;

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

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

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

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

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

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

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

	if (!sync)
		return ret;

	ret = wait_for_completion_interruptible(&event->complete);

	cnss_pr_dbg("Completed event: %s(%d), state: 0x%lx, ret: %d/%d\n",
		    cnss_driver_event_to_str(type), type,
		    plat_priv->driver_state, ret, event->ret);

	spin_lock_irqsave(&plat_priv->event_lock, flags);
	if (ret == -ERESTARTSYS && event->ret == CNSS_EVENT_PENDING) {
		event->sync = false;
		spin_unlock_irqrestore(&plat_priv->event_lock, flags);
		return ret;
	}
	spin_unlock_irqrestore(&plat_priv->event_lock, flags);

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

	return ret;
}

int cnss_power_up(struct device *dev)
{
	int ret = 0;
@@ -664,6 +995,26 @@ static int cnss_qca6290_powerup(struct cnss_plat_data *plat_priv)
	}

bypass_fbc:
	if (qmi_bypass)
		goto skip_fw_ready;

	reinit_completion(&plat_priv->fw_ready_event);
	ret = wait_for_completion_timeout(&plat_priv->fw_ready_event,
					  msecs_to_jiffies(FW_READY_TIMEOUT));

	if (ret == 0) {
		cnss_pr_err("Timeout for FW ready event!\n");
		ret = -EBUSY;
		goto stop_mhi;
	}

skip_fw_ready:
	if (enable_waltest) {
		cnss_pr_dbg("Firmware waltest is enabled.\n");
		ret = 0;
		goto out;
	}

	if (plat_priv->driver_status == CNSS_LOAD_UNLOAD) {
		ret = plat_priv->driver_ops->probe(pci_priv->pci_dev,
						   pci_priv->pci_device_id);
@@ -687,6 +1038,8 @@ bypass_fbc:
		goto stop_mhi;
	}

	set_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state);

	return 0;

stop_mhi:
@@ -711,6 +1064,9 @@ static int cnss_qca6290_shutdown(struct cnss_plat_data *plat_priv)
	if (!plat_priv->driver_ops)
		return -EINVAL;

	if (enable_waltest)
		goto bypass_driver_remove;

	if (plat_priv->driver_status == CNSS_LOAD_UNLOAD) {
		cnss_request_bus_bandwidth(CNSS_BUS_WIDTH_NONE);
		plat_priv->driver_ops->remove(pci_priv->pci_dev);
@@ -721,6 +1077,9 @@ static int cnss_qca6290_shutdown(struct cnss_plat_data *plat_priv)
		plat_priv->driver_ops->shutdown(pci_priv->pci_dev);
	}

	clear_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state);

bypass_driver_remove:
	if (!fbc_bypass)
		cnss_pci_stop_mhi(pci_priv);

@@ -1015,7 +1374,7 @@ static int cnss_register_ramdump(struct cnss_plat_data *plat_priv)
			ramdump_info->ramdump_size = ramdump_size;
	}

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

	if (ramdump_info->ramdump_size == 0) {
@@ -1111,6 +1470,11 @@ static ssize_t cnss_fs_ready_store(struct device *dev,
	cnss_pr_dbg("File system is ready, fs_ready is %d, count is %zu\n",
		    fs_ready, count);

	if (qmi_bypass) {
		cnss_pr_dbg("QMI is bypassed.\n");
		return count;
	}

	if (!plat_priv) {
		cnss_pr_err("plat_priv is NULL!\n");
		return count;
@@ -1153,6 +1517,29 @@ static void cnss_remove_sysfs(struct cnss_plat_data *plat_priv)
	device_remove_file(&plat_priv->plat_dev->dev, &dev_attr_fs_ready);
}

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

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

	return 0;
}

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

static const struct platform_device_id cnss_platform_id_table[] = {
	{ .name = "qca6174", .driver_data = QCA6174_DEVICE_ID, },
	{ .name = "qca6290", .driver_data = QCA6290_DEVICE_ID, },
@@ -1235,12 +1622,24 @@ static int cnss_probe(struct platform_device *plat_dev)
	if (ret)
		goto unreg_bus_scale;

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

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

	register_pm_notifier(&cnss_pm_notifier);

	cnss_pr_info("Platform driver probed successfully.\n");

	return 0;

deinit_event_work:
	cnss_event_work_deinit(plat_priv);
remove_sysfs:
	cnss_remove_sysfs(plat_priv);
unreg_bus_scale:
	cnss_unregister_bus_scale(plat_priv);
unreg_ramdump:
@@ -1267,13 +1666,15 @@ static int cnss_remove(struct platform_device *plat_dev)
	struct cnss_plat_data *plat_priv = platform_get_drvdata(plat_dev);

	unregister_pm_notifier(&cnss_pm_notifier);
	cnss_qmi_deinit(plat_priv);
	cnss_event_work_deinit(plat_priv);
	cnss_remove_sysfs(plat_priv);
	cnss_unregister_bus_scale(plat_priv);
	cnss_unregister_ramdump(plat_priv);
	cnss_unregister_subsys(plat_priv);
	cnss_unregister_esoc(plat_priv);
	cnss_put_resources(plat_priv);
	cnss_pci_deinit(plat_priv);
	cnss_put_resources(plat_priv);
	platform_set_drvdata(plat_dev, NULL);
	plat_env = NULL;

+44 −0
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@
#include <soc/qcom/memory_dump.h>
#include <soc/qcom/subsystem_restart.h>

#include "qmi.h"

#define MAX_NO_OF_MAC_ADDR		4

enum cnss_dev_bus_type {
@@ -81,6 +83,31 @@ struct cnss_wlan_mac_info {
	bool is_wlan_mac_set;
};

struct cnss_fw_mem {
	size_t size;
	void *va;
	phys_addr_t pa;
	bool valid;
};

enum cnss_driver_event_type {
	CNSS_DRIVER_EVENT_SERVER_ARRIVE,
	CNSS_DRIVER_EVENT_SERVER_EXIT,
	CNSS_DRIVER_EVENT_REQUEST_MEM,
	CNSS_DRIVER_EVENT_FW_MEM_READY,
	CNSS_DRIVER_EVENT_FW_READY,
	CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE,
	CNSS_DRIVER_EVENT_MAX,
};

enum cnss_driver_state {
	CNSS_QMI_WLFW_CONNECTED,
	CNSS_FW_MEM_READY,
	CNSS_FW_READY,
	CNSS_COLD_BOOT_CAL_DONE,
	CNSS_DRIVER_PROBED,
};

struct cnss_plat_data {
	struct platform_device *plat_dev;
	void *bus_priv;
@@ -98,10 +125,27 @@ struct cnss_plat_data {
	enum cnss_driver_status driver_status;
	uint32_t recovery_count;
	struct cnss_wlan_mac_info wlan_mac_info;
	unsigned long driver_state;
	struct completion fw_ready_event;
	struct list_head event_list;
	spinlock_t event_lock; /* spinlock for driver work event handling */
	struct work_struct event_work;
	struct workqueue_struct *event_wq;
	struct qmi_handle *qmi_wlfw_clnt;
	struct work_struct qmi_recv_msg_work;
	struct notifier_block qmi_wlfw_clnt_nb;
	struct wlfw_rf_chip_info_s_v01 chip_info;
	struct wlfw_rf_board_info_s_v01 board_info;
	struct wlfw_soc_info_s_v01 soc_info;
	struct wlfw_fw_version_info_s_v01 fw_version_info;
	struct cnss_fw_mem fw_mem;
};

void *cnss_bus_dev_to_bus_priv(struct device *dev);
struct cnss_plat_data *cnss_bus_dev_to_plat_priv(struct device *dev);
int cnss_driver_event_post(struct cnss_plat_data *plat_priv,
			   enum cnss_driver_event_type type,
			   bool sync, void *data);
int cnss_get_vreg(struct cnss_plat_data *plat_priv);
int cnss_get_pinctrl(struct cnss_plat_data *plat_priv);
int cnss_power_on_device(struct cnss_plat_data *plat_priv);
+732 −0

File added.

Preview size limit exceeded, changes collapsed.

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

#ifndef _CNSS_QMI_H
#define _CNSS_QMI_H

#include "wlan_firmware_service_v01.h"

struct cnss_plat_data;

int cnss_qmi_init(struct cnss_plat_data *plat_priv);
void cnss_qmi_deinit(struct cnss_plat_data *plat_priv);
int cnss_wlfw_server_arrive(struct cnss_plat_data *plat_priv);
int cnss_wlfw_server_exit(struct cnss_plat_data *plat_priv);
int cnss_wlfw_respond_mem_send_sync(struct cnss_plat_data *plat_priv);
int cnss_wlfw_tgt_cap_send_sync(struct cnss_plat_data *plat_priv);
int cnss_wlfw_bdf_dnld_send_sync(struct cnss_plat_data *plat_priv);
int cnss_wlfw_wlan_mode_send_sync(struct cnss_plat_data *plat_priv,
				  enum wlfw_driver_mode_enum_v01 mode);
int cnss_wlfw_wlan_cfg_send_sync(struct cnss_plat_data *plat_priv,
				 struct wlfw_wlan_cfg_req_msg_v01 *data);

#endif /* _CNSS_QMI_H */
Loading