diff mbox series

[RFC,v2,38/96] cl8k: add mac80211.c

Message ID 20220524113502.1094459-39-viktor.barna@celeno.com
State New
Headers show
Series wireless: cl8k driver for Celeno IEEE 802.11ax devices | expand

Commit Message

Viktor Barna May 24, 2022, 11:34 a.m. UTC
From: Viktor Barna <viktor.barna@celeno.com>

(Part of the split. Please, take a look at the cover letter for more
details).

Signed-off-by: Viktor Barna <viktor.barna@celeno.com>
---
 drivers/net/wireless/celeno/cl8k/mac80211.c | 2392 +++++++++++++++++++
 1 file changed, 2392 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/mac80211.c

Comments

Johannes Berg May 26, 2022, 7:49 p.m. UTC | #1
On Tue, 2022-05-24 at 14:34 +0300, viktor.barna@celeno.com wrote:
> 
> +static void cl_ops_tx_single(struct cl_hw *cl_hw,
> +			     struct sk_buff *skb,
> +			     struct ieee80211_tx_info *tx_info,
> +			     struct cl_sta *cl_sta,
> +			     struct ieee80211_sta *sta)
> +{

...

> +	if (sta) {
> +		u32 sta_vht_cap = sta->vht_cap.cap;
> +		struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
> +
> +		if (!(sta_vht_cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK))
> +			goto out_tx;
> +
> +		if (ieee80211_is_assoc_resp(mgmt->frame_control)) {
> +			int len = skb->len - (mgmt->u.assoc_resp.variable - skb->data);
> +			const u8 *vht_cap_addr = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY,
> +								 mgmt->u.assoc_resp.variable,
> +								 len);
> +
> +			if (vht_cap_addr) {
> +				struct ieee80211_vht_cap *vht_cap =
> +					(struct ieee80211_vht_cap *)(2 + vht_cap_addr);
> +
> +				vht_cap->vht_cap_info &=
> +					~(cpu_to_le32(IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK |
> +						      IEEE80211_VHT_CAP_SHORT_GI_160));
> +			}

Huh??


> +int cl_ops_config(struct ieee80211_hw *hw, u32 changed)
> +{
> +	/*
> +	 * Handler for configuration requests. IEEE 802.11 code calls this
> +	 * function to change hardware configuration, e.g., channel.
> +	 * This function should never fail but returns a negative error code
> +	 * if it does. The callback can sleep
> +	 */
> +	int error = 0;
> +
> +	if (changed & IEEE80211_CONF_CHANGE_CHANNEL)
> +		error = cl_ops_conf_change_channel(hw);

I'm really surprised to see this callback in a modern driver - wouldn't
you want to support some form of multi-channel operation? Even just
using the chanctx callbacks might make some of the DFS things you have
there easier?


> 
> +void cl_ops_bss_info_changed(struct ieee80211_hw *hw,
> +			     struct ieee80211_vif *vif,
> +			     struct ieee80211_bss_conf *info,
> +			     u32 changed)
> +{

...

> +		if (beacon) {
> +			size_t ies_len = beacon->tail_len;
> +			const u8 *ies = beacon->tail;
> +			const u8 *cap =  NULL;
> +			int var_offset = offsetof(struct ieee80211_mgmt, u.beacon.variable);
> +			int len = beacon->head_len - var_offset;
> +			const u8 *var_pos = beacon->head + var_offset;
> +			const u8 *rate_ie = NULL;
> +
> +			cl_vif->wmm_enabled = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT,
> +								      WLAN_OUI_TYPE_MICROSOFT_WMM,
> +								      ies,
> +								      ies_len);
> +			cl_dbg_info(cl_hw, "vif=%d wmm_enabled=%d\n",
> +				    cl_vif->vif_index,
> +				    cl_vif->wmm_enabled);
> +
> +			cap = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, ies, ies_len);
> +			if (cap && cap[1] >= sizeof(*ht_cap)) {
> +				ht_cap = (void *)(cap + 2);
> +				sgi_en |= (le16_to_cpu(ht_cap->cap_info) &
> +					   IEEE80211_HT_CAP_SGI_20) ||
> +					  (le16_to_cpu(ht_cap->cap_info) &
> +					   IEEE80211_HT_CAP_SGI_40);
> +			}
> +
> +			cap = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, ies, ies_len);
> +			if (cap && cap[1] >= sizeof(*vht_cap)) {
> +				vht_cap = (void *)(cap + 2);
> +				sgi_en |= (le32_to_cpu(vht_cap->vht_cap_info) &
> +					   IEEE80211_VHT_CAP_SHORT_GI_80) ||
> +					  (le32_to_cpu(vht_cap->vht_cap_info) &
> +					   IEEE80211_VHT_CAP_SHORT_GI_160);
> +			}
> +
> +			cap = cfg80211_find_ext_ie(WLAN_EID_EXT_HE_CAPABILITY, ies, ies_len);
> +			if (cap && cap[1] >= sizeof(*he_cap) + 1)
> +				he_cap = (void *)(cap + 3);
> +
> +			rate_ie = cfg80211_find_ie(WLAN_EID_SUPP_RATES, var_pos, len);
> +			if (rate_ie) {
> +				if (cl_band_is_24g(cl_hw))
> +					if (cl_is_valid_g_rates(rate_ie))
> +						hw_mode = cl_hw->conf->ci_cck_in_hw_mode ?
> +							     HW_MODE_BG : HW_MODE_G;
> +					else
> +						hw_mode = HW_MODE_B;
> +				else
> +					hw_mode = HW_MODE_A;
> +			}
> +		} else {
> +			cl_dbg_warn(cl_hw, "beacon_data not set!\n");
> +		}
> +

This feels ... odd. You really shouldn't have to look into the beacon to
figure out these things?

And SGI etc. are per-STA rate control parameters anyway? Hmm.

> +/* This function is required for PS flow - do not remove */
> +int cl_ops_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set)
> +{
> +	return 0;
> +}

You have all this hardware/firmware and you implement this? Interesting
design choice. One that I'm sure you'll revisit for WiFi 7 ;-)

johannes
Viktor Barna July 11, 2022, 11:13 p.m. UTC | #2
On Thu, 26 May 2022 21:49:17 +0200, johannes@sipsolutions.net wrote:
> I'm really surprised to see this callback in a modern driver - wouldn't
> you want to support some form of multi-channel operation? Even just
> using the chanctx callbacks might make some of the DFS things you have
> there easier?

That is a good point. We picked the “old” channel API, which took less time to
implement – the benefits of channel context were not clear enough during that
time.

> This feels ... odd. You really shouldn't have to look into the beacon to
> figure out these things?
> 
> And SGI etc. are per-STA rate control parameters anyway? Hmm.

Information from this dynamic parsing is required for our driver and FW to
function properly.

> You have all this hardware/firmware and you implement this? Interesting
> design choice. One that I'm sure you'll revisit for WiFi 7 ;-)

Actually, the driver is doing that in cl_tx_handle_beacon_tim function as a
result of TBTT interrupt. However, the next version of the RFC will be without
that code – we are moving the routine to the FW side due to timing issues in
the multiclient setup.

