diff --git a/drivers/net/wireless/celeno/cl8k/key.c b/drivers/net/wireless/celeno/cl8k/key.c
new file mode 100644
index 000000000000..99821b86a795
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/key.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include "chip.h"
+#include "phy.h"
+#include "fw.h"
+#include "sta.h"
+#include "enhanced_tim.h"
+#include "key.h"
+
+#define DECRYPT_CCMPSUCCESS_FLAGS (RX_FLAG_DECRYPTED | RX_FLAG_MIC_STRIPPED)
+
+void cl_vif_key_init(struct cl_vif *cl_vif)
+{
+	INIT_LIST_HEAD(&cl_vif->key_list_head);
+}
+
+static struct cl_key_conf *cl_vif_key_find(struct cl_vif *cl_vif,
+					   struct ieee80211_key_conf *key_conf_in)
+{
+	struct cl_key_conf *key, *tmp, *out = NULL;
+
+	if (!key_conf_in)
+		return NULL;
+
+	list_for_each_entry_safe(key, tmp, &cl_vif->key_list_head, list) {
+		struct ieee80211_key_conf *key_conf = key->key_conf;
+
+		if (key_conf_in->keyidx != key_conf->keyidx)
+			continue;
+
+		out = key;
+		break;
+	}
+
+	return out;
+}
+
+static struct ieee80211_key_conf *cl_vif_key_conf_default(struct cl_vif *cl_vif)
+{
+	struct cl_key_conf *key, *tmp;
+	struct ieee80211_key_conf *out = NULL;
+
+	list_for_each_entry_safe(key, tmp, &cl_vif->key_list_head, list) {
+		if (key->key_conf->keyidx != cl_vif->key_idx_default)
+			continue;
+
+		out = key->key_conf;
+		break;
+	}
+
+	return out;
+}
+
+static int cl_vif_key_add(struct cl_vif *cl_vif, struct ieee80211_key_conf *key_conf)
+{
+	struct cl_key_conf *key = NULL, *old_key = NULL;
+
+	key = kzalloc(sizeof(*key), GFP_KERNEL);
+	if (!key)
+		return -ENOMEM;
+
+	if (!list_empty(&cl_vif->key_list_head))
+		old_key = list_first_entry(&cl_vif->key_list_head, struct cl_key_conf, list);
+
+	cl_vif->key_idx_default = old_key ? old_key->key_conf->keyidx : key_conf->keyidx;
+	key->key_conf = key_conf;
+	list_add_tail(&key->list, &cl_vif->key_list_head);
+
+	return 0;
+}
+
+static void cl_vif_key_del(struct cl_vif *cl_vif, struct ieee80211_key_conf *key_conf_in)
+{
+	struct cl_key_conf *key, *tmp;
+
+	if (!key_conf_in)
+		return;
+
+	list_for_each_entry_safe(key, tmp, &cl_vif->key_list_head, list) {
+		struct ieee80211_key_conf *key_conf = key->key_conf;
+
+		if (key_conf_in->keyidx != key_conf->keyidx)
+			continue;
+
+		list_del(&key->list);
+		kfree(key);
+	}
+
+	if (!list_empty(&cl_vif->key_list_head)) {
+		struct cl_key_conf *new_key = list_first_entry(&cl_vif->key_list_head,
+							       struct cl_key_conf, list);
+
+		cl_vif->key_idx_default = new_key->key_conf->keyidx;
+	}
+}
+
+static int cl_vif_key_check_and_add(struct cl_vif *cl_vif,
+				    struct ieee80211_key_conf *key_conf)
+{
+	struct cl_key_conf *key = cl_vif_key_find(cl_vif, key_conf);
+
+	if (key) {
+		cl_dbg_warn(cl_vif->cl_hw,
+			    "[%s] error: previous key found. delete old key and add new key\n",
+			    __func__);
+		cl_vif_key_del(cl_vif, key->key_conf);
+	}
+
+	return cl_vif_key_add(cl_vif, key_conf);
+}
+
+static inline void cl_ccmp_hdr2pn(u8 *pn, u8 *hdr)
+{
+	pn[0] = hdr[7];
+	pn[1] = hdr[6];
+	pn[2] = hdr[5];
+	pn[3] = hdr[4];
+	pn[4] = hdr[1];
+	pn[5] = hdr[0];
+}
+
+static int cl_key_validate_pn(struct cl_hw *cl_hw, struct cl_sta *cl_sta, struct sk_buff *skb)
+{
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+	int hdrlen = 0, res = 0;
+	u8 pn[IEEE80211_CCMP_PN_LEN];
+	u8 tid = 0;
+
+	hdrlen = ieee80211_hdrlen(hdr->frame_control);
+	tid = ieee80211_get_tid(hdr);
+
+	cl_ccmp_hdr2pn(pn, skb->data + hdrlen);
+	res = memcmp(pn, cl_sta->rx_pn[tid], IEEE80211_CCMP_PN_LEN);
+	if (res < 0) {
+		cl_hw->rx_info.pkt_drop_invalid_pn++;
+		return -1;
+	}
+
+	memcpy(cl_sta->rx_pn[tid], pn, IEEE80211_CCMP_PN_LEN);
+
+	return 0;
+}
+
+void cl_vif_key_deinit(struct cl_vif *cl_vif)
+{
+	struct cl_key_conf *key, *tmp;
+
+	list_for_each_entry_safe(key, tmp, &cl_vif->key_list_head, list) {
+		list_del(&key->list);
+		kfree(key);
+	}
+}
+
+static int cl_cmd_set_key(struct cl_hw *cl_hw,
+			  struct ieee80211_vif *vif,
+			  struct ieee80211_sta *sta,
+			  struct ieee80211_key_conf *key)
+{
+	int error = 0;
+	struct mm_key_add_cfm *key_add_cfm;
+	u8 cipher_suite = 0;
+
+	/* Retrieve the cipher suite selector */
+	switch (key->cipher) {
+	case WLAN_CIPHER_SUITE_WEP40:
+		cipher_suite = MAC_CIPHER_SUITE_WEP40;
+		break;
+	case WLAN_CIPHER_SUITE_WEP104:
+		cipher_suite = MAC_CIPHER_SUITE_WEP104;
+		break;
+	case WLAN_CIPHER_SUITE_TKIP:
+		cipher_suite = MAC_CIPHER_SUITE_TKIP;
+		break;
+	case WLAN_CIPHER_SUITE_CCMP:
+		cipher_suite = MAC_CIPHER_SUITE_CCMP;
+		break;
+	case WLAN_CIPHER_SUITE_GCMP:
+	case WLAN_CIPHER_SUITE_GCMP_256:
+		cipher_suite = MAC_CIPHER_SUITE_GCMP;
+		break;
+	case WLAN_CIPHER_SUITE_AES_CMAC:
+		return -EOPNOTSUPP;
+	default:
+		return -EINVAL;
+	}
+
+	error = cl_msg_tx_key_add(cl_hw, vif, sta, key, cipher_suite);
+	if (error)
+		return error;
+
+	key_add_cfm = (struct mm_key_add_cfm *)(cl_hw->msg_cfm_params[MM_KEY_ADD_CFM]);
+	if (!key_add_cfm)
+		return -ENOMSG;
+
+	if (key_add_cfm->status != 0) {
+		cl_dbg_verbose(cl_hw, "Status Error (%u)\n", key_add_cfm->status);
+		cl_msg_tx_free_cfm_params(cl_hw, MM_KEY_ADD_CFM);
+		return -EIO;
+	}
+
+	/* Save the index retrieved from firmware */
+	key->hw_key_idx = key_add_cfm->hw_key_idx;
+
+	cl_msg_tx_free_cfm_params(cl_hw, MM_KEY_ADD_CFM);
+
+	/*
+	 * Now inform mac80211 about our choices regarding header fields generation:
+	 * we let mac80211 take care of all generations
+	 */
+	key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
+	if (key->cipher == WLAN_CIPHER_SUITE_TKIP)
+		key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
+
+	if (sta) {
+		struct cl_sta *cl_sta = (struct cl_sta *)sta->drv_priv;
+
+		cl_sta->key_conf = key;
+	} else {
+		error = cl_vif_key_check_and_add((struct cl_vif *)vif->drv_priv, key);
+	}
+
+	return error;
+}
+
+static int cl_cmd_disable_key(struct cl_hw *cl_hw,
+			      struct ieee80211_vif *vif,
+			      struct ieee80211_sta *sta,
+			      struct ieee80211_key_conf *key)
+{
+	int ret = 0;
+	struct cl_sta *cl_sta = NULL;
+	struct cl_tx_queue *tx_queue = &cl_hw->tx_queues->single[HIGH_PRIORITY_QUEUE];
+
+	if (sta) {
+		cl_sta = (struct cl_sta *)sta->drv_priv;
+
+		cl_sta->key_conf = NULL;
+		cl_sta->key_disable = true;
+
+		/*
+		 * Make sure there aren't any packets in firmware before deleting the key,
+		 * otherwise they will be transmitted without encryption.
+		 */
+		cl_sta->stop_tx = true;
+		cl_single_cfm_clear_tim_bit_sta(cl_hw, cl_sta->sta_idx);
+		cl_agg_cfm_clear_tim_bit_sta(cl_hw, cl_sta);
+		cl_txq_flush_sta(cl_hw, cl_sta);
+		cl_single_cfm_poll_empty_sta(cl_hw, cl_sta->sta_idx);
+		cl_agg_cfm_poll_empty_sta(cl_hw, cl_sta);
+
+		if (!list_empty(&tx_queue->hdrs)) {
+			spin_lock_bh(&cl_hw->tx_lock_single);
+			cl_enhanced_tim_set_tx_single(cl_hw, HIGH_PRIORITY_QUEUE,
+						      tx_queue->hw_index,
+						      false, tx_queue->cl_sta,
+						      tx_queue->tid);
+			spin_unlock_bh(&cl_hw->tx_lock_single);
+		}
+	} else {
+		cl_vif_key_del((struct cl_vif *)vif->drv_priv, key);
+	}
+
+	ret = cl_msg_tx_key_del(cl_hw, key->hw_key_idx);
+
+	if (cl_sta)
+		cl_sta->stop_tx = false;
+
+	return ret;
+}
+
+int cl_key_set(struct cl_hw *cl_hw,
+	       enum set_key_cmd cmd,
+	       struct ieee80211_vif *vif,
+	       struct ieee80211_sta *sta,
+	       struct ieee80211_key_conf *key)
+{
+	int error = 0;
+
+	switch (cmd) {
+	case SET_KEY:
+		error = cl_cmd_set_key(cl_hw, vif, sta, key);
+		break;
+
+	case DISABLE_KEY:
+		error = cl_cmd_disable_key(cl_hw, vif, sta, key);
+		break;
+
+	default:
+		error = -EINVAL;
+		break;
+	}
+
+	return error;
+}
+
+struct ieee80211_key_conf *cl_key_get(struct cl_sta *cl_sta)
+{
+	if (cl_sta->key_conf)
+		return cl_sta->key_conf;
+
+	if (cl_sta->cl_vif)
+		return cl_vif_key_conf_default(cl_sta->cl_vif);
+
+	return NULL;
+}
+
+bool cl_key_is_cipher_ccmp_gcmp(struct ieee80211_key_conf *keyconf)
+{
+	u32 cipher;
+
+	if (!keyconf)
+		return false;
+
+	cipher = keyconf->cipher;
+
+	return ((cipher == WLAN_CIPHER_SUITE_CCMP) ||
+		(cipher == WLAN_CIPHER_SUITE_GCMP) ||
+		(cipher == WLAN_CIPHER_SUITE_GCMP_256));
+}
+
+void cl_key_ccmp_gcmp_pn_to_hdr(u8 *hdr, u64 pn, int key_id)
+{
+	hdr[0] = pn;
+	hdr[1] = pn >> 8;
+	hdr[2] = 0;
+	hdr[3] = 0x20 | (key_id << 6);
+	hdr[4] = pn >> 16;
+	hdr[5] = pn >> 24;
+	hdr[6] = pn >> 32;
+	hdr[7] = pn >> 40;
+}
+
+u8 cl_key_get_cipher_len(struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_key_conf *key_conf = tx_info->control.hw_key;
+
+	if (key_conf) {
+		switch (key_conf->cipher) {
+		case WLAN_CIPHER_SUITE_WEP40:
+		case WLAN_CIPHER_SUITE_WEP104:
+			return IEEE80211_WEP_IV_LEN;
+		case WLAN_CIPHER_SUITE_TKIP:
+			return  IEEE80211_TKIP_IV_LEN;
+		case WLAN_CIPHER_SUITE_CCMP:
+			return  IEEE80211_CCMP_HDR_LEN;
+		case WLAN_CIPHER_SUITE_CCMP_256:
+			return  IEEE80211_CCMP_256_HDR_LEN;
+		case WLAN_CIPHER_SUITE_GCMP:
+		case WLAN_CIPHER_SUITE_GCMP_256:
+			return  IEEE80211_GCMP_HDR_LEN;
+		}
+	}
+
+	return 0;
+}
+
+int cl_key_handle_pn_validation(struct cl_hw *cl_hw, struct sk_buff *skb,
+				struct cl_sta *cl_sta)
+{
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+
+	if (!ieee80211_is_data(hdr->frame_control) ||
+	    ieee80211_is_frag(hdr))
+		return CL_PN_VALID_STATE_NOT_NEEDED;
+
+	if (!(status->flag & DECRYPT_CCMPSUCCESS_FLAGS))
+		return CL_PN_VALID_STATE_NOT_NEEDED;
+
+	if (!cl_sta)
+		return CL_PN_VALID_STATE_NOT_NEEDED;
+
+	if (cl_key_validate_pn(cl_hw, cl_sta, skb))
+		return CL_PN_VALID_STATE_FAILED;
+
+	status = IEEE80211_SKB_RXCB(skb);
+	status->flag |= RX_FLAG_PN_VALIDATED;
+
+	return CL_PN_VALID_STATE_SUCCESS;
+}
