All of lore.kernel.org
 help / color / mirror / Atom feed
From: viktor.barna@celeno.com
To: linux-wireless@vger.kernel.org
Cc: Kalle Valo <kvalo@codeaurora.org>,
	"David S . Miller" <davem@davemloft.net>,
	Jakub Kicinski <kuba@kernel.org>,
	Aviad Brikman <aviad.brikman@celeno.com>,
	Eliav Farber <eliav.farber@gmail.com>,
	Maksym Kokhan <maksym.kokhan@celeno.com>,
	Oleksandr Savchenko <oleksandr.savchenko@celeno.com>,
	Shay Bar <shay.bar@celeno.com>,
	Viktor Barna <viktor.barna@celeno.com>
Subject: [RFC v2 09/96] cl8k: add calib.c
Date: Tue, 24 May 2022 14:33:35 +0300	[thread overview]
Message-ID: <20220524113502.1094459-10-viktor.barna@celeno.com> (raw)
In-Reply-To: <20220524113502.1094459-1-viktor.barna@celeno.com>

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 --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;
+}
+
-- 
2.36.1


  parent reply	other threads:[~2022-05-24 11:37 UTC|newest]

Thread overview: 125+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-24 11:33 [RFC v2 00/96] wireless: cl8k driver for Celeno IEEE 802.11ax devices viktor.barna
2022-05-24 11:33 ` [RFC v2 01/96] celeno: add Kconfig viktor.barna
2022-05-24 11:33 ` [RFC v2 02/96] celeno: add Makefile viktor.barna
2022-05-24 11:33 ` [RFC v2 03/96] cl8k: add Kconfig viktor.barna
2022-05-26 18:18   ` Johannes Berg
2022-05-27  6:09     ` Kalle Valo
2022-07-11 23:04       ` Viktor Barna
2022-07-13  7:32   ` Kalle Valo
2022-05-24 11:33 ` [RFC v2 04/96] cl8k: add Makefile viktor.barna
2022-05-26 18:24   ` Johannes Berg
2022-07-13  7:39   ` Kalle Valo
2022-05-24 11:33 ` [RFC v2 05/96] cl8k: add ampdu.c viktor.barna
2022-05-26 18:19   ` Johannes Berg
2022-05-26 18:22   ` Johannes Berg
2022-05-24 11:33 ` [RFC v2 06/96] cl8k: add ampdu.h viktor.barna
2022-05-24 11:33 ` [RFC v2 07/96] cl8k: add bf.c viktor.barna
2022-05-24 17:24   ` Jeff Johnson
2022-05-24 11:33 ` [RFC v2 08/96] cl8k: add bf.h viktor.barna
2022-05-24 11:33 ` viktor.barna [this message]
2022-05-24 11:33 ` [RFC v2 10/96] cl8k: add calib.h viktor.barna
2022-05-24 11:33 ` [RFC v2 11/96] cl8k: add channel.c viktor.barna
2022-05-24 11:33 ` [RFC v2 12/96] cl8k: add channel.h viktor.barna
2022-05-24 11:33 ` [RFC v2 13/96] cl8k: add chip.c viktor.barna
2022-05-24 11:33 ` [RFC v2 14/96] cl8k: add chip.h viktor.barna
2022-05-24 11:33 ` [RFC v2 15/96] cl8k: add config.c viktor.barna
2022-05-24 11:33 ` [RFC v2 16/96] cl8k: add config.h viktor.barna
2022-05-25 18:31   ` Jeff Johnson
2022-05-24 11:33 ` [RFC v2 17/96] cl8k: add debug.c viktor.barna
2022-05-24 11:33 ` [RFC v2 18/96] cl8k: add debug.h viktor.barna
2022-05-24 11:33 ` [RFC v2 19/96] cl8k: add def.h viktor.barna
2022-05-25 18:39   ` Jeff Johnson
2022-05-24 11:33 ` [RFC v2 20/96] cl8k: add dfs.c viktor.barna
2022-05-24 11:33 ` [RFC v2 21/96] cl8k: add dfs.h viktor.barna
2022-05-24 11:33 ` [RFC v2 22/96] cl8k: add dsp.c viktor.barna
2022-05-24 11:33 ` [RFC v2 23/96] cl8k: add dsp.h viktor.barna
2022-05-24 11:33 ` [RFC v2 24/96] cl8k: add e2p.c viktor.barna
2022-05-24 11:33 ` [RFC v2 25/96] cl8k: add e2p.h viktor.barna
2022-05-24 11:33 ` [RFC v2 26/96] cl8k: add eeprom.h viktor.barna
2022-05-24 11:33 ` [RFC v2 27/96] cl8k: add ela.c viktor.barna
2022-05-24 11:33 ` [RFC v2 28/96] cl8k: add ela.h viktor.barna
2022-05-24 11:33 ` [RFC v2 29/96] cl8k: add enhanced_tim.c viktor.barna
2022-05-24 11:33 ` [RFC v2 30/96] cl8k: add enhanced_tim.h viktor.barna
2022-05-24 11:33 ` [RFC v2 31/96] cl8k: add fw.c viktor.barna
2022-05-24 11:33 ` [RFC v2 32/96] cl8k: add fw.h viktor.barna
2022-05-25 18:58   ` Jeff Johnson
2022-05-24 11:33 ` [RFC v2 33/96] cl8k: add hw.c viktor.barna
2022-05-24 11:34 ` [RFC v2 34/96] cl8k: add hw.h viktor.barna
2022-05-24 11:34 ` [RFC v2 35/96] cl8k: add ipc_shared.h viktor.barna
2022-05-24 11:34 ` [RFC v2 36/96] cl8k: add key.c viktor.barna
2022-05-26 19:38   ` Johannes Berg
2022-07-11 23:10     ` Viktor Barna
2022-05-24 11:34 ` [RFC v2 37/96] cl8k: add key.h viktor.barna
2022-05-24 11:34 ` [RFC v2 38/96] cl8k: add mac80211.c viktor.barna
2022-05-26 19:49   ` Johannes Berg
2022-07-11 23:13     ` Viktor Barna
2022-05-24 11:34 ` [RFC v2 39/96] cl8k: add mac80211.h viktor.barna
2022-05-26 19:52   ` Johannes Berg
2022-05-24 11:34 ` [RFC v2 40/96] cl8k: add mac_addr.c viktor.barna
2022-05-26 22:31   ` Jeff Johnson
2022-05-24 11:34 ` [RFC v2 41/96] cl8k: add mac_addr.h viktor.barna
2022-05-24 11:34 ` [RFC v2 42/96] cl8k: add main.c viktor.barna
2022-05-26 23:01   ` Jeff Johnson
2022-05-24 11:34 ` [RFC v2 43/96] cl8k: add main.h viktor.barna
2022-05-24 11:34 ` [RFC v2 44/96] cl8k: add maintenance.c viktor.barna
2022-05-24 11:34 ` [RFC v2 45/96] cl8k: add maintenance.h viktor.barna
2022-05-24 11:34 ` [RFC v2 46/96] cl8k: add motion_sense.c viktor.barna
2022-05-24 11:34 ` [RFC v2 47/96] cl8k: add motion_sense.h viktor.barna
2022-05-24 11:34 ` [RFC v2 48/96] cl8k: add pci.c viktor.barna
2022-05-24 11:34 ` [RFC v2 49/96] cl8k: add pci.h viktor.barna
2022-05-24 11:34 ` [RFC v2 50/96] cl8k: add phy.c viktor.barna
2022-06-01  0:27   ` Jeff Johnson
2022-07-11 23:16     ` Viktor Barna
2022-05-24 11:34 ` [RFC v2 51/96] cl8k: add phy.h viktor.barna
2022-05-24 11:34 ` [RFC v2 52/96] cl8k: add platform.c viktor.barna
2022-05-24 11:34 ` [RFC v2 53/96] cl8k: add platform.h viktor.barna
2022-05-24 11:34 ` [RFC v2 54/96] cl8k: add power.c viktor.barna
2022-05-24 11:34 ` [RFC v2 55/96] cl8k: add power.h viktor.barna
2022-05-24 11:34 ` [RFC v2 56/96] cl8k: add radio.c viktor.barna
2022-05-24 11:34 ` [RFC v2 57/96] cl8k: add radio.h viktor.barna
2022-05-24 11:34 ` [RFC v2 58/96] cl8k: add rates.c viktor.barna
2022-05-24 11:34 ` [RFC v2 59/96] cl8k: add rates.h viktor.barna
2022-05-26 19:54   ` Johannes Berg
2022-07-11 23:17     ` Viktor Barna
2022-07-12  7:17       ` Johannes Berg
2022-05-24 11:34 ` [RFC v2 60/96] cl8k: add recovery.c viktor.barna
2022-05-24 11:34 ` [RFC v2 61/96] cl8k: add recovery.h viktor.barna
2022-05-24 11:34 ` [RFC v2 62/96] cl8k: add regdom.c viktor.barna
2022-05-24 11:34 ` [RFC v2 63/96] cl8k: add regdom.h viktor.barna
2022-05-24 11:34 ` [RFC v2 64/96] cl8k: add reg/reg_access.h viktor.barna
2022-05-24 11:34 ` [RFC v2 65/96] cl8k: add reg/reg_defs.h viktor.barna
2022-05-24 11:34 ` [RFC v2 66/96] cl8k: add rfic.c viktor.barna
2022-05-24 11:34 ` [RFC v2 67/96] cl8k: add rfic.h viktor.barna
2022-06-02 20:40   ` Jeff Johnson
2022-07-11 23:18     ` Viktor Barna
2022-05-24 11:34 ` [RFC v2 68/96] cl8k: add rx.c viktor.barna
2022-05-24 11:34 ` [RFC v2 69/96] cl8k: add rx.h viktor.barna
2022-05-24 11:34 ` [RFC v2 70/96] cl8k: add scan.c viktor.barna
2022-05-24 11:34 ` [RFC v2 71/96] cl8k: add scan.h viktor.barna
2022-05-24 11:34 ` [RFC v2 72/96] cl8k: add sounding.c viktor.barna
2022-05-24 11:34 ` [RFC v2 73/96] cl8k: add sounding.h viktor.barna
2022-05-24 11:34 ` [RFC v2 74/96] cl8k: add sta.c viktor.barna
2022-05-24 11:34 ` [RFC v2 75/96] cl8k: add sta.h viktor.barna
2022-05-24 11:34 ` [RFC v2 76/96] cl8k: add stats.c viktor.barna
2022-06-02 20:59   ` Jeff Johnson
2022-07-11 23:20     ` Viktor Barna
2022-05-24 11:34 ` [RFC v2 77/96] cl8k: add stats.h viktor.barna
2022-05-24 11:34 ` [RFC v2 78/96] cl8k: add tcv.c viktor.barna
2022-05-24 11:34 ` [RFC v2 79/96] cl8k: add tcv.h viktor.barna
2022-05-24 11:34 ` [RFC v2 80/96] cl8k: add temperature.c viktor.barna
2022-05-24 11:34 ` [RFC v2 81/96] cl8k: add temperature.h viktor.barna
2022-05-24 11:34 ` [RFC v2 82/96] cl8k: add traffic.c viktor.barna
2022-05-24 11:34 ` [RFC v2 83/96] cl8k: add traffic.h viktor.barna
2022-05-24 11:34 ` [RFC v2 84/96] cl8k: add tx.c viktor.barna
2022-05-24 11:34 ` [RFC v2 85/96] cl8k: add tx.h viktor.barna
2022-05-24 11:34 ` [RFC v2 86/96] cl8k: add utils.c viktor.barna
2022-05-24 11:34 ` [RFC v2 87/96] cl8k: add utils.h viktor.barna
2022-05-24 11:34 ` [RFC v2 88/96] cl8k: add version.c viktor.barna
2022-05-24 11:34 ` [RFC v2 89/96] cl8k: add version.h viktor.barna
2022-05-24 11:34 ` [RFC v2 90/96] cl8k: add vif.c viktor.barna
2022-05-24 11:34 ` [RFC v2 91/96] cl8k: add vif.h viktor.barna
2022-05-24 11:34 ` [RFC v2 92/96] cl8k: add vns.c viktor.barna
2022-05-24 11:34 ` [RFC v2 93/96] cl8k: add vns.h viktor.barna
2022-05-24 11:35 ` [RFC v2 94/96] cl8k: add wrs.c viktor.barna
2022-05-24 11:35 ` [RFC v2 95/96] cl8k: add wrs.h viktor.barna
2022-05-24 11:35 ` [RFC v2 96/96] wireless: add Celeno vendor viktor.barna

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220524113502.1094459-10-viktor.barna@celeno.com \
    --to=viktor.barna@celeno.com \
    --cc=aviad.brikman@celeno.com \
    --cc=davem@davemloft.net \
    --cc=eliav.farber@gmail.com \
    --cc=kuba@kernel.org \
    --cc=kvalo@codeaurora.org \
    --cc=linux-wireless@vger.kernel.org \
    --cc=maksym.kokhan@celeno.com \
    --cc=oleksandr.savchenko@celeno.com \
    --cc=shay.bar@celeno.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.