diff --git a/drivers/net/wireless/celeno/cl8k/stats.c b/drivers/net/wireless/celeno/cl8k/stats.c
new file mode 100644
index 000000000000..c526199513f4
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/stats.c
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/string.h>
+
+#include "reg/reg_access.h"
+#include "sta.h"
+#include "utils.h"
+#include "reg/reg_defs.h"
+#include "rates.h"
+#include "debug.h"
+#include "tx.h"
+#include "vif.h"
+#include "stats.h"
+
+static void cll_stats_config_ps(struct cl_sta *cl_sta)
+{
+	struct sta_info *stainfo = IEEE80211_STA_TO_STAINFO(cl_sta->sta);
+
+	cl_sta->stats->ps.timestamp_sleep = jiffies;
+	cl_sta->stats->ps.is_ps = test_sta_flag(stainfo, WLAN_STA_PS_STA);
+}
+
+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_is_prod_or_listener(cl_hw)) {
+		kfree(cl_hw->rx_stats);
+		cl_hw->rx_stats = NULL;
+	}
+}
+
+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 = le32_to_cpu(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;
+	stats->tx.packet_success += agg_report->success;
+	stats->tx.packet_fail += agg_report->fail;
+}
+
+static 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;
+	}
+
+	rx_stats->packet_success += rxhdr->frm_successful_rx;
+}
+
+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_is_prod_or_listener(cl_hw)) {
+		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_is_prod_or_listener(cl_hw))) {
+		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;
+
+	if (cl_hw->conf->ci_stats_en) {
+		/*
+		 * Take regular lock and not BH,
+		 * because cl_sta_add_to_lut() already disables BH
+		 */
+		spin_lock(&cl_hw->lock_stats);
+
+		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_hw->conf->ci_stats_en) {
+		spin_lock_bh(&cl_hw->lock_stats);
+		cl_stats_disable(cl_hw);
+		spin_unlock_bh(&cl_hw->lock_stats);
+	}
+}
+
+void cl_stats_sta_remove(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+	if (!cl_hw->conf->ci_stats_en)
+		return;
+
+	spin_lock_bh(&cl_hw->lock_stats);
+
+	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)
+{
+	struct cl_stats *stats = cl_sta->stats;
+
+	if (!cl_hw->conf->ci_stats_en)
+		return;
+
+	spin_lock(&cl_hw->lock_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)
+{
+	if (!cl_hw->conf->ci_stats_en)
+		return;
+
+	spin_lock(&cl_hw->lock_stats);
+
+	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;
+
+	if (!cl_hw->conf->ci_stats_en)
+		return;
+
+	spin_lock_bh(&cl_hw->lock_stats);
+
+	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]++;
+	}
+
+	spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_rx_rate(struct cl_hw *cl_hw, struct cl_sta *cl_sta, struct hw_rxhdr *rxhdr)
+{
+	if (!cl_hw->conf->ci_stats_en)
+		return;
+
+	spin_lock(&cl_hw->lock_stats);
+
+	_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)
+{
+	if (!cl_hw->conf->ci_stats_en)
+		return;
+
+	spin_lock(&cl_hw->lock_stats);
+
+	_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;
+
+	if (!cl_hw->conf->ci_stats_en)
+		return;
+
+	spin_lock_bh(&cl_hw->lock_stats);
+
+	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_bh(&cl_hw->lock_stats);
+}
+
+int cl_stats_get_rssi(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+	struct cl_stats *stats = NULL;
+	u32 i = 0, j = 0;
+	u64 total_rssi = 0;
+	s8 avg_signal = 0;
+
+	if (!cl_hw->conf->ci_stats_en)
+		return 0;
+
+	u64 avg_rssi[MAX_ANTENNAS] = {0};
+	u64 sum_rssi[MAX_ANTENNAS] = {0};
+
+	spin_lock_bh(&cl_hw->lock_stats);
+
+	stats = cl_sta->stats;
+
+	if (!stats)
+		goto out;
+
+	for (i = 0; i < RSSI_ARR_SIZE; i++) {
+		total_rssi = 0;
+
+		for (j = 0; j < cl_hw->num_antennas; j++) {
+			sum_rssi[j] += stats->rssi[i][j];
+			avg_rssi[j] += i * stats->rssi[i][j];
+		}
+	}
+
+	for (j = 0; j < cl_hw->num_antennas; j++)
+		if (sum_rssi[j])
+			avg_rssi[j] = div64_u64(avg_rssi[j], sum_rssi[j]);
+
+	for (j = 0; j < cl_hw->num_antennas; j++)
+		total_rssi += avg_rssi[j];
+
+	avg_signal = -div64_u64(total_rssi, cl_hw->num_antennas);
+out:
+	spin_unlock_bh(&cl_hw->lock_stats);
+
+	return avg_signal;
+}
+
+void cl_stats_get_tx(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+		     u64 *total_tx_success, u64 *total_tx_fail)
+{
+	if (!cl_hw->conf->ci_stats_en)
+		return;
+
+	spin_lock_bh(&cl_hw->lock_stats);
+
+	if (!cl_sta->stats)
+		goto out;
+
+	*total_tx_success = cl_sta->stats->tx.packet_success;
+	*total_tx_fail = cl_sta->stats->tx.packet_fail;
+
+out:
+	spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+u64 cl_stats_get_rx(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+	u64 total_rx_packets = 0;
+
+	if (!cl_hw->conf->ci_stats_en)
+		return 0;
+
+	spin_lock_bh(&cl_hw->lock_stats);
+
+	if (!cl_sta->stats)
+		goto out;
+
+	total_rx_packets = cl_sta->stats->rx.packet_success;
+
+out:
+	spin_unlock_bh(&cl_hw->lock_stats);
+
+	return total_rx_packets;
+}
+
