diff --git a/drivers/net/wireless/celeno/cl8k/ate.c b/drivers/net/wireless/celeno/cl8k/ate.c
new file mode 100644
index 000000000000..95e4e73cd9c0
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/ate.c
@@ -0,0 +1,841 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "ate.h"
+#include "tx/tx_inject.h"
+#include "calib.h"
+#include "rate_ctrl.h"
+#include "fw/msg_tx.h"
+#include "mib.h"
+#include "edca.h"
+#include "reg/reg_mac_hw.h"
+#include "reg/reg_macdsp_api.h"
+#include "reg/reg_riu.h"
+#include "tx/tx_queue.h"
+#include "utils/utils.h"
+#include "band.h"
+#include "fem.h"
+#include "chandef.h"
+#include "mac_addr.h"
+#include "power.h"
+#include "e2p.h"
+
+#define DIFF(_diff, _new, _old, _member)\
+       ((_diff)._member = (_new)._member - (_old)._member)
+
+/* Max freq delta is 100MHz in Q2 */
+#define MAX_FREQ_DELTA (100 << 2)
+
+static void set_fixed_rate(struct cl_hw *cl_hw)
+{
+       struct cl_ate_db *ate_db = &cl_hw->ate_db;
+       union cl_rate_ctrl_info_he rate_ctrl_he = {.word = 0};
+       u8 ltf = 0;
+
+       if (ate_db->mode == WRS_MODE_HE) {
+               rate_ctrl_he.field.spatial_conf = RATE_CNTRL_HE_SPATIAL_CONF_DEF;
+
+               if (ate_db->ltf == LTF_MAX)
+                       ltf = cl_map_gi_to_ltf(WRS_MODE_HE, ate_db->gi);
+               else
+                       ltf = ate_db->ltf;
+       }
+
+       cl_hw->entry_fixed_rate = true;
+
+       cl_rate_ctrl_set_fixed(cl_hw, rate_ctrl_he.word, ate_db->mode, ate_db->mcs,
+                              ate_db->nss, ate_db->bw, ate_db->gi, ltf);
+}
+
+static inline void read_stat(struct cl_hw *cl_hw, struct ate_stats *stats)
+{
+       stats->tx_bw20 = cl_mib_cntr_read(cl_hw, MIB_DOT11_20MHZ_FRAME_TRANSMITTED_COUNT);
+       stats->tx_bw40 = cl_mib_cntr_read(cl_hw, MIB_DOT11_40MHZ_FRAME_TRANSMITTED_COUNT);
+       stats->tx_bw80 = cl_mib_cntr_read(cl_hw, MIB_DOT11_80MHZ_FRAME_TRANSMITTED_COUNT);
+       stats->tx_bw160 = cl_mib_cntr_read(cl_hw, MIB_DOT11_160MHZ_FRAME_TRANSMITTED_COUNT);
+       stats->rx_bw20 = cl_mib_cntr_read(cl_hw, MIB_DOT11_20MHZ_FRAME_RECEIVED_COUNT);
+       stats->rx_bw40 = cl_mib_cntr_read(cl_hw, MIB_DOT11_40MHZ_FRAME_RECEIVED_COUNT);
+       stats->rx_bw80 = cl_mib_cntr_read(cl_hw, MIB_DOT11_80MHZ_FRAME_RECEIVED_COUNT);
+       stats->rx_bw160 = cl_mib_cntr_read(cl_hw, MIB_DOT11_160MHZ_FRAME_RECEIVED_COUNT);
+       stats->fcs_err = cl_mib_cntr_read(cl_hw, MIB_DOT11_FCS_ERROR_COUNT);
+       stats->phy_err = cl_mib_cntr_read(cl_hw, MIB_DOT11_RX_PHY_ERROR_COUNT);
+       stats->delimiter_err = cl_mib_cntr_read(cl_hw, MIB_DOT11_AMPDU_DELIMITER_CRC_ERROR_COUNT);
+}
+
+static bool is_valid_rate_he(struct cl_hw *cl_hw, u8 bw, u8 nss, u8 mcs, u8 gi)
+{
+       u8 ltf = cl_hw->ate_db.ltf;
+
+       /* BW */
+       if (!cl_hw->conf->ce_txldpc_en) {
+               if (bw > CHNL_BW_20) {
+                       u8 bw_mhz = BW_TO_MHZ(bw);
+
+                       cl_dbg_err(cl_hw, "Invalid bw [%u] - must be 20 when tx ldpc disabled\n",
+                                  bw_mhz);
+                       return false;
+               }
+       }
+
+       /* NSS */
+       if (nss >= cl_hw->conf->ce_tx_nss) {
+               cl_dbg_err(cl_hw, "Invalid nss [%u] - must be < %u\n",
+                          nss, cl_hw->conf->ce_tx_nss);
+               return false;
+       }
+
+       /* MCS */
+       if (cl_hw->conf->ce_txldpc_en) {
+               if (mcs >= WRS_MCS_MAX_HE) {
+                       cl_dbg_err(cl_hw, "Invalid mcs [%u] - must be 0 - 11\n", mcs);
+                       return false;
+               }
+       } else {
+               if (mcs >= WRS_MCS_10) {
+                       cl_dbg_err(cl_hw, "Invalid mcs [%u] - must be 0-9 when tx ldpc disabled\n",
+                                  mcs);
+                       return false;
+               }
+       }
+
+       /* GI */
+       if (gi >= WRS_GI_MAX_HE) {
+               cl_dbg_err(cl_hw, "Invalid gi [%u] - must be 0(0.8u)/1(1.6u)/2(3.2u)\n", gi);
+               return false;
+       }
+
+       /* LTF */
+       if (ltf > LTF_MAX) {
+               cl_dbg_err(cl_hw, "Invalid ltf [%u] - must be 0(X1)/1(X2)/2(X4)\n", ltf);
+               return -EINVAL;
+       } else if (ltf < LTF_MAX) {
+               /*
+                * Supported GI/LTF combinations:
+                * GI = 3.2: LTF_X4
+                * GI = 1.6: LTF_X2
+                * GI = 0.8: LTF_X1, LTF_X2, LTF_X4
+                */
+               if (gi == WRS_GI_LONG) {
+                       if (ltf != LTF_X4) {
+                               cl_dbg_err(cl_hw, "ltf must be 2 (=X4) for gi=0\n");
+                               return false;
+                       }
+               } else if (gi == WRS_GI_SHORT) {
+                       if (ltf != LTF_X2) {
+                               cl_dbg_err(cl_hw, "ltf must be 1 (=X2) for gi=1\n");
+                               return false;
+                       }
+               }
+       }
+
+       return true;
+}
+
+static bool is_valid_rate_vht(struct cl_hw *cl_hw, u8 bw, u8 nss, u8 mcs, u8 gi)
+{
+       /* BW */
+       if (bw == CHNL_BW_160 && nss >= WRS_SS_3) {
+               cl_dbg_err(cl_hw, "bw 160 is invalid in 3/4 nss\n");
+               return false;
+       }
+
+       /* NSS */
+       if (nss >= cl_hw->conf->ce_tx_nss) {
+               cl_dbg_err(cl_hw, "Invalid nss [%u] - must be < %u\n",
+                          nss, cl_hw->conf->ce_tx_nss);
+               return false;
+       }
+
+       /* MCS */
+       if (mcs >= WRS_MCS_MAX_VHT) {
+               cl_dbg_err(cl_hw, "Invalid mcs [%u] - must be 0-9\n", mcs);
+               return false;
+       }
+
+       /* GI */
+       if (gi >= WRS_GI_MAX_VHT) {
+               cl_dbg_err(cl_hw, "Invalid gi [%u] - must be 0(0.8u)/1(0.4u)\n", gi);
+               return false;
+       }
+
+       /* Make sure it is not an invalid VHT rate */
+       if (bw == CHNL_BW_20 && mcs == WRS_MCS_9)
+               if (nss == WRS_SS_1 || nss == WRS_SS_2 || nss == WRS_SS_4) {
+                       cl_dbg_err(cl_hw, "nss 1/2/4 are invalid in bw 20, mcs 9\n");
+                       return false;
+               }
+
+       if (bw == CHNL_BW_80 && mcs == WRS_MCS_6 && nss == WRS_SS_3) {
+               cl_dbg_err(cl_hw, "bw 80, mcs 6, nss 3 is invalid\n");
+               return false;
+       }
+
+       return true;
+}
+
+static bool is_valid_rate_ht(struct cl_hw *cl_hw, u8 bw, u8 nss, u8 mcs, u8 gi)
+{
+       /* BW */
+       if (bw > CHNL_BW_40) {
+               u8 bw_mhz = BW_TO_MHZ(bw);
+
+               cl_dbg_err(cl_hw, "Invalid bw [%u] - must be 20/40\n", bw_mhz);
+               return false;
+       }
+
+       /* NSS */
+       if (nss >= cl_hw->conf->ce_tx_nss) {
+               cl_dbg_err(cl_hw, "Invalid nss [%u] - must be < %u\n",
+                          nss, cl_hw->conf->ce_tx_nss);
+               return false;
+       }
+
+       /* MCS */
+       if (mcs >= WRS_MCS_MAX_HT) {
+               cl_dbg_err(cl_hw, "Invalid mcs [%u] - must be 0 - 7\n", mcs);
+               return false;
+       }
+
+       /* GI */
+       if (gi >= WRS_GI_MAX_HT) {
+               cl_dbg_err(cl_hw, "Invalid gi [%u] - must be 0(0.8u)/1(0.4u)\n", gi);
+               return false;
+       }
+
+       return true;
+}
+
+static bool is_valid_rate_ofdm(struct cl_hw *cl_hw, u8 bw, u8 nss, u8 mcs, u8 gi)
+{
+       /*
+        * BW
+        * There is no need to check if bw is valid.
+        * It was already done in is_valid_bw_mhz().
+        * For ofdm we allow bw to be > 20, for FORMAT_NON_HT_DUP.
+        */
+
+       /* NSS */
+       if (nss != 0) {
+               cl_dbg_err(cl_hw, "Invalid  nss [%u] - must be 0\n", nss);
+               return false;
+       }
+
+       /* MCS */
+       if (mcs >= WRS_MCS_MAX_OFDM) {
+               cl_dbg_err(cl_hw, "Invalid  mcs [%u] - must be 0 - 7\n", mcs);
+               return false;
+       }
+
+       /* GI */
+       if (gi != 0) {
+               cl_dbg_err(cl_hw, "Invalid  gi [%u] - nust be 0\n", gi);
+               return false;
+       }
+
+       return true;
+}
+
+static bool is_valid_rate_cck(struct cl_hw *cl_hw, u8 bw, u8 nss, u8 mcs, u8 gi)
+{
+       /* BW */
+       if (bw > CHNL_BW_20) {
+               u8 bw_mhz = BW_TO_MHZ(bw);
+
+               cl_dbg_err(cl_hw, "Invalid bw [%u] - must be 20\n", bw_mhz);
+               return false;
+       }
+
+       /* NSS */
+       if (nss != 0) {
+               cl_dbg_err(cl_hw, "Invalid nss [%u] - must be 0\n", nss);
+               return false;
+       }
+
+       /* MCS */
+       if (mcs >= WRS_MCS_MAX_CCK) {
+               cl_dbg_err(cl_hw, "Invalid mcs [%u] - must be 0 - 3\n", mcs);
+               return false;
+       }
+
+       /* GI */
+       if (gi != 0) {
+               cl_dbg_err(cl_hw, "Invalid gi [%u] - nust be 0\n", gi);
+               return false;
+       }
+
+       return true;
+}
+
+static bool is_valid_rate(struct cl_hw *cl_hw)
+{
+       u8 mode = cl_hw->ate_db.mode;
+       u8 bw = cl_hw->ate_db.bw;
+       u8 nss = cl_hw->ate_db.nss;
+       u8 mcs = cl_hw->ate_db.mcs;
+       u8 gi = cl_hw->ate_db.gi;
+
+       switch (mode) {
+       case WRS_MODE_HE:
+               return is_valid_rate_he(cl_hw, bw, nss, mcs, gi);
+       case WRS_MODE_VHT:
+               return is_valid_rate_vht(cl_hw, bw, nss, mcs, gi);
+       case WRS_MODE_HT:
+               return is_valid_rate_ht(cl_hw, bw, nss, mcs, gi);
+       case WRS_MODE_OFDM:
+               return is_valid_rate_ofdm(cl_hw, bw, nss, mcs, gi);
+       case WRS_MODE_CCK:
+               return is_valid_rate_cck(cl_hw, bw, nss, mcs, gi);
+       default:
+               cl_dbg_err(cl_hw,
+                          "Invalid mode [%u] - must be: 0(cck)/1(ofdm)/2(ht)/3(vht)/4(he)\n",
+                          mode);
+               break;
+       }
+
+       return false;
+}
+
+static bool is_valid_bw(struct cl_hw *cl_hw)
+{
+       if (cl_hw->bw < cl_hw->ate_db.bw) {
+               cl_dbg_err(cl_hw, "TX bw [%u] can't be greater than channel bw [%u]\n",
+                          BW_TO_MHZ(cl_hw->ate_db.bw), BW_TO_MHZ(cl_hw->bw));
+               return false;
+       }
+
+       return true;
+}
+
+static bool is_valid_bw_mhz(struct cl_hw *cl_hw, u8 bw_mhz)
+{
+       if (BAND_IS_5G_6G(cl_hw)) {
+               if (bw_mhz != BW_TO_MHZ(CHNL_BW_20) &&
+                   bw_mhz != BW_TO_MHZ(CHNL_BW_40) &&
+                   bw_mhz != BW_TO_MHZ(CHNL_BW_80) &&
+                   bw_mhz != BW_TO_MHZ(CHNL_BW_160)) {
+                       cl_dbg_err(cl_hw,
+                                  "Invalid bw [%u] - must be 20/40/80/160\n", bw_mhz);
+                       return false;
+               }
+       } else {
+               if (bw_mhz != BW_TO_MHZ(CHNL_BW_20) &&
+                   bw_mhz != BW_TO_MHZ(CHNL_BW_40)) {
+                       cl_dbg_err(cl_hw, "Invalid bw [%u] - must be 20/40\n", bw_mhz);
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+int cl_ate_reset(struct wiphy *wiphy, struct wireless_dev *wdev,
+                const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       if (cl_tx_inject_is_running(cl_hw)) {
+               tasklet_kill(&cl_hw->tx_inject.tasklet);
+               cl_ate_stop(wiphy, NULL, NULL, 0);
+       }
+
+       /* Reset rate parameters */
+       cl_hw->ate_db.mode = 0;
+       cl_hw->ate_db.bw = 0;
+       cl_hw->ate_db.nss = 0;
+       cl_hw->ate_db.mcs = 0;
+       cl_hw->ate_db.gi = 0;
+       cl_hw->ate_db.ltf = LTF_MAX;
+
+       cl_hw->entry_fixed_rate = false;
+
+       /* Reset TX power */
+       cl_hw->ate_db.tx_power = S8_MAX;
+       memset(cl_hw->ate_db.tx_power_offset, S8_MAX, MAX_ANTENNAS);
+
+       cl_tx_inject_reset(cl_hw);
+
+       /* Go to ACTIVE state */
+       if (cl_hw->chip->conf->ce_production_mode)
+               cl_msg_tx_set_idle(cl_hw, MAC_ACTIVE);
+
+       if (cl_hw->ate_db.ant_mask) {
+               u8 default_ant_mask = ANT_MASK(cl_hw->num_antennas);
+
+               cl_msg_tx_set_ant_bitmap(cl_hw, default_ant_mask);
+               cl_hw->ate_db.ant_mask = 0;
+       }
+
+       cl_hw->ate_db.active = true;
+
+       /*
+        * Rearm last_tbtt_irq so that error message will
+        * not be printed in cl_irq_status_tbtt()
+        */
+       cl_hw->last_tbtt_irq = jiffies;
+
+       cl_dbg_trace(cl_hw, "\n");
+
+       return 0;
+}
+
+int cl_ate_mode(struct wiphy *wiphy, struct wireless_dev *wdev,
+               const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       cl_hw->ate_db.mode = *(u8 *)data;
+
+       cl_dbg_trace(cl_hw, "mode = %u\n", cl_hw->ate_db.mode);
+
+       return 0;
+}
+
+int cl_ate_bw(struct wiphy *wiphy, struct wireless_dev *wdev,
+             const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       u8 bw_mhz = *(u8 *)data;
+
+       if (!is_valid_bw_mhz(cl_hw, bw_mhz))
+               return -EINVAL;
+
+       cl_hw->ate_db.bw = MHZ_TO_BW(bw_mhz);
+
+       cl_dbg_trace(cl_hw, "bw = %u\n", bw_mhz);
+
+       return 0;
+}
+
+int cl_ate_mcs(struct wiphy *wiphy, struct wireless_dev *wdev,
+              const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       cl_hw->ate_db.mcs = *(u8 *)data;
+
+       cl_dbg_trace(cl_hw, "mcs = %u\n", cl_hw->ate_db.mcs);
+
+       return 0;
+}
+
+int cl_ate_nss(struct wiphy *wiphy, struct wireless_dev *wdev,
+              const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       cl_hw->ate_db.nss = *(u8 *)data;
+
+       cl_dbg_trace(cl_hw, "nss = %u\n", cl_hw->ate_db.nss);
+
+       return 0;
+}
+
+int cl_ate_gi(struct wiphy *wiphy, struct wireless_dev *wdev,
+             const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       cl_hw->ate_db.gi = *(u8 *)data;
+       cl_dbg_trace(cl_hw, "gi = %u\n", cl_hw->ate_db.gi);
+
+       return 0;
+}
+
+int cl_ate_ltf(struct wiphy *wiphy, struct wireless_dev *wdev,
+              const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       cl_hw->ate_db.ltf = *(u8 *)data;
+
+       cl_dbg_trace(cl_hw, "ltf = %u\n", cl_hw->ate_db.ltf);
+
+       return 0;
+}
+
+int cl_ate_ldpc(struct wiphy *wiphy, struct wireless_dev *wdev,
+               const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       cl_hw->conf->ce_txldpc_en = (bool)(*(u8 *)data);
+
+       cl_dbg_trace(cl_hw, "ldpc = %u\n", cl_hw->conf->ce_txldpc_en);
+
+       return 0;
+}
+
+int cl_ate_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
+                  const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       u32 channel = ((u32 *)data)[0];
+       u32 bw_mhz = ((u32 *)data)[1];
+       u32 bw = 0;
+       u32 primary = 0;
+       u32 center = 0;
+       enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20;
+
+       if (!is_valid_bw_mhz(cl_hw, bw_mhz))
+               return -EINVAL;
+
+       if (cl_band_is_6g(cl_hw) && channel == 2 &&
+           bw_mhz != BW_TO_MHZ(CHNL_BW_20)) {
+               cl_dbg_err(cl_hw, "Only 20Mhz is allowed for channel 2\n");
+               return -EINVAL;
+       }
+
+       bw = MHZ_TO_BW(bw_mhz);
+
+       if (cl_chandef_calc(cl_hw, channel, bw, &width, &primary, &center)) {
+               cl_dbg_err(cl_hw, "cl_chandef_calc failed\n");
+               return -EINVAL;
+       }
+
+       if (cl_hw->set_calib) {
+               cl_hw->set_calib = false;
+               cl_calib_power_read(cl_hw);
+       }
+
+       cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center);
+
+       return 0;
+}
+
+int cl_ate_ant(struct wiphy *wiphy, struct wireless_dev *wdev,
+              const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       u8 ant = *(u8 *)data;
+       u8 mask;
+
+       if (ant >= MAX_ANTENNAS) {
+               cl_dbg_err(cl_hw, "Invalid antenna value [%u]", ant);
+               return -EINVAL;
+       }
+
+       mask = (1 << ant);
+
+       if (mask != cl_hw->ate_db.ant_mask) {
+               cl_hw->ate_db.ant_mask = mask;
+               cl_msg_tx_set_ant_bitmap(cl_hw, mask);
+       }
+
+       cl_dbg_trace(cl_hw, "ant = %u, mask = 0x%x\n", ant, mask);
+
+       return 0;
+}
+
+#define FULL_ANT_MASK ((1 << MAX_ANTENNAS) - 1)
+
+int cl_ate_multi_ant(struct wiphy *wiphy, struct wireless_dev *wdev,
+                    const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       u8 mask = *(u8 *)data;
+
+       if (mask == 0 || mask > FULL_ANT_MASK) {
+               cl_dbg_err(cl_hw, "Invalid antenna bitmap [0x%x]", mask);
+               return -EINVAL;
+       }
+
+       if (mask != cl_hw->ate_db.ant_mask) {
+               cl_hw->ate_db.ant_mask = mask;
+               cl_msg_tx_set_ant_bitmap(cl_hw, mask);
+       }
+
+       cl_dbg_trace(cl_hw, "mask = 0x%x\n", mask);
+
+       return 0;
+}
+
+int cl_ate_packet_len(struct wiphy *wiphy, struct wireless_dev *wdev,
+                     const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       u32 packet_len = *(u32 *)data;
+
+       cl_dbg_trace(cl_hw, "packet_len = %u\n", packet_len);
+
+       return cl_tx_inject_set_length(cl_hw, packet_len);
+}
+
+int cl_ate_vector(struct wiphy *wiphy, struct wireless_dev *wdev,
+                 const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       u32 size = data_len / sizeof(u32);
+       int ret = 0;
+
+       cl_dbg_trace(cl_hw, "\n");
+
+       ret = cl_calib_pivot_channels_set(cl_hw, data, size);
+
+       /* Write EEPROM version when starting calibration process */
+       if (!ret)
+               return cl_e2p_write_version(cl_hw->chip);
+
+       return ret;
+}
+
+int cl_ate_vector_reset(struct wiphy *wiphy, struct wireless_dev *wdev,
+                       const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       cl_dbg_trace(cl_hw, "\n");
+
+       return cl_calib_pivot_channels_reset(cl_hw);
+}
+
+#define FREQ_OFST_MAX  959
+
+int cl_ate_freq_offset(struct wiphy *wiphy, struct wireless_dev *wdev,
+                      const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       u16 freq_offset = *(u16 *)data;
+
+       if (freq_offset > FREQ_OFST_MAX) {
+               cl_dbg_err(cl_hw, "Invalid freq offset 0x%04x\n", freq_offset);
+               return -1;
+       }
+
+       cl_dbg_trace(cl_hw, "Freq offset 0x%04x\n", freq_offset);
+
+       return cl_msg_tx_set_freq_offset(cl_hw, freq_offset);
+}
+
+int cl_ate_stat(struct wiphy *wiphy, struct wireless_dev *wdev,
+               const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       struct ate_stats new_stats;
+       struct ate_stats ret_stats;
+
+       read_stat(cl_hw, &new_stats);
+
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, tx_bw20);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, tx_bw40);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, tx_bw80);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, tx_bw160);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, rx_bw20);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, rx_bw40);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, rx_bw80);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, rx_bw160);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, fcs_err);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, phy_err);
+       DIFF(ret_stats, new_stats, cl_hw->ate_db.stats, delimiter_err);
+
+       /* Present rx seccess of the defined bw */
+       switch (cl_hw->ate_db.bw) {
+       case CHNL_BW_20:
+               ret_stats.rx_success = ret_stats.rx_bw20;
+               break;
+       case CHNL_BW_40:
+               ret_stats.rx_success = ret_stats.rx_bw40;
+               break;
+       case CHNL_BW_80:
+               ret_stats.rx_success = ret_stats.rx_bw80;
+               break;
+       case CHNL_BW_160:
+               ret_stats.rx_success = ret_stats.rx_bw160;
+               break;
+       default:
+               /* Should not get here */
+               return -EINVAL;
+       }
+
+       /* Read rssi */
+       macdsp_api_inbdpow_20_unpack(cl_hw, &ret_stats.rssi3, &ret_stats.rssi2,
+                                    &ret_stats.rssi1, &ret_stats.rssi0);
+       ret_stats.rssi4 = S8_MIN;
+       ret_stats.rssi5 = S8_MIN;
+
+       cl_dbg_trace(cl_hw, "\n");
+
+       return cl_vendor_reply(cl_hw, &ret_stats, sizeof(struct ate_stats));
+}
+
+int cl_ate_stat_reset(struct wiphy *wiphy, struct wireless_dev *wdev,
+                     const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       read_stat(cl_hw, &cl_hw->ate_db.stats);
+
+       cl_dbg_trace(cl_hw, "\n");
+
+       return 0;
+}
+
+int cl_ate_power(struct wiphy *wiphy, struct wireless_dev *wdev,
+                const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       s8 tx_power = *(s8 *)data;
+       s8 tx_power_q1 = 0;
+
+       if (tx_power < POWER_MIN_DB || tx_power > POWER_MAX_DB) {
+               cl_dbg_err(cl_hw, "Invalid power (%d). Must be between %d and %d\n",
+                          tx_power, POWER_MIN_DB, POWER_MAX_DB);
+               return 0;
+       }
+
+       cl_hw->ate_db.tx_power = tx_power;
+       tx_power_q1 = tx_power << 1;
+
+       cl_dbg_trace(cl_hw, "ate_power = %u\n", tx_power);
+
+       memset(&cl_hw->phy_data_info.data->pwr_tables,
+              tx_power_q1, sizeof(struct cl_pwr_tables));
+
+       cl_msg_tx_refresh_power(cl_hw);
+
+       return 0;
+}
+
+int cl_ate_power_offset(struct wiphy *wiphy, struct wireless_dev *wdev,
+                       const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       s8 *pwr_offset = cl_hw->ate_db.tx_power_offset;
+       int i;
+
+       for (i = 0; i < MAX_ANTENNAS; i++) {
+               pwr_offset[i] = ((s8 *)data)[i];
+
+               if (pwr_offset[i] < POWER_OFFSET_MIN_Q2 ||
+                   pwr_offset[i] > POWER_OFFSET_MAX_Q2) {
+                       cl_dbg_err(cl_hw, "Invalid power offset (%d). Valid range (%d - %d)\n",
+                                  pwr_offset[i], POWER_OFFSET_MIN_Q2, POWER_OFFSET_MAX_Q2);
+                       memset(pwr_offset, S8_MAX, MAX_ANTENNAS);
+                       return -1;
+               }
+       }
+
+       cl_dbg_trace(cl_hw, "power_offset = %d,%d,%d,%d,%d,%d\n",
+                    pwr_offset[0], pwr_offset[1], pwr_offset[2],
+                    pwr_offset[3], pwr_offset[4], pwr_offset[5]);
+
+       return cl_msg_tx_set_ant_pwr_offset(cl_hw, pwr_offset);
+}
+
+int cl_ate_tx_start(struct wiphy *wiphy, struct wireless_dev *wdev,
+                   const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       u32 tx_cnt = *(u32 *)data;
+
+       if (!cl_hw->ate_db.active) {
+               cl_dbg_err(cl_hw, "Must call 'ATE reset' first.\n");
+               return -EPERM;
+       }
+
+       if (tx_cnt == 0) {
+               cl_tx_inject_stop_traffic(cl_hw);
+               return 0;
+       }
+
+       if (cl_tx_inject_is_running(cl_hw)) {
+               cl_dbg_err(cl_hw, "TX already running.\n");
+               return -EPERM;
+       }
+
+       if (!is_valid_rate(cl_hw) || !is_valid_bw(cl_hw))
+               return -EPERM;
+
+       set_fixed_rate(cl_hw);
+       cl_tx_inject_start(cl_hw, tx_cnt);
+
+       cl_dbg_trace(cl_hw, "\n");
+
+       return 0;
+}
+
+int cl_ate_tx_continuous(struct wiphy *wiphy, struct wireless_dev *wdev,
+                        const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       if (!cl_hw->ate_db.active) {
+               cl_dbg_err(cl_hw, "Must call 'ATE reset' first.\n");
+               return -EPERM;
+       }
+
+       if (cl_tx_inject_is_running(cl_hw)) {
+               cl_dbg_err(cl_hw, "TX already running.\n");
+               return -EPERM;
+       }
+
+       if (!is_valid_rate(cl_hw) || !is_valid_bw(cl_hw))
+               return -EPERM;
+
+       set_fixed_rate(cl_hw);
+       cl_tx_inject_start_continuous(cl_hw);
+
+       cl_dbg_trace(cl_hw, "\n");
+
+       return 0;
+}
+
+int cl_ate_stop(struct wiphy *wiphy, struct wireless_dev *wdev,
+               const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       cl_tx_inject_stop(cl_hw);
+
+       /* Go back to IDLE state */
+       if (cl_hw->chip->conf->ce_production_mode)
+               cl_msg_tx_set_idle(cl_hw, MAC_IDLE_SYNC);
+
+       cl_hw->ate_db.active = false;
+
+       cl_dbg_trace(cl_hw, "\n");
+
+       return 0;
+}
+
+int cl_ate_help(struct wiphy *wiphy, struct wireless_dev *wdev,
+               const void *data, int data_len)
+{
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       char *ret_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       int err = 0;
+
+       if (!ret_buf)
+               return -ENOMEM;
+
+       snprintf(ret_buf, PAGE_SIZE,
+                "usage:\n"
+                "reset - Reset ATE configuration\n"
+                "mode <0=CCK,1=OFDM,2=HT,3=VHT,4=HE> - Set mode\n"
+                "bw <20/40/80/160> - Set TX bandwidth parameter\n"
+                "mcs <CCK=0-3, OFDM/HT=0-7, VHT=0-9, HE=0-11> - set mcs parameter\n"
+                "nss <0-3> - set nss parameter\n"
+                "gi <CCK/OFDM=0, HT/VHT=0-1, HE=0-2> - set gi\n"
+                "ltf <HE-LTF: 0=LTF_X1,1=LTF_X2,2=LTF_X4> - set ltf\n"
+                "ldpc <0=Disable, 1=Enable> - set ldpc parameter\n"
+                "channel <ch number> <ch bw [20/40/80/160]> <Frequency delta"
+                " from center Frequency (optional)> - change channel\n"
+                "ant <Antenna index 0-5> - Enable single antenna\n"
+                "multi_ant <Ant bitmap> - Enable multiple antennas\n"
+                "packet_len <packet length (16-4096)> - Set length of packets to inject\n"
+                "vector <Channel vector separated by space> - Set"
+                " vector of channels to calibrate\n"
+                "freq_offset <0-959> - Set frequency offset\n"
+                "stat <reset (optional)> - Display/Reset statistics\n"
+                "power <-10dB - 30dB> - Set tx power\n"
+                "power_offset <offset_ant1 ... offset_ant6> - Power"
+                " offset per anthenna [range +/-64][units=0.25dB]\n"
+                "tx_start <Num of packets> - Start TX packets\n"
+                "tx_continuous - Start transmitting infinite packets\n"
+                "stop - Stop transmission\n");
+
+       err = cl_vendor_reply(cl_hw, ret_buf, strlen(ret_buf));
+       kfree(ret_buf);
+
+       return err;
+}
+
