new file mode 100644
@@ -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, ¢er)) {
+ 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, ¢er)) {
+ 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;
+}
+