diff mbox series

[RFC,v2,09/96] cl8k: add calib.c

Message ID 20220524113502.1094459-10-viktor.barna@celeno.com
State New
Headers show
Series wireless: cl8k driver for Celeno IEEE 802.11ax devices | expand

Commit Message

Viktor Barna May 24, 2022, 11:33 a.m. UTC
From: Viktor Barna <viktor.barna@celeno.com>

(Part of the split. Please, take a look at the cover letter for more
details).

Signed-off-by: Viktor Barna <viktor.barna@celeno.com>
---
 drivers/net/wireless/celeno/cl8k/calib.c | 2266 ++++++++++++++++++++++
 1 file changed, 2266 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/calib.c
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/calib.c b/drivers/net/wireless/celeno/cl8k/calib.c
new file mode 100644
index 000000000000..93fe6a2e00ee
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/calib.c
@@ -0,0 +1,2266 @@ 
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/dcache.h>
+
+#include "reg/reg_defs.h"
+#include "chip.h"
+#include "config.h"
+#include "radio.h"
+#include "e2p.h"
+#include "rfic.h"
+#include "debug.h"
+#include "utils.h"
+#include "calib.h"
+
+static void cl_calib_common_init_cfm(struct cl_iq_dcoc_data *iq_dcoc_data)
+{
+	int i;
+
+	for (i = 0; i < CALIB_CFM_MAX; i++)
+		iq_dcoc_data->dcoc_iq_cfm[i].status = CALIB_FAIL;
+}
+
+void cl_calib_common_fill_phy_data(struct cl_hw *cl_hw, struct cl_iq_dcoc_info *iq_dcoc_db,
+				   u8 flags)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	u8 bw = cl_hw->bw;
+	u8 channel_idx = cl_calib_dcoc_channel_bw_to_idx(cl_hw, cl_hw->channel, bw);
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u8 sx = cl_hw->sx_idx;
+
+	if (flags & SET_PHY_DATA_FLAGS_DCOC)
+		cl_calib_dcoc_fill_data(cl_hw, iq_dcoc_db);
+
+	if (flags & SET_PHY_DATA_FLAGS_IQ_TX_LOLC)
+		cl_calib_iq_lolc_fill_data(cl_hw, iq_dcoc_db->iq_tx_lolc);
+
+	if (flags & SET_PHY_DATA_FLAGS_IQ_TX)
+		cl_calib_iq_fill_data(cl_hw, iq_dcoc_db->iq_tx,
+				      chip->calib_db.iq_tx[tcv_idx][channel_idx][bw][sx]);
+
+	if (flags & SET_PHY_DATA_FLAGS_IQ_RX)
+		cl_calib_iq_fill_data(cl_hw, iq_dcoc_db->iq_rx,
+				      chip->calib_db.iq_rx[tcv_idx][channel_idx][bw][sx]);
+}
+
+int cl_calib_common_tables_alloc(struct cl_hw *cl_hw)
+{
+	struct cl_iq_dcoc_data *buf = NULL;
+	u32 len = sizeof(struct cl_iq_dcoc_data);
+	dma_addr_t phys_dma_addr;
+
+	buf = dma_alloc_coherent(cl_hw->chip->dev, len, &phys_dma_addr, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	cl_hw->iq_dcoc_data_info.iq_dcoc_data = buf;
+	cl_hw->iq_dcoc_data_info.dma_addr = phys_dma_addr;
+
+	cl_calib_common_init_cfm(cl_hw->iq_dcoc_data_info.iq_dcoc_data);
+	return 0;
+}
+
+void cl_calib_common_tables_free(struct cl_hw *cl_hw)
+{
+	struct cl_iq_dcoc_data_info *iq_dcoc_data_info = &cl_hw->iq_dcoc_data_info;
+	u32 len = sizeof(struct cl_iq_dcoc_data);
+	dma_addr_t phys_dma_addr = iq_dcoc_data_info->dma_addr;
+
+	if (!iq_dcoc_data_info->iq_dcoc_data)
+		return;
+
+	dma_free_coherent(cl_hw->chip->dev, len, (void *)iq_dcoc_data_info->iq_dcoc_data,
+			  phys_dma_addr);
+	iq_dcoc_data_info->iq_dcoc_data = NULL;
+}
+
+static void _cl_calib_common_start_work(struct work_struct *ws)
+{
+	struct cl_calib_work *calib_work = container_of(ws, struct cl_calib_work, ws);
+	struct cl_hw *cl_hw = calib_work->cl_hw;
+	struct cl_hw *cl_hw_other = cl_hw_other_tcv(cl_hw);
+	struct cl_chip *chip = cl_hw->chip;
+
+	cl_calib_iq_init_calibration(cl_hw);
+
+	if (cl_chip_is_both_enabled(chip))
+		cl_calib_iq_init_calibration(cl_hw_other);
+
+	/* Start cl_radio_on after calibration ends */
+	cl_radio_on_start(cl_hw);
+
+	if (cl_chip_is_both_enabled(chip))
+		cl_radio_on_start(cl_hw_other);
+
+	kfree(calib_work);
+}
+
+void cl_calib_common_start_work(struct cl_hw *cl_hw)
+{
+	struct cl_calib_work *calib_work = kzalloc(sizeof(*calib_work), GFP_ATOMIC);
+
+	if (!calib_work)
+		return;
+
+	calib_work->cl_hw = cl_hw;
+	INIT_WORK(&calib_work->ws, _cl_calib_common_start_work);
+	queue_work(cl_hw->drv_workqueue, &calib_work->ws);
+}
+
+s16 cl_calib_common_get_temperature(struct cl_hw *cl_hw, u8 cfm_type)
+{
+	struct calib_cfm *dcoc_iq_cfm =
+		&cl_hw->iq_dcoc_data_info.iq_dcoc_data->dcoc_iq_cfm[cfm_type];
+	u16 raw_bits = (le16_to_cpu(dcoc_iq_cfm->raw_bits_data_0) +
+		le16_to_cpu(dcoc_iq_cfm->raw_bits_data_1)) / 2;
+
+	return cl_temperature_calib_calc(cl_hw, raw_bits);
+}
+
+#ifdef CONFIG_CL8K_EEPROM_STM24256
+static u16 cl_calib_common_eeprom_get_idx(struct cl_hw *cl_hw, int bw_idx, u16 channel,
+					  u16 channels_plan[], u8 num_of_channels)
+{
+	int i;
+
+	for (i = 0; i < num_of_channels; i++)
+		if (channels_plan[i] == channel)
+			return i;
+
+	return U16_MAX;
+}
+
+static u16 cl_calib_common_eeprom_get_addr(struct cl_hw *cl_hw, int bw_idx, u16 channel)
+{
+	int idx = 0;
+	u16 addr = 0;
+	u16 *channels;
+	u8 num_of_channels;
+
+	switch (bw_idx) {
+	case CHNL_BW_20:
+		channels = cl_hw->conf->ci_calib_eeprom_channels_20mhz;
+
+		if (cl_hw_is_tcv0(cl_hw)) {
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV0;
+			idx = cl_calib_common_eeprom_get_idx(cl_hw, bw_idx, channel, channels,
+							     num_of_channels);
+			addr = ADDR_CALIB_IQ_DCOC_DATA_20MHZ_TCV0;
+		} else {
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV1;
+			idx = cl_calib_common_eeprom_get_idx(cl_hw, bw_idx, channel, channels,
+							     num_of_channels);
+			addr = ADDR_CALIB_IQ_DCOC_DATA_20MHZ_TCV1;
+		}
+		break;
+	case CHNL_BW_40:
+		channels = cl_hw->conf->ci_calib_eeprom_channels_40mhz;
+
+		if (cl_hw_is_tcv0(cl_hw)) {
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_40MHZ_TCV0;
+			idx = cl_calib_common_eeprom_get_idx(cl_hw, bw_idx, channel, channels,
+							     num_of_channels);
+			addr = ADDR_CALIB_IQ_DCOC_DATA_40MHZ_TCV0;
+		} else {
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_40MHZ_TCV1;
+			idx = cl_calib_common_eeprom_get_idx(cl_hw, bw_idx, channel, channels,
+							     num_of_channels);
+			addr = ADDR_CALIB_IQ_DCOC_DATA_40MHZ_TCV1;
+		}
+		break;
+	case CHNL_BW_80:
+		channels = cl_hw->conf->ci_calib_eeprom_channels_80mhz;
+
+		if (cl_hw_is_tcv0(cl_hw)) {
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_80MHZ_TCV0;
+			idx = cl_calib_common_eeprom_get_idx(cl_hw, bw_idx, channel, channels,
+							     num_of_channels);
+			addr = ADDR_CALIB_IQ_DCOC_DATA_80MHZ_TCV0;
+		} else {
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_80MHZ_TCV1;
+			idx = cl_calib_common_eeprom_get_idx(cl_hw, bw_idx, channel, channels,
+							     num_of_channels);
+			addr = ADDR_CALIB_IQ_DCOC_DATA_80MHZ_TCV1;
+		}
+		break;
+	case CHNL_BW_160:
+		channels = cl_hw->conf->ci_calib_eeprom_channels_80mhz;
+
+		if (cl_hw_is_tcv0(cl_hw)) {
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_80MHZ_TCV0;
+			idx = cl_calib_common_eeprom_get_idx(cl_hw, bw_idx, channel, channels,
+							     num_of_channels);
+			addr = ADDR_CALIB_IQ_DCOC_DATA_80MHZ_TCV0;
+		} else {
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_80MHZ_TCV1;
+			idx = cl_calib_common_eeprom_get_idx(cl_hw, bw_idx, channel, channels,
+							     num_of_channels);
+			addr = ADDR_CALIB_IQ_DCOC_DATA_80MHZ_TCV1;
+		}
+		break;
+	default:
+		return U16_MAX;
+	}
+
+	if (idx == U16_MAX)
+		return U16_MAX;
+
+	return ((u16)addr + (u16)(idx * sizeof(struct eeprom_calib_data)));
+}
+
+static void cl_calib_common_write_lolc_to_eeprom(struct cl_calib_db *calib_db,
+						 struct eeprom_calib_data *calib_data,
+						 u8 ch_idx, u8 bw, u8 sx_idx, u8 tcv_idx)
+{
+	memcpy(calib_data->lolc,
+	       calib_db->iq_tx_lolc[tcv_idx][ch_idx][bw][sx_idx],
+	       sizeof(u32) * MAX_ANTENNAS);
+}
+
+static void cl_calib_common_write_dcoc_to_eeprom(struct cl_calib_db *calib_db,
+						 struct eeprom_calib_data *calib_data,
+						 u8 ch_idx, u8 bw, u8 sx_idx, u8 tcv_idx)
+{
+	memcpy(calib_data->dcoc,
+	       calib_db->dcoc[tcv_idx][ch_idx][bw][sx_idx],
+	       sizeof(struct cl_dcoc_calib) * MAX_ANTENNAS * DCOC_LNA_GAIN_NUM);
+}
+
+static void cl_calib_common_write_iq_to_eeprom(struct cl_calib_db *calib_db,
+					       struct eeprom_calib_data *calib_data,
+					       u8 ch_idx, u8 bw, u8 sx_idx, u8 tcv_idx)
+{
+	memcpy(calib_data->iq_tx,
+	       calib_db->iq_tx[tcv_idx][ch_idx][bw][sx_idx],
+	       sizeof(struct cl_iq_calib) * MAX_ANTENNAS);
+	memcpy(calib_data->iq_rx,
+	       calib_db->iq_rx[tcv_idx][ch_idx][bw][sx_idx],
+	       sizeof(struct cl_iq_calib) * MAX_ANTENNAS);
+}
+
+static s8 cl_calib_common_find_worst_iq_tone(struct cl_iq_report iq_report_dma)
+{
+	u8 tone = 0;
+	s8 worst_tone = S8_MIN;
+
+	for (tone = 0; tone < IQ_NUM_TONES_CFM; tone++)
+		if (worst_tone < iq_report_dma.ir_db[IQ_POST_IDX][tone])
+			worst_tone = iq_report_dma.ir_db[IQ_POST_IDX][tone];
+
+	return worst_tone;
+}
+
+static void cl_calib_common_write_score_dcoc(struct cl_hw *cl_hw,
+					     struct eeprom_calib_data *calib_data)
+{
+	u8 lna, ant;
+
+	for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++) {
+		for (ant = 0; ant < MAX_ANTENNAS; ant++) {
+			struct cl_dcoc_report *dcoc_calib_report =
+				&cl_hw->iq_dcoc_data_info.iq_dcoc_data->report.dcoc[lna][ant];
+
+			calib_data->score[ant].dcoc_i_mv[lna] =
+				(s16)le16_to_cpu(dcoc_calib_report->i_dc);
+			calib_data->score[ant].dcoc_q_mv[lna] =
+				(s16)le16_to_cpu(dcoc_calib_report->q_dc);
+		}
+	}
+}
+
+static void cl_calib_common_write_score_lolc(struct cl_hw *cl_hw,
+					     struct eeprom_calib_data *calib_data)
+{
+	u8 ant;
+	struct cl_iq_dcoc_report *report = &cl_hw->iq_dcoc_data_info.iq_dcoc_data->report;
+
+	for (ant = 0; ant < MAX_ANTENNAS; ant++) {
+		calib_data->score[ant].lolc_score =
+			(s16)(le16_to_cpu(report->lolc_report[ant].lolc_qual)) >> 8;
+	}
+}
+
+static void cl_calib_common_write_score_iq(struct cl_hw *cl_hw,
+					   struct eeprom_calib_data *calib_data)
+{
+	u8 ant;
+
+	for (ant = 0; ant < MAX_ANTENNAS; ant++) {
+		struct cl_iq_report iq_report_dma_tx =
+			cl_hw->iq_dcoc_data_info.iq_dcoc_data->report.iq_tx[ant];
+		struct cl_iq_report iq_report_dma_rx =
+			cl_hw->iq_dcoc_data_info.iq_dcoc_data->report.iq_rx[ant];
+
+		calib_data->score[ant].iq_tx_score = iq_report_dma_tx.ir_db_avg_post;
+		calib_data->score[ant].iq_rx_score = iq_report_dma_rx.ir_db_avg_post;
+		calib_data->score[ant].iq_tx_worst_score =
+			cl_calib_common_find_worst_iq_tone(iq_report_dma_tx);
+		calib_data->score[ant].iq_rx_worst_score =
+			cl_calib_common_find_worst_iq_tone(iq_report_dma_rx);
+	}
+}
+
+static void cl_calib_common_write_score_to_eeprom(struct cl_hw *cl_hw,
+						  struct eeprom_calib_data *calib_data)
+{
+	cl_calib_common_write_score_dcoc(cl_hw, calib_data);
+	cl_calib_common_write_score_lolc(cl_hw, calib_data);
+	cl_calib_common_write_score_iq(cl_hw, calib_data);
+}
+
+static void cl_calib_common_write_eeprom(struct cl_hw *cl_hw, u32 channel, u8 bw, u8 sx_idx,
+					 u8 tcv_idx)
+{
+	u8 ch_idx = cl_calib_dcoc_channel_bw_to_idx(cl_hw, channel, bw);
+	u16 eeprom_addr = cl_calib_common_eeprom_get_addr(cl_hw, bw, channel);
+	struct eeprom_calib_data calib_data;
+	struct cl_calib_db *calib_db = &cl_hw->chip->calib_db;
+
+	if (eeprom_addr == U16_MAX)
+		return;
+
+	calib_data.valid = true;
+	calib_data.temperature = cl_calib_common_get_temperature(cl_hw, CALIB_CFM_IQ);
+	cl_calib_common_write_lolc_to_eeprom(calib_db, &calib_data, ch_idx, bw, sx_idx, tcv_idx);
+	cl_calib_common_write_dcoc_to_eeprom(calib_db, &calib_data, ch_idx, bw, sx_idx, tcv_idx);
+	cl_calib_common_write_iq_to_eeprom(calib_db, &calib_data, ch_idx, bw, sx_idx, tcv_idx);
+	cl_calib_common_write_score_to_eeprom(cl_hw, &calib_data);
+
+	cl_e2p_write(cl_hw->chip, (u8 *)&calib_data, (u16)sizeof(struct eeprom_calib_data),
+		     eeprom_addr);
+}
+
+static bool cl_calib_common_is_channel_included_in_eeprom_bitmap(struct cl_hw *cl_hw)
+{
+	u16 i;
+	u16 *eeprom_valid_ch = NULL;
+	u16 num_of_channels;
+
+	switch (cl_hw->bw) {
+	case CHNL_BW_20:
+		eeprom_valid_ch = cl_hw->conf->ci_calib_eeprom_channels_20mhz;
+
+		if (cl_hw_is_tcv0(cl_hw))
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV0;
+		else
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV1;
+		break;
+	case CHNL_BW_40:
+		eeprom_valid_ch = cl_hw->conf->ci_calib_eeprom_channels_40mhz;
+
+		if (cl_hw_is_tcv0(cl_hw))
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV0;
+		else
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV1;
+		break;
+	case CHNL_BW_80:
+		eeprom_valid_ch = cl_hw->conf->ci_calib_eeprom_channels_80mhz;
+
+		if (cl_hw_is_tcv0(cl_hw))
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV0;
+		else
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV1;
+		break;
+	case CHNL_BW_160:
+		eeprom_valid_ch = cl_hw->conf->ci_calib_eeprom_channels_160mhz;
+
+		if (cl_hw_is_tcv0(cl_hw))
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV0;
+		else
+			num_of_channels = EEPROM_CALIB_DATA_ELEM_NUM_20MHZ_TCV1;
+		break;
+	default:
+		return false;
+	}
+	for (i = 0; i < num_of_channels; i++)
+		if (cl_hw->channel == eeprom_valid_ch[i])
+			return true;
+
+	return false;
+}
+#endif /* CONFIG_CL8K_EEPROM_STM24256 */
+
+int cl_calib_common_handle_set_channel_cfm(struct cl_hw *cl_hw, struct cl_calib_params calib_params)
+{
+	struct cl_iq_dcoc_data *iq_dcoc_data = cl_hw->iq_dcoc_data_info.iq_dcoc_data;
+	u8 mode = calib_params.mode;
+
+	cl_dbg_trace(cl_hw, "\n ------  FINISH CALIB CHANNEL  -----\n");
+
+	/*
+	 * In case any of the requested calibrations failed - no need to copy
+	 * the other Calibration data, and fail the whole calibration process.
+	 */
+	if ((mode & SET_CHANNEL_MODE_CALIB_DCOC &&
+	     iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_DCOC].status != CALIB_SUCCESS) ||
+	    (mode & SET_CHANNEL_MODE_CALIB_IQ &&
+	     iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_IQ].status != CALIB_SUCCESS)) {
+		cl_dbg_err(cl_hw, "Calibration failed! mode = %u, DCOC_CFM_STATUS = %u, "
+			   "IQ_CFM_STATUS = %u\n", mode,
+			   iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_DCOC].status,
+			   iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_IQ].status);
+		/* Set status to CALIB_FAIL to ensure that FW is writing the values. */
+		iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_DCOC].status = CALIB_FAIL;
+		iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_IQ].status = CALIB_FAIL;
+		return -EINVAL;
+	}
+
+	cl_dbg_trace(cl_hw, "mode = %u\n", mode);
+
+	if (mode & SET_CHANNEL_MODE_CALIB_DCOC)
+		cl_calib_dcoc_handle_set_channel_cfm(cl_hw, calib_params.first_channel);
+
+	if (mode & SET_CHANNEL_MODE_CALIB_IQ)
+		cl_calib_iq_handle_set_channel_cfm(cl_hw, calib_params.plan_bitmap);
+
+	if (mode & SET_CHANNEL_MODE_CALIB_LOLC)
+		cl_calib_iq_lolc_handle_set_channel_cfm(cl_hw, calib_params.plan_bitmap);
+
+#ifdef CONFIG_CL8K_EEPROM_STM24256
+	if (cl_hw->chip->conf->ci_calib_eeprom_en && cl_hw->chip->conf->ce_production_mode &&
+	    cl_hw->chip->is_calib_eeprom_loaded && cl_hw->chip->conf->ce_calib_runtime_en)
+		if (cl_calib_common_is_channel_included_in_eeprom_bitmap(cl_hw))
+			cl_calib_common_write_eeprom(cl_hw, cl_hw->channel, cl_hw->bw,
+						     cl_hw->sx_idx, cl_hw->tcv_idx);
+#endif
+
+	return 0;
+}
+
+int cl_calib_common_check_errors(struct cl_hw *cl_hw)
+{
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u16 dcoc_erros = cl_hw->chip->calib_db.errors[tcv_idx].dcoc;
+	u16 lolc_erros = cl_hw->chip->calib_db.errors[tcv_idx].lolc;
+	u16 iq_tx_erros = cl_hw->chip->calib_db.errors[tcv_idx].iq_tx;
+	u16 iq_rx_erros = cl_hw->chip->calib_db.errors[tcv_idx].iq_rx;
+
+	if (!cl_hw->chip->conf->ci_calib_check_errors)
+		return 0;
+
+	if (dcoc_erros > 0 || lolc_erros > 0 || iq_tx_erros > 0 || iq_rx_erros > 0) {
+		CL_DBG_ERROR(cl_hw, "Abort: dcoc_erros %u, lolc_erros %u,"
+			     " iq_tx_erros %u, iq_rx_erros %u\n",
+			     dcoc_erros, lolc_erros, iq_tx_erros, iq_rx_erros);
+		return -ECANCELED;
+	}
+
+	return 0;
+}
+
+static const u8 calib_channels_24g[CALIB_CHAN_24G_MAX] = {
+	1, 6, 11
+};
+
+static const u8 calib_channels_5g_plan[CALIB_CHAN_5G_PLAN] = {
+	36, 52, 100, 116, 132, 149
+};
+
+static const u8 calib_channels_6g_plan[CALIB_CHAN_6G_PLAN] = {
+	1, 17, 33, 49, 65, 81, 97, 113, 129, 145, 161, 177, 193, 209, 225
+};
+
+static const u8 calib_channels_5g_bw_20[] = {
+	36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
+	149, 153, 157, 161, 165
+};
+
+static const u8 calib_channels_5g_bw_40[] = {
+	36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157
+};
+
+static const u8 calib_channels_5g_bw_80[] = {
+	36, 52, 100, 116, 132, 149
+};
+
+static const u8 calib_channels_5g_bw_160[] = {
+	36, 100
+};
+
+static const u8 calib_channels_6g_bw_20[] = {
+	1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93,
+	97, 101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141, 145, 149, 153, 157, 161, 165,
+	169, 173, 177, 181, 185, 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 233
+};
+
+static const u8 calib_channels_6g_bw_40[] = {
+	1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, 97, 105, 113, 121, 129, 137, 145, 153, 161,
+	169, 177, 185, 193, 201, 209, 217, 225
+};
+
+static const u8 calib_channels_6g_bw_80[] = {
+	1, 17, 33, 49, 65, 81, 97, 113, 129, 145, 161, 177, 193, 209, 225
+};
+
+static const u8 calib_channels_6g_bw_160[] = {
+	1, 33, 65, 97, 129, 161, 193, 225
+};
+
+static void cl_calib_dcoc_handle_data(struct cl_hw *cl_hw, s16 calib_temperature, u8 channel, u8 bw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	int lna, chain;
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u8 sx = cl_hw->sx_idx;
+	u8 channel_idx = cl_calib_dcoc_channel_bw_to_idx(cl_hw, channel, bw);
+	struct cl_dcoc_calib *dcoc_calib;
+	struct cl_dcoc_calib *dcoc_calib_dma;
+
+	for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++) {
+		riu_chain_for_each(chain) {
+			dcoc_calib =
+				&chip->calib_db.dcoc[tcv_idx][channel_idx][bw][sx][chain][lna];
+			dcoc_calib_dma =
+				&cl_hw->iq_dcoc_data_info.iq_dcoc_data->iq_dcoc_db.dcoc[lna][chain];
+			dcoc_calib->i = dcoc_calib_dma->i;
+			dcoc_calib->q = dcoc_calib_dma->q;
+		}
+	}
+}
+
+static void cl_calib_dcoc_handle_report(struct cl_hw *cl_hw, s16 calib_temperature,
+					int channel, u8 bw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	int lna, chain;
+	struct cl_dcoc_report *dcoc_calib_report_dma;
+	int bw_mhz = BW_TO_MHZ(bw);
+	u8 dcoc_threshold = chip->conf->ci_dcoc_mv_thr[bw];
+	s16 i, q;
+
+	for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++) {
+		riu_chain_for_each(chain) {
+			dcoc_calib_report_dma =
+				&cl_hw->iq_dcoc_data_info.iq_dcoc_data->report.dcoc[lna][chain];
+
+			i = (s16)le16_to_cpu(dcoc_calib_report_dma->i_dc);
+			q = (s16)le16_to_cpu(dcoc_calib_report_dma->q_dc);
+
+			if (abs(i) > dcoc_threshold || abs(q) > dcoc_threshold) {
+				cl_dbg_warn(cl_hw,
+					    "Warning: DCOC value exceeds threshold [%umV]: channel %u, bw = %u, lna = %u, chain = %u, I[mV] = %d, I[Iter] = %u, Q[mV] = %d, Q[Iter] = %u\n",
+					    dcoc_threshold, channel, bw_mhz, lna, chain, i,
+					    le16_to_cpu(dcoc_calib_report_dma->i_iterations), q,
+					    le16_to_cpu(dcoc_calib_report_dma->q_iterations));
+				chip->calib_db.errors[cl_hw->tcv_idx].dcoc++;
+			}
+		}
+	}
+}
+
+static int cl_calib_dcoc_calibrate_channel(struct cl_hw *cl_hw, u32 channel, u32 bw,
+					   bool first_channel)
+{
+	u32 primary = 0;
+	u32 center = 0;
+	enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20;
+	struct cl_calib_params calib_params = {SET_CHANNEL_MODE_CALIB_DCOC, first_channel, 0, 0};
+
+	if (cl_chandef_calc(cl_hw, channel, bw, &width, &primary, &center)) {
+		cl_dbg_err(cl_hw, "cl_chandef_calc failed\n");
+		return -EINVAL;
+	}
+
+	cl_dbg_trace(cl_hw, "\n ------  START CALIB DCOC CHANNEL  -----\n");
+	cl_dbg_trace(cl_hw, "channel = %u first_channel = %u\n", channel, first_channel);
+
+	/* Set Channel Mode to DCOC Calibration Mode */
+	return cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center, calib_params);
+}
+
+static void cl_calib_dcoc_average(struct cl_chip *chip, u8 tcv_idx, u8 center,
+				  u8 bw, u8 chain, u8 sx, u8 lna)
+{
+	struct cl_dcoc_calib *dcoc_db_left;
+	struct cl_dcoc_calib *dcoc_db_center;
+	struct cl_dcoc_calib *dcoc_db_right;
+	u8 left_idx = cl_calib_dcoc_tcv_channel_to_idx(chip, tcv_idx,
+						       calib_channels_6g_plan[center - 1], bw);
+	u8 center_idx = cl_calib_dcoc_tcv_channel_to_idx(chip, tcv_idx,
+							 calib_channels_6g_plan[center], bw);
+	u8 right_idx = cl_calib_dcoc_tcv_channel_to_idx(chip, tcv_idx,
+							calib_channels_6g_plan[center + 1], bw);
+
+	dcoc_db_left = &chip->calib_db.dcoc[tcv_idx][left_idx][bw][sx][chain][lna];
+	dcoc_db_center = &chip->calib_db.dcoc[tcv_idx][center_idx][bw][sx][chain][lna];
+	dcoc_db_right = &chip->calib_db.dcoc[tcv_idx][right_idx][bw][sx][chain][lna];
+
+	dcoc_db_center->i = (dcoc_db_left->i + dcoc_db_right->i) / 2;
+	dcoc_db_center->q = (dcoc_db_left->q + dcoc_db_right->q) / 2;
+}
+
+static int cl_calib_dcoc_calibrate_6g(struct cl_hw *cl_hw)
+{
+	int i;
+	u8 chain, lna;
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u8 sx = tcv_idx;
+	bool first_channel = true;
+	struct cl_chip *chip = cl_hw->chip;
+
+	/* Calibrate channels: 1, 33, 65, 97, 129, 161, 193, 225 */
+	for (i = 0; i < CALIB_CHAN_6G_PLAN; i += 2) {
+		if (cl_hw->conf->ci_cap_bandwidth == CHNL_BW_160 &&
+		    (cl_calib_dcoc_calibrate_channel(cl_hw, calib_channels_6g_plan[i], CHNL_BW_160,
+						     first_channel) == 0))
+			first_channel = false;
+
+		if (cl_calib_dcoc_calibrate_channel(cl_hw, calib_channels_6g_plan[i], CHNL_BW_80,
+						    first_channel) == 0)
+			first_channel = false;
+
+		if (cl_calib_dcoc_calibrate_channel(cl_hw, calib_channels_6g_plan[i], CHNL_BW_20,
+						    first_channel) == 0)
+			first_channel = false;
+	}
+
+	/*
+	 * For these channels 17, 49, 81, 113, 145, 177, 209
+	 * calculate average of closest neighbors
+	 */
+	for (i = 1; i < CALIB_CHAN_6G_PLAN - 1; i += 2)
+		riu_chain_for_each(chain)
+			for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++) {
+				cl_calib_dcoc_average(chip, tcv_idx, i, CHNL_BW_80,
+						      chain, sx, lna);
+				cl_calib_dcoc_average(chip, tcv_idx, i, CHNL_BW_20,
+						      chain, sx, lna);
+			}
+
+	return first_channel;
+}
+
+static int cl_calib_dcoc_calibrate_5g(struct cl_hw *cl_hw)
+{
+	int i;
+	bool first_channel = true;
+
+	if (cl_hw->conf->ci_cap_bandwidth == CHNL_BW_160) {
+		if (cl_calib_dcoc_calibrate_channel(cl_hw, 36, CHNL_BW_160, first_channel) == 0)
+			first_channel = false;
+
+		if (cl_calib_dcoc_calibrate_channel(cl_hw, 100, CHNL_BW_160, first_channel) == 0)
+			first_channel = false;
+	}
+
+	for (i = 0; i < CALIB_CHAN_5G_PLAN; i++) {
+		if (cl_calib_dcoc_calibrate_channel(cl_hw, calib_channels_5g_plan[i], CHNL_BW_80,
+						    first_channel) == 0)
+			first_channel = false;
+
+		if (cl_calib_dcoc_calibrate_channel(cl_hw, calib_channels_5g_plan[i], CHNL_BW_20,
+						    first_channel) == 0)
+			first_channel = false;
+	}
+
+	return first_channel;
+}
+
+static int cl_calib_dcoc_calibrate_24g(struct cl_hw *cl_hw)
+{
+	int i;
+	bool first_channel = true;
+
+	for (i = 0; i < CALIB_CHAN_24G_MAX; i++) {
+		if (cl_calib_dcoc_calibrate_channel(cl_hw, calib_channels_24g[i], CHNL_BW_40,
+						    first_channel) == 0)
+			first_channel = false;
+
+		if (cl_calib_dcoc_calibrate_channel(cl_hw, calib_channels_24g[i], CHNL_BW_20,
+						    first_channel) == 0)
+			first_channel = false;
+	}
+
+	return first_channel;
+}
+
+static void cl_calib_dcoc_calibrate(struct cl_hw *cl_hw)
+{
+	if (cl_band_is_6g(cl_hw))
+		cl_calib_dcoc_calibrate_6g(cl_hw);
+	else if (cl_band_is_5g(cl_hw))
+		cl_calib_dcoc_calibrate_5g(cl_hw);
+	else if (cl_band_is_24g(cl_hw))
+		cl_calib_dcoc_calibrate_24g(cl_hw);
+}
+
+void cl_calib_dcoc_init_calibration(struct cl_hw *cl_hw)
+{
+	u8 tcv_idx = cl_hw->tcv_idx;
+	struct cl_chip *chip = cl_hw->chip;
+	struct cl_iq_dcoc_conf *iq_dcoc_conf = &chip->iq_dcoc_conf;
+	u8 fem_mode = cl_hw->fem_mode;
+
+	/* No need to init calibration to non-Olympus phy */
+	if (!IS_REAL_PHY(chip))
+		return;
+	if (cl_hw_is_tcv0(cl_hw) && chip->conf->ci_tcv1_chains_sx0)
+		return;
+
+	if (!iq_dcoc_conf->dcoc_calib_needed[tcv_idx]) {
+		u8 file_num_antennas = iq_dcoc_conf->dcoc_file_num_ant[tcv_idx];
+
+		if (file_num_antennas < cl_hw->num_antennas) {
+			cl_dbg_verbose(cl_hw,
+				       "Num of antennas [%u], is larger than DCOC calibration file"
+				       " num of antennas [%u], recalibration is needed\n",
+				       cl_hw->num_antennas, file_num_antennas);
+		} else {
+			return;
+		}
+	}
+
+	/* Set FEM mode to LNA Bypass Only mode for DCOC Calibration. */
+	cl_fem_set_dcoc_bypass(cl_hw);
+	cl_afe_cfg_calib(chip);
+
+	cl_calib_dcoc_calibrate(cl_hw);
+
+	/* Restore FEM mode to its original mode. */
+	cl_fem_dcoc_restore(cl_hw, fem_mode);
+	cl_afe_cfg_restore(chip);
+
+	iq_dcoc_conf->dcoc_calib_needed[tcv_idx] = false;
+	iq_dcoc_conf->dcoc_file_num_ant[tcv_idx] = cl_hw->num_antennas;
+}
+
+static u8 cl_calib_dcoc_get_chan_idx(const u8 calib_chan_list[], u8 list_len, u8 channel)
+{
+	u8 i = 0;
+
+	for (i = 1; i < list_len; i++)
+		if (calib_chan_list[i] > channel)
+			return (i - 1);
+
+	return (list_len - 1);
+}
+
+static u8 cl_calib_dcoc_convert_to_channel_in_plan(u8 channel, u8 band)
+{
+	u8 idx;
+
+	if (band == BAND_6G) {
+		idx = cl_calib_dcoc_get_chan_idx(calib_channels_6g_plan,
+						 ARRAY_SIZE(calib_channels_6g_plan), channel);
+		return calib_channels_6g_plan[idx];
+	}
+
+	idx = cl_calib_dcoc_get_chan_idx(calib_channels_5g_plan,
+					 ARRAY_SIZE(calib_channels_5g_plan), channel);
+
+	return calib_channels_5g_plan[idx];
+}
+
+u8 cl_calib_dcoc_channel_bw_to_idx(struct cl_hw *cl_hw, u8 channel, u8 bw)
+{
+	if (cl_band_is_6g(cl_hw)) {
+		if (bw == CHNL_BW_160)
+			return cl_calib_dcoc_get_chan_idx(calib_channels_6g_bw_160,
+							  ARRAY_SIZE(calib_channels_6g_bw_160),
+							  channel);
+		/*
+		 * In case of non runtime calib - channels that don't exist in the plan list will
+		 * not be calibrated. Thus, the calib data need to be fetched from a close channel
+		 * that was calibrated - AKA "fallback channel".
+		 * In this case the channel value should convert to the "fallback channel" that had
+		 * been calibrated. The func will return the idx value of the "fallback channel"
+		 * instead of the original idx channel.
+		 */
+		if (!cl_hw->chip->conf->ce_calib_runtime_en)
+			channel = cl_calib_dcoc_convert_to_channel_in_plan(channel, BAND_6G);
+
+		if (bw == CHNL_BW_20)
+			return cl_calib_dcoc_get_chan_idx(calib_channels_6g_bw_20,
+							  ARRAY_SIZE(calib_channels_6g_bw_20),
+							  channel);
+
+		if (bw == CHNL_BW_40)
+			return cl_calib_dcoc_get_chan_idx(calib_channels_6g_bw_40,
+							  ARRAY_SIZE(calib_channels_6g_bw_40),
+							  channel);
+
+		if (bw == CHNL_BW_80)
+			return cl_calib_dcoc_get_chan_idx(calib_channels_6g_bw_80,
+							  ARRAY_SIZE(calib_channels_6g_bw_80),
+							  channel);
+	}
+
+	if (cl_band_is_5g(cl_hw)) {
+		if (bw == CHNL_BW_160)
+			return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_160,
+							  ARRAY_SIZE(calib_channels_5g_bw_160),
+							  channel);
+
+		if (!cl_hw->chip->conf->ce_calib_runtime_en)
+			channel = cl_calib_dcoc_convert_to_channel_in_plan(channel, BAND_5G);
+
+		if (bw == CHNL_BW_20)
+			return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_20,
+							  ARRAY_SIZE(calib_channels_5g_bw_20),
+							  channel);
+
+		if (bw == CHNL_BW_40)
+			return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_40,
+							  ARRAY_SIZE(calib_channels_5g_bw_40),
+							  channel);
+
+		if (bw == CHNL_BW_80)
+			return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_80,
+							  ARRAY_SIZE(calib_channels_5g_bw_80),
+							  channel);
+	}
+
+	return cl_calib_dcoc_get_chan_idx(calib_channels_24g, ARRAY_SIZE(calib_channels_24g),
+					  channel);
+}
+
+void cl_calib_dcoc_fill_data(struct cl_hw *cl_hw, struct cl_iq_dcoc_info *iq_dcoc_db)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	u8 lna = 0, chain = 0;
+	u8 bw = cl_hw->bw;
+	u8 channel_idx = cl_calib_dcoc_channel_bw_to_idx(cl_hw, cl_hw->channel, bw);
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u8 sx = cl_hw->sx_idx;
+
+	for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++)
+		riu_chain_for_each(chain)
+			iq_dcoc_db->dcoc[lna][chain] =
+				chip->calib_db.dcoc[tcv_idx][channel_idx][bw][sx][chain][lna];
+}
+
+u8 cl_calib_dcoc_tcv_channel_to_idx(struct cl_chip *chip, u8 tcv_idx, u8 channel, u8 bw)
+{
+	u8 i = 0;
+
+	if (cl_chip_is_6g(chip)) {
+		if (tcv_idx == TCV0) {
+			if (bw == CHNL_BW_20)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_6g_bw_20,
+								  ARRAY_SIZE
+								  (calib_channels_6g_bw_20),
+								  channel);
+
+			if (bw == CHNL_BW_40)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_6g_bw_40,
+								  ARRAY_SIZE
+								  (calib_channels_6g_bw_40),
+								  channel);
+
+			if (bw == CHNL_BW_80)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_6g_bw_80,
+								  ARRAY_SIZE
+								  (calib_channels_6g_bw_80),
+								  channel);
+
+			if (bw == CHNL_BW_160)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_6g_bw_160,
+								  ARRAY_SIZE
+								  (calib_channels_6g_bw_160),
+								  channel);
+		} else if (tcv_idx == TCV1) {
+			if (bw == CHNL_BW_20)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_20,
+								  ARRAY_SIZE
+								  (calib_channels_5g_bw_20),
+								  channel);
+
+			if (bw == CHNL_BW_40)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_40,
+								  ARRAY_SIZE
+								  (calib_channels_5g_bw_40),
+								  channel);
+
+			if (bw == CHNL_BW_80)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_80,
+								  ARRAY_SIZE
+								  (calib_channels_5g_bw_80),
+								  channel);
+
+			if (bw == CHNL_BW_160)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_160,
+								  ARRAY_SIZE
+								  (calib_channels_5g_bw_160),
+								  channel);
+		}
+	} else {
+		if (channel <= NUM_CHANNELS_24G) {
+			for (i = 0; i < CALIB_CHAN_24G_MAX; i++)
+				if (calib_channels_24g[i] == channel)
+					return i;
+		} else {
+			if (bw == CHNL_BW_20)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_20,
+								ARRAY_SIZE
+								(calib_channels_5g_bw_20),
+								channel);
+
+			if (bw == CHNL_BW_40)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_40,
+								ARRAY_SIZE
+								(calib_channels_5g_bw_40),
+								channel);
+
+			if (bw == CHNL_BW_80)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_80,
+								ARRAY_SIZE
+								(calib_channels_5g_bw_80),
+								channel);
+
+			if (bw == CHNL_BW_160)
+				return cl_calib_dcoc_get_chan_idx(calib_channels_5g_bw_160,
+								ARRAY_SIZE
+								(calib_channels_5g_bw_160),
+								channel);
+		}
+	}
+
+	return 0;
+}
+
+void cl_calib_dcoc_handle_set_channel_cfm(struct cl_hw *cl_hw, bool first_channel)
+{
+	struct calib_cfm *dcoc_iq_cfm =
+		&cl_hw->iq_dcoc_data_info.iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_DCOC];
+	s16 calib_temperature = cl_calib_common_get_temperature(cl_hw, CALIB_CFM_DCOC);
+	u8 channel = cl_hw->channel;
+	u8 bw = cl_hw->bw;
+
+	cl_dbg_trace(cl_hw, "calib_temperature = %d, channel = %u, bw = %u\n", calib_temperature,
+		     channel, bw);
+
+	cl_calib_dcoc_handle_data(cl_hw, calib_temperature, channel, bw);
+	cl_calib_dcoc_handle_report(cl_hw, calib_temperature, channel, bw);
+
+	/*
+	 * Set the default status to FAIL, to ensure FW is actually changing the value,
+	 * if the calibration succeeded.
+	 */
+	dcoc_iq_cfm->status = CALIB_FAIL;
+}
+
+static void cl_calib_iq_handle_data(struct cl_hw *cl_hw, s16 calib_temperature, u8 channel,
+				    u8 bw, u8 plan_bitmap)
+{
+	int chain;
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u8 sx = cl_hw->sx_idx;
+	u8 channel_idx = cl_calib_dcoc_channel_bw_to_idx(cl_hw, channel, bw);
+	struct cl_iq_calib iq_calib_dma;
+
+	riu_chain_for_each(chain) {
+		if ((plan_bitmap & (1 << chain)) == 0)
+			continue;
+
+		iq_calib_dma = cl_hw->iq_dcoc_data_info.iq_dcoc_data->iq_dcoc_db.iq_tx[chain];
+		cl_hw->chip->calib_db.iq_tx[tcv_idx][channel_idx][bw][sx][chain] = iq_calib_dma;
+
+		iq_calib_dma = cl_hw->iq_dcoc_data_info.iq_dcoc_data->iq_dcoc_db.iq_rx[chain];
+		cl_hw->chip->calib_db.iq_rx[tcv_idx][channel_idx][bw][sx][chain] = iq_calib_dma;
+	}
+}
+
+static void cl_calib_iq_lolc_handle_data(struct cl_hw *cl_hw, s16 calib_temperature,
+					 u8 channel, u8 bw, u8 plan_bitmap)
+{
+	int chain;
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u8 sx = cl_hw->sx_idx;
+	u8 channel_idx = cl_calib_dcoc_channel_bw_to_idx(cl_hw, channel, bw);
+	__le32 lolc_calib_dma;
+
+	riu_chain_for_each(chain) {
+		if ((plan_bitmap & (1 << chain)) == 0)
+			continue;
+
+		lolc_calib_dma =
+			cl_hw->iq_dcoc_data_info.iq_dcoc_data->iq_dcoc_db.iq_tx_lolc[chain];
+		cl_hw->chip->calib_db.iq_tx_lolc[tcv_idx][channel_idx][bw][sx][chain] =
+			le32_to_cpu(lolc_calib_dma);
+	}
+}
+
+static void cl_calib_iq_lolc_handle_report(struct cl_hw *cl_hw, s16 calib_temperature,
+					   int channel, u8 bw, u8 plan_bitmap)
+{
+	struct cl_iq_dcoc_report *report = &cl_hw->iq_dcoc_data_info.iq_dcoc_data->report;
+	int chain;
+	struct cl_lolc_report lolc_report_dma;
+	int bw_mhz = BW_TO_MHZ(bw);
+	s16 lolc_threshold = cl_hw->chip->conf->ci_lolc_db_thr;
+	s32 lolc_qual = 0;
+
+	riu_chain_for_each(chain) {
+		if ((plan_bitmap & (1 << chain)) == 0)
+			continue;
+
+		lolc_report_dma = report->lolc_report[chain];
+		lolc_qual = (s16)(le16_to_cpu(lolc_report_dma.lolc_qual)) >> 8;
+
+		cl_dbg_trace(cl_hw, "LOLC Quality [chain = %u] = %d, Iter = %u\n",
+			     chain, lolc_qual, lolc_report_dma.n_iter);
+
+		if (lolc_qual > lolc_threshold) {
+			cl_dbg_warn(cl_hw,
+				    "Warning: LOLC value exceeds threshold [%ddB]: channel %u, "
+				    "bw  = %u, chain = %u, LOLC[dB] = %d, I[Iter] = %u\n",
+				    lolc_threshold, channel, bw_mhz, chain, lolc_qual,
+				    lolc_report_dma.n_iter);
+			cl_hw->chip->calib_db.errors[cl_hw->tcv_idx].lolc++;
+		}
+	}
+}
+
+static int cl_calib_iq_calibrate_channel(struct cl_hw *cl_hw, u32 channel, u32 bw,
+					 bool first_channel)
+{
+	u32 primary = 0;
+	u32 center = 0;
+	enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20;
+	struct cl_calib_params calib_params = {
+		(SET_CHANNEL_MODE_CALIB_IQ | SET_CHANNEL_MODE_CALIB_LOLC),
+		first_channel, SX_FREQ_OFFSET_Q2, 0
+	};
+
+	/* Convert ant to riu chain in the calib plan_bitmap */
+	calib_params.plan_bitmap =
+		cl_hw_ant_mask_to_riu_chain_mask(cl_hw, cl_hw->mask_num_antennas);
+
+	if (cl_chandef_calc(cl_hw, channel, bw, &width, &primary, &center)) {
+		cl_dbg_err(cl_hw, "cl_chandef_calc failed\n");
+		return -EINVAL;
+	}
+
+	cl_dbg_trace(cl_hw, "\n ------  START CALIB IQ CHANNEL  -----\n");
+	cl_dbg_trace(cl_hw, "channel = %u first_channel = %d\n", channel, first_channel);
+
+	/* Set channel mode to LO+IQ calibration mode */
+	return cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center, calib_params);
+}
+
+static u8 cl_calib_iq_convert_plan_to_calib_db_idx(u8 chan_idx_src, u8 bw)
+{
+	u8 shift_idx = 0;
+	/*
+	 * Calibration data is fetched from calibrated channels to previous uncalibrated channels in
+	 * plan list.
+	 *
+	 * For example: channel 65 was calibrated due the channels plan list. Calibration data of
+	 * channel 65 saved in calib_db struct in the relevant chan idx place due the BW, as the
+	 * follow:
+	 * chan_idx 16 for BW 20,
+	 * chan_idx 8 for BW 40
+	 * chan_idx 4 for BW 80
+	 * chan_idx 2 for BW 160.
+	 *
+	 * We want to copy calib data of IQ & LOLC from channel 65 to channel 49. Doing the same
+	 * also to the other uncalib channels: 33->17, 65->49, 97->81 etc.
+	 *
+	 * The chan idx of channel 49 in the calib_db by BW is:
+	 * chan_idx 12 for BW 20,
+	 * chan_idx 6 for BW 40
+	 * chan_idx 3 for BW 80
+	 * (no exist chan_idx for BW 160)
+	 *
+	 * We copy the data in calib_db from idx of channel 65 to the idx of channel 49:
+	 * chan_idx 16 to chan_idx 12 (in BW 20)
+	 * chan_idx 8 to chan_idx 6 (in BW 40)
+	 * chan_idx 4 to chan_idx 3 (in BW 80)
+	 *
+	 * In general, the dst chan idx will be calculated by;
+	 * dst_idx = src_idx - 4 (for BW 20)
+	 * dst_idx = src_idx - 2 (for BW 40)
+	 * dst_idx = src_idx - 1 (for BW 80)
+	 *
+	 * The way to calc this is shiftting is by the follow bitmask:
+	 * 4 >> bw
+	 */
+	shift_idx = 4 >> bw;
+
+	return chan_idx_src - shift_idx;
+}
+
+static void cl_calib_iq_copy_data_to_uncalibrated_channels_6g(struct cl_hw *cl_hw)
+{
+	struct cl_calib_db *calib_db = &cl_hw->chip->calib_db;
+	int i;
+	u8 sx = cl_hw->sx_idx;
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u8 chan_idx_src = 0;
+	u8 chan_idx_dst = 0;
+	u8 chain = 0;
+	u8 bw = 0;
+
+	/*
+	 * Copy iq & lo calib data from 6g list plan calibrate channels: 1, 33, 65, 97, 129, 161,
+	 * 193, 22 to uncalibrated channels 17, 49, 81, 113, 145, 177, 209. copy to the correct
+	 * channel idx for each different bw
+	 */
+	for (i = 1; i < CALIB_CHAN_6G_PLAN - 1; i += 2)
+		riu_chain_for_each(chain)
+			/* Iterate only CHNL_BW_80 and CHNL_BW_20 */
+			for (bw = CHNL_BW_20; bw <= CHNL_BW_80; bw += 2) {
+				chan_idx_src =
+					cl_calib_dcoc_channel_bw_to_idx(cl_hw,
+									calib_channels_6g_plan[i],
+									bw);
+
+				chan_idx_dst =
+					cl_calib_iq_convert_plan_to_calib_db_idx(chan_idx_src, bw);
+				memcpy(&calib_db->iq_tx[tcv_idx][chan_idx_dst][bw][sx][chain],
+				       &calib_db->iq_tx[tcv_idx][chan_idx_src][bw][sx][chain],
+				       sizeof(struct cl_iq_calib));
+				memcpy(&calib_db->iq_rx[tcv_idx][chan_idx_dst][bw][sx][chain],
+				       &calib_db->iq_rx[tcv_idx][chan_idx_src][bw][sx][chain],
+				       sizeof(struct cl_iq_calib));
+				calib_db->iq_tx_lolc[tcv_idx][chan_idx_dst][bw][sx][chain] =
+					calib_db->iq_tx_lolc[tcv_idx][chan_idx_src][bw][sx][chain];
+			}
+}
+
+static bool cl_calib_iq_calibrate_6g(struct cl_hw *cl_hw)
+{
+	int i;
+	bool first_channel = true;
+
+	/* Calibrate channels: 1, 33, 65, 97, 129, 161, 193, 225 */
+	for (i = 0; i < CALIB_CHAN_6G_PLAN; i += 2) {
+		if ((cl_calib_iq_calibrate_channel(cl_hw, calib_channels_6g_plan[i], CHNL_BW_160,
+						   first_channel) == 0))
+			first_channel = false;
+
+		if (cl_calib_iq_calibrate_channel(cl_hw, calib_channels_6g_plan[i], CHNL_BW_80,
+						  first_channel) == 0)
+			first_channel = false;
+
+		if (cl_calib_iq_calibrate_channel(cl_hw, calib_channels_6g_plan[i], CHNL_BW_20,
+						  first_channel) == 0)
+			first_channel = false;
+	}
+
+	/*
+	 * For these channels 17, 49, 81, 113, 145, 177, 209
+	 * copy data of next neighbor
+	 */
+	cl_calib_iq_copy_data_to_uncalibrated_channels_6g(cl_hw);
+
+	return first_channel;
+}
+
+static bool cl_calib_iq_calibrate_5g(struct cl_hw *cl_hw)
+{
+	int i;
+	bool first_channel = true;
+
+	if (cl_calib_iq_calibrate_channel(cl_hw, 36, CHNL_BW_160, first_channel) == 0)
+		first_channel = false;
+
+	if (cl_calib_iq_calibrate_channel(cl_hw, 100, CHNL_BW_160, first_channel) == 0)
+		first_channel = false;
+
+	for (i = 0; i < CALIB_CHAN_5G_PLAN; i++) {
+		if (cl_calib_iq_calibrate_channel(cl_hw, calib_channels_5g_plan[i], CHNL_BW_80,
+						  first_channel) == 0)
+			first_channel = false;
+
+		if (cl_calib_iq_calibrate_channel(cl_hw, calib_channels_5g_plan[i], CHNL_BW_20,
+						  first_channel) == 0)
+			first_channel = false;
+	}
+
+	return first_channel;
+}
+
+static bool cl_calib_iq_calibrate_24g(struct cl_hw *cl_hw)
+{
+	int i;
+	bool first_channel = true;
+
+	if (cl_hw->chip->conf->ce_production_mode) {
+		if (cl_calib_iq_calibrate_channel(cl_hw, 1, CHNL_BW_160,
+						  first_channel) == 0)
+			first_channel = false;
+
+		if (cl_calib_iq_calibrate_channel(cl_hw, 1, CHNL_BW_80,
+						  first_channel) == 0)
+			first_channel = false;
+	}
+
+	for (i = 0; i < CALIB_CHAN_24G_MAX; i++) {
+		if (cl_calib_iq_calibrate_channel(cl_hw, calib_channels_24g[i], CHNL_BW_40,
+						  first_channel) == 0)
+			first_channel = false;
+
+		if (cl_calib_iq_calibrate_channel(cl_hw, calib_channels_24g[i], CHNL_BW_20,
+						  first_channel) == 0)
+			first_channel = false;
+	}
+
+	return first_channel;
+}
+
+static void cl_calib_iq_calibrate(struct cl_hw *cl_hw)
+{
+	if (cl_band_is_6g(cl_hw))
+		cl_calib_iq_calibrate_6g(cl_hw);
+	else if (cl_band_is_5g(cl_hw))
+		cl_calib_iq_calibrate_5g(cl_hw);
+	else if (cl_band_is_24g(cl_hw))
+		cl_calib_iq_calibrate_24g(cl_hw);
+}
+
+static void cl_calib_iq_init_calibration_tcv(struct cl_hw *cl_hw)
+{
+	u8 tcv_idx = cl_hw->tcv_idx;
+
+	cl_calib_iq_calibrate(cl_hw);
+
+	cl_hw->chip->iq_dcoc_conf.iq_file_num_ant[tcv_idx] = cl_hw->num_antennas;
+}
+
+void cl_calib_restore_channel(struct cl_hw *cl_hw, struct cl_calib_iq_restore *iq_restore)
+{
+	u8 bw = iq_restore->bw;
+	u32 primary = iq_restore->primary;
+	u32 center = iq_restore->center;
+	u8 channel = iq_restore->channel;
+
+	cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center, CL_CALIB_PARAMS_DEFAULT_STRUCT);
+}
+
+void cl_calib_save_channel(struct cl_hw *cl_hw, struct cl_calib_iq_restore *iq_restore)
+{
+	iq_restore->bw = cl_hw->bw;
+	iq_restore->primary = cl_hw->primary_freq;
+	iq_restore->center = cl_hw->center_freq;
+	iq_restore->channel = ieee80211_frequency_to_channel(cl_hw->primary_freq);
+
+	cl_dbg_chip_trace(cl_hw, "bw = %u, primary = %d, center = %d, channel = %u\n",
+			  iq_restore->bw, iq_restore->primary,
+			  iq_restore->center, iq_restore->channel);
+}
+
+int cl_calib_iq_set_idle(struct cl_hw *cl_hw, bool idle)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+	struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+	bool tcv0_en = cl_chip_is_tcv0_enabled(chip) && cl_radio_is_on(cl_hw_tcv0);
+	bool tcv1_en = cl_chip_is_tcv1_enabled(chip) && cl_radio_is_on(cl_hw_tcv1);
+
+	if (!idle) {
+		if (tcv1_en)
+			cl_msg_tx_set_idle(cl_hw_tcv1, MAC_ACTIVE, false);
+
+		if (tcv0_en)
+			cl_msg_tx_set_idle(cl_hw_tcv0, MAC_ACTIVE, false);
+
+		return 0;
+	}
+
+	if (tcv1_en)
+		cl_msg_tx_idle_async(cl_hw_tcv1, false);
+
+	if (tcv0_en)
+		cl_msg_tx_set_idle(cl_hw_tcv0, MAC_IDLE_SYNC, false);
+
+	cl_dbg_info(cl_hw, "idle_async_set = %u\n", cl_hw->idle_async_set);
+
+	if (wait_event_timeout(cl_hw->wait_queue, !cl_hw->idle_async_set,
+			       CL_MSG_CFM_TIMEOUT_JIFFIES))
+		return 0;
+
+	cl_dbg_err(cl_hw, "Timeout occurred - MM_IDLE_ASYNC_IND\n");
+	return -ETIMEDOUT;
+}
+
+bool cl_calib_iq_calibration_needed(struct cl_hw *cl_hw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	struct cl_iq_dcoc_conf *iq_dcoc_conf = &chip->iq_dcoc_conf;
+	bool calib_needed = false;
+
+	if (!IS_REAL_PHY(chip))
+		return false;
+
+	if (cl_hw->chip->conf->ce_calib_runtime_en)
+		return false;
+
+	if (cl_hw_is_tcv0(cl_hw) && chip->conf->ci_tcv1_chains_sx0)
+		return false;
+
+	if (cl_chip_is_tcv0_enabled(chip)) {
+		u8 num_antennas_tcv0 = chip->cl_hw_tcv0->num_antennas;
+
+		if (iq_dcoc_conf->iq_file_num_ant[TCV0] < num_antennas_tcv0 &&
+		    !chip->conf->ci_tcv1_chains_sx0) {
+			cl_dbg_verbose(cl_hw,
+				       "Num of antennas [%u], is larger than LOLC Calibration File "
+				       "num of antennas [%u], recalibration is needed\n",
+				       num_antennas_tcv0, iq_dcoc_conf->iq_file_num_ant[TCV0]);
+			calib_needed = true;
+		}
+	}
+
+	if (cl_chip_is_tcv1_enabled(chip)) {
+		u8 num_antennas_tcv1 = chip->cl_hw_tcv1->num_antennas;
+
+		if (iq_dcoc_conf->iq_file_num_ant[TCV1] < num_antennas_tcv1) {
+			cl_dbg_verbose(cl_hw,
+				       "Num of antennas [%u], is larger than LOLC Calibration File "
+				       "num of antennas [%u], recalibration is needed\n",
+				       num_antennas_tcv1, iq_dcoc_conf->iq_file_num_ant[TCV1]);
+			calib_needed = true;
+		}
+	}
+
+	return calib_needed;
+}
+
+void cl_calib_iq_init_calibration(struct cl_hw *cl_hw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	u8 fem_mode = cl_hw->fem_mode;
+	struct cl_iq_dcoc_conf *iq_dcoc_conf = &chip->iq_dcoc_conf;
+	struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+	struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+	struct cl_calib_iq_restore iq_restore_tcv0;
+	struct cl_calib_iq_restore iq_restore_tcv1;
+	u8 save_tcv0_needed = cl_hw_tcv0 && cl_hw_tcv0->primary_freq &&
+		!chip->conf->ci_tcv1_chains_sx0;
+	u8 save_tcv1_needed = cl_hw_tcv1 && cl_hw_tcv1->primary_freq;
+
+	if (save_tcv0_needed)
+		cl_calib_save_channel(cl_hw_tcv0, &iq_restore_tcv0);
+
+	if (save_tcv1_needed)
+		cl_calib_save_channel(cl_hw_tcv1, &iq_restore_tcv1);
+
+	cl_fem_set_iq_bypass(cl_hw);
+	cl_afe_cfg_calib(chip);
+
+	if (cl_hw_tcv0 &&
+	    (chip->iq_dcoc_conf.force_calib ||
+	    (iq_dcoc_conf->iq_file_num_ant[TCV0] < cl_hw_tcv0->num_antennas &&
+	    !chip->conf->ci_tcv1_chains_sx0))) {
+		cl_calib_iq_init_calibration_tcv(cl_hw_tcv0);
+	}
+
+	if (cl_hw_tcv1 &&
+	    (chip->iq_dcoc_conf.force_calib ||
+	    iq_dcoc_conf->iq_file_num_ant[TCV1] < cl_hw_tcv1->num_antennas)) {
+		cl_calib_iq_init_calibration_tcv(cl_hw_tcv1);
+	}
+
+	cl_fem_iq_restore(cl_hw, fem_mode);
+	cl_afe_cfg_restore(chip);
+
+	if (save_tcv0_needed)
+		cl_calib_restore_channel(cl_hw_tcv0, &iq_restore_tcv0);
+
+	if (save_tcv1_needed)
+		cl_calib_restore_channel(cl_hw_tcv1, &iq_restore_tcv1);
+}
+
+void cl_calib_iq_fill_data(struct cl_hw *cl_hw, struct cl_iq_calib *iq_data,
+			   struct cl_iq_calib *iq_chip_data)
+{
+	u8 ant = 0;
+
+	for (ant = 0; ant < MAX_ANTENNAS; ant++) {
+		iq_data[ant].coef0 = iq_chip_data[ant].coef0;
+		iq_data[ant].coef1 = iq_chip_data[ant].coef1;
+		iq_data[ant].coef2 = iq_chip_data[ant].coef2;
+		iq_data[ant].gain = iq_chip_data[ant].gain;
+	}
+}
+
+void cl_calib_iq_lolc_fill_data(struct cl_hw *cl_hw, __le32 *iq_lolc)
+{
+	struct cl_calib_db *calib_db = &cl_hw->chip->calib_db;
+	u8 ant = 0;
+	u8 bw = cl_hw->bw;
+	u8 chan_idx = cl_calib_dcoc_channel_bw_to_idx(cl_hw, cl_hw->channel, bw);
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u8 sx = cl_hw->sx_idx;
+
+	for (ant = 0; ant < MAX_ANTENNAS; ant++)
+		iq_lolc[ant] = cpu_to_le32(calib_db->iq_tx_lolc[tcv_idx][chan_idx][bw][sx][ant]);
+}
+
+void cl_calib_iq_handle_set_channel_cfm(struct cl_hw *cl_hw, u8 plan_bitmap)
+{
+	struct calib_cfm *dcoc_iq_cfm =
+		&cl_hw->iq_dcoc_data_info.iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_IQ];
+	s16 calib_temperature = cl_calib_common_get_temperature(cl_hw, CALIB_CFM_IQ);
+	u8 channel = cl_hw->channel;
+	u8 bw = cl_hw->bw;
+
+	CL_DBG(cl_hw, DBG_LVL_TRACE, "calib_temperature = %d, channel = %u, bw = %u\n",
+	       calib_temperature, channel, bw);
+
+	cl_calib_iq_handle_data(cl_hw, calib_temperature, channel, bw, plan_bitmap);
+
+	/*
+	 * Set the default status to FAIL, to ensure FW is actually changing the value,
+	 * if the calibration succeeded.
+	 */
+	dcoc_iq_cfm->status = CALIB_FAIL;
+}
+
+void cl_calib_iq_lolc_handle_set_channel_cfm(struct cl_hw *cl_hw, u8 plan_bitmap)
+{
+	struct calib_cfm *dcoc_iq_cfm =
+		&cl_hw->iq_dcoc_data_info.iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_IQ];
+	s16 calib_temperature = cl_calib_common_get_temperature(cl_hw, CALIB_CFM_IQ);
+	u8 channel = cl_hw->channel;
+	u8 bw = cl_hw->bw;
+
+	cl_dbg_trace(cl_hw, "calib_temperature = %d, channel = %u, bw = %u\n", calib_temperature,
+		     channel, bw);
+
+	cl_calib_iq_lolc_handle_data(cl_hw, calib_temperature, channel, bw, plan_bitmap);
+	cl_calib_iq_lolc_handle_report(cl_hw, calib_temperature, channel, bw, plan_bitmap);
+
+	/*
+	 * Set the default status to FAIL, to ensure FW is actually changing the value,
+	 * if the calibration succeeded.
+	 */
+	dcoc_iq_cfm->status = CALIB_FAIL;
+}
+
+void cl_calib_iq_get_tone_vector(struct cl_hw *cl_hw, __le16 *tone_vector)
+{
+	u8 tone = 0;
+	u8 *vector_ptr = NULL;
+
+	switch (cl_hw->bw) {
+	case CHNL_BW_20:
+		vector_ptr = cl_hw->conf->ci_calib_conf_tone_vector_20bw;
+		break;
+	case CHNL_BW_40:
+		vector_ptr = cl_hw->conf->ci_calib_conf_tone_vector_40bw;
+		break;
+	case CHNL_BW_80:
+		vector_ptr = cl_hw->conf->ci_calib_conf_tone_vector_80bw;
+		break;
+	case CHNL_BW_160:
+		vector_ptr = cl_hw->conf->ci_calib_conf_tone_vector_160bw;
+		break;
+	default:
+		vector_ptr = cl_hw->conf->ci_calib_conf_tone_vector_20bw;
+		break;
+	}
+
+	for (tone = 0; tone < IQ_NUM_TONES_REQ; tone++)
+		tone_vector[tone] = cpu_to_le16((u16)vector_ptr[tone]);
+}
+
+void cl_calib_iq_init_production(struct cl_hw *cl_hw)
+{
+	struct cl_hw *cl_hw_other = NULL;
+	struct cl_chip *chip = cl_hw->chip;
+
+	if (!cl_chip_is_both_enabled(chip) ||
+	    (cl_hw_is_tcv1(cl_hw) && chip->conf->ci_tcv1_chains_sx0)) {
+		if (cl_calib_iq_calibration_needed(cl_hw))
+			cl_calib_iq_init_calibration(cl_hw);
+		return;
+	}
+
+	cl_hw_other = cl_hw_other_tcv(cl_hw);
+	if (!cl_hw_other)
+		return;
+
+	if (cl_hw_other->iq_cal_ready) {
+		cl_hw_other->iq_cal_ready = false;
+		cl_calib_iq_init_calibration(cl_hw);
+	} else if (cl_calib_iq_calibration_needed(cl_hw)) {
+		cl_hw->iq_cal_ready = true;
+		cl_dbg_verbose(cl_hw, "IQ Calibration needed. Wait for both TCVs before starting "
+			       "calibration.\n");
+	}
+}
+
+/*
+ * CL80x0: TCV0 - 5g, TCV1 - 24g
+ * ==============================================
+ * 50  48  46  44  42  40  38  36  --> Start 5g
+ * 100 64  62  60  58  56  54  52
+ * 116 114 112 110 108 106 104 102
+ * 134 132 128 126 124 122 120 118
+ * 153 151 149 144 142 140 138 136
+ * 3   2   1   165 161 159 157 155  --> Start 24g
+ * 11  10  9   8   7   6   5   4
+ *                     14  13  12
+ */
+
+/*
+ * CL80x6: TCV0 - 6g, TCV1 - 5g
+ * ==============================================
+ * 25  21  17  13  9   5   2   1   --> Start 6g
+ * 57  53  49  45  41  37  33  29
+ * 89  85  81  77  73  69  65  61
+ * 121 117 113 109 105 101 97  93
+ * 153 147 143 139 135 131 127 123
+ * 185 181 177 173 169 165 161 157
+ * 217 213 209 205 201 197 193 189
+ * 42  40  38  36  233 229 225 221 --> Start 5g
+ * 58  56  54  52  50  48  46  44
+ * 108 106 104 102 100 64  62  60
+ * 124 122 120 118 116 114 112 110
+ * 142 140 138 136 134 132 128 126
+ * 161 159 157 155 153 151 149 144
+ *                             165
+ */
+
+#define BITMAP_80X0_START_TCV0  0
+#define BITMAP_80X0_MAX_TCV0    NUM_CHANNELS_5G
+
+#define BITMAP_80X0_START_TCV1 NUM_CHANNELS_5G
+#define BITMAP_80X0_MAX_TCV1   (NUM_CHANNELS_5G + NUM_CHANNELS_24G)
+
+#define BITMAP_80X6_START_TCV0  0
+#define BITMAP_80X6_MAX_TCV0    NUM_BITMAP_CHANNELS_6G
+
+#define BITMAP_80X6_START_TCV1  NUM_BITMAP_CHANNELS_6G
+#define BITMAP_80X6_MAX_TCV1    (NUM_BITMAP_CHANNELS_6G + NUM_CHANNELS_5G)
+
+#define INVALID_ADDR 0xffff
+
+static u8 cl_get_bitmap_start_tcv1(struct cl_chip *chip)
+{
+	if (cl_chip_is_6g(chip))
+		return BITMAP_80X6_START_TCV1;
+	else
+		return BITMAP_80X0_START_TCV1;
+}
+
+static u8 cl_idx_to_arr_offset(u8 idx)
+{
+	/* Divide by 8 for array index */
+	return idx >> 3;
+}
+
+static u8 cl_idx_to_bit_offset(u8 idx)
+{
+	/* Reminder is for bit index (assummed array of u8) */
+	return idx & 0x07;
+}
+
+static const u8 bits_cnt_table256[] = {
+	0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
+	1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+	1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+	1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+	3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+	1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+	3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+	3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+	3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+	4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
+};
+
+static bool cl_is_vector_unset(const u8 *bitmap)
+{
+	/* Check bitmap is unset i.e. all values are CURR_BMP_UNSET */
+	u8 empty_bitmap[BIT_MAP_SIZE] = {0};
+
+	return !memcmp(bitmap, empty_bitmap, BIT_MAP_SIZE);
+}
+
+static bool cl_bitmap_test_bit_idx(const u8 *bitmap, u8 idx)
+{
+	/* Check bit at a given index is set i.e. 1 */
+	u8 arr_idx = cl_idx_to_arr_offset(idx), bit_idx = cl_idx_to_bit_offset(idx);
+
+	if (arr_idx >= BIT_MAP_SIZE)
+		return false;
+
+	/* Convert non-zero to true and zero to false */
+	return !!(bitmap[arr_idx] & BIT(bit_idx));
+}
+
+static void cl_bitmap_shift(u8 *bitmap, u8 shft)
+{
+	/* Shifts an array of byte of size len by shft number of bits to the left */
+	u8 bitmap_tmp[BIT_MAP_SIZE] = {0};
+	u8 msb_shifts = shft % 8;
+	u8 lsb_shifts = 8 - msb_shifts;
+	u8 byte_shift = shft / 8;
+	u8 last_byte = BIT_MAP_SIZE - byte_shift - 1;
+	u8 msb_idx;
+	u8 i;
+
+	memcpy(bitmap_tmp, bitmap, BIT_MAP_SIZE);
+	memset(bitmap, 0, BIT_MAP_SIZE);
+
+	for (i = 0;  i < BIT_MAP_SIZE; i++) {
+		if (i <= last_byte) {
+			msb_idx = i + byte_shift;
+			bitmap[i] = bitmap_tmp[msb_idx] >> msb_shifts;
+			if (i != last_byte)
+				bitmap[i] |= bitmap_tmp[msb_idx + 1] << lsb_shifts;
+		}
+	}
+}
+
+static bool cl_bitmap_set_bit_idx(struct cl_hw *cl_hw, u8 *bitmap, u8 bitmap_size, u8 idx)
+{
+	/* Set bit at a given index */
+	u8 arr_idx = cl_idx_to_arr_offset(idx), bit_idx = cl_idx_to_bit_offset(idx);
+
+	if (arr_idx >= bitmap_size) {
+		cl_dbg_err(cl_hw, "invalid arr_idx (%u)\n", arr_idx);
+		return false;
+	}
+
+	bitmap[arr_idx] |= BIT(bit_idx);
+	return true;
+}
+
+static u16 cl_bitmap_look_lsb_up(struct cl_hw *cl_hw, u8 *bitmap, u16 idx, bool ext)
+{
+	/* Find closest set bit with index higher than idx inside bitmap */
+	u16 curr_idx = idx;
+	u8 curr = 0;
+	u32 chan_num = ext ? NUM_EXT_CHANNELS_6G : cl_channel_num(cl_hw);
+
+	while (++curr_idx < chan_num) {
+		curr = bitmap[cl_idx_to_arr_offset(curr_idx)];
+		if (curr & (1ULL << cl_idx_to_bit_offset(curr_idx)))
+			return curr_idx;
+	}
+
+	/* No matching bit found - return original index */
+	return idx;
+}
+
+static u16 bitmap_look_msb_down(struct cl_hw *cl_hw, u8 *bitmap, u16 idx, bool ext)
+{
+	/* Find closest set bit with index lower than idx inside bitmap */
+	u16 curr_idx = idx;
+	u8 curr = 0;
+	u32 chan_num = ext ? NUM_EXT_CHANNELS_6G : cl_channel_num(cl_hw);
+
+	if (idx >= chan_num) {
+		cl_dbg_err(cl_hw, "Invalid channel index [%u]\n", idx);
+		return idx;
+	}
+
+	while (curr_idx-- != 0) {
+		curr = bitmap[cl_idx_to_arr_offset(curr_idx)];
+		if (curr & (1ULL << cl_idx_to_bit_offset(curr_idx)))
+			return curr_idx;
+	}
+
+	/* No matching bit found - return original index */
+	return idx;
+}
+
+static u8 cl_address_offset_tcv1(struct cl_hw *cl_hw)
+{
+	/* Calculate eeprom calibration data offset for tcv1 */
+	struct cl_chip *chip = cl_hw->chip;
+	u8 i, cnt = 0;
+	u8 bitmap[BIT_MAP_SIZE] = {0};
+
+	if (cl_e2p_read(chip, bitmap, BIT_MAP_SIZE, ADDR_CALIB_POWER_CHAN_BMP))
+		return 0;
+
+	for (i = 0; i < cl_get_bitmap_start_tcv1(chip); i++)
+		cnt += cl_bitmap_test_bit_idx(bitmap, i);
+
+	return cnt;
+}
+
+static int cl_point_idx_to_address(struct cl_hw *cl_hw, u8 *bitmap, struct point *pt)
+{
+	/* Calculate eeprom address for a given idx and phy (initiated point) */
+	u8 i, cnt = 0;
+
+	pt->addr = INVALID_ADDR;
+
+	if (!cl_bitmap_test_bit_idx(bitmap, pt->idx))
+		return 0;
+
+	if (pt->phy >= MAX_ANTENNAS) {
+		cl_dbg_err(cl_hw, "Invalid phy number %u", pt->phy);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < pt->idx; i++)
+		cnt += cl_bitmap_test_bit_idx(bitmap, i);
+
+	if (cl_hw_is_tcv1(cl_hw))
+		cnt += cl_address_offset_tcv1(cl_hw);
+
+	pt->addr = ADDR_CALIB_POWER_PHY +
+		sizeof(struct eeprom_phy_calib) * (cnt * MAX_ANTENNAS + pt->phy);
+
+	return 0;
+}
+
+static bool cl_linear_equation_signed(struct cl_hw *cl_hw, const u16 x, s8 *y,
+				      const u16 x0, const s8 y0, const u16 x1, const s8 y1)
+{
+	/* Calculate y given to points (x0,y0) and (x1,y1) and x */
+	s32 numerator = (x - x0) * (y1 - y0);
+	s32 denominator = x1 - x0;
+
+	if (unlikely(!denominator)) {
+		cl_dbg_err(cl_hw, "zero denominator\n");
+		return false;
+	}
+
+	*y = (s8)(y0 + DIV_ROUND_CLOSEST(numerator, denominator));
+
+	return true;
+}
+
+static void cl_extend_bitmap_6g(struct cl_hw *cl_hw, u8 *bitmap, u8 *ext_bitmap)
+{
+	u8 i, ext_idx;
+
+	for (i = 0; i < cl_channel_num(cl_hw); ++i) {
+		if (cl_bitmap_test_bit_idx(bitmap, i)) {
+			ext_idx = CHAN_BITMAP_IDX_6G_2_EXT_IDX(i);
+			cl_bitmap_set_bit_idx(cl_hw, ext_bitmap, EXT_BIT_MAP_SIZE, ext_idx);
+		}
+	}
+}
+
+static bool cl_calculate_calib(struct cl_hw *cl_hw, u8 *bitmap,
+			       struct point *p0, struct point *p1, struct point *p2)
+{
+	/* Main interpolation/extrapolation function */
+	bool calc_succsess = false, use_ext = false;
+	u16 freq0, freq1, freq2;
+	u8 e2p_ext_bitmap[EXT_BIT_MAP_SIZE] = {0};
+	u8 *bitmap_to_use = bitmap;
+
+	if (unlikely(cl_is_vector_unset(bitmap)))
+		return false;
+
+	/*
+	 * In case that the band is 6g and the channel index wasn't found, it might be because
+	 * the channel is missing from the original bitmap (that includes only 20MHz channels).
+	 * In case of center channels in 40/180/160MHz, it can't be found in the original bitmap
+	 * and therefore we need to extend the bitmap to include these channels in order to perform
+	 * interpolation on it.
+	 */
+	if (cl_band_is_6g(cl_hw) && p0->idx == INVALID_CHAN_IDX) {
+		p0->idx = cl_channel_to_ext_index_6g(cl_hw, p0->chan);
+		cl_extend_bitmap_6g(cl_hw, bitmap, e2p_ext_bitmap);
+		bitmap_to_use = e2p_ext_bitmap;
+		use_ext = true;
+	}
+
+	p1->idx = cl_bitmap_look_lsb_up(cl_hw, bitmap_to_use, p0->idx, use_ext);
+	p2->idx = bitmap_look_msb_down(cl_hw, bitmap_to_use, p0->idx, use_ext);
+
+	/* Invalid case */
+	if (p1->idx == p0->idx && p2->idx == p0->idx) {
+		cl_dbg_err(cl_hw, "Invalid index %u or bad bit map\n", p0->idx);
+		return false;
+	}
+
+	/* Extrapolation case */
+	if (p1->idx == p0->idx)
+		p1->idx = bitmap_look_msb_down(cl_hw, bitmap_to_use, p2->idx, use_ext);
+	if (p2->idx == p0->idx)
+		p2->idx = cl_bitmap_look_lsb_up(cl_hw, bitmap_to_use, p1->idx, use_ext);
+
+	if (use_ext) {
+		/* Convert indices from extended bitmap to eeprom bitmap */
+		p1->idx = CHAN_EXT_IDX_6G_2_BITMAP_IDX(p1->idx);
+		p2->idx = CHAN_EXT_IDX_6G_2_BITMAP_IDX(p2->idx);
+	}
+
+	/* Address from index */
+	if (cl_point_idx_to_address(cl_hw, bitmap, p1) || p1->addr == INVALID_ADDR) {
+		cl_dbg_err(cl_hw, "Point calculation failed\n");
+		return false;
+	}
+
+	if (cl_point_idx_to_address(cl_hw, bitmap, p2) || p2->addr == INVALID_ADDR) {
+		cl_dbg_err(cl_hw, "Point calculation failed\n");
+		return false;
+	}
+
+	/* Read from eeprom */
+	if (cl_e2p_read(cl_hw->chip, (u8 *)&p1->calib, sizeof(struct eeprom_phy_calib), p1->addr))
+		return false;
+
+	/* No interpolation required */
+	if (p1->addr == p2->addr) {
+		p0->calib = p1->calib;
+		return true;
+	}
+
+	/* Interpolation or extrapolation is required - read from eeprom */
+	if (cl_e2p_read(cl_hw->chip, (u8 *)&p2->calib, sizeof(struct eeprom_phy_calib), p2->addr))
+		return false;
+
+	freq0 = (use_ext ? cl_channel_ext_idx_to_freq_6g(cl_hw, p0->idx) :
+		 cl_channel_idx_to_freq(cl_hw, p0->idx));
+	freq1 = cl_channel_idx_to_freq(cl_hw, p1->idx);
+	freq2 = cl_channel_idx_to_freq(cl_hw, p2->idx);
+
+	/* Interpolate/extrapolate target power */
+	calc_succsess = cl_linear_equation_signed(cl_hw,
+						  freq0, &p0->calib.pow,
+						  freq1, p1->calib.pow,
+						  freq2, p2->calib.pow);
+
+	/* Interpolate/extrapolate power offset */
+	calc_succsess = calc_succsess && cl_linear_equation_signed(cl_hw,
+								   freq0, &p0->calib.offset,
+								   freq1, p1->calib.offset,
+								   freq2, p2->calib.offset);
+
+	/* Interpolate/extrapolate calibration temperature */
+	calc_succsess = calc_succsess && cl_linear_equation_signed(cl_hw,
+								   freq0, &p0->calib.tmp,
+								   freq1, p1->calib.tmp,
+								   freq2, p2->calib.tmp);
+
+	if (unlikely(!calc_succsess)) {
+		cl_dbg_err(cl_hw,
+			   "Calc failed: freq0 %u idx0 %u%s, freq1 %u idx1 %u, freq2 %u idx2 %u\n",
+			   freq0, p0->idx, use_ext ? " (ext)" : "",
+			   freq1, p1->idx, freq2, p2->idx);
+		return false;
+	}
+
+	return true;
+}
+
+static int cl_read_validate_vector_bitmap(struct cl_hw *cl_hw, u8 *bitmap)
+{
+	struct cl_chip *chip = cl_hw->chip;
+
+	if (cl_e2p_read(chip, bitmap, BIT_MAP_SIZE, ADDR_CALIB_POWER_CHAN_BMP))
+		return -1;
+
+	/* Test if e2p was read succsefull since it is not ALL EMPTY */
+	if (cl_is_vector_unset(bitmap)) {
+		cl_dbg_err(cl_hw, "Vector not ready\n");
+		return -EPERM;
+	}
+
+	if (cl_hw_is_tcv1(cl_hw)) {
+		u8 bitmap_start = cl_get_bitmap_start_tcv1(chip);
+
+		cl_bitmap_shift(bitmap, bitmap_start);
+	}
+
+	return 0;
+}
+
+static int cl_read_or_interpolate_point(struct cl_hw *cl_hw, u8 *bitmap, struct point *p0)
+{
+	struct point p1 = {.phy = p0->phy};
+	struct point p2 = {.phy = p0->phy};
+	struct point tmp_pt = *p0;
+
+	/* Invalid address = no physical address was allocated to this channel */
+	if (tmp_pt.addr != INVALID_ADDR) {
+		if (cl_e2p_read(cl_hw->chip, (u8 *)&tmp_pt.calib,
+				sizeof(struct eeprom_phy_calib), tmp_pt.addr))
+			return -1;
+	} else {
+		/* Interpolate */
+		if (!cl_calculate_calib(cl_hw, bitmap, &tmp_pt, &p1, &p2)) {
+			cl_dbg_err(cl_hw, "Interpolation Error\n");
+			return -EFAULT;
+		}
+	}
+
+	if (tmp_pt.calib.pow == 0 && tmp_pt.calib.offset == 0 && tmp_pt.calib.tmp == 0) {
+		u16 freq = ieee80211_channel_to_frequency(tmp_pt.chan, cl_hw->nl_band);
+
+		cl_dbg_err(cl_hw, "Verify calibration point: addr %x, idx %u, freq %u, phy %u\n",
+			   tmp_pt.addr, tmp_pt.idx, freq, tmp_pt.phy);
+		/* *Uninitiated eeprom value */
+		return -EINVAL;
+	}
+
+	/* Now p0 will contain "Valid" calculations of calib" */
+	p0->calib = tmp_pt.calib;
+	return 0;
+}
+
+static void cl_calib_power_reset(struct cl_hw *cl_hw)
+{
+	u8 ch_idx;
+	u16 phy;
+	u32 chan_num = cl_band_is_6g(cl_hw) ? NUM_EXT_CHANNELS_6G : cl_channel_num(cl_hw);
+	static const struct cl_tx_power_info default_info = {
+		.power       = UNCALIBRATED_POWER,
+		.offset      = UNCALIBRATED_POWER_OFFSET,
+		.temperature = UNCALIBRATED_TEMPERATURE
+	};
+
+	/* Initiate tx_pow_info struct to default values */
+	for (ch_idx = 0; ch_idx < chan_num; ch_idx++)
+		for (phy = 0; phy < MAX_ANTENNAS; phy++)
+			cl_hw->tx_pow_info[ch_idx][phy] = default_info;
+}
+
+static void cl_calib_fill_power_info(struct cl_hw *cl_hw, u8 chan_idx, u8 ant,
+				     struct point *point)
+{
+	cl_hw->tx_pow_info[chan_idx][ant].power = point->calib.pow;
+	cl_hw->tx_pow_info[chan_idx][ant].offset = point->calib.offset;
+	cl_hw->tx_pow_info[chan_idx][ant].temperature = point->calib.tmp;
+}
+
+#define PHY0_OFFSET_FIX_Q2 -8 /* -2db */
+#define PHY3_OFFSET_FIX_Q2 14 /* +3.5db */
+
+static void cl_calib_phy_offset_adjust(struct cl_hw *cl_hw, u8 eeprom_version,
+				       u8 phy, struct point *point)
+{
+	/*
+	 * Work around:
+	 * Add 3.5dB offset to PHY3 if EEPROM version is 0.
+	 * Decrease 2dB offset to all PHYs if EEPROM version is 1.
+	 */
+	if (!cl_chip_is_6g(cl_hw->chip)) {
+		if (cl_band_is_5g(cl_hw) && eeprom_version == 0 && phy == 3)
+			point->calib.offset += PHY3_OFFSET_FIX_Q2;
+		else if (cl_band_is_24g(cl_hw) && eeprom_version == 1)
+			point->calib.offset += PHY0_OFFSET_FIX_Q2;
+	}
+}
+
+void cl_calib_power_read(struct cl_hw *cl_hw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	int ret;
+	u8 bitmap[BIT_MAP_SIZE] = {0};
+	struct point curr_point = {0};
+	u8 *phy = &curr_point.phy;
+	u8 *ch_idx = &curr_point.idx;
+	u8 ext_chan_idx = 0;
+	u8 ant;
+	u8 eeprom_version = chip->eeprom_cache->general.version;
+	u32 tmp_freq = 0;
+
+	/* Initiate tx_pow_info struct to default values */
+	cl_calib_power_reset(cl_hw);
+
+	/* Vector not initiated set table to default values */
+	if (unlikely(cl_read_validate_vector_bitmap(cl_hw, bitmap))) {
+		cl_dbg_trace(cl_hw, "initiate to default values\n");
+		return;
+	}
+
+	/* Perform only on calibrated boards - cl_read_validate_vector_bitmap succeeded (0) */
+	for (*ch_idx = 0; *ch_idx < cl_channel_num(cl_hw); (*ch_idx)++) {
+		for (ant = 0; ant < MAX_ANTENNAS; ant++) {
+			if (!(cl_hw->mask_num_antennas & BIT(ant)))
+				continue;
+
+			/*
+			 * In old eeprom versions (< 3) power info was saved in eeprom
+			 * per riu chain (unintentionally) so we need to fetch it accordingly
+			 */
+			if (eeprom_version < 3)
+				*phy = cl_hw_ant_to_riu_chain(cl_hw, ant);
+			else
+				*phy = ant;
+
+			ret = cl_point_idx_to_address(cl_hw, bitmap, &curr_point);
+
+			if (ret) {
+				/* Don't overwrite default values */
+				cl_dbg_err(cl_hw, "point idx to address failed\n");
+				continue;
+			}
+
+			if (cl_band_is_6g(cl_hw)) {
+				tmp_freq = cl_channel_idx_to_freq(cl_hw, *ch_idx);
+				curr_point.chan = ieee80211_frequency_to_channel(tmp_freq);
+			}
+
+			ret = cl_read_or_interpolate_point(cl_hw, bitmap, &curr_point);
+			/* Unable to calculate new value ==> DON'T overwrite default values */
+			if (unlikely(ret))
+				continue;
+
+			cl_calib_phy_offset_adjust(cl_hw, eeprom_version, *phy, &curr_point);
+
+			if (cl_band_is_6g(cl_hw))
+				ext_chan_idx = CHAN_BITMAP_IDX_6G_2_EXT_IDX(*ch_idx);
+			else
+				ext_chan_idx = *ch_idx;
+
+			cl_calib_fill_power_info(cl_hw, ext_chan_idx, ant, &curr_point);
+		}
+	}
+
+	if (!cl_band_is_6g(cl_hw))
+		goto calib_read_out;
+
+	/*
+	 * Fill info for channels that are missing from the original bitmap, which are the
+	 * center channels in 40/180/160MHz (channels 3, 7, 11, etc..).
+	 */
+	for (ext_chan_idx = 0; ext_chan_idx < NUM_EXT_CHANNELS_6G; ext_chan_idx++) {
+		/* Skip channels that were already filled above */
+		tmp_freq = cl_channel_ext_idx_to_freq_6g(cl_hw, ext_chan_idx);
+
+		/* Chan field needs to be updated before calling to cl_read_or_interpolate_point */
+		curr_point.chan = ieee80211_frequency_to_channel(tmp_freq);
+
+		/* If the channel is found in the bitmap - we already handled it above */
+		if (cl_channel_to_bitmap_index(cl_hw, curr_point.chan) != INVALID_CHAN_IDX)
+			continue;
+
+		for (ant = 0; ant < MAX_ANTENNAS; ant++) {
+			if (!(cl_hw->mask_num_antennas & BIT(ant)))
+				continue;
+
+			/*
+			 * In old eeprom versions (< 3) power info was saved in eeprom
+			 * per riu chain (unintentionally) so we need to fetch it accordingly
+			 */
+			if (eeprom_version < 3)
+				*phy = cl_hw_ant_to_riu_chain(cl_hw, ant);
+			else
+				*phy = ant;
+
+			/*
+			 * Addr and idx fields needs to be invalid to successfully interpolate the
+			 * power info on the extended eeprom bitmap.
+			 */
+			curr_point.addr = INVALID_ADDR;
+			curr_point.idx = INVALID_CHAN_IDX;
+
+			ret = cl_read_or_interpolate_point(cl_hw, bitmap, &curr_point);
+
+			/* Unable to calculate new value ==> DON'T overwrite default values */
+			if (unlikely(ret))
+				continue;
+
+			cl_calib_fill_power_info(cl_hw, ext_chan_idx, ant, &curr_point);
+		}
+	}
+calib_read_out:
+	cl_dbg_trace(cl_hw, "Created tx_pow_info\n");
+}
+
+void cl_calib_power_offset_fill(struct cl_hw *cl_hw, u8 channel,
+				u8 bw, u8 offset[MAX_ANTENNAS])
+{
+	u8 i, chain;
+	u8 chan_idx = cl_channel_to_index(cl_hw, cl_hw->channel);
+	s8 pow_offset;
+	s8 signed_offset;
+
+	if (chan_idx == INVALID_CHAN_IDX)
+		return;
+
+	for (i = 0; i < MAX_ANTENNAS; i++) {
+		if (!(cl_hw->mask_num_antennas & BIT(i)))
+			continue;
+
+		pow_offset = cl_hw->tx_pow_info[chan_idx][i].offset;
+		signed_offset = cl_power_offset_check_margin(cl_hw, bw, i, pow_offset);
+		chain = cl_hw_ant_to_riu_chain(cl_hw, i);
+		offset[chain] = cl_convert_signed_to_reg_value(signed_offset);
+	}
+}
+
+struct cl_runtime_work {
+	struct work_struct ws;
+	struct cl_hw *cl_hw;
+	u32 channel;
+	u8 bw;
+	u16 primary;
+	u16 center;
+};
+
+static int _cl_calib_runtime_and_switch_channel(struct cl_hw *cl_hw, u32 channel, u8 bw,
+						u16 primary, u16 center,
+						struct cl_calib_params calib_params)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	struct cl_calib_iq_restore iq_restore_other_tcv;
+	struct cl_hw *cl_hw_other = cl_hw_other_tcv(cl_hw);
+	int ret = 0;
+	u8 fem_mode = cl_hw->fem_mode;
+	u8 save_other_tcv_needed = cl_chip_is_both_enabled(chip) && cl_hw_other &&
+					!!cl_hw_other->primary_freq;
+
+	cl_hw->calib_runtime_needed = false;
+
+	if (save_other_tcv_needed)
+		cl_calib_save_channel(cl_hw_other, &iq_restore_other_tcv);
+
+	if (cl_chip_is_both_enabled(chip)) {
+		ret = cl_calib_iq_set_idle(cl_hw, true);
+		if (ret)
+			return ret;
+	}
+
+	cl_fem_set_iq_bypass(cl_hw);
+	cl_afe_cfg_calib(chip);
+
+	/* Calibration by the default values */
+	if (cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center, calib_params)) {
+		cl_dbg_chip_err(cl_hw, "Failed to calibrate channel %u, bw %u\n",
+				channel, bw);
+		ret = -1;
+	}
+
+	cl_fem_iq_restore(cl_hw, fem_mode);
+	cl_afe_cfg_restore(chip);
+
+	if (save_other_tcv_needed)
+		cl_calib_restore_channel(cl_hw_other, &iq_restore_other_tcv);
+
+	/* Set channel to load the new calib data */
+	ret += cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center,
+				     CL_CALIB_PARAMS_DEFAULT_STRUCT);
+
+	if (cl_chip_is_both_enabled(chip))
+		cl_calib_iq_set_idle(cl_hw, false);
+
+	return ret;
+}
+
+static void cl_calib_runtime_work_handler(struct work_struct *ws)
+{
+	struct cl_runtime_work *runtime_work = container_of(ws, struct cl_runtime_work, ws);
+
+	cl_calib_runtime_and_switch_channel(runtime_work->cl_hw, runtime_work->channel,
+					    runtime_work->bw, runtime_work->primary,
+					    runtime_work->center);
+
+	kfree(runtime_work);
+}
+
+static bool cl_calib_runtime_is_channel_calibrated(struct cl_hw *cl_hw, u8 channel, u8 bw)
+{
+	int chain, lna;
+	u8 chan_idx = cl_calib_dcoc_channel_bw_to_idx(cl_hw, channel, bw);
+	u8 tcv_idx = cl_hw->tcv_idx;
+	u8 sx = cl_hw->sx_idx;
+
+	riu_chain_for_each(chain) {
+		if (!cl_hw->chip->calib_db.iq_tx[tcv_idx][chan_idx][bw][sx][chain].gain &&
+		    !cl_hw->chip->calib_db.iq_tx[tcv_idx][chan_idx][bw][sx][chain].coef0 &&
+		    !cl_hw->chip->calib_db.iq_tx[tcv_idx][chan_idx][bw][sx][chain].coef1 &&
+		    !cl_hw->chip->calib_db.iq_tx[tcv_idx][chan_idx][bw][sx][chain].coef2) {
+			cl_dbg_trace(cl_hw, "IQ TX calibration data is missing\n");
+			return false;
+		}
+
+		if (!cl_hw->chip->calib_db.iq_rx[tcv_idx][chan_idx][bw][sx][chain].gain &&
+		    !cl_hw->chip->calib_db.iq_rx[tcv_idx][chan_idx][bw][sx][chain].coef0 &&
+		    !cl_hw->chip->calib_db.iq_rx[tcv_idx][chan_idx][bw][sx][chain].coef1 &&
+		    !cl_hw->chip->calib_db.iq_rx[tcv_idx][chan_idx][bw][sx][chain].coef2) {
+			cl_dbg_trace(cl_hw, "IQ RX calibration data is missing\n");
+			return false;
+		}
+
+		if (!cl_hw->chip->calib_db.iq_tx_lolc[tcv_idx][chan_idx][bw][sx][chain]) {
+			cl_dbg_trace(cl_hw, "LOLC calibration data is missing\n");
+			return false;
+		}
+	}
+
+	for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++) {
+		riu_chain_for_each(chain) {
+			if (!cl_hw->chip->calib_db.dcoc[tcv_idx][chan_idx][bw][sx][chain][lna].i &&
+			    !cl_hw->chip->calib_db.dcoc[tcv_idx][chan_idx][bw][sx][chain][lna].q) {
+				cl_dbg_trace(cl_hw, "DCOC calibration data is missing\n");
+				return false;
+			}
+		}
+	}
+
+	/* If all the calibration data of channel exist */
+	return true;
+}
+
+bool cl_calib_runtime_is_allowed(struct cl_hw *cl_hw)
+{
+	if (!cl_hw)
+		return false;
+
+	if (cl_hw->scanner && cl_is_scan_in_progress(cl_hw->scanner))
+		return false;
+
+	return true;
+}
+
+void cl_calib_runtime_work(struct cl_hw *cl_hw, u32 channel, u8 bw, u16 primary,
+			   u16 center)
+{
+	struct cl_runtime_work *runtime_work = kzalloc(sizeof(*runtime_work), GFP_ATOMIC);
+
+	if (!runtime_work)
+		return;
+
+	runtime_work->cl_hw = cl_hw;
+	runtime_work->channel = channel;
+	runtime_work->bw = bw;
+	runtime_work->primary = primary;
+	runtime_work->center = center;
+	INIT_WORK(&runtime_work->ws, cl_calib_runtime_work_handler);
+	queue_work(cl_hw->drv_workqueue, (struct work_struct *)(&runtime_work->ws));
+}
+
+int cl_calib_runtime_and_switch_channel(struct cl_hw *cl_hw, u32 channel, u8 bw, u32 primary,
+					u32 center)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	struct cl_hw *cl_hw_other = cl_hw_other_tcv(cl_hw);
+	struct cl_calib_params calib_params = {SET_CHANNEL_MODE_CALIB, false, SX_FREQ_OFFSET_Q2, 0};
+	int ret = 0;
+	bool calib_needed = (cl_hw->chip->conf->ci_calib_runtime_force ||
+			     !cl_calib_runtime_is_channel_calibrated(cl_hw, channel, bw)) &&
+			    !cl_hw->sw_scan_in_progress;
+
+	mutex_lock(&cl_hw->chip->calib_runtime_mutex);
+
+	if (!calib_needed || !cl_calib_runtime_is_allowed(cl_hw) ||
+	    (cl_chip_is_both_enabled(chip) && !cl_calib_runtime_is_allowed(cl_hw_other))) {
+		if (calib_needed)
+			cl_hw->calib_runtime_needed = true;
+
+		/* Switch channel without calibration */
+		ret = cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center,
+					    CL_CALIB_PARAMS_DEFAULT_STRUCT);
+		mutex_unlock(&cl_hw->chip->calib_runtime_mutex);
+
+		return ret;
+	}
+
+	/* Convert ant to riu chain in the calib plan_bitmap */
+	calib_params.plan_bitmap = cl_hw_ant_mask_to_riu_chain_mask(cl_hw,
+								    cl_hw->mask_num_antennas);
+
+	/* This mutex needs to be held during the whole calibration process */
+	mutex_lock(&cl_hw->chip->set_idle_mutex);
+	ret = _cl_calib_runtime_and_switch_channel(cl_hw, channel, bw, primary, center,
+						   calib_params);
+	mutex_unlock(&cl_hw->chip->set_idle_mutex);
+
+	mutex_unlock(&cl_hw->chip->calib_runtime_mutex);
+
+	return ret;
+}
+