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))