コード例 #1
0
void wcn36xx_dxe_tx_ack_ind(struct wcn36xx *wcn, u32 status)
{
    struct ieee80211_tx_info *info;
    struct sk_buff *skb;
    unsigned long flags;

    spin_lock_irqsave(&wcn->dxe_lock, flags);
    skb = wcn->tx_ack_skb;
    wcn->tx_ack_skb = NULL;
    spin_unlock_irqrestore(&wcn->dxe_lock, flags);

    if (!skb) {
        wcn36xx_warn("Spurious TX complete indication\n");
        return;
    }

    info = IEEE80211_SKB_CB(skb);

    if (status == 1)
        info->flags |= IEEE80211_TX_STAT_ACK;

    wcn36xx_dbg(WCN36XX_DBG_DXE, "dxe tx ack status: %d\n", status);

    ieee80211_tx_status_irqsafe(wcn->hw, skb);
    ieee80211_wake_queues(wcn->hw);
}
コード例 #2
0
ファイル: smd.c プロジェクト: kvalo/wcn36xx
static int wcn36xx_smd_config_bss_rsp(struct wcn36xx *wcn, void *buf, size_t len)
{
	struct wcn36xx_hal_config_bss_rsp_msg *rsp;
	struct wcn36xx_hal_config_bss_rsp_params *params;

	if (len < sizeof(*rsp))
		return -EINVAL;

	rsp = (struct wcn36xx_hal_config_bss_rsp_msg *)buf;
	params = &rsp->bss_rsp_params;

	if (params->status != WCN36XX_FW_MSG_RESULT_SUCCESS) {
		wcn36xx_warn("hal config bss response failure: %d",
			     params->status);
		return -EIO;
	}

	wcn36xx_dbg(WCN36XX_DBG_HAL,
		    "hal config bss rsp status %d bss_idx %d dpu_desc_index %d"
		    " sta_idx %d self_idx %d bcast_idx %d mac %pM power %d",
		    params->status, params->bss_index, params->dpu_desc_index,
		    params->bss_sta_index, params->bss_self_sta_index,
		    params->bss_bcast_sta_idx, params->mac,
		    params->tx_mgmt_power);

	wcn->current_vif->sta_index =  params->bss_sta_index;
	wcn->current_vif->dpu_desc_index = params->dpu_desc_index;
	return 0;
}
コード例 #3
0
ファイル: smd.c プロジェクト: kvalo/wcn36xx
int wcn36xx_smd_update_proberesp_tmpl(struct wcn36xx *wcn, struct sk_buff *skb)
{
	struct wcn36xx_hal_send_probe_resp_req_msg msg;

	INIT_HAL_MSG(msg, WCN36XX_HAL_UPDATE_PROBE_RSP_TEMPLATE_REQ);

	/* // TODO need to find out why this is needed? */
	/* msg_body.beacon_length = skb_beacon->len + 6; */

	if (skb->len > BEACON_TEMPLATE_SIZE) {
		wcn36xx_warn("probe response template is too big: %d",
			     skb->len);
		return -E2BIG;
	}

	msg.probe_resp_template_len = skb->len;
	memcpy(&msg.probe_resp_template, skb->data, skb->len);

	memcpy(&msg.bssid, &wcn->addresses[0], ETH_ALEN);

	PREPARE_HAL_BUF(wcn->smd_buf, msg);

	wcn36xx_dbg(WCN36XX_DBG_HAL,
		    "hal update probe rsp len %d bssid %pM",
		    msg.probe_resp_template_len, msg.bssid);

	return wcn36xx_smd_send_and_wait(wcn, msg.header.len);
};
コード例 #4
0
ファイル: smd.c プロジェクト: kvalo/wcn36xx
static int wcn36xx_smd_config_sta_rsp(struct wcn36xx *wcn, void *buf,
				      size_t len)
{
	struct wcn36xx_hal_config_sta_rsp_msg *rsp;
	struct config_sta_rsp_params *params;

	if (len < sizeof(*rsp))
		return -EINVAL;

	rsp = (struct wcn36xx_hal_config_sta_rsp_msg *)buf;
	params = &rsp->params;

	if (params->status != WCN36XX_FW_MSG_RESULT_SUCCESS) {
		wcn36xx_warn("hal config sta response failure: %d",
			     params->status);
		return -EIO;
	}

	wcn36xx_dbg(WCN36XX_DBG_HAL,
		    "hal config sta rsp status %d sta_index %d bssid_index %d p2p %d",
		    params->status, params->sta_index, params->bssid_index,
		    params->p2p);

	return 0;
}
コード例 #5
0
ファイル: dxe.c プロジェクト: dreamfly281/wcn36xx
void wcn36xx_rx_ready_work(struct work_struct *work)
{
	struct wcn36xx *wcn =
		container_of(work, struct wcn36xx, rx_ready_work);
	int int_src;

	wcn36xx_dxe_read_register(wcn, WCN36XX_DXE_INT_SRC_RAW_REG, &int_src);

	/* RX_LOW_PRI */
	if (int_src & WCN36XX_DXE_INT_CH1_MASK) {
		wcn36xx_dxe_write_register(wcn, WCN36XX_DXE_0_INT_CLR,
					   WCN36XX_DXE_INT_CH1_MASK);
		wcn36xx_rx_handle_packets(wcn, &(wcn->dxe_rx_l_ch));
	}

	/* RX_HIGH_PRI */
	if (int_src & WCN36XX_DXE_INT_CH3_MASK) {
		/* Clean up all the INT within this channel */
		wcn36xx_dxe_write_register(wcn, WCN36XX_DXE_0_INT_CLR,
					   WCN36XX_DXE_INT_CH3_MASK);
		wcn36xx_rx_handle_packets(wcn, &(wcn->dxe_rx_h_ch));
	}

	if (!int_src)
		wcn36xx_warn("No DXE interrupt pending");

	enable_irq(wcn->rx_irq);
}
コード例 #6
0
ファイル: smd.c プロジェクト: kvalo/wcn36xx
static int wcn36xx_smd_add_sta_self_rsp(struct wcn36xx *wcn,
					void *buf,
					size_t len)
{
	struct wcn36xx_hal_add_sta_self_rsp_msg *rsp;

	if (len < sizeof(*rsp))
		return -EINVAL;

	rsp = (struct wcn36xx_hal_add_sta_self_rsp_msg *)buf;

	if (rsp->status != WCN36XX_FW_MSG_RESULT_SUCCESS) {
		wcn36xx_warn("hal add sta self failure: %d",
			     rsp->status);
		return -EIO;
	}

	wcn36xx_dbg(WCN36XX_DBG_HAL, "hal add sta self status %d "
		    "self_sta_index %d dpu_index %d",
		    rsp->status, rsp->self_sta_index, rsp->dpu_index);
	wcn->current_vif->sta_index = rsp->self_sta_index;
	wcn->current_vif->dpu_desc_index = rsp->dpu_index;

	return 0;
}
コード例 #7
0
ファイル: dxe.c プロジェクト: dreamfly281/wcn36xx
static int wcn36xx_rx_handle_packets(struct wcn36xx *wcn,
				     struct wcn36xx_dxe_ch *ch)
{
	struct wcn36xx_dxe_ctl *ctl = ch->head_blk_ctl;
	struct wcn36xx_dxe_desc *dxe = ctl->desc;
	dma_addr_t  dma_addr;
	struct sk_buff *skb;

