diff --git a/drivers/net/wireless/celeno/cl8k/hw.c b/drivers/net/wireless/celeno/cl8k/hw.c
new file mode 100644
index 000000000000..834622549f9a
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/hw.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/spinlock.h>
+
+#include "tx.h"
+#include "rates.h"
+#include "reg/reg_access.h"
+#include "recovery.h"
+#include "hw.h"
+
+static void cl_hw_init_tcv0(struct cl_hw *cl_hw)
+{
+	struct cl_controller_reg *controller_reg = &cl_hw->controller_reg;
+
+	cl_hw->fw_dst_kern_id = KERN_LMAC;
+	cl_hw->fw_prefix = 'l';
+
+	controller_reg->breset = LMAC_BRESET;
+	controller_reg->debug_enable = LMAC_DEBUG_ENABLE;
+	controller_reg->dreset = LMAC_DRESET;
+	controller_reg->ocd_halt_on_reset = LMAC_OCD_HALT_ON_RESET;
+	controller_reg->run_stall = LMAC_RUN_STALL;
+
+	cl_hw->mac_hw_regs_offset = 0;
+	cl_hw->phy_regs_offset = REG_PHY_LMAC_OFFSET;
+}
+
+static void cl_hw_init_tcv1(struct cl_hw *cl_hw)
+{
+	struct cl_controller_reg *controller_reg = &cl_hw->controller_reg;
+
+	cl_hw->fw_dst_kern_id = KERN_SMAC;
+	cl_hw->fw_prefix = 's';
+
+	controller_reg->breset = SMAC_BRESET;
+	controller_reg->debug_enable = SMAC_DEBUG_ENABLE;
+	controller_reg->dreset = SMAC_DRESET;
+	controller_reg->ocd_halt_on_reset = SMAC_OCD_HALT_ON_RESET;
+	controller_reg->run_stall = SMAC_RUN_STALL;
+
+	cl_hw->mac_hw_regs_offset = REG_MAC_HW_SMAC_OFFSET;
+	cl_hw->phy_regs_offset = REG_PHY_SMAC_OFFSET;
+}
+
+static void cl_hw_set_first_last_riu_chains(struct cl_hw *cl_hw, u8 first_ant, u8 last_ant)
+{
+	u8 ant, chain;
+	u8 min_chain = U8_MAX;
+	u8 max_chain = 0;
+
+	for (ant = first_ant; ant <= last_ant; ant++) {
+		chain = cl_hw_ant_to_riu_chain(cl_hw, ant);
+
+		if (chain < min_chain)
+			min_chain = chain;
+
+		if (chain > max_chain)
+			max_chain = chain;
+	}
+
+	cl_hw->first_riu_chain = min_chain;
+	cl_hw->last_riu_chain = max_chain;
+}
+
+void cl_hw_init(struct cl_chip *chip, struct cl_hw *cl_hw, u8 tcv_idx)
+{
+	write_lock(&chip->cl_hw_lock);
+	chip->cl_hw_lut[tcv_idx] = cl_hw;
+	write_unlock(&chip->cl_hw_lock);
+
+	if (tcv_idx == TCV0)
+		cl_hw_init_tcv0(cl_hw);
+	else
+		cl_hw_init_tcv1(cl_hw);
+}
+
+void cl_hw_deinit(struct cl_hw *cl_hw, u8 tcv_idx)
+{
+	struct cl_chip *chip = cl_hw->chip;
+
+	write_lock(&chip->cl_hw_lock);
+	chip->cl_hw_lut[tcv_idx] = NULL;
+	write_unlock(&chip->cl_hw_lock);
+}
+
+struct cl_hw *cl_hw_other_tcv(struct cl_hw *cl_hw)
+{
+	/* This function must be called after read lock is taken */
+	return cl_hw->chip->cl_hw_lut[1 - cl_hw->tcv_idx];
+}
+
+bool cl_hw_is_tcv0(struct cl_hw *cl_hw)
+{
+	return (cl_hw->tcv_idx == TCV0);
+}
+
+bool cl_hw_is_tcv1(struct cl_hw *cl_hw)
+{
+	return (cl_hw->tcv_idx == TCV1);
+}
+
+bool cl_hw_is_first_tcv(struct cl_hw *cl_hw)
+{
+	if (cl_hw_is_tcv0(cl_hw))
+		return true;
+	else
+		return cl_chip_is_only_tcv1_enabled(cl_hw->chip);
+}
+
+int cl_hw_set_antennas(struct cl_hw *cl_hw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	u8 last_ant;
+	u8 ant_shift = cl_hw_ant_shift(cl_hw);
+
+	/* Set num_antennas and max_antennas + masks for both. */
+	switch (chip->fem.wiring_id) {
+	case FEM_WIRING_0_TCV0_6_TCV1_6:
+	case FEM_WIRING_3_TCV0_2_ELASTIC_4_TCV1_2:
+		cl_hw->max_antennas = 6;
+		break;
+	case FEM_WIRING_24_TCV0_6_TCV1_4:
+		cl_hw->max_antennas = cl_hw_is_tcv0(cl_hw) ? 6 : 4;
+		break;
+	case FEM_WIRING_7_TCV0_4_TCV1_4:
+	case FEM_WIRING_9_TCV0_4_TCV1_4:
+	case FEM_WIRING_10_TCV0_4_TCV1_4:
+	case FEM_WIRING_11_TCV0_4_TCV1_4_RX_ONLY:
+	case FEM_WIRING_12_TCV0_4_TCV1_4_RX_ONLY:
+	case FEM_WIRING_15_CHAMELEON_4TX_4RX:
+	case FEM_WIRING_18_TCV0_4_TCV1_4:
+	case FEM_WIRING_23_TCV0_4_TCV1_4:
+	case FEM_WIRING_33_TCV0_4_TCV1_4:
+		cl_hw->max_antennas = 4;
+		break;
+	case FEM_WIRING_13_SENSING_4RX_2TX:
+	case FEM_WIRING_14_SENSING_4TX_2RX:
+	case FEM_WIRING_25_TCV0_4_TCV1_2_MODE_0:
+	case FEM_WIRING_26_TCV0_4_TCV1_2_MODE_1:
+	case FEM_WIRING_28_TCV0_4_TCV1_2:
+	case FEM_WIRING_29_TCV0_4_TCV1_2:
+		cl_hw->max_antennas = cl_hw_is_tcv0(cl_hw) ? 4 : 2;
+		break;
+	case FEM_WIRING_30_TCV0_4_TCV1_2:
+			cl_hw->max_antennas = cl_hw_is_tcv0(cl_hw) ? 4 : 2;
+		break;
+	case FEM_WIRING_17_TCV0_4_TCV1_0:
+	case FEM_WIRING_32_TCV0_4_TCV1_0:
+		cl_hw->max_antennas = cl_hw_is_tcv0(cl_hw) ? 4 : 0;
+		break;
+	case FEM_WIRING_16_TCV0_2_TCV1_2:
+	case FEM_WIRING_20_TCV0_2_TCV1_2:
+		cl_hw->max_antennas = 2;
+		break;
+	case FEM_WIRING_27_TCV0_2_TCV1_1:
+	case FEM_WIRING_31_TCV0_2_TCV1_1:
+		cl_hw->max_antennas = cl_hw_is_tcv0(cl_hw) ? 2 : 1;
+		break;
+	default:
+		if (chip->conf->ce_production_mode)
+			cl_hw->max_antennas = chip->max_antennas;
+		else
+			return -1;
+		break;
+	}
+
+	cl_hw->num_antennas = cl_hw->conf->ce_num_antennas;
+	cl_hw->mask_num_antennas = ANT_MASK(cl_hw->num_antennas) << ant_shift;
+	cl_hw->first_ant = ant_shift;
+	last_ant = max_t(s8, cl_hw->num_antennas + ant_shift - 1, 0);
+
+	cl_hw_set_first_last_riu_chains(cl_hw, cl_hw->first_ant, last_ant);
+
+	cl_dbg_trace(cl_hw, "num_antennas = %u, max_antennas = %u\n",
+		     cl_hw->num_antennas, cl_hw->max_antennas);
+
+	if (cl_hw->num_antennas > cl_hw->max_antennas) {
+		CL_DBG_ERROR(cl_hw, "num_antennas (%u) > max_antennas (%u)\n",
+			     cl_hw->num_antennas, cl_hw->max_antennas);
+		return -1;
+	}
+
+	return 0;
+}
+
+u8 cl_hw_ant_shift(struct cl_hw *cl_hw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+
+	/* CL808x uses antennas 0 - 3 for both bands */
+	if (cl_chip_is_8ant(chip))
+		return 0;
+
+	if (cl_chip_is_6ant(chip)) {
+		/*
+		 * CL806X on wiring id 24 uses antennas 0-3 for TCV0 and antennas 2-3 for TCV1.
+		 * When only 1 antenna is configured - ant 3 is used.
+		 */
+		if (cl_hw_is_tcv1(cl_hw) && chip->fem.wiring_id == FEM_WIRING_24_TCV0_6_TCV1_4)
+			return (4 - cl_hw->conf->ce_num_antennas);
+
+		/* Other CL806X use antennas 0 - 3 for TCV0 and antennas 0-1 for TCV1 */
+		return 0;
+	}
+
+	/*
+	 * CL8046 uses chains 0 - 3 for TCV0 and no chain for TCV1.
+	 * Wiring ID 31 uses chains 2-3 for TCV0 and chain 4 for TCV1.
+	 */
+	if (cl_chip_is_6g(chip)) {
+		if (chip->fem.wiring_id == FEM_WIRING_31_TCV0_2_TCV1_1)
+			return cl_hw_is_tcv0(cl_hw) ? 2 : 4;
+
+		return 0;
+	}
+
+	/* CL8040 uses chains 1 - 2 for TCV0 and TCV1 */
+	return 1;
+}
+
+u8 cl_hw_ant_to_riu_chain(struct cl_hw *cl_hw, u8 ant)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	u8 res = ant;
+
+	if (cl_hw_is_tcv0(cl_hw)) {
+		if (chip->fem.wiring_id == FEM_WIRING_31_TCV0_2_TCV1_1)
+			return ((ant - 2) & 0x3);
+
+		if (chip->fem.wiring_id == FEM_WIRING_30_TCV0_4_TCV1_2) {
+			if (ant == 2)
+				return 3;
+			if (ant == 3)
+				return 2;
+
+			return ant;
+		}
+
+		return ant;
+	}
+
+	switch (chip->fem.wiring_id) {
+	case FEM_WIRING_0_TCV0_6_TCV1_6:
+	case FEM_WIRING_3_TCV0_2_ELASTIC_4_TCV1_2:
+	case FEM_WIRING_17_TCV0_4_TCV1_0:
+	case FEM_WIRING_32_TCV0_4_TCV1_0:
+		return ant;
+	case FEM_WIRING_7_TCV0_4_TCV1_4:
+	case FEM_WIRING_13_SENSING_4RX_2TX:
+	case FEM_WIRING_14_SENSING_4TX_2RX:
+	case FEM_WIRING_15_CHAMELEON_4TX_4RX:
+	case FEM_WIRING_16_TCV0_2_TCV1_2:
+	case FEM_WIRING_18_TCV0_4_TCV1_4:
+	case FEM_WIRING_20_TCV0_2_TCV1_2:
+	case FEM_WIRING_23_TCV0_4_TCV1_4:
+	case FEM_WIRING_25_TCV0_4_TCV1_2_MODE_0:
+	case FEM_WIRING_26_TCV0_4_TCV1_2_MODE_1:
+	case FEM_WIRING_28_TCV0_4_TCV1_2:
+	case FEM_WIRING_29_TCV0_4_TCV1_2:
+	case FEM_WIRING_30_TCV0_4_TCV1_2:
+	case FEM_WIRING_33_TCV0_4_TCV1_4:
+		res = (3 - ant);
+		break;
+	case FEM_WIRING_9_TCV0_4_TCV1_4:
+	case FEM_WIRING_10_TCV0_4_TCV1_4:
+	case FEM_WIRING_11_TCV0_4_TCV1_4_RX_ONLY:
+	case FEM_WIRING_12_TCV0_4_TCV1_4_RX_ONLY:
+	case FEM_WIRING_24_TCV0_6_TCV1_4:
+		res = (5 - ant);
+		break;
+	case FEM_WIRING_27_TCV0_2_TCV1_1:
+		res = (2 - ant);
+		break;
+	case FEM_WIRING_31_TCV0_2_TCV1_1:
+		res = (ant == 2 ? 4 : (ant == 4 ? 2 : 0));
+		break;
+	default:
+		break;
+	}
+
+	/* Verify that the returned value is valid */
+	return res & ANT_MASK(MAX_ANTENNAS);
+}
+
+u8 cl_hw_ant_mask_to_riu_chain_mask(struct cl_hw *cl_hw, u8 ant_mask)
+{
+	u8 ant, riu_chain, riu_chain_mask = 0x0;
+
+	for (ant = 0; ant < MAX_ANTENNAS; ant++) {
+		if (ant_mask & BIT(ant)) {
+			riu_chain = cl_hw_ant_to_riu_chain(cl_hw, ant);
+			riu_chain_mask |= BIT(riu_chain);
+		}
+	}
+
+	return riu_chain_mask;
+}
+
+bool cl_hw_is_prod_or_listener(struct cl_hw *cl_hw)
+{
+	/* TODO: Move ce_listener_en to cl_chip */
+	if (cl_hw->chip->conf->ce_production_mode ||
+	    (cl_hw->conf && cl_hw->conf->ce_listener_en))
+		return true;
+
+	return false;
+}
+
+#define ASSERT_PATTERN 0xC0DEDEAD
+
+/*
+ * Function will take time stamp for each hw error indication.
+ * when time diff between each error is less than ce_hw_assert_time_max
+ * cl_hw_restart work will be scheduled
+ */
+static bool cl_hw_assert_storm_detect(struct cl_hw *cl_hw)
+{
+	struct cl_hw_asserts_info *assert_info = &cl_hw->assert_info;
+	u8 idx = assert_info->index % CL_MIN_ASSERT_CNT;
+	/* Get the oldest assert timestamp. */
+	u8 prev_idx = (assert_info->index + 1) % CL_MIN_ASSERT_CNT;
+	bool is_hw_restarted = false;
+
+	if (assert_info->restart_sched) {
+		is_hw_restarted = true;
+	} else {
+		/* Take time stamp of the assert */
+		assert_info->timestamp[idx] = jiffies;
+		assert_info->index++;
+		/* In case hw assert time diff is less than CL_HW_ASSERT_TIME_MAX, restart hw. */
+		if (assert_info->index > CL_MIN_ASSERT_CNT) {
+			unsigned long time_diff_jiffies =
+				assert_info->timestamp[idx] - assert_info->timestamp[prev_idx];
+			unsigned int time_diff_msecs = jiffies_to_msecs(time_diff_jiffies);
+
+			if (time_diff_msecs < cl_hw->conf->ce_hw_assert_time_max) {
+				assert_info->index = 0;
+
+				cl_dbg_err(cl_hw, "Assert storm detect (time_diff = %u)\n",
+					   time_diff_msecs);
+				cl_recovery_start(cl_hw, RECOVERY_ASSERT_STORM_DETECT);
+
+				is_hw_restarted = true;
+			}
+		}
+	}
+
+	return is_hw_restarted;
+}
+
+void cl_hw_assert_info_init(struct cl_hw *cl_hw)
+{
+	memset(&cl_hw->assert_info, 0, sizeof(cl_hw->assert_info));
+}
+
+static void cl_recovery_no_dump_start(struct cl_hw *cl_hw, enum recovery_reason reason)
+{
+	cl_dbg_trace(cl_hw, "Starting recovery due to assert no dump\n");
+	cl_recovery_start(cl_hw, reason);
+}
+
+void cl_hw_assert_print(struct cl_hw *cl_hw, struct cl_ipc_e2a_msg *msg)
+{
+	struct dbg_print_ind *ind = (struct dbg_print_ind *)msg->param;
+	const char *assert_string;
+	u32 assert_pattern;
+	u16 file_id = le16_to_cpu(ind->file_id);
+	u16 line = le16_to_cpu(ind->line);
+	u16 has_param = le16_to_cpu(ind->has_param);
+	u32 param = le32_to_cpu(ind->param);
+
+	/* If ce_hw_assert_time_max is 0, HW assert storm detection is disabled */
+	if (cl_hw->conf->ce_hw_assert_time_max)
+		if (cl_hw_assert_storm_detect(cl_hw))
+			return;
+
+	assert_string = cl_dbgfile_get_msg_txt(&cl_hw->dbg_data, file_id, line);
+
+	/* Avoid printing background asserts */
+	if (cl_hw->conf->ce_bg_assert_print || !strstr(assert_string, "ASSERT_REC")) {
+		/* Print ASSERT message with file_id, line, [parameter] */
+		if (has_param)
+			cl_dbg_err(cl_hw, "ASSERT_TCV%u @ FILE=%u LINE=%u param=0x%08X\n",
+				   cl_hw->idx, file_id, line, param);
+		else
+			cl_dbg_err(cl_hw, "ASSERT_TCV%u @ file=%u line=%u\n",
+				   cl_hw->idx, file_id, line);
+
+		if (!assert_string)
+			assert_string = "ASSERT STRING NOT FOUND";
+
+		/* TODO:length of single print may be limited,consider print long msgs by pieces */
+		cl_dbg_err(cl_hw, "%.500s\n", assert_string);
+	}
+
+	assert_pattern = ioread32((void __iomem *)&cl_hw->ipc_env->shared->assert_pattern);
+
+	/* Reset ASSERT pattern if needed (in order to prevent assert prints loop) */
+	if (assert_pattern == ASSERT_PATTERN)
+		iowrite32(0, (void __iomem *)&cl_hw->ipc_env->shared->assert_pattern);
+
+	if (!ind->err_no_dump)
+		return;
+
+	cl_recovery_no_dump_start(cl_hw, RECOVERY_UNRECOVERABLE_ASSERT_NO_DUMP);
+}
+
+void cl_hw_assert_check(struct cl_hw *cl_hw)
+{
+	struct cl_ipc_shared_env __iomem *shared_env = cl_hw->ipc_env->shared;
+	u32 assert_pattern = ioread32((void __iomem *)&shared_env->assert_pattern);
+
+	if (assert_pattern == ASSERT_PATTERN) {
+		u16 line = ioread16((void __iomem *)&shared_env->assert_line_num);
+		u16 file_id = ioread16((void __iomem *)&shared_env->assert_file_id);
+		u32 param = ioread32((void __iomem *)&shared_env->assert_param);
+		const char *assert_string = cl_dbgfile_get_msg_txt(&cl_hw->dbg_data, file_id, line);
+
+		/* Print 1st ASSERT message with file_id, line, [parameter] */
+		cl_dbg_err(cl_hw, "ASSERT_%cmac @ FILE=%u LINE=%u param=0x%08X\n",
+			   cl_hw->fw_prefix, file_id, line, param);
+
+		if (!assert_string)
+			assert_string = "ASSERT STRING NOT FOUND";
+
+		cl_dbg_err(cl_hw, "%.500s\n", assert_string);
+
+		/* Reset ASSERT pattern in order to prevent assert prints loop */
+		iowrite32(0, (void __iomem *)&shared_env->assert_pattern);
+	}
+}
