wcnss: Add support to download calibrated data

WCNSS Firmware needs raw NV data (settings & some register values),
to do the power-on calibration. Once firmware does the power-on
calibration; it can then re-use the calibrated data. And it need not
do the calibration again when the next time it boots. WCNSS sends this
calibrated data to Apps; and Apps saves this into a persistent area.
And in the subsequent bootup, Apps sends the calibrated data to WCNSS.

Add interfaces to read calibrated data; and also add inteface to write
calibrated data to platform driver.

Change-Id: Ic18d40150093024b10a53e28f8a423dd9d3846eb
CRs-Fixed: 458741
Signed-off-by: Sameer Thalappil <sameert@codeaurora.org>
diff --git a/drivers/net/wireless/wcnss/wcnss_riva.c b/drivers/net/wireless/wcnss/wcnss_riva.c
index d5ac58d..21fae6f 100644
--- a/drivers/net/wireless/wcnss/wcnss_riva.c
+++ b/drivers/net/wireless/wcnss/wcnss_riva.c
@@ -106,15 +106,12 @@
 			goto fail;
 		}
 		/* NV bit is set to indicate that platform driver is capable
-		 * of doing NV download. SSR should not set NV bit; during
-		 * SSR NV bin is downloaded by WLAN drive.
+		 * of doing NV download.
 		 */
-		if (!wcnss_cold_boot_done()) {
-			pr_debug("wcnss: Indicate NV bin download\n");
-			reg = readl_relaxed(RIVA_SPARE_OUT);
-			reg |= NVBIN_DLND_BIT;
-			writel_relaxed(reg, RIVA_SPARE_OUT);
-		}
+		pr_debug("wcnss: Indicate NV bin download\n");
+		reg = readl_relaxed(RIVA_SPARE_OUT);
+		reg |= NVBIN_DLND_BIT;
+		writel_relaxed(reg, RIVA_SPARE_OUT);
 
 		writel_relaxed(0, RIVA_PMU_CFG);
 		reg = readl_relaxed(RIVA_PMU_CFG);
diff --git a/drivers/net/wireless/wcnss/wcnss_wlan.c b/drivers/net/wireless/wcnss/wcnss_wlan.c
index 8b28a50..23a9f15 100644
--- a/drivers/net/wireless/wcnss/wcnss_wlan.c
+++ b/drivers/net/wireless/wcnss/wcnss_wlan.c
@@ -25,6 +25,14 @@
 #include <linux/gpio.h>
 #include <linux/wakelock.h>
 #include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/clk.h>
+#include <linux/ratelimit.h>
+#include <linux/kthread.h>
+#include <linux/wait.h>
+#include <linux/uaccess.h>
+
 #include <mach/peripheral-loader.h>
 #include <mach/msm_smd.h>
 #include <mach/msm_iomap.h>
@@ -44,6 +52,10 @@
 module_param(has_48mhz_xo, int, S_IWUSR | S_IRUGO);
 MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present");
 
+static int has_calibrated_data = WCNSS_CONFIG_UNSPECIFIED;
+module_param(has_calibrated_data, int, S_IWUSR | S_IRUGO);
+MODULE_PARM_DESC(has_calibrated_data, "whether calibrated data file available");
+
 static DEFINE_SPINLOCK(reg_spinlock);
 
 #define MSM_RIVA_PHYS			0x03204000
@@ -60,20 +72,27 @@
 #define CCU_LAST_ADDR2_OFFSET		0x10c
 
 #define WCNSS_CTRL_CHANNEL			"WCNSS_CTRL"
-#define WCNSS_MAX_FRAME_SIZE		500
+#define WCNSS_MAX_FRAME_SIZE		(4*1024)
 #define WCNSS_VERSION_LEN			30
 
 /* message types */
 #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	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	WCNSS_CALDATA_UPLD_REQ        (WCNSS_CTRL_MSG_START + 4)
