From 9f5732533180512df8f122b86412e074f88625c5 Mon Sep 17 00:00:00 2001 From: Sameer Thalappil Date: Wed, 20 Feb 2013 13:54:17 -0800 Subject: [PATCH] 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. NV bin id not downloaded by platform driver during SSR. Change-Id: I070aae4270c54a3af4d4c0a24897e8c0c9cbfaa5 Acked-by: Rajesh Chauhan Signed-off-by: Sameer Thalappil --- drivers/net/wireless/wcnss/wcnss_riva.c | 33 +++- drivers/net/wireless/wcnss/wcnss_wlan.c | 212 +++++++++++++++++++++++- include/linux/wcnss_wlan.h | 1 + 3 files changed, 229 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/wcnss/wcnss_riva.c b/drivers/net/wireless/wcnss/wcnss_riva.c index 25d9b5d3937a..c4cc8e9bc568 100644 --- a/drivers/net/wireless/wcnss/wcnss_riva.c +++ b/drivers/net/wireless/wcnss/wcnss_riva.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 @@ -44,6 +44,9 @@ static DEFINE_SEMAPHORE(riva_power_on_lock); #define RIVA_PMU_CFG_IRIS_XO_MODE 0x6 #define RIVA_PMU_CFG_IRIS_XO_MODE_48 (3 << 1) +#define RIVA_SPARE_OUT (msm_riva_base + 0x0b4) +#define NVBIN_DLND_BIT BIT(25) + #define VREG_NULL_CONFIG 0x0000 #define VREG_GET_REGULATOR_MASK 0x0001 #define VREG_SET_VOLTAGE_MASK 0x0002 @@ -96,6 +99,16 @@ static int configure_iris_xo(struct device *dev, bool use_48mhz_xo, int on) goto fail; } + /* power on thru SSR should not set NV bit, + * during SSR, NV bin is downloaded by WLAN driver + */ + 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); + } + /* Enable IRIS XO */ rc = clk_prepare_enable(cxo); if (rc) { @@ -135,15 +148,15 @@ static int configure_iris_xo(struct device *dev, bool use_48mhz_xo, int on) wlan_clock = msm_xo_get(MSM_XO_TCXO_A2, id); if (IS_ERR(wlan_clock)) { rc = PTR_ERR(wlan_clock); - pr_err("Failed to get MSM_XO_TCXO_A2 voter" - " (%d)\n", rc); + pr_err("Failed to get MSM_XO_TCXO_A2 voter (%d)\n", + rc); goto fail; } rc = msm_xo_mode_vote(wlan_clock, MSM_XO_MODE_ON); if (rc < 0) { - pr_err("Configuring MSM_XO_MODE_ON failed" - " (%d)\n", rc); + pr_err("Configuring MSM_XO_MODE_ON failed (%d)\n", + rc); goto msm_xo_vote_fail; } } @@ -151,8 +164,8 @@ static int configure_iris_xo(struct device *dev, bool use_48mhz_xo, int on) if (wlan_clock != NULL && !use_48mhz_xo) { rc = msm_xo_mode_vote(wlan_clock, MSM_XO_MODE_OFF); if (rc < 0) - pr_err("Configuring MSM_XO_MODE_OFF failed" - " (%d)\n", rc); + pr_err("Configuring MSM_XO_MODE_OFF failed (%d)\n", + rc); } } @@ -359,7 +372,11 @@ int req_riva_power_on_lock(char *driver_name) node = kmalloc(sizeof(struct host_driver), GFP_KERNEL); if (!node) goto err; - strncpy(node->name, driver_name, sizeof(node->name)); + if (strlcpy(node->name, driver_name, sizeof(node->name)) + >= sizeof(node->name)) { + kfree(node); + goto err; + } mutex_lock(&list_lock); /* Lock when the first request is added */ diff --git a/drivers/net/wireless/wcnss/wcnss_wlan.c b/drivers/net/wireless/wcnss/wcnss_wlan.c index b30ed4567bff..3bc8d89a580a 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 +#include #include #include #include @@ -34,6 +35,7 @@ /* module params */ #define WCNSS_CONFIG_UNSPECIFIED (-1) + static int has_48mhz_xo = WCNSS_CONFIG_UNSPECIFIED; module_param(has_48mhz_xo, int, S_IWUSR | S_IRUGO); MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present"); @@ -54,13 +56,16 @@ static DEFINE_SPINLOCK(reg_spinlock); #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 { @@ -71,6 +76,57 @@ struct wcnss_version { 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 + * 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; @@ -81,6 +137,7 @@ static struct { 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 int serial_number; @@ -89,6 +146,7 @@ static struct { 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; @@ -564,6 +622,16 @@ void wcnss_allow_suspend() } EXPORT_SYMBOL(wcnss_allow_suspend); +int wcnss_cold_boot_done(void) +{ + if (penv) + return penv->cold_boot_done; + else + return -ENODEV; +} +EXPORT_SYMBOL(wcnss_cold_boot_done); + + static int wcnss_smd_tx(void *data, int len) { int ret = 0; @@ -571,7 +639,7 @@ static int wcnss_smd_tx(void *data, int len) 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) { @@ -606,7 +674,7 @@ static void wcnssctrl_rx_handler(struct work_struct *worker) phdr = (struct smd_msg_hdr *)buf; - switch (phdr->type) { + switch (phdr->msg_type) { case WCNSS_VERSION_RSP: pversion = (struct wcnss_version *)buf; @@ -619,10 +687,22 @@ static void wcnssctrl_rx_handler(struct work_struct *worker) "%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 riva ccpu, + * 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_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; } @@ -632,15 +712,126 @@ static void wcnss_send_version_req(struct work_struct *worker) 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) { @@ -711,6 +902,7 @@ wcnss_trigger_config(struct platform_device *pdev) 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"); @@ -720,6 +912,8 @@ wcnss_trigger_config(struct platform_device *pdev) goto fail_wake; } + penv->cold_boot_done = 1; + return 0; fail_wake: diff --git a/include/linux/wcnss_wlan.h b/include/linux/wcnss_wlan.h index 44ad73e42179..eaf7ed383dc7 100644 --- a/include/linux/wcnss_wlan.h +++ b/include/linux/wcnss_wlan.h @@ -53,6 +53,7 @@ void wcnss_allow_suspend(void); void wcnss_prevent_suspend(void); void wcnss_ssr_boot_notify(void); void wcnss_reset_intr(void); +int wcnss_cold_boot_done(void); #define wcnss_wlan_get_drvdata(dev) dev_get_drvdata(dev) #define wcnss_wlan_set_drvdata(dev, data) dev_set_drvdata((dev), (data))