diff --git a/drivers/net/wireless/celeno/cl8k/tx/agg_tx_report.c b/drivers/net/wireless/celeno/cl8k/tx/agg_tx_report.c
new file mode 100644
index 000000000000..478e5e734f08
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/tx/agg_tx_report.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "tx/agg_tx_report.h"
+#include "stats.h"
+#include "rate_ctrl.h"
+#include "wrs/wrs_api.h"
+#include "data_rates.h"
+
+static bool is_same_rate(struct cl_agg_tx_report *agg_report, struct cl_wrs_tx_params *tx_params)
+{
+       union cl_rate_ctrl_info rate_ctrl_info = {.word = agg_report->rate_cntrl_info};
+       u8 mcs = U8_MAX, nss = U8_MAX;
+
+       if (agg_report->bw_requested != tx_params->bw ||
+           rate_ctrl_info.field.gi != tx_params->gi)
+               return false;
+
+       cl_rate_ctrl_parse(&rate_ctrl_info, &nss, &mcs);
+
+       return ((mcs == tx_params->mcs) && (nss == tx_params->nss));
+}
+
+static void sync_tx_rate(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+                        struct cl_agg_tx_report *agg_report,
+                        struct cl_wrs_info *wrs_info, struct cl_wrs_params *wrs_params)
+{
+       if (!agg_report->is_fallback && is_same_rate(agg_report, &wrs_params->tx_params)) {
+               cl_wrs_api_rate_sync(cl_hw, cl_sta, wrs_params);
+
+               wrs_info->synced = true;
+               wrs_info->quick_rate_check = true;
+               wrs_info->quick_rate_agg_cntr = 0;
+               wrs_info->quick_rate_pkt_cntr = 0;
+       } else {
+               wrs_info->sync_attempts++;
+       }
+}
+
+static void ba_not_received_handler(struct cl_hw *cl_hw, struct cl_wrs_info *wrs_info,
+                                   struct cl_agg_tx_report *agg_report)
+{
+       /* Ignore 'BA not received' if station is in power-save or if RTS limit was reached */
+       if (agg_report->is_sta_ps || agg_report->is_rts_retry_limit_reached)
+               return;
+
+       /* Count number of consecutive 'BA not received' */
+       wrs_info->ba_not_rcv_consecutive++;
+
+       /* Save longest sequence of consecutive 'BA not received' */
+       if (wrs_info->ba_not_rcv_consecutive > wrs_info->ba_not_rcv_consecutive_max)
+               wrs_info->ba_not_rcv_consecutive_max = wrs_info->ba_not_rcv_consecutive;
+
+       if (cl_hw->wrs_db.ba_not_rcv_collision_filter) {
+               /*
+                * First 'BA not received' - might just be a collision.
+                * Don't add fail to ba_not_rcv but keep aside.
+                * Second consecutive 'BA not received' - not likely to be a collisions.
+                * Add fail to ba_not_rcv including previous fail that was kept aside.
+                * More than two consecutive 'BA not received' - very unlikely to be a collisions.
+                * Add fail to ba_not_rcv.
+                */
+               if (wrs_info->ba_not_rcv_consecutive == 1)
+                       wrs_info->tx_fail_prev = agg_report->fail;
+               else if (wrs_info->ba_not_rcv_consecutive == 2)
+                       wrs_info->ba_not_rcv += (agg_report->fail + wrs_info->tx_fail_prev);
+               else
+                       wrs_info->ba_not_rcv += agg_report->fail;
+       } else {
+               wrs_info->ba_not_rcv += agg_report->fail;
+       }
+}
+
+void cl_agg_tx_report_handler(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+                             struct cl_agg_tx_report *agg_report)
+{
+       struct cl_wrs_info *wrs_info = &cl_sta->wrs_info;
+       struct cl_wrs_params *wrs_params = &cl_sta->wrs_sta.su_params;
+       bool skip_epr_update = false;
+       union cl_rate_ctrl_info rate_ctrl_info = {.word = agg_report->rate_cntrl_info};
+
+       /* Retry_count for cl_wlan */
+       cl_sta->retry_count += agg_report->success_after_retry;
+
+       /*
+        * In case of big packets (4300 in VHT and 5400 in HE) and low
+        * rate (BW 20, NSS 1, MCS 0), firmware will increase rate to MCS 1,
+        * and give an indication to driver (set rate_fix_mcs1 in cl_agg_tx_report).
+        * WRS should also move to MCS 1, and give the maximum time
+        * penalty time from MCS 0 toMCS 1.
+        */
+       if (agg_report->rate_fix_mcs1 && !agg_report->is_fallback)
+               if (cl_wrs_api_up_mcs1(cl_hw, cl_sta, wrs_params))
+                       return;
+
+       /* WRS sync mechanism */
+       if (!wrs_info->synced)
+               sync_tx_rate(cl_hw, cl_sta, agg_report, wrs_info, wrs_params);
+
+       if (agg_report->bf && cl_sta->bf_db.is_on && !cl_sta->bf_db.synced) {
+               cl_sta->bf_db.synced = true;
+               /* Resetting the WRS UP weights */
+               cl_wrs_api_beamforming_sync(cl_hw, cl_sta);
+       }
+
+       if (agg_report->ba_not_received) {
+               ba_not_received_handler(cl_hw, wrs_info, agg_report);
+       } else {
+               if (!skip_epr_update)
+                       wrs_info->tx_fail += agg_report->fail;
+
+               wrs_info->ba_not_rcv_consecutive = 0;
+       }
+
+       if (!skip_epr_update) {
+               u8 mcs = 0, nss = 0, bw = 0;
+               u16 data_rate = 0;
+
+               switch (agg_report->bw_requested) {
+               case CHNL_BW_160:
+                       bw = (cl_hw->wrs_db.adjacent_interference20 ||
+                             cl_hw->wrs_db.adjacent_interference40 ||
+                             cl_hw->wrs_db.adjacent_interference80) ?
+                               rate_ctrl_info.field.bw : agg_report->bw_requested;
+                       break;
+               case CHNL_BW_80:
+                       bw = (cl_hw->wrs_db.adjacent_interference20 ||
+                             cl_hw->wrs_db.adjacent_interference40) ?
+                               rate_ctrl_info.field.bw : agg_report->bw_requested;
+                       break;
+               case CHNL_BW_40:
+                       bw = cl_hw->wrs_db.adjacent_interference20 ?
+                               rate_ctrl_info.field.bw : agg_report->bw_requested;
+                       break;
+               case CHNL_BW_20:
+                       bw = agg_report->bw_requested;
+                       break;
+               }
+
+               cl_rate_ctrl_parse(&rate_ctrl_info, &nss, &mcs);
+
+               data_rate = cl_data_rates_get_x10(rate_ctrl_info.field.format_mod,
+                                                 bw,
+                                                 nss,
+                                                 mcs,
+                                                 rate_ctrl_info.field.gi);
+
+               wrs_info->epr_acc += ((u64)agg_report->success * data_rate);
+               wrs_info->tx_success += agg_report->success;
+       }
+
+       if (cl_hw->wrs_db.quick_down_en && wrs_info->quick_rate_check) {
+               if (is_same_rate(agg_report, &wrs_params->tx_params)) {
+                       wrs_info->quick_rate_agg_cntr++;
+                       wrs_info->quick_rate_pkt_cntr += (agg_report->success + agg_report->fail);
+
+                       if (wrs_info->quick_rate_agg_cntr >= cl_hw->wrs_db.quick_down_agg_thr &&
+                           wrs_info->quick_rate_pkt_cntr > cl_hw->wrs_db.quick_down_pkt_thr) {
+                               wrs_info->quick_rate_check = false;
+                               cl_wrs_api_quick_down_check(cl_hw, cl_sta, wrs_params);
+                       }
+               }
+       }
+}
+
+void cl_agg_tx_report_simulate_for_single(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+                                         struct cl_hw_tx_status *status)
+{
+       /* Assign statistics struct */
+       struct cl_agg_tx_report agg_report;
+       union cl_rate_ctrl_info rate_ctrl_info;
+
+       memset(&agg_report, 0, sizeof(struct cl_agg_tx_report));
+
+       agg_report.bf = status->bf;
+       agg_report.success = status->frm_successful;
+       agg_report.fail = status->num_mpdu_retries + (status->frm_successful ? 0 : 1);
+       agg_report.success_after_retry =
+               (status->frm_successful && status->num_mpdu_retries) ? 1 : 0;
+       agg_report.retry_limit_reached = !status->frm_successful ? 1 : 0;
+       agg_report.success_more_one_retry =
+               (status->frm_successful && (status->num_mpdu_retries > 1)) ? 1 : 0;
+       agg_report.sta_idx = cl_sta->sta_idx;
+       agg_report.bw_requested = status->bw_requested;
+
+       rate_ctrl_info.field.bw = status->bw_transmitted;
+       rate_ctrl_info.field.gi = status->gi;
+       rate_ctrl_info.field.format_mod = status->format_mod;
+       rate_ctrl_info.field.mcs_index = status->mcs_index;
+
+       cl_rate_ctrl_convert(&rate_ctrl_info);
+
+       agg_report.rate_cntrl_info = rate_ctrl_info.word;
+       cl_agg_tx_report_handler(cl_hw, cl_sta, &agg_report);
+       cl_stats_update_tx_single(cl_hw, cl_sta, &agg_report);
+}