+#define	WCNSS_CALDATA_UPLD_RSP        (WCNSS_CTRL_MSG_START + 5)
+#define	WCNSS_CALDATA_DNLD_REQ        (WCNSS_CTRL_MSG_START + 6)
+#define	WCNSS_CALDATA_DNLD_RSP        (WCNSS_CTRL_MSG_START + 7)
 
 
 #define VALID_VERSION(version) \
 	((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
 
+#define FW_CALDATA_CAPABLE() \
+	((penv->fw_major >= 1) && (penv->fw_minor >= 5) ? 1 : 0)
+
 struct smd_msg_hdr {
 	unsigned int msg_type;
 	unsigned int msg_len;
@@ -109,6 +128,13 @@
  * header, so NV fragment size as next multiple of 1Kb is 3Kb.
  */
 #define NV_FRAGMENT_SIZE  3072
+#define MAX_CALIBRATED_DATA_SIZE  (64*1024)
+#define LAST_FRAGMENT        (1 << 0)
+#define MESSAGE_TO_FOLLOW    (1 << 1)
+#define CAN_RECEIVE_CALDATA  (1 << 15)
+#define WCNSS_RESP_SUCCESS   1
+#define WCNSS_RESP_FAIL      0
+
 
 /* Macro to find the total number fragments of the NV bin Image */
 #define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
@@ -127,11 +153,14 @@
 	unsigned short frag_number;
 
 	/*
-	 * When set to 1 it indicates that no more fragments will
-	 * be sent. Receiver shall send back response message after
-	 * last fragment.
+	 * bit 0: When set to 1 it indicates that no more fragments will
+	 * be sent.
+	 * bit 1: When set, a new message will be followed by this message
+	 * bit 2- bit 14:  Reserved
+	 * bit 15: when set, it indicates that the sender is capable of
+	 * receiving Calibrated data.
 	 */
-	unsigned short is_last_fragment;
+	unsigned short msg_flags;
 
 	/* NV Image size (number of bytes) */
 	unsigned int nvbin_buffer_size;
@@ -143,6 +172,7 @@
 	 */
 };
 
+
 struct nvbin_dnld_req_msg {
 	/*
 	 * Note: The length specified in nvbin_dnld_req_msg messages
@@ -153,6 +183,39 @@
 	struct nvbin_dnld_req_params dnld_req_params;
 };
 
+struct cal_data_params {
+
+	/* The total size of the calibrated data, including all the
+	 * fragments.
+	 */
+	unsigned int total_size;
+	unsigned short frag_number;
+	/*
+	 * bit 0: When set to 1 it indicates that no more fragments will
+	 * be sent.
+	 * bit 1: When set, a new message will be followed by this message
+	 * bit 2- bit 15: Reserved
+	 */
+	unsigned short msg_flags;
+	/*
+	 * fragment size
+	 */
+	unsigned int frag_size;
+	/*
+	 * Following the frag_size, frag_size of fragmented
+	 * data will be followed.
+	 */
+};
+
+struct cal_data_msg {
+	/*
+	 * The length specified in cal_data_msg should be
+	 * hdr.msg_len = sizeof(cal_data_msg) + frag_size
+	 */
+	struct smd_msg_hdr hdr;
+	struct cal_data_params cal_params;
+};
+
 static struct {
 	struct platform_device *pdev;
 	void		*pil;
@@ -163,9 +226,10 @@
 	const struct dev_pm_ops *pm_ops;
 	int		triggered;
 	int		smd_channel_ready;
-	int		cold_boot_done;
 	smd_channel_t	*smd_ch;
 	unsigned char	wcnss_version[WCNSS_VERSION_LEN];
+	unsigned char   fw_major;
+	unsigned char   fw_minor;
 	unsigned int	serial_number;
 	int		thermal_mitigation;
 	void		(*tm_notify)(struct device *, int);
@@ -176,6 +240,20 @@
 	struct work_struct wcnssctrl_rx_work;
 	struct wake_lock wcnss_wake_lock;
 	void __iomem *msm_wcnss_base;
+	int	ssr_boot;
+	int	nv_downloaded;
+	unsigned char *fw_cal_data;
+	unsigned char *user_cal_data;
+	int	fw_cal_rcvd;
+	int	fw_cal_exp_frag;
+	int	fw_cal_available;
+	int	user_cal_read;
+	int	user_cal_available;
+	int	user_cal_rcvd;
+	int	user_cal_exp_size;
+	int	device_opened;
+	struct mutex dev_lock;
+	wait_queue_head_t read_wait;
 } *penv = NULL;
 
 static ssize_t wcnss_serial_number_show(struct device *dev,
@@ -358,21 +436,26 @@
 	switch (event) {
 	case SMD_EVENT_DATA:
 		len = smd_read_avail(penv->smd_ch);
-		if (len < 0)
+		if (len < 0) {
 			pr_err("wcnss: failed to read from smd %d\n", len);
+			return;
+		}
 		schedule_work(&penv->wcnssctrl_rx_work);
 		break;
 
 	case SMD_EVENT_OPEN:
 		pr_debug("wcnss: opening WCNSS SMD channel :%s",
 				WCNSS_CTRL_CHANNEL);
-		if (!VALID_VERSION(penv->wcnss_version))
-			schedule_work(&penv->wcnssctrl_version_work);
+		schedule_work(&penv->wcnssctrl_version_work);
+
 		break;
 
 	case SMD_EVENT_CLOSE:
 		pr_debug("wcnss: closing WCNSS SMD channel :%s",
 				WCNSS_CTRL_CHANNEL);
+		/* This SMD is closed only during SSR */
+		penv->ssr_boot = true;
+		penv->nv_downloaded = 0;
 		break;
 
 	default:
@@ -521,6 +604,15 @@
 }
 EXPORT_SYMBOL(wcnss_get_wlan_config);
 
+int wcnss_device_ready(void)
+{
+	if (penv && penv->pdev && penv->nv_downloaded)
+		return 1;
+	return 0;
+}
+EXPORT_SYMBOL(wcnss_device_ready);
+
+
 struct resource *wcnss_wlan_get_memory_map(struct device *dev)
 {
 	if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
@@ -704,15 +796,13 @@
 }
 EXPORT_SYMBOL(wcnss_allow_suspend);
 
-int wcnss_cold_boot_done(void)
+int fw_cal_data_available(void)
 {
 	if (penv)
-		return penv->cold_boot_done;
+		return penv->fw_cal_available;
 	else
 		return -ENODEV;
 }
-EXPORT_SYMBOL(wcnss_cold_boot_done);
-
 
 static int wcnss_smd_tx(void *data, int len)
 {
@@ -731,13 +821,144 @@
 	return ret;
 }
 
+static unsigned char wcnss_fw_status(void)
+{
+	int len = 0;
+	int rc = 0;
+
+	unsigned char fw_status = 0xFF;
+
+	len = smd_read_avail(penv->smd_ch);
+	if (len < 1) {
+		pr_err("%s: invalid firmware status", __func__);
+		return fw_status;
+	}
+
+	rc = smd_read(penv->smd_ch, &fw_status, 1);
+	if (rc < 0) {
+		pr_err("%s: incomplete data read from smd\n", __func__);
+		return fw_status;
+	}
+	return fw_status;
+}
+
+static void wcnss_send_cal_rsp(unsigned char fw_status)
+{
+	struct smd_msg_hdr *rsphdr;
+	unsigned char *msg = NULL;
+	int rc;
+
+	msg = kmalloc((sizeof(struct smd_msg_hdr) + 1), GFP_KERNEL);
+	if (NULL == msg) {
+		pr_err("wcnss: %s: failed to get memory\n", __func__);
+		return;
+	}
+
+	rsphdr = (struct smd_msg_hdr *)msg;
+	rsphdr->msg_type = WCNSS_CALDATA_UPLD_RSP;
+	rsphdr->msg_len = sizeof(struct smd_msg_hdr) + 1;
+	memcpy(msg+sizeof(struct smd_msg_hdr), &fw_status, 1);
+
+	rc = wcnss_smd_tx(msg, rsphdr->msg_len);
+	if (rc < 0)
+		pr_err("wcnss: smd tx failed\n");
+}
+
+/* Collect calibrated data from WCNSS */
+void extract_cal_data(int len)
+{
+	int rc;
+	struct cal_data_params calhdr;
+	unsigned char fw_status = WCNSS_RESP_FAIL;
+
+	if (len < sizeof(struct cal_data_params)) {
+		pr_err("wcnss: incomplete cal header length\n");
+		return;
+	}
+
+	rc = smd_read(penv->smd_ch, (unsigned char *)&calhdr,
+			sizeof(struct cal_data_params));
+	if (rc < sizeof(struct cal_data_params)) {
+		pr_err("wcnss: incomplete cal header read from smd\n");
+		return;
+	}
+
+	if (penv->fw_cal_exp_frag != calhdr.frag_number) {
+		pr_err("wcnss: Invalid frgament");
+		goto exit;
+	}
+
+	if (calhdr.frag_size > WCNSS_MAX_FRAME_SIZE) {
+		pr_err("wcnss: Invalid fragment size");
+		goto exit;
+	}
+
+	if (0 == calhdr.frag_number) {
+		if (calhdr.total_size > MAX_CALIBRATED_DATA_SIZE) {
+			pr_err("wcnss: Invalid cal data size %d",
+				calhdr.total_size);
+			goto exit;
+		}
+		kfree(penv->fw_cal_data);
+		penv->fw_cal_rcvd = 0;
+		penv->fw_cal_data = kmalloc(calhdr.total_size,
+				GFP_KERNEL);
+		if (penv->fw_cal_data == NULL) {
+			smd_read(penv->smd_ch, NULL, calhdr.frag_size);
+			goto exit;
+		}
+	}
+
+	mutex_lock(&penv->dev_lock);
+	if (penv->fw_cal_rcvd + calhdr.frag_size >
+			MAX_CALIBRATED_DATA_SIZE) {
+		pr_err("calibrated data size is more than expected %d",
+				penv->fw_cal_rcvd + calhdr.frag_size);
+		penv->fw_cal_exp_frag = 0;
+		penv->fw_cal_rcvd = 0;
+		smd_read(penv->smd_ch, NULL, calhdr.frag_size);
+		goto unlock_exit;
+	}
+
+	rc = smd_read(penv->smd_ch, penv->fw_cal_data + penv->fw_cal_rcvd,
+			calhdr.frag_size);
+	if (rc < calhdr.frag_size)
+		goto unlock_exit;
+
+	penv->fw_cal_exp_frag++;
+	penv->fw_cal_rcvd += calhdr.frag_size;
+
+	if (calhdr.msg_flags & LAST_FRAGMENT) {
+		penv->fw_cal_exp_frag = 0;
+		penv->fw_cal_available = true;
+		pr_info("wcnss: cal data collection completed\n");
+	}
+	mutex_unlock(&penv->dev_lock);
+	wake_up(&penv->read_wait);
+
+	if (penv->fw_cal_available) {
+		fw_status = WCNSS_RESP_SUCCESS;
+		wcnss_send_cal_rsp(fw_status);
+	}
+	return;
+
+unlock_exit:
+	mutex_unlock(&penv->dev_lock);
+
+exit:
+	wcnss_send_cal_rsp(fw_status);
+	return;
+}
+
+
 static void wcnssctrl_rx_handler(struct work_struct *worker)
 {
 	int len = 0;
 	int rc = 0;
-	unsigned char buf[WCNSS_MAX_FRAME_SIZE];
+	unsigned char buf[sizeof(struct wcnss_version)];
 	struct smd_msg_hdr *phdr;
 	struct wcnss_version *pversion;
+	unsigned char fw_status = 0;
 
 	len = smd_read_avail(penv->smd_ch);
 	if (len > WCNSS_MAX_FRAME_SIZE) {
@@ -748,23 +969,33 @@
 	if (len <= 0)
 		return;
 
-	rc = smd_read(penv->smd_ch, buf, len);
-	if (rc < len) {
-		pr_err("wcnss: incomplete data read from smd\n");
+	rc = smd_read(penv->smd_ch, buf, sizeof(struct smd_msg_hdr));
+	if (rc < sizeof(struct smd_msg_hdr)) {
+		pr_err("wcnss: incomplete header read from smd\n");
 		return;
 	}
+	len -= sizeof(struct smd_msg_hdr);
 
 	phdr = (struct smd_msg_hdr *)buf;
 
 	switch (phdr->msg_type) {
 
 	case WCNSS_VERSION_RSP:
-		pversion = (struct wcnss_version *)buf;
-		if (len != sizeof(struct wcnss_version)) {
+		if (len != sizeof(struct wcnss_version)
+				- sizeof(struct smd_msg_hdr)) {
 			pr_err("wcnss: invalid version data from wcnss %d\n",
-				len);
+					len);
 			return;
 		}
+		rc = smd_read(penv->smd_ch, buf+sizeof(struct smd_msg_hdr),
+				len);
+		if (rc < len) {
+			pr_err("wcnss: incomplete data read from smd\n");
+			return;
+		}
+		pversion = (struct wcnss_version *)buf;
+		penv->fw_major = pversion->major;
+		penv->fw_minor = pversion->minor;
 		snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
 			"%02x%02x%02x%02x", pversion->major, pversion->minor,
 					pversion->version, pversion->revision);
@@ -776,11 +1007,28 @@
 		if ((pversion->major >= 1) && (pversion->minor >= 4)) {
 			pr_info("wcnss: schedule dnld work for riva\n");
 			schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
+		} else {
+			penv->nv_downloaded = true;
 		}
 		break;
 
 	case WCNSS_NVBIN_DNLD_RSP:
-		pr_info("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu\n");
+		penv->nv_downloaded = true;
+		fw_status = wcnss_fw_status();
+		pr_debug("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu %u\n",
+			fw_status);
+		break;
+
+	case WCNSS_CALDATA_DNLD_RSP:
+		penv->nv_downloaded = true;
+		fw_status = wcnss_fw_status();
+		pr_debug("wcnss: received WCNSS_CALDATA_DNLD_RSP from ccpu %u\n",
+			fw_status);
+		break;
+
+	case WCNSS_CALDATA_UPLD_REQ:
+		penv->fw_cal_available = 0;
+		extract_cal_data(len);
 		break;
 
 	default:
@@ -803,7 +1051,8 @@
 	return;
 }
 
-static void wcnss_nvbin_dnld_req(struct work_struct *worker)
+
+static void wcnss_nvbin_dnld(void)
 {
 	int ret = 0;
 	struct nvbin_dnld_req_msg *dnld_req_msg;
@@ -820,8 +1069,8 @@
 	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);
+		pr_err("wcnss: %s: request_firmware failed for %s\n",
+			__func__, NVBIN_FILE);
 		return;
 	}
 
@@ -842,13 +1091,14 @@
 		NV_FRAGMENT_SIZE), GFP_KERNEL);
 
 	if (NULL == outbuffer) {
-		pr_err("wcnss: wcnss_nvbin_dnld_req: failed to get buffer\n");
+		pr_err("wcnss: %s: failed to get buffer\n", __func__);
 		goto err_free_nv;
 	}
 
 	dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
 
 	dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
+	dnld_req_msg->dnld_req_params.msg_flags = 0;
 
 	for (count = 0; count < total_fragments; count++) {
 		dnld_req_msg->dnld_req_params.frag_number = count;
@@ -859,10 +1109,14 @@
 			if (!cur_frag_size)
 				cur_frag_size = NV_FRAGMENT_SIZE;
 
-			dnld_req_msg->dnld_req_params.is_last_fragment = 1;
+			dnld_req_msg->dnld_req_params.msg_flags |=
+				LAST_FRAGMENT;
+			dnld_req_msg->dnld_req_params.msg_flags |=
+				CAN_RECEIVE_CALDATA;
 		} else {
 			cur_frag_size = NV_FRAGMENT_SIZE;
-			dnld_req_msg->dnld_req_params.is_last_fragment = 0;
+			dnld_req_msg->dnld_req_params.msg_flags &=
+				~LAST_FRAGMENT;
 		}
 
 		dnld_req_msg->dnld_req_params.nvbin_buffer_size =
@@ -880,7 +1134,8 @@
 
 		retry_count = 0;
 		while ((ret == -ENOSPC) && (retry_count <= 3)) {
-			pr_debug("wcnss: wcnss_nvbin_dnld_req: smd tx failed, ENOSPC\n");
+			pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
+				__func__);
 			pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
 				count, dnld_req_msg->hdr.msg_len,
 				total_fragments, retry_count);
@@ -893,7 +1148,7 @@
 		}
 
 		if (ret < 0) {
-			pr_err("wcnss: wcnss_nvbin_dnld_req: smd tx failed\n");
+			pr_err("wcnss: %s: smd tx failed\n", __func__);
 			pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
 				count, dnld_req_msg->hdr.msg_len,
 				total_fragments, retry_count);
