aboutsummaryrefslogtreecommitdiff
path: root/drivers/net
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/wireless/ath/wcn36xx/Makefile5
-rw-r--r--drivers/net/wireless/ath/wcn36xx/dxe.c63
-rw-r--r--drivers/net/wireless/ath/wcn36xx/dxe.h1
-rw-r--r--drivers/net/wireless/ath/wcn36xx/hal.h18
-rw-r--r--drivers/net/wireless/ath/wcn36xx/main.c53
-rw-r--r--drivers/net/wireless/ath/wcn36xx/smd.c37
-rw-r--r--drivers/net/wireless/ath/wcn36xx/smd.h9
-rw-r--r--drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c314
-rw-r--r--drivers/net/wireless/ath/wcn36xx/wcn36xx.h44
-rw-r--r--drivers/net/wireless/ath/wcn36xx/wcnss_core.c295
-rw-r--r--drivers/net/wireless/ath/wcn36xx/wcnss_core.h99
11 files changed, 883 insertions, 55 deletions
diff --git a/drivers/net/wireless/ath/wcn36xx/Makefile b/drivers/net/wireless/ath/wcn36xx/Makefile
index 50c43b4382ba..9f6370f0cabc 100644
--- a/drivers/net/wireless/ath/wcn36xx/Makefile
+++ b/drivers/net/wireless/ath/wcn36xx/Makefile
@@ -1,7 +1,10 @@
-obj-$(CONFIG_WCN36XX) := wcn36xx.o
+obj-$(CONFIG_WCN36XX) := wcn36xx.o wcn36xx-platform.o
wcn36xx-y += main.o \
dxe.o \
txrx.o \
smd.o \
pmc.o \
debug.o
+
+wcn36xx-platform-y += wcn36xx-msm.o\
+ wcnss_core.o
diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.c b/drivers/net/wireless/ath/wcn36xx/dxe.c
index 086549b732b9..196c73fe8b27 100644
--- a/drivers/net/wireless/ath/wcn36xx/dxe.c
+++ b/drivers/net/wireless/ath/wcn36xx/dxe.c
@@ -46,7 +46,7 @@ static void wcn36xx_dxe_write_register(struct wcn36xx *wcn, int addr, int data)
#define wcn36xx_dxe_write_register_x(wcn, reg, reg_data) \
do { \
- if (wcn->chip_version == WCN36XX_CHIP_3680) \
+ if (wcn->chip_version != WCN36XX_CHIP_3660) \
wcn36xx_dxe_write_register(wcn, reg ## _3680, reg_data); \
else \
wcn36xx_dxe_write_register(wcn, reg ## _3660, reg_data); \
@@ -79,6 +79,7 @@ static int wcn36xx_dxe_allocate_ctl_block(struct wcn36xx_dxe_ch *ch)
struct wcn36xx_dxe_ctl *cur_ctl = NULL;
int i;
+ spin_lock_init(&ch->lock);
for (i = 0; i < ch->desc_num; i++) {
cur_ctl = kzalloc(sizeof(*cur_ctl), GFP_KERNEL);
if (!cur_ctl)
@@ -169,7 +170,7 @@ void wcn36xx_dxe_free_ctl_blks(struct wcn36xx *wcn)
wcn36xx_dxe_free_ctl_block(&wcn->dxe_rx_h_ch);
}
-static int wcn36xx_dxe_init_descs(struct wcn36xx_dxe_ch *wcn_ch)
+static int wcn36xx_dxe_init_descs(struct device *dev, struct wcn36xx_dxe_ch *wcn_ch)
{
struct wcn36xx_dxe_desc *cur_dxe = NULL;
struct wcn36xx_dxe_desc *prev_dxe = NULL;
@@ -178,7 +179,7 @@ static int wcn36xx_dxe_init_descs(struct wcn36xx_dxe_ch *wcn_ch)
int i;
size = wcn_ch->desc_num * sizeof(struct wcn36xx_dxe_desc);
- wcn_ch->cpu_addr = dma_alloc_coherent(NULL, size, &wcn_ch->dma_addr,
+ wcn_ch->cpu_addr = dma_alloc_coherent(dev, size, &wcn_ch->dma_addr,
GFP_KERNEL);
if (!wcn_ch->cpu_addr)
return -ENOMEM;
@@ -270,7 +271,7 @@ static int wcn36xx_dxe_enable_ch_int(struct wcn36xx *wcn, u16 wcn_ch)
return 0;
}
-static int wcn36xx_dxe_fill_skb(struct wcn36xx_dxe_ctl *ctl)
+static int wcn36xx_dxe_fill_skb(struct device *dev, struct wcn36xx_dxe_ctl *ctl)
{
struct wcn36xx_dxe_desc *dxe = ctl->desc;
struct sk_buff *skb;
@@ -279,7 +280,7 @@ static int wcn36xx_dxe_fill_skb(struct wcn36xx_dxe_ctl *ctl)
if (skb == NULL)
return -ENOMEM;
- dxe->dst_addr_l = dma_map_single(NULL,
+ dxe->dst_addr_l = dma_map_single(dev,
skb_tail_pointer(skb),
WCN36XX_PKT_SIZE,
DMA_FROM_DEVICE);
@@ -297,7 +298,7 @@ static int wcn36xx_dxe_ch_alloc_skb(struct wcn36xx *wcn,
cur_ctl = wcn_ch->head_blk_ctl;
for (i = 0; i < wcn_ch->desc_num; i++) {
- wcn36xx_dxe_fill_skb(cur_ctl);
+ wcn36xx_dxe_fill_skb(wcn->dev, cur_ctl);
cur_ctl = cur_ctl->next;
}
@@ -345,7 +346,7 @@ void wcn36xx_dxe_tx_ack_ind(struct wcn36xx *wcn, u32 status)
static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch)
{
- struct wcn36xx_dxe_ctl *ctl = ch->tail_blk_ctl;
+ struct wcn36xx_dxe_ctl *ctl;
struct ieee80211_tx_info *info;
unsigned long flags;
@@ -354,23 +355,25 @@ static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch)
* completely full head and tail are pointing to the same element
* and while-do will not make any cycles.
*/
+ spin_lock_irqsave(&ch->lock, flags);
+ ctl = ch->tail_blk_ctl;
do {
if (ctl->desc->ctrl & WCN36XX_DXE_CTRL_VALID_MASK)
break;
if (ctl->skb) {
- dma_unmap_single(NULL, ctl->desc->src_addr_l,
+ dma_unmap_single(wcn->dev, ctl->desc->src_addr_l,
ctl->skb->len, DMA_TO_DEVICE);
info = IEEE80211_SKB_CB(ctl->skb);
if (!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) {
/* Keep frame until TX status comes */
ieee80211_free_txskb(wcn->hw, ctl->skb);
}
- spin_lock_irqsave(&ctl->skb_lock, flags);
+ spin_lock(&ctl->skb_lock);
if (wcn->queues_stopped) {
wcn->queues_stopped = false;
ieee80211_wake_queues(wcn->hw);
}
- spin_unlock_irqrestore(&ctl->skb_lock, flags);
+ spin_unlock(&ctl->skb_lock);
ctl->skb = NULL;
}
@@ -379,6 +382,7 @@ static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch)
!(ctl->desc->ctrl & WCN36XX_DXE_CTRL_VALID_MASK));
ch->tail_blk_ctl = ctl;
+ spin_unlock_irqrestore(&ch->lock, flags);
}
static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
@@ -474,7 +478,7 @@ static int wcn36xx_rx_handle_packets(struct wcn36xx *wcn,
while (!(dxe->ctrl & WCN36XX_DXE_CTRL_VALID_MASK)) {
skb = ctl->skb;
dma_addr = dxe->dst_addr_l;
- wcn36xx_dxe_fill_skb(ctl);
+ wcn36xx_dxe_fill_skb(wcn->dev, ctl);
switch (ch->ch_type) {
case WCN36XX_DXE_CH_RX_L:
@@ -491,7 +495,7 @@ static int wcn36xx_rx_handle_packets(struct wcn36xx *wcn,
wcn36xx_warn("Unknown channel\n");
}
- dma_unmap_single(NULL, dma_addr, WCN36XX_PKT_SIZE,
+ dma_unmap_single(wcn->dev, dma_addr, WCN36XX_PKT_SIZE,
DMA_FROM_DEVICE);
wcn36xx_rx_skb(wcn, skb);
ctl = ctl->next;
@@ -540,7 +544,7 @@ int wcn36xx_dxe_allocate_mem_pools(struct wcn36xx *wcn)
16 - (WCN36XX_BD_CHUNK_SIZE % 8);
s = wcn->mgmt_mem_pool.chunk_size * WCN36XX_DXE_CH_DESC_NUMB_TX_H;
- cpu_addr = dma_alloc_coherent(NULL, s, &wcn->mgmt_mem_pool.phy_addr,
+ cpu_addr = dma_alloc_coherent(wcn->dev, s, &wcn->mgmt_mem_pool.phy_addr,
GFP_KERNEL);
if (!cpu_addr)
goto out_err;
@@ -555,7 +559,7 @@ int wcn36xx_dxe_allocate_mem_pools(struct wcn36xx *wcn)
16 - (WCN36XX_BD_CHUNK_SIZE % 8);
s = wcn->data_mem_pool.chunk_size * WCN36XX_DXE_CH_DESC_NUMB_TX_L;
- cpu_addr = dma_alloc_coherent(NULL, s, &wcn->data_mem_pool.phy_addr,
+ cpu_addr = dma_alloc_coherent(wcn->dev, s, &wcn->data_mem_pool.phy_addr,
GFP_KERNEL);
if (!cpu_addr)
goto out_err;
@@ -574,13 +578,13 @@ out_err:
void wcn36xx_dxe_free_mem_pools(struct wcn36xx *wcn)
{
if (wcn->mgmt_mem_pool.virt_addr)
- dma_free_coherent(NULL, wcn->mgmt_mem_pool.chunk_size *
+ dma_free_coherent(wcn->dev, wcn->mgmt_mem_pool.chunk_size *
WCN36XX_DXE_CH_DESC_NUMB_TX_H,
wcn->mgmt_mem_pool.virt_addr,
wcn->mgmt_mem_pool.phy_addr);
if (wcn->data_mem_pool.virt_addr) {
- dma_free_coherent(NULL, wcn->data_mem_pool.chunk_size *
+ dma_free_coherent(wcn->dev, wcn->data_mem_pool.chunk_size *
WCN36XX_DXE_CH_DESC_NUMB_TX_L,
wcn->data_mem_pool.virt_addr,
wcn->data_mem_pool.phy_addr);
@@ -596,12 +600,14 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
struct wcn36xx_dxe_desc *desc = NULL;
struct wcn36xx_dxe_ch *ch = NULL;
unsigned long flags;
+ int ret;
ch = is_low ? &wcn->dxe_tx_l_ch : &wcn->dxe_tx_h_ch;
+ spin_lock_irqsave(&ch->lock, flags);
ctl = ch->head_blk_ctl;
- spin_lock_irqsave(&ctl->next->skb_lock, flags);
+ spin_lock(&ctl->next->skb_lock);
/*
* If skb is not null that means that we reached the tail of the ring
@@ -611,10 +617,11 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
if (NULL != ctl->next->skb) {
ieee80211_stop_queues(wcn->hw);
wcn->queues_stopped = true;
- spin_unlock_irqrestore(&ctl->next->skb_lock, flags);
+ spin_unlock(&ctl->next->skb_lock);
+ spin_unlock_irqrestore(&ch->lock, flags);
return -EBUSY;
}
- spin_unlock_irqrestore(&ctl->next->skb_lock, flags);
+ spin_unlock(&ctl->next->skb_lock);
ctl->skb = NULL;
desc = ctl->desc;
@@ -640,10 +647,11 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
desc = ctl->desc;
if (ctl->bd_cpu_addr) {
wcn36xx_err("bd_cpu_addr cannot be NULL for skb DXE\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto unlock;
}
- desc->src_addr_l = dma_map_single(NULL,
+ desc->src_addr_l = dma_map_single(wcn->dev,
ctl->skb->data,
ctl->skb->len,
DMA_TO_DEVICE);
@@ -679,7 +687,10 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
ch->reg_ctrl, ch->def_ctrl);
}
- return 0;
+ ret = 0;
+unlock:
+ spin_unlock_irqrestore(&ch->lock, flags);
+ return ret;
}
int wcn36xx_dxe_init(struct wcn36xx *wcn)
@@ -696,7 +707,7 @@ int wcn36xx_dxe_init(struct wcn36xx *wcn)
/***************************************/
/* Init descriptors for TX LOW channel */
/***************************************/
- wcn36xx_dxe_init_descs(&wcn->dxe_tx_l_ch);
+ wcn36xx_dxe_init_descs(wcn->dev, &wcn->dxe_tx_l_ch);
wcn36xx_dxe_init_tx_bd(&wcn->dxe_tx_l_ch, &wcn->data_mem_pool);
/* Write channel head to a NEXT register */
@@ -714,7 +725,7 @@ int wcn36xx_dxe_init(struct wcn36xx *wcn)
/***************************************/
/* Init descriptors for TX HIGH channel */
/***************************************/
- wcn36xx_dxe_init_descs(&wcn->dxe_tx_h_ch);
+ wcn36xx_dxe_init_descs(wcn->dev, &wcn->dxe_tx_h_ch);
wcn36xx_dxe_init_tx_bd(&wcn->dxe_tx_h_ch, &wcn->mgmt_mem_pool);
/* Write channel head to a NEXT register */
@@ -734,7 +745,7 @@ int wcn36xx_dxe_init(struct wcn36xx *wcn)
/***************************************/
/* Init descriptors for RX LOW channel */
/***************************************/
- wcn36xx_dxe_init_descs(&wcn->dxe_rx_l_ch);
+ wcn36xx_dxe_init_descs(wcn->dev, &wcn->dxe_rx_l_ch);
/* For RX we need to preallocated buffers */
wcn36xx_dxe_ch_alloc_skb(wcn, &wcn->dxe_rx_l_ch);
@@ -764,7 +775,7 @@ int wcn36xx_dxe_init(struct wcn36xx *wcn)
/***************************************/
/* Init descriptors for RX HIGH channel */
/***************************************/
- wcn36xx_dxe_init_descs(&wcn->dxe_rx_h_ch);
+ wcn36xx_dxe_init_descs(wcn->dev, &wcn->dxe_rx_h_ch);
/* For RX we need to prealocat buffers */
wcn36xx_dxe_ch_alloc_skb(wcn, &wcn->dxe_rx_h_ch);
diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.h b/drivers/net/wireless/ath/wcn36xx/dxe.h
index 35ee7e966bd2..f869fd3a9951 100644
--- a/drivers/net/wireless/ath/wcn36xx/dxe.h
+++ b/drivers/net/wireless/ath/wcn36xx/dxe.h
@@ -243,6 +243,7 @@ struct wcn36xx_dxe_ctl {
};
struct wcn36xx_dxe_ch {
+ spinlock_t lock;
enum wcn36xx_dxe_ch_type ch_type;
void *cpu_addr;
dma_addr_t dma_addr;
diff --git a/drivers/net/wireless/ath/wcn36xx/hal.h b/drivers/net/wireless/ath/wcn36xx/hal.h
index a1f1127d7808..df4425d410e6 100644
--- a/drivers/net/wireless/ath/wcn36xx/hal.h
+++ b/drivers/net/wireless/ath/wcn36xx/hal.h
@@ -345,6 +345,10 @@ enum wcn36xx_hal_host_msg_type {
WCN36XX_HAL_DHCP_START_IND = 189,
WCN36XX_HAL_DHCP_STOP_IND = 190,
+ WCN36XX_HAL_AVOID_FREQ_RANGE_IND = 233,
+
+ WCN36XX_HAL_PRINT_REG_INFO_IND = 259,
+
WCN36XX_HAL_MSG_MAX = WCN36XX_HAL_MSG_TYPE_MAX_ENUM_SIZE
};
@@ -4381,6 +4385,20 @@ enum place_holder_in_cap_bitmap {
RTT = 20,
RATECTRL = 21,
WOW = 22,
+ WLAN_ROAM_SCAN_OFFLOAD = 23,
+ SPECULATIVE_PS_POLL = 24,
+ SCAN_SCH = 25,
+ IBSS_HEARTBEAT_OFFLOAD = 26,
+ WLAN_SCAN_OFFLOAD = 27,
+ WLAN_PERIODIC_TX_PTRN = 28,
+ ADVANCE_TDLS = 29,
+ BATCH_SCAN = 30,
+ FW_IN_TX_PATH = 31,
+ EXTENDED_NSOFFLOAD_SLOT = 32,
+ CH_SWITCH_V1 = 33,
+ HT40_OBSS_SCAN = 34,
+ UPDATE_CHANNEL_LIST = 35,
+
MAX_FEATURE_SUPPORTED = 128,
};
diff --git a/drivers/net/wireless/ath/wcn36xx/main.c b/drivers/net/wireless/ath/wcn36xx/main.c
index 900e72a089d8..bbd4eca5e9f1 100644
--- a/drivers/net/wireless/ath/wcn36xx/main.c
+++ b/drivers/net/wireless/ath/wcn36xx/main.c
@@ -201,7 +201,21 @@ static const char * const wcn36xx_caps_names[] = {
"BCN_FILTER", /* 19 */
"RTT", /* 20 */
"RATECTRL", /* 21 */
- "WOW" /* 22 */
+ "WOW", /* 22 */
+ "WLAN_ROAM_SCAN_OFFLOAD", /* 23 */
+ "SPECULATIVE_PS_POLL", /* 24 */
+ "SCAN_SCH", /* 25 */
+ "IBSS_HEARTBEAT_OFFLOAD", /* 26 */
+ "WLAN_SCAN_OFFLOAD", /* 27 */
+ "WLAN_PERIODIC_TX_PTRN", /* 28 */
+ "ADVANCE_TDLS", /* 29 */
+ "BATCH_SCAN", /* 30 */
+ "FW_IN_TX_PATH", /* 31 */
+ "EXTENDED_NSOFFLOAD_SLOT", /* 32 */
+ "CH_SWITCH_V1", /* 33 */
+ "HT40_OBSS_SCAN", /* 34 */
+ "UPDATE_CHANNEL_LIST", /* 35 */
+
};
static const char *wcn36xx_get_cap_name(enum place_holder_in_cap_bitmap x)
@@ -221,17 +235,6 @@ static void wcn36xx_feat_caps_info(struct wcn36xx *wcn)
}
}
-static void wcn36xx_detect_chip_version(struct wcn36xx *wcn)
-{
- if (get_feat_caps(wcn->fw_feat_caps, DOT11AC)) {
- wcn36xx_info("Chip is 3680\n");
- wcn->chip_version = WCN36XX_CHIP_3680;
- } else {
- wcn36xx_info("Chip is 3660\n");
- wcn->chip_version = WCN36XX_CHIP_3660;
- }
-}
-
static int wcn36xx_start(struct ieee80211_hw *hw)
{
struct wcn36xx *wcn = hw->priv;
@@ -286,8 +289,6 @@ static int wcn36xx_start(struct ieee80211_hw *hw)
wcn36xx_feat_caps_info(wcn);
}
- wcn36xx_detect_chip_version(wcn);
-
/* DMA channel initialization */
ret = wcn36xx_dxe_init(wcn);
if (ret) {
@@ -951,6 +952,10 @@ static int wcn36xx_init_ieee80211(struct wcn36xx *wcn)
ieee80211_hw_set(wcn->hw, SIGNAL_DBM);
ieee80211_hw_set(wcn->hw, HAS_RATE_CONTROL);
+ /* 3620 powersaving currently unstable */
+ if (wcn->chip_version == WCN36XX_CHIP_3620)
+ __clear_bit(IEEE80211_HW_SUPPORTS_PS, wcn->hw->flags);
+
wcn->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
BIT(NL80211_IFTYPE_AP) |
BIT(NL80211_IFTYPE_ADHOC) |
@@ -1036,11 +1041,25 @@ static int wcn36xx_probe(struct platform_device *pdev)
wcn = hw->priv;
wcn->hw = hw;
wcn->dev = &pdev->dev;
- wcn->ctrl_ops = pdev->dev.platform_data;
+ wcn->dev->dma_mask = kzalloc(sizeof(*wcn->dev->dma_mask), GFP_KERNEL);
+ if (!wcn->dev->dma_mask) {
+ ret = -ENOMEM;
+ goto dma_mask_err;
+ }
+ dma_set_mask_and_coherent(wcn->dev, DMA_BIT_MASK(32));
+ wcn->wcn36xx_data = pdev->dev.platform_data;
+ wcn->ctrl_ops = &wcn->wcn36xx_data->ctrl_ops;
+ wcn->wcn36xx_data->wcn = wcn;
+ if (!wcn->ctrl_ops->get_chip_type) {
+ dev_err(&pdev->dev, "Missing ops->get_chip_type\n");
+ ret = -EINVAL;
+ goto out_wq;
+ }
+ wcn->chip_version = wcn->ctrl_ops->get_chip_type(wcn);
mutex_init(&wcn->hal_mutex);
- if (!wcn->ctrl_ops->get_hw_mac(addr)) {
+ if (!wcn->ctrl_ops->get_hw_mac(wcn, addr)) {
wcn36xx_info("mac address: %pM\n", addr);
SET_IEEE80211_PERM_ADDR(wcn->hw, addr);
}
@@ -1059,6 +1078,8 @@ static int wcn36xx_probe(struct platform_device *pdev)
out_unmap:
iounmap(wcn->mmio);
out_wq:
+ kfree(wcn->dev->dma_mask);
+dma_mask_err:
ieee80211_free_hw(hw);
out_err:
return ret;
diff --git a/drivers/net/wireless/ath/wcn36xx/smd.c b/drivers/net/wireless/ath/wcn36xx/smd.c
index c9263e1c75d4..d950f286a9d5 100644
--- a/drivers/net/wireless/ath/wcn36xx/smd.c
+++ b/drivers/net/wireless/ath/wcn36xx/smd.c
@@ -253,7 +253,7 @@ static int wcn36xx_smd_send_and_wait(struct wcn36xx *wcn, size_t len)
init_completion(&wcn->hal_rsp_compl);
start = jiffies;
- ret = wcn->ctrl_ops->tx(wcn->hal_buf, len);
+ ret = wcn->ctrl_ops->tx(wcn, wcn->hal_buf, len);
if (ret) {
wcn36xx_err("HAL TX failed\n");
goto out;
@@ -302,6 +302,23 @@ static int wcn36xx_smd_rsp_status_check(void *buf, size_t len)
return 0;
}
+static int wcn36xx_smd_rsp_status_check_v2(struct wcn36xx *wcn, void *buf,
+ size_t len)
+{
+ struct wcn36xx_fw_msg_status_rsp_v2 *rsp;
+
+ if (wcn->chip_version != WCN36XX_CHIP_3620 ||
+ len < sizeof(struct wcn36xx_hal_msg_header) + sizeof(*rsp))
+ return wcn36xx_smd_rsp_status_check(buf, len);
+
+ rsp = buf + sizeof(struct wcn36xx_hal_msg_header);
+
+ if (WCN36XX_FW_MSG_RESULT_SUCCESS != rsp->status)
+ return rsp->status;
+
+ return 0;
+}
+
int wcn36xx_smd_load_nv(struct wcn36xx *wcn)
{
struct nv_data *nv_d;
@@ -1582,7 +1599,8 @@ int wcn36xx_smd_remove_bsskey(struct wcn36xx *wcn,
wcn36xx_err("Sending hal_remove_bsskey failed\n");
goto out;
}
- ret = wcn36xx_smd_rsp_status_check(wcn->hal_buf, wcn->hal_rsp_len);
+ ret = wcn36xx_smd_rsp_status_check_v2(wcn, wcn->hal_buf,
+ wcn->hal_rsp_len);
if (ret) {
wcn36xx_err("hal_remove_bsskey response failed err=%d\n", ret);
goto out;
@@ -1951,7 +1969,8 @@ int wcn36xx_smd_trigger_ba(struct wcn36xx *wcn, u8 sta_index)
wcn36xx_err("Sending hal_trigger_ba failed\n");
goto out;
}
- ret = wcn36xx_smd_rsp_status_check(wcn->hal_buf, wcn->hal_rsp_len);
+ ret = wcn36xx_smd_rsp_status_check_v2(wcn, wcn->hal_buf,
+ wcn->hal_rsp_len);
if (ret) {
wcn36xx_err("hal_trigger_ba response failed err=%d\n", ret);
goto out;
@@ -2082,6 +2101,7 @@ out:
mutex_unlock(&wcn->hal_mutex);
return ret;
}
+
static void wcn36xx_smd_rsp_process(struct wcn36xx *wcn, void *buf, size_t len)
{
struct wcn36xx_hal_msg_header *msg_header = buf;
@@ -2128,6 +2148,10 @@ static void wcn36xx_smd_rsp_process(struct wcn36xx *wcn, void *buf, size_t len)
complete(&wcn->hal_rsp_compl);
break;
+ case WCN36XX_HAL_DEL_BA_IND:
+ case WCN36XX_HAL_PRINT_REG_INFO_IND:
+ case WCN36XX_HAL_COEX_IND:
+ case WCN36XX_HAL_AVOID_FREQ_RANGE_IND:
case WCN36XX_HAL_OTA_TX_COMPL_IND:
case WCN36XX_HAL_MISSED_BEACON_IND:
case WCN36XX_HAL_DELETE_STA_CONTEXT_IND:
@@ -2174,6 +2198,11 @@ static void wcn36xx_ind_smd_work(struct work_struct *work)
msg_header = (struct wcn36xx_hal_msg_header *)hal_ind_msg->msg;
switch (msg_header->msg_type) {
+ case WCN36XX_HAL_DEL_BA_IND:
+ case WCN36XX_HAL_PRINT_REG_INFO_IND:
+ case WCN36XX_HAL_COEX_IND:
+ case WCN36XX_HAL_AVOID_FREQ_RANGE_IND:
+ break;
case WCN36XX_HAL_OTA_TX_COMPL_IND:
wcn36xx_smd_tx_compl_ind(wcn,
hal_ind_msg->msg,
@@ -2227,7 +2256,7 @@ out:
void wcn36xx_smd_close(struct wcn36xx *wcn)
{
- wcn->ctrl_ops->close();
+ wcn->ctrl_ops->close(wcn);
destroy_workqueue(wcn->hal_ind_wq);
mutex_destroy(&wcn->hal_ind_mutex);
}
diff --git a/drivers/net/wireless/ath/wcn36xx/smd.h b/drivers/net/wireless/ath/wcn36xx/smd.h
index 008d03423dbf..8361f9e3995b 100644
--- a/drivers/net/wireless/ath/wcn36xx/smd.h
+++ b/drivers/net/wireless/ath/wcn36xx/smd.h
@@ -44,6 +44,15 @@ struct wcn36xx_fw_msg_status_rsp {
u32 status;
} __packed;
+/* wcn3620 returns this for tigger_ba */
+
+struct wcn36xx_fw_msg_status_rsp_v2 {
+ u8 bss_id[6];
+ u32 status __packed;
+ u16 count_following_candidates __packed;
+ /* candidate list follows */
+};
+
struct wcn36xx_hal_ind_msg {
struct list_head list;
u8 *msg;
diff --git a/drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c b/drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c
new file mode 100644
index 000000000000..647f623f1985
--- /dev/null
+++ b/drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2013 Eugene Krasnikov <k.eugene.e@gmail.com>
+ * Copyright (c) 2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/completion.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <linux/remoteproc.h>
+#include <linux/soc/qcom/smd.h>
+#include "wcn36xx.h"
+#include "wcnss_core.h"
+
+#define MAC_ADDR_0 "wlan/macaddr0"
+
+struct smd_packet_item {
+ struct list_head list;
+ void *buf;
+ size_t count;
+};
+
+static int wcn36xx_msm_smsm_change_state(u32 clear_mask, u32 set_mask)
+{
+ return 0;
+}
+
+static int wcn36xx_msm_get_hw_mac(struct wcn36xx *wcn, u8 *addr)
+{
+ const struct firmware *addr_file = NULL;
+ int status;
+ u8 tmp[18];
+ static const u8 qcom_oui[3] = {0x00, 0x0A, 0xF5};
+ static const char *files = {MAC_ADDR_0};
+ struct wcn36xx_platform_data *pdata = wcn->wcn36xx_data;
+
+ status = request_firmware(&addr_file, files, &pdata->core->dev);
+
+ if (status < 0) {
+ /* Assign a random mac with Qualcomm oui */
+ dev_err(&pdata->core->dev, "Failed (%d) to read macaddress"
+ "file %s, using a random address instead", status, files);
+ memcpy(addr, qcom_oui, 3);
+ get_random_bytes(addr + 3, 3);
+ } else {
+ memset(tmp, 0, sizeof(tmp));
+ memcpy(tmp, addr_file->data, sizeof(tmp) - 1);
+ sscanf(tmp, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ &addr[0],
+ &addr[1],
+ &addr[2],
+ &addr[3],
+ &addr[4],
+ &addr[5]);
+
+ release_firmware(addr_file);
+ }
+
+ return 0;
+}
+
+static int wcn36xx_msm_smd_send_and_wait(struct wcn36xx *wcn, char *buf, size_t len)
+{
+ int ret = 0;
+ struct wcn36xx_platform_data *pdata = wcn->wcn36xx_data;
+
+ mutex_lock(&pdata->wlan_ctrl_lock);
+ ret = qcom_smd_send(pdata->wlan_ctrl_channel, buf, len);
+ if (ret) {
+ dev_err(wcn->dev, "wlan ctrl channel tx failed\n");
+ }
+ mutex_unlock(&pdata->wlan_ctrl_lock);
+
+ return ret;
+}
+
+static int wcn36xx_msm_smd_open(struct wcn36xx *wcn, void *rsp_cb)
+{
+ struct wcn36xx_platform_data *pdata = wcn->wcn36xx_data;
+
+ pdata->cb = rsp_cb;
+ return 0;
+}
+
+static void wcn36xx_msm_smd_close(struct wcn36xx *wcn)
+{
+ return;
+}
+
+static int wcn36xx_msm_get_chip_type(struct wcn36xx *wcn)
+{
+ struct wcn36xx_platform_data *pdata = wcn->wcn36xx_data;
+ return pdata->chip_type;
+}
+
+static struct wcn36xx_platform_data wcn36xx_data = {
+ .ctrl_ops = {
+ .open = wcn36xx_msm_smd_open,
+ .close = wcn36xx_msm_smd_close,
+ .tx = wcn36xx_msm_smd_send_and_wait,
+ .get_hw_mac = wcn36xx_msm_get_hw_mac,
+ .smsm_change_state = wcn36xx_msm_smsm_change_state,
+ .get_chip_type = wcn36xx_msm_get_chip_type,
+ },
+};
+
+static void wlan_ctrl_smd_process(struct work_struct *worker)
+{
+ unsigned long flags;
+ struct wcn36xx_platform_data *pdata =
+ container_of(worker,
+ struct wcn36xx_platform_data, packet_process_work);
+
+ spin_lock_irqsave(&pdata->packet_lock, flags);
+ while (!list_empty(&pdata->packet_list)) {
+ struct smd_packet_item *packet;
+
+ packet = list_first_entry(&pdata->packet_list,
+ struct smd_packet_item, list);
+ list_del(&packet->list);
+ spin_unlock_irqrestore(&pdata->packet_lock, flags);
+ pdata->cb(pdata->wcn, packet->buf, packet->count);
+ kfree(packet->buf);
+ spin_lock_irqsave(&pdata->packet_lock, flags);
+ }
+ spin_unlock_irqrestore(&pdata->packet_lock, flags);
+}
+
+static int qcom_smd_wlan_ctrl_probe(struct qcom_smd_device *sdev)
+{
+ pr_info("%s: enter\n", __func__);
+ mutex_init(&wcn36xx_data.wlan_ctrl_lock);
+ init_completion(&wcn36xx_data.wlan_ctrl_ack);
+
+ wcn36xx_data.sdev = sdev;
+ spin_lock_init(&wcn36xx_data.packet_lock);
+ INIT_LIST_HEAD(&wcn36xx_data.packet_list);
+ INIT_WORK(&wcn36xx_data.packet_process_work, wlan_ctrl_smd_process);
+
+ dev_set_drvdata(&sdev->dev, &wcn36xx_data);
+ wcn36xx_data.wlan_ctrl_channel = sdev->channel;
+
+ of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev);
+
+ return 0;
+}
+
+static void qcom_smd_wlan_ctrl_remove(struct qcom_smd_device *sdev)
+{
+ of_platform_depopulate(&sdev->dev);
+}
+
+static int qcom_smd_wlan_ctrl_callback(struct qcom_smd_device *qsdev,
+ const void *data,
+ size_t count)
+{
+ unsigned long flags;
+ struct smd_packet_item *packet = NULL;
+ struct wcn36xx_platform_data *pdata = dev_get_drvdata(&qsdev->dev);
+ void *buf = kzalloc(count + sizeof(struct smd_packet_item),
+ GFP_ATOMIC);
+ if (!buf) {
+ dev_err(&pdata->core->dev, "can't allocate buffer\n");
+ return -ENOMEM;
+ }
+
+ memcpy_fromio(buf, data, count);
+ packet = buf + count;
+ packet->buf = buf;
+ packet->count = count;
+
+ spin_lock_irqsave(&pdata->packet_lock, flags);
+ list_add_tail(&packet->list, &pdata->packet_list);
+ spin_unlock_irqrestore(&pdata->packet_lock, flags);
+ schedule_work(&pdata->packet_process_work);
+
+ /* buf will be freed in workqueue */
+
+ return 0;
+}
+
+static const struct qcom_smd_id qcom_smd_wlan_ctrl_match[] = {
+ { .name = "WLAN_CTRL" },
+ {}
+};
+
+static struct qcom_smd_driver qcom_smd_wlan_ctrl_driver = {
+ .probe = qcom_smd_wlan_ctrl_probe,
+ .remove = qcom_smd_wlan_ctrl_remove,
+ .callback = qcom_smd_wlan_ctrl_callback,
+ .smd_match_table = qcom_smd_wlan_ctrl_match,
+ .driver = {
+ .name = "qcom_smd_wlan_ctrl",
+ .owner = THIS_MODULE,
+ },
+};
+
+static const struct of_device_id wcn36xx_msm_match_table[] = {
+ { .compatible = "qcom,wcn3660", .data = (void *)WCN36XX_CHIP_3660 },
+ { .compatible = "qcom,wcn3680", .data = (void *)WCN36XX_CHIP_3680 },
+ { .compatible = "qcom,wcn3620", .data = (void *)WCN36XX_CHIP_3620 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, wcn36xx_msm_match_table);
+
+static int wcn36xx_msm_probe(struct platform_device *pdev)
+{
+ int ret;
+ const struct of_device_id *of_id;
+ struct resource *r;
+ struct resource res[3];
+ static const char const *rnames[] = {
+ "wcnss_mmio", "wcnss_wlantx_irq", "wcnss_wlanrx_irq" };
+ static const int rtype[] = {
+ IORESOURCE_MEM, IORESOURCE_IRQ, IORESOURCE_IRQ };
+ struct device_node *dn;
+ int n;
+
+ wcnss_core_prepare(pdev);
+
+ dn = of_parse_phandle(pdev->dev.of_node, "rproc", 0);
+ if (!dn) {
+ dev_err(&pdev->dev, "No rproc specified in DT\n");
+ } else {
+ struct rproc *rproc= rproc_get_by_phandle(dn->phandle);
+ if (rproc)
+ rproc_boot(rproc);
+ else {
+ dev_err(&pdev->dev, "No rproc registered\n");
+ }
+ }
+
+ qcom_smd_driver_register(&qcom_smd_wlan_ctrl_driver);
+ wcnss_core_init();
+
+ of_id = of_match_node(wcn36xx_msm_match_table, pdev->dev.of_node);
+ if (!of_id)
+ return -EINVAL;
+
+ wcn36xx_data.chip_type = (enum wcn36xx_chip_type)of_id->data;
+
+ wcn36xx_data.core = platform_device_alloc("wcn36xx", -1);
+
+ for (n = 0; n < ARRAY_SIZE(rnames); n++) {
+ r = platform_get_resource_byname(pdev, rtype[n], rnames[n]);
+ if (!r) {
+ dev_err(&wcn36xx_data.core->dev,
+ "Missing resource %s'\n", rnames[n]);
+ ret = -ENOMEM;
+ return ret;
+ }
+ res[n] = *r;
+ }
+
+ platform_device_add_resources(wcn36xx_data.core, res, n);
+ wcn36xx_data.core->dev.platform_data = &wcn36xx_data;
+
+ platform_device_add(wcn36xx_data.core);
+
+ dev_info(&pdev->dev, "%s initialized\n", __func__);
+
+ return 0;
+}
+
+static int wcn36xx_msm_remove(struct platform_device *pdev)
+{
+ platform_device_del(wcn36xx_data.core);
+ platform_device_put(wcn36xx_data.core);
+ return 0;
+}
+
+static struct platform_driver wcn36xx_msm_driver = {
+ .probe = wcn36xx_msm_probe,
+ .remove = wcn36xx_msm_remove,
+ .driver = {
+ .name = "wcn36xx-msm",
+ .owner = THIS_MODULE,
+ .of_match_table = wcn36xx_msm_match_table,
+ },
+};
+
+static int __init wcn36xx_msm_init(void)
+{
+ return platform_driver_register(&wcn36xx_msm_driver);
+}
+module_init(wcn36xx_msm_init);
+
+static void __exit wcn36xx_msm_exit(void)
+{
+ platform_driver_unregister(&wcn36xx_msm_driver);
+}
+module_exit(wcn36xx_msm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Eugene Krasnikov k.eugene.e@gmail.com");
+MODULE_FIRMWARE(MAC_ADDR_0);
+
diff --git a/drivers/net/wireless/ath/wcn36xx/wcn36xx.h b/drivers/net/wireless/ath/wcn36xx/wcn36xx.h
index 7b41e833e18c..f88cf7e8715b 100644
--- a/drivers/net/wireless/ath/wcn36xx/wcn36xx.h
+++ b/drivers/net/wireless/ath/wcn36xx/wcn36xx.h
@@ -103,19 +103,49 @@ struct nv_data {
u8 table;
};
+enum wcn36xx_chip_type {
+ WCN36XX_CHIP_UNKNOWN,
+ WCN36XX_CHIP_3660,
+ WCN36XX_CHIP_3680,
+ WCN36XX_CHIP_3620,
+};
+
/* Interface for platform control path
*
* @open: hook must be called when wcn36xx wants to open control channel.
* @tx: sends a buffer.
*/
struct wcn36xx_platform_ctrl_ops {
- int (*open)(void *drv_priv, void *rsp_cb);
- void (*close)(void);
- int (*tx)(char *buf, size_t len);
- int (*get_hw_mac)(u8 *addr);
+ int (*open)(struct wcn36xx *wcn, void *rsp_cb);
+ void (*close)(struct wcn36xx *wcn);
+ int (*tx)(struct wcn36xx *wcn, char *buf, size_t len);
+ int (*get_hw_mac)(struct wcn36xx *wcn, u8 *addr);
+ int (*get_chip_type)(struct wcn36xx *wcn);
int (*smsm_change_state)(u32 clear_mask, u32 set_mask);
};
+struct wcn36xx_platform_data {
+ enum wcn36xx_chip_type chip_type;
+
+ struct platform_device *core;
+
+ struct qcom_smd_device *sdev;
+ struct qcom_smd_channel *wlan_ctrl_channel;
+ struct completion wlan_ctrl_ack;
+ struct mutex wlan_ctrl_lock;
+
+ struct pinctrl *pinctrl;
+
+ struct wcn36xx *wcn;
+
+ void (*cb)(struct wcn36xx *wcn, void *buf, size_t len);
+ struct wcn36xx_platform_ctrl_ops ctrl_ops;
+
+ struct work_struct packet_process_work;
+ spinlock_t packet_lock;
+ struct list_head packet_list;
+};
+
/**
* struct wcn36xx_vif - holds VIF related fields
*
@@ -193,7 +223,7 @@ struct wcn36xx {
u8 fw_minor;
u8 fw_major;
u32 fw_feat_caps[WCN36XX_HAL_CAPS_SIZE];
- u32 chip_version;
+ enum wcn36xx_chip_type chip_version;
/* extra byte for the NULL termination */
u8 crm_version[WCN36XX_HAL_VERSION_LENGTH + 1];
@@ -204,6 +234,7 @@ struct wcn36xx {
int rx_irq;
void __iomem *mmio;
+ struct wcn36xx_platform_data *wcn36xx_data;
struct wcn36xx_platform_ctrl_ops *ctrl_ops;
/*
* smd_buf must be protected with smd_mutex to garantee
@@ -241,9 +272,6 @@ struct wcn36xx {
};
-#define WCN36XX_CHIP_3660 0
-#define WCN36XX_CHIP_3680 1
-
static inline bool wcn36xx_is_fw_version(struct wcn36xx *wcn,
u8 major,
u8 minor,
diff --git a/drivers/net/wireless/ath/wcn36xx/wcnss_core.c b/drivers/net/wireless/ath/wcn36xx/wcnss_core.c
new file mode 100644
index 000000000000..e7d389e87886
--- /dev/null
+++ b/drivers/net/wireless/ath/wcn36xx/wcnss_core.c
@@ -0,0 +1,295 @@
+#include <linux/completion.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <linux/soc/qcom/smd.h>
+#include "wcn36xx.h"
+#include "wcnss_core.h"
+
+#define WCNSS_CTRL_TIMEOUT (msecs_to_jiffies(500))
+
+static int wcnss_core_config(struct platform_device *pdev, void __iomem *base)
+{
+ int ret = 0;
+ u32 value, iris_read_v = INVALID_IRIS_REG;
+ int clk_48m = 0;
+
+ value = readl_relaxed(base + SPARE_OFFSET);
+ value |= WCNSS_FW_DOWNLOAD_ENABLE;
+ writel_relaxed(value, base + SPARE_OFFSET);
+
+ writel_relaxed(0, base + PMU_OFFSET);
+ value = readl_relaxed(base + PMU_OFFSET);
+ value |= WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP |
+ WCNSS_PMU_CFG_IRIS_XO_EN;
+ writel_relaxed(value, base + PMU_OFFSET);
+
+ iris_read_v = readl_relaxed(base + IRIS_REG_OFFSET);
+ pr_info("iris_read_v: 0x%x\n", iris_read_v);
+
+ iris_read_v &= 0xffff;
+ iris_read_v |= 0x04;
+ writel_relaxed(iris_read_v, base + IRIS_REG_OFFSET);
+
+ value = readl_relaxed(base + PMU_OFFSET);
+ value |= WCNSS_PMU_CFG_IRIS_XO_READ;
+ writel_relaxed(value, base + PMU_OFFSET);
+
+ while (readl_relaxed(base + PMU_OFFSET) &
+ WCNSS_PMU_CFG_IRIS_XO_READ_STS)
+ cpu_relax();
+
+ iris_read_v = readl_relaxed(base + 0x1134);
+ pr_info("wcnss: IRIS Reg: 0x%08x\n", iris_read_v);
+ clk_48m = (iris_read_v >> 30) ? 0 : 1;
+ value &= ~WCNSS_PMU_CFG_IRIS_XO_READ;
+
+ /* XO_MODE[b2:b1]. Clear implies 19.2MHz */
+ value &= ~WCNSS_PMU_CFG_IRIS_XO_MODE;
+
+ if (clk_48m)
+ value |= WCNSS_PMU_CFG_IRIS_XO_MODE_48;
+
+ writel_relaxed(value, base + PMU_OFFSET);
+
+ /* Reset IRIS */
+ value |= WCNSS_PMU_CFG_IRIS_RESET;
+ writel_relaxed(value, base + PMU_OFFSET);
+
+ while (readl_relaxed(base + PMU_OFFSET) &
+ WCNSS_PMU_CFG_IRIS_RESET_STS)
+ cpu_relax();
+
+ /* reset IRIS reset bit */
+ value &= ~WCNSS_PMU_CFG_IRIS_RESET;
+ writel_relaxed(value, base + PMU_OFFSET);
+
+ /* start IRIS XO configuration */
+ value |= WCNSS_PMU_CFG_IRIS_XO_CFG;
+ writel_relaxed(value, base + PMU_OFFSET);
+
+ /* Wait for XO configuration to finish */
+ while (readl_relaxed(base + PMU_OFFSET) &
+ WCNSS_PMU_CFG_IRIS_XO_CFG_STS)
+ cpu_relax();
+
+ /* Stop IRIS XO configuration */
+ value &= ~(WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP |
+ WCNSS_PMU_CFG_IRIS_XO_CFG);
+ writel_relaxed(value, base + PMU_OFFSET);
+
+ msleep(200);
+
+ return ret;
+}
+
+int wcnss_core_prepare(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct resource *res;
+ void __iomem *wcnss_base;
+
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, "pronto_phy_base");
+ if (!res) {
+ ret = -EIO;
+ dev_err(&pdev->dev, "resource pronto_phy_base failed\n");
+ return ret;
+ }
+
+ wcnss_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(wcnss_base)) {
+ dev_err(&pdev->dev, "pronto_phy_base map failed\n");
+ return PTR_ERR(wcnss_base);
+ }
+
+ ret = wcnss_core_config(pdev, wcnss_base);
+ return ret;
+}
+
+struct wcnss_platform_data {
+ struct qcom_smd_channel *channel;
+ struct completion ack;
+ struct mutex lock;
+
+ struct work_struct rx_work;
+ struct work_struct download_work;
+
+ struct qcom_smd_device *sdev;
+};
+
+static struct completion fw_ready_compl;
+#define NV_FILE_NAME "wlan/prima/WCNSS_qcom_wlan_nv.bin"
+static void wcn36xx_nv_download_work(struct work_struct *worker)
+{
+ int ret = 0, i;
+ const struct firmware *nv = NULL;
+ struct wcnss_platform_data *wcnss_data =
+ container_of(worker, struct wcnss_platform_data, download_work);
+ struct device *dev = &wcnss_data->sdev->dev;
+
+ struct nvbin_dnld_req_msg *msg;
+ const void *nv_blob_start;
+ char *pkt = NULL;
+ int nv_blob_size = 0, fragments;
+
+ ret = request_firmware(&nv, NV_FILE_NAME, dev);
+ if (ret || !nv || !nv->data || !nv->size) {
+ dev_err(dev, "request firmware for %s (ret = %d)\n",
+ NV_FILE_NAME, ret);
+ return;
+ }
+
+ nv_blob_start = nv->data + 4;
+ nv_blob_size = nv->size -4;
+
+ fragments = (nv_blob_size + NV_FRAGMENT_SIZE - 1)/NV_FRAGMENT_SIZE;
+
+ pkt = kzalloc(sizeof(struct nvbin_dnld_req_msg) + NV_FRAGMENT_SIZE,
+ GFP_KERNEL);
+ if (!pkt) {
+ dev_err(dev, "allocation packet for nv download failed\n");
+ release_firmware(nv);
+ }
+
+ msg = (struct nvbin_dnld_req_msg *)pkt;
+ msg->hdr.msg_type = WCNSS_NV_DOWNLOAD_REQ;
+ msg->dnld_req_params.msg_flags = 0;
+
+ i = 0;
+ do {
+ int pkt_len = 0;
+
+ msg->dnld_req_params.frag_number = i;
+ if (nv_blob_size > NV_FRAGMENT_SIZE) {
+ msg->dnld_req_params.msg_flags &=
+ ~LAST_FRAGMENT;
+ pkt_len = NV_FRAGMENT_SIZE;
+ } else {
+ pkt_len = nv_blob_size;
+ msg->dnld_req_params.msg_flags |=
+ LAST_FRAGMENT | CAN_RECEIVE_CALDATA;
+ }
+
+ msg->dnld_req_params.nvbin_buffer_size = pkt_len;
+ msg->hdr.msg_len =
+ sizeof(struct nvbin_dnld_req_msg) + pkt_len;
+
+ memcpy(pkt + sizeof(struct nvbin_dnld_req_msg),
+ nv_blob_start + i * NV_FRAGMENT_SIZE, pkt_len);
+
+ ret = qcom_smd_send(wcnss_data->channel, pkt, msg->hdr.msg_len);
+ if (ret) {
+ dev_err(dev, "nv download failed\n");
+ goto out;
+ }
+
+ i++;
+ nv_blob_size -= NV_FRAGMENT_SIZE;
+ msleep(100);
+ } while (nv_blob_size > 0);
+
+out:
+ kfree(pkt);
+ release_firmware(nv);
+ return;
+}
+
+static int qcom_smd_wcnss_ctrl_callback(struct qcom_smd_device *qsdev,
+ const void *data,
+ size_t count)
+{
+ struct wcnss_platform_data *wcnss_data = dev_get_drvdata(&qsdev->dev);
+ struct smd_msg_hdr phdr;
+ const unsigned char *tmp = data;
+
+ memcpy_fromio(&phdr, data, sizeof(struct smd_msg_hdr));
+
+ switch (phdr.msg_type) {
+ /* CBC COMPLETE means firmware ready for go */
+ case WCNSS_CBC_COMPLETE_IND:
+ complete(&fw_ready_compl);
+ pr_info("wcnss: received WCNSS_CBC_COMPLETE_IND from FW\n");
+ break;
+
+ case WCNSS_NV_DOWNLOAD_RSP:
+ pr_info("fw_status: %d\n", tmp[sizeof(struct smd_msg_hdr)]);
+ break;
+ }
+
+ complete(&wcnss_data->ack);
+ return 0;
+}
+
+static int qcom_smd_wcnss_ctrl_probe(struct qcom_smd_device *sdev)
+{
+ struct wcnss_platform_data *wcnss_data;
+
+ wcnss_data = devm_kzalloc(&sdev->dev, sizeof(*wcnss_data), GFP_KERNEL);
+ if (!wcnss_data)
+ return -ENOMEM;
+
+ mutex_init(&wcnss_data->lock);
+ init_completion(&wcnss_data->ack);
+
+ wcnss_data->sdev = sdev;
+
+ dev_set_drvdata(&sdev->dev, wcnss_data);
+ wcnss_data->channel = sdev->channel;
+
+ INIT_WORK(&wcnss_data->download_work, wcn36xx_nv_download_work);
+
+ of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev);
+
+ /* We are ready for download here */
+ schedule_work(&wcnss_data->download_work);
+ return 0;
+}
+
+static void qcom_smd_wcnss_ctrl_remove(struct qcom_smd_device *sdev)
+{
+ of_platform_depopulate(&sdev->dev);
+}
+
+static const struct qcom_smd_id qcom_smd_wcnss_ctrl_match[] = {
+ { .name = "WCNSS_CTRL" },
+ {}
+};
+
+static struct qcom_smd_driver qcom_smd_wcnss_ctrl_driver = {
+ .probe = qcom_smd_wcnss_ctrl_probe,
+ .remove = qcom_smd_wcnss_ctrl_remove,
+ .callback = qcom_smd_wcnss_ctrl_callback,
+ .smd_match_table = qcom_smd_wcnss_ctrl_match,
+ .driver = {
+ .name = "qcom_smd_wcnss_ctrl",
+ .owner = THIS_MODULE,
+ },
+};
+
+void wcnss_core_init(void)
+{
+ int ret = 0;
+
+ init_completion(&fw_ready_compl);
+ qcom_smd_driver_register(&qcom_smd_wcnss_ctrl_driver);
+
+ ret = wait_for_completion_interruptible_timeout(
+ &fw_ready_compl, msecs_to_jiffies(FW_READY_TIMEOUT));
+ if (ret <= 0) {
+ pr_err("timeout waiting for wcnss firmware ready indicator\n");
+ return;
+ }
+
+ return;
+}
+
+void wcnss_core_deinit(void)
+{
+ qcom_smd_driver_unregister(&qcom_smd_wcnss_ctrl_driver);
+}
diff --git a/drivers/net/wireless/ath/wcn36xx/wcnss_core.h b/drivers/net/wireless/ath/wcn36xx/wcnss_core.h
new file mode 100644
index 000000000000..49524c8dddfb
--- /dev/null
+++ b/drivers/net/wireless/ath/wcn36xx/wcnss_core.h
@@ -0,0 +1,99 @@
+#ifndef _WCNSS_CORE_H_
+#define _WCNSS_CORE_H_
+
+#define PMU_OFFSET 0x1004
+#define SPARE_OFFSET 0x1088
+#define IRIS_REG_OFFSET 0x1134
+
+#define INVALID_IRIS_REG 0xbaadbaad
+
+#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)
+#define WCNSS_PMU_CFG_IRIS_XO_CFG_STS BIT(6) /* 1: in progress, 0: done */
+
+#define WCNSS_PMU_CFG_IRIS_RESET BIT(7)
+#define WCNSS_PMU_CFG_IRIS_RESET_STS BIT(8) /* 1: in progress, 0: done */
+#define WCNSS_PMU_CFG_IRIS_XO_READ BIT(9)
+#define WCNSS_PMU_CFG_IRIS_XO_READ_STS BIT(10)
+#define WCNSS_FW_DOWNLOAD_ENABLE BIT(25)
+
+#define WCNSS_PMU_CFG_IRIS_XO_MODE 0x6
+#define WCNSS_PMU_CFG_IRIS_XO_MODE_48 (3 << 1)
+
+#define NV_DOWNLOAD_TIMEOUT 500
+#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
+
+
+#define WCNSS_NV_DOWNLOAD_REQ 0x01000002
+#define WCNSS_NV_DOWNLOAD_RSP 0x01000003
+#define WCNSS_CBC_COMPLETE_IND 0x0100000C
+
+/*time out 10s for the firmware status ready indicator */
+#define FW_READY_TIMEOUT (10000)
+
+
+struct smd_msg_hdr {
+ unsigned int msg_type;
+ unsigned int msg_len;
+};
+
+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;
+
+ /* 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 msg_flags;
+
+ /* 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;
+};
+
+struct wcnss_version {
+ struct smd_msg_hdr hdr;
+ unsigned char major;
+ unsigned char minor;
+ unsigned char version;
+ unsigned char revision;
+};
+
+
+int wcnss_core_prepare(struct platform_device *pdev);
+void wcnss_core_init(void);
+void wcnss_core_deinit(void);
+
+#endif
+