diff mbox series

[RFC,v1,184/256] cl8k: add stats.c

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

Commit Message

Viktor Barna June 17, 2021, 4:01 p.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/stats.c | 1402 ++++++++++++++++++++++
 1 file changed, 1402 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/stats.c

--
2.30.0
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/stats.c b/drivers/net/wireless/celeno/cl8k/stats.c
new file mode 100644
index 000000000000..f340f60ebab9
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/stats.c
@@ -0,0 +1,1402 @@ 
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include <linux/string.h>
+#include "stats.h"
+#include "rx/rx.h"
+#include "reg/reg_access.h"
+#include "sta.h"
+#include "band.h"
+#include "mib.h"
+#include "rate_ctrl.h"
+#include "vif.h"
+#include "data_rates.h"
+
+typedef void (*stats_callback)(struct cl_hw *, struct cl_sta *);
+
+static const char *bw_str[CHNL_BW_MAX_OFDMA] = {
+       [CHNL_BW_2_5] = "2.5",
+       [CHNL_BW_5] = "5",
+       [CHNL_BW_10] = "10",
+       [CHNL_BW_20] = "20",
+       [CHNL_BW_40] = "40",
+       [CHNL_BW_80] = "80",
+       [CHNL_BW_160] = "160"
+};
+
+static const char *gi_he_str[WRS_GI_MAX_HE] = {
+       [WRS_GI_LONG] = "3.2",
+       [WRS_GI_SHORT] = "1.6",
+       [WRS_GI_VSHORT] = "0.8"
+};
+
+static const char *gi_ht_vht_str[WRS_GI_MAX_HT] = {
+       [WRS_GI_LONG] = "0.8",
+       [WRS_GI_SHORT] = "0.4",
+};
+
+static void cl_stats_sta_reset(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+       struct cl_ps_stats *ps = &cl_sta->stats->ps;
+       bool is_ps = ps->is_ps;
+       unsigned long timestamp_sleep = ps->timestamp_sleep;
+
+       memset(cl_sta->stats, 0, sizeof(struct cl_stats));
+
+       /* Restore value of power-save state and timestamp */
+       ps->is_ps = is_ps;
+       ps->timestamp_sleep = timestamp_sleep;
+}
+
+static void cl_stats_print_per(struct cl_hw *cl_hw, u32 delay)
+{
+       u32 system_per = 0, air_per = 0, ampdu_all,
+               old_phy_error, old_fcs_error, old_overflow, old_complete,
+               old_ampdu_failed, old_real_fcs, old_ampdu_success,
+               new_phy_error, new_fcs_error, new_overflow, new_complete,
+               new_ampdu_failed, new_real_fcs, new_ampdu_success;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int len = 0;
+
+       old_phy_error = cl_mib_cntr_read(cl_hw, MIB_DOT11_RX_PHY_ERROR_COUNT);
+       old_fcs_error = cl_mib_cntr_read(cl_hw, MIB_DOT11_FCS_ERROR_COUNT);
+       old_overflow = cl_mib_cntr_read(cl_hw, MIB_DOT11_RX_FIFO_OVERFLOW_COUNT);
+       old_complete = cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT0) +
+                      cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT1) +
+                      cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT2) +
+                      cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT3) +
+                      cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT4) +
+                      cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT5) +
+                      cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT6) +
+                      cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT7);
+       old_ampdu_failed = cl_mib_cntr_read(cl_hw, MIB_AMPDU_INCORRECT_RCVED_COUNT);
+       old_ampdu_success = cl_mib_cntr_read(cl_hw, MIB_RW_U_AMPDU_RECEIVED_COUNT);
+       old_real_fcs = old_fcs_error - old_overflow;
+
+       mdelay(delay);
+
+       new_phy_error = cl_mib_cntr_read(cl_hw, MIB_DOT11_RX_PHY_ERROR_COUNT);
+       new_fcs_error = cl_mib_cntr_read(cl_hw, MIB_DOT11_FCS_ERROR_COUNT);
+       new_overflow = cl_mib_cntr_read(cl_hw, MIB_DOT11_RX_FIFO_OVERFLOW_COUNT);
+       new_complete = cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT0) +
+               cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT1) +
+               cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT2) +
+               cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT3) +
+               cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT4) +
+               cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT5) +
+               cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT6) +
+               cl_mib_cntr_read(cl_hw, MIB_RW_QOS_U_RECEIVED_MPDU_COUNT7);
+       new_ampdu_failed = cl_mib_cntr_read(cl_hw, MIB_AMPDU_INCORRECT_RCVED_COUNT);
+       new_ampdu_success = cl_mib_cntr_read(cl_hw, MIB_RW_U_AMPDU_RECEIVED_COUNT);
+       new_real_fcs = new_fcs_error - new_overflow;
+
+       /* Overflow handling */
+       if (old_complete > new_complete ||
+           old_overflow > new_overflow ||
+           old_fcs_error > new_fcs_error ||
+           old_phy_error > new_phy_error ||
+           old_ampdu_failed > new_ampdu_failed)
+               return;
+
+       ampdu_all = (new_ampdu_failed - old_ampdu_failed) +
+               (new_ampdu_success - old_ampdu_success);
+
+       if (new_complete - old_complete) {
+               system_per = ((new_overflow - old_overflow) * 100 /
+                             (new_complete - old_complete));
+       } else {
+               cl_snprintf(&buf, &len, &buf_size, "No successfully received packets\n");
+               goto out;
+       }
+
+       if ((new_fcs_error + new_phy_error + new_complete) -
+           (old_fcs_error + old_phy_error + old_complete))
+               air_per = (new_real_fcs - old_real_fcs) * 100 /
+               ((new_real_fcs + new_complete) - (old_real_fcs + old_complete));
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "Air PER = [%u%%]\n"
+                   "System PER = [%u%%]\n"
+                   "Successfully received packets = [%u]\n"
+                   "Number of phy errors received: [%u]\n"
+                   "Aggregation failure ratio: [%u/%u]\n",
+                   air_per, system_per, new_complete - old_complete,
+                   new_phy_error - old_phy_error,
+                   new_ampdu_failed - old_ampdu_failed, ampdu_all);
+
+out:
+       cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+}
+
+static void cl_stats_print_rssi(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+       struct cl_stats *stats = cl_sta->stats;
+       u32 i = 0, j = 0;
+       u64 avg_rssi[MAX_ANTENNAS] = {0};
+       u64 sum_rssi[MAX_ANTENNAS] = {0};
+       u64 total_rssi = 0;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int len = 0;
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "STA #%u, MAC %pM\n", cl_sta->sta_idx, cl_sta->addr);
+
+       cl_snprintf(&buf, &len, &buf_size, "|----");
+       for (j = 0; j < cl_hw->num_antennas; j++)
+               cl_snprintf(&buf, &len, &buf_size, "-----------");
+       cl_snprintf(&buf, &len, &buf_size, "|\n");
+
+       cl_snprintf(&buf, &len, &buf_size, "|RSSI");
+       for (j = 0; j < cl_hw->num_antennas; j++)
+               cl_snprintf(&buf, &len, &buf_size, "|   Ant%u   ", j + 1);
+       cl_snprintf(&buf, &len, &buf_size, "|\n");
+
+       cl_snprintf(&buf, &len, &buf_size, "|----");
+       for (j = 0; j < cl_hw->num_antennas; j++)
+               cl_snprintf(&buf, &len, &buf_size, "+----------");
+       cl_snprintf(&buf, &len, &buf_size, "|\n");
+
+       for (i = 0; i < RSSI_ARR_SIZE; i++) {
+               total_rssi = 0;
+
+               for (j = 0; j < cl_hw->num_antennas; j++) {
+                       total_rssi += stats->rssi[i][j];
+                       sum_rssi[j] += stats->rssi[i][j];
+                       avg_rssi[j] += (i * stats->rssi[i][j]);
+               }
+
+               /* Does not print rssi entries with 0 packets */
+               if (total_rssi == 0)
+                       continue;
+
+               cl_snprintf(&buf, &len, &buf_size, "|%3d ", -i);
+               for (j = 0; j < cl_hw->num_antennas; j++)
+                       cl_snprintf(&buf, &len, &buf_size,
+                                   "|%10u", stats->rssi[i][j]);
+               cl_snprintf(&buf, &len, &buf_size, "|\n");
+       }
+
+       total_rssi = 0;
+
+       for (j = 0; j < cl_hw->num_antennas; j++) {
+               if (sum_rssi[j] == 0)
+                       goto out;
+
+               avg_rssi[j] = div64_u64(avg_rssi[j], sum_rssi[j]);
+       }
+
+       cl_snprintf(&buf, &len, &buf_size, "|----");
+       for (j = 0; j < cl_hw->num_antennas; j++)
+               cl_snprintf(&buf, &len, &buf_size, "+----------");
+       cl_snprintf(&buf, &len, &buf_size, "|\n");
+
+       cl_snprintf(&buf, &len, &buf_size, "|AVG ");
+       for (j = 0; j < cl_hw->num_antennas; j++)
+               cl_snprintf(&buf, &len, &buf_size, "|%10lld", -avg_rssi[j]);
+       cl_snprintf(&buf, &len, &buf_size, "|\n");
+
+       cl_snprintf(&buf, &len, &buf_size, "|----");
+       for (j = 0; j < cl_hw->num_antennas; j++)
+               cl_snprintf(&buf, &len, &buf_size, "-----------");
+       cl_snprintf(&buf, &len, &buf_size, "|\n");
+
+out:
+       cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+}
+
+static void cl_stats_print_fec_coding(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+       struct cl_stats *stats = cl_sta->stats;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int len = 0;
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "\nSTA #%u, MAC %pM\n", cl_sta->sta_idx, cl_sta->addr);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "BCC  = %u\n", stats->fec_coding[CL_FEC_CODING_BCC]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "LDPC = %u\n", stats->fec_coding[CL_FEC_CODING_LDPC]);
+
+       cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+}
+
+static void cl_stats_print_power_save(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+       struct cl_ps_stats *ps_stats = &cl_sta->stats->ps;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int len = 0;
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "STA #%u, MAC %pM\n", cl_sta->sta_idx, cl_sta->addr);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "Current state = %s\n", ps_stats->is_ps ? "SLEEP" : "AWAKE");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "|-------------------------|\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| Period     | Counter    |\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "|-------------------------|\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| <= 50ms    | %10u |\n", ps_stats->period[PS_PERIOD_50MS]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| <= 100ms   | %10u |\n", ps_stats->period[PS_PERIOD_100MS]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| <= 250ms   | %10u |\n", ps_stats->period[PS_PERIOD_250MS]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| <= 500ms   | %10u |\n", ps_stats->period[PS_PERIOD_500MS]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| <= 750ms   | %10u |\n", ps_stats->period[PS_PERIOD_750MS]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| <= 1000ms  | %10u |\n", ps_stats->period[PS_PERIOD_1000MS]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| <= 2000ms  | %10u |\n", ps_stats->period[PS_PERIOD_2000MS]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| <= 5000ms  | %10u |\n", ps_stats->period[PS_PERIOD_5000MS]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "| <= 10000ms | %10u |\n", ps_stats->period[PS_PERIOD_10000MS]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "|  > 10000ms | %10u |\n", ps_stats->period[PS_PERIOD_ABOVE]);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "|-------------------------|\n");
+
+       cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+}
+
+static void _cl_stats_print_tx(char **buf, int *len, ssize_t *buf_size,
+                              struct cl_tx_cntrs *cntrs,
+                              const char *mode_str, const char *bw_str, const char *gi_str,
+                              u8 nss, u8 mcs, u8 bf,
+                              u64 *total_success, u64 *total_fail)
+{
+       u8 per = 0;
+
+       if (cntrs->fail == 0 && cntrs->success == 0)
+               return;
+
+       per = (u8)div64_u64(100 * (u64)cntrs->fail, cntrs->fail + cntrs->success);
+
+       cl_snprintf(buf, len, buf_size,
+                   "|%-8s|%-3s|%2u|%3u|%3s|%2u|%10u|%10u|%3u|\n",
+                   mode_str, bw_str, nss, mcs, gi_str, bf, cntrs->success, cntrs->fail, per);
+
+       *total_success += cntrs->success;
+       *total_fail += cntrs->fail;
+}
+
+static void cl_stats_print_tx_he(char **buf, int *len, ssize_t *buf_size,
+                                struct cl_stats *stats, u64 *total_success, u64 *total_fail)
+{
+       struct cl_tx_cntrs *cntrs;
+       u8 bw = 0, nss = 0, mcs = 0, gi = 0, bf = 0;
+
+       for (bw = 0; bw < CHNL_BW_MAX; bw++)
+               for (nss = 0; nss < WRS_SS_MAX; nss++)
+                       for (mcs = 0; mcs < WRS_MCS_MAX; mcs++)
+                               for (gi = 0; gi < WRS_GI_MAX; gi++)
+                                       for (bf = 0; bf < BF_IDX_MAX; bf++) {
+                                               cntrs = &stats->tx.he[bw][nss][mcs][gi][bf];
+                                               _cl_stats_print_tx(buf, len, buf_size,
+                                                                  cntrs,
+                                                                  "HE",
+                                                                  bw_str[bw], gi_he_str[gi],
+                                                                  nss, mcs, bf,
+                                                                  total_success, total_fail);
+                                       }
+}
+
+static void cl_stats_print_tx_vht(char **buf, int *len, ssize_t *buf_size,
+                                 struct cl_stats *stats, u64 *total_success, u64 *total_fail)
+{
+       struct cl_tx_cntrs *cntrs;
+       u8 bw = 0, nss = 0, mcs = 0, gi = 0, bf = 0;
+
+       for (bw = 0; bw < CHNL_BW_MAX_VHT; bw++)
+               for (nss = 0; nss < WRS_SS_MAX; nss++)
+                       for (mcs = 0; mcs < WRS_MCS_MAX_VHT; mcs++)
+                               for (gi = 0; gi < WRS_GI_MAX_VHT; gi++)
+                                       for (bf = 0; bf < BF_IDX_MAX; bf++) {
+                                               cntrs = &stats->tx.vht[bw][nss][mcs][gi][bf];
+                                               _cl_stats_print_tx(buf, len, buf_size,
+                                                                  cntrs,
+                                                                  "VHT",
+                                                                  bw_str[bw], gi_ht_vht_str[gi],
+                                                                  nss, mcs, bf,
+                                                                  total_success, total_fail);
+                                       }
+}
+
+static void cl_stats_print_tx_ht(char **buf, int *len, ssize_t *buf_size,
+                                struct cl_stats *stats, u64 *total_success, u64 *total_fail)
+{
+       struct cl_tx_cntrs *cntrs;
+       u8 bw = 0, nss = 0, mcs = 0, gi = 0;
+
+       for (bw = 0; bw < CHNL_BW_MAX_HT; bw++)
+               for (nss = 0; nss < WRS_SS_MAX; nss++)
+                       for (mcs = 0; mcs < WRS_MCS_MAX_HT; mcs++)
+                               for (gi = 0; gi < WRS_GI_MAX_HT; gi++) {
+                                       cntrs = &stats->tx.ht[bw][nss][mcs][gi];
+                                       _cl_stats_print_tx(buf, len, buf_size,
+                                                          cntrs,
+                                                          "HT",
+                                                          bw_str[bw], gi_ht_vht_str[gi],
+                                                          nss, mcs, 0,
+                                                          total_success, total_fail);
+                               }
+}
+
+static void cl_stats_print_tx_ofdm(char **buf, int *len, ssize_t *buf_size,
+                                  struct cl_stats *stats, u64 *total_success, u64 *total_fail)
+{
+       struct cl_tx_cntrs *cntrs;
+       u8 mcs;
+
+       for (mcs = 0; mcs < WRS_MCS_MAX_OFDM; mcs++) {
+               cntrs = &stats->tx.ofdm[mcs];
+               _cl_stats_print_tx(buf, len, buf_size,
+                                  cntrs,
+                                  "OFDM", bw_str[0], "0",
+                                  0, mcs, 0,
+                                  total_success, total_fail);
+       }
+}
+
+static void cl_stats_print_tx_cck(char **buf, int *len, ssize_t *buf_size,
+                                 struct cl_stats *stats, u64 *total_success, u64 *total_fail)
+{
+       struct cl_tx_cntrs *cntrs;
+       u8 mcs;
+
+       for (mcs = 0; mcs < WRS_MCS_MAX_CCK; mcs++) {
+               cntrs = &stats->tx.cck[mcs];
+               _cl_stats_print_tx(buf, len, buf_size,
+                                  cntrs,
+                                  "CCK", bw_str[0], "0",
+                                  0, mcs, 0,
+                                  total_success, total_fail);
+       }
+}
+
+static void cl_stats_print_tx(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+       struct cl_stats *stats = cl_sta->stats;
+       u64 total_success = 0, total_fail = 0;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int len = 0;
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "\nSTA #%u, MAC %pM\n", cl_sta->sta_idx, cl_sta->addr);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "------------------------------------------------------\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "|MODE    |BW |SS|MCS|GI |BF| Success  |   Fail   |PER|\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "|--------+---+--+---+---+--+----------+----------+---|\n");
+
+       cl_stats_print_tx_he(&buf, &len, &buf_size, stats, &total_success, &total_fail);
+       cl_stats_print_tx_vht(&buf, &len, &buf_size, stats, &total_success, &total_fail);
+       cl_stats_print_tx_ht(&buf, &len, &buf_size, stats, &total_success, &total_fail);
+       cl_stats_print_tx_ofdm(&buf, &len, &buf_size, stats, &total_success, &total_fail);
+       cl_stats_print_tx_cck(&buf, &len, &buf_size, stats, &total_success, &total_fail);
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "------------------------------------------------------\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "                           |%10llu|%10llu|\n", total_success, total_fail);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "                           -----------------------\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "\n");
+
+       cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+}
+
+static void _cl_stats_print_rx(struct cl_hw *cl_hw, struct cl_rx_stats *rx_stats,
+                              struct cl_sta *cl_sta)
+{
+       u8 mode = 0, mcs = 0, nss = 0, bw = 0, gi = 0, flag = rx_stats->flag;
+       u16 data_rate = 0, data_rate_div_10 = 0, data_rate_mod_10 = 0;
+       u64 remainder = 0, packets = 0, total_packets[WRS_MODE_MAX] = {0},
+               total_data_rate[WRS_MODE_MAX] = {0}, equivalent_data_rate = 0;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int len = 0;
+
+       if (cl_sta)
+               cl_snprintf(&buf, &len, &buf_size,
+                           "\nSTA #%u, MAC %pM\n",
+                           cl_sta->sta_idx, cl_sta->addr);
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "-------------------------------------------------------\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "|  MODE   | BW | SS | MCS | GI | Data-Rate | #Packets |\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "|---------+----+----+-----+----+-----------+----------|\n");
+
+       if ((flag & RX_STATS_HE_TRIG) == 0)
+               goto stats_he_ext;
+
+       for (bw = 0; bw < CHNL_BW_MAX_HE; bw++) {
+               for (nss = 0; nss < WRS_SS_MAX; nss++) {
+                       for (mcs = 0; mcs < WRS_MCS_MAX_HE; mcs++) {
+                               for (gi = 0; gi < WRS_GI_MAX_HE; gi++) {
+                                       packets = rx_stats->he_trig[bw][nss][mcs][gi];
+
+                                       if (packets == 0)
+                                               continue;
+
+                                       data_rate = cl_data_rates_get_x10(WRS_MODE_HE, bw,
+                                                                         nss, mcs, gi);
+                                       data_rate_div_10 = data_rate / 10;
+                                       data_rate_mod_10 = data_rate % 10;
+
+                                       total_packets[WRS_MODE_HE] += packets;
+                                       total_data_rate[WRS_MODE_HE] += (packets * data_rate);
+
+                                       cl_snprintf(&buf, &len, &buf_size,
+                                                   "| HE_TRIG |%4u|%4u|%5u|%4s| %7u.%u |%10llu|\n",
+                                                   BW_TO_MHZ(bw), nss, mcs, gi_he_str[gi],
+                                                   data_rate_div_10, data_rate_mod_10, packets);
+                               }
+                       }
+               }
+       }
+
+stats_he_ext:
+       if ((flag & RX_STATS_HE_EXT) == 0)
+               goto stats_he_mu;
+
+       for (bw = 0; bw < CHNL_BW_MAX_HE; bw++) {
+               for (nss = 0; nss < WRS_SS_MAX; nss++) {
+                       for (mcs = 0; mcs < WRS_MCS_MAX_HE; mcs++) {
+                               for (gi = 0; gi < WRS_GI_MAX_HE; gi++) {
+                                       packets = rx_stats->he_ext[bw][nss][mcs][gi];
+
+                                       if (packets == 0)
+                                               continue;
+
+                                       data_rate = cl_data_rates_get_x10(WRS_MODE_HE, bw,
+                                                                         nss, mcs, gi);
+                                       data_rate_div_10 = data_rate / 10;
+                                       data_rate_mod_10 = data_rate % 10;
+
+                                       total_packets[WRS_MODE_HE] += packets;
+                                       total_data_rate[WRS_MODE_HE] += (packets * data_rate);
+
+                                       cl_snprintf(&buf, &len, &buf_size,
+                                                   "| HE_EXT  |%4u|%4u|%5u|%4s| %7u.%u |%10llu|\n",
+                                                   BW_TO_MHZ(bw), nss, mcs, gi_he_str[gi],
+                                                   data_rate_div_10, data_rate_mod_10, packets);
+                               }
+                       }
+               }
+       }
+
+stats_he_mu:
+       if ((flag & RX_STATS_HE_MU) == 0)
+               goto stats_he_su;
+
+       for (bw = 0; bw < CHNL_BW_MAX_HE; bw++) {
+               for (nss = 0; nss < WRS_SS_MAX; nss++) {
+                       for (mcs = 0; mcs < WRS_MCS_MAX_HE; mcs++) {
+                               for (gi = 0; gi < WRS_GI_MAX_HE; gi++) {
+                                       packets = rx_stats->he_mu[bw][nss][mcs][gi];
+
+                                       if (packets == 0)
+                                               continue;
+
+                                       data_rate = cl_data_rates_get_x10(WRS_MODE_HE, bw,
+                                                                         nss, mcs, gi);
+                                       data_rate_div_10 = data_rate / 10;
+                                       data_rate_mod_10 = data_rate % 10;
+
+                                       total_packets[WRS_MODE_HE] += packets;
+                                       total_data_rate[WRS_MODE_HE] += (packets * data_rate);
+
+                                       cl_snprintf(&buf, &len, &buf_size,
+                                                   "| HE_MU   |%4u|%4u|%5u|%4s| %7u.%u |%10llu|\n",
+                                                   BW_TO_MHZ(bw), nss, mcs, gi_he_str[gi],
+                                                   data_rate_div_10, data_rate_mod_10, packets);
+                               }
+                       }
+               }
+       }
+
+stats_he_su:
+       if ((flag & RX_STATS_HE_SU) == 0)
+               goto stats_vht;
+
+       for (bw = 0; bw < CHNL_BW_MAX_HE; bw++) {
+               for (nss = 0; nss < WRS_SS_MAX; nss++) {
+                       for (mcs = 0; mcs < WRS_MCS_MAX_HE; mcs++) {
+                               for (gi = 0; gi < WRS_GI_MAX_HE; gi++) {
+                                       packets = rx_stats->he_su[bw][nss][mcs][gi];
+
+                                       if (packets == 0)
+                                               continue;
+
+                                       data_rate = cl_data_rates_get_x10(WRS_MODE_HE, bw,
+                                                                         nss, mcs, gi);
+                                       data_rate_div_10 = data_rate / 10;
+                                       data_rate_mod_10 = data_rate % 10;
+
+                                       total_packets[WRS_MODE_HE] += packets;
+                                       total_data_rate[WRS_MODE_HE] += (packets * data_rate);
+
+                                       cl_snprintf(&buf, &len, &buf_size,
+                                                   "| HE_SU   |%4u|%4u|%5u|%4s| %7u.%u |%10llu|\n",
+                                                   BW_TO_MHZ(bw), nss, mcs, gi_he_str[gi],
+                                                   data_rate_div_10, data_rate_mod_10, packets);
+                               }
+                       }
+               }
+       }
+
+stats_vht:
+       if ((flag & RX_STATS_VHT) == 0)
+               goto stats_ht;
+
+       for (bw = 0; bw < CHNL_BW_MAX_VHT; bw++) {
+               for (nss = 0; nss < WRS_SS_MAX; nss++) {
+                       for (mcs = 0; mcs < WRS_MCS_MAX_VHT; mcs++) {
+                               for (gi = 0; gi < WRS_GI_MAX_VHT; gi++) {
+                                       packets = rx_stats->vht[bw][nss][mcs][gi];
+
+                                       if (packets == 0)
+                                               continue;
+
+                                       data_rate = cl_data_rates_get_x10(WRS_MODE_VHT,
+                                                                         bw, nss, mcs, gi);
+                                       data_rate_div_10 = data_rate / 10;
+                                       data_rate_mod_10 = data_rate % 10;
+
+                                       total_packets[WRS_MODE_VHT] += packets;
+                                       total_data_rate[WRS_MODE_VHT] += (packets * data_rate);
+
+                                       cl_snprintf(&buf, &len, &buf_size,
+                                                   "| VHT     |%4u|%4u|%5u|%4s| %7u.%u |%10llu|\n",
+                                                   BW_TO_MHZ(bw), nss, mcs, gi_ht_vht_str[gi],
+                                                   data_rate_div_10, data_rate_mod_10, packets);
+                               }
+                       }
+               }
+       }
+
+stats_ht:
+       if ((flag & RX_STATS_HT) == 0)
+               goto stats_ofdm;
+
+       for (bw = 0; bw < CHNL_BW_MAX_HT; bw++) {
+               for (nss = 0; nss < WRS_SS_MAX; nss++) {
+                       for (mcs = 0; mcs < WRS_MCS_MAX_HT; mcs++) {
+                               for (gi = 0; gi < WRS_GI_MAX_HT; gi++) {
+                                       packets = rx_stats->ht[bw][nss][mcs][gi];
+
+                                       if (packets == 0)
+                                               continue;
+
+                                       data_rate = cl_data_rates_get_x10(WRS_MODE_HT,
+                                                                         bw, nss, mcs, gi);
+                                       data_rate_div_10 = data_rate / 10;
+                                       data_rate_mod_10 = data_rate % 10;
+
+                                       total_packets[WRS_MODE_HT] += packets;
+                                       total_data_rate[WRS_MODE_HT] += (packets * data_rate);
+
+                                       cl_snprintf(&buf, &len, &buf_size,
+                                                   "| HT      |%4u|%4u|%5u|%4s| %7u.%u |%10llu|\n",
+                                                   BW_TO_MHZ(bw), nss, mcs, gi_ht_vht_str[gi],
+                                                   data_rate_div_10, data_rate_mod_10, packets);
+                               }
+                       }
+               }
+       }
+
+stats_ofdm:
+       if ((flag & RX_STATS_OFDM) == 0)
+               goto stats_cck;
+
+       for (mcs = 0; mcs < WRS_MCS_MAX_OFDM; mcs++) {
+               packets = rx_stats->ofdm[mcs];
+
+               if (packets == 0)
+                       continue;
+
+               data_rate = cl_data_rates_get_x10(WRS_MODE_OFDM, 0, 0, mcs, 0);
+               data_rate_div_10 = data_rate / 10;
+               data_rate_mod_10 = data_rate % 10;
+
+               total_packets[WRS_MODE_OFDM] += packets;
+               total_data_rate[WRS_MODE_OFDM] += (packets * data_rate);
+
+               cl_snprintf(&buf, &len, &buf_size,
+                           "| OFDM    |  20|   0|%5u|   0| %7u.%u |%10llu|\n",
+                           mcs, data_rate_div_10, data_rate_mod_10, packets);
+       }
+
+stats_cck:
+       if ((flag & RX_STATS_CCK) == 0)
+               goto stats_end;
+
+       for (mcs = 0; mcs < WRS_MCS_MAX_CCK; mcs++) {
+               packets = rx_stats->cck[mcs];
+
+               if (packets == 0)
+                       continue;
+
+               data_rate = cl_data_rates_get_x10(WRS_MODE_CCK, 0, 0, mcs, 0);
+               data_rate_div_10 = data_rate / 10;
+               data_rate_mod_10 = data_rate % 10;
+
+               total_packets[WRS_MODE_CCK] += packets;
+               total_data_rate[WRS_MODE_CCK] += (packets * data_rate);
+
+               cl_snprintf(&buf, &len, &buf_size,
+                           "| CCK     |  20|   0|%5u|   0| %7u.%u |%10llu|\n",
+                           mcs, data_rate_div_10, data_rate_mod_10, packets);
+       }
+
+stats_end:
+       cl_snprintf(&buf, &len, &buf_size,
+                   "-------------------------------------------------------\n");
+
+       for (mode = 0; mode < WRS_MODE_MAX; mode++) {
+               if (total_packets[mode] == 0)
+                       continue;
+
+               equivalent_data_rate = div64_u64(total_data_rate[mode], total_packets[mode]);
+               data_rate_div_10 = (u16)div64_u64_rem(equivalent_data_rate, 10, &remainder);
+
+               cl_snprintf(&buf, &len, &buf_size,
+                           "%s: Equivalent data rate = %u.%u Mbps\n\n",
+                           WRS_MODE_STR(mode), data_rate_div_10, (u16)remainder);
+       }
+
+       cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+}
+
+static void cl_stats_print_rx(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+       if (cl_hw->chip->conf->ce_production_mode)
+               _cl_stats_print_rx(cl_hw, cl_hw->rx_stats, NULL);
+       else
+               _cl_stats_print_rx(cl_hw, &cl_sta->stats->rx, cl_sta);
+}
+
+static void cl_stats_print_cpu(struct cl_hw *cl_hw)
+{
+       u32 cpu = 0;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int len = 0;
+       struct cl_cpu_cntr *cpu_cntr = &cl_hw->cpu_cntr;
+
+       cl_snprintf(&buf, &len, &buf_size, "|-------------------------|\n");
+       cl_snprintf(&buf, &len, &buf_size, "|CPU|Tx Agg    |Tx Single |\n");
+       cl_snprintf(&buf, &len, &buf_size, "|---+----------+----------|\n");
+
+       for (cpu = 0; cpu < CPU_MAX_NUM; cpu++) {
+               if (cpu_cntr->tx_agg[cpu] == 0 && cpu_cntr->tx_single[cpu] == 0)
+                       continue;
+
+               cl_snprintf(&buf, &len, &buf_size, "| %u |%10u|%10u|\n",
+                           cpu,
+                           cpu_cntr->tx_agg[cpu],
+                           cpu_cntr->tx_single[cpu]);
+       }
+
+       cl_snprintf(&buf, &len, &buf_size, "|-------------------------|\n");
+
+       cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+}
+
+static void cl_sta_print_do(struct cl_hw *cl_hw, u8 sta_idx, stats_callback callback)
+{
+       struct cl_sta *cl_sta;
+
+       cl_sta_lock(cl_hw);
+
+       cl_sta = cl_sta_get(cl_hw, sta_idx);
+       if (cl_sta)
+               callback(cl_hw, cl_sta);
+
+       cl_sta_unlock(cl_hw);
+}
+
+static void cl_stats_cli_cpu(struct cl_hw *cl_hw, bool action)
+{
+       if (action)
+               cl_stats_print_cpu(cl_hw);
+       else
+               memset(&cl_hw->cpu_cntr, 0, sizeof(struct cl_cpu_cntr));
+}
+
+static void cl_stats_cli_rx_info(struct cl_hw *cl_hw, bool action)
+{
+       if (action)
+               cl_rx_info_print(cl_hw);
+       else
+               cl_rx_info_reset(cl_hw);
+}
+
+static void cll_stats_config_ps(struct cl_sta *cl_sta)
+{
+       cl_sta->stats->ps.timestamp_sleep = jiffies;
+       cl_sta->stats->ps.is_ps = test_sta_flag(cl_sta->stainfo, WLAN_STA_PS_STA) ? true : false;
+}
+
+static void cl_stats_free(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+       kfree(cl_sta->stats);
+       cl_sta->stats = NULL;
+}
+
+static void cl_stats_disable(struct cl_hw *cl_hw)
+{
+       pr_debug("Statistics disabled\n");
+       cl_hw->conf->ci_stats_en = false;
+       cl_sta_loop(cl_hw, cl_stats_free);
+
+       if (cl_hw->chip->conf->ce_production_mode) {
+               kfree(cl_hw->rx_stats);
+               cl_hw->rx_stats = NULL;
+       }
+}
+
+static void cl_stats_enable(struct cl_hw *cl_hw)
+{
+       /*
+        * Allocate for all existing stations.
+        * If one of the allocations fails disable ci_stats_en (and free the
+        * stations that were already allocated).
+        * In production mode also allocate cl_hw->rx_stats
+        */
+       struct cl_sta *cl_sta = NULL;
+       bool success = true;
+
+       if (cl_hw->chip->conf->ce_production_mode) {
+               cl_hw->rx_stats = kzalloc(sizeof(*cl_hw->rx_stats), GFP_ATOMIC);
+
+               if (!cl_hw->rx_stats)
+                       goto out;
+       }
+
+       read_lock(&cl_hw->cl_sta_db.lock);
+
+       list_for_each_entry(cl_sta, &cl_hw->cl_sta_db.head, list) {
+               cl_sta->stats = kzalloc(sizeof(*cl_sta->stats), GFP_ATOMIC);
+
+               if (cl_sta->stats) {
+                       cll_stats_config_ps(cl_sta);
+               } else {
+                       success = false;
+                       break;
+               }
+       }
+
+       if (success) {
+               pr_debug("Statistics enabled\n");
+               cl_hw->conf->ci_stats_en = true;
+       } else {
+               list_for_each_entry(cl_sta, &cl_hw->cl_sta_db.head, list)
+                       cl_stats_free(cl_hw, cl_sta);
+       }
+
+out:
+       read_unlock(&cl_hw->cl_sta_db.lock);
+}
+
+static void cl_stats_cli_enable(struct cl_hw *cl_hw, bool enable)
+{
+       spin_lock_bh(&cl_hw->lock_stats);
+
+       if (enable == cl_hw->conf->ci_stats_en) {
+               pr_debug("Statistics are already %s\n", enable ? "Enabled" : "Disabled");
+               goto out;
+       }
+
+       if (enable)
+               cl_stats_enable(cl_hw);
+       else
+               cl_stats_disable(cl_hw);
+
+out:
+       spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+static void cl_stats_cli_rx(struct cl_hw *cl_hw, u8 sta_idx)
+{
+       spin_lock_bh(&cl_hw->lock_stats);
+
+       if (!cl_hw->conf->ci_stats_en)
+               goto out;
+
+       if (cl_hw->chip->conf->ce_production_mode) {
+               cl_stats_print_rx(cl_hw, NULL);
+       } else {
+               if (sta_idx == STA_IDX_INVALID)
+                       cl_sta_loop(cl_hw, cl_stats_print_rx);
+               else
+                       cl_sta_print_do(cl_hw, sta_idx, cl_stats_print_rx);
+       }
+
+out:
+       spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+static void cl_stats_cli_rssi(struct cl_hw *cl_hw, u8 sta_idx)
+{
+       spin_lock_bh(&cl_hw->lock_stats);
+
+       if (!cl_hw->conf->ci_stats_en)
+               goto out;
+
+       if (sta_idx == STA_IDX_INVALID)
+               cl_sta_loop(cl_hw, cl_stats_print_rssi);
+       else
+               cl_sta_print_do(cl_hw, sta_idx, cl_stats_print_rssi);
+
+out:
+       spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+static void cl_stats_cli_fec_coding(struct cl_hw *cl_hw, u8 sta_idx)
+{
+       spin_lock_bh(&cl_hw->lock_stats);
+
+       if (!cl_hw->conf->ci_stats_en)
+               goto out;
+
+       if (sta_idx == STA_IDX_INVALID)
+               cl_sta_loop(cl_hw, cl_stats_print_fec_coding);
+       else
+               cl_sta_print_do(cl_hw, sta_idx, cl_stats_print_fec_coding);
+
+out:
+       spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+static void cl_stats_cli_ps(struct cl_hw *cl_hw, u8 sta_idx)
+{
+       spin_lock_bh(&cl_hw->lock_stats);
+
+       if (!cl_hw->conf->ci_stats_en)
+               goto out;
+
+       if (sta_idx == STA_IDX_INVALID)
+               cl_sta_loop(cl_hw, cl_stats_print_power_save);
+       else
+               cl_sta_print_do(cl_hw, sta_idx, cl_stats_print_power_save);
+
+out:
+       spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+static void cl_stats_cli_tx(struct cl_hw *cl_hw, u8 sta_idx)
+{
+       spin_lock_bh(&cl_hw->lock_stats);
+
+       if (!cl_hw->conf->ci_stats_en)
+               goto out;
+
+       if (sta_idx == STA_IDX_INVALID)
+               cl_sta_loop(cl_hw, cl_stats_print_tx);
+       else
+               cl_sta_print_do(cl_hw, sta_idx, cl_stats_print_tx);
+
+out:
+       spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+static void cl_stats_cli_reset(struct cl_hw *cl_hw, u8 sta_idx)
+{
+       spin_lock_bh(&cl_hw->lock_stats);
+
+       if (!cl_hw->conf->ci_stats_en)
+               goto out;
+
+       if (cl_hw->chip->conf->ce_production_mode) {
+               memset(cl_hw->rx_stats, 0, sizeof(struct cl_rx_stats));
+       } else {
+               if (sta_idx == STA_IDX_INVALID)
+                       cl_sta_loop(cl_hw, cl_stats_sta_reset);
+               else
+                       cl_sta_print_do(cl_hw, sta_idx, cl_stats_sta_reset);
+       }
+
+out:
+       spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+static int cl_stats_cli_help(struct cl_hw *cl_hw)
+{
+       char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       int err = 0;
+
+       if (!buf)
+               return -ENOMEM;
+
+       snprintf(buf, PAGE_SIZE,
+                "stats usage:\n"
+                "-a : Print air PER and system PER [delay-ms)]\n"
+                "-b : CPU stats [0-reset, 1-print]\n"
+                "-e : Enable/Disable statistics [0-dis, 1-en]\n"
+                "-f : Print RX FEC coding counters [sta_idx]\n"
+                "-p : Print power-save statistics [sta_idx]\n"
+                "-r : Print RX stats table [sta_idx]\n"
+                "-s : Print RSSI stats tables [sta_idx]\n"
+                "-t : Print TX stats table [sta_idx]\n"
+                "-u : Uplink stats [0-reset, 1-print]\n"
+                "-z : Reset stats tables [sta_idx]\n");
+
+       err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+       kfree(buf);
+
+       return err;
+}
+
+static void _cl_stats_update_tx(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+                               struct cl_agg_tx_report *agg_report)
+{
+       struct cl_stats *stats = cl_sta->stats;
+       struct cl_tx_cntrs *cntrs;
+       union cl_rate_ctrl_info rate_ctrl_info = {.word = agg_report->rate_cntrl_info};
+       u8 bw, nss, mcs, gi, bf;
+
+       switch (rate_ctrl_info.field.format_mod) {
+       case WRS_MODE_HE:
+               nss = (rate_ctrl_info.field.mcs_index >> 4);
+               mcs = (rate_ctrl_info.field.mcs_index & 0xF);
+               gi = rate_ctrl_info.field.gi;
+               bw = rate_ctrl_info.field.bw;
+               bf = agg_report->bf;
+               cntrs = &stats->tx.he[bw][nss][mcs][gi][bf];
+               break;
+       case WRS_MODE_VHT:
+               bw = rate_ctrl_info.field.bw;
+               nss = (rate_ctrl_info.field.mcs_index >> 4);
+               mcs = (rate_ctrl_info.field.mcs_index & 0xF);
+               gi = rate_ctrl_info.field.gi;
+               bf = agg_report->bf;
+               cntrs = &stats->tx.vht[bw][nss][mcs][gi][bf];
+               break;
+       case WRS_MODE_HT:
+               bw = rate_ctrl_info.field.bw;
+               nss = (rate_ctrl_info.field.mcs_index >> 3);
+               mcs = (rate_ctrl_info.field.mcs_index & 0x7);
+               gi = rate_ctrl_info.field.gi;
+               cntrs = &stats->tx.ht[bw][nss][mcs][gi];
+               break;
+       case WRS_MODE_OFDM:
+               mcs = rate_ctrl_info.field.mcs_index - RATE_CTRL_OFFSET_OFDM;
+               cntrs = &stats->tx.ofdm[mcs];
+               break;
+       case WRS_MODE_CCK:
+               mcs = rate_ctrl_info.field.mcs_index;
+               cntrs = &stats->tx.cck[mcs];
+               break;
+       default:
+               return;
+       }
+
+       cntrs->success += agg_report->success;
+       cntrs->fail += agg_report->fail;
+}
+
+void cl_stats_init(struct cl_hw *cl_hw)
+{
+       spin_lock_init(&cl_hw->lock_stats);
+
+       if (cl_hw->conf->ci_stats_en && cl_hw->chip->conf->ce_production_mode) {
+               cl_hw->rx_stats = kzalloc(sizeof(*cl_hw->rx_stats), GFP_ATOMIC);
+
+               if (!cl_hw->rx_stats)
+                       cl_hw->conf->ci_stats_en = false;
+       }
+}
+
+void cl_stats_deinit(struct cl_hw *cl_hw)
+{
+       spin_lock_bh(&cl_hw->lock_stats);
+
+       if (cl_hw->conf->ci_stats_en && cl_hw->chip->conf->ce_production_mode) {
+               cl_hw->conf->ci_stats_en = false;
+
+               kfree(cl_hw->rx_stats);
+               cl_hw->rx_stats = NULL;
+       }
+
+       spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+void cl_stats_sta_add(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+       /* If allocation failed disable ci_stats_en, and free the memory of all other stations */
+       bool disable = false;
+
+       /* Take regular lock and not BH, because cl_sta_add_to_lut() already disables BH */
+       spin_lock(&cl_hw->lock_stats);
+
+       if (cl_hw->conf->ci_stats_en) {
+               cl_sta->stats = kzalloc(sizeof(*cl_sta->stats), GFP_ATOMIC);
+
+               if (cl_sta->stats)
+                       cll_stats_config_ps(cl_sta);
+               else
+                       disable = true;
+       }
+
+       spin_unlock(&cl_hw->lock_stats);
+
+       if (disable)
+               cl_stats_cli_enable(cl_hw, false);
+}
+
+void cl_stats_sta_remove(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+       spin_lock_bh(&cl_hw->lock_stats);
+
+       if (cl_hw->conf->ci_stats_en)
+               cl_stats_free(cl_hw, cl_sta);
+
+       spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_tx_agg(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+                           struct cl_agg_tx_report *agg_report)
+{
+       spin_lock(&cl_hw->lock_stats);
+
+       if (cl_hw->conf->ci_stats_en) {
+               struct cl_stats *stats = cl_sta->stats;
+
+               stats->tx.agg_cntr++;
+               stats->tx.fail_cntr += agg_report->fail;
+               _cl_stats_update_tx(cl_hw, cl_sta, agg_report);
+       }
+
+       spin_unlock(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_tx_single(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+                              struct cl_agg_tx_report *agg_report)
+{
+       spin_lock(&cl_hw->lock_stats);
+
+       if (cl_hw->conf->ci_stats_en) {
+               cl_sta->stats->tx.fail_cntr += agg_report->fail;
+               _cl_stats_update_tx(cl_hw, cl_sta, agg_report);
+       }
+
+       spin_unlock(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_rx_rssi(struct cl_hw *cl_hw, struct cl_sta *cl_sta, s8 rssi[MAX_ANTENNAS])
+{
+       int i;
+       s8 rx_rssi;
+
+       spin_lock(&cl_hw->lock_stats);
+
+       if (!cl_hw->conf->ci_stats_en)
+               goto out;
+
+       for (i = 0; i < cl_hw->num_antennas; i++) {
+               rx_rssi = rssi[i] * -1;
+
+               if (rx_rssi >= 0 && rx_rssi < RSSI_ARR_SIZE)
+                       cl_sta->stats->rssi[rx_rssi][i]++;
+       }
+
+out:
+       spin_unlock(&cl_hw->lock_stats);
+}
+
+void _cl_stats_update_rx_rate(struct cl_hw *cl_hw, struct cl_rx_stats *rx_stats,
+                             struct hw_rxhdr *rxhdr)
+{
+       u8 bw, nss, mcs, gi;
+
+       switch (rxhdr->format_mod) {
+       case FORMATMOD_HE_TRIG:
+               nss = rxhdr->n_sts & 0x3;
+               mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_HE);
+               gi = min_t(u8, rxhdr->gi_type, WRS_GI_MAX_HE);
+               rx_stats->he_trig[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+               rx_stats->flag |= RX_STATS_HE_TRIG;
+               break;
+       case FORMATMOD_HE_EXT:
+               nss = rxhdr->n_sts & 0x3;
+               mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_HE);
+               gi = min_t(u8, rxhdr->gi_type, WRS_GI_MAX_HE);
+               rx_stats->he_ext[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+               rx_stats->flag |= RX_STATS_HE_EXT;
+               break;
+       case FORMATMOD_HE_MU:
+               nss = rxhdr->n_sts & 0x3;
+               mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_HE);
+               gi = min_t(u8, rxhdr->gi_type, WRS_GI_MAX_HE);
+               rx_stats->he_mu[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+               rx_stats->flag |= RX_STATS_HE_MU;
+               break;
+       case FORMATMOD_HE_SU:
+               nss = rxhdr->n_sts & 0x3;
+               mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_HE);
+               gi = min_t(u8, rxhdr->gi_type, WRS_GI_MAX_HE);
+               rx_stats->he_su[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+               rx_stats->flag |= RX_STATS_HE_SU;
+               break;
+       case FORMATMOD_VHT:
+               nss = rxhdr->n_sts & 0x3;
+               mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_VHT);
+               gi = rxhdr->gi_type & 0x1;
+               rx_stats->vht[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+               rx_stats->flag |= RX_STATS_VHT;
+               break;
+       case FORMATMOD_HT_MF:
+       case FORMATMOD_HT_GF:
+               bw = rxhdr->ch_bw & 0x1;
+               nss = (rxhdr->mcs >> 3) & 0x3;
+               mcs = rxhdr->mcs & 0x7;
+               gi = rxhdr->gi_type & 0x1;
+               rx_stats->ht[bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+               rx_stats->flag |= RX_STATS_HT;
+               break;
+       case FORMATMOD_NON_HT:
+               if (rxhdr->mcs >= RATE_CTRL_OFFSET_OFDM) {
+                       mcs = (rxhdr->mcs - RATE_CTRL_OFFSET_OFDM) & 0x7;
+                       rx_stats->ofdm[mcs] += rxhdr->frm_successful_rx;
+                       rx_stats->flag |= RX_STATS_OFDM;
+               } else if (cl_band_is_24g(cl_hw)) {
+                       mcs = rxhdr->mcs & 0x3;
+                       rx_stats->cck[mcs] += rxhdr->frm_successful_rx;
+                       rx_stats->flag |= RX_STATS_CCK;
+               }
+               break;
+       }
+}
+
+void cl_stats_update_rx_rate(struct cl_hw *cl_hw, struct cl_sta *cl_sta, struct hw_rxhdr *rxhdr)
+{
+       spin_lock(&cl_hw->lock_stats);
+
+       if (cl_hw->conf->ci_stats_en) {
+               _cl_stats_update_rx_rate(cl_hw, &cl_sta->stats->rx, rxhdr);
+               cl_sta->stats->fec_coding[rxhdr->fec_coding]++;
+       }
+
+       spin_unlock(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_rx_rate_production(struct cl_hw *cl_hw, struct hw_rxhdr *rxhdr)
+{
+       spin_lock(&cl_hw->lock_stats);
+
+       if (cl_hw->conf->ci_stats_en)
+               _cl_stats_update_rx_rate(cl_hw, cl_hw->rx_stats, rxhdr);
+
+       spin_unlock(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_ps(struct cl_hw *cl_hw, struct cl_sta *cl_sta, bool is_ps)
+{
+       struct cl_ps_stats *ps;
+
+       spin_lock(&cl_hw->lock_stats);
+
+       if (!cl_hw->conf->ci_stats_en)
+               goto out;
+
+       ps = &cl_sta->stats->ps;
+
+       if (ps->is_ps == is_ps)
+               goto out;
+
+       ps->is_ps = is_ps;
+
+       if (is_ps) {
+               ps->timestamp_sleep = jiffies;
+       } else {
+               unsigned long sleep_time = jiffies_to_msecs(jiffies - ps->timestamp_sleep);
+
+               if (sleep_time <= 50)
+                       ps->period[PS_PERIOD_50MS]++;
+               else if (sleep_time <= 100)
+                       ps->period[PS_PERIOD_100MS]++;
+               else if (sleep_time <= 250)
+                       ps->period[PS_PERIOD_250MS]++;
+               else if (sleep_time <= 500)
+                       ps->period[PS_PERIOD_500MS]++;
+               else if (sleep_time <= 750)
+                       ps->period[PS_PERIOD_750MS]++;
+               else if (sleep_time <= 1000)
+                       ps->period[PS_PERIOD_1000MS]++;
+               else if (sleep_time <= 2000)
+                       ps->period[PS_PERIOD_2000MS]++;
+               else if (sleep_time <= 5000)
+                       ps->period[PS_PERIOD_5000MS]++;
+               else if (sleep_time <= 10000)
+                       ps->period[PS_PERIOD_10000MS]++;
+               else
+                       ps->period[PS_PERIOD_ABOVE]++;
+       }
+
+out:
+       spin_unlock(&cl_hw->lock_stats);
+}
+
+int cl_stats_cli(struct cl_hw *cl_hw, struct cl_vif *cl_vif, struct cli_params *cli_params)
+{
+       bool print_per = false;
+       bool cpu_stats = false;
+       bool enable_tx_rx_rssi = false;
+       bool print_rx = false;
+       bool print_rssi = false;
+       bool print_fec_coding = false;
+       bool print_power_save = false;
+       bool print_tx = false;
+       bool uplink_stats = false;
+       bool reset_stats = false;
+       u32 expected_params = -1;
+
+       switch (cli_params->option) {
+       case 'a':
+               print_per = true;
+               expected_params = 1;
+               break;
+       case 'b':
+               cpu_stats = true;
+               expected_params = 1;
+               break;
+       case 'e':
+               enable_tx_rx_rssi = true;
+               expected_params = 1;
+               break;
+       case 'f':
+               print_fec_coding = true;
+               expected_params = 1;
+               break;
+       case 'p':
+               print_power_save = true;
+               expected_params = 1;
+               break;
+       case 't':
+               print_tx = true;
+               expected_params = 1;
+               break;
+       case 'r':
+               print_rx = true;
+               expected_params = cl_hw->chip->conf->ce_production_mode ? 0 : 1;
+               break;
+       case 'z':
+               reset_stats = true;
+               expected_params = cl_hw->chip->conf->ce_production_mode ? 0 : 1;
+               break;
+       case 's':
+               print_rssi = true;
+               expected_params = 1;
+               break;
+       case 'u':
+               uplink_stats = true;
+               expected_params = 1;
+               break;
+       case '?':
+               return cl_stats_cli_help(cl_hw);
+       default:
+               cl_dbg_err(cl_hw, "Illegal option (%c) - try '?' for help\n", cli_params->option);
+               goto out_err;
+       }
+
+       if (expected_params != cli_params->num_params) {
+               cl_dbg_err(cl_hw, "Wrong number of arguments (expected %u) (actual %u)\n",
+                          expected_params, cli_params->num_params);
+               goto out_err;
+       }
+
+       if (print_per) {
+               cl_stats_print_per(cl_hw, (u32)cli_params->params[0]);
+               return 0;
+       }
+
+       if (cpu_stats) {
+               cl_stats_cli_cpu(cl_hw, (bool)cli_params->params[0]);
+               return 0;
+       }
+
+       if (uplink_stats) {
+               cl_stats_cli_rx_info(cl_hw, (bool)cli_params->params[0]);
+               return 0;
+       }
+
+       if (enable_tx_rx_rssi) {
+               cl_stats_cli_enable(cl_hw, (bool)cli_params->params[0]);
+               return 0;
+       }
+
+       if (!cl_hw->conf->ci_stats_en) {
+               pr_debug("Statistics are disabled!\n"
+                        "To enable them type: "
+                        "'iwcl <interface> cecli stats -e.1'\n");
+               return 0;
+       }
+
+       if (print_rx) {
+               u8 sta_idx = (u8)cli_params->params[0];
+
+               cl_stats_cli_rx(cl_hw, sta_idx);
+               return 0;
+       }
+
+       if (print_rssi) {
+               u8 sta_idx = (u8)cli_params->params[0];
+
+               cl_stats_cli_rssi(cl_hw, sta_idx);
+               return 0;
+       }
+
+       if (print_fec_coding) {
+               u8 sta_idx = (u8)cli_params->params[0];
+
+               cl_stats_cli_fec_coding(cl_hw, sta_idx);
+               return 0;
+       }
+
+       if (print_power_save) {
+               u8 sta_idx = (u8)cli_params->params[0];
+
+               cl_stats_cli_ps(cl_hw, sta_idx);
+               return 0;
+       }
+
+       if (print_tx) {
+               u8 sta_idx = (u8)cli_params->params[0];
+
+               cl_stats_cli_tx(cl_hw, sta_idx);
+               return 0;
+       }
+
+       if (reset_stats) {
+               u8 sta_idx = (u8)cli_params->params[0];
+
+               cl_stats_cli_reset(cl_hw, sta_idx);
+               return 0;
+       }
+
+out_err:
+       return -EIO;
+}