diff --git a/drivers/net/wireless/celeno/cl8k/ampdu.c b/drivers/net/wireless/celeno/cl8k/ampdu.c
new file mode 100644
index 000000000000..7116995b5059
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/ampdu.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "ampdu.h"
+#include "hw.h"
+#include "fw/msg_tx.h"
+#include "tx/tx_queue.h"
+#include "tx/agg_cfm.h"
+#include "recovery.h"
+#include "tx/tx_inject.h"
+#include "tx/baw.h"
+#include "utils/math.h"
+#include "band.h"
+#include "rx/rx_reorder.h"
+#ifdef CONFIG_CL_PCIE
+#include "bus/pci/ipc.h"
+#endif
+
+int cl_ampdu_rx_start(struct cl_hw *cl_hw,
+                     struct cl_sta *cl_sta,
+                     u16 tid,
+                     u16 ssn,
+                     u16 buf_size)
+{
+       /* @IEEE80211_AMPDU_RX_START: start RX aggregation */
+       if (!cl_hw->conf->ci_agg_rx)
+               return -EOPNOTSUPP;
+
+       cl_dbg_trace(cl_hw, "sta_idx [%u] tid [%u]\n", cl_sta->sta_idx, tid);
+
+       buf_size = min(buf_size, cl_hw->conf->ce_max_agg_size_rx);
+
+       if (cl_hw->conf->ci_fast_rx_en)
+               cl_rx_reorder_init(cl_hw, cl_sta, tid, buf_size);
+
+       cl_msg_tx_ba_add(cl_hw, BA_AGMT_RX, cl_sta->sta_idx, tid, buf_size, ssn);
+
+       return 0;
+}
+
+void cl_ampdu_rx_stop(struct cl_hw *cl_hw,
+                     struct cl_sta *cl_sta,
+                     u16 tid)
+{
+       /* @IEEE80211_AMPDU_RX_STOP: stop RX aggregation */
+       cl_dbg_trace(cl_hw, "sta_idx [%u] tid [%u]\n", cl_sta->sta_idx, tid);
+
+       if (cl_hw->conf->ci_fast_rx_en)
+               cl_rx_reorder_close(cl_sta, tid);
+}
+
+int cl_ampdu_tx_start(struct cl_hw *cl_hw,
+                     struct ieee80211_vif *vif,
+                     struct cl_sta *cl_sta,
+                     u16 tid,
+                     u16 ssn)
+{
+       /* @IEEE80211_AMPDU_TX_START: start TX aggregation */
+       struct mm_available_ba_txq_cfm *cfm = NULL;
+       int ret = 0;
+
+       if (!ieee80211_hw_check(cl_hw->hw, AMPDU_AGGREGATION) || !cl_hw->conf->ci_agg_tx)
+               return -EOPNOTSUPP;
+
+       if (!cl_txq_is_agg_available(cl_hw)) {
+               cl_dbg_warn(cl_hw, "No free aggregation queue for sta_idx [%u] tid [%u]\n",
+                           cl_sta->sta_idx, tid);
+               return -1;
+       }
+
+       ret = cl_msg_tx_available_ba_txq(cl_hw, cl_sta->sta_idx, tid);
+       if (ret)
+               return ret;
+
+       /* Read FW confirm message */
+       cfm = (struct mm_available_ba_txq_cfm *)(cl_hw->msg_cfm_params[MM_AVAILABLE_BA_TXQ_CFM]);
+       if (!cfm)
+               return -ENOMSG;
+
+       /* Check if status is valid */
+       if (cfm->status != BA_TXQUEUE_INVALID && cfm->status != BA_TXQUEUE_VALID) {
+               cl_dbg_verbose(cl_hw, "Status Error (%u)\n", cfm->status);
+               cl_msg_tx_free_cfm_params(cl_hw, MM_AVAILABLE_BA_TXQ_CFM);
+               return -EIO;
+       }
+
+       if (cfm->status == BA_TXQUEUE_INVALID) {
+               cl_dbg_warn(cl_hw, "BA_TXQUEUE_INVALID - sta_idx [%u] tid [%u]\n",
+                           cfm->sta_idx, cfm->tid);
+               cl_msg_tx_free_cfm_params(cl_hw, MM_AVAILABLE_BA_TXQ_CFM);
+               return -1;
+       }
+
+       cl_msg_tx_free_cfm_params(cl_hw, MM_AVAILABLE_BA_TXQ_CFM);
+       cl_txq_agg_request_add(cl_hw, cl_sta->sta_idx, tid);
+       cl_baw_start(&cl_sta->baws[tid], ssn);
+
+       /* Mandatory callback once setup preparations are done at lower level */
+       ieee80211_start_tx_ba_cb_irqsafe(vif, cl_sta->addr, tid);
+
+       return 0;
+}
+
+int cl_ampdu_tx_operational(struct cl_hw *cl_hw,
+                           struct cl_sta *cl_sta,
+                           u16 tid,
+                           u16 buf_size,
+                           bool amsdu_supported)
+{
+       /* @IEEE80211_AMPDU_TX_OPERATIONAL: TX aggregation has become operational */
+       struct mm_ba_add_cfm *cfm = NULL;
+       struct cl_baw *baw = &cl_sta->baws[tid];
+       u16 ssn = baw->ssn;
+       int ret = 0;
+
+       buf_size = min(buf_size, cl_hw->conf->ce_max_agg_size_tx);
+
+       /* Send MM_BA_ADD_TX_REQ message to firmware */
+       ret = cl_msg_tx_ba_add(cl_hw, BA_AGMT_TX, cl_sta->sta_idx, tid, buf_size, ssn);
+       if (ret)
+               return ret;
+
+       /* Handle message confirmation */
+       cfm = (struct mm_ba_add_cfm *)(cl_hw->msg_cfm_params[MM_BA_ADD_TX_CFM]);
+       if (!cfm)
+               return -ENOMSG;
+
+       if (cfm->status != BA_AGMT_ESTABLISHED) {
+               cl_dbg_verbose(cl_hw, "Status Error (%u)\n", cfm->status);
+               cl_msg_tx_free_cfm_params(cl_hw, MM_BA_ADD_TX_CFM);
+               cl_txq_agg_request_del(cl_hw, cl_sta->sta_idx, tid);
+               return -EIO;
+       }
+
+       cl_baw_operational(cl_hw, baw, cfm->agg_idx, amsdu_supported);
+       cl_agg_cfm_set_ssn(cl_hw, ssn, cfm->agg_idx);
+#ifdef CONFIG_CL_PCIE
+       cl_hw->ipc_env->ring_indices_elem->indices->new_ssn_idx[cfm->agg_idx] = cpu_to_le16(ssn);
+#endif
+
+       if (amsdu_supported)
+               cl_tx_amsdu_set_max_len(cl_hw, cl_sta, tid);
+       else
+               cl_dbg_trace(cl_hw, "AMSDU not supported - sta_idx=%u\n", cl_sta->sta_idx);
+
+       cl_txq_agg_alloc(cl_hw, cl_sta, cfm, buf_size);
+       cl_msg_tx_free_cfm_params(cl_hw, MM_BA_ADD_TX_CFM);
+
+       return 0;
+}
+
+void _cl_ampdu_tx_stop(struct cl_hw *cl_hw,
+                      struct cl_tx_queue *tx_queue,
+                      struct cl_sta *cl_sta,
+                      u8 tid)
+{
+       struct mm_ba_del_cfm *cfm = NULL;
+       u8 fw_agg_idx = tx_queue->index;
+
+       if (cl_recovery_in_progress(cl_hw))
+               goto out;
+
+       /*
+        * TX stop flow:
+        * 1) Flush TX queues - done in cl_ampdu_tx_stop()
+        * 2) Poll confirmation queue and clear enhanced TIM
+        * 3) Send MM_STA_DEL_REQ message to firmware
+        * 4) Poll again confirmation and flush confirmation queue
+        * 5) Reset write index
+        */
+       cl_agg_cfm_poll_empty(cl_hw, fw_agg_idx, false);
+
+       /* Send MM_BA_DEL_REQ message to firmware */
+       if (cl_msg_tx_ba_del(cl_hw, cl_sta->sta_idx, tid))
+               goto out;
+
+       cfm = (struct mm_ba_del_cfm *)(cl_hw->msg_cfm_params[MM_BA_DEL_CFM]);
+       if (!cfm) {
+               cl_dbg_err(cl_hw, "Unable to fetch CFM\n");
+               goto out;
+       }
+
+       /* Check confirmation status */
+       if (cfm->status != BA_AGMT_DELETED && cfm->status != BA_AGMT_DOES_NOT_EXIST)
+               cl_dbg_verbose(cl_hw, "Status Error (%u)\n", cfm->status);
+
+       cl_msg_tx_free_cfm_params(cl_hw, MM_BA_DEL_CFM);
+
+out:
+       cl_agg_cfm_poll_empty(cl_hw, fw_agg_idx, true);
+       cl_txq_agg_free(cl_hw, tx_queue, cl_sta, tid);
+
+#ifdef CONFIG_CL_PCIE
+       /* Reset the synchronization counters between the fw and the IPC layer */
+       cl_hw->ipc_env->ring_indices_elem->indices->txdesc_write_idx.agg[fw_agg_idx] = 0;
+#endif
+}
+
+int cl_ampdu_tx_stop(struct cl_hw *cl_hw,
+                    struct ieee80211_vif *vif,
+                    enum ieee80211_ampdu_mlme_action action,
+                    struct cl_sta *cl_sta,
+                    u16 tid)
+{
+       /*
+        * @IEEE80211_AMPDU_TX_STOP_CONT: stop TX aggregation but continue transmitting
+        *      queued packets, now unaggregated. After all packets are transmitted the
+        *      driver has to call ieee80211_stop_tx_ba_cb_irqsafe().
+        * @IEEE80211_AMPDU_TX_STOP_FLUSH: stop TX aggregation and flush all packets,
+        *      called when the station is removed. There's no need or reason to call
+        *      ieee80211_stop_tx_ba_cb_irqsafe() in this case as mac80211 assumes the
+        *      session is gone and removes the station.
+        * @IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: called when TX aggregation is stopped
+        *      but the driver hasn't called ieee80211_stop_tx_ba_cb_irqsafe() yet and
+        *      now the connection is dropped and the station will be removed. Drivers
+        *      should clean up and drop remaining packets when this is called.
+        */
+
+       /* !!!  Comment in agg-tx.c, ___ieee80211_stop_tx_ba_session():  !!!
+        * !!!  HW shall not deny going back to legacy                   !!!
+        * !!!  Therefore cl_ampdu_tx_stop() always returns 0            !!!
+        */
+
+       struct cl_tx_queue *tx_queue = cl_sta->agg_tx_queues[tid];
+       struct cl_baw *baw = &cl_sta->baws[tid];
+
+       spin_lock_bh(&cl_hw->tx_lock_agg);
+
+       cl_baw_stop(baw);
+       cl_txq_agg_request_del(cl_hw, cl_sta->sta_idx, tid);
+
+       /* Check if BA session exist */
+       if (!tx_queue) {
+               spin_unlock_bh(&cl_hw->tx_lock_agg);
+
+               if (!cl_recovery_in_progress(cl_hw))
+                       cl_dbg_warn(cl_hw, "Queue doesn't exist - sta_idx [%u] tid [%u]\n",
+                                   cl_sta->sta_idx, tid);
+
+               goto out;
+       }
+
+       if (action == IEEE80211_AMPDU_TX_STOP_CONT) {
+               /*
+                * The order of flow here is very important here to avoid reorder problem!
+                * 1) Take single lock to block single traffic
+                * 2) Stop agg traffic.
+                * 3) Transfer agg-to-single and push all skbs from agg queue to single queue.
+                * 4) Transfer BA window pending queue to single queue.
+                * 5) Release single lock
+                */
+               spin_lock_bh(&cl_hw->tx_lock_single);
+               cl_txq_agg_stop(cl_sta, tid);
+               cl_txq_transfer_agg_to_single(cl_hw, tx_queue);
+               cl_baw_pending_to_single(cl_hw, cl_sta, baw);
+               spin_unlock_bh(&cl_hw->tx_lock_single);
+       } else {
+               cl_txq_agg_stop(cl_sta, tid);
+               cl_txq_flush(cl_hw, tx_queue);
+               cl_baw_pending_purge(baw);
+       }
+
+       cl_tx_amsdu_anchor_reset(&cl_sta->amsdu_anchor[tid]);
+
+       spin_unlock_bh(&cl_hw->tx_lock_agg);
+
+       _cl_ampdu_tx_stop(cl_hw, tx_queue, cl_sta, tid);
+
+out:
+       /* Mandatory callback once we've made our own tear down ops */
+       if (action != IEEE80211_AMPDU_TX_STOP_FLUSH)
+               ieee80211_stop_tx_ba_cb_irqsafe(vif, cl_sta->addr, tid);
+
+       return 0;
+}
+
+#define HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_OFFSET 3
+#define HE_EXP_MAX 22 /* 2 ^ 22 = 4194304 < 6500631 */
+
+static void _cl_ampdu_size_exp(struct ieee80211_sta *sta,
+                              u8 *ampdu_exp_he,
+                              u8 *ampdu_exp_vht,
+                              u8 *ampdu_exp_ht)
+{
+       struct ieee80211_sta_he_cap *he_cap = &sta->he_cap;
+       u8 mac_cap_info3 = he_cap->he_cap_elem.mac_cap_info[3];
+       u8 he_exp;
+
+       if (sta->ht_cap.ht_supported)
+               *ampdu_exp_ht = IEEE80211_HT_MAX_AMPDU_FACTOR + sta->ht_cap.ampdu_factor;
+
+       if (sta->vht_cap.vht_supported) {
+               u32 vht_exp = (sta->vht_cap.cap &
+                              IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK) >>
+                       IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT;
+
+               *ampdu_exp_vht = IEEE80211_HT_MAX_AMPDU_FACTOR + vht_exp;
+       }
+
+       he_exp = (mac_cap_info3 & IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_MASK) >>
+               HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_OFFSET;
+
+       if (sta->vht_cap.vht_supported) {
+               if (he_exp)
+                       *ampdu_exp_he = min(IEEE80211_HE_VHT_MAX_AMPDU_FACTOR + he_exp, HE_EXP_MAX);
+               else
+                       *ampdu_exp_he = *ampdu_exp_vht;
+       } else if (sta->ht_cap.ht_supported) {
+               if (he_exp)
+                       *ampdu_exp_he = IEEE80211_HE_HT_MAX_AMPDU_FACTOR + he_exp;
+               else
+                       *ampdu_exp_he = *ampdu_exp_ht;
+       }
+}
+
+static void _cl_ampdu_size_exp_6g(struct ieee80211_sta *sta, u8 *ampdu_exp_he)
+{
+       u8 mac_cap_info3 = sta->he_cap.he_cap_elem.mac_cap_info[3];
+       u8 he_exp_ext = (mac_cap_info3 & IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_MASK) >>
+               HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_OFFSET;
+
+       if (he_exp_ext) {
+               *ampdu_exp_he = min(IEEE80211_HE_VHT_MAX_AMPDU_FACTOR + he_exp_ext, HE_EXP_MAX);
+       } else {
+               struct ieee80211_he_6ghz_capa *he_6g_cap = &sta->he_6ghz_capa;
+               u8 he_exp_6ghz = (he_6g_cap->capa & HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP_MASK) >>
+                       HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP_OFFSET;
+
+               *ampdu_exp_he = min(HE_6GHZ_CAP_MAX_AMPDU_LEN_FACTOR + he_exp_6ghz, HE_EXP_MAX);
+       }
+}
+
+void cl_ampdu_size_exp(struct cl_hw *cl_hw, struct ieee80211_sta *sta,
+                      u8 *ampdu_exp_he, u8 *ampdu_exp_vht, u8 *ampdu_exp_ht)
+{
+       if (cl_band_is_6g(cl_hw))
+               _cl_ampdu_size_exp_6g(sta, ampdu_exp_he);
+       else
+               _cl_ampdu_size_exp(sta, ampdu_exp_he, ampdu_exp_vht, ampdu_exp_ht);
+
+       cl_dbg_info(cl_hw, "ampdu_size_exp: he = %u, vht = %u, ht = %u\n",
+                   *ampdu_exp_he, *ampdu_exp_vht, *ampdu_exp_ht);
+}
+