@@ -912,6 +1167,138 @@
 	return;
 }
 
+
+static void wcnss_caldata_dnld(const void *cal_data,
+		unsigned int cal_data_size, bool msg_to_follow)
+{
+	int ret = 0;
+	struct cal_data_msg *cal_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;
+
+	total_fragments = TOTALFRAGMENTS(cal_data_size);
+
+	outbuffer = kmalloc((sizeof(struct cal_data_msg) +
+		NV_FRAGMENT_SIZE), GFP_KERNEL);
+
+	if (NULL == outbuffer) {
+		pr_err("wcnss: %s: failed to get buffer\n", __func__);
+		return;
+	}
+
+	cal_msg = (struct cal_data_msg *)outbuffer;
+
+	cal_msg->hdr.msg_type = WCNSS_CALDATA_DNLD_REQ;
+	cal_msg->cal_params.msg_flags = 0;
+
+	for (count = 0; count < total_fragments; count++) {
+		cal_msg->cal_params.frag_number = count;
+
+		if (count == (total_fragments - 1)) {
+			cur_frag_size = cal_data_size % NV_FRAGMENT_SIZE;
+			if (!cur_frag_size)
+				cur_frag_size = NV_FRAGMENT_SIZE;
+
+			cal_msg->cal_params.msg_flags
+			    |= LAST_FRAGMENT;
+			if (msg_to_follow)
+				cal_msg->cal_params.msg_flags |=
+					MESSAGE_TO_FOLLOW;
+		} else {
+			cur_frag_size = NV_FRAGMENT_SIZE;
+			cal_msg->cal_params.msg_flags &=
+				~LAST_FRAGMENT;
+		}
+
+		cal_msg->cal_params.total_size = cal_data_size;
+		cal_msg->cal_params.frag_size =
+			cur_frag_size;
+
+		cal_msg->hdr.msg_len =
+			sizeof(struct cal_data_msg) + cur_frag_size;
+
+		memcpy((outbuffer + sizeof(struct cal_data_msg)),
+			(cal_data + count * NV_FRAGMENT_SIZE),
+			cur_frag_size);
+
+		ret = wcnss_smd_tx(outbuffer, cal_msg->hdr.msg_len);
+
+		retry_count = 0;
+		while ((ret == -ENOSPC) && (retry_count <= 3)) {
+			pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
+					__func__);
+			pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
+				count, cal_msg->hdr.msg_len,
+				total_fragments, retry_count);
+
+			/* wait and try again */
+			msleep(20);
+			retry_count++;
+			ret = wcnss_smd_tx(outbuffer,
+				cal_msg->hdr.msg_len);
+		}
+
+		if (ret < 0) {
+			pr_err("wcnss: %s: smd tx failed\n", __func__);
+			pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
+				count, cal_msg->hdr.msg_len,
+				total_fragments, retry_count);
+			goto err_dnld;
+		}
+	}
+
+
+err_dnld:
+	/* free buffer */
+	kfree(outbuffer);
+
+	return;
+}
+
+
+static void wcnss_nvbin_dnld_main(struct work_struct *worker)
+{
+	int retry = 0;
+
+	if (!FW_CALDATA_CAPABLE())
+		goto nv_download;
+
+	if (!penv->fw_cal_available && WCNSS_CONFIG_UNSPECIFIED
+		!= has_calibrated_data && !penv->user_cal_available) {
+		while (!penv->user_cal_available && retry++ < 5)
+			msleep(500);
+	}
+
+	/* only cal data is sent during ssr (if available) */
+	if (penv->fw_cal_available && penv->ssr_boot) {
+		pr_info_ratelimited("wcnss: cal download during SSR, using fw cal");
+		wcnss_caldata_dnld(penv->fw_cal_data, penv->fw_cal_rcvd, false);
+		return;
+
+	} else if (penv->user_cal_available && penv->ssr_boot) {
+		pr_info_ratelimited("wcnss: cal download during SSR, using user cal");
+		wcnss_caldata_dnld(penv->user_cal_data,
+		penv->user_cal_rcvd, false);
+		return;
+
+	} else if (penv->user_cal_available) {
+		pr_info_ratelimited("wcnss: cal download during cold boot, using user cal");
+		wcnss_caldata_dnld(penv->user_cal_data,
+		penv->user_cal_rcvd, true);
+	}
+
+nv_download:
+	pr_info_ratelimited("wcnss: NV download");
+	wcnss_nvbin_dnld();
+
+	return;
+}
+
+
+
 static int
 wcnss_trigger_config(struct platform_device *pdev)
 {
@@ -982,7 +1369,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);
+	INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_main);
 
 	wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
 
