diff --git a/drivers/net/wireless/celeno/cl8k/traffic.c b/drivers/net/wireless/celeno/cl8k/traffic.c
new file mode 100644
index 000000000000..788fd02e28a8
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/traffic.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+
+#include "bf.h"
+#include "tx.h"
+#include "radio.h"
+#include "utils.h"
+#include "debug.h"
+#include "hw.h"
+#include "traffic.h"
+
+#define TRAFFIC_CNTR_ACTIVE_THR     3       /* 3 * 100ms = 300ms */
+#define TRAFFIC_CNTR_IDLE_THR       20      /* 20 * 100ms = 2sec */
+
+/* Threshold in bytes */
+#define TRAFFIC_ACTIVE_THR_DRV      1920    /* = 150Kbit/sec (150 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_BF       26214   /* = 2mbit/sec (2 * 1024 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_MU       131070  /* = 10mbit/sec (10 * 1024 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_EDCA_6G  2621440 /* = 200mbit/sec (200 * 1024 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_EDCA_5G  2621440 /* = 200mbit/sec (200 * 1024 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_EDCA_24G 655360  /* = 50mbit/sec (50 * 1024 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_DFS      13107   /* = 1mbit/sec (1 * 1024 * 1024 / 8 / 10) */
+
+static void cl_traffic_mon_ipv4(struct sk_buff *skb, struct cl_sta *cl_sta,
+				enum cl_traffic_mon_direction direction)
+{
+	struct iphdr *iphdr = ip_hdr(skb);
+
+	if (iphdr->protocol == IPPROTO_UDP)
+		cl_sta->traffic_mon[CL_TRFC_MON_PROT_UDP][direction].bytes +=
+			ntohs(iphdr->tot_len) - IPV4_HDR_LEN(iphdr->ihl) - sizeof(struct udphdr);
+	else if (iphdr->protocol == IPPROTO_TCP)
+		cl_sta->traffic_mon[CL_TRFC_MON_PROT_TCP][direction].bytes +=
+			ntohs(iphdr->tot_len) - IPV4_HDR_LEN(iphdr->ihl) - sizeof(struct tcphdr);
+}
+
+static void cl_traffic_mon_ipv6(struct sk_buff *skb, struct cl_sta *cl_sta,
+				enum cl_traffic_mon_direction direction)
+{
+	struct ipv6hdr *ipv6hdr = ipv6_hdr(skb);
+
+	if (ipv6hdr->nexthdr == IPPROTO_UDP)
+		cl_sta->traffic_mon[CL_TRFC_MON_PROT_UDP][direction].bytes +=
+			ntohs(ipv6hdr->payload_len) - sizeof(struct udphdr);
+	else if (ipv6hdr->nexthdr == IPPROTO_TCP)
+		cl_sta->traffic_mon[CL_TRFC_MON_PROT_TCP][direction].bytes +=
+			ntohs(ipv6hdr->payload_len) - sizeof(struct tcphdr);
+}
+
+static void cl_traffic_mon_calc(struct sk_buff *skb, struct cl_sta *cl_sta,
+				enum cl_traffic_mon_direction direction)
+{
+	if (cl_set_network_header_if_proto(skb, ETH_P_IP))
+		cl_traffic_mon_ipv4(skb, cl_sta, direction);
+	else if (cl_set_network_header_if_proto(skb, ETH_P_IPV6))
+		cl_traffic_mon_ipv6(skb, cl_sta, direction);
+}
+
+void cl_traffic_mon_tx(struct cl_sta *cl_sta, struct sk_buff *skb)
+{
+	struct cl_hw *cl_hw = cl_sta->cl_vif->cl_hw;
+
+	if (cl_hw->conf->ci_traffic_mon_en)
+		cl_traffic_mon_calc(skb, cl_sta, CL_TRFC_MON_DIR_DL);
+}
+
+void cl_traffic_mon_rx(struct cl_sta *cl_sta, struct sk_buff *skb)
+{
+	struct cl_hw *cl_hw = cl_sta->cl_vif->cl_hw;
+
+	if (cl_hw->conf->ci_traffic_mon_en)
+		cl_traffic_mon_calc(skb, cl_sta, CL_TRFC_MON_DIR_UL);
+}
+
+void cl_traffic_mon_sta_maintenance(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+	u8 i, j;
+
+	for (i = 0; i < CL_TRFC_MON_PROT_MAX; i++)
+		for (j = 0; j < CL_TRFC_MON_DIR_MAX; j++) {
+			cl_sta->traffic_mon[i][j].bytes_per_sec = cl_sta->traffic_mon[i][j].bytes;
+			cl_sta->traffic_mon[i][j].bytes = 0;
+		}
+}
+
+static void cl_traffic_sta_start(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+				 enum cl_traffic_level level, enum cl_traffic_direction direction)
+{
+	cl_hw->traffic_db.num_active_sta_dir[direction][level]++;
+
+	/* If other direction is not active increase num_active_sta */
+	if (!cl_sta->traffic_db[1 - direction].activity_db[level].is_active)
+		cl_hw->traffic_db.num_active_sta[level]++;
+
+	if (level == TRAFFIC_LEVEL_DRV)	{
+		/*
+		 * Dynamic CTS:
+		 * If protection mode is disabled, environment is clean,
+		 * and station threshold was reached switch to CTS.
+		 */
+		if (cl_hw->traffic_db.num_active_sta[TRAFFIC_LEVEL_DRV] ==
+		     cl_hw->conf->ci_dyn_cts_sta_thr) {
+			if (cl_prot_mode_get(cl_hw) == TXL_NO_PROT) {
+				cl_hw->traffic_db.dynamic_cts = true;
+				cl_prot_mode_set(cl_hw, TXL_PROT_CTS);
+			}
+		}
+	} else if (level == TRAFFIC_LEVEL_BF) {
+		if (direction == TRAFFIC_DIRECTION_TX)
+			cl_bf_sta_active(cl_hw, cl_sta, true);
+	}
+}
+
+static void cl_traffic_sta_stop(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+				enum cl_traffic_level level, enum cl_traffic_direction direction)
+{
+	cl_hw->traffic_db.num_active_sta_dir[direction][level]--;
+
+	/* If other direction is not active decrease num_active_sta */
+	if (!cl_sta->traffic_db[1 - direction].activity_db[level].is_active)
+		cl_hw->traffic_db.num_active_sta[level]--;
+
+	if (level == TRAFFIC_LEVEL_DRV) {
+		/* Dynamic CTS:
+		 * If it was turned on and active station count became lower than
+		 * threshold --> return to no protection
+		 */
+		if (cl_hw->traffic_db.dynamic_cts) {
+			if (cl_hw->traffic_db.num_active_sta[TRAFFIC_LEVEL_DRV] ==
+			    (cl_hw->conf->ci_dyn_cts_sta_thr - 1)) {
+				cl_hw->traffic_db.dynamic_cts = false;
+				cl_prot_mode_set(cl_hw, TXL_NO_PROT);
+			}
+		}
+	} else if (level == TRAFFIC_LEVEL_BF) {
+		if (direction == TRAFFIC_DIRECTION_TX)
+			cl_bf_sta_active(cl_hw, cl_sta, false);
+	}
+}
+
+static void cl_traffic_check_activity(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+				      enum cl_traffic_level level,
+				      enum cl_traffic_direction direction)
+{
+	struct cl_traffic_activity *activity_db =
+		&cl_sta->traffic_db[direction].activity_db[level];
+	u32 num_bytes = cl_sta->traffic_db[direction].num_bytes;
+
+	if (num_bytes > cl_hw->traffic_db.active_bytes_thr[level]) {
+		activity_db->cntr_active++;
+		activity_db->cntr_idle = 0;
+
+		/* If traffic is above threshold for X consective times change state to active */
+		if (!activity_db->is_active &&
+		    activity_db->cntr_active >= TRAFFIC_CNTR_ACTIVE_THR) {
+			activity_db->is_active = true;
+			cl_traffic_sta_start(cl_hw, cl_sta, level, direction);
+		}
+	} else {
+		activity_db->cntr_active = 0;
+		activity_db->cntr_idle++;
+
+		/* If traffic is below threshold for Y consective times change state to idle */
+		if (activity_db->is_active && activity_db->cntr_idle >= TRAFFIC_CNTR_IDLE_THR) {
+			activity_db->is_active = false;
+			cl_traffic_sta_stop(cl_hw, cl_sta, level, direction);
+		}
+	}
+}
+
+static void cl_traffic_monitor_sta_traffic(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+	enum cl_traffic_level level = 0;
+
+	/* Check Tx & Rx activity in all levels */
+	for (level = 0; level < TRAFFIC_LEVEL_MAX; level++) {
+		cl_traffic_check_activity(cl_hw, cl_sta, level, TRAFFIC_DIRECTION_TX);
+		cl_traffic_check_activity(cl_hw, cl_sta, level, TRAFFIC_DIRECTION_RX);
+	}
+
+	/* Save previous Tx num bytes */
+	cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes_prev =
+		cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes;
+
+	/* Reset num_bytes */
+	cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes = 0;
+	cl_sta->traffic_db[TRAFFIC_DIRECTION_RX].num_bytes = 0;
+}
+
+void cl_traffic_init(struct cl_hw *cl_hw)
+{
+	struct cl_traffic_main *traffic_db = &cl_hw->traffic_db;
+
+	traffic_db->active_bytes_thr[TRAFFIC_LEVEL_DRV] = TRAFFIC_ACTIVE_THR_DRV;
+	traffic_db->active_bytes_thr[TRAFFIC_LEVEL_BF] = TRAFFIC_ACTIVE_THR_BF;
+	traffic_db->active_bytes_thr[TRAFFIC_LEVEL_MU] = TRAFFIC_ACTIVE_THR_MU;
+
+	if (cl_band_is_6g(cl_hw))
+		traffic_db->active_bytes_thr[TRAFFIC_LEVEL_EDCA] = TRAFFIC_ACTIVE_THR_EDCA_6G;
+	else if (cl_band_is_5g(cl_hw))
+		traffic_db->active_bytes_thr[TRAFFIC_LEVEL_EDCA] = TRAFFIC_ACTIVE_THR_EDCA_5G;
+	else
+		traffic_db->active_bytes_thr[TRAFFIC_LEVEL_EDCA] = TRAFFIC_ACTIVE_THR_EDCA_24G;
+
+	traffic_db->active_bytes_thr[TRAFFIC_LEVEL_DFS] = TRAFFIC_ACTIVE_THR_DFS;
+}
+
+void cl_traffic_tx_handler(struct cl_hw *cl_hw, struct cl_sta *cl_sta, u32 num_bytes)
+{
+	cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes += num_bytes;
+	cl_sta->tx_bytes += num_bytes;
+}
+
+void cl_traffic_rx_handler(struct cl_hw *cl_hw, struct cl_sta *cl_sta, u32 num_bytes)
+{
+	cl_sta->traffic_db[TRAFFIC_DIRECTION_RX].num_bytes += num_bytes;
+	cl_sta->rx_bytes += num_bytes;
+}
+
+void cl_traffic_maintenance(struct cl_hw *cl_hw)
+{
+	cl_sta_loop(cl_hw, cl_traffic_monitor_sta_traffic);
+}
+
+void cl_traffic_sta_remove(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+	/* Check if station disconnected during traffic */
+	enum cl_traffic_level level = 0;
+	enum cl_traffic_direction direction = 0;
+
+	for (direction = 0; direction < TRAFFIC_DIRECTION_MAX; direction++) {
+		for (level = 0; level < TRAFFIC_LEVEL_MAX; level++) {
+			if (cl_sta->traffic_db[direction].activity_db[level].is_active)
+				cl_traffic_sta_stop(cl_hw, cl_sta, level, direction);
+		}
+
+		memset(&cl_sta->traffic_db, 0, sizeof(cl_sta->traffic_db));
+	}
+}
+
+bool cl_traffic_is_sta_active(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+	return (cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].activity_db[TRAFFIC_LEVEL_DRV].is_active ||
+		cl_sta->traffic_db[TRAFFIC_DIRECTION_RX].activity_db[TRAFFIC_LEVEL_DRV].is_active);
+}
+
+bool cl_traffic_is_sta_tx_exist(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+	return ((cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes != 0) ||
+		(cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes_prev != 0));
+}
