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

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

Merge "cnss: Add support for QCA6180"

parents 34e0b666 20dcc3ec
Loading
Loading
Loading
Loading
+351 −4
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@
#include <linux/pm_qos.h>
#include <linux/esoc_client.h>
#include <linux/pinctrl/consumer.h>
#include <linux/firmware.h>
#include <linux/dma-mapping.h>
#include <linux/msm-bus.h>
#include <linux/msm-bus-board.h>
#include <linux/spinlock.h>
@@ -34,8 +36,6 @@
#include <linux/rwsem.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <linux/firmware.h>
#include <linux/dma-mapping.h>
#include <linux/log2.h>
#ifdef CONFIG_PCI_MSM
#include <linux/msm_pcie.h>
@@ -89,6 +89,10 @@ static struct cnss_fw_files FW_FILES_DEFAULT = {
"qwlan.bin", "bdwlan.bin", "otp.bin", "utf.bin",
"utfbd.bin", "epping.bin", "evicted.bin"};

#define QCA6180_VENDOR_ID	(0x168C)
#define QCA6180_DEVICE_ID      (0x0041)
#define QCA6180_REV_ID_OFFSET	(0x08)

#define WLAN_VREG_NAME		"vdd-wlan"
#define WLAN_VREG_IO_NAME	"vdd-wlan-io"
#define WLAN_VREG_XTAL_NAME	"vdd-wlan-xtal"
@@ -116,6 +120,23 @@ static struct cnss_fw_files FW_FILES_DEFAULT = {

static DEFINE_SPINLOCK(pci_link_down_lock);

#define FW_NAME_FIXED_LEN	(6)
#define MAX_NUM_OF_SEGMENTS	(16)
#define MAX_INDEX_FILE_SIZE	(512)
#define FW_FILENAME_LENGTH	(13)
#define TYPE_LENGTH		(4)
#define PER_FILE_DATA		(21)
#define MAX_IMAGE_SIZE		(2*1024*1024)
#define FW_IMAGE_FTM		(0x01)
#define FW_IMAGE_MISSION	(0x02)
#define FW_IMAGE_PRINT		(0x03)

#define SEG_METADATA		(0x01)
#define SEG_NON_PAGED		(0x02)
#define SEG_LOCKED_PAGE		(0x03)
#define SEG_UNLOCKED_PAGE	(0x04)
#define SEG_NON_SECURE_DATA	(0x05)

struct cnss_wlan_gpio_info {
	char *name;
	u32 num;
@@ -134,6 +155,41 @@ struct cnss_wlan_vreg_info {
	bool state;
};

struct segment_memory {
	dma_addr_t dma_region;
	void *cpu_region;
	u32 size;
};

/* FW image descriptor lists */
struct image_desc_hdr {
	u8 image_id;
	u8 reserved[3];
	u32 segments_cnt;
};

struct segment_desc {
	u8 segment_id;
	u8 segment_idx;
	u8 flags[2];
	u32 addr_count;
	u32 addr_low;
	u32 addr_high;
};

struct region_desc {
	u32 addr_low;
	u32 addr_high;
	u32 size;
	u32 reserved;
};

struct index_file {
	u32 type;
	u32 segment_idx;
	u8 file_name[13];
};

/* device_info is expected to be fully populated after cnss_config is invoked.
 * The function pointer callbacks are expected to be non null as well.
 */
@@ -178,6 +234,13 @@ static struct cnss_data {
#ifdef CONFIG_CNSS_SECURE_FW
	void *fw_mem;
#endif
	u32 device_id;
	void *fw_image_cpu;
	int fw_image_setup;
	dma_addr_t dma_fw_image;
	u32 dma_fw_size;
	u32 seg_count;
	struct segment_memory seg_mem[MAX_NUM_OF_SEGMENTS];
} *penv;

static int cnss_wlan_vreg_on(struct cnss_wlan_vreg_info *vreg_info)
@@ -657,6 +720,230 @@ static void cnss_wlan_fw_mem_alloc(struct pci_dev *pdev)
}
#endif

static int get_image_file(const u8 *index_info, u8 *file_name,
	u32 *type, u32 *segment_idx)
{

	if (!file_name || !index_info || !type)
		return -EINVAL;

	memcpy(type, index_info, TYPE_LENGTH);
	memcpy(segment_idx, index_info + TYPE_LENGTH, TYPE_LENGTH);
	memcpy(file_name, index_info + TYPE_LENGTH + TYPE_LENGTH,
		FW_FILENAME_LENGTH);

	pr_debug("%u: %u: %s", *type, *segment_idx, file_name);

	return PER_FILE_DATA;
}

static void print_allocated_image_table(void)
{
	u32 seg = 0, count = 0;
	u8 *dump_addr;
	struct segment_memory *pseg_mem = penv->seg_mem;

	while (seg++ < penv->seg_count) {
		dump_addr = (u8 *)pseg_mem->cpu_region +
			sizeof(struct region_desc);
		for (count = 0; count < pseg_mem->size -
				sizeof(struct region_desc); count++)
			pr_debug("%02x", dump_addr[count]);

		pseg_mem++;
	}
}

static void free_allocated_image_table(void)
{
	struct device *dev = &penv->pdev->dev;
	struct segment_memory *pseg_mem = penv->seg_mem;
	u32 seg = 0;

	while (seg++ < penv->seg_count) {
		dma_free_coherent(dev, pseg_mem->size,
		  pseg_mem->cpu_region, pseg_mem->dma_region);
		pseg_mem++;
	}
	dma_free_coherent(dev,
		sizeof(struct segment_desc) * MAX_NUM_OF_SEGMENTS,
		    penv->fw_image_cpu, penv->dma_fw_image);
	penv->seg_count = 0;
	penv->dma_fw_image = 0;
	penv->dma_fw_size = 0;
}

static int cnss_setup_fw_image_table(int mode)
{
	struct image_desc_hdr *image_hdr;
	struct segment_desc *pseg = NULL;
	const struct firmware *fw_index, *fw_image;
	struct device *dev = NULL;
	char reserved[3] = "";
	u8 image_file[FW_FILENAME_LENGTH] = "";
	u8 index_file[FW_FILENAME_LENGTH] = "";
	u8 index_info[MAX_INDEX_FILE_SIZE] = "";
	size_t image_desc_size = 0, file_size = 0;
	size_t index_pos = 0, image_pos = 0;
	struct region_desc *reg_desc = NULL;
	u32 type = 0;
	u32 segment_idx = 0;
	uintptr_t address;
	int ret = 0;
	dma_addr_t dma_addr;

	if (!penv || !penv->pdev) {
		pr_err("cnss: invalid penv or pdev or dev\n");
		ret = -EINVAL;
		goto err;
	}
	dev = &penv->pdev->dev;

	image_desc_size = sizeof(struct image_desc_hdr) +
		sizeof(struct segment_desc) * MAX_NUM_OF_SEGMENTS;

	penv->fw_image_cpu = dma_alloc_coherent(dev, image_desc_size,
			&penv->dma_fw_image, GFP_KERNEL);

	if (!penv->fw_image_cpu) {
		pr_err("cnss: image desc allocation failure\n");
		ret = -ENOMEM;
		goto err;
	}

	memset(penv->fw_image_cpu, 0, image_desc_size);

	image_hdr = (struct image_desc_hdr *)penv->fw_image_cpu;
	image_hdr->image_id = mode;
	memcpy(image_hdr->reserved, reserved, 3);

	/*  meta data file has image details */
	ret = scnprintf(index_file, FW_FILENAME_LENGTH, "qwlan.bin");
	if (ret < 0)
		goto err_free;

	pr_err("cnss: request meta data file %s\n", index_file);
	ret = request_firmware(&fw_index, index_file, dev);
	if (ret || !fw_index || !fw_index->data || !fw_index->size) {
		pr_err("cnss: meta data file open failure %s\n", index_file);
		goto err_free;
	}

	if (fw_index->size > MAX_INDEX_FILE_SIZE) {
		pr_err("cnss: meta data file has invalid size %s: %zu\n",
				index_file, fw_index->size);
		release_firmware(fw_index);
		goto err_free;
	}

	memcpy(index_info, fw_index->data, fw_index->size);
	file_size = fw_index->size;
	release_firmware(fw_index);

	while (file_size >= PER_FILE_DATA  && image_pos < image_desc_size &&
			image_hdr->segments_cnt < MAX_NUM_OF_SEGMENTS) {

		ret = get_image_file(index_info + index_pos,
			image_file, &type, &segment_idx);
		if (ret == -EINVAL)
			goto err_free;

		file_size -= ret;
		index_pos += ret;
		pseg = penv->fw_image_cpu + image_pos +
				sizeof(struct image_desc_hdr);

		switch (type) {
		case SEG_METADATA:
		case SEG_NON_PAGED:
		case SEG_LOCKED_PAGE:
		case SEG_UNLOCKED_PAGE:
		case SEG_NON_SECURE_DATA:

			image_hdr->segments_cnt++;
			pseg->segment_id = type;
			pseg->segment_idx = (u8)(segment_idx & 0xff);
			memcpy(pseg->flags, reserved, 2);

			ret = request_firmware(&fw_image, image_file, dev);
			if (ret || !fw_image || !fw_image->data ||
				!fw_image->size) {
				pr_err("cnss: image file read failed %s",
						image_file);
				goto err_free;
			}
			if (fw_image->size > MAX_IMAGE_SIZE) {
				pr_err("cnss: %s: image file invalid size %zu\n",
					image_file, fw_image->size);
				release_firmware(fw_image);
				ret = -EINVAL;
				goto err_free;
			}
			reg_desc = dma_alloc_coherent(dev,
				sizeof(struct region_desc) + fw_image->size,
				    &dma_addr, GFP_KERNEL);
			if (!reg_desc) {
				pr_err("cnss: region allocation failure\n");
				ret = -ENOMEM;
				release_firmware(fw_image);
				goto err_free;
			}
			address = (uintptr_t) dma_addr;
			pseg->addr_low = address & 0xFFFFFFFF;
			pseg->addr_high = 0x00;
			/* one region for one image file */
			pseg->addr_count = 1;
			memcpy((u8 *)reg_desc + sizeof(struct region_desc),
					fw_image->data, fw_image->size);
			address += sizeof(struct region_desc);
			reg_desc->addr_low = address & 0xFFFFFFFF;
			reg_desc->addr_high = 0x00;
			reg_desc->reserved = 0;
			reg_desc->size = fw_image->size;

			penv->seg_mem[penv->seg_count].dma_region = dma_addr;
			penv->seg_mem[penv->seg_count].cpu_region = reg_desc;
			penv->seg_mem[penv->seg_count].size =
				sizeof(struct region_desc) + fw_image->size;

			release_firmware(fw_image);
			penv->seg_count++;
			break;

		default:
			pr_err("cnss: Unknown segment %d", type);
			ret = -EINVAL;
			goto err_free;
	    }
	    image_pos += sizeof(struct segment_desc);
	}
	penv->dma_fw_size = sizeof(struct image_desc_hdr) +
	  sizeof(struct segment_desc) * image_hdr->segments_cnt;

	pr_info("Image setup table built on host");

	return file_size;


err_free:
	free_allocated_image_table();
err:
	pr_err("cnss: image file setup failed %d\n", ret);
	return ret;
}

int cnss_get_fw_image(dma_addr_t *fw_image, u32 *image_size)
{
	if (!fw_image || !penv || !penv->seg_count)
		return -EINVAL;

	*fw_image = penv->dma_fw_image;
	*image_size = penv->dma_fw_size;

	return 0;
}
EXPORT_SYMBOL(cnss_get_fw_image);

static int cnss_wlan_pci_probe(struct pci_dev *pdev,
			       const struct pci_device_id *id)
{
@@ -671,6 +958,7 @@ static int cnss_wlan_pci_probe(struct pci_dev *pdev,
	penv->pdev = pdev;
	penv->id = id;
	penv->fw_available = false;
	penv->device_id = pdev->device;

	if (penv->pci_register_again) {
		pr_debug("%s: PCI re-registration complete\n", __func__);
@@ -678,8 +966,24 @@ static int cnss_wlan_pci_probe(struct pci_dev *pdev,
		return 0;
	}

	pci_read_config_word(pdev, QCA6174_REV_ID_OFFSET, &penv->revision_id);
	switch (pdev->device) {
	case QCA6180_DEVICE_ID:
		pci_read_config_word(pdev, QCA6180_REV_ID_OFFSET,
				&penv->revision_id);
		break;

	case QCA6174_DEVICE_ID:
		pci_read_config_word(pdev, QCA6174_REV_ID_OFFSET,
				&penv->revision_id);
		cnss_setup_fw_files(penv->revision_id);
		break;

	default:
		pr_err("cnss: unknown device found %d\n", pdev->device);
		ret = -EPROBE_DEFER;
		goto err_unknown;
	}


	if (penv->pcie_link_state) {
		pci_save_state(pdev);
@@ -740,6 +1044,7 @@ static int cnss_wlan_pci_probe(struct pci_dev *pdev,

end_dma_alloc:
	dma_free_coherent(dev, EVICT_BIN_MAX_SIZE, cpu_addr, dma_handle);
err_unknown:
err_pcie_suspend:
	return ret;
}
@@ -824,6 +1129,7 @@ static struct notifier_block cnss_pm_notifier = {
static DEFINE_PCI_DEVICE_TABLE(cnss_wlan_pci_id_table) = {
	{ QCA6174_VENDOR_ID, QCA6174_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID },
	{ QCA6174_VENDOR_ID, BEELINER_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID },
	{ QCA6180_VENDOR_ID, QCA6180_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID },
	{ 0 }
};
MODULE_DEVICE_TABLE(pci, cnss_wlan_pci_id_table);
@@ -837,6 +1143,44 @@ struct pci_driver cnss_wlan_pci_driver = {
	.resume   = cnss_wlan_pci_resume,
};

static ssize_t fw_image_setup_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	if (!penv)
		return -ENODEV;

	return scnprintf(buf, PAGE_SIZE, "%u\n", penv->fw_image_setup);
}

static ssize_t fw_image_setup_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int val;
	int ret;

	if (!penv)
		return -ENODEV;

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

	if (val == FW_IMAGE_FTM || val == FW_IMAGE_MISSION) {
		pr_err("fw image setup triggered %d\n", val);
		ret = cnss_setup_fw_image_table(val);
		if (ret != 0) {
			pr_err("Invalid parsing of FW image files %d", ret);
			return -EINVAL;
		}
		penv->fw_image_setup = val;
	} else if (val == FW_IMAGE_PRINT)
		print_allocated_image_table();

	return count;
}

static DEVICE_ATTR(fw_image_setup, S_IRUSR | S_IWUSR,
	fw_image_setup_show, fw_image_setup_store);

void recovery_work_handler(struct work_struct *recovery)
{
	cnss_device_self_recovery();
@@ -1810,6 +2154,8 @@ static int cnss_probe(struct platform_device *pdev)
	memset(phys_to_virt(0), 0, SZ_4K);
#endif

	ret = device_create_file(dev, &dev_attr_fw_image_setup);

	pr_info("cnss: Platform driver probed successfully.\n");
	return ret;

@@ -1852,6 +2198,7 @@ static int cnss_remove(struct platform_device *pdev)
	struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info;

	unregister_pm_notifier(&cnss_pm_notifier);
	device_remove_file(&pdev->dev, &dev_attr_fw_image_setup);

	cnss_pm_wake_lock_destroy(&penv->ws);

+1 −0
Original line number Diff line number Diff line
@@ -91,6 +91,7 @@ enum cnss_driver_status {
	CNSS_LOAD_UNLOAD
};

extern int cnss_get_fw_image(dma_addr_t *fw_image, u32 *image_size);
extern void cnss_device_crashed(void);
extern void cnss_device_self_recovery(void);
extern int cnss_get_ramdump_mem(unsigned long *address, unsigned long *size);