@@ -992,8 +1379,6 @@
 		goto fail_wake;
 	}
 
-	penv->cold_boot_done = 1;
-
 	return 0;
 
 fail_wake:
@@ -1013,20 +1398,128 @@
 	return ret;
 }
 
-#ifndef MODULE
 static int wcnss_node_open(struct inode *inode, struct file *file)
 {
 	struct platform_device *pdev;
 
-	pr_info(DEVICE " triggered by userspace\n");
+	/* first open is only to trigger WCNSS platform driver */
+	if (!penv->triggered) {
+		pr_info(DEVICE " triggered by userspace\n");
+		pdev = penv->pdev;
+		return wcnss_trigger_config(pdev);
 
-	pdev = penv->pdev;
-	return wcnss_trigger_config(pdev);
+	} else if (penv->device_opened) {
+		pr_info(DEVICE " already opened\n");
+		return -EBUSY;
+	}
+
+	mutex_lock(&penv->dev_lock);
+	penv->user_cal_rcvd = 0;
+	penv->user_cal_read = 0;
+	penv->user_cal_available = false;
+	penv->user_cal_data = NULL;
+	penv->device_opened = 1;
+	mutex_unlock(&penv->dev_lock);
+
+	return 0;
 }
 
+static ssize_t wcnss_wlan_read(struct file *fp, char __user
+			*buffer, size_t count, loff_t *position)
+{
+	int rc = 0;
+
+	if (!penv->device_opened)
+		return -EFAULT;
+
+	rc = wait_event_interruptible(penv->read_wait, penv->fw_cal_rcvd
+			> penv->user_cal_read || penv->fw_cal_available);
+
+	if (rc < 0)
+		return rc;
+
+	mutex_lock(&penv->dev_lock);
+
+	if (penv->fw_cal_available && penv->fw_cal_rcvd
+			== penv->user_cal_read) {
+		rc = 0;
+		goto exit;
+	}
+
+	if (count > penv->fw_cal_rcvd - penv->user_cal_read)
+		count = penv->fw_cal_rcvd - penv->user_cal_read;
+
+	rc = copy_to_user(buffer, penv->fw_cal_data +
+			penv->user_cal_read, count);
+	if (rc == 0) {
+		penv->user_cal_read += count;
+		rc = count;
+	}
+
+exit:
+	mutex_unlock(&penv->dev_lock);
+	return rc;
+}
+
+/* first (valid) write to this device should be 4 bytes cal file size */
+static ssize_t wcnss_wlan_write(struct file *fp, const char __user
+			*user_buffer, size_t count, loff_t *position)
+{
+	int rc = 0;
+	int size = 0;
+
+	if (!penv->device_opened || penv->user_cal_available)
+		return -EFAULT;
+
+	if (penv->user_cal_rcvd == 0 && count >= 4
+			&& !penv->user_cal_data) {
+		rc = copy_from_user((void *)&size, user_buffer, 4);
+		if (size > MAX_CALIBRATED_DATA_SIZE) {
+			pr_err(DEVICE " invalid size to write %d\n", size);
+			return -EFAULT;
+		}
+
+		rc += count;
+		count -= 4;
+		penv->user_cal_exp_size =  size;
+		penv->user_cal_data = kmalloc(size, GFP_KERNEL);
+		if (penv->user_cal_data == NULL) {
+			pr_err(DEVICE " no memory to write\n");
+			return -ENOMEM;
+		}
+		if (0 == count)
+			goto exit;
+
+	} else if (penv->user_cal_rcvd == 0 && count < 4)
+		return -EFAULT;
+
+	if (MAX_CALIBRATED_DATA_SIZE < count + penv->user_cal_rcvd) {
+		pr_err(DEVICE " invalid size to write %d\n", count +
+				penv->user_cal_rcvd);
+		rc = -ENOMEM;
+		goto exit;
+	}
+	rc = copy_from_user((void *)penv->user_cal_data +
+			penv->user_cal_rcvd, user_buffer, count);
+	if (0 == rc) {
+		penv->user_cal_rcvd += count;
+		rc += count;
+	}
+	if (penv->user_cal_rcvd == penv->user_cal_exp_size) {
+		penv->user_cal_available = true;
+		pr_info_ratelimited("wcnss: user cal written");
+	}
+
+exit:
+	return rc;
+}
+
+
 static const struct file_operations wcnss_node_fops = {
 	.owner = THIS_MODULE,
 	.open = wcnss_node_open,
+	.read = wcnss_wlan_read,
+	.write = wcnss_wlan_write,
 };
 
 static struct miscdevice wcnss_misc = {
@@ -1034,8 +1527,6 @@
 	.name = DEVICE,
 	.fops = &wcnss_node_fops,
 };
