18e84c258SEugene Krasnikov /*
28e84c258SEugene Krasnikov  * Copyright (c) 2013 Eugene Krasnikov <k.eugene.e@gmail.com>
38e84c258SEugene Krasnikov  *
48e84c258SEugene Krasnikov  * Permission to use, copy, modify, and/or distribute this software for any
58e84c258SEugene Krasnikov  * purpose with or without fee is hereby granted, provided that the above
68e84c258SEugene Krasnikov  * copyright notice and this permission notice appear in all copies.
78e84c258SEugene Krasnikov  *
88e84c258SEugene Krasnikov  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
98e84c258SEugene Krasnikov  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
108e84c258SEugene Krasnikov  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
118e84c258SEugene Krasnikov  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
128e84c258SEugene Krasnikov  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
138e84c258SEugene Krasnikov  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
148e84c258SEugene Krasnikov  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
158e84c258SEugene Krasnikov  */
168e84c258SEugene Krasnikov 
178e84c258SEugene Krasnikov #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
188e84c258SEugene Krasnikov 
198e84c258SEugene Krasnikov #include <linux/module.h>
204bda7fafSPontus Fuchs #include <linux/firmware.h>
218e84c258SEugene Krasnikov #include <linux/platform_device.h>
2205ddce49SBjorn Andersson #include <linux/of_address.h>
2305ddce49SBjorn Andersson #include <linux/of_device.h>
24f303a931SBjorn Andersson #include <linux/of_irq.h>
25f303a931SBjorn Andersson #include <linux/soc/qcom/smd.h>
26f303a931SBjorn Andersson #include <linux/soc/qcom/smem_state.h>
27f303a931SBjorn Andersson #include <linux/soc/qcom/wcnss_ctrl.h>
288e84c258SEugene Krasnikov #include "wcn36xx.h"
298e84c258SEugene Krasnikov 
308e84c258SEugene Krasnikov unsigned int wcn36xx_dbg_mask;
318e84c258SEugene Krasnikov module_param_named(debug_mask, wcn36xx_dbg_mask, uint, 0644);
328e84c258SEugene Krasnikov MODULE_PARM_DESC(debug_mask, "Debugging mask");
338e84c258SEugene Krasnikov 
348e84c258SEugene Krasnikov #define CHAN2G(_freq, _idx) { \
3557fbcce3SJohannes Berg 	.band = NL80211_BAND_2GHZ, \
368e84c258SEugene Krasnikov 	.center_freq = (_freq), \
378e84c258SEugene Krasnikov 	.hw_value = (_idx), \
388e84c258SEugene Krasnikov 	.max_power = 25, \
398e84c258SEugene Krasnikov }
408e84c258SEugene Krasnikov 
418e84c258SEugene Krasnikov #define CHAN5G(_freq, _idx) { \
4257fbcce3SJohannes Berg 	.band = NL80211_BAND_5GHZ, \
438e84c258SEugene Krasnikov 	.center_freq = (_freq), \
448e84c258SEugene Krasnikov 	.hw_value = (_idx), \
458e84c258SEugene Krasnikov 	.max_power = 25, \
468e84c258SEugene Krasnikov }
478e84c258SEugene Krasnikov 
488e84c258SEugene Krasnikov /* The wcn firmware expects channel values to matching
498e84c258SEugene Krasnikov  * their mnemonic values. So use these for .hw_value. */
508e84c258SEugene Krasnikov static struct ieee80211_channel wcn_2ghz_channels[] = {
518e84c258SEugene Krasnikov 	CHAN2G(2412, 1), /* Channel 1 */
528e84c258SEugene Krasnikov 	CHAN2G(2417, 2), /* Channel 2 */
538e84c258SEugene Krasnikov 	CHAN2G(2422, 3), /* Channel 3 */
548e84c258SEugene Krasnikov 	CHAN2G(2427, 4), /* Channel 4 */
558e84c258SEugene Krasnikov 	CHAN2G(2432, 5), /* Channel 5 */
568e84c258SEugene Krasnikov 	CHAN2G(2437, 6), /* Channel 6 */
578e84c258SEugene Krasnikov 	CHAN2G(2442, 7), /* Channel 7 */
588e84c258SEugene Krasnikov 	CHAN2G(2447, 8), /* Channel 8 */
598e84c258SEugene Krasnikov 	CHAN2G(2452, 9), /* Channel 9 */
608e84c258SEugene Krasnikov 	CHAN2G(2457, 10), /* Channel 10 */
618e84c258SEugene Krasnikov 	CHAN2G(2462, 11), /* Channel 11 */
628e84c258SEugene Krasnikov 	CHAN2G(2467, 12), /* Channel 12 */
638e84c258SEugene Krasnikov 	CHAN2G(2472, 13), /* Channel 13 */
648e84c258SEugene Krasnikov 	CHAN2G(2484, 14)  /* Channel 14 */
658e84c258SEugene Krasnikov 
668e84c258SEugene Krasnikov };
678e84c258SEugene Krasnikov 
688e84c258SEugene Krasnikov static struct ieee80211_channel wcn_5ghz_channels[] = {
698e84c258SEugene Krasnikov 	CHAN5G(5180, 36),
708e84c258SEugene Krasnikov 	CHAN5G(5200, 40),
718e84c258SEugene Krasnikov 	CHAN5G(5220, 44),
728e84c258SEugene Krasnikov 	CHAN5G(5240, 48),
738e84c258SEugene Krasnikov 	CHAN5G(5260, 52),
748e84c258SEugene Krasnikov 	CHAN5G(5280, 56),
758e84c258SEugene Krasnikov 	CHAN5G(5300, 60),
768e84c258SEugene Krasnikov 	CHAN5G(5320, 64),
778e84c258SEugene Krasnikov 	CHAN5G(5500, 100),
788e84c258SEugene Krasnikov 	CHAN5G(5520, 104),
798e84c258SEugene Krasnikov 	CHAN5G(5540, 108),
808e84c258SEugene Krasnikov 	CHAN5G(5560, 112),
818e84c258SEugene Krasnikov 	CHAN5G(5580, 116),
828e84c258SEugene Krasnikov 	CHAN5G(5600, 120),
838e84c258SEugene Krasnikov 	CHAN5G(5620, 124),
848e84c258SEugene Krasnikov 	CHAN5G(5640, 128),
858e84c258SEugene Krasnikov 	CHAN5G(5660, 132),
868e84c258SEugene Krasnikov 	CHAN5G(5700, 140),
878e84c258SEugene Krasnikov 	CHAN5G(5745, 149),
888e84c258SEugene Krasnikov 	CHAN5G(5765, 153),
898e84c258SEugene Krasnikov 	CHAN5G(5785, 157),
908e84c258SEugene Krasnikov 	CHAN5G(5805, 161),
918e84c258SEugene Krasnikov 	CHAN5G(5825, 165)
928e84c258SEugene Krasnikov };
938e84c258SEugene Krasnikov 
948e84c258SEugene Krasnikov #define RATE(_bitrate, _hw_rate, _flags) { \
958e84c258SEugene Krasnikov 	.bitrate        = (_bitrate),                   \
968e84c258SEugene Krasnikov 	.flags          = (_flags),                     \
978e84c258SEugene Krasnikov 	.hw_value       = (_hw_rate),                   \
988e84c258SEugene Krasnikov 	.hw_value_short = (_hw_rate)  \
998e84c258SEugene Krasnikov }
1008e84c258SEugene Krasnikov 
1018e84c258SEugene Krasnikov static struct ieee80211_rate wcn_2ghz_rates[] = {
1028e84c258SEugene Krasnikov 	RATE(10, HW_RATE_INDEX_1MBPS, 0),
1038e84c258SEugene Krasnikov 	RATE(20, HW_RATE_INDEX_2MBPS, IEEE80211_RATE_SHORT_PREAMBLE),
1048e84c258SEugene Krasnikov 	RATE(55, HW_RATE_INDEX_5_5MBPS, IEEE80211_RATE_SHORT_PREAMBLE),
1058e84c258SEugene Krasnikov 	RATE(110, HW_RATE_INDEX_11MBPS, IEEE80211_RATE_SHORT_PREAMBLE),
1068e84c258SEugene Krasnikov 	RATE(60, HW_RATE_INDEX_6MBPS, 0),
1078e84c258SEugene Krasnikov 	RATE(90, HW_RATE_INDEX_9MBPS, 0),
1088e84c258SEugene Krasnikov 	RATE(120, HW_RATE_INDEX_12MBPS, 0),
1098e84c258SEugene Krasnikov 	RATE(180, HW_RATE_INDEX_18MBPS, 0),
1108e84c258SEugene Krasnikov 	RATE(240, HW_RATE_INDEX_24MBPS, 0),
1118e84c258SEugene Krasnikov 	RATE(360, HW_RATE_INDEX_36MBPS, 0),
1128e84c258SEugene Krasnikov 	RATE(480, HW_RATE_INDEX_48MBPS, 0),
1138e84c258SEugene Krasnikov 	RATE(540, HW_RATE_INDEX_54MBPS, 0)
1148e84c258SEugene Krasnikov };
1158e84c258SEugene Krasnikov 
1168e84c258SEugene Krasnikov static struct ieee80211_rate wcn_5ghz_rates[] = {
1178e84c258SEugene Krasnikov 	RATE(60, HW_RATE_INDEX_6MBPS, 0),
1188e84c258SEugene Krasnikov 	RATE(90, HW_RATE_INDEX_9MBPS, 0),
1198e84c258SEugene Krasnikov 	RATE(120, HW_RATE_INDEX_12MBPS, 0),
1208e84c258SEugene Krasnikov 	RATE(180, HW_RATE_INDEX_18MBPS, 0),
1218e84c258SEugene Krasnikov 	RATE(240, HW_RATE_INDEX_24MBPS, 0),
1228e84c258SEugene Krasnikov 	RATE(360, HW_RATE_INDEX_36MBPS, 0),
1238e84c258SEugene Krasnikov 	RATE(480, HW_RATE_INDEX_48MBPS, 0),
1248e84c258SEugene Krasnikov 	RATE(540, HW_RATE_INDEX_54MBPS, 0)
1258e84c258SEugene Krasnikov };
1268e84c258SEugene Krasnikov 
1278e84c258SEugene Krasnikov static struct ieee80211_supported_band wcn_band_2ghz = {
1288e84c258SEugene Krasnikov 	.channels	= wcn_2ghz_channels,
1298e84c258SEugene Krasnikov 	.n_channels	= ARRAY_SIZE(wcn_2ghz_channels),
1308e84c258SEugene Krasnikov 	.bitrates	= wcn_2ghz_rates,
1318e84c258SEugene Krasnikov 	.n_bitrates	= ARRAY_SIZE(wcn_2ghz_rates),
1328e84c258SEugene Krasnikov 	.ht_cap		= {
1338e84c258SEugene Krasnikov 		.cap =	IEEE80211_HT_CAP_GRN_FLD |
1348e84c258SEugene Krasnikov 			IEEE80211_HT_CAP_SGI_20 |
1358e84c258SEugene Krasnikov 			IEEE80211_HT_CAP_DSSSCCK40 |
1368e84c258SEugene Krasnikov 			IEEE80211_HT_CAP_LSIG_TXOP_PROT,
1378e84c258SEugene Krasnikov 		.ht_supported = true,
1388e84c258SEugene Krasnikov 		.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
1398e84c258SEugene Krasnikov 		.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
1408e84c258SEugene Krasnikov 		.mcs = {
1418e84c258SEugene Krasnikov 			.rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
1428e84c258SEugene Krasnikov 			.rx_highest = cpu_to_le16(72),
1438e84c258SEugene Krasnikov 			.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
1448e84c258SEugene Krasnikov 		}
1458e84c258SEugene Krasnikov 	}
1468e84c258SEugene Krasnikov };
1478e84c258SEugene Krasnikov 
1488e84c258SEugene Krasnikov static struct ieee80211_supported_band wcn_band_5ghz = {
1498e84c258SEugene Krasnikov 	.channels	= wcn_5ghz_channels,
1508e84c258SEugene Krasnikov 	.n_channels	= ARRAY_SIZE(wcn_5ghz_channels),
1518e84c258SEugene Krasnikov 	.bitrates	= wcn_5ghz_rates,
1528e84c258SEugene Krasnikov 	.n_bitrates	= ARRAY_SIZE(wcn_5ghz_rates),
1538e84c258SEugene Krasnikov 	.ht_cap		= {
1548e84c258SEugene Krasnikov 		.cap =	IEEE80211_HT_CAP_GRN_FLD |
1558e84c258SEugene Krasnikov 			IEEE80211_HT_CAP_SGI_20 |
1568e84c258SEugene Krasnikov 			IEEE80211_HT_CAP_DSSSCCK40 |
1578e84c258SEugene Krasnikov 			IEEE80211_HT_CAP_LSIG_TXOP_PROT |
1588e84c258SEugene Krasnikov 			IEEE80211_HT_CAP_SGI_40 |
1598e84c258SEugene Krasnikov 			IEEE80211_HT_CAP_SUP_WIDTH_20_40,
1608e84c258SEugene Krasnikov 		.ht_supported = true,
1618e84c258SEugene Krasnikov 		.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
1628e84c258SEugene Krasnikov 		.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
1638e84c258SEugene Krasnikov 		.mcs = {
1648e84c258SEugene Krasnikov 			.rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
1658e84c258SEugene Krasnikov 			.rx_highest = cpu_to_le16(72),
1668e84c258SEugene Krasnikov 			.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
1678e84c258SEugene Krasnikov 		}
1688e84c258SEugene Krasnikov 	}
1698e84c258SEugene Krasnikov };
1708e84c258SEugene Krasnikov 
1718e84c258SEugene Krasnikov #ifdef CONFIG_PM
1728e84c258SEugene Krasnikov 
1738e84c258SEugene Krasnikov static const struct wiphy_wowlan_support wowlan_support = {
1748e84c258SEugene Krasnikov 	.flags = WIPHY_WOWLAN_ANY
1758e84c258SEugene Krasnikov };
1768e84c258SEugene Krasnikov 
1778e84c258SEugene Krasnikov #endif
1788e84c258SEugene Krasnikov 
1798e84c258SEugene Krasnikov static inline u8 get_sta_index(struct ieee80211_vif *vif,
1808e84c258SEugene Krasnikov 			       struct wcn36xx_sta *sta_priv)
1818e84c258SEugene Krasnikov {
1828e84c258SEugene Krasnikov 	return NL80211_IFTYPE_STATION == vif->type ?
1838e84c258SEugene Krasnikov 	       sta_priv->bss_sta_index :
1848e84c258SEugene Krasnikov 	       sta_priv->sta_index;
1858e84c258SEugene Krasnikov }
1868e84c258SEugene Krasnikov 
1872be6636aSPontus Fuchs static const char * const wcn36xx_caps_names[] = {
1882be6636aSPontus Fuchs 	"MCC",				/* 0 */
1892be6636aSPontus Fuchs 	"P2P",				/* 1 */
1902be6636aSPontus Fuchs 	"DOT11AC",			/* 2 */
1912be6636aSPontus Fuchs 	"SLM_SESSIONIZATION",		/* 3 */
1922be6636aSPontus Fuchs 	"DOT11AC_OPMODE",		/* 4 */
1932be6636aSPontus Fuchs 	"SAP32STA",			/* 5 */
1942be6636aSPontus Fuchs 	"TDLS",				/* 6 */
1952be6636aSPontus Fuchs 	"P2P_GO_NOA_DECOUPLE_INIT_SCAN",/* 7 */
1962be6636aSPontus Fuchs 	"WLANACTIVE_OFFLOAD",		/* 8 */
1972be6636aSPontus Fuchs 	"BEACON_OFFLOAD",		/* 9 */
1982be6636aSPontus Fuchs 	"SCAN_OFFLOAD",			/* 10 */
1992be6636aSPontus Fuchs 	"ROAM_OFFLOAD",			/* 11 */
2002be6636aSPontus Fuchs 	"BCN_MISS_OFFLOAD",		/* 12 */
2012be6636aSPontus Fuchs 	"STA_POWERSAVE",		/* 13 */
2022be6636aSPontus Fuchs 	"STA_ADVANCED_PWRSAVE",		/* 14 */
2032be6636aSPontus Fuchs 	"AP_UAPSD",			/* 15 */
2042be6636aSPontus Fuchs 	"AP_DFS",			/* 16 */
2052be6636aSPontus Fuchs 	"BLOCKACK",			/* 17 */
2062be6636aSPontus Fuchs 	"PHY_ERR",			/* 18 */
2072be6636aSPontus Fuchs 	"BCN_FILTER",			/* 19 */
2082be6636aSPontus Fuchs 	"RTT",				/* 20 */
2092be6636aSPontus Fuchs 	"RATECTRL",			/* 21 */
210ffc03c33SBjorn Andersson 	"WOW",				/* 22 */
211ffc03c33SBjorn Andersson 	"WLAN_ROAM_SCAN_OFFLOAD",	/* 23 */
212ffc03c33SBjorn Andersson 	"SPECULATIVE_PS_POLL",		/* 24 */
213ffc03c33SBjorn Andersson 	"SCAN_SCH",			/* 25 */
214ffc03c33SBjorn Andersson 	"IBSS_HEARTBEAT_OFFLOAD",	/* 26 */
215ffc03c33SBjorn Andersson 	"WLAN_SCAN_OFFLOAD",		/* 27 */
216ffc03c33SBjorn Andersson 	"WLAN_PERIODIC_TX_PTRN",	/* 28 */
217ffc03c33SBjorn Andersson 	"ADVANCE_TDLS",			/* 29 */
218ffc03c33SBjorn Andersson 	"BATCH_SCAN",			/* 30 */
219ffc03c33SBjorn Andersson 	"FW_IN_TX_PATH",		/* 31 */
220ffc03c33SBjorn Andersson 	"EXTENDED_NSOFFLOAD_SLOT",	/* 32 */
221ffc03c33SBjorn Andersson 	"CH_SWITCH_V1",			/* 33 */
222ffc03c33SBjorn Andersson 	"HT40_OBSS_SCAN",		/* 34 */
223ffc03c33SBjorn Andersson 	"UPDATE_CHANNEL_LIST",		/* 35 */
224ffc03c33SBjorn Andersson 	"WLAN_MCADDR_FLT",		/* 36 */
225ffc03c33SBjorn Andersson 	"WLAN_CH144",			/* 37 */
226ffc03c33SBjorn Andersson 	"NAN",				/* 38 */
227ffc03c33SBjorn Andersson 	"TDLS_SCAN_COEXISTENCE",	/* 39 */
228ffc03c33SBjorn Andersson 	"LINK_LAYER_STATS_MEAS",	/* 40 */
229ffc03c33SBjorn Andersson 	"MU_MIMO",			/* 41 */
230ffc03c33SBjorn Andersson 	"EXTENDED_SCAN",		/* 42 */
231ffc03c33SBjorn Andersson 	"DYNAMIC_WMM_PS",		/* 43 */
232ffc03c33SBjorn Andersson 	"MAC_SPOOFED_SCAN",		/* 44 */
233ffc03c33SBjorn Andersson 	"BMU_ERROR_GENERIC_RECOVERY",	/* 45 */
234ffc03c33SBjorn Andersson 	"DISA",				/* 46 */
235ffc03c33SBjorn Andersson 	"FW_STATS",			/* 47 */
236ffc03c33SBjorn Andersson 	"WPS_PRBRSP_TMPL",		/* 48 */
237ffc03c33SBjorn Andersson 	"BCN_IE_FLT_DELTA",		/* 49 */
238ffc03c33SBjorn Andersson 	"TDLS_OFF_CHANNEL",		/* 51 */
239ffc03c33SBjorn Andersson 	"RTT3",				/* 52 */
240ffc03c33SBjorn Andersson 	"MGMT_FRAME_LOGGING",		/* 53 */
241ffc03c33SBjorn Andersson 	"ENHANCED_TXBD_COMPLETION",	/* 54 */
242ffc03c33SBjorn Andersson 	"LOGGING_ENHANCEMENT",		/* 55 */
243ffc03c33SBjorn Andersson 	"EXT_SCAN_ENHANCED",		/* 56 */
244ffc03c33SBjorn Andersson 	"MEMORY_DUMP_SUPPORTED",	/* 57 */
245ffc03c33SBjorn Andersson 	"PER_PKT_STATS_SUPPORTED",	/* 58 */
246ffc03c33SBjorn Andersson 	"EXT_LL_STAT",			/* 60 */
247ffc03c33SBjorn Andersson 	"WIFI_CONFIG",			/* 61 */
248ffc03c33SBjorn Andersson 	"ANTENNA_DIVERSITY_SELECTION",	/* 62 */
2492be6636aSPontus Fuchs };
2502be6636aSPontus Fuchs 
2512be6636aSPontus Fuchs static const char *wcn36xx_get_cap_name(enum place_holder_in_cap_bitmap x)
2522be6636aSPontus Fuchs {
2532be6636aSPontus Fuchs 	if (x >= ARRAY_SIZE(wcn36xx_caps_names))
2542be6636aSPontus Fuchs 		return "UNKNOWN";
2552be6636aSPontus Fuchs 	return wcn36xx_caps_names[x];
2562be6636aSPontus Fuchs }
2572be6636aSPontus Fuchs 
2582be6636aSPontus Fuchs static void wcn36xx_feat_caps_info(struct wcn36xx *wcn)
2592be6636aSPontus Fuchs {
2602be6636aSPontus Fuchs 	int i;
2612be6636aSPontus Fuchs 
2622be6636aSPontus Fuchs 	for (i = 0; i < MAX_FEATURE_SUPPORTED; i++) {
2632be6636aSPontus Fuchs 		if (get_feat_caps(wcn->fw_feat_caps, i))
2642be6636aSPontus Fuchs 			wcn36xx_info("FW Cap %s\n", wcn36xx_get_cap_name(i));
2652be6636aSPontus Fuchs 	}
2662be6636aSPontus Fuchs }
2672be6636aSPontus Fuchs 
2688e84c258SEugene Krasnikov static int wcn36xx_start(struct ieee80211_hw *hw)
2698e84c258SEugene Krasnikov {
2708e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
2718e84c258SEugene Krasnikov 	int ret;
2728e84c258SEugene Krasnikov 
2738e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac start\n");
2748e84c258SEugene Krasnikov 
2758e84c258SEugene Krasnikov 	/* SMD initialization */
2768e84c258SEugene Krasnikov 	ret = wcn36xx_smd_open(wcn);
2778e84c258SEugene Krasnikov 	if (ret) {
2788e84c258SEugene Krasnikov 		wcn36xx_err("Failed to open smd channel: %d\n", ret);
2798e84c258SEugene Krasnikov 		goto out_err;
2808e84c258SEugene Krasnikov 	}
2818e84c258SEugene Krasnikov 
2828e84c258SEugene Krasnikov 	/* Allocate memory pools for Mgmt BD headers and Data BD headers */
2838e84c258SEugene Krasnikov 	ret = wcn36xx_dxe_allocate_mem_pools(wcn);
2848e84c258SEugene Krasnikov 	if (ret) {
2858e84c258SEugene Krasnikov 		wcn36xx_err("Failed to alloc DXE mempool: %d\n", ret);
2868e84c258SEugene Krasnikov 		goto out_smd_close;
2878e84c258SEugene Krasnikov 	}
2888e84c258SEugene Krasnikov 
2898e84c258SEugene Krasnikov 	ret = wcn36xx_dxe_alloc_ctl_blks(wcn);
2908e84c258SEugene Krasnikov 	if (ret) {
2918e84c258SEugene Krasnikov 		wcn36xx_err("Failed to alloc DXE ctl blocks: %d\n", ret);
2928e84c258SEugene Krasnikov 		goto out_free_dxe_pool;
2938e84c258SEugene Krasnikov 	}
2948e84c258SEugene Krasnikov 
2958e84c258SEugene Krasnikov 	wcn->hal_buf = kmalloc(WCN36XX_HAL_BUF_SIZE, GFP_KERNEL);
2968e84c258SEugene Krasnikov 	if (!wcn->hal_buf) {
2978e84c258SEugene Krasnikov 		wcn36xx_err("Failed to allocate smd buf\n");
2988e84c258SEugene Krasnikov 		ret = -ENOMEM;
2998e84c258SEugene Krasnikov 		goto out_free_dxe_ctl;
3008e84c258SEugene Krasnikov 	}
3018e84c258SEugene Krasnikov 
3028e84c258SEugene Krasnikov 	ret = wcn36xx_smd_load_nv(wcn);
3038e84c258SEugene Krasnikov 	if (ret) {
3048e84c258SEugene Krasnikov 		wcn36xx_err("Failed to push NV to chip\n");
3058e84c258SEugene Krasnikov 		goto out_free_smd_buf;
3068e84c258SEugene Krasnikov 	}
3078e84c258SEugene Krasnikov 
3088e84c258SEugene Krasnikov 	ret = wcn36xx_smd_start(wcn);
3098e84c258SEugene Krasnikov 	if (ret) {
3108e84c258SEugene Krasnikov 		wcn36xx_err("Failed to start chip\n");
3118e84c258SEugene Krasnikov 		goto out_free_smd_buf;
3128e84c258SEugene Krasnikov 	}
3138e84c258SEugene Krasnikov 
314f2ed5d24SPontus Fuchs 	if (!wcn36xx_is_fw_version(wcn, 1, 2, 2, 24)) {
315f2ed5d24SPontus Fuchs 		ret = wcn36xx_smd_feature_caps_exchange(wcn);
316f2ed5d24SPontus Fuchs 		if (ret)
317f2ed5d24SPontus Fuchs 			wcn36xx_warn("Exchange feature caps failed\n");
318f2ed5d24SPontus Fuchs 		else
319f2ed5d24SPontus Fuchs 			wcn36xx_feat_caps_info(wcn);
320f2ed5d24SPontus Fuchs 	}
321f2ed5d24SPontus Fuchs 
3228e84c258SEugene Krasnikov 	/* DMA channel initialization */
3238e84c258SEugene Krasnikov 	ret = wcn36xx_dxe_init(wcn);
3248e84c258SEugene Krasnikov 	if (ret) {
3258e84c258SEugene Krasnikov 		wcn36xx_err("DXE init failed\n");
3268e84c258SEugene Krasnikov 		goto out_smd_stop;
3278e84c258SEugene Krasnikov 	}
3288e84c258SEugene Krasnikov 
3298e84c258SEugene Krasnikov 	wcn36xx_debugfs_init(wcn);
3308e84c258SEugene Krasnikov 
3318e84c258SEugene Krasnikov 	INIT_LIST_HEAD(&wcn->vif_list);
3324b12462aSBob Copeland 	spin_lock_init(&wcn->dxe_lock);
3334b12462aSBob Copeland 
3348e84c258SEugene Krasnikov 	return 0;
3358e84c258SEugene Krasnikov 
3368e84c258SEugene Krasnikov out_smd_stop:
3378e84c258SEugene Krasnikov 	wcn36xx_smd_stop(wcn);
3388e84c258SEugene Krasnikov out_free_smd_buf:
3398e84c258SEugene Krasnikov 	kfree(wcn->hal_buf);
3408e84c258SEugene Krasnikov out_free_dxe_ctl:
3418e84c258SEugene Krasnikov 	wcn36xx_dxe_free_ctl_blks(wcn);
3424aa2d31fSChristophe Jaillet out_free_dxe_pool:
3434aa2d31fSChristophe Jaillet 	wcn36xx_dxe_free_mem_pools(wcn);
3448e84c258SEugene Krasnikov out_smd_close:
3458e84c258SEugene Krasnikov 	wcn36xx_smd_close(wcn);
3468e84c258SEugene Krasnikov out_err:
3478e84c258SEugene Krasnikov 	return ret;
3488e84c258SEugene Krasnikov }
3498e84c258SEugene Krasnikov 
3508e84c258SEugene Krasnikov static void wcn36xx_stop(struct ieee80211_hw *hw)
3518e84c258SEugene Krasnikov {
3528e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
3538e84c258SEugene Krasnikov 
3548e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac stop\n");
3558e84c258SEugene Krasnikov 
3568e84c258SEugene Krasnikov 	wcn36xx_debugfs_exit(wcn);
3578e84c258SEugene Krasnikov 	wcn36xx_smd_stop(wcn);
3588e84c258SEugene Krasnikov 	wcn36xx_dxe_deinit(wcn);
3598e84c258SEugene Krasnikov 	wcn36xx_smd_close(wcn);
3608e84c258SEugene Krasnikov 
3618e84c258SEugene Krasnikov 	wcn36xx_dxe_free_mem_pools(wcn);
3628e84c258SEugene Krasnikov 	wcn36xx_dxe_free_ctl_blks(wcn);
3638e84c258SEugene Krasnikov 
3648e84c258SEugene Krasnikov 	kfree(wcn->hal_buf);
3658e84c258SEugene Krasnikov }
3668e84c258SEugene Krasnikov 
3678e84c258SEugene Krasnikov static int wcn36xx_config(struct ieee80211_hw *hw, u32 changed)
3688e84c258SEugene Krasnikov {
3698e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
3708e84c258SEugene Krasnikov 	struct ieee80211_vif *vif = NULL;
3718e84c258SEugene Krasnikov 	struct wcn36xx_vif *tmp;
3728e84c258SEugene Krasnikov 
3738e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac config changed 0x%08x\n", changed);
3748e84c258SEugene Krasnikov 
3758e84c258SEugene Krasnikov 	if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
3768e84c258SEugene Krasnikov 		int ch = WCN36XX_HW_CHANNEL(wcn);
3778e84c258SEugene Krasnikov 		wcn36xx_dbg(WCN36XX_DBG_MAC, "wcn36xx_config channel switch=%d\n",
3788e84c258SEugene Krasnikov 			    ch);
3798e84c258SEugene Krasnikov 		list_for_each_entry(tmp, &wcn->vif_list, list) {
380ce75877fSPontus Fuchs 			vif = wcn36xx_priv_to_vif(tmp);
3818e84c258SEugene Krasnikov 			wcn36xx_smd_switch_channel(wcn, vif, ch);
3828e84c258SEugene Krasnikov 		}
3838e84c258SEugene Krasnikov 	}
3848e84c258SEugene Krasnikov 
3858e84c258SEugene Krasnikov 	return 0;
3868e84c258SEugene Krasnikov }
3878e84c258SEugene Krasnikov 
3888e84c258SEugene Krasnikov static void wcn36xx_configure_filter(struct ieee80211_hw *hw,
3898e84c258SEugene Krasnikov 				     unsigned int changed,
3908e84c258SEugene Krasnikov 				     unsigned int *total, u64 multicast)
3918e84c258SEugene Krasnikov {
39220a779edSPontus Fuchs 	struct wcn36xx_hal_rcv_flt_mc_addr_list_type *fp;
39320a779edSPontus Fuchs 	struct wcn36xx *wcn = hw->priv;
39420a779edSPontus Fuchs 	struct wcn36xx_vif *tmp;
39520a779edSPontus Fuchs 	struct ieee80211_vif *vif = NULL;
39620a779edSPontus Fuchs 
3978e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac configure filter\n");
3988e84c258SEugene Krasnikov 
39920a779edSPontus Fuchs 	*total &= FIF_ALLMULTI;
40020a779edSPontus Fuchs 
40120a779edSPontus Fuchs 	fp = (void *)(unsigned long)multicast;
40220a779edSPontus Fuchs 	list_for_each_entry(tmp, &wcn->vif_list, list) {
40320a779edSPontus Fuchs 		vif = wcn36xx_priv_to_vif(tmp);
40420a779edSPontus Fuchs 
40520a779edSPontus Fuchs 		/* FW handles MC filtering only when connected as STA */
40620a779edSPontus Fuchs 		if (*total & FIF_ALLMULTI)
40720a779edSPontus Fuchs 			wcn36xx_smd_set_mc_list(wcn, vif, NULL);
40820a779edSPontus Fuchs 		else if (NL80211_IFTYPE_STATION == vif->type && tmp->sta_assoc)
40920a779edSPontus Fuchs 			wcn36xx_smd_set_mc_list(wcn, vif, fp);
41020a779edSPontus Fuchs 	}
41120a779edSPontus Fuchs 	kfree(fp);
41220a779edSPontus Fuchs }
41320a779edSPontus Fuchs 
41420a779edSPontus Fuchs static u64 wcn36xx_prepare_multicast(struct ieee80211_hw *hw,
41520a779edSPontus Fuchs 				     struct netdev_hw_addr_list *mc_list)
41620a779edSPontus Fuchs {
41720a779edSPontus Fuchs 	struct wcn36xx_hal_rcv_flt_mc_addr_list_type *fp;
41820a779edSPontus Fuchs 	struct netdev_hw_addr *ha;
41920a779edSPontus Fuchs 
42020a779edSPontus Fuchs 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac prepare multicast list\n");
42120a779edSPontus Fuchs 	fp = kzalloc(sizeof(*fp), GFP_ATOMIC);
42220a779edSPontus Fuchs 	if (!fp) {
42320a779edSPontus Fuchs 		wcn36xx_err("Out of memory setting filters.\n");
42420a779edSPontus Fuchs 		return 0;
42520a779edSPontus Fuchs 	}
42620a779edSPontus Fuchs 
42720a779edSPontus Fuchs 	fp->mc_addr_count = 0;
42820a779edSPontus Fuchs 	/* update multicast filtering parameters */
42920a779edSPontus Fuchs 	if (netdev_hw_addr_list_count(mc_list) <=
43020a779edSPontus Fuchs 	    WCN36XX_HAL_MAX_NUM_MULTICAST_ADDRESS) {
43120a779edSPontus Fuchs 		netdev_hw_addr_list_for_each(ha, mc_list) {
43220a779edSPontus Fuchs 			memcpy(fp->mc_addr[fp->mc_addr_count],
43320a779edSPontus Fuchs 					ha->addr, ETH_ALEN);
43420a779edSPontus Fuchs 			fp->mc_addr_count++;
43520a779edSPontus Fuchs 		}
43620a779edSPontus Fuchs 	}
43720a779edSPontus Fuchs 
43820a779edSPontus Fuchs 	return (u64)(unsigned long)fp;
4398e84c258SEugene Krasnikov }
4408e84c258SEugene Krasnikov 
4418e84c258SEugene Krasnikov static void wcn36xx_tx(struct ieee80211_hw *hw,
4428e84c258SEugene Krasnikov 		       struct ieee80211_tx_control *control,
4438e84c258SEugene Krasnikov 		       struct sk_buff *skb)
4448e84c258SEugene Krasnikov {
4458e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
4468e84c258SEugene Krasnikov 	struct wcn36xx_sta *sta_priv = NULL;
4478e84c258SEugene Krasnikov 
4488e84c258SEugene Krasnikov 	if (control->sta)
449a92e4696SPontus Fuchs 		sta_priv = wcn36xx_sta_to_priv(control->sta);
4508e84c258SEugene Krasnikov 
4518e84c258SEugene Krasnikov 	if (wcn36xx_start_tx(wcn, sta_priv, skb))
4528e84c258SEugene Krasnikov 		ieee80211_free_txskb(wcn->hw, skb);
4538e84c258SEugene Krasnikov }
4548e84c258SEugene Krasnikov 
4558e84c258SEugene Krasnikov static int wcn36xx_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
4568e84c258SEugene Krasnikov 			   struct ieee80211_vif *vif,
4578e84c258SEugene Krasnikov 			   struct ieee80211_sta *sta,
4588e84c258SEugene Krasnikov 			   struct ieee80211_key_conf *key_conf)
4598e84c258SEugene Krasnikov {
4608e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
461ce75877fSPontus Fuchs 	struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif);
46281c69263SPontus Fuchs 	struct wcn36xx_sta *sta_priv = wcn36xx_sta_to_priv(sta);
4638e84c258SEugene Krasnikov 	int ret = 0;
4648e84c258SEugene Krasnikov 	u8 key[WLAN_MAX_KEY_LEN];
4658e84c258SEugene Krasnikov 
4668e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac80211 set key\n");
4678e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "Key: cmd=0x%x algo:0x%x, id:%d, len:%d flags 0x%x\n",
4688e84c258SEugene Krasnikov 		    cmd, key_conf->cipher, key_conf->keyidx,
4698e84c258SEugene Krasnikov 		    key_conf->keylen, key_conf->flags);
4708e84c258SEugene Krasnikov 	wcn36xx_dbg_dump(WCN36XX_DBG_MAC, "KEY: ",
4718e84c258SEugene Krasnikov 			 key_conf->key,
4728e84c258SEugene Krasnikov 			 key_conf->keylen);
4738e84c258SEugene Krasnikov 
4748e84c258SEugene Krasnikov 	switch (key_conf->cipher) {
4758e84c258SEugene Krasnikov 	case WLAN_CIPHER_SUITE_WEP40:
4768e84c258SEugene Krasnikov 		vif_priv->encrypt_type = WCN36XX_HAL_ED_WEP40;
4778e84c258SEugene Krasnikov 		break;
4788e84c258SEugene Krasnikov 	case WLAN_CIPHER_SUITE_WEP104:
4798e84c258SEugene Krasnikov 		vif_priv->encrypt_type = WCN36XX_HAL_ED_WEP40;
4808e84c258SEugene Krasnikov 		break;
4818e84c258SEugene Krasnikov 	case WLAN_CIPHER_SUITE_CCMP:
4828e84c258SEugene Krasnikov 		vif_priv->encrypt_type = WCN36XX_HAL_ED_CCMP;
4838e84c258SEugene Krasnikov 		break;
4848e84c258SEugene Krasnikov 	case WLAN_CIPHER_SUITE_TKIP:
4858e84c258SEugene Krasnikov 		vif_priv->encrypt_type = WCN36XX_HAL_ED_TKIP;
4868e84c258SEugene Krasnikov 		break;
4878e84c258SEugene Krasnikov 	default:
4888e84c258SEugene Krasnikov 		wcn36xx_err("Unsupported key type 0x%x\n",
4898e84c258SEugene Krasnikov 			      key_conf->cipher);
4908e84c258SEugene Krasnikov 		ret = -EOPNOTSUPP;
4918e84c258SEugene Krasnikov 		goto out;
4928e84c258SEugene Krasnikov 	}
4938e84c258SEugene Krasnikov 
4948e84c258SEugene Krasnikov 	switch (cmd) {
4958e84c258SEugene Krasnikov 	case SET_KEY:
4968e84c258SEugene Krasnikov 		if (WCN36XX_HAL_ED_TKIP == vif_priv->encrypt_type) {
4978e84c258SEugene Krasnikov 			/*
4988e84c258SEugene Krasnikov 			 * Supplicant is sending key in the wrong order:
4998e84c258SEugene Krasnikov 			 * Temporal Key (16 b) - TX MIC (8 b) - RX MIC (8 b)
5008e84c258SEugene Krasnikov 			 * but HW expects it to be in the order as described in
5018e84c258SEugene Krasnikov 			 * IEEE 802.11 spec (see chapter 11.7) like this:
5028e84c258SEugene Krasnikov 			 * Temporal Key (16 b) - RX MIC (8 b) - TX MIC (8 b)
5038e84c258SEugene Krasnikov 			 */
5048e84c258SEugene Krasnikov 			memcpy(key, key_conf->key, 16);
5058e84c258SEugene Krasnikov 			memcpy(key + 16, key_conf->key + 24, 8);
5068e84c258SEugene Krasnikov 			memcpy(key + 24, key_conf->key + 16, 8);
5078e84c258SEugene Krasnikov 		} else {
5088e84c258SEugene Krasnikov 			memcpy(key, key_conf->key, key_conf->keylen);
5098e84c258SEugene Krasnikov 		}
5108e84c258SEugene Krasnikov 
5118e84c258SEugene Krasnikov 		if (IEEE80211_KEY_FLAG_PAIRWISE & key_conf->flags) {
5128e84c258SEugene Krasnikov 			sta_priv->is_data_encrypted = true;
5138e84c258SEugene Krasnikov 			/* Reconfigure bss with encrypt_type */
5148e84c258SEugene Krasnikov 			if (NL80211_IFTYPE_STATION == vif->type)
5158e84c258SEugene Krasnikov 				wcn36xx_smd_config_bss(wcn,
5168e84c258SEugene Krasnikov 						       vif,
5178e84c258SEugene Krasnikov 						       sta,
5188e84c258SEugene Krasnikov 						       sta->addr,
5198e84c258SEugene Krasnikov 						       true);
5208e84c258SEugene Krasnikov 
5218e84c258SEugene Krasnikov 			wcn36xx_smd_set_stakey(wcn,
5228e84c258SEugene Krasnikov 				vif_priv->encrypt_type,
5238e84c258SEugene Krasnikov 				key_conf->keyidx,
5248e84c258SEugene Krasnikov 				key_conf->keylen,
5258e84c258SEugene Krasnikov 				key,
5268e84c258SEugene Krasnikov 				get_sta_index(vif, sta_priv));
5278e84c258SEugene Krasnikov 		} else {
5288e84c258SEugene Krasnikov 			wcn36xx_smd_set_bsskey(wcn,
5298e84c258SEugene Krasnikov 				vif_priv->encrypt_type,
5308e84c258SEugene Krasnikov 				key_conf->keyidx,
5318e84c258SEugene Krasnikov 				key_conf->keylen,
5328e84c258SEugene Krasnikov 				key);
5338e84c258SEugene Krasnikov 			if ((WLAN_CIPHER_SUITE_WEP40 == key_conf->cipher) ||
5348e84c258SEugene Krasnikov 			    (WLAN_CIPHER_SUITE_WEP104 == key_conf->cipher)) {
5358e84c258SEugene Krasnikov 				sta_priv->is_data_encrypted = true;
5368e84c258SEugene Krasnikov 				wcn36xx_smd_set_stakey(wcn,
5378e84c258SEugene Krasnikov 					vif_priv->encrypt_type,
5388e84c258SEugene Krasnikov 					key_conf->keyidx,
5398e84c258SEugene Krasnikov 					key_conf->keylen,
5408e84c258SEugene Krasnikov 					key,
5418e84c258SEugene Krasnikov 					get_sta_index(vif, sta_priv));
5428e84c258SEugene Krasnikov 			}
5438e84c258SEugene Krasnikov 		}
5448e84c258SEugene Krasnikov 		break;
5458e84c258SEugene Krasnikov 	case DISABLE_KEY:
5468e84c258SEugene Krasnikov 		if (!(IEEE80211_KEY_FLAG_PAIRWISE & key_conf->flags)) {
5472716a8acSPontus Fuchs 			vif_priv->encrypt_type = WCN36XX_HAL_ED_NONE;
5488e84c258SEugene Krasnikov 			wcn36xx_smd_remove_bsskey(wcn,
5498e84c258SEugene Krasnikov 				vif_priv->encrypt_type,
5508e84c258SEugene Krasnikov 				key_conf->keyidx);
5518e84c258SEugene Krasnikov 		} else {
5528e84c258SEugene Krasnikov 			sta_priv->is_data_encrypted = false;
5538e84c258SEugene Krasnikov 			/* do not remove key if disassociated */
5548e84c258SEugene Krasnikov 			if (sta_priv->aid)
5558e84c258SEugene Krasnikov 				wcn36xx_smd_remove_stakey(wcn,
5568e84c258SEugene Krasnikov 					vif_priv->encrypt_type,
5578e84c258SEugene Krasnikov 					key_conf->keyidx,
5588e84c258SEugene Krasnikov 					get_sta_index(vif, sta_priv));
5598e84c258SEugene Krasnikov 		}
5608e84c258SEugene Krasnikov 		break;
5618e84c258SEugene Krasnikov 	default:
5628e84c258SEugene Krasnikov 		wcn36xx_err("Unsupported key cmd 0x%x\n", cmd);
5638e84c258SEugene Krasnikov 		ret = -EOPNOTSUPP;
5648e84c258SEugene Krasnikov 		goto out;
5658e84c258SEugene Krasnikov 	}
5668e84c258SEugene Krasnikov 
5678e84c258SEugene Krasnikov out:
5688e84c258SEugene Krasnikov 	return ret;
5698e84c258SEugene Krasnikov }
5708e84c258SEugene Krasnikov 
57188603903SBjorn Andersson static void wcn36xx_hw_scan_worker(struct work_struct *work)
5728e84c258SEugene Krasnikov {
57388603903SBjorn Andersson 	struct wcn36xx *wcn = container_of(work, struct wcn36xx, scan_work);
57488603903SBjorn Andersson 	struct cfg80211_scan_request *req = wcn->scan_req;
57588603903SBjorn Andersson 	u8 channels[WCN36XX_HAL_PNO_MAX_NETW_CHANNELS_EX];
57688603903SBjorn Andersson 	struct cfg80211_scan_info scan_info = {};
57703c95dbeSBjorn Andersson 	bool aborted = false;
57888603903SBjorn Andersson 	int i;
57988603903SBjorn Andersson 
58088603903SBjorn Andersson 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac80211 scan %d channels worker\n", req->n_channels);
58188603903SBjorn Andersson 
58288603903SBjorn Andersson 	for (i = 0; i < req->n_channels; i++)
58388603903SBjorn Andersson 		channels[i] = req->channels[i]->hw_value;
58488603903SBjorn Andersson 
58588603903SBjorn Andersson 	wcn36xx_smd_update_scan_params(wcn, channels, req->n_channels);
5868e84c258SEugene Krasnikov 
5878e84c258SEugene Krasnikov 	wcn36xx_smd_init_scan(wcn, HAL_SYS_MODE_SCAN);
58888603903SBjorn Andersson 	for (i = 0; i < req->n_channels; i++) {
58903c95dbeSBjorn Andersson 		mutex_lock(&wcn->scan_lock);
59003c95dbeSBjorn Andersson 		aborted = wcn->scan_aborted;
59103c95dbeSBjorn Andersson 		mutex_unlock(&wcn->scan_lock);
59203c95dbeSBjorn Andersson 
59303c95dbeSBjorn Andersson 		if (aborted)
59403c95dbeSBjorn Andersson 			break;
59503c95dbeSBjorn Andersson 
59688603903SBjorn Andersson 		wcn->scan_freq = req->channels[i]->center_freq;
59788603903SBjorn Andersson 		wcn->scan_band = req->channels[i]->band;
59888603903SBjorn Andersson 
59988603903SBjorn Andersson 		wcn36xx_smd_start_scan(wcn, req->channels[i]->hw_value);
60088603903SBjorn Andersson 		msleep(30);
60188603903SBjorn Andersson 		wcn36xx_smd_end_scan(wcn, req->channels[i]->hw_value);
60288603903SBjorn Andersson 
60388603903SBjorn Andersson 		wcn->scan_freq = 0;
60488603903SBjorn Andersson 	}
60588603903SBjorn Andersson 	wcn36xx_smd_finish_scan(wcn, HAL_SYS_MODE_SCAN);
60688603903SBjorn Andersson 
60703c95dbeSBjorn Andersson 	scan_info.aborted = aborted;
60888603903SBjorn Andersson 	ieee80211_scan_completed(wcn->hw, &scan_info);
60988603903SBjorn Andersson 
61088603903SBjorn Andersson 	mutex_lock(&wcn->scan_lock);
61188603903SBjorn Andersson 	wcn->scan_req = NULL;
61288603903SBjorn Andersson 	mutex_unlock(&wcn->scan_lock);
6138e84c258SEugene Krasnikov }
6148e84c258SEugene Krasnikov 
61588603903SBjorn Andersson static int wcn36xx_hw_scan(struct ieee80211_hw *hw,
61688603903SBjorn Andersson 			   struct ieee80211_vif *vif,
61788603903SBjorn Andersson 			   struct ieee80211_scan_request *hw_req)
6188e84c258SEugene Krasnikov {
6198e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
6208e84c258SEugene Krasnikov 
62188603903SBjorn Andersson 	mutex_lock(&wcn->scan_lock);
62288603903SBjorn Andersson 	if (wcn->scan_req) {
62388603903SBjorn Andersson 		mutex_unlock(&wcn->scan_lock);
62488603903SBjorn Andersson 		return -EBUSY;
62588603903SBjorn Andersson 	}
62603c95dbeSBjorn Andersson 
62703c95dbeSBjorn Andersson 	wcn->scan_aborted = false;
62888603903SBjorn Andersson 	wcn->scan_req = &hw_req->req;
62988603903SBjorn Andersson 	mutex_unlock(&wcn->scan_lock);
63088603903SBjorn Andersson 
63188603903SBjorn Andersson 	schedule_work(&wcn->scan_work);
63288603903SBjorn Andersson 
63388603903SBjorn Andersson 	return 0;
6348e84c258SEugene Krasnikov }
6358e84c258SEugene Krasnikov 
63603c95dbeSBjorn Andersson static void wcn36xx_cancel_hw_scan(struct ieee80211_hw *hw,
63703c95dbeSBjorn Andersson 				   struct ieee80211_vif *vif)
63803c95dbeSBjorn Andersson {
63903c95dbeSBjorn Andersson 	struct wcn36xx *wcn = hw->priv;
64003c95dbeSBjorn Andersson 
64103c95dbeSBjorn Andersson 	mutex_lock(&wcn->scan_lock);
64203c95dbeSBjorn Andersson 	wcn->scan_aborted = true;
64303c95dbeSBjorn Andersson 	mutex_unlock(&wcn->scan_lock);
64403c95dbeSBjorn Andersson 
64503c95dbeSBjorn Andersson 	cancel_work_sync(&wcn->scan_work);
64603c95dbeSBjorn Andersson }
64703c95dbeSBjorn Andersson 
6488e84c258SEugene Krasnikov static void wcn36xx_update_allowed_rates(struct ieee80211_sta *sta,
64957fbcce3SJohannes Berg 					 enum nl80211_band band)
6508e84c258SEugene Krasnikov {
6518e84c258SEugene Krasnikov 	int i, size;
6528e84c258SEugene Krasnikov 	u16 *rates_table;
653a92e4696SPontus Fuchs 	struct wcn36xx_sta *sta_priv = wcn36xx_sta_to_priv(sta);
6548e84c258SEugene Krasnikov 	u32 rates = sta->supp_rates[band];
6558e84c258SEugene Krasnikov 
6568e84c258SEugene Krasnikov 	memset(&sta_priv->supported_rates, 0,
6578e84c258SEugene Krasnikov 		sizeof(sta_priv->supported_rates));
6588e84c258SEugene Krasnikov 	sta_priv->supported_rates.op_rate_mode = STA_11n;
6598e84c258SEugene Krasnikov 
6608e84c258SEugene Krasnikov 	size = ARRAY_SIZE(sta_priv->supported_rates.dsss_rates);
6618e84c258SEugene Krasnikov 	rates_table = sta_priv->supported_rates.dsss_rates;
66257fbcce3SJohannes Berg 	if (band == NL80211_BAND_2GHZ) {
6638e84c258SEugene Krasnikov 		for (i = 0; i < size; i++) {
6648e84c258SEugene Krasnikov 			if (rates & 0x01) {
6658e84c258SEugene Krasnikov 				rates_table[i] = wcn_2ghz_rates[i].hw_value;
6668e84c258SEugene Krasnikov 				rates = rates >> 1;
6678e84c258SEugene Krasnikov 			}
6688e84c258SEugene Krasnikov 		}
6698e84c258SEugene Krasnikov 	}
6708e84c258SEugene Krasnikov 
6718e84c258SEugene Krasnikov 	size = ARRAY_SIZE(sta_priv->supported_rates.ofdm_rates);
6728e84c258SEugene Krasnikov 	rates_table = sta_priv->supported_rates.ofdm_rates;
6738e84c258SEugene Krasnikov 	for (i = 0; i < size; i++) {
6748e84c258SEugene Krasnikov 		if (rates & 0x01) {
6758e84c258SEugene Krasnikov 			rates_table[i] = wcn_5ghz_rates[i].hw_value;
6768e84c258SEugene Krasnikov 			rates = rates >> 1;
6778e84c258SEugene Krasnikov 		}
6788e84c258SEugene Krasnikov 	}
6798e84c258SEugene Krasnikov 
6808e84c258SEugene Krasnikov 	if (sta->ht_cap.ht_supported) {
6818e84c258SEugene Krasnikov 		BUILD_BUG_ON(sizeof(sta->ht_cap.mcs.rx_mask) >
6828e84c258SEugene Krasnikov 			sizeof(sta_priv->supported_rates.supported_mcs_set));
6838e84c258SEugene Krasnikov 		memcpy(sta_priv->supported_rates.supported_mcs_set,
6848e84c258SEugene Krasnikov 		       sta->ht_cap.mcs.rx_mask,
6858e84c258SEugene Krasnikov 		       sizeof(sta->ht_cap.mcs.rx_mask));
6868e84c258SEugene Krasnikov 	}
6878e84c258SEugene Krasnikov }
6888e84c258SEugene Krasnikov void wcn36xx_set_default_rates(struct wcn36xx_hal_supported_rates *rates)
6898e84c258SEugene Krasnikov {
6908e84c258SEugene Krasnikov 	u16 ofdm_rates[WCN36XX_HAL_NUM_OFDM_RATES] = {
6918e84c258SEugene Krasnikov 		HW_RATE_INDEX_6MBPS,
6928e84c258SEugene Krasnikov 		HW_RATE_INDEX_9MBPS,
6938e84c258SEugene Krasnikov 		HW_RATE_INDEX_12MBPS,
6948e84c258SEugene Krasnikov 		HW_RATE_INDEX_18MBPS,
6958e84c258SEugene Krasnikov 		HW_RATE_INDEX_24MBPS,
6968e84c258SEugene Krasnikov 		HW_RATE_INDEX_36MBPS,
6978e84c258SEugene Krasnikov 		HW_RATE_INDEX_48MBPS,
6988e84c258SEugene Krasnikov 		HW_RATE_INDEX_54MBPS
6998e84c258SEugene Krasnikov 	};
7008e84c258SEugene Krasnikov 	u16 dsss_rates[WCN36XX_HAL_NUM_DSSS_RATES] = {
7018e84c258SEugene Krasnikov 		HW_RATE_INDEX_1MBPS,
7028e84c258SEugene Krasnikov 		HW_RATE_INDEX_2MBPS,
7038e84c258SEugene Krasnikov 		HW_RATE_INDEX_5_5MBPS,
7048e84c258SEugene Krasnikov 		HW_RATE_INDEX_11MBPS
7058e84c258SEugene Krasnikov 	};
7068e84c258SEugene Krasnikov 
7078e84c258SEugene Krasnikov 	rates->op_rate_mode = STA_11n;
7088e84c258SEugene Krasnikov 	memcpy(rates->dsss_rates, dsss_rates,
7098e84c258SEugene Krasnikov 		sizeof(*dsss_rates) * WCN36XX_HAL_NUM_DSSS_RATES);
7108e84c258SEugene Krasnikov 	memcpy(rates->ofdm_rates, ofdm_rates,
7118e84c258SEugene Krasnikov 		sizeof(*ofdm_rates) * WCN36XX_HAL_NUM_OFDM_RATES);
7128e84c258SEugene Krasnikov 	rates->supported_mcs_set[0] = 0xFF;
7138e84c258SEugene Krasnikov }
7148e84c258SEugene Krasnikov static void wcn36xx_bss_info_changed(struct ieee80211_hw *hw,
7158e84c258SEugene Krasnikov 				     struct ieee80211_vif *vif,
7168e84c258SEugene Krasnikov 				     struct ieee80211_bss_conf *bss_conf,
7178e84c258SEugene Krasnikov 				     u32 changed)
7188e84c258SEugene Krasnikov {
7198e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
7208e84c258SEugene Krasnikov 	struct sk_buff *skb = NULL;
7218e84c258SEugene Krasnikov 	u16 tim_off, tim_len;
7228e84c258SEugene Krasnikov 	enum wcn36xx_hal_link_state link_state;
723ce75877fSPontus Fuchs 	struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif);
7248e84c258SEugene Krasnikov 
7258e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss info changed vif %p changed 0x%08x\n",
7268e84c258SEugene Krasnikov 		    vif, changed);
7278e84c258SEugene Krasnikov 
7288e84c258SEugene Krasnikov 	if (changed & BSS_CHANGED_BEACON_INFO) {
7298e84c258SEugene Krasnikov 		wcn36xx_dbg(WCN36XX_DBG_MAC,
7308e84c258SEugene Krasnikov 			    "mac bss changed dtim period %d\n",
7318e84c258SEugene Krasnikov 			    bss_conf->dtim_period);
7328e84c258SEugene Krasnikov 
7338e84c258SEugene Krasnikov 		vif_priv->dtim_period = bss_conf->dtim_period;
7348e84c258SEugene Krasnikov 	}
7358e84c258SEugene Krasnikov 
7368e84c258SEugene Krasnikov 	if (changed & BSS_CHANGED_PS) {
7378e84c258SEugene Krasnikov 		wcn36xx_dbg(WCN36XX_DBG_MAC,
7388e84c258SEugene Krasnikov 			    "mac bss PS set %d\n",
7398e84c258SEugene Krasnikov 			    bss_conf->ps);
7408e84c258SEugene Krasnikov 		if (bss_conf->ps) {
7418e84c258SEugene Krasnikov 			wcn36xx_pmc_enter_bmps_state(wcn, vif);
7428e84c258SEugene Krasnikov 		} else {
7438e84c258SEugene Krasnikov 			wcn36xx_pmc_exit_bmps_state(wcn, vif);
7448e84c258SEugene Krasnikov 		}
7458e84c258SEugene Krasnikov 	}
7468e84c258SEugene Krasnikov 
7478e84c258SEugene Krasnikov 	if (changed & BSS_CHANGED_BSSID) {
7488e84c258SEugene Krasnikov 		wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss changed_bssid %pM\n",
7498e84c258SEugene Krasnikov 			    bss_conf->bssid);
7508e84c258SEugene Krasnikov 
7518e84c258SEugene Krasnikov 		if (!is_zero_ether_addr(bss_conf->bssid)) {
7528e84c258SEugene Krasnikov 			vif_priv->is_joining = true;
75390023c03SPontus Fuchs 			vif_priv->bss_index = WCN36XX_HAL_BSS_INVALID_IDX;
7548e84c258SEugene Krasnikov 			wcn36xx_smd_join(wcn, bss_conf->bssid,
7558e84c258SEugene Krasnikov 					 vif->addr, WCN36XX_HW_CHANNEL(wcn));
7568e84c258SEugene Krasnikov 			wcn36xx_smd_config_bss(wcn, vif, NULL,
7578e84c258SEugene Krasnikov 					       bss_conf->bssid, false);
7588e84c258SEugene Krasnikov 		} else {
7598e84c258SEugene Krasnikov 			vif_priv->is_joining = false;
7608e84c258SEugene Krasnikov 			wcn36xx_smd_delete_bss(wcn, vif);
7612716a8acSPontus Fuchs 			vif_priv->encrypt_type = WCN36XX_HAL_ED_NONE;
7628e84c258SEugene Krasnikov 		}
7638e84c258SEugene Krasnikov 	}
7648e84c258SEugene Krasnikov 
7658e84c258SEugene Krasnikov 	if (changed & BSS_CHANGED_SSID) {
7668e84c258SEugene Krasnikov 		wcn36xx_dbg(WCN36XX_DBG_MAC,
7678e84c258SEugene Krasnikov 			    "mac bss changed ssid\n");
7688e84c258SEugene Krasnikov 		wcn36xx_dbg_dump(WCN36XX_DBG_MAC, "ssid ",
7698e84c258SEugene Krasnikov 				 bss_conf->ssid, bss_conf->ssid_len);
7708e84c258SEugene Krasnikov 
7718e84c258SEugene Krasnikov 		vif_priv->ssid.length = bss_conf->ssid_len;
7728e84c258SEugene Krasnikov 		memcpy(&vif_priv->ssid.ssid,
7738e84c258SEugene Krasnikov 		       bss_conf->ssid,
7748e84c258SEugene Krasnikov 		       bss_conf->ssid_len);
7758e84c258SEugene Krasnikov 	}
7768e84c258SEugene Krasnikov 
7778e84c258SEugene Krasnikov 	if (changed & BSS_CHANGED_ASSOC) {
7788e84c258SEugene Krasnikov 		vif_priv->is_joining = false;
7798e84c258SEugene Krasnikov 		if (bss_conf->assoc) {
7808e84c258SEugene Krasnikov 			struct ieee80211_sta *sta;
7818e84c258SEugene Krasnikov 			struct wcn36xx_sta *sta_priv;
7828e84c258SEugene Krasnikov 
7838e84c258SEugene Krasnikov 			wcn36xx_dbg(WCN36XX_DBG_MAC,
7848e84c258SEugene Krasnikov 				    "mac assoc bss %pM vif %pM AID=%d\n",
7858e84c258SEugene Krasnikov 				     bss_conf->bssid,
7868e84c258SEugene Krasnikov 				     vif->addr,
7878e84c258SEugene Krasnikov 				     bss_conf->aid);
7888e84c258SEugene Krasnikov 
789043ce546SPontus Fuchs 			vif_priv->sta_assoc = true;
7908e84c258SEugene Krasnikov 			rcu_read_lock();
7918e84c258SEugene Krasnikov 			sta = ieee80211_find_sta(vif, bss_conf->bssid);
7928e84c258SEugene Krasnikov 			if (!sta) {
7938e84c258SEugene Krasnikov 				wcn36xx_err("sta %pM is not found\n",
7948e84c258SEugene Krasnikov 					      bss_conf->bssid);
7958e84c258SEugene Krasnikov 				rcu_read_unlock();
7968e84c258SEugene Krasnikov 				goto out;
7978e84c258SEugene Krasnikov 			}
798a92e4696SPontus Fuchs 			sta_priv = wcn36xx_sta_to_priv(sta);
7998e84c258SEugene Krasnikov 
8008e84c258SEugene Krasnikov 			wcn36xx_update_allowed_rates(sta, WCN36XX_BAND(wcn));
8018e84c258SEugene Krasnikov 
8028e84c258SEugene Krasnikov 			wcn36xx_smd_set_link_st(wcn, bss_conf->bssid,
8038e84c258SEugene Krasnikov 				vif->addr,
8048e84c258SEugene Krasnikov 				WCN36XX_HAL_LINK_POSTASSOC_STATE);
8058e84c258SEugene Krasnikov 			wcn36xx_smd_config_bss(wcn, vif, sta,
8068e84c258SEugene Krasnikov 					       bss_conf->bssid,
8078e84c258SEugene Krasnikov 					       true);
8088e84c258SEugene Krasnikov 			sta_priv->aid = bss_conf->aid;
8098e84c258SEugene Krasnikov 			/*
8108e84c258SEugene Krasnikov 			 * config_sta must be called from  because this is the
8118e84c258SEugene Krasnikov 			 * place where AID is available.
8128e84c258SEugene Krasnikov 			 */
8138e84c258SEugene Krasnikov 			wcn36xx_smd_config_sta(wcn, vif, sta);
8148e84c258SEugene Krasnikov 			rcu_read_unlock();
8158e84c258SEugene Krasnikov 		} else {
8168e84c258SEugene Krasnikov 			wcn36xx_dbg(WCN36XX_DBG_MAC,
8178e84c258SEugene Krasnikov 				    "disassociated bss %pM vif %pM AID=%d\n",
8188e84c258SEugene Krasnikov 				    bss_conf->bssid,
8198e84c258SEugene Krasnikov 				    vif->addr,
8208e84c258SEugene Krasnikov 				    bss_conf->aid);
821043ce546SPontus Fuchs 			vif_priv->sta_assoc = false;
8228e84c258SEugene Krasnikov 			wcn36xx_smd_set_link_st(wcn,
8238e84c258SEugene Krasnikov 						bss_conf->bssid,
8248e84c258SEugene Krasnikov 						vif->addr,
8258e84c258SEugene Krasnikov 						WCN36XX_HAL_LINK_IDLE_STATE);
8268e84c258SEugene Krasnikov 		}
8278e84c258SEugene Krasnikov 	}
8288e84c258SEugene Krasnikov 
8298e84c258SEugene Krasnikov 	if (changed & BSS_CHANGED_AP_PROBE_RESP) {
8308e84c258SEugene Krasnikov 		wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss changed ap probe resp\n");
8318e84c258SEugene Krasnikov 		skb = ieee80211_proberesp_get(hw, vif);
8328e84c258SEugene Krasnikov 		if (!skb) {
8338e84c258SEugene Krasnikov 			wcn36xx_err("failed to alloc probereq skb\n");
8348e84c258SEugene Krasnikov 			goto out;
8358e84c258SEugene Krasnikov 		}
8368e84c258SEugene Krasnikov 
8378e84c258SEugene Krasnikov 		wcn36xx_smd_update_proberesp_tmpl(wcn, vif, skb);
8388e84c258SEugene Krasnikov 		dev_kfree_skb(skb);
8398e84c258SEugene Krasnikov 	}
8408e84c258SEugene Krasnikov 
841b3e3f871SChun-Yeow Yeoh 	if (changed & BSS_CHANGED_BEACON_ENABLED ||
842b3e3f871SChun-Yeow Yeoh 	    changed & BSS_CHANGED_BEACON) {
8438e84c258SEugene Krasnikov 		wcn36xx_dbg(WCN36XX_DBG_MAC,
8448e84c258SEugene Krasnikov 			    "mac bss changed beacon enabled %d\n",
8458e84c258SEugene Krasnikov 			    bss_conf->enable_beacon);
8468e84c258SEugene Krasnikov 
8478e84c258SEugene Krasnikov 		if (bss_conf->enable_beacon) {
848908628dbSPontus Fuchs 			vif_priv->dtim_period = bss_conf->dtim_period;
84990023c03SPontus Fuchs 			vif_priv->bss_index = WCN36XX_HAL_BSS_INVALID_IDX;
8508e84c258SEugene Krasnikov 			wcn36xx_smd_config_bss(wcn, vif, NULL,
8518e84c258SEugene Krasnikov 					       vif->addr, false);
8528e84c258SEugene Krasnikov 			skb = ieee80211_beacon_get_tim(hw, vif, &tim_off,
8538e84c258SEugene Krasnikov 						       &tim_len);
8548e84c258SEugene Krasnikov 			if (!skb) {
8558e84c258SEugene Krasnikov 				wcn36xx_err("failed to alloc beacon skb\n");
8568e84c258SEugene Krasnikov 				goto out;
8578e84c258SEugene Krasnikov 			}
8588e84c258SEugene Krasnikov 			wcn36xx_smd_send_beacon(wcn, vif, skb, tim_off, 0);
8598e84c258SEugene Krasnikov 			dev_kfree_skb(skb);
8608e84c258SEugene Krasnikov 
8618e84c258SEugene Krasnikov 			if (vif->type == NL80211_IFTYPE_ADHOC ||
8628e84c258SEugene Krasnikov 			    vif->type == NL80211_IFTYPE_MESH_POINT)
8638e84c258SEugene Krasnikov 				link_state = WCN36XX_HAL_LINK_IBSS_STATE;
8648e84c258SEugene Krasnikov 			else
8658e84c258SEugene Krasnikov 				link_state = WCN36XX_HAL_LINK_AP_STATE;
8668e84c258SEugene Krasnikov 
8678e84c258SEugene Krasnikov 			wcn36xx_smd_set_link_st(wcn, vif->addr, vif->addr,
8688e84c258SEugene Krasnikov 						link_state);
8698e84c258SEugene Krasnikov 		} else {
8705443918dSBjorn Andersson 			wcn36xx_smd_delete_bss(wcn, vif);
8718e84c258SEugene Krasnikov 			wcn36xx_smd_set_link_st(wcn, vif->addr, vif->addr,
8728e84c258SEugene Krasnikov 						WCN36XX_HAL_LINK_IDLE_STATE);
8738e84c258SEugene Krasnikov 		}
8748e84c258SEugene Krasnikov 	}
8758e84c258SEugene Krasnikov out:
8768e84c258SEugene Krasnikov 	return;
8778e84c258SEugene Krasnikov }
8788e84c258SEugene Krasnikov 
8798e84c258SEugene Krasnikov /* this is required when using IEEE80211_HW_HAS_RATE_CONTROL */
8808e84c258SEugene Krasnikov static int wcn36xx_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
8818e84c258SEugene Krasnikov {
8828e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
8838e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac set RTS threshold %d\n", value);
8848e84c258SEugene Krasnikov 
8858e84c258SEugene Krasnikov 	wcn36xx_smd_update_cfg(wcn, WCN36XX_HAL_CFG_RTS_THRESHOLD, value);
8868e84c258SEugene Krasnikov 	return 0;
8878e84c258SEugene Krasnikov }
8888e84c258SEugene Krasnikov 
8898e84c258SEugene Krasnikov static void wcn36xx_remove_interface(struct ieee80211_hw *hw,
8908e84c258SEugene Krasnikov 				     struct ieee80211_vif *vif)
8918e84c258SEugene Krasnikov {
8928e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
893ce75877fSPontus Fuchs 	struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif);
8948e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac remove interface vif %p\n", vif);
8958e84c258SEugene Krasnikov 
8968e84c258SEugene Krasnikov 	list_del(&vif_priv->list);
8978e84c258SEugene Krasnikov 	wcn36xx_smd_delete_sta_self(wcn, vif->addr);
8988e84c258SEugene Krasnikov }
8998e84c258SEugene Krasnikov 
9008e84c258SEugene Krasnikov static int wcn36xx_add_interface(struct ieee80211_hw *hw,
9018e84c258SEugene Krasnikov 				 struct ieee80211_vif *vif)
9028e84c258SEugene Krasnikov {
9038e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
904ce75877fSPontus Fuchs 	struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif);
9058e84c258SEugene Krasnikov 
9068e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac add interface vif %p type %d\n",
9078e84c258SEugene Krasnikov 		    vif, vif->type);
9088e84c258SEugene Krasnikov 
9098e84c258SEugene Krasnikov 	if (!(NL80211_IFTYPE_STATION == vif->type ||
9108e84c258SEugene Krasnikov 	      NL80211_IFTYPE_AP == vif->type ||
9118e84c258SEugene Krasnikov 	      NL80211_IFTYPE_ADHOC == vif->type ||
9128e84c258SEugene Krasnikov 	      NL80211_IFTYPE_MESH_POINT == vif->type)) {
9138e84c258SEugene Krasnikov 		wcn36xx_warn("Unsupported interface type requested: %d\n",
9148e84c258SEugene Krasnikov 			     vif->type);
9158e84c258SEugene Krasnikov 		return -EOPNOTSUPP;
9168e84c258SEugene Krasnikov 	}
9178e84c258SEugene Krasnikov 
9188e84c258SEugene Krasnikov 	list_add(&vif_priv->list, &wcn->vif_list);
9198e84c258SEugene Krasnikov 	wcn36xx_smd_add_sta_self(wcn, vif);
9208e84c258SEugene Krasnikov 
9218e84c258SEugene Krasnikov 	return 0;
9228e84c258SEugene Krasnikov }
9238e84c258SEugene Krasnikov 
9248e84c258SEugene Krasnikov static int wcn36xx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
9258e84c258SEugene Krasnikov 			   struct ieee80211_sta *sta)
9268e84c258SEugene Krasnikov {
9278e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
928ce75877fSPontus Fuchs 	struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif);
929a92e4696SPontus Fuchs 	struct wcn36xx_sta *sta_priv = wcn36xx_sta_to_priv(sta);
9308e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac sta add vif %p sta %pM\n",
9318e84c258SEugene Krasnikov 		    vif, sta->addr);
9328e84c258SEugene Krasnikov 
933e26dc173SBob Copeland 	spin_lock_init(&sta_priv->ampdu_lock);
9348e84c258SEugene Krasnikov 	sta_priv->vif = vif_priv;
9358e84c258SEugene Krasnikov 	/*
9368e84c258SEugene Krasnikov 	 * For STA mode HW will be configured on BSS_CHANGED_ASSOC because
9378e84c258SEugene Krasnikov 	 * at this stage AID is not available yet.
9388e84c258SEugene Krasnikov 	 */
9398e84c258SEugene Krasnikov 	if (NL80211_IFTYPE_STATION != vif->type) {
9408e84c258SEugene Krasnikov 		wcn36xx_update_allowed_rates(sta, WCN36XX_BAND(wcn));
9418e84c258SEugene Krasnikov 		sta_priv->aid = sta->aid;
9428e84c258SEugene Krasnikov 		wcn36xx_smd_config_sta(wcn, vif, sta);
9438e84c258SEugene Krasnikov 	}
9448e84c258SEugene Krasnikov 	return 0;
9458e84c258SEugene Krasnikov }
9468e84c258SEugene Krasnikov 
9478e84c258SEugene Krasnikov static int wcn36xx_sta_remove(struct ieee80211_hw *hw,
9488e84c258SEugene Krasnikov 			      struct ieee80211_vif *vif,
9498e84c258SEugene Krasnikov 			      struct ieee80211_sta *sta)
9508e84c258SEugene Krasnikov {
9518e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
952a92e4696SPontus Fuchs 	struct wcn36xx_sta *sta_priv = wcn36xx_sta_to_priv(sta);
9538e84c258SEugene Krasnikov 
9548e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac sta remove vif %p sta %pM index %d\n",
9558e84c258SEugene Krasnikov 		    vif, sta->addr, sta_priv->sta_index);
9568e84c258SEugene Krasnikov 
9578e84c258SEugene Krasnikov 	wcn36xx_smd_delete_sta(wcn, sta_priv->sta_index);
9588e84c258SEugene Krasnikov 	sta_priv->vif = NULL;
9598e84c258SEugene Krasnikov 	return 0;
9608e84c258SEugene Krasnikov }
9618e84c258SEugene Krasnikov 
9628e84c258SEugene Krasnikov #ifdef CONFIG_PM
9638e84c258SEugene Krasnikov 
9648e84c258SEugene Krasnikov static int wcn36xx_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wow)
9658e84c258SEugene Krasnikov {
9668e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
9678e84c258SEugene Krasnikov 
9688e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac suspend\n");
9698e84c258SEugene Krasnikov 
9708e84c258SEugene Krasnikov 	flush_workqueue(wcn->hal_ind_wq);
9718e84c258SEugene Krasnikov 	wcn36xx_smd_set_power_params(wcn, true);
9728e84c258SEugene Krasnikov 	return 0;
9738e84c258SEugene Krasnikov }
9748e84c258SEugene Krasnikov 
9758e84c258SEugene Krasnikov static int wcn36xx_resume(struct ieee80211_hw *hw)
9768e84c258SEugene Krasnikov {
9778e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
9788e84c258SEugene Krasnikov 
9798e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac resume\n");
9808e84c258SEugene Krasnikov 
9818e84c258SEugene Krasnikov 	flush_workqueue(wcn->hal_ind_wq);
9828e84c258SEugene Krasnikov 	wcn36xx_smd_set_power_params(wcn, false);
9838e84c258SEugene Krasnikov 	return 0;
9848e84c258SEugene Krasnikov }
9858e84c258SEugene Krasnikov 
9868e84c258SEugene Krasnikov #endif
9878e84c258SEugene Krasnikov 
9888e84c258SEugene Krasnikov static int wcn36xx_ampdu_action(struct ieee80211_hw *hw,
9898e84c258SEugene Krasnikov 		    struct ieee80211_vif *vif,
99050ea05efSSara Sharon 		    struct ieee80211_ampdu_params *params)
9918e84c258SEugene Krasnikov {
9928e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
993a92e4696SPontus Fuchs 	struct wcn36xx_sta *sta_priv = wcn36xx_sta_to_priv(params->sta);
99450ea05efSSara Sharon 	struct ieee80211_sta *sta = params->sta;
99550ea05efSSara Sharon 	enum ieee80211_ampdu_mlme_action action = params->action;
99650ea05efSSara Sharon 	u16 tid = params->tid;
99750ea05efSSara Sharon 	u16 *ssn = &params->ssn;
9988e84c258SEugene Krasnikov 
9998e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "mac ampdu action action %d tid %d\n",
10008e84c258SEugene Krasnikov 		    action, tid);
10018e84c258SEugene Krasnikov 
10028e84c258SEugene Krasnikov 	switch (action) {
10038e84c258SEugene Krasnikov 	case IEEE80211_AMPDU_RX_START:
10048e84c258SEugene Krasnikov 		sta_priv->tid = tid;
10058e84c258SEugene Krasnikov 		wcn36xx_smd_add_ba_session(wcn, sta, tid, ssn, 0,
10068e84c258SEugene Krasnikov 			get_sta_index(vif, sta_priv));
10078e84c258SEugene Krasnikov 		wcn36xx_smd_add_ba(wcn);
10088e84c258SEugene Krasnikov 		wcn36xx_smd_trigger_ba(wcn, get_sta_index(vif, sta_priv));
10098e84c258SEugene Krasnikov 		break;
10108e84c258SEugene Krasnikov 	case IEEE80211_AMPDU_RX_STOP:
10118e84c258SEugene Krasnikov 		wcn36xx_smd_del_ba(wcn, tid, get_sta_index(vif, sta_priv));
10128e84c258SEugene Krasnikov 		break;
10138e84c258SEugene Krasnikov 	case IEEE80211_AMPDU_TX_START:
1014e26dc173SBob Copeland 		spin_lock_bh(&sta_priv->ampdu_lock);
1015e26dc173SBob Copeland 		sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_START;
1016e26dc173SBob Copeland 		spin_unlock_bh(&sta_priv->ampdu_lock);
1017e26dc173SBob Copeland 
10188e84c258SEugene Krasnikov 		ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid);
10198e84c258SEugene Krasnikov 		break;
10208e84c258SEugene Krasnikov 	case IEEE80211_AMPDU_TX_OPERATIONAL:
1021e26dc173SBob Copeland 		spin_lock_bh(&sta_priv->ampdu_lock);
1022e26dc173SBob Copeland 		sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_OPERATIONAL;
1023e26dc173SBob Copeland 		spin_unlock_bh(&sta_priv->ampdu_lock);
1024e26dc173SBob Copeland 
10258e84c258SEugene Krasnikov 		wcn36xx_smd_add_ba_session(wcn, sta, tid, ssn, 1,
10268e84c258SEugene Krasnikov 			get_sta_index(vif, sta_priv));
10278e84c258SEugene Krasnikov 		break;
10288e84c258SEugene Krasnikov 	case IEEE80211_AMPDU_TX_STOP_FLUSH:
10298e84c258SEugene Krasnikov 	case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
10308e84c258SEugene Krasnikov 	case IEEE80211_AMPDU_TX_STOP_CONT:
1031e26dc173SBob Copeland 		spin_lock_bh(&sta_priv->ampdu_lock);
1032e26dc173SBob Copeland 		sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_NONE;
1033e26dc173SBob Copeland 		spin_unlock_bh(&sta_priv->ampdu_lock);
1034e26dc173SBob Copeland 
10358e84c258SEugene Krasnikov 		ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
10368e84c258SEugene Krasnikov 		break;
10378e84c258SEugene Krasnikov 	default:
10388e84c258SEugene Krasnikov 		wcn36xx_err("Unknown AMPDU action\n");
10398e84c258SEugene Krasnikov 	}
10408e84c258SEugene Krasnikov 
10418e84c258SEugene Krasnikov 	return 0;
10428e84c258SEugene Krasnikov }
10438e84c258SEugene Krasnikov 
10448e84c258SEugene Krasnikov static const struct ieee80211_ops wcn36xx_ops = {
10458e84c258SEugene Krasnikov 	.start			= wcn36xx_start,
10468e84c258SEugene Krasnikov 	.stop			= wcn36xx_stop,
10478e84c258SEugene Krasnikov 	.add_interface		= wcn36xx_add_interface,
10488e84c258SEugene Krasnikov 	.remove_interface	= wcn36xx_remove_interface,
10498e84c258SEugene Krasnikov #ifdef CONFIG_PM
10508e84c258SEugene Krasnikov 	.suspend		= wcn36xx_suspend,
10518e84c258SEugene Krasnikov 	.resume			= wcn36xx_resume,
10528e84c258SEugene Krasnikov #endif
10538e84c258SEugene Krasnikov 	.config			= wcn36xx_config,
105420a779edSPontus Fuchs 	.prepare_multicast	= wcn36xx_prepare_multicast,
10558e84c258SEugene Krasnikov 	.configure_filter       = wcn36xx_configure_filter,
10568e84c258SEugene Krasnikov 	.tx			= wcn36xx_tx,
10578e84c258SEugene Krasnikov 	.set_key		= wcn36xx_set_key,
105888603903SBjorn Andersson 	.hw_scan		= wcn36xx_hw_scan,
105903c95dbeSBjorn Andersson 	.cancel_hw_scan		= wcn36xx_cancel_hw_scan,
10608e84c258SEugene Krasnikov 	.bss_info_changed	= wcn36xx_bss_info_changed,
10618e84c258SEugene Krasnikov 	.set_rts_threshold	= wcn36xx_set_rts_threshold,
10628e84c258SEugene Krasnikov 	.sta_add		= wcn36xx_sta_add,
10638e84c258SEugene Krasnikov 	.sta_remove		= wcn36xx_sta_remove,
10648e84c258SEugene Krasnikov 	.ampdu_action		= wcn36xx_ampdu_action,
10658e84c258SEugene Krasnikov };
10668e84c258SEugene Krasnikov 
10678e84c258SEugene Krasnikov static int wcn36xx_init_ieee80211(struct wcn36xx *wcn)
10688e84c258SEugene Krasnikov {
10698e84c258SEugene Krasnikov 	int ret = 0;
10708e84c258SEugene Krasnikov 
10718e84c258SEugene Krasnikov 	static const u32 cipher_suites[] = {
10728e84c258SEugene Krasnikov 		WLAN_CIPHER_SUITE_WEP40,
10738e84c258SEugene Krasnikov 		WLAN_CIPHER_SUITE_WEP104,
10748e84c258SEugene Krasnikov 		WLAN_CIPHER_SUITE_TKIP,
10758e84c258SEugene Krasnikov 		WLAN_CIPHER_SUITE_CCMP,
10768e84c258SEugene Krasnikov 	};
10778e84c258SEugene Krasnikov 
107830686bf7SJohannes Berg 	ieee80211_hw_set(wcn->hw, TIMING_BEACON_ONLY);
107930686bf7SJohannes Berg 	ieee80211_hw_set(wcn->hw, AMPDU_AGGREGATION);
108030686bf7SJohannes Berg 	ieee80211_hw_set(wcn->hw, CONNECTION_MONITOR);
108130686bf7SJohannes Berg 	ieee80211_hw_set(wcn->hw, SUPPORTS_PS);
108230686bf7SJohannes Berg 	ieee80211_hw_set(wcn->hw, SIGNAL_DBM);
108330686bf7SJohannes Berg 	ieee80211_hw_set(wcn->hw, HAS_RATE_CONTROL);
108488603903SBjorn Andersson 	ieee80211_hw_set(wcn->hw, SINGLE_SCAN_ON_ALL_BANDS);
10858e84c258SEugene Krasnikov 
10868e84c258SEugene Krasnikov 	wcn->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
10878e84c258SEugene Krasnikov 		BIT(NL80211_IFTYPE_AP) |
10888e84c258SEugene Krasnikov 		BIT(NL80211_IFTYPE_ADHOC) |
10898e84c258SEugene Krasnikov 		BIT(NL80211_IFTYPE_MESH_POINT);
10908e84c258SEugene Krasnikov 
109157fbcce3SJohannes Berg 	wcn->hw->wiphy->bands[NL80211_BAND_2GHZ] = &wcn_band_2ghz;
109257fbcce3SJohannes Berg 	wcn->hw->wiphy->bands[NL80211_BAND_5GHZ] = &wcn_band_5ghz;
10938e84c258SEugene Krasnikov 
109488603903SBjorn Andersson 	wcn->hw->wiphy->max_scan_ssids = WCN36XX_MAX_SCAN_SSIDS;
109588603903SBjorn Andersson 	wcn->hw->wiphy->max_scan_ie_len = WCN36XX_MAX_SCAN_IE_LEN;
109688603903SBjorn Andersson 
10978e84c258SEugene Krasnikov 	wcn->hw->wiphy->cipher_suites = cipher_suites;
10988e84c258SEugene Krasnikov 	wcn->hw->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites);
10998e84c258SEugene Krasnikov 
11008e84c258SEugene Krasnikov 	wcn->hw->wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD;
11018e84c258SEugene Krasnikov 
11028e84c258SEugene Krasnikov #ifdef CONFIG_PM
11038e84c258SEugene Krasnikov 	wcn->hw->wiphy->wowlan = &wowlan_support;
11048e84c258SEugene Krasnikov #endif
11058e84c258SEugene Krasnikov 
11068e84c258SEugene Krasnikov 	wcn->hw->max_listen_interval = 200;
11078e84c258SEugene Krasnikov 
11088e84c258SEugene Krasnikov 	wcn->hw->queues = 4;
11098e84c258SEugene Krasnikov 
11108e84c258SEugene Krasnikov 	SET_IEEE80211_DEV(wcn->hw, wcn->dev);
11118e84c258SEugene Krasnikov 
11128e84c258SEugene Krasnikov 	wcn->hw->sta_data_size = sizeof(struct wcn36xx_sta);
11138e84c258SEugene Krasnikov 	wcn->hw->vif_data_size = sizeof(struct wcn36xx_vif);
11148e84c258SEugene Krasnikov 
11158e84c258SEugene Krasnikov 	return ret;
11168e84c258SEugene Krasnikov }
11178e84c258SEugene Krasnikov 
11188e84c258SEugene Krasnikov static int wcn36xx_platform_get_resources(struct wcn36xx *wcn,
11198e84c258SEugene Krasnikov 					  struct platform_device *pdev)
11208e84c258SEugene Krasnikov {
112105ddce49SBjorn Andersson 	struct device_node *mmio_node;
11228e84c258SEugene Krasnikov 	struct resource *res;
112305ddce49SBjorn Andersson 	int index;
112405ddce49SBjorn Andersson 	int ret;
112505ddce49SBjorn Andersson 
11268e84c258SEugene Krasnikov 	/* Set TX IRQ */
1127f303a931SBjorn Andersson 	res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "tx");
11288e84c258SEugene Krasnikov 	if (!res) {
11298e84c258SEugene Krasnikov 		wcn36xx_err("failed to get tx_irq\n");
11308e84c258SEugene Krasnikov 		return -ENOENT;
11318e84c258SEugene Krasnikov 	}
11328e84c258SEugene Krasnikov 	wcn->tx_irq = res->start;
11338e84c258SEugene Krasnikov 
11348e84c258SEugene Krasnikov 	/* Set RX IRQ */
1135f303a931SBjorn Andersson 	res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "rx");
11368e84c258SEugene Krasnikov 	if (!res) {
11378e84c258SEugene Krasnikov 		wcn36xx_err("failed to get rx_irq\n");
11388e84c258SEugene Krasnikov 		return -ENOENT;
11398e84c258SEugene Krasnikov 	}
11408e84c258SEugene Krasnikov 	wcn->rx_irq = res->start;
11418e84c258SEugene Krasnikov 
1142f303a931SBjorn Andersson 	/* Acquire SMSM tx enable handle */
1143f303a931SBjorn Andersson 	wcn->tx_enable_state = qcom_smem_state_get(&pdev->dev,
1144f303a931SBjorn Andersson 			"tx-enable", &wcn->tx_enable_state_bit);
1145f303a931SBjorn Andersson 	if (IS_ERR(wcn->tx_enable_state)) {
1146f303a931SBjorn Andersson 		wcn36xx_err("failed to get tx-enable state\n");
1147f303a931SBjorn Andersson 		return PTR_ERR(wcn->tx_enable_state);
1148f303a931SBjorn Andersson 	}
1149f303a931SBjorn Andersson 
1150f303a931SBjorn Andersson 	/* Acquire SMSM tx rings empty handle */
1151f303a931SBjorn Andersson 	wcn->tx_rings_empty_state = qcom_smem_state_get(&pdev->dev,
1152f303a931SBjorn Andersson 			"tx-rings-empty", &wcn->tx_rings_empty_state_bit);
1153f303a931SBjorn Andersson 	if (IS_ERR(wcn->tx_rings_empty_state)) {
1154f303a931SBjorn Andersson 		wcn36xx_err("failed to get tx-rings-empty state\n");
1155f303a931SBjorn Andersson 		return PTR_ERR(wcn->tx_rings_empty_state);
1156f303a931SBjorn Andersson 	}
1157f303a931SBjorn Andersson 
115805ddce49SBjorn Andersson 	mmio_node = of_parse_phandle(pdev->dev.parent->of_node, "qcom,mmio", 0);
115905ddce49SBjorn Andersson 	if (!mmio_node) {
116005ddce49SBjorn Andersson 		wcn36xx_err("failed to acquire qcom,mmio reference\n");
116105ddce49SBjorn Andersson 		return -EINVAL;
11628e84c258SEugene Krasnikov 	}
116305ddce49SBjorn Andersson 
11646f10b4e1SBjorn Andersson 	wcn->is_pronto = !!of_device_is_compatible(mmio_node, "qcom,pronto");
11656f10b4e1SBjorn Andersson 
116605ddce49SBjorn Andersson 	/* Map the CCU memory */
116705ddce49SBjorn Andersson 	index = of_property_match_string(mmio_node, "reg-names", "ccu");
116805ddce49SBjorn Andersson 	wcn->ccu_base = of_iomap(mmio_node, index);
116905ddce49SBjorn Andersson 	if (!wcn->ccu_base) {
117005ddce49SBjorn Andersson 		wcn36xx_err("failed to map ccu memory\n");
117105ddce49SBjorn Andersson 		ret = -ENOMEM;
117205ddce49SBjorn Andersson 		goto put_mmio_node;
11738e84c258SEugene Krasnikov 	}
117405ddce49SBjorn Andersson 
117505ddce49SBjorn Andersson 	/* Map the DXE memory */
117605ddce49SBjorn Andersson 	index = of_property_match_string(mmio_node, "reg-names", "dxe");
117705ddce49SBjorn Andersson 	wcn->dxe_base = of_iomap(mmio_node, index);
117805ddce49SBjorn Andersson 	if (!wcn->dxe_base) {
117905ddce49SBjorn Andersson 		wcn36xx_err("failed to map dxe memory\n");
118005ddce49SBjorn Andersson 		ret = -ENOMEM;
118105ddce49SBjorn Andersson 		goto unmap_ccu;
118205ddce49SBjorn Andersson 	}
118305ddce49SBjorn Andersson 
118405ddce49SBjorn Andersson 	of_node_put(mmio_node);
11858e84c258SEugene Krasnikov 	return 0;
118605ddce49SBjorn Andersson 
118705ddce49SBjorn Andersson unmap_ccu:
118805ddce49SBjorn Andersson 	iounmap(wcn->ccu_base);
118905ddce49SBjorn Andersson put_mmio_node:
119005ddce49SBjorn Andersson 	of_node_put(mmio_node);
119105ddce49SBjorn Andersson 	return ret;
11928e84c258SEugene Krasnikov }
11938e84c258SEugene Krasnikov 
11948e84c258SEugene Krasnikov static int wcn36xx_probe(struct platform_device *pdev)
11958e84c258SEugene Krasnikov {
11968e84c258SEugene Krasnikov 	struct ieee80211_hw *hw;
11978e84c258SEugene Krasnikov 	struct wcn36xx *wcn;
1198f303a931SBjorn Andersson 	void *wcnss;
11998e84c258SEugene Krasnikov 	int ret;
1200f303a931SBjorn Andersson 	const u8 *addr;
12018e84c258SEugene Krasnikov 
12028e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "platform probe\n");
12038e84c258SEugene Krasnikov 
1204f303a931SBjorn Andersson 	wcnss = dev_get_drvdata(pdev->dev.parent);
1205f303a931SBjorn Andersson 
12068e84c258SEugene Krasnikov 	hw = ieee80211_alloc_hw(sizeof(struct wcn36xx), &wcn36xx_ops);
12078e84c258SEugene Krasnikov 	if (!hw) {
12088e84c258SEugene Krasnikov 		wcn36xx_err("failed to alloc hw\n");
12098e84c258SEugene Krasnikov 		ret = -ENOMEM;
12108e84c258SEugene Krasnikov 		goto out_err;
12118e84c258SEugene Krasnikov 	}
12128e84c258SEugene Krasnikov 	platform_set_drvdata(pdev, hw);
12138e84c258SEugene Krasnikov 	wcn = hw->priv;
12148e84c258SEugene Krasnikov 	wcn->hw = hw;
12158e84c258SEugene Krasnikov 	wcn->dev = &pdev->dev;
12168e84c258SEugene Krasnikov 	mutex_init(&wcn->hal_mutex);
121788603903SBjorn Andersson 	mutex_init(&wcn->scan_lock);
121888603903SBjorn Andersson 
121988603903SBjorn Andersson 	INIT_WORK(&wcn->scan_work, wcn36xx_hw_scan_worker);
12208e84c258SEugene Krasnikov 
1221f303a931SBjorn Andersson 	wcn->smd_channel = qcom_wcnss_open_channel(wcnss, "WLAN_CTRL", wcn36xx_smd_rsp_process);
1222f303a931SBjorn Andersson 	if (IS_ERR(wcn->smd_channel)) {
1223f303a931SBjorn Andersson 		wcn36xx_err("failed to open WLAN_CTRL channel\n");
1224f303a931SBjorn Andersson 		ret = PTR_ERR(wcn->smd_channel);
1225f303a931SBjorn Andersson 		goto out_wq;
1226f303a931SBjorn Andersson 	}
1227f303a931SBjorn Andersson 
1228f303a931SBjorn Andersson 	qcom_smd_set_drvdata(wcn->smd_channel, hw);
1229f303a931SBjorn Andersson 
1230f303a931SBjorn Andersson 	addr = of_get_property(pdev->dev.of_node, "local-mac-address", &ret);
1231f303a931SBjorn Andersson 	if (addr && ret != ETH_ALEN) {
1232f303a931SBjorn Andersson 		wcn36xx_err("invalid local-mac-address\n");
1233f303a931SBjorn Andersson 		ret = -EINVAL;
1234f303a931SBjorn Andersson 		goto out_wq;
1235f303a931SBjorn Andersson 	} else if (addr) {
12368e84c258SEugene Krasnikov 		wcn36xx_info("mac address: %pM\n", addr);
12378e84c258SEugene Krasnikov 		SET_IEEE80211_PERM_ADDR(wcn->hw, addr);
12388e84c258SEugene Krasnikov 	}
12398e84c258SEugene Krasnikov 
12408e84c258SEugene Krasnikov 	ret = wcn36xx_platform_get_resources(wcn, pdev);
12418e84c258SEugene Krasnikov 	if (ret)
12428e84c258SEugene Krasnikov 		goto out_wq;
12438e84c258SEugene Krasnikov 
12448e84c258SEugene Krasnikov 	wcn36xx_init_ieee80211(wcn);
12458e84c258SEugene Krasnikov 	ret = ieee80211_register_hw(wcn->hw);
12468e84c258SEugene Krasnikov 	if (ret)
12478e84c258SEugene Krasnikov 		goto out_unmap;
12488e84c258SEugene Krasnikov 
12498e84c258SEugene Krasnikov 	return 0;
12508e84c258SEugene Krasnikov 
12518e84c258SEugene Krasnikov out_unmap:
125205ddce49SBjorn Andersson 	iounmap(wcn->ccu_base);
125305ddce49SBjorn Andersson 	iounmap(wcn->dxe_base);
12548e84c258SEugene Krasnikov out_wq:
12558e84c258SEugene Krasnikov 	ieee80211_free_hw(hw);
12568e84c258SEugene Krasnikov out_err:
12578e84c258SEugene Krasnikov 	return ret;
12588e84c258SEugene Krasnikov }
1259f303a931SBjorn Andersson 
12608e84c258SEugene Krasnikov static int wcn36xx_remove(struct platform_device *pdev)
12618e84c258SEugene Krasnikov {
12628e84c258SEugene Krasnikov 	struct ieee80211_hw *hw = platform_get_drvdata(pdev);
12638e84c258SEugene Krasnikov 	struct wcn36xx *wcn = hw->priv;
12648e84c258SEugene Krasnikov 	wcn36xx_dbg(WCN36XX_DBG_MAC, "platform remove\n");
12658e84c258SEugene Krasnikov 
12664bda7fafSPontus Fuchs 	release_firmware(wcn->nv);
12678e84c258SEugene Krasnikov 
12688e84c258SEugene Krasnikov 	ieee80211_unregister_hw(hw);
1269f303a931SBjorn Andersson 
1270f303a931SBjorn Andersson 	qcom_smem_state_put(wcn->tx_enable_state);
1271f303a931SBjorn Andersson 	qcom_smem_state_put(wcn->tx_rings_empty_state);
1272f303a931SBjorn Andersson 
127305ddce49SBjorn Andersson 	iounmap(wcn->dxe_base);
127405ddce49SBjorn Andersson 	iounmap(wcn->ccu_base);
1275d5362888SBjorn Andersson 
1276d5362888SBjorn Andersson 	mutex_destroy(&wcn->hal_mutex);
12778e84c258SEugene Krasnikov 	ieee80211_free_hw(hw);
12788e84c258SEugene Krasnikov 
12798e84c258SEugene Krasnikov 	return 0;
12808e84c258SEugene Krasnikov }
1281f303a931SBjorn Andersson 
1282f303a931SBjorn Andersson static const struct of_device_id wcn36xx_of_match[] = {
1283f303a931SBjorn Andersson 	{ .compatible = "qcom,wcnss-wlan" },
12848e84c258SEugene Krasnikov 	{}
12858e84c258SEugene Krasnikov };
1286f303a931SBjorn Andersson MODULE_DEVICE_TABLE(of, wcn36xx_of_match);
12878e84c258SEugene Krasnikov 
12888e84c258SEugene Krasnikov static struct platform_driver wcn36xx_driver = {
12898e84c258SEugene Krasnikov 	.probe      = wcn36xx_probe,
12908e84c258SEugene Krasnikov 	.remove     = wcn36xx_remove,
12918e84c258SEugene Krasnikov 	.driver         = {
12928e84c258SEugene Krasnikov 		.name   = "wcn36xx",
1293f303a931SBjorn Andersson 		.of_match_table = wcn36xx_of_match,
12948e84c258SEugene Krasnikov 	},
12958e84c258SEugene Krasnikov };
12968e84c258SEugene Krasnikov 
1297f303a931SBjorn Andersson module_platform_driver(wcn36xx_driver);
12988e84c258SEugene Krasnikov 
12998e84c258SEugene Krasnikov MODULE_LICENSE("Dual BSD/GPL");
13008e84c258SEugene Krasnikov MODULE_AUTHOR("Eugene Krasnikov k.eugene.e@gmail.com");
13018e84c258SEugene Krasnikov MODULE_FIRMWARE(WLAN_NV_FILE);
1302