diff options
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/Makefile | 5 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/dxe.c | 63 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/dxe.h | 1 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/hal.h | 18 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/main.c | 53 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/smd.c | 37 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/smd.h | 9 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c | 314 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/wcn36xx.h | 44 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/wcnss_core.c | 295 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wcn36xx/wcnss_core.h | 99 |
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 + |