new file mode 100644
@@ -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, ¢er)) {
+ 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;
+}
+