	while (!(dxe->ctrl & WCN36XX_DXE_CTRL_VALID_MASK)) {
		skb = ctl->skb;
		dma_addr = dxe->dst_addr_l;
		wcn36xx_dxe_fill_skb(ctl);

		switch (ch->ch_type) {
		case WCN36XX_DXE_CH_RX_L:
			dxe->ctrl = WCN36XX_DXE_CTRL_RX_L;
			break;
		case WCN36XX_DXE_CH_RX_H:
			dxe->ctrl = WCN36XX_DXE_CTRL_RX_H;
			break;
		default:
			wcn36xx_warn("Unknown channel");
		}

		dma_unmap_single(NULL, dma_addr, WCN36XX_PKT_SIZE,
				 DMA_FROM_DEVICE);
		wcn36xx_rx_skb(wcn, skb);
		ctl = ctl->next;
		dxe = ctl->desc;
	}
	ch->head_blk_ctl = ctl;

	return 0;
}
コード例 #8
0
ファイル: dxe.c プロジェクト: Lyude/linux
void wcn36xx_dxe_rx_frame(struct wcn36xx *wcn)
{
	int int_src;

	wcn36xx_dxe_read_register(wcn, WCN36XX_DXE_INT_SRC_RAW_REG, &int_src);

	/* RX_LOW_PRI */
	if (int_src & WCN36XX_DXE_INT_CH1_MASK)
		wcn36xx_rx_handle_packets(wcn, &wcn->dxe_rx_l_ch,
					  WCN36XX_DXE_CTRL_RX_L,
					  WCN36XX_DXE_INT_CH1_MASK,
					  WCN36XX_INT_MASK_CHAN_RX_L,
					  WCN36XX_DXE_CH_STATUS_REG_ADDR_RX_L);

	/* RX_HIGH_PRI */
	if (int_src & WCN36XX_DXE_INT_CH3_MASK)
		wcn36xx_rx_handle_packets(wcn, &wcn->dxe_rx_h_ch,
					  WCN36XX_DXE_CTRL_RX_H,
					  WCN36XX_DXE_INT_CH3_MASK,
					  WCN36XX_INT_MASK_CHAN_RX_H,
					  WCN36XX_DXE_CH_STATUS_REG_ADDR_RX_H);

	if (!int_src)
		wcn36xx_warn("No DXE interrupt pending\n");
}
コード例 #9
0
ファイル: smd.c プロジェクト: kvalo/wcn36xx
static void wcn36xx_smd_rsp_process(struct wcn36xx *wcn, void *buf, size_t len)
{
	struct wcn36xx_hal_msg_header *msg_header = buf;

	wcn36xx_dbg_dump(WCN36XX_DBG_SMD_DUMP, "SMD <<< ", buf, len);
	switch (msg_header->msg_type) {
	case WCN36XX_HAL_START_RSP:
		wcn36xx_smd_start_rsp(wcn, buf, len);
		break;
	case WCN36XX_HAL_CONFIG_STA_RSP:
		wcn36xx_smd_config_sta_rsp(wcn, buf, len);
		break;
	case WCN36XX_HAL_CONFIG_BSS_RSP:
		wcn36xx_smd_config_bss_rsp(wcn, buf, len);
		break;
	case WCN36XX_HAL_STOP_RSP:
	case WCN36XX_HAL_ADD_STA_SELF_RSP:
		wcn36xx_smd_add_sta_self_rsp(wcn, buf, len);
		break;
	case WCN36XX_HAL_DEL_STA_SELF_RSP:
	case WCN36XX_HAL_DELETE_STA_RSP:
	case WCN36XX_HAL_INIT_SCAN_RSP:
	case WCN36XX_HAL_START_SCAN_RSP:
	case WCN36XX_HAL_END_SCAN_RSP:
	case WCN36XX_HAL_FINISH_SCAN_RSP:
	case WCN36XX_HAL_DOWNLOAD_NV_RSP:
	case WCN36XX_HAL_DELETE_BSS_RSP:
	case WCN36XX_HAL_SEND_BEACON_RSP:
	case WCN36XX_HAL_SET_LINK_ST_RSP:
	case WCN36XX_HAL_UPDATE_PROBE_RSP_TEMPLATE_RSP:
	case WCN36XX_HAL_SET_BSSKEY_RSP:
	case WCN36XX_HAL_SET_STAKEY_RSP:
		if (wcn36xx_smd_rsp_status_check(buf, len)) {
			wcn36xx_warn("error response from hal request %d",
				     msg_header->msg_type);
		}
		break;
	case WCN36XX_HAL_JOIN_RSP:
		wcn36xx_smd_join_rsp(buf, len);
		break;
	case WCN36XX_HAL_UPDATE_SCAN_PARAM_RSP:
		wcn36xx_smd_update_scan_params_rsp(buf, len);
		break;
	case WCN36XX_HAL_CH_SWITCH_RSP:
		wcn36xx_smd_switch_channel_rsp(buf, len);
		break;
	case WCN36XX_HAL_OTA_TX_COMPL_IND:
		wcn36xx_smd_tx_compl_ind(wcn, buf, len);
		break;
	default:
		wcn36xx_error("SMD_EVENT (%d) not supported", msg_header->msg_type);
	}
}
コード例 #10
0
ファイル: txrx.c プロジェクト: kanstrup/wcn36xx
void wcn36xx_fill_tx_bd(struct wcn36xx *wcn, struct wcn36xx_tx_bd *bd,
			u8 broadcast, u8 encrypt, struct ieee80211_hdr *hdr,
			bool tx_compl)
{
	bd->dpu_rf = WCN36XX_BMU_WQ_TX;
	bd->pdu.tid   = WCN36XX_TID;
	bd->pdu.reserved3 = 0xd;

	if (broadcast) {
		/* broadcast */
		bd->ub = 1;
		bd->queue_id = WCN36XX_TX_B_WQ_ID;

		/* default rate for broadcast */
		if (ieee80211_is_mgmt(hdr->frame_control))
			bd->bd_rate = (wcn->band == IEEE80211_BAND_5GHZ) ?
				WCN36XX_BD_RATE_CTRL :
				WCN36XX_BD_RATE_MGMT;
		/* No ack needed not unicast */
		bd->ack_policy = 1;
	} else {
		bd->queue_id = WCN36XX_TX_U_WQ_ID;
		/* default rate for unicast */
		bd->ack_policy = 0;
		if (ieee80211_is_data(hdr->frame_control))
			bd->bd_rate = WCN36XX_BD_RATE_DATA;
		else if (ieee80211_is_mgmt(hdr->frame_control))
			bd->bd_rate = (wcn->band == IEEE80211_BAND_5GHZ) ?
				WCN36XX_BD_RATE_CTRL :
				WCN36XX_BD_RATE_MGMT;
		else if (ieee80211_is_ctl(hdr->frame_control))
			bd->bd_rate = WCN36XX_BD_RATE_CTRL;
		else
			wcn36xx_warn("frame control type unknown");
	}

	if (ieee80211_is_data(hdr->frame_control)) {
		bd->dpu_sign = wcn->current_vif->ucast_dpu_signature;
		bd->queue_id = 0;
		bd->sta_index = wcn->current_vif->sta_index;
		bd->dpu_desc_idx = wcn->current_vif->dpu_desc_index;
	} else {
		bd->sta_index = wcn->current_vif->self_sta_index;
		bd->dpu_desc_idx = wcn->current_vif->self_dpu_desc_index;
	}

	bd->dpu_ne = encrypt;
	bd->tx_comp = tx_compl;

	buff_to_be((u32 *)bd, sizeof(*bd)/sizeof(u32));
	bd->tx_bd_sign = 0xbdbdbdbd;
}
コード例 #11
0
ファイル: smd.c プロジェクト: kvalo/wcn36xx
static int wcn36xx_smd_tx_compl_ind(struct wcn36xx *wcn, void *buf, size_t len)
{
	struct wcn36xx_hal_tx_compl_ind_msg *rsp = buf;

	if (len != sizeof(*rsp)) {
		wcn36xx_warn("Bad TX complete indication");
		return -EIO;
	}

	wcn36xx_dxe_tx_ack_ind(wcn, rsp->status);

	return 0;
}
コード例 #12
0
ファイル: smd.c プロジェクト: kvalo/wcn36xx
static int wcn36xx_smd_update_scan_params_rsp(void *buf, size_t len)
{
	struct wcn36xx_hal_update_scan_params_resp *rsp;

	rsp = (struct wcn36xx_hal_update_scan_params_resp *)buf;

	/* Remove the PNO version bit */
	rsp->status &= (~(WCN36XX_FW_MSG_PNO_VERSION_MASK));

	if (WCN36XX_FW_MSG_RESULT_SUCCESS != rsp->status) {
		wcn36xx_warn("error response from update scan");
		return -EIO;
	}
	return 0;
}
コード例 #13
0
void wcn36xx_dxe_rx_frame(struct wcn36xx *wcn)
{
    int int_src;

    wcn36xx_dxe_read_register(wcn, WCN36XX_DXE_INT_SRC_RAW_REG, &int_src);

    /* RX_LOW_PRI */
    if (int_src & WCN36XX_DXE_INT_CH1_MASK) {
        wcn36xx_dxe_write_register(wcn, WCN36XX_DXE_0_INT_CLR,
                                   WCN36XX_DXE_INT_CH1_MASK);
        wcn36xx_rx_handle_packets(wcn, &(wcn->dxe_rx_l_ch));
    }

    /* RX_HIGH_PRI */
    if (int_src & WCN36XX_DXE_INT_CH3_MASK) {
        /* Clean up all the INT within this channel */
        wcn36xx_dxe_write_register(wcn, WCN36XX_DXE_0_INT_CLR,
                                   WCN36XX_DXE_INT_CH3_MASK);
        wcn36xx_rx_handle_packets(wcn, &(wcn->dxe_rx_h_ch));
    }

    if (!int_src)
        wcn36xx_warn("No DXE interrupt pending\n");
}
コード例 #14
0
ファイル: smd.c プロジェクト: kvalo/wcn36xx
int wcn36xx_smd_config_bss(struct wcn36xx *wcn, enum nl80211_iftype type,
			   const u8 *bssid, bool update,
			   u16 beacon_interval)
{
	struct wcn36xx_hal_config_bss_req_msg msg;
	struct wcn36xx_hal_config_bss_params *bss;
	struct wcn36xx_hal_config_sta_params *sta;

	INIT_HAL_MSG(msg, WCN36XX_HAL_CONFIG_BSS_REQ);

	bss = &msg.bss_params;
	sta = &bss->sta;

	WARN_ON(is_zero_ether_addr(bssid));

	memcpy(&bss->bssid, bssid, ETH_ALEN);

	memcpy(&bss->self_mac_addr, &wcn->addresses[0], ETH_ALEN);

	if (type == NL80211_IFTYPE_STATION) {
		bss->bss_type = WCN36XX_HAL_INFRASTRUCTURE_MODE;

		/* STA */
		bss->oper_mode = 1;

	} else if (type == NL80211_IFTYPE_AP) {
		bss->bss_type = WCN36XX_HAL_INFRA_AP_MODE;

		/* AP */
		bss->oper_mode = 0;
	} else if (type == NL80211_IFTYPE_ADHOC ||
		   type == NL80211_IFTYPE_MESH_POINT) {
		bss->bss_type = WCN36XX_HAL_IBSS_MODE;

		/* STA */
		bss->oper_mode = 1;
	} else {
		wcn36xx_warn("Unknown type for bss config: %d", type);
	}

	bss->nw_type = WCN36XX_HAL_11G_NW_TYPE;
	bss->short_slot_time_supported = 0;
	bss->lla_coexist = 0;
	bss->llb_coexist = 0;
	bss->llg_coexist = 0;
	bss->ht20_coexist = 0;
	bss->lln_non_gf_coexist = 0;
	bss->lsig_tx_op_protection_full_support = 0;
	bss->rifs_mode = 0;
	bss->beacon_interval = beacon_interval;
	bss->dtim_period = 2;
	bss->tx_channel_width_set = 0;
	bss->oper_channel = wcn->ch;
	bss->ext_channel = 0;
	bss->reserved = 0;

	memcpy(&sta->bssid, bssid, ETH_ALEN);

	sta->aid = wcn->aid;
	sta->type = 0;
	sta->short_preamble_supported = 0;
	memcpy(&sta->mac, &wcn->addresses[0], ETH_ALEN);
	sta->listen_interval = 8;
	sta->wmm_enabled = 0;
	sta->ht_capable = 0;
	sta->tx_channel_width_set = 0;
	sta->rifs_mode = 0;
	sta->lsig_txop_protection = 0;
	sta->max_ampdu_size = 0;
	sta->max_ampdu_density = 0;
	sta->sgi_40mhz = 0;
	sta->sgi_20Mhz = 0;

	memcpy(&sta->supported_rates, &wcn->supported_rates,
	       sizeof(wcn->supported_rates));

	sta->rmf = 0;
	sta->encrypt_type = 0;
	sta->action = 0;
	sta->uapsd = 0;
	sta->max_sp_len = 0;
	sta->green_field_capable = 0;
	sta->mimo_ps = 0;
	sta->delayed_ba_support = 0;
	sta->max_ampdu_duration = 0;
	sta->dsss_cck_mode_40mhz = 0;
	sta->sta_index = 0xff;
	sta->bssid_index = 0;
	sta->p2p = 0;

	/* wcn->ssid is only valid in AP and IBSS mode */
	bss->ssid.length = wcn->ssid.length;
	memcpy(bss->ssid.ssid, wcn->ssid.ssid, wcn->ssid.length);

	bss->action = 0;

	/* FIXME: set rateset */

	bss->ht = 0;
	bss->obss_prot_enabled = 0;
	bss->rmf = 0;
	bss->ht_oper_mode = 0;
	bss->dual_cts_protection = 0;
	bss->max_probe_resp_retry_limit = 0;
	bss->hidden_ssid = 0;
	bss->proxy_probe_resp = 0;
	bss->edca_params_valid = 0;

	/* FIXME: set acbe, acbk, acvi and acvo */

	bss->ext_set_sta_key_param_valid = 0;

	/* FIXME: set ext_set_sta_key_param */

	bss->wcn36xx_hal_persona = 1;
	bss->spectrum_mgt_enable = 0;
	bss->tx_mgmt_power = 0;
	bss->max_tx_power = 0x10;

	if (update) {
		sta->bssid_index = 0;
		bss->action = 1;
	} else {
		sta->bssid_index = 0xff;
		bss->action = 0;
	}
	if (!(wcn->fw_major == 1 &&
		wcn->fw_minor == 2 &&
		wcn->fw_version == 2 &&
		wcn->fw_revision == 24))
		return wcn36xx_smd_config_bss_v1(wcn, &msg);

	PREPARE_HAL_BUF(wcn->smd_buf, msg);

	wcn36xx_dbg(WCN36XX_DBG_HAL,
		    "hal config bss bssid %pM self_mac_addr %pM bss_type %d oper_mode %d nw_type %d",
		    bss->bssid, bss->self_mac_addr, bss->bss_type,
		    bss->oper_mode, bss->nw_type);

	wcn36xx_dbg(WCN36XX_DBG_HAL,
		    "- sta bssid %pM action %d sta_index %d bssid_index %d aid %d type %d mac %pM",
		    sta->bssid, sta->action, sta->sta_index,
		    sta->bssid_index, sta->aid, sta->type, sta->mac);

	return wcn36xx_smd_send_and_wait(wcn, msg.header.len);
}
コード例 #15
0
ファイル: main.c プロジェクト: Coi-l/wcn36xx
static int wcn36xx_start(struct ieee80211_hw *hw)
{
	struct wcn36xx *wcn = hw->priv;
	int ret;

	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac start");

	/* SMD initialization */
	ret = wcn36xx_smd_open(wcn);
	if (ret) {
		wcn36xx_error("Failed to open smd channel: %d", ret);
		goto out_err;
	}

	/* Not to receive INT until the whole buf from SMD is read */
	smd_disable_read_intr(wcn->smd_ch);

	/* Allocate memory pools for Mgmt BD headers and Data BD headers */
	ret = wcn36xx_dxe_allocate_mem_pools(wcn);
	if (ret) {
		wcn36xx_error("Failed to alloc DXE mempool: %d", ret);
		goto out_smd_close;
	}

	wcn36xx_dxe_alloc_ctl_blks(wcn);
	if (ret) {
		wcn36xx_error("Failed to alloc DXE ctl blocks: %d", ret);
		goto out_free_dxe_pool;
	}

	INIT_WORK(&wcn->rx_ready_work, wcn36xx_rx_ready_work);

	/* Maximum SMD message size is 4k */
	wcn->smd_buf = kmalloc(4096, GFP_KERNEL);
	if (!wcn->smd_buf) {
		wcn36xx_error("Failed to allocate smd buf");
		ret = -ENOMEM;
		goto out_free_dxe_ctl;
	}

	/* TODO pass configuration to FW */
	ret = wcn36xx_smd_load_nv(wcn);
	if (ret) {
		wcn36xx_error("Failed to push NV to chip");
		goto out_free_smd_buf;
	}

	ret = wcn36xx_smd_start(wcn);
	if (ret) {
		wcn36xx_error("Failed to start chip");
		goto out_free_smd_buf;
	}

	/* DMA channel initialization */
	ret = wcn36xx_dxe_init(wcn);
	if (ret) {
		wcn36xx_error("DXE init failed");
		goto out_smd_stop;
	}

	wcn36xx_pmc_init(wcn);
	wcn36xx_debugfs_init(wcn);

	if (!wcn36xx_is_fw_version(wcn, 1, 2, 2, 24)) {
		ret = wcn36xx_smd_feature_caps_exchange(wcn);
		if (ret)
			wcn36xx_warn("Exchange feature caps failed");
	}

	return 0;

out_smd_stop:
	wcn36xx_smd_stop(wcn);
out_free_smd_buf:
	kfree(wcn->smd_buf);
out_free_dxe_pool:
	wcn36xx_dxe_free_mem_pools(wcn);
out_free_dxe_ctl:
	wcn36xx_dxe_free_ctl_blks(wcn);
out_smd_close:
	wcn36xx_smd_close(wcn);
out_err:
	return ret;
}