wcnss: Add support for nv bin download at coldboot
NV bin is downloaded when wlan driver is loaded. This change
adds support for NV bin download during coldboot of WCNSS and
thus much before wlan driver is loaded.
Change-Id: I7d8d116cf830f5c38ab39534800fa461418a17e6
Acked-by: Rajesh Chauhan <rajeshc@qca.qualcomm.com>
Signed-off-by: Sameer Thalappil <sameert@codeaurora.org>
diff --git a/drivers/net/wireless/wcnss/wcnss_vreg.c b/drivers/net/wireless/wcnss/wcnss_vreg.c
index 75c75a8..025410a 100644
--- a/drivers/net/wireless/wcnss/wcnss_vreg.c
+++ b/drivers/net/wireless/wcnss/wcnss_vreg.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2011-2013, 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
@@ -40,6 +40,10 @@
#define RIVA_PMU_OFFSET 0x28
#define PRONTO_PMU_OFFSET 0x1004
+#define RIVA_SPARE_OFFSET 0x0b4
+#define PRONTO_SPARE_OFFSET 0x1088
+#define NVBIN_DLND_BIT BIT(25)
+
#define WCNSS_PMU_CFG_IRIS_XO_CFG BIT(3)
#define WCNSS_PMU_CFG_IRIS_XO_EN BIT(4)
#define WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP BIT(5)
@@ -115,13 +119,16 @@
int rc = 0;
int size = 0;
int pmu_offset = 0;
+ int spare_offset = 0;
unsigned long wcnss_phys_addr;
void __iomem *pmu_conf_reg;
+ void __iomem *spare_reg;
struct clk *clk;
if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
wcnss_phys_addr = MSM_PRONTO_PHYS;
pmu_offset = PRONTO_PMU_OFFSET;
+ spare_offset = PRONTO_SPARE_OFFSET;
size = 0x3000;
clk = clk_get(dev, "xo");
@@ -132,6 +139,7 @@
} else {
wcnss_phys_addr = MSM_RIVA_PHYS;
pmu_offset = RIVA_PMU_OFFSET;
+ spare_offset = RIVA_SPARE_OFFSET;
size = SZ_256;
clk = clk_get(dev, "cxo");
@@ -147,6 +155,13 @@
pr_err("ioremap wcnss physical failed\n");
goto fail;
}
+
+ pr_debug("wcnss: Indicate NV bin download\n");
+ spare_reg = msm_wcnss_base + spare_offset;
+ reg = readl_relaxed(spare_reg);
+ reg |= NVBIN_DLND_BIT;
+ writel_relaxed(reg, spare_reg);
+
pmu_conf_reg = msm_wcnss_base + pmu_offset;
/* Enable IRIS XO */
diff --git a/drivers/net/wireless/wcnss/wcnss_wlan.c b/drivers/net/wireless/wcnss/wcnss_wlan.c
index 55b4192..439b1f8 100644
--- a/drivers/net/wireless/wcnss/wcnss_wlan.c
+++ b/drivers/net/wireless/wcnss/wcnss_wlan.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2011-2013, 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
@@ -11,6 +11,7 @@
*/
#include <linux/module.h>
+#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/platform_device.h>
@@ -75,13 +76,16 @@
#define WCNSS_CTRL_MSG_START 0x01000000
#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0)
#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1)
+#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2)
+#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3)
+
#define VALID_VERSION(version) \
((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
struct smd_msg_hdr {
- unsigned int type;
- unsigned int len;
+ unsigned int msg_type;
+ unsigned int msg_len;
};
struct wcnss_version {
@@ -92,6 +96,57 @@
unsigned char revision;
};
+#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
+
+/*
+ * On SMD channel 4K of maximum data can be transferred, including message
+ * header, so NV fragment size as next multiple of 1Kb is 3Kb.
+ */
+#define NV_FRAGMENT_SIZE 3072
+
+/* Macro to find the total number fragments of the NV bin Image */
+#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
+ (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1))
+
+struct nvbin_dnld_req_params {
+ /*
+ * Fragment sequence number of the NV bin Image. NV Bin Image
+ * might not fit into one message due to size limitation of
+ * the SMD channel FIFO so entire NV blob is chopped into
+ * multiple fragments starting with seqeunce number 0. The
+ * last fragment is indicated by marking is_last_fragment field
+ * to 1. At receiving side, NV blobs would be concatenated
+ * together without any padding bytes in between.
+ */
+ unsigned short frag_number;
+
+ /*
+ * When set to 1 it indicates that no more fragments will
+ * be sent. Receiver shall send back response message after
+ * the last fragment.
+ */
+ unsigned short is_last_fragment;
+
+ /* NV Image size (number of bytes) */
+ unsigned int nvbin_buffer_size;
+
+ /*
+ * Following the 'nvbin_buffer_size', there should be
+ * nvbin_buffer_size bytes of NV bin Image i.e.
+ * uint8[nvbin_buffer_size].
+ */
+};
+
+struct nvbin_dnld_req_msg {
+ /*
+ * Note: The length specified in nvbin_dnld_req_msg messages
+ * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
+ * nvbin_buffer_size.
+ */
+ struct smd_msg_hdr hdr;
+ struct nvbin_dnld_req_params dnld_req_params;
+};
+
static struct {
struct platform_device *pdev;
void *pil;
@@ -111,6 +166,7 @@
struct wcnss_wlan_config wlan_config;
struct delayed_work wcnss_work;
struct work_struct wcnssctrl_version_work;
+ struct work_struct wcnssctrl_nvbin_dnld_work;
struct work_struct wcnssctrl_rx_work;
struct wake_lock wcnss_wake_lock;
void __iomem *msm_wcnss_base;
@@ -404,7 +460,7 @@
void wcnss_flush_delayed_boot_votes()
{
- flush_delayed_work_sync(&penv->wcnss_work);
+ flush_delayed_work(&penv->wcnss_work);
}
EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
@@ -678,7 +734,7 @@
ret = smd_write_avail(penv->smd_ch);
if (ret < len) {
pr_err("wcnss: no space available for smd frame\n");
- ret = -ENOSPC;
+ return -ENOSPC;
}
ret = smd_write(penv->smd_ch, data, len);
if (ret < len) {
@@ -695,6 +751,7 @@
unsigned char buf[WCNSS_MAX_FRAME_SIZE];
struct smd_msg_hdr *phdr;
struct wcnss_version *pversion;
+ int hw_type;
len = smd_read_avail(penv->smd_ch);
if (len > WCNSS_MAX_FRAME_SIZE) {
@@ -713,7 +770,7 @@
phdr = (struct smd_msg_hdr *)buf;
- switch (phdr->type) {
+ switch (phdr->msg_type) {
case WCNSS_VERSION_RSP:
pversion = (struct wcnss_version *)buf;
@@ -726,10 +783,38 @@
"%02x%02x%02x%02x", pversion->major, pversion->minor,
pversion->version, pversion->revision);
pr_info("wcnss: version %s\n", penv->wcnss_version);
+ /* schedule work to download nvbin to ccpu */
+ hw_type = wcnss_hardware_type();
+ switch (hw_type) {
+ case WCNSS_RIVA_HW:
+ /* supported only if riva major >= 1 and minor >= 4 */
+ if ((pversion->major >= 1) && (pversion->minor >= 4)) {
+ pr_info("wcnss: schedule dnld work for riva\n");
+ schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
+ }
+ break;
+
+ case WCNSS_PRONTO_HW:
+ /* supported only if pronto major >= 1 and minor >= 4 */
+ if ((pversion->major >= 1) && (pversion->minor >= 4)) {
+ pr_info("wcnss: schedule dnld work for pronto\n");
+ schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
+ }
+ break;
+
+ default:
+ pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
+ hw_type);
+ break;
+ }
+ break;
+
+ case WCNSS_NVBIN_DNLD_RSP:
+ pr_info("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu\n");
break;
default:
- pr_err("wcnss: invalid message type %d\n", phdr->type);
+ pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
}
return;
}
@@ -739,15 +824,126 @@
struct smd_msg_hdr smd_msg;
int ret = 0;
- smd_msg.type = WCNSS_VERSION_REQ;
- smd_msg.len = sizeof(smd_msg);
- ret = wcnss_smd_tx(&smd_msg, smd_msg.len);
+ smd_msg.msg_type = WCNSS_VERSION_REQ;
+ smd_msg.msg_len = sizeof(smd_msg);
+ ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
if (ret < 0)
pr_err("wcnss: smd tx failed\n");
return;
}
+static void wcnss_nvbin_dnld_req(struct work_struct *worker)
+{
+ int ret = 0;
+ struct nvbin_dnld_req_msg *dnld_req_msg;
+ unsigned short total_fragments = 0;
+ unsigned short count = 0;
+ unsigned short retry_count = 0;
+ unsigned short cur_frag_size = 0;
+ unsigned char *outbuffer = NULL;
+ const void *nv_blob_addr = NULL;
+ unsigned int nv_blob_size = 0;
+ const struct firmware *nv = NULL;
+ struct device *dev = NULL;
+
+ dev = wcnss_wlan_get_device();
+
+ ret = request_firmware(&nv, NVBIN_FILE, dev);
+
+ if (ret || !nv || !nv->data || !nv->size) {
+ pr_err("wcnss: wcnss_nvbin_dnld_req: request_firmware failed for %s\n",
+ NVBIN_FILE);
+ return;
+ }
+
+ /*
+ * First 4 bytes in nv blob is validity bitmap.
+ * We cannot validate nv, so skip those 4 bytes.
+ */
+ nv_blob_addr = nv->data + 4;
+ nv_blob_size = nv->size - 4;
+
+ total_fragments = TOTALFRAGMENTS(nv_blob_size);
+
+ pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
+ nv_blob_size, total_fragments);
+
+ /* get buffer for nv bin dnld req message */
+ outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
+ NV_FRAGMENT_SIZE), GFP_KERNEL);
+
+ if (NULL == outbuffer) {
+ pr_err("wcnss: wcnss_nvbin_dnld_req: failed to get buffer\n");
+ goto err_free_nv;
+ }
+
+ dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
+
+ dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
+
+ for (count = 0; count < total_fragments; count++) {
+ dnld_req_msg->dnld_req_params.frag_number = count;
+
+ if (count == (total_fragments - 1)) {
+ /* last fragment, take care of boundry condition */
+ cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
+ if (!cur_frag_size)
+ cur_frag_size = NV_FRAGMENT_SIZE;
+
+ dnld_req_msg->dnld_req_params.is_last_fragment = 1;
+ } else {
+ cur_frag_size = NV_FRAGMENT_SIZE;
+ dnld_req_msg->dnld_req_params.is_last_fragment = 0;
+ }
+
+ dnld_req_msg->dnld_req_params.nvbin_buffer_size =
+ cur_frag_size;
+
+ dnld_req_msg->hdr.msg_len =
+ sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
+
+ /* copy NV fragment */
+ memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
+ (nv_blob_addr + count * NV_FRAGMENT_SIZE),
+ cur_frag_size);
+
+ ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
+
+ retry_count = 0;
+ while ((ret == -ENOSPC) && (retry_count <= 3)) {
+ pr_debug("wcnss: wcnss_nvbin_dnld_req: smd tx failed, ENOSPC\n");
+ pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
+ count, dnld_req_msg->hdr.msg_len,
+ total_fragments, retry_count);
+
+ /* wait and try again */
+ msleep(20);
+ retry_count++;
+ ret = wcnss_smd_tx(outbuffer,
+ dnld_req_msg->hdr.msg_len);
+ }
+
+ if (ret < 0) {
+ pr_err("wcnss: wcnss_nvbin_dnld_req: smd tx failed\n");
+ pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
+ count, dnld_req_msg->hdr.msg_len,
+ total_fragments, retry_count);
+ goto err_dnld;
+ }
+ }
+
+err_dnld:
+ /* free buffer */
+ kfree(outbuffer);
+
+err_free_nv:
+ /* release firmware */
+ release_firmware(nv);
+
+ return;
+}
+
static int
wcnss_trigger_config(struct platform_device *pdev)
{
@@ -832,6 +1028,7 @@
}
INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
+ INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_req);
wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");