-#endif /* ifndef MODULE */
-
 
 static int __devinit
 wcnss_wlan_probe(struct platform_device *pdev)
@@ -1061,19 +1552,8 @@
 	if (ret)
 		return -ENOENT;
 
-
-#ifdef MODULE
-
-	/*
-	 * Since we were built as a module, we are running because
-	 * the module was loaded, therefore we assume userspace
-	 * applications are available to service PIL, so we can
-	 * trigger the WCNSS configuration now
-	 */
-	pr_info(DEVICE " probed in MODULE mode\n");
-	return wcnss_trigger_config(pdev);
-
-#else
+	mutex_init(&penv->dev_lock);
+	init_waitqueue_head(&penv->read_wait);
 
 	/*
 	 * Since we were built into the kernel we'll be called as part
@@ -1087,7 +1567,6 @@
 	pr_info(DEVICE " probed in built-in mode\n");
 	return misc_register(&wcnss_misc);
 
-#endif
 }
 
 static int __devexit
diff --git a/include/linux/wcnss_wlan.h b/include/linux/wcnss_wlan.h
index ee18b14..4c99e77 100644
--- a/include/linux/wcnss_wlan.h
+++ b/include/linux/wcnss_wlan.h
@@ -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
@@ -27,6 +27,7 @@
 
 #define WCNSS_WLAN_IRQ_INVALID -1
 #define HAVE_WCNSS_RESET_INTR 1
+#define HAVE_WCNSS_CAL_DOWNLOAD 1
 
 struct device *wcnss_wlan_get_device(void);
 struct resource *wcnss_wlan_get_memory_map(struct device *dev);
@@ -53,10 +54,10 @@
 void wcnss_prevent_suspend(void);
 void wcnss_ssr_boot_notify(void);
 void wcnss_reset_intr(void);
-int wcnss_cold_boot_done(void);
 void wcnss_riva_dump_pmic_regs(void);
 void *wcnss_prealloc_get(unsigned int size);
 int wcnss_prealloc_put(void *ptr);
+int wcnss_device_ready(void);
 
 #define wcnss_wlan_get_drvdata(dev) dev_get_drvdata(dev)
 #define wcnss_wlan_set_drvdata(dev, data) dev_set_drvdata((dev), (data))