Best regards,
Viktor Barna
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/mac80211.c b/drivers/net/wireless/celeno/cl8k/mac80211.c
new file mode 100644
index 000000000000..13989327ccdb
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/mac80211.c
@@ -0,0 +1,2392 @@ 
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/log2.h>
+#include <net/mac80211.h>
+
+#include "debug.h"
+#include "utils.h"
+#include "sta.h"
+#include "dfs.h"
+#include "regdom.h"
+#include "hw.h"
+#include "mac80211.h"
+#include "ampdu.h"
+#include "tx.h"
+#include "radio.h"
+#include "recovery.h"
+#include "rates.h"
+#include "temperature.h"
+#include "vns.h"
+#include "key.h"
+#include "version.h"
+#include "power.h"
+#include "stats.h"
+#include "scan.h"
+#include "mac_addr.h"
+#include "chip.h"
+
+#define RATE_1_MBPS   10
+#define RATE_2_MBPS   20
+#define RATE_5_5_MBPS 55
+#define RATE_11_MBPS  110
+#define RATE_6_MBPS   60
+#define RATE_9_MBPS   90
+#define RATE_12_MBPS  120
+#define RATE_18_MBPS  180
+#define RATE_24_MBPS  240
+#define RATE_36_MBPS  360
+#define RATE_48_MBPS  480
+#define RATE_54_MBPS  540
+
+#define CL_HT_CAPABILITIES                                              \
+{                                                                       \
+	.ht_supported = true,                                           \
+	.cap = IEEE80211_HT_CAP_DSSSCCK40 | IEEE80211_HT_CAP_MAX_AMSDU, \
+	.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,                     \
+	.ampdu_density = IEEE80211_HT_MPDU_DENSITY_1,                   \
+	.mcs = {                                                        \
+		.rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0 },         \
+		.tx_params = IEEE80211_HT_MCS_TX_DEFINED,               \
+	},                                                              \
+}
+
+#define CL_VHT_CAPABILITIES                                             \
+{                                                                       \
+	.vht_supported = false,                                         \
+	.cap = 0,                                                       \
+	.vht_mcs = {                                                    \
+		.rx_mcs_map = cpu_to_le16(                              \
+				IEEE80211_VHT_MCS_SUPPORT_0_7   << 0  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 2  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 4  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 6  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 8  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 14), \
+		.tx_mcs_map = cpu_to_le16(                              \
+				IEEE80211_VHT_MCS_SUPPORT_0_7   << 0  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 2  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 4  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 6  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 8  | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 | \
+				IEEE80211_VHT_MCS_NOT_SUPPORTED << 14), \
+	}                                                               \
+}
+
+#define CL_HE_CAP_ELEM_STATION                                                       \
+{                                                                                    \
+	.mac_cap_info[0] = IEEE80211_HE_MAC_CAP0_HTC_HE,                             \
+	.mac_cap_info[1] = 0,                                                        \
+	.mac_cap_info[2] = 0,                                                        \
+	.mac_cap_info[3] = IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_2,            \
+	.mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_BQR,                                \
+	.mac_cap_info[5] = IEEE80211_HE_MAC_CAP5_HT_VHT_TRIG_FRAME_RX,               \
+	.phy_cap_info[0] = IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_RU_MAPPING_IN_2G, \
+	.phy_cap_info[1] = IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A,                     \
+	.phy_cap_info[2] = 0,                                                        \
+	.phy_cap_info[3] = 0,                                                        \
+	.phy_cap_info[4] = 0,                                                        \
+	.phy_cap_info[5] = 0,                                                        \
+	.phy_cap_info[6] = 0,                                                        \
+	.phy_cap_info[7] = 0,                                                        \
+	.phy_cap_info[8] = IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G,       \
+	.phy_cap_info[9] = IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_16US,           \
+	.phy_cap_info[10] = 0,                                                       \
+}
+
+#define CL_HE_CAP_ELEM_AP                                                       \
+{                                                                               \
+	.mac_cap_info[0] = IEEE80211_HE_MAC_CAP0_HTC_HE,                        \
+	.mac_cap_info[1] = 0,                                                   \
+	.mac_cap_info[2] = 0,                                                   \
+	.mac_cap_info[3] = IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_2,       \
+	.mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_BQR,                           \
+	.mac_cap_info[5] = 0,                                                   \
+	.phy_cap_info[0] = 0,                                                   \
+	.phy_cap_info[1] = IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A,                \
+	.phy_cap_info[2] = 0,                                                   \
+	.phy_cap_info[3] = 0,                                                   \
+	.phy_cap_info[4] = 0,                                                   \
+	.phy_cap_info[5] = 0,                                                   \
+	.phy_cap_info[6] = 0,                                                   \
+	.phy_cap_info[7] = 0,                                                   \
+	.phy_cap_info[8] = 0,                                                   \
+	.phy_cap_info[9] = IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_16US,      \
+	.phy_cap_info[10] = 0,                                                  \
+}
+
+#define CL_HE_CAP_ELEM_MESH_POINT                                               \
+{                                                                               \
+	.mac_cap_info[0] = IEEE80211_HE_MAC_CAP0_HTC_HE,                        \
+	.mac_cap_info[1] = 0,                                                   \
+	.mac_cap_info[2] = 0,                                                   \
+	.mac_cap_info[3] = IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_2,       \
+	.mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_BQR,                           \
+	.mac_cap_info[5] = 0,                                                   \
+	.phy_cap_info[0] = 0,                                                   \
+	.phy_cap_info[1] = IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A,                \
+	.phy_cap_info[2] = 0,                                                   \
+	.phy_cap_info[3] = 0,                                                   \
+	.phy_cap_info[4] = 0,                                                   \
+	.phy_cap_info[5] = 0,                                                   \
+	.phy_cap_info[6] = 0,                                                   \
+	.phy_cap_info[7] = 0,                                                   \
+	.phy_cap_info[8] = 0,                                                   \
+	.phy_cap_info[9] = IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_16US,      \
+	.phy_cap_info[10] = 0,                                                  \
+}
+
+#define CL_HE_MCS_NSS_SUPP                   \
+{                                            \
+	.rx_mcs_80 = cpu_to_le16(0xff00),    \
+	.tx_mcs_80 = cpu_to_le16(0xff00),    \
+	.rx_mcs_160 = cpu_to_le16(0xff00),   \
+	.tx_mcs_160 = cpu_to_le16(0xff00),   \
+	.rx_mcs_80p80 = cpu_to_le16(0xffff), \
+	.tx_mcs_80p80 = cpu_to_le16(0xffff), \
+}
+
+#define RATE(_bitrate, _hw_rate, _flags) { \
+	.bitrate = (_bitrate),             \
+	.flags = (_flags),                 \
+	.hw_value = (_hw_rate),            \
+}
+
+#define CHAN(_freq, _idx) {     \
+	.center_freq = (_freq), \
+	.hw_value = (_idx),     \
+	.max_power = 18,        \
+}
+
+static struct ieee80211_sband_iftype_data cl_he_data[] = {
+	{
+		.types_mask = BIT(NL80211_IFTYPE_STATION),
+		.he_cap = {
+			.has_he = true,
+			.he_cap_elem = CL_HE_CAP_ELEM_STATION,
+			.he_mcs_nss_supp = CL_HE_MCS_NSS_SUPP,
+		},
+	},
+	{
+		.types_mask = BIT(NL80211_IFTYPE_AP),
+		.he_cap = {
+			.has_he = true,
+			.he_cap_elem = CL_HE_CAP_ELEM_AP,
+			.he_mcs_nss_supp = CL_HE_MCS_NSS_SUPP,
+		},
+	},
+	{
+		.types_mask = BIT(NL80211_IFTYPE_MESH_POINT),
+		.he_cap = {
+			.has_he = true,
+			.he_cap_elem = CL_HE_CAP_ELEM_MESH_POINT,
+			.he_mcs_nss_supp = CL_HE_MCS_NSS_SUPP,
+		},
+	},
+};
+
+static struct ieee80211_rate cl_ratetable[] = {
+	RATE(10,  0x00, 0),
+	RATE(20,  0x01, IEEE80211_RATE_SHORT_PREAMBLE),
+	RATE(55,  0x02, IEEE80211_RATE_SHORT_PREAMBLE),
+	RATE(110, 0x03, IEEE80211_RATE_SHORT_PREAMBLE),
+	RATE(60,  0x04, 0),
+	RATE(90,  0x05, 0),
+	RATE(120, 0x06, 0),
+	RATE(180, 0x07, 0),
+	RATE(240, 0x08, 0),
+	RATE(360, 0x09, 0),
+	RATE(480, 0x0A, 0),
+	RATE(540, 0x0B, 0),
+};
+
+/* The channels indexes here are not used anymore */
+static struct ieee80211_channel cl_2ghz_channels[] = {
+	CHAN(2412, 0),
+	CHAN(2417, 1),
+	CHAN(2422, 2),
+	CHAN(2427, 3),
+	CHAN(2432, 4),
+	CHAN(2437, 5),
+	CHAN(2442, 6),
+	CHAN(2447, 7),
+	CHAN(2452, 8),
+	CHAN(2457, 9),
+	CHAN(2462, 10),
+	CHAN(2467, 11),
+	CHAN(2472, 12),
+	CHAN(2484, 13),
+};
+
+static struct ieee80211_channel cl_5ghz_channels[] = {
+	CHAN(5180, 0),  /* 36 -  20MHz */
+	CHAN(5200, 1),  /* 40 -  20MHz */
+	CHAN(5220, 2),  /* 44 -  20MHz */
+	CHAN(5240, 3),  /* 48 -  20MHz */
+	CHAN(5260, 4),  /* 52 -  20MHz */
+	CHAN(5280, 5),  /* 56 -  20MHz */
+	CHAN(5300, 6),  /* 60 -  20MHz */
+	CHAN(5320, 7),  /* 64 -  20MHz */
+	CHAN(5500, 8),  /* 100 - 20MHz */
+	CHAN(5520, 9),  /* 104 - 20MHz */
+	CHAN(5540, 10), /* 108 - 20MHz */
+	CHAN(5560, 11), /* 112 - 20MHz */
+	CHAN(5580, 12), /* 116 - 20MHz */
+	CHAN(5600, 13), /* 120 - 20MHz */
+	CHAN(5620, 14), /* 124 - 20MHz */
+	CHAN(5640, 15), /* 128 - 20MHz */
+	CHAN(5660, 16), /* 132 - 20MHz */
+	CHAN(5680, 17), /* 136 - 20MHz */
+	CHAN(5700, 18), /* 140 - 20MHz */
+	CHAN(5720, 19), /* 144 - 20MHz */
+	CHAN(5745, 20), /* 149 - 20MHz */
+	CHAN(5765, 21), /* 153 - 20MHz */
+	CHAN(5785, 22), /* 157 - 20MHz */
+	CHAN(5805, 23), /* 161 - 20MHz */
+	CHAN(5825, 24), /* 165 - 20MHz */
+};
+
+static struct ieee80211_channel cl_6ghz_channels[] = {
+	CHAN(5955, 1),  /* 1 - 20MHz */
+	CHAN(5935, 2),  /* 2 - 20MHz */
+	CHAN(5975, 5),  /* 5 - 20MHz */
+	CHAN(5995, 9),  /* 9 - 20MHz */
+	CHAN(6015, 13),  /* 13 - 20MHz */
+	CHAN(6035, 17),  /* 17 - 20MHz */
+	CHAN(6055, 21),  /* 21 - 20MHz */
+	CHAN(6075, 25),  /* 25 - 20MHz */
+	CHAN(6095, 29),  /* 29 - 20MHz */
+	CHAN(6115, 33),  /* 33 - 20MHz */
+	CHAN(6135, 37),  /* 37 - 20MHz */
+	CHAN(6155, 41),  /* 41 - 20MHz */
+	CHAN(6175, 45),  /* 45 - 20MHz */
+	CHAN(6195, 49),  /* 49 - 20MHz */
+	CHAN(6215, 53),  /* 53 - 20MHz */
+	CHAN(6235, 57),  /* 57 - 20MHz */
+	CHAN(6255, 61),  /* 61 - 20MHz */
+	CHAN(6275, 65),  /* 65 - 20MHz */
+	CHAN(6295, 69),  /* 69 - 20MHz */
+	CHAN(6315, 73),  /* 73 - 20MHz */
+	CHAN(6335, 77),  /* 77 - 20MHz */
+	CHAN(6355, 81),  /* 81 - 20MHz */
+	CHAN(6375, 85),  /* 85 - 20MHz */
+	CHAN(6395, 89),  /* 89 - 20MHz */
+	CHAN(6415, 93),  /* 93 - 20MHz */
+	CHAN(6435, 97),  /* 97 - 20MHz */
+	CHAN(6455, 101),  /* 101 - 20MHz */
+	CHAN(6475, 105),  /* 105 - 20MHz */
+	CHAN(6495, 109),  /* 109 - 20MHz */
+	CHAN(6515, 113),  /* 113 - 20MHz */
+	CHAN(6535, 117),  /* 117 - 20MHz */
+	CHAN(6555, 121),  /* 121 - 20MHz */
+	CHAN(6575, 125),  /* 125 - 20MHz */
+	CHAN(6595, 129),  /* 129 - 20MHz */
+	CHAN(6615, 133),  /* 133 - 20MHz */
+	CHAN(6635, 137),  /* 137 - 20MHz */
+	CHAN(6655, 141),  /* 141 - 20MHz */
+	CHAN(6675, 145),  /* 145 - 20MHz */
+	CHAN(6695, 149),  /* 149 - 20MHz */
+	CHAN(6715, 153),  /* 153 - 20MHz */
+	CHAN(6735, 157),  /* 157 - 20MHz */
+	CHAN(6755, 161),  /* 161 - 20MHz */
+	CHAN(6775, 165),  /* 165 - 20MHz */
+	CHAN(6795, 169),  /* 169 - 20MHz */
+	CHAN(6815, 173),  /* 173 - 20MHz */
+	CHAN(6835, 177),  /* 177 - 20MHz */
+	CHAN(6855, 181),  /* 181 - 20MHz */
+	CHAN(6875, 188),  /* 185 - 20MHz */
+	CHAN(6895, 189),  /* 189 - 20MHz */
+	CHAN(6915, 193),  /* 193 - 20MHz */
+	CHAN(6935, 197),  /* 197 - 20MHz */
+	CHAN(6955, 201),  /* 201 - 20MHz */
+	CHAN(6975, 205),  /* 205 - 20MHz */
+	CHAN(6995, 209),  /* 209 - 20MHz */
+	CHAN(7015, 213),  /* 213 - 20MHz */
+	CHAN(7035, 217),  /* 217 - 20MHz */
+	CHAN(7055, 221),  /* 221 - 20MHz */
+	CHAN(7075, 225),  /* 225 - 20MHz */
+	CHAN(7095, 229),  /* 229 - 20MHz */
+	CHAN(7115, 233),  /* 233 - 20MHz */
+};
+
+static struct ieee80211_supported_band cl_band_2ghz = {
+	.channels   = cl_2ghz_channels,
+	.n_channels = ARRAY_SIZE(cl_2ghz_channels),
+	.bitrates   = cl_ratetable,
+	.n_bitrates = ARRAY_SIZE(cl_ratetable),
+	.ht_cap     = CL_HT_CAPABILITIES,
+	.vht_cap    = CL_VHT_CAPABILITIES,
+};
+
+static struct ieee80211_supported_band cl_band_5ghz = {
+	.channels   = cl_5ghz_channels,
+	.n_channels = ARRAY_SIZE(cl_5ghz_channels),
+	.bitrates   = &cl_ratetable[4],
+	.n_bitrates = ARRAY_SIZE(cl_ratetable) - 4,
+	.ht_cap     = CL_HT_CAPABILITIES,
+	.vht_cap    = CL_VHT_CAPABILITIES,
+};
+
+static struct ieee80211_supported_band cl_band_6ghz = {
+	.channels   = cl_6ghz_channels,
+	.n_channels = ARRAY_SIZE(cl_6ghz_channels),
+	.bitrates   = &cl_ratetable[4],
+	.n_bitrates = ARRAY_SIZE(cl_ratetable) - 4,
+};
+
+static const struct ieee80211_iface_limit cl_limits[] = {
+	{
+		.max   = ARRAY_SIZE(((struct cl_hw *)0)->addresses),
+		.types = BIT(NL80211_IFTYPE_AP) |
+			 BIT(NL80211_IFTYPE_STATION) |
+			 BIT(NL80211_IFTYPE_MESH_POINT),
+	},
+};
+
+#define WLAN_EXT_CAPA1_2040_BSS_COEX_MGMT_ENABLED BIT(0)
+
+static u8 cl_if_types_ext_capa_ap_24g[] = {
+	[0]  = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING,
+	[7]  = WLAN_EXT_CAPA8_OPMODE_NOTIF,
+};
+
+static const struct wiphy_iftype_ext_capab cl_iftypes_ext_capa_24g[] = {
+	{
+		.iftype = NL80211_IFTYPE_AP,
+		.extended_capabilities = cl_if_types_ext_capa_ap_24g,
+		.extended_capabilities_mask = cl_if_types_ext_capa_ap_24g,
+		.extended_capabilities_len = sizeof(cl_if_types_ext_capa_ap_24g),
+	},
+};
+
+static u8 cl_if_types_ext_capa_ap_5g[] = {
+	[0]  = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING,
+	[7]  = WLAN_EXT_CAPA8_OPMODE_NOTIF,
+};
+
+static const struct wiphy_iftype_ext_capab cl_iftypes_ext_capa_5g[] = {
+	{
+		.iftype = NL80211_IFTYPE_AP,
+		.extended_capabilities = cl_if_types_ext_capa_ap_5g,
+		.extended_capabilities_mask = cl_if_types_ext_capa_ap_5g,
+		.extended_capabilities_len = sizeof(cl_if_types_ext_capa_ap_5g),
+	},
+};
+
+static u8 cl_if_types_ext_capa_ap_6g[] = {
+	[0]  = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING,
+	[7]  = WLAN_EXT_CAPA8_OPMODE_NOTIF,
+};
+
+static const struct wiphy_iftype_ext_capab cl_iftypes_ext_capa_6g[] = {
+	{
+		.iftype = NL80211_IFTYPE_AP,
+		.extended_capabilities = cl_if_types_ext_capa_ap_6g,
+		.extended_capabilities_mask = cl_if_types_ext_capa_ap_6g,
+		.extended_capabilities_len = sizeof(cl_if_types_ext_capa_ap_6g),
+	},
+};
+
+static struct ieee80211_iface_combination cl_combinations[] = {
+	{
+		.limits = cl_limits,
+		.n_limits = ARRAY_SIZE(cl_limits),
+		.num_different_channels = 1,
+		.max_interfaces = ARRAY_SIZE(((struct cl_hw *)0)->addresses),
+		.beacon_int_min_gcd = 100,
+		.radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20) |
+				       BIT(NL80211_CHAN_WIDTH_40) |
+				       BIT(NL80211_CHAN_WIDTH_80) |
+				       BIT(NL80211_CHAN_WIDTH_160),
+	}
+};
+
+static const int cl_ac2hwq[AC_MAX] = {
+	[NL80211_TXQ_Q_VO] = CL_HWQ_VO,
+	[NL80211_TXQ_Q_VI] = CL_HWQ_VI,
+	[NL80211_TXQ_Q_BE] = CL_HWQ_BE,
+	[NL80211_TXQ_Q_BK] = CL_HWQ_BK
+};
+
+static const int cl_ac2edca[AC_MAX] = {
+	[NL80211_TXQ_Q_VO] = EDCA_AC_VO,
+	[NL80211_TXQ_Q_VI] = EDCA_AC_VI,
+	[NL80211_TXQ_Q_BE] = EDCA_AC_BE,
+	[NL80211_TXQ_Q_BK] = EDCA_AC_BK
+};
+
+static u8 cl_he_mcs_supp_tx(struct cl_hw *cl_hw, u8 nss)
+{
+	u8 mcs = cl_hw->conf->ce_he_mcs_nss_supp_tx[nss];
+
+	switch (mcs) {
+	case WRS_MCS_7:
+		return IEEE80211_HE_MCS_SUPPORT_0_7;
+	case WRS_MCS_9:
+		return IEEE80211_HE_MCS_SUPPORT_0_9;
+	case WRS_MCS_11:
+		return IEEE80211_HE_MCS_SUPPORT_0_11;
+	}
+
+	cl_dbg_err(cl_hw, "Invalid mcs %u for nss %u. Must be 7, 9 or 11!\n", mcs, nss);
+	return IEEE80211_HE_MCS_NOT_SUPPORTED;
+}
+
+static u8 cl_he_mcs_supp_rx(struct cl_hw *cl_hw, u8 nss)
+{
+	u8 mcs = cl_hw->conf->ce_he_mcs_nss_supp_rx[nss];
+
+	switch (mcs) {
+	case WRS_MCS_7:
+		return IEEE80211_HE_MCS_SUPPORT_0_7;
+	case WRS_MCS_9:
+		return IEEE80211_HE_MCS_SUPPORT_0_9;
+	case WRS_MCS_11:
+		return IEEE80211_HE_MCS_SUPPORT_0_11;
+	}
+
+	cl_dbg_err(cl_hw, "Invalid mcs %u for nss %u. Must be 7, 9 or 11!\n", mcs, nss);
+	return IEEE80211_HE_MCS_NOT_SUPPORTED;
+}
+
+static u8 cl_vht_mcs_supp_tx(struct cl_hw *cl_hw, u8 nss)
+{
+	u8 mcs = cl_hw->conf->ce_vht_mcs_nss_supp_tx[nss];
+
+	switch (mcs) {
+	case WRS_MCS_7:
+		return IEEE80211_VHT_MCS_SUPPORT_0_7;
+	case WRS_MCS_8:
+		return IEEE80211_VHT_MCS_SUPPORT_0_8;
+	case WRS_MCS_9:
+		return IEEE80211_VHT_MCS_SUPPORT_0_9;
+	}
+
+	cl_dbg_err(cl_hw, "Invalid mcs %u for nss %u. Must be 7-9!\n", mcs, nss);
+	return IEEE80211_VHT_MCS_NOT_SUPPORTED;
+}
+
+static u8 cl_vht_mcs_supp_rx(struct cl_hw *cl_hw, u8 nss)
+{
+	u8 mcs = cl_hw->conf->ce_vht_mcs_nss_supp_rx[nss];
+
+	switch (mcs) {
+	case WRS_MCS_7:
+		return IEEE80211_VHT_MCS_SUPPORT_0_7;
+	case WRS_MCS_8:
+		return IEEE80211_VHT_MCS_SUPPORT_0_8;
+	case WRS_MCS_9:
+		return IEEE80211_VHT_MCS_SUPPORT_0_9;
+	}
+
+	cl_dbg_err(cl_hw, "Invalid mcs %u for nss %u. Must be 7-9!\n", mcs, nss);
+	return IEEE80211_VHT_MCS_NOT_SUPPORTED;
+}
+
+static void cl_set_he_6ghz_capab(struct cl_hw *cl_hw)
+{
+	struct ieee80211_he_6ghz_capa *he_6ghz_cap0 = &cl_hw->iftype_data[0].he_6ghz_capa;
+	struct ieee80211_he_6ghz_capa *he_6ghz_cap1 = &cl_hw->iftype_data[1].he_6ghz_capa;
+	struct ieee80211_he_6ghz_capa *he_6ghz_cap2 = &cl_hw->iftype_data[2].he_6ghz_capa;
+
+	he_6ghz_cap0->capa = cpu_to_le16(IEEE80211_HT_MPDU_DENSITY_1);
+
+	he_6ghz_cap0->capa |=
+		cpu_to_le16(cl_hw->conf->ci_max_mpdu_len << HE_6GHZ_CAP_MAX_MPDU_LEN_OFFSET);
+	he_6ghz_cap0->capa |=
+		cpu_to_le16(IEEE80211_VHT_MAX_AMPDU_1024K << HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP_OFFSET);
+
+	he_6ghz_cap0->capa |= cpu_to_le16(IEEE80211_HE_6GHZ_CAP_RX_ANTPAT_CONS |
+					  IEEE80211_HE_6GHZ_CAP_TX_ANTPAT_CONS);
+
+	he_6ghz_cap1->capa = he_6ghz_cap0->capa;
+	he_6ghz_cap2->capa = he_6ghz_cap0->capa;
+}
+
+static void _cl_set_he_capab(struct cl_hw *cl_hw, u8 idx)
+{
+	struct ieee80211_sta_he_cap *he_cap = &cl_hw->iftype_data[idx].he_cap;
+	struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp = &he_cap->he_mcs_nss_supp;
+	struct ieee80211_he_cap_elem *he_cap_elem = &he_cap->he_cap_elem;
+	u8 rx_nss = cl_hw->conf->ce_rx_nss;
+	u8 tx_nss = cl_hw->conf->ce_tx_nss;
+	int i = 0;
+
+	if (BAND_IS_5G_6G(cl_hw)) {
+		he_cap_elem->phy_cap_info[0] |=
+			IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
+
+		for (i = 0; i < rx_nss; i++)
+			he_mcs_nss_supp->rx_mcs_160 |=
+				cpu_to_le16(cl_he_mcs_supp_rx(cl_hw, i) << (i * 2));
+
+		for (i = 0; i < tx_nss; i++)
+			he_mcs_nss_supp->tx_mcs_160 |=
+				cpu_to_le16(cl_he_mcs_supp_tx(cl_hw, i) << (i * 2));
+
+		he_cap_elem->phy_cap_info[0] |=
+			IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G;
+
+		for (i = 0; i < rx_nss; i++)
+			he_mcs_nss_supp->rx_mcs_80 |=
+				cpu_to_le16(cl_he_mcs_supp_rx(cl_hw, i) << (i * 2));
+
+		for (i = 0; i < tx_nss; i++)
+			he_mcs_nss_supp->tx_mcs_80 |=
+				cpu_to_le16(cl_he_mcs_supp_tx(cl_hw, i) << (i * 2));
+	} else {
+		he_cap_elem->phy_cap_info[0] |=
+			IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G;
+
+		for (i = 0; i < rx_nss; i++)
+			he_mcs_nss_supp->rx_mcs_80 |=
+				cpu_to_le16(cl_he_mcs_supp_rx(cl_hw, i) << (i * 2));
+
+		for (i = 0; i < tx_nss; i++)
+			he_mcs_nss_supp->tx_mcs_80 |=
+				cpu_to_le16(cl_he_mcs_supp_tx(cl_hw, i) << (i * 2));
+	}
+
+	for (i = rx_nss; i < 8; i++) {
+		he_mcs_nss_supp->rx_mcs_80 |=
+			cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+		he_mcs_nss_supp->rx_mcs_160 |=
+			cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+	}
+
+	for (i = tx_nss; i < 8; i++) {
+		he_mcs_nss_supp->tx_mcs_80 |=
+			cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+		he_mcs_nss_supp->tx_mcs_160 |=
+			cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+	}
+
+	if (cl_hw->conf->ci_he_rxldpc_en)
+		he_cap_elem->phy_cap_info[1] |=
+			IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD;
+
+	if (cl_hw->conf->ci_rx_he_mu_ppdu)
+		he_cap_elem->phy_cap_info[3] |=
+			IEEE80211_HE_PHY_CAP3_RX_PARTIAL_BW_SU_IN_20MHZ_MU;
+
+	if (cl_hw->conf->ci_bf_en) {
+		he_cap_elem->phy_cap_info[3] |=
+			IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER;
+		he_cap_elem->phy_cap_info[5] |=
+			IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_4;
+	}
+}
+
+static void cl_set_he_capab(struct cl_hw *cl_hw)
+{
+	struct ieee80211_sta_he_cap *he_cap0 = &cl_hw->iftype_data[0].he_cap;
+	struct ieee80211_he_cap_elem *he_cap_elem = &he_cap0->he_cap_elem;
+	struct cl_tcv_conf *conf = cl_hw->conf;
+	u8 tf_mac_pad_dur = conf->ci_tf_mac_pad_dur;
+
+	memcpy(&cl_hw->iftype_data, cl_he_data, sizeof(cl_hw->iftype_data));
+
+	if (tf_mac_pad_dur == 1)
+		he_cap_elem->mac_cap_info[1] |= IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_8US;
+	else if (tf_mac_pad_dur == 2)
+		he_cap_elem->mac_cap_info[1] |= IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US;
+
+	_cl_set_he_capab(cl_hw, 0);
+	_cl_set_he_capab(cl_hw, 1);
+	_cl_set_he_capab(cl_hw, 2);
+
+	if (cl_band_is_6g(cl_hw))
+		cl_set_he_6ghz_capab(cl_hw);
+
+	cl_hw->sband.n_iftype_data = ARRAY_SIZE(cl_he_data);
+	cl_hw->sband.iftype_data = cl_hw->iftype_data;
+}
+
+#define RATE_1_MBPS   10
+#define RATE_2_MBPS   20
+#define RATE_5_5_MBPS 55
+#define RATE_11_MBPS  110
+#define RATE_6_MBPS   60
+#define RATE_9_MBPS   90
+#define RATE_12_MBPS  120
+#define RATE_18_MBPS  180
+#define RATE_24_MBPS  240
+#define RATE_36_MBPS  360
+#define RATE_48_MBPS  480
+#define RATE_54_MBPS  540
+
+static u16 cl_cap_convert_rate_to_bitmap(u16 rate)
+{
+	switch (rate) {
+	case RATE_1_MBPS:
+		return BIT(0);
+	case RATE_2_MBPS:
+		return BIT(1);
+	case RATE_5_5_MBPS:
+		return BIT(2);
+	case RATE_11_MBPS:
+		return BIT(3);
+	case RATE_6_MBPS:
+		return BIT(4);
+	case RATE_9_MBPS:
+		return BIT(5);
+	case RATE_12_MBPS:
+		return BIT(6);
+	case RATE_18_MBPS:
+		return BIT(7);
+	case RATE_24_MBPS:
+		return BIT(8);
+	case RATE_36_MBPS:
+		return BIT(9);
+	case RATE_48_MBPS:
+		return BIT(10);
+	case RATE_54_MBPS:
+		return BIT(11);
+	default:
+		return 0;
+	}
+}
+
+u16 cl_cap_set_mesh_basic_rates(struct cl_hw *cl_hw)
+{
+	int i;
+	struct cl_tcv_conf *conf = cl_hw->conf;
+	u16 basic_rates = 0;
+
+	for (i = 0; i < MESH_BASIC_RATE_MAX; i++)
+		basic_rates |= cl_cap_convert_rate_to_bitmap(conf->ci_mesh_basic_rates[i]);
+
+	return basic_rates;
+}
+
+void cl_cap_dyn_params(struct cl_hw *cl_hw)
+{
+	struct ieee80211_hw *hw = cl_hw->hw;
+	struct wiphy *wiphy = hw->wiphy;
+	struct cl_tcv_conf *conf = cl_hw->conf;
+	u8 rx_nss = conf->ce_rx_nss;
+	u8 tx_nss = conf->ce_tx_nss;
+	u8 guard_interval = conf->ci_short_guard_interval;
+	u8 i;
+	u8 bw = cl_hw->conf->ci_cap_bandwidth;
+	struct ieee80211_supported_band *sband = &cl_hw->sband;
+	struct ieee80211_sta_ht_cap *sband_ht_cap = &sband->ht_cap;
+	struct ieee80211_sta_vht_cap *sband_vht_cap = &sband->vht_cap;
+
+	if (cl_band_is_6g(cl_hw)) {
+		memcpy(sband, &cl_band_6ghz, sizeof(struct ieee80211_supported_band));
+	} else if (cl_band_is_5g(cl_hw)) {
+		memcpy(sband, &cl_band_5ghz, sizeof(struct ieee80211_supported_band));
+	} else {
+		memcpy(sband, &cl_band_2ghz, sizeof(struct ieee80211_supported_band));
+
+		if (!conf->ci_vht_cap_24g)
+			memset(&sband->vht_cap, 0, sizeof(struct ieee80211_sta_vht_cap));
+	}
+
+	/* 6GHz doesn't support HT/VHT */
+	if (!cl_band_is_6g(cl_hw)) {
+		if (bw > CHNL_BW_20)
+			sband_ht_cap->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+
+		/* Guard_interval */
+		if (guard_interval) {
+			sband_ht_cap->cap |= IEEE80211_HT_CAP_SGI_20;
+
+			if (bw >= CHNL_BW_40)
+				sband_ht_cap->cap |= IEEE80211_HT_CAP_SGI_40;
+
+			if (bw >= CHNL_BW_80)
+				sband_vht_cap->cap |= IEEE80211_VHT_CAP_SHORT_GI_80;
+
+			if (bw == CHNL_BW_160)
+				sband_vht_cap->cap |= IEEE80211_VHT_CAP_SHORT_GI_160;
+		}
+	}
+
+	/* Amsdu */
+	cl_rx_amsdu_hw_en(hw, conf->ce_rxamsdu_en);
+	cl_hw->txamsdu_en = conf->ce_txamsdu_en;
+
+	/* Hw flags */
+	ieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING);
+	ieee80211_hw_set(hw, SIGNAL_DBM);
+	ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS);
+	ieee80211_hw_set(hw, QUEUE_CONTROL);
+	ieee80211_hw_set(hw, WANT_MONITOR_VIF);
+	ieee80211_hw_set(hw, SPECTRUM_MGMT);
+	ieee80211_hw_set(hw, SUPPORTS_HT_CCK_RATES);
+	ieee80211_hw_set(hw, HAS_RATE_CONTROL);
+	ieee80211_hw_set(hw, SUPPORT_FAST_XMIT);
+	ieee80211_hw_set(hw, NO_AUTO_VIF);
+	ieee80211_hw_set(hw, MFP_CAPABLE);
+	ieee80211_hw_set(hw, SUPPORTS_PER_STA_GTK);
+	ieee80211_hw_set(hw, SUPPORTS_TX_ENCAP_OFFLOAD);
+
+	wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE;
+	wiphy->features |= NL80211_FEATURE_AP_SCAN;
+	wiphy->available_antennas_tx = ANT_MASK(cl_hw->max_antennas);
+	wiphy->available_antennas_rx = ANT_MASK(cl_hw->max_antennas);
+
+	wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL);
+
+	if (conf->ci_fast_rx_en) {
+		ieee80211_hw_set(hw, SUPPORTS_REORDERING_BUFFER);
+		ieee80211_hw_set(hw, AP_LINK_PS);
+	}
+
+	if (cl_band_is_6g(cl_hw)) {
+		hw->wiphy->iftype_ext_capab = cl_iftypes_ext_capa_6g;
+		hw->wiphy->num_iftype_ext_capab = ARRAY_SIZE(cl_iftypes_ext_capa_6g);
+	} else if (cl_band_is_5g(cl_hw)) {
+		hw->wiphy->iftype_ext_capab = cl_iftypes_ext_capa_5g;
+		hw->wiphy->num_iftype_ext_capab = ARRAY_SIZE(cl_iftypes_ext_capa_5g);
+	} else if (cl_band_is_24g(cl_hw)) {
+		/* Turn on "20/40 Coex Mgmt Support" bit (24g only) */
+		if (conf->ce_acs_coex_en) {
+			u8 *ext_cap = (u8 *)cl_iftypes_ext_capa_24g[0].extended_capabilities;
+
+			ext_cap[0] |= WLAN_EXT_CAPA1_2040_BSS_COEX_MGMT_ENABLED;
+		}
+
+		hw->wiphy->iftype_ext_capab = cl_iftypes_ext_capa_24g;
+		hw->wiphy->num_iftype_ext_capab = ARRAY_SIZE(cl_iftypes_ext_capa_24g);
+	}
+
+	/*
+	 * To disable the dynamic PS we say to the stack that we support it in
+	 * HW. This will force mac80211 rely on us to handle this.
+	 */
+	ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS);
+
+	if (conf->ci_agg_tx)
+		ieee80211_hw_set(hw, AMPDU_AGGREGATION);
+
+	wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+				 BIT(NL80211_IFTYPE_AP) |
+				 BIT(NL80211_IFTYPE_MESH_POINT);
+
+	wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL |
+			WIPHY_FLAG_HAS_CHANNEL_SWITCH |
+			WIPHY_FLAG_IBSS_RSN;
+
+	if (conf->ci_uapsd_en)
+		wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
+
+	/* Modify MAX BSS num according to the desired config value */
+	for (i = 0; i < ARRAY_SIZE(cl_combinations); i++)
+		cl_combinations[i].max_interfaces = conf->ci_max_bss_num;
+	wiphy->iface_combinations = cl_combinations;
+	wiphy->n_iface_combinations = ARRAY_SIZE(cl_combinations);
+
+	/*
+	 * hw_scan ops may ask driver to forge active scan request. So the
+	 * scan capabs are filled in (the are same as inside mac80211).
+	 * However, they are not representing real hw_scan logic, since it will
+	 * fallback to the sw_scan for active scan request.
+	 **/
+	wiphy->max_scan_ssids = 4;
+	wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
+
+	hw->max_rates = IEEE80211_TX_MAX_RATES;
+	hw->max_report_rates = IEEE80211_TX_MAX_RATES;
+	hw->max_rate_tries = 1;
+
+	hw->max_tx_aggregation_subframes = conf->ce_max_agg_size_tx;
+	hw->max_rx_aggregation_subframes = conf->ce_max_agg_size_rx;
+
+	hw->vif_data_size = sizeof(struct cl_vif);
+	hw->sta_data_size = sizeof(struct cl_sta);
+
+	hw->extra_tx_headroom = 0;
+	hw->queues = IEEE80211_MAX_QUEUES;
+	hw->offchannel_tx_hw_queue = CL_HWQ_VO;
+
+	if (!cl_band_is_6g(cl_hw)) {
+		if (conf->ci_ht_rxldpc_en)
+			sband_ht_cap->cap |= IEEE80211_HT_CAP_LDPC_CODING;
+
+		sband_ht_cap->cap |= IEEE80211_HT_CAP_MAX_AMSDU;
+
+		sband_vht_cap->cap |= cl_hw->conf->ci_max_mpdu_len;
+		if (conf->ci_bf_en) {
+			sband_vht_cap->cap |=
+				IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE |
+				IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE |
+				(3 << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT) |
+				(3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT);
+		}
+	}
+
+	if (cl_band_is_5g(cl_hw) || (cl_band_is_24g(cl_hw) && conf->ci_vht_cap_24g)) {
+		if (bw == CHNL_BW_160)
+			sband_vht_cap->cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ;
+
+		sband_vht_cap->cap |= (conf->ci_max_ampdu_len_exp <<
+				       IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT);
+
+		if (conf->ci_vht_rxldpc_en)
+			sband_vht_cap->cap |= IEEE80211_VHT_CAP_RXLDPC;
+
+		sband_vht_cap->cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN;
+		sband_vht_cap->cap |= IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN;
+
+		sband_vht_cap->vht_mcs.rx_mcs_map = cpu_to_le16(0);
+		sband_vht_cap->vht_mcs.tx_mcs_map = cpu_to_le16(0);
+
+		for (i = 0; i < rx_nss; i++)
+			sband_vht_cap->vht_mcs.rx_mcs_map |=
+				cpu_to_le16(cl_vht_mcs_supp_rx(cl_hw, i) << (i * 2));
+
+		for (; i < 8; i++)
+			sband_vht_cap->vht_mcs.rx_mcs_map |=
+				cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << (i * 2));
+
+		for (i = 0; i < tx_nss; i++)
+			sband_vht_cap->vht_mcs.tx_mcs_map |=
+				cpu_to_le16(cl_vht_mcs_supp_tx(cl_hw, i) << (i * 2));
+
+		for (; i < 8; i++)
+			sband_vht_cap->vht_mcs.tx_mcs_map |=
+				cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << (i * 2));
+
+		sband_vht_cap->vht_supported = true;
+	}
+
+	/* 6GHz band supports HE only */
+	if (!cl_band_is_6g(cl_hw))
+		for (i = 0; i < rx_nss; i++)
+			sband_ht_cap->mcs.rx_mask[i] = U8_MAX;
+
+	cl_set_he_capab(cl_hw);
+
+	/* Get channels and power limitations information from ChannelInfo file */
+	cl_chan_info_init(cl_hw);
+
+	if (cl_band_is_6g(cl_hw)) {
+		wiphy->bands[NL80211_BAND_2GHZ] = NULL;
+		wiphy->bands[NL80211_BAND_5GHZ] = NULL;
+		wiphy->bands[NL80211_BAND_6GHZ] = sband;
+	} else if (cl_band_is_5g(cl_hw)) {
+		wiphy->bands[NL80211_BAND_2GHZ] = NULL;
+		wiphy->bands[NL80211_BAND_5GHZ] = sband;
+		wiphy->bands[NL80211_BAND_6GHZ] = NULL;
+	} else {
+		wiphy->bands[NL80211_BAND_2GHZ] = sband;
+		wiphy->bands[NL80211_BAND_5GHZ] = NULL;
+		wiphy->bands[NL80211_BAND_6GHZ] = NULL;
+	}
+}
+
+enum he_pkt_ext_constellations {
+	HE_PKT_EXT_BPSK = 0,
+	HE_PKT_EXT_QPSK,
+	HE_PKT_EXT_16QAM,
+	HE_PKT_EXT_64QAM,
+	HE_PKT_EXT_256QAM,
+	HE_PKT_EXT_1024QAM,
+	HE_PKT_EXT_RESERVED,
+	HE_PKT_EXT_NONE,
+};
+
+static u8 mcs_to_constellation[WRS_MCS_MAX_HE] = {
+	HE_PKT_EXT_BPSK,
+	HE_PKT_EXT_QPSK,
+	HE_PKT_EXT_QPSK,
+	HE_PKT_EXT_16QAM,
+	HE_PKT_EXT_16QAM,
+	HE_PKT_EXT_64QAM,
+	HE_PKT_EXT_64QAM,
+	HE_PKT_EXT_64QAM,
+	HE_PKT_EXT_256QAM,
+	HE_PKT_EXT_256QAM,
+	HE_PKT_EXT_1024QAM,
+	HE_PKT_EXT_1024QAM
+};
+
+#define QAM_THR_1 0
+#define QAM_THR_2 1
+#define QAM_THR_MAX 2
+
+static u8 cl_get_ppe_val(u8 *ppe, u8 ppe_pos_bit)
+{
+	u8 byte_num = ppe_pos_bit / 8;
+	u8 bit_num = ppe_pos_bit % 8;
+	u8 residue_bits;
+	u8 res;
+
+	if (bit_num <= 5)
+		return (ppe[byte_num] >> bit_num) &
+		       (BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE) - 1);
+
+	/*
+	 * If bit_num > 5, we have to combine bits with next byte.
+	 * Calculate how many bits we need to take from current byte (called
+	 * here "residue_bits"), and add them to bits from next byte.
+	 */
+	residue_bits = 8 - bit_num;
+
+	res = (ppe[byte_num + 1] &
+	       (BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE - residue_bits) - 1)) <<
+	      residue_bits;
+	res += (ppe[byte_num] >> bit_num) & (BIT(residue_bits) - 1);
+
+	return res;
+}
+
+static void cl_set_fixed_ppe_val(u8 pe_dur[CHNL_BW_MAX][WRS_MCS_MAX_HE], u8 dur)
+{
+	u8 val = ((dur << 6) | (dur << 4) | (dur << 2) | dur);
+
+	memset(pe_dur, val, CHNL_BW_MAX * WRS_MCS_MAX_HE);
+}
+
+void cl_cap_ppe_duration(struct cl_hw *cl_hw, struct ieee80211_sta *sta,
+			 u8 pe_dur[CHNL_BW_MAX][WRS_MCS_MAX_HE])
+{
+	/* Force NVRAM parameter */
+	if (cl_hw->conf->ci_pe_duration <= PPE_16US) {
+		cl_set_fixed_ppe_val(pe_dur, cl_hw->conf->ci_pe_duration);
+		return;
+	}
+
+	/*
+	 * If STA sets the PPE Threshold Present subfield to 0,
+	 * the value should be set according to the Nominal Packet Padding subfield
+	 */
+	if ((sta->he_cap.he_cap_elem.phy_cap_info[6] &
+	     IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) == 0) {
+		switch (sta->he_cap.he_cap_elem.phy_cap_info[9] &
+			IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_MASK) {
+		case IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_0US:
+			cl_set_fixed_ppe_val(pe_dur, PPE_0US);
+			break;
+		case IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_8US:
+			cl_set_fixed_ppe_val(pe_dur, PPE_8US);
+			break;
+		case IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_16US:
+		default:
+			cl_set_fixed_ppe_val(pe_dur, PPE_16US);
+			break;
+		}
+
+		return;
+	}
+
+	/*
+	 * struct iwl_he_pkt_ext - QAM thresholds
+	 * The required PPE is set via HE Capabilities IE, per Nss x BW x MCS
+	 * The IE is organized in the following way:
+	 * Support for Nss x BW (or RU) matrix:
+	 *	(0=SISO, 1=MIMO2) x (0-20MHz, 1-40MHz, 2-80MHz, 3-160MHz)
+	 * Each entry contains 2 QAM thresholds for 8us and 16us:
+	 *	0=BPSK, 1=QPSK, 2=16QAM, 3=64QAM, 4=256QAM, 5=1024QAM, 6=RES, 7=NONE
+	 * i.e. QAM_th1 < QAM_th2 such if TX uses QAM_tx:
+	 *	QAM_tx < QAM_th1            --> PPE=0us
+	 *	QAM_th1 <= QAM_tx < QAM_th2 --> PPE=8us
+	 *	QAM_th2 <= QAM_tx           --> PPE=16us
+	 * @pkt_ext_qam_th: QAM thresholds
+	 *	For each Nss/Bw define 2 QAM thrsholds (0..5)
+	 *	For rates below the low_th, no need for PPE
+	 *	For rates between low_th and high_th, need 8us PPE
+	 *	For rates equal or higher then the high_th, need 16us PPE
+	 *	Nss (0-siso, 1-mimo2) x BW (0-20MHz, 1-40MHz, 2-80MHz, 3-160MHz) x
+	 *		(0-low_th, 1-high_th)
+	 */
+	u8 pkt_ext_qam_th[WRS_SS_MAX][CHNL_BW_MAX][QAM_THR_MAX];
+
+	/* If PPE Thresholds exist, parse them into a FW-familiar format. */
+	u8 nss = (sta->he_cap.ppe_thres[0] & IEEE80211_PPE_THRES_NSS_MASK) + 1;
+	u8 ru_index_bitmap = u32_get_bits(sta->he_cap.ppe_thres[0],
+					  IEEE80211_PPE_THRES_RU_INDEX_BITMASK_MASK);
+	u8 *ppe = &sta->he_cap.ppe_thres[0];
+	u8 ppe_pos_bit = 7; /* Starting after PPE header */
+	u8 bw, ss, mcs, constellation;
+
+	if (nss > WRS_SS_MAX)
+		nss = WRS_SS_MAX;
+
+	for (ss = 0; ss < nss; ss++) {
+		u8 ru_index_tmp = ru_index_bitmap << 1;
+
+		for (bw = 0; bw <= cl_hw->bw; bw++) {
+			ru_index_tmp >>= 1;
+			if (!(ru_index_tmp & 1))
+				continue;
+
+			pkt_ext_qam_th[ss][bw][QAM_THR_2] = cl_get_ppe_val(ppe, ppe_pos_bit);
+			ppe_pos_bit += IEEE80211_PPE_THRES_INFO_PPET_SIZE;
+			pkt_ext_qam_th[ss][bw][QAM_THR_1] = cl_get_ppe_val(ppe, ppe_pos_bit);
+			ppe_pos_bit += IEEE80211_PPE_THRES_INFO_PPET_SIZE;
+		}
+	}
+
+	/* Reset PE duration before filling it */
+	memset(pe_dur, 0, CHNL_BW_MAX * WRS_MCS_MAX_HE);
+
+	for (ss = 0; ss < nss; ss++) {
+		for (bw = 0; bw <= cl_hw->bw; bw++) {
+			for (mcs = 0; mcs < WRS_MCS_MAX_HE; mcs++) {
+				constellation = mcs_to_constellation[mcs];
+
+				if (constellation < pkt_ext_qam_th[ss][bw][QAM_THR_1])
+					pe_dur[bw][mcs] |= (PPE_0US << (ss * 2));
+				else if (constellation < pkt_ext_qam_th[ss][bw][QAM_THR_2])
+					pe_dur[bw][mcs] |= (PPE_8US << (ss * 2));
+				else
+					pe_dur[bw][mcs] |= (PPE_16US << (ss * 2));
+			}
+		}
+	}
+}
+
+static void cl_ops_tx_agg(struct cl_hw *cl_hw,
+			  struct sk_buff *skb,
+			  struct ieee80211_tx_info *tx_info,
+			  struct cl_sta *cl_sta)
+{
+	cl_hw->tx_packet_cntr.forward.from_mac_agg++;
+
+	if (!cl_sta) {
+		struct cl_vif *cl_vif =
+			(struct cl_vif *)tx_info->control.vif->drv_priv;
+		u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
+		u8 ac = tid_to_ac[tid];
+
+		kfree_skb(skb);
+		cl_dbg_err(cl_hw, "cl_sta null in agg packet\n");
+		cl_hw->tx_packet_cntr.drop.sta_null_in_agg++;
+		cl_vif->trfc_cntrs[ac].tx_errors++;
+		return;
+	}
+
+	/* AMSDU in HW can work only with header conversion. */
+	tx_info->control.flags &= ~IEEE80211_TX_CTRL_AMSDU;
+	cl_tx_agg(cl_hw, cl_sta, skb, false, true);
+}
+
+static void cl_ops_tx_single(struct cl_hw *cl_hw,
+			     struct sk_buff *skb,
+			     struct ieee80211_tx_info *tx_info,
+			     struct cl_sta *cl_sta,
+			     struct ieee80211_sta *sta)
+{
+	bool is_vns = cl_vns_is_very_near(cl_hw, cl_sta, skb);
+
+	cl_hw->tx_packet_cntr.forward.from_mac_single++;
+	if (cl_hw->tx_db.block_prob_resp) {
+		struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+
+		if (ieee80211_is_probe_resp(hdr->frame_control)) {
+			struct cl_vif *cl_vif = NETDEV_TO_CL_VIF(skb->dev);
+			u8 ac = cl_vif->vif->hw_queue[skb_get_queue_mapping(skb)];
+
+			cl_tx_single_free_skb(cl_hw, skb);
+			cl_hw->tx_packet_cntr.drop.probe_response++;
+			cl_vif->trfc_cntrs[ac].tx_dropped++;
+			return;
+		}
+	}
+
+	if (sta) {
+		u32 sta_vht_cap = sta->vht_cap.cap;
+		struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+
+		if (!(sta_vht_cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK))
+			goto out_tx;
+
+		if (ieee80211_is_assoc_resp(mgmt->frame_control)) {
+			int len = skb->len - (mgmt->u.assoc_resp.variable - skb->data);
+			const u8 *vht_cap_addr = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY,
+								 mgmt->u.assoc_resp.variable,
+								 len);
+
+			if (vht_cap_addr) {
+				struct ieee80211_vht_cap *vht_cap =
+					(struct ieee80211_vht_cap *)(2 + vht_cap_addr);
+
+				vht_cap->vht_cap_info &=
+					~(cpu_to_le32(IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK |
+						      IEEE80211_VHT_CAP_SHORT_GI_160));
+			}
+		}
+	}
+
+out_tx:
+	cl_tx_single(cl_hw, cl_sta, skb, is_vns, true);
+}
+
+static u16 cl_ops_recalc_smallest_tbtt(struct cl_hw *cl_hw)
+{
+	struct wiphy *wiphy = cl_hw->hw->wiphy;
+	u8 cmb_idx = wiphy->n_iface_combinations - 1;
+	struct cl_vif *cl_vif = NULL;
+	u16 ret = 0;
+	u8 topology = cl_hw_get_iface_conf(cl_hw);
+
+	read_lock_bh(&cl_hw->vif_db.lock);
+	list_for_each_entry(cl_vif, &cl_hw->vif_db.head, list) {
+		if (cl_vif->vif->type == NL80211_IFTYPE_STATION)
+			continue;
+		else if (ret == 0)
+			ret = cl_vif->vif->bss_conf.beacon_int;
+		else if (cl_vif->vif->bss_conf.beacon_int)
+			ret = min(ret, cl_vif->vif->bss_conf.beacon_int);
+	}
+	read_unlock_bh(&cl_hw->vif_db.lock);
+
+	if (ret == 0) {
+		WARN_ONCE(topology != CL_IFCONF_STA && topology != CL_IFCONF_MESH_ONLY,
+			  "invalid smallest beacon interval");
+		return wiphy->iface_combinations[cmb_idx].beacon_int_min_gcd;
+	}
+	return ret;
+}
+
+static void cl_ops_set_mesh_tbtt(struct cl_hw *cl_hw, u16 this_beacon_int,
+				 u16 smallest_beacon_int)
+{
+	u16 div = this_beacon_int / smallest_beacon_int;
+
+	cl_hw->mesh_tbtt_div = (div > 0) ? div : 1;
+}
+
+void cl_ops_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb)
+{
+	/*
+	 * Almost all traffic passing here is singles.
+	 * Only when opening a BA session some packets with
+	 * IEEE80211_TX_CTL_AMPDU set can pass here.
+	 * All skbs passing here did header conversion.
+	 */
+	struct cl_hw *cl_hw = (struct cl_hw *)hw->priv;
+	struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_sta *sta = control->sta;
+	struct cl_sta *cl_sta = NULL;
+
+	if (sta) {
+		cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+
+		/*
+		 * Prior to STA connection sta can be set but we don't
+		 * want cl_sta to be used since it's not initialized yet
+		 */
+		if (cl_sta->sta_idx == STA_IDX_INVALID)
+			cl_sta = NULL;
+	}
+
+	if (cl_recovery_in_progress(cl_hw)) {
+		cl_hw->tx_packet_cntr.drop.in_recovery++;
+
+		if (cl_sta) {
+			struct cl_vif *cl_vif = cl_sta->cl_vif;
+
+			if (cl_vif) {
+				struct ieee80211_vif *vif = cl_vif->vif;
+				u8 hw_queue;
+
+				if (vif) {
+					hw_queue = vif->hw_queue[skb_get_queue_mapping(skb)];
+					cl_vif->trfc_cntrs[hw_queue].tx_dropped++;
+				}
+			}
+		}
+
+		cl_tx_drop_skb(skb);
+		return;
+	}
+
+	if (cl_sta && (tx_info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP))
+		goto fast_tx;
+
+	if (tx_info->flags & IEEE80211_TX_CTL_AMPDU)
+		cl_ops_tx_agg(cl_hw, skb, tx_info, cl_sta);
+	else
+		cl_ops_tx_single(cl_hw, skb, tx_info, cl_sta, sta);
+
+	return;
+
+fast_tx:
+	if (tx_info->flags & IEEE80211_TX_CTL_AMPDU)
+		cl_tx_fast_agg(cl_hw, cl_sta, skb, true);
+	else
+		cl_tx_fast_single(cl_hw, cl_sta, skb, true);
+}
+
+int cl_ops_start(struct ieee80211_hw *hw)
+{
+	/*
+	 * Called before the first netdevice attached to the hardware
+	 * is enabled. This should turn on the hardware and must turn on
+	 * frame reception (for possibly enabled monitor interfaces.)
+	 * Returns negative error codes, these may be seen in userspace,
+	 * or zero.
+	 * When the device is started it should not have a MAC address
+	 * to avoid acknowledging frames before a non-monitor device
+	 * is added.
+	 * Must be implemented and can sleep.
+	 * It does not return until the firmware is up and running.
+	 */
+	int error = 0;
+	struct cl_hw *cl_hw = hw->priv;
+	struct cl_tcv_conf *conf = cl_hw->conf;
+	struct cl_hw *cl_hw_other = cl_hw_other_tcv(cl_hw);
+
+	if (!cl_hw->ipc_env) {
+		CL_DBG_ERROR(cl_hw, "ipc_env is NULL!\n");
+		return -ENODEV;
+	}
+
+	/* Exits if device is already started */
+	if (WARN_ON(test_bit(CL_DEV_STARTED, &cl_hw->drv_flags)))
+		return -EBUSY;
+
+	/* Device is now started.
+	 * Set CL_DEV_STARTED bit before the calls to other messages sent to
+	 * firmware, to prevent them from being blocked*
+	 */
+
+	set_bit(CL_DEV_STARTED, &cl_hw->drv_flags);
+
+	if (!cl_recovery_in_progress(cl_hw)) {
+		/* Read version */
+		error = cl_version_update(cl_hw);
+		if (error)
+			return error;
+
+		error = cl_temperature_diff_e2p_read(cl_hw);
+		if (error)
+			return error;
+	}
+
+	/* Set firmware debug module filter */
+	error = cl_msg_tx_dbg_set_ce_mod_filter(cl_hw, conf->ci_fw_dbg_module);
+	if (error)
+		return error;
+
+	/* Set firmware debug severity level */
+	error = cl_msg_tx_dbg_set_sev_filter(cl_hw, conf->ci_fw_dbg_severity);
+	if (error)
+		return error;
+
+	/* Set firmware rate fallbacks */
+	error = cl_msg_tx_set_rate_fallback(cl_hw);
+	if (error)
+		return error;
+
+	error = cl_msg_tx_ndp_tx_control(cl_hw,
+					 conf->ci_sensing_ndp_tx_chain_mask,
+					 conf->ci_sensing_ndp_tx_bw,
+					 conf->ci_sensing_ndp_tx_format,
+					 conf->ci_sensing_ndp_tx_num_ltf);
+	if (error)
+		return error;
+
+	/* Set default, multicast, broadcast rate */
+	cl_rate_ctrl_set_default(cl_hw);
+#ifdef CONFIG_CL8K_DYN_MCAST_RATE
+	cl_dyn_mcast_rate_set(cl_hw);
+#endif /* CONFIG_CL8K_DYN_MCAST_RATE */
+#ifdef CONFIG_CL8K_DYN_BCAST_RATE
+	cl_dyn_bcast_rate_set(cl_hw, 0);
+#endif /* CONFIG_CL8K_DYN_BCAST_RATE */
+
+	ieee80211_wake_queues(hw);
+
+	clear_bit(CL_DEV_INIT, &cl_hw->drv_flags);
+
+	cl_edca_hw_conf(cl_hw);
+
+	if (!cl_hw->chip->conf->ce_calib_runtime_en) {
+		cl_calib_dcoc_init_calibration(cl_hw);
+
+		if (cl_hw->chip->conf->ce_production_mode)
+			cl_calib_iq_init_production(cl_hw);
+		else if (!cl_hw_other || test_bit(CL_DEV_STARTED, &cl_hw_other->drv_flags))
+			cl_calib_iq_init_calibration(cl_hw);
+	}
+
+	return error;
+}
+
+void cl_ops_stop(struct ieee80211_hw *hw)
+{
+	/*
+	 * Called after last netdevice attached to the hardware
+	 * is disabled. This should turn off the hardware (at least
+	 * it must turn off frame reception.)
+	 * May be called right after add_interface if that rejects
+	 * an interface. If you added any work onto the mac80211 workqueue
+	 * you should ensure to cancel it on this callback.
+	 * Must be implemented and can sleep.
+	 */
+	struct cl_hw *cl_hw = hw->priv;
+
+	/* Stop mac80211 queues */
+	ieee80211_stop_queues(hw);
+
+	/* Go to idle */
+	cl_msg_tx_set_idle(cl_hw, MAC_IDLE_SYNC, true);
+
+	/*
+	 * Clear CL_DEV_STARTED to prevent message to be sent (besides reset and start).
+	 * It also blocks transmission of new packets
+	 */
+	clear_bit(CL_DEV_STARTED, &cl_hw->drv_flags);
+
+	cl_hw->num_ap_started = 0;
+	cl_hw->channel = 0;
+	cl_hw->radio_status = RADIO_STATUS_OFF;
+}
+
+static int cl_add_interface_to_firmware(struct cl_hw *cl_hw,
+					struct ieee80211_vif *vif, u8 vif_index)
+{
+	struct mm_add_if_cfm *add_if_cfm;
+	int ret = 0;
+
+	/* Forward the information to the firmware */
+	ret = cl_msg_tx_add_if(cl_hw, vif, vif_index);
+	if (ret)
+		return ret;
+
+	add_if_cfm = (struct mm_add_if_cfm *)(cl_hw->msg_cfm_params[MM_ADD_IF_CFM]);
+	if (!add_if_cfm)
+		return -ENOMSG;
+
+	if (add_if_cfm->status != 0) {
+		cl_dbg_verbose(cl_hw, "Status Error (%u)\n", add_if_cfm->status);
+		ret = -EIO;
+	}
+
+	cl_msg_tx_free_cfm_params(cl_hw, MM_ADD_IF_CFM);
+
+	return ret;
+}
+
+static enum cl_iface_conf cl_recalc_hw_iface(struct cl_hw *cl_hw)
+{
+	struct cl_vif *cl_vif = NULL;
+	u8 num_ap = 0, num_sta = 0, num_mp = 0;
+
+	read_lock_bh(&cl_hw->vif_db.lock);
+	list_for_each_entry(cl_vif, &cl_hw->vif_db.head, list) {
+		switch (cl_vif->vif->type) {
+		case NL80211_IFTYPE_AP:
+			num_ap++;
+			break;
+		case NL80211_IFTYPE_STATION:
+			num_sta++;
+			break;
+		case NL80211_IFTYPE_MESH_POINT:
+			num_mp++;
+			break;
+		default:
+			read_unlock_bh(&cl_hw->vif_db.lock);
+			return CL_IFCONF_MAX;
+		}
+	}
+	read_unlock_bh(&cl_hw->vif_db.lock);
+
+	if (num_ap > 0 && num_sta == 0 && num_mp == 0)
+		return CL_IFCONF_AP;
+	if (num_ap == 0 && num_sta == 1 && num_mp == 0)
+		return CL_IFCONF_STA;
+	if (num_ap == 1 && num_sta == 1 && num_mp == 0)
+		return CL_IFCONF_REPEATER;
+	if (num_ap > 0 && num_sta == 0 && num_mp == 1)
+		return CL_IFCONF_MESH_AP;
+	if (num_ap == 0 && num_sta == 0 && num_mp == 1)
+		return CL_IFCONF_MESH_ONLY;
+
+	return CL_IFCONF_MAX;
+}
+
+int cl_ops_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	/*
+	 * Called when a netdevice attached to the hardware is
+	 * enabled. Because it is not called for monitor mode devices, start
+	 * and stop must be implemented.
+	 * The driver should perform any initialization it needs before
+	 * the device can be enabled. The initial configuration for the
+	 * interface is given in the conf parameter.
+	 * The callback may refuse to add an interface by returning a
+	 * negative error code (which will be seen in userspace.)
+	 * Must be implemented and can sleep.
+	 */
+	struct cl_hw *cl_hw = hw->priv;
+	struct cl_chip *chip = cl_hw->chip;
+	struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+	struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif);
+	struct net_device *dev = NULL;
+	u8 ac;
+
+	if (!wdev)
+		return -ENODEV;
+
+	dev = wdev->netdev;
+	if (!dev)
+		return -ENODEV;
+
+	/*
+	 * In recovery just send the message to firmware and exit
+	 * (also make sure cl_vif already exists).
+	 */
+	if (cl_recovery_in_progress(cl_hw) && cl_vif_get_by_dev(cl_hw, dev))
+		return cl_add_interface_to_firmware(cl_hw, vif, cl_vif->vif_index);
+
+	cl_vif->cl_hw = cl_hw;
+	cl_vif->vif = vif;
+	cl_vif->dev = dev;
+	cl_vif->vif_index = cl_mac_addr_find_idx(cl_hw, vif->addr);
+
+	/* MAC address not found - invalid address */
+	if (cl_vif->vif_index == BSS_INVALID_IDX) {
+		cl_dbg_err(cl_hw, "Error: Invalid MAC address %pM for vif %s\n",
+			   vif->addr, dev->name);
+
+		return -EINVAL;
+	}
+
+	if (chip->conf->ce_production_mode || vif->type == NL80211_IFTYPE_STATION)
+		cl_vif->tx_en = true;
+
+	cl_vif_key_init(cl_vif);
+
+	if (cl_add_interface_to_firmware(cl_hw, vif, cl_vif->vif_index))
+		return -EINVAL;
+
+	cl_vif->conn_data = kzalloc(sizeof(*cl_vif->conn_data), GFP_KERNEL);
+	if (!cl_vif->conn_data) {
+		cl_dbg_verbose(cl_hw, "Memory allocation for conn_data failed !!!\n");
+		return -ENOMEM;
+	}
+
+	if (vif->type != NL80211_IFTYPE_STATION)
+		vif->cab_queue = CL_HWQ_VO;
+
+	cl_vif_add(cl_hw, cl_vif);
+	cl_hw_set_iface_conf(cl_hw, cl_recalc_hw_iface(cl_hw));
+
+	for (ac = 0; ac < AC_MAX; ac++)
+		vif->hw_queue[ac] = cl_ac2hwq[ac];
+
+	if (cl_radio_is_on(cl_hw) && vif->type == NL80211_IFTYPE_AP)
+		cl_vif->tx_en = true;
+
+	/* Set active state in station mode after ifconfig down and up */
+	if (cl_hw->conf->ce_listener_en)
+		cl_radio_on(cl_hw);
+	else if (cl_radio_is_on(cl_hw) && vif->type == NL80211_IFTYPE_STATION)
+		cl_msg_tx_set_idle(cl_hw, MAC_ACTIVE, true);
+
+	if (vif->type == NL80211_IFTYPE_MESH_POINT) {
+		tasklet_init(&cl_hw->tx_mesh_bcn_task, cl_tx_bcn_mesh_task,
+			     (unsigned long)cl_vif);
+		cl_radio_on(cl_hw);
+		cl_vif->tx_en = true;
+		cl_vif->mesh_basic_rates = cl_cap_set_mesh_basic_rates(cl_hw);
+	}
+
+	return 0;
+}
+
+void cl_ops_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	/*
+	 * Notifies a driver that an interface is going down.
+	 * The stop callback is called after this if it is the last interface
+	 * and no monitor interfaces are present.
+	 * When all interfaces are removed, the MAC address in the hardware
+	 * must be cleared so the device no longer acknowledges packets,
+	 * the mac_addr member of the conf structure is, however, set to the
+	 * MAC address of the device going away.
+	 * Hence, this callback must be implemented. It can sleep.
+	 */
+	struct cl_hw *cl_hw = hw->priv;
+	struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+
+	if (vif->type == NL80211_IFTYPE_MESH_POINT)
+		tasklet_kill(&cl_hw->tx_mesh_bcn_task);
+
+	if (!cl_recovery_in_progress(cl_hw)) {
+		kfree(cl_vif->conn_data);
+		cl_vif_remove(cl_hw, cl_vif);
+		cl_msg_tx_remove_if(cl_hw, cl_vif->vif_index);
+	} else {
+		cl_vif_remove(cl_hw, cl_vif);
+	}
+	cl_hw_set_iface_conf(cl_hw, cl_recalc_hw_iface(cl_hw));
+
+	cl_vif_key_deinit(cl_vif);
+
+	cl_vif->cl_hw = NULL;
+	cl_vif->vif = NULL;
+	cl_vif->dev = NULL;
+}
+
+static int cl_ops_conf_change_channel(struct ieee80211_hw *hw)
+{
+	struct cl_hw *cl_hw = hw->priv;
+	struct cl_chip *chip = cl_hw->chip;
+	struct cfg80211_chan_def *chandef = &hw->conf.chandef;
+	enum nl80211_chan_width width = chandef->width;
+	u32 primary = chandef->chan->center_freq;
+	u32 center = chandef->center_freq1;
+	u32 channel = ieee80211_frequency_to_channel(primary);
+	u8 bw = cl_width_to_bw(width);
+	int ret = 0;
+
+	if (!test_bit(CL_DEV_STARTED, &cl_hw->drv_flags))
+		return 0;
+
+	/* WA: for the first set-channel in production mode use the nvram values */
+	if (cl_hw_is_prod_or_listener(cl_hw) || !IS_REAL_PHY(chip)) {
+		ret = cl_chandef_get_default(cl_hw, &channel, &bw,
+					     &width, &primary, &center);
+
+		if (ret != 0)
+			return ret;
+	}
+
+	cl_dbg_trace(cl_hw,
+		     "channel(%u), primary(%u), center(%u), width(%u), bw(%u)\n",
+		     channel, primary, center, width, bw);
+
+	if (cl_hw->channel == channel &&
+	    cl_hw->bw == bw &&
+	    cl_hw->primary_freq == primary &&
+	    cl_hw->center_freq == center)
+		goto dfs_cac;
+
+	/*
+	 * Flush the pending data to ensure that we will finish the pending
+	 * transmissions before changing the channel
+	 */
+	if (IS_REAL_PHY(chip))
+		cl_ops_flush(hw, NULL, -1, false);
+
+	if (cl_hw->chip->conf->ce_calib_runtime_en)
+		ret = cl_calib_runtime_and_switch_channel(cl_hw, channel, bw, primary, center);
+	else
+		ret = cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center,
+					    CL_CALIB_PARAMS_DEFAULT_STRUCT);
+	if (ret)
+		return -EIO;
+
+	/**
+	 * Set preffered channel type to HT+/- based on current hapd
+	 * configuration.
+	 */
+	if (cl_band_is_24g(cl_hw)) {
+		u8 ct = cfg80211_get_chandef_type(&hw->conf.chandef);
+
+		switch (ct) {
+		case NL80211_CHAN_HT40PLUS:
+		case NL80211_CHAN_HT40MINUS:
+			if (ct != cl_hw->ht40_preffered_ch_type) {
+				cl_dbg_info(cl_hw, "HT40 preffered channel type=%s\n",
+					    ct == NL80211_CHAN_HT40PLUS ? "HT+" : "HT-");
+				cl_hw->ht40_preffered_ch_type = ct;
+			}
+		}
+	}
+
+	cl_wrs_api_bss_set_bw(cl_hw, bw);
+
+dfs_cac:
+	/*
+	 * TODO: This callback is being spawned even in STA mode, moreover,
+	 * "start_ap" comes later - it is unclear whether we are an AP at this
+	 * stage. Likely, may be solved by moving "force_cac_*" states to beginning
+	 * of "start_ap", but the request should stay in current callback
+	 */
+	if (!cl_band_is_5g(cl_hw))
+		return 0;
+
+	/*
+	 * Radar listening may occur at DFS channels during in-service mode,
+	 * so CAC may clear the channels, but radar listening should be
+	 * still active, and should start it as soon as we can.
+	 */
+	if (hw->conf.radar_enabled) {
+		/* If channel policy demans to be in CAC - need to request it */
+		if (!cl_dfs_is_in_cac(cl_hw) &&
+		    chandef->chan->dfs_state == NL80211_DFS_USABLE)
+			cl_dfs_request_cac(cl_hw, true);
+
+		if (!cl_dfs_radar_listening(cl_hw))
+			cl_dfs_radar_listen_start(cl_hw);
+	} else {
+		/*
+		 * No sense to continue be in silent mode if the channel was
+		 * cleared
+		 */
+
+		if (cl_dfs_is_in_cac(cl_hw) &&
+		    chandef->chan->dfs_state == NL80211_DFS_AVAILABLE)
+			cl_dfs_request_cac(cl_hw, false);
+
+		if (cl_dfs_radar_listening(cl_hw))
+			cl_dfs_radar_listen_end(cl_hw);
+	}
+	/*
+	 * We have just finished channel switch.
+	 * Now, check what to do with CAC.
+	 */
+	if (cl_dfs_requested_cac(cl_hw))
+		cl_dfs_force_cac_start(cl_hw);
+	else if (cl_dfs_is_in_cac(cl_hw))
+		cl_dfs_force_cac_end(cl_hw);
+
+	return 0;
+}
+
+int cl_ops_config(struct ieee80211_hw *hw, u32 changed)
+{
+	/*
+	 * Handler for configuration requests. IEEE 802.11 code calls this
+	 * function to change hardware configuration, e.g., channel.
+	 * This function should never fail but returns a negative error code
+	 * if it does. The callback can sleep
+	 */
+	int error = 0;
+
+	if (changed & IEEE80211_CONF_CHANGE_CHANNEL)
+		error = cl_ops_conf_change_channel(hw);
+
+	return error;
+}
+
+/*
+ * @bss_info_changed: Handler for configuration requests related to BSS
+ *  parameters that may vary during BSS's lifespan, and may affect low
+ *  level driver (e.g. assoc/disassoc status, erp parameters).
+ *  This function should not be used if no BSS has been set, unless
+ *  for association indication. The @changed parameter indicates which
+ *  of the bss parameters has changed when a call is made. The callback
+ *  can sleep.
+ */
+void cl_ops_bss_info_changed(struct ieee80211_hw *hw,
+			     struct ieee80211_vif *vif,
+			     struct ieee80211_bss_conf *info,
+			     u32 changed)
+{
+	struct cl_hw *cl_hw = hw->priv;
+	struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+
+	if (changed & BSS_CHANGED_ASSOC) {
+		if (cl_msg_tx_set_associated(cl_hw, info))
+			return;
+	}
+
+	if (changed & BSS_CHANGED_BSSID) {
+		if (cl_msg_tx_set_bssid(cl_hw, info->bssid, cl_vif->vif_index))
+			return;
+	}
+
+	if (changed & BSS_CHANGED_BEACON_INT) {
+		u16 smallest_int = cl_ops_recalc_smallest_tbtt(cl_hw);
+
+		cl_hw->smallest_beacon_int = smallest_int;
+
+		if (vif->type == NL80211_IFTYPE_AP ||
+		    cl_hw_get_iface_conf(cl_hw) == CL_IFCONF_MESH_ONLY) {
+			if (cl_msg_tx_set_beacon_int(cl_hw, info->beacon_int,
+						     cl_vif->vif_index))
+				return;
+			if (cl_msg_tx_dtim(cl_hw, info->dtim_period))
+				return;
+		}
+
+		if (vif->type == NL80211_IFTYPE_MESH_POINT &&
+		    cl_hw_get_iface_conf(cl_hw) == CL_IFCONF_MESH_AP) {
+			cl_ops_set_mesh_tbtt(cl_hw, info->beacon_int, smallest_int);
+		}
+	}
+
+	if (changed & BSS_CHANGED_BASIC_RATES) {
+		int shift = hw->wiphy->bands[hw->conf.chandef.chan->band]->bitrates[0].hw_value;
+
+		if (vif->type == NL80211_IFTYPE_MESH_POINT)
+			if (cl_vif->mesh_basic_rates)
+				info->basic_rates = cl_vif->mesh_basic_rates;
+
+		if (cl_msg_tx_set_basic_rates(cl_hw, info->basic_rates << shift))
+			return;
+		/* TODO: check if cl_msg_tx_set_mode() should be called */
+	}
+
+	if (changed & BSS_CHANGED_ERP_SLOT) {
+		/*
+		 * We must be in 11g mode here
+		 * TODO: we can add a check on the mode
+		 */
+		if (cl_msg_tx_set_slottime(cl_hw, info->use_short_slot))
+			return;
+	}
+
+	if (changed & BSS_CHANGED_BANDWIDTH)
+		cl_wrs_api_bss_set_bw(cl_hw, cl_width_to_bw(info->chandef.width));
+
+	if (changed & BSS_CHANGED_TXPOWER) {
+		if (info->txpower_type == NL80211_TX_POWER_FIXED) {
+			cl_hw->new_tx_power = info->txpower;
+			cl_power_tables_update(cl_hw, &cl_hw->phy_data_info.data->pwr_tables);
+			cl_msg_tx_refresh_power(cl_hw);
+		}
+	}
+
+	if (changed & BSS_CHANGED_BEACON) {
+		struct beacon_data *beacon = NULL;
+		struct ieee80211_sub_if_data *sdata =
+			container_of(vif, struct ieee80211_sub_if_data, vif);
+		struct ieee80211_ht_cap *ht_cap = NULL;
+		struct ieee80211_vht_cap *vht_cap = NULL;
+		struct ieee80211_he_cap_elem *he_cap = NULL;
+		bool sgi_en = false;
+		u8 hw_mode = cl_hw->hw_mode;
+		enum cl_wireless_mode wireless_mode = cl_hw->wireless_mode;
+
+		rcu_read_lock();
+
+		if (sdata->vif.type == NL80211_IFTYPE_AP)
+			beacon = rcu_dereference(sdata->u.ap.beacon);
+		else if (ieee80211_vif_is_mesh(&sdata->vif))
+			beacon = rcu_dereference(sdata->u.mesh.beacon);
+
+		if (beacon) {
+			size_t ies_len = beacon->tail_len;
+			const u8 *ies = beacon->tail;
+			const u8 *cap =  NULL;
+			int var_offset = offsetof(struct ieee80211_mgmt, u.beacon.variable);
+			int len = beacon->head_len - var_offset;
+			const u8 *var_pos = beacon->head + var_offset;
+			const u8 *rate_ie = NULL;
+
+			cl_vif->wmm_enabled = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT,
+								      WLAN_OUI_TYPE_MICROSOFT_WMM,
+								      ies,
+								      ies_len);
+			cl_dbg_info(cl_hw, "vif=%d wmm_enabled=%d\n",
+				    cl_vif->vif_index,
+				    cl_vif->wmm_enabled);
+
+			cap = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, ies, ies_len);
+			if (cap && cap[1] >= sizeof(*ht_cap)) {
+				ht_cap = (void *)(cap + 2);
+				sgi_en |= (le16_to_cpu(ht_cap->cap_info) &
+					   IEEE80211_HT_CAP_SGI_20) ||
+					  (le16_to_cpu(ht_cap->cap_info) &
+					   IEEE80211_HT_CAP_SGI_40);
+			}
+
+			cap = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, ies, ies_len);
+			if (cap && cap[1] >= sizeof(*vht_cap)) {
+				vht_cap = (void *)(cap + 2);
+				sgi_en |= (le32_to_cpu(vht_cap->vht_cap_info) &
+					   IEEE80211_VHT_CAP_SHORT_GI_80) ||
+					  (le32_to_cpu(vht_cap->vht_cap_info) &
+					   IEEE80211_VHT_CAP_SHORT_GI_160);
+			}
+
+			cap = cfg80211_find_ext_ie(WLAN_EID_EXT_HE_CAPABILITY, ies, ies_len);
+			if (cap && cap[1] >= sizeof(*he_cap) + 1)
+				he_cap = (void *)(cap + 3);
+
+			rate_ie = cfg80211_find_ie(WLAN_EID_SUPP_RATES, var_pos, len);
+			if (rate_ie) {
+				if (cl_band_is_24g(cl_hw))
+					if (cl_is_valid_g_rates(rate_ie))
+						hw_mode = cl_hw->conf->ci_cck_in_hw_mode ?
+							     HW_MODE_BG : HW_MODE_G;
+					else
+						hw_mode = HW_MODE_B;
+				else
+					hw_mode = HW_MODE_A;
+			}
+		} else {
+			cl_dbg_warn(cl_hw, "beacon_data not set!\n");
+		}
+
+		rcu_read_unlock();
+
+		/*
+		 * FIXME: 1. WRS has no VIF-specific capabs settings.
+		 *        2. WRS has no BW-specific SGI configuration support.
+		 **/
+
+		/* If found any capabs info and state is different - update sgi */
+		if ((ht_cap || vht_cap) && (cl_wrs_api_bss_is_sgi_en(cl_hw) != sgi_en))
+			cl_wrs_api_bss_set_sgi(cl_hw, sgi_en);
+
+		if (hw_mode != cl_hw->hw_mode) {
+			cl_hw->hw_mode = hw_mode;
+			sgi_en =
+				(ht_cap || vht_cap) ? sgi_en : cl_hw->conf->ci_short_guard_interval;
+#ifdef CONFIG_CL8K_DYN_BCAST_RATE
+			cl_dyn_bcast_update(cl_hw);
+#endif /* CONFIG_CL8K_DYN_BCAST_RATE */
+#ifdef CONFIG_CL8K_DYN_MCAST_RATE
+			cl_dyn_mcast_update(cl_hw);
+#endif /* CONFIG_CL8K_DYN_MCAST_RATE */
+			cl_wrs_api_bss_capab_update(cl_hw, cl_hw->bw, sgi_en);
+		}
+
+		wireless_mode = cl_recalc_wireless_mode(cl_hw, !!ht_cap, !!vht_cap, !!he_cap);
+		if (wireless_mode != cl_hw->wireless_mode) {
+			sgi_en =
+				(ht_cap || vht_cap) ? sgi_en : cl_hw->conf->ci_short_guard_interval;
+			cl_hw->wireless_mode = wireless_mode;
+#ifdef CONFIG_CL8K_DYN_MCAST_RATE
+			cl_dyn_mcast_update(cl_hw);
+#endif /* CONFIG_CL8K_DYN_MCAST_RATE */
+			cl_wrs_api_bss_capab_update(cl_hw, cl_hw->bw, sgi_en);
+		}
+	}
+}
+
+int cl_ops_start_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	set_bit(CL_DEV_AP_STARTED, &cl_hw->drv_flags);
+
+	cl_hw->num_ap_started++;
+	if (cl_hw->conf->ce_radio_on) {
+		if (cl_radio_is_off(cl_hw))
+			cl_radio_on(cl_hw);
+
+		return 0;
+	}
+
+	/*
+	 * Set active state when cl_ops_start_ap() is called not during first driver start
+	 * but rather after removing all interfaces and then doing up again to one interface.
+	 */
+	if (cl_radio_is_on(cl_hw) && !cl_recovery_in_progress(cl_hw))
+		cl_msg_tx_set_idle(cl_hw, MAC_ACTIVE, true);
+
+	return 0;
+}
+
+void cl_ops_stop_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	/*
+	 * Unset CL_DEV_AP_STARTED in order to avoid
+	 * calling cl_ops_conf_change_channel after unloading the driver
+	 */
+	clear_bit(CL_DEV_AP_STARTED, &cl_hw->drv_flags);
+
+	cl_hw->num_ap_started--;
+
+	if (!cl_hw->num_ap_started)
+		cl_hw->channel = 0;
+}
+
+u64 cl_ops_prepare_multicast(struct ieee80211_hw *hw, struct netdev_hw_addr_list *mc_list)
+{
+	return netdev_hw_addr_list_count(mc_list);
+}
+
+void cl_ops_configure_filter(struct ieee80211_hw *hw, u32 changed_flags,
+			     u32 *total_flags, u64 multicast)
+{
+	/*
+	 * configure_filter: Configure the device's RX filter.
+	 * See the section "Frame filtering" for more information.
+	 * This callback must be implemented and can sleep.
+	 */
+	struct cl_hw *cl_hw = hw->priv;
+
+	cl_dbg_trace(cl_hw, "total_flags = 0x%08x\n", *total_flags);
+
+	/*
+	 * Reset our filter flags since our start/stop ops reset
+	 * the programmed settings
+	 */
+	if (!test_bit(CL_DEV_STARTED, &cl_hw->drv_flags)) {
+		*total_flags = 0;
+		return;
+	}
+
+	if (multicast)
+		*total_flags |= FIF_ALLMULTI;
+	else
+		*total_flags &= ~FIF_ALLMULTI;
+
+	/* TODO: optimize with changed_flags vs multicast */
+	cl_msg_tx_set_filter(cl_hw, *total_flags, false);
+
+	*total_flags &= ~(1 << 31);
+}
+
+int cl_ops_set_key(struct ieee80211_hw *hw,
+		   enum set_key_cmd cmd,
+		   struct ieee80211_vif *vif,
+		   struct ieee80211_sta *sta,
+		   struct ieee80211_key_conf *key)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	return cl_key_set(cl_hw, cmd, vif, sta, key);
+}
+
+void cl_ops_sw_scan_start(struct ieee80211_hw *hw,
+			  struct ieee80211_vif *vif,
+			  const u8 *mac_addr)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	cl_hw->sw_scan_in_progress = 1;
+
+	if (cl_hw->conf->ce_radio_on &&
+	    cl_radio_is_off(cl_hw) &&
+	    vif->type == NL80211_IFTYPE_STATION)
+		cl_radio_on(cl_hw);
+
+	if (cl_dfs_is_in_cac(cl_hw))
+		cl_dfs_force_cac_end(cl_hw);
+}
+
+void cl_ops_sw_scan_complete(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	cl_hw->sw_scan_in_progress = 0;
+}
+
+int cl_ops_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+		     enum ieee80211_sta_state old_state, enum ieee80211_sta_state new_state)
+{
+	struct cl_hw *cl_hw = hw->priv;
+	int error = 0;
+
+	if (old_state == new_state)
+		return 0;
+
+	if (old_state == IEEE80211_STA_NOTEXIST &&
+	    new_state == IEEE80211_STA_NONE) {
+		cl_sta_init_sta(cl_hw, sta);
+	} else if (old_state == IEEE80211_STA_AUTH &&
+		   new_state == IEEE80211_STA_ASSOC) {
+		error = cl_sta_add(cl_hw, vif, sta);
+	} else if (old_state == IEEE80211_STA_ASSOC &&
+		   new_state == IEEE80211_STA_AUTH) {
+		cl_sta_remove(cl_hw, vif, sta);
+	}
+
+	return error;
+}
+
+void cl_ops_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		       enum sta_notify_cmd cmd, struct ieee80211_sta *sta)
+{
+	struct cl_hw *cl_hw = (struct cl_hw *)hw->priv;
+	struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+	bool is_ps = (bool)!cmd;
+
+	cl_sta_ps_notify(cl_hw, cl_sta, is_ps);
+}
+
+int cl_ops_conf_tx(struct ieee80211_hw *hw,
+		   struct ieee80211_vif *vif,
+		   u16 ac_queue,
+		   const struct ieee80211_tx_queue_params *params)
+{
+	/*
+	 * Configure TX queue parameters (EDCF (aifs, cw_min, cw_max),
+	 * bursting) for a hardware TX queue.
+	 * Returns a negative error code on failure.
+	 * The callback can sleep.
+	 */
+
+	/* We only handle STA edca here */
+	if (vif->type == NL80211_IFTYPE_STATION) {
+		struct cl_hw *cl_hw = hw->priv;
+		struct ieee80211_he_mu_edca_param_ac_rec mu_edca = {0};
+		struct edca_params edca_params = {
+			.aifsn = (u8)(params->aifs),
+			.cw_min = (u8)(ilog2(params->cw_min + 1)),
+			.cw_max = (u8)(ilog2(params->cw_max + 1)),
+			.txop = (u8)(params->txop)
+		};
+
+		if (cl_hw->wireless_mode > WIRELESS_MODE_HT_VHT)
+			memcpy(&mu_edca, &params->mu_edca_param_rec, sizeof(mu_edca));
+
+		cl_edca_set(cl_hw, cl_ac2edca[ac_queue], &edca_params, &mu_edca);
+	}
+	return 0;
+}
+
+void cl_ops_sta_rc_update(struct ieee80211_hw *hw,
+			  struct ieee80211_vif *vif,
+			  struct ieee80211_sta *sta,
+			  u32 changed)
+{
+	struct cl_hw *cl_hw = (struct cl_hw *)hw->priv;
+	struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+	struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+	u8 bw = wrs_sta->max_rate_cap.bw;
+	u8 nss = wrs_sta->max_rate_cap.nss;
+
+	if (changed & IEEE80211_RC_SMPS_CHANGED)
+		cl_wrs_api_set_smps_mode(cl_hw, sta, sta->bandwidth);
+
+	WARN_ON(sta->rx_nss == 0);
+	if (changed & IEEE80211_RC_NSS_CHANGED)
+		nss = min_t(u8, sta->rx_nss, WRS_SS_MAX) - 1;
+
+	if (changed & IEEE80211_RC_BW_CHANGED)
+		bw = sta->bandwidth;
+
+	if ((changed & IEEE80211_RC_NSS_CHANGED) || (changed & IEEE80211_RC_BW_CHANGED))
+		cl_wrs_api_nss_or_bw_changed(cl_hw, sta, nss, bw);
+}
+
+int cl_ops_ampdu_action(struct ieee80211_hw *hw,
+			struct ieee80211_vif *vif,
+			struct ieee80211_ampdu_params *params)
+{
+	struct cl_hw *cl_hw = (struct cl_hw *)hw->priv;
+	struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(params->sta);
+	int ret = 0;
+
+	switch (params->action) {
+	case IEEE80211_AMPDU_RX_START:
+		ret = cl_ampdu_rx_start(cl_hw, cl_sta, params->tid,
+					params->ssn, params->buf_size);
+		break;
+	case IEEE80211_AMPDU_RX_STOP:
+		cl_ampdu_rx_stop(cl_hw, cl_sta, params->tid);
+		break;
+	case IEEE80211_AMPDU_TX_START:
+		ret = cl_ampdu_tx_start(cl_hw, vif, cl_sta, params->tid,
+					params->ssn);
+		break;
+	case IEEE80211_AMPDU_TX_OPERATIONAL:
+		ret = cl_ampdu_tx_operational(cl_hw, cl_sta, params->tid,
+					      params->buf_size, params->amsdu);
+		break;
+	case IEEE80211_AMPDU_TX_STOP_CONT:
+	case IEEE80211_AMPDU_TX_STOP_FLUSH:
+	case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
+		ret = cl_ampdu_tx_stop(cl_hw, vif, params->action, cl_sta,
+				       params->tid);
+		break;
+	default:
+		pr_warn("Error: Unknown AMPDU action (%d)\n", params->action);
+	}
+
+	return ret;
+}
+
+int cl_ops_post_channel_switch(struct ieee80211_hw *hw,
+			       struct ieee80211_vif *vif)
+{
+	/* TODO: Need to handle post switch */
+	return 0;
+}
+
+void cl_ops_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u32 queues, bool drop)
+{
+	struct cl_hw *cl_hw = hw->priv;
+	int flush_duration;
+
+	if (test_bit(CL_DEV_HW_RESTART, &cl_hw->drv_flags)) {
+		cl_dbg_verbose(cl_hw, ": bypassing (CL_DEV_HW_RESTART set)\n");
+		return;
+	}
+
+	/* Wait for a maximum time of 200ms until all pending frames are flushed */
+	for (flush_duration = 0; flush_duration < 200; flush_duration++) {
+		if (!cl_txq_frames_pending(cl_hw))
+			return;
+
+		/* Lets sleep and hope for the best */
+		usleep_range(1000, 2000);
+	}
+}
+
+bool cl_ops_tx_frames_pending(struct ieee80211_hw *hw)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	return cl_txq_frames_pending(cl_hw);
+}
+
+void cl_ops_reconfig_complete(struct ieee80211_hw *hw,
+			      enum ieee80211_reconfig_type reconfig_type)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	cl_recovery_reconfig_complete(cl_hw);
+}
+
+int cl_ops_get_txpower(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int *dbm)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	if (cl_hw->phy_data_info.data)
+		*dbm = cl_power_get_max(cl_hw);
+	else
+		*dbm = 0;
+
+	return 0;
+}
+
+int cl_ops_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
+{
+	/* TODO: Fix this call */
+	return 0;
+}
+
+static void cl_ops_mgd_assoc(struct cl_hw *cl_hw, struct ieee80211_vif *vif)
+{
+	struct ieee80211_sub_if_data *sdata = container_of(vif, struct ieee80211_sub_if_data, vif);
+	struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+	struct ieee80211_sta *sta = ieee80211_find_sta(vif, sdata->u.mgd.bssid);
+
+	if (!sta) {
+		/* Should never happen */
+		cl_dbg_verbose(cl_hw, "sta is NULL !!!\n");
+		return;
+	}
+
+	cl_sta_mgd_add(cl_hw, cl_vif, sta);
+
+	if (cl_hw_get_iface_conf(cl_hw) == CL_IFCONF_REPEATER) {
+		cl_vif_ap_tx_enable(cl_hw, true);
+		set_bit(CL_DEV_REPEATER, &cl_hw->drv_flags);
+	}
+}
+
+static void cl_ops_mgd_disassoc(struct cl_hw *cl_hw)
+{
+	if (cl_hw_get_iface_conf(cl_hw) == CL_IFCONF_REPEATER) {
+		cl_vif_ap_tx_enable(cl_hw, false);
+		clear_bit(CL_DEV_REPEATER, &cl_hw->drv_flags);
+	}
+}
+
+void cl_ops_event_callback(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+			   const struct ieee80211_event *event)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	if (event->type == MLME_EVENT) {
+		if (event->u.mlme.data == ASSOC_EVENT &&
+		    event->u.mlme.status == MLME_SUCCESS)
+			cl_ops_mgd_assoc(cl_hw, vif);
+		else if (event->u.mlme.data == DEAUTH_TX_EVENT ||
+			 event->u.mlme.data == DEAUTH_RX_EVENT)
+			cl_ops_mgd_disassoc(cl_hw);
+	}
+}
+
+/* This function is required for PS flow - do not remove */
+int cl_ops_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set)
+{
+	return 0;
+}
+
+int cl_ops_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant)
+{
+	struct cl_hw *cl_hw = hw->priv;
+
+	*rx_ant = cl_hw->mask_num_antennas;
+	*tx_ant = cl_hw->mask_num_antennas;
+
+	return 0;
+}
+
+u32 cl_ops_get_expected_throughput(struct ieee80211_hw *hw,
+				   struct ieee80211_sta *sta)
+{
+	struct cl_sta *cl_sta = (struct cl_sta *)sta->drv_priv;
+
+	return cl_sta->wrs_sta.tx_su_params.data_rate;
+}
+
+void cl_ops_sta_statistics(struct ieee80211_hw *hw,
+			   struct ieee80211_vif *vif,
+			   struct ieee80211_sta *sta,
+			   struct station_info *sinfo)
+{
+	struct cl_hw *cl_hw = hw->priv;
+	struct cl_sta *cl_sta = NULL;
+	u64 total_tx_success = 0, total_tx_fail = 0;
+	struct cl_wrs_params *wrs_params = NULL;
+
+	if (!sta)
+		return;
+
+	cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+
+	/*
+	 * Since cl8k implements rate control algorithm (sets IEEE80211_HW_HAS_RATE_CONTROL)
+	 * it is needed to initialize both rx/tx bitrates manually
+	 */
+	cl_wrs_lock_bh(&cl_hw->wrs_db);
+	wrs_params = &cl_sta->wrs_sta.tx_su_params;
+	cl_wrs_fill_sinfo_rates(&sinfo->txrate, wrs_params, cl_sta);
+	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
+	cl_wrs_unlock_bh(&cl_hw->wrs_db);
+
+	/* mac80211 will fill sinfo stats if driver not set sinfo->filled flag */
+	if (!cl_hw->conf->ci_stats_en)
+		return;
+
+	cl_stats_get_tx(cl_hw, cl_sta, &total_tx_success, &total_tx_fail);
+	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS);
+	sinfo->tx_packets = total_tx_success;
+	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED);
+	sinfo->tx_failed = total_tx_fail;
+
+	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS);
+	sinfo->rx_packets = cl_stats_get_rx(cl_hw, cl_sta);
+
+	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES64);
+	sinfo->tx_bytes = cl_sta->tx_bytes;
+	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES64);
+	sinfo->rx_bytes = cl_sta->rx_bytes;
+	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_RETRIES);
+	sinfo->tx_retries = cl_sta->retry_count;
+
+	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG);
+	sinfo->signal_avg = cl_stats_get_rssi(cl_hw, cl_sta);
+}
+
+int cl_ops_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey)
+{
+	struct ieee80211_conf *conf = &hw->conf;
+	struct cl_hw *cl_hw = hw->priv;
+	struct ieee80211_supported_band *sband = hw->wiphy->bands[conf->chandef.chan->band];
+	struct cl_chan_scanner *scanner = cl_hw->scanner;
+	struct cl_channel_stats *scanned_channel = NULL;
+	int chan_num;
+	u8 i;
+
+	if (idx >= sband->n_channels)
+		return -ENOENT;
+
+	survey->channel = &sband->channels[idx];
+	chan_num = ieee80211_frequency_to_channel(sband->channels[idx].center_freq);
+
+	for (i = 0; i < scanner->channels_num; i++) {
+		if (scanner->channels[i].channel == chan_num) {
+			scanned_channel = &scanner->channels[i];
+			break;
+		}
+	}
+
+	if (!scanned_channel) {
+		survey->filled = 0;
+		return 0;
+	}
+
+	survey->filled = SURVEY_INFO_TIME | SURVEY_INFO_TIME_SCAN |
+			 SURVEY_INFO_NOISE_DBM | SURVEY_INFO_TIME_TX |
+			 SURVEY_INFO_TIME_RX | SURVEY_INFO_TIME_BUSY |
+			 SURVEY_INFO_TIME_EXT_BUSY;
+
+	survey->noise = scanned_channel->ch_noise;
+
+	survey->time = scanned_channel->scan_time_ms;
+	survey->time_scan = survey->time;
+
+	survey->time_rx = div64_u64(scanned_channel->util_time_rx, USEC_PER_MSEC);
+	survey->time_tx = div64_u64(scanned_channel->util_time_tx, USEC_PER_MSEC);
+
+	survey->time_busy = div64_u64(scanned_channel->util_time_busy, USEC_PER_MSEC);
+	survey->time_ext_busy = survey->time_busy;
+
+	return 0;
+}
+
+static void cl_scan_completion_cb(struct cl_hw *cl_hw, void *arg)
+{
+	struct cl_chan_scanner *scanner = cl_hw->scanner;
+	struct cfg80211_scan_info info = {
+		.aborted = scanner->scan_aborted,
+	};
+
+	cl_dbg_trace(cl_hw, "Completed scan request, aborted: %u\n", info.aborted);
+
+	cl_scan_channel_switch(cl_hw, scanner->prescan_channel, scanner->prescan_bw, true);
+	ieee80211_scan_completed(cl_hw->hw, &info);
+}
+
+int cl_ops_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		   struct ieee80211_scan_request *hw_req)
+{
+	struct cfg80211_scan_request *req = &hw_req->req;
+	struct cl_hw *cl_hw = hw->priv;
+	struct cl_chan_scanner *scanner = cl_hw->scanner;
+	u8 scan_channels[MAX_CHANNELS] = {0};
+	u8 i;
+	int ret = 0;
+
+	cl_dbg_trace(cl_hw, "Hardware scan request: n_channels:%u, n_ssids:%d\n",
+		     req->n_channels, req->n_ssids);
+
+	if (cl_hw->conf->ce_radio_on && cl_radio_is_off(cl_hw))
+		cl_radio_on(cl_hw);
+
+	ret = mutex_lock_interruptible(&scanner->cl_hw->set_channel_mutex);
+	if (ret != 0)
+		return ret;
+	scanner->prescan_bw = cl_hw->bw;
+	scanner->prescan_channel = cl_hw->channel;
+	mutex_unlock(&scanner->cl_hw->set_channel_mutex);
+
+	if (req->n_ssids > 0) {
+		/*
+		 * This is active scan request. We do not support it yet, so we
+		 * need to force the mac80211 to fallback to the sw_scan.
+		 */
+		cl_dbg_trace(cl_hw, "activating fall-back strategy - sw_scan\n");
+		return 1;
+	}
+
+	if (req->n_channels > ARRAY_SIZE(scan_channels)) {
+		cl_dbg_warn(cl_hw, "invalid number of channels to scan: %u\n",
+			    req->n_channels);
+		return -ERANGE;
+	}
+
+	for (i = 0; i < req->n_channels; ++i) {
+		if (req->channels[i]->band != cl_hw->nl_band) {
+			cl_dbg_warn(cl_hw, "band %u is invalid\n", req->channels[i]->band);
+			return -EINVAL;
+		}
+		scan_channels[i] = ieee80211_frequency_to_channel(req->channels[i]->center_freq);
+	}
+
+	ret = cl_trigger_off_channel_scan(scanner, req->duration, 0,
+					  scan_channels, CHNL_BW_20, req->n_channels,
+					  cl_scan_completion_cb, NULL);
+	return ret;
+}
+
+void cl_ops_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	struct cl_hw *cl_hw = hw->priv;
+	struct cl_chan_scanner *scanner = cl_hw->scanner;
+
+	if (!cl_is_scan_in_progress(scanner))
+		return;
+
+	cl_abort_scan(scanner);
+	wait_event_interruptible_timeout(scanner->wq,
+					 !cl_is_scan_in_progress(scanner),
+					 msecs_to_jiffies(MSEC_PER_SEC));
+}