diff mbox series

[RFC,v1,028/256] cl8k: add calib.c

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

Commit Message

Viktor Barna June 17, 2021, 3:58 p.m. UTC
From: Viktor Barna <viktor.barna@celeno.com>

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

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

--
2.30.0
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/calib.c b/drivers/net/wireless/celeno/cl8k/calib.c
new file mode 100644
index 000000000000..8861964e3aff
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/calib.c
@@ -0,0 +1,1682 @@ 
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include <linux/string.h>
+#include <linux/bitops.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+
+#include "calib.h"
+#include "temperature.h"
+#include "utils/utils.h"
+#include "chip.h"
+#include "chandef.h"
+#include "fw/msg_cfm.h"
+#include "fw/msg_tx.h"
+#include "band.h"
+#include "e2p.h"
+#include "channel.h"
+#include "power.h"
+#include "afe.h"
+#include "radio.h"
+
+/*
+ * 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_CHANNELS_6G
+
+#define BITMAP_80X6_START_TCV1  NUM_CHANNELS_6G
+#define BITMAP_80X6_MAX_TCV1    (NUM_CHANNELS_6G + NUM_CHANNELS_5G)
+
+#define INVALID_ADDR 0xffff
+
+#define S12_S_BIT (0x00000800)
+#define U12_BIT_MASK (0x00000FFF)
+#define CAST_S12_TO_S32(i) ((~(i) & S12_S_BIT) ? (i) : ((i) | ~U12_BIT_MASK))
+
+static const u8 calib_channels_24g[CALIB_CHAN_24G_MAX] = {
+       1, 6, 11
+};
+
+static const u8 calib_channels_5g[CALIB_CHAN_5G_MAX] = {
+       36, 52, 100, 116, 132, 149
+};
+
+static const u8 calib_channels_6g[CALIB_CHAN_6G_MAX] = {
+       1, 17, 33, 49, 65, 81, 97, 113, 129, 145, 161, 177, 193, 209, 225
+};
+
+static u8 tone_vector_arr[CHNL_BW_MAX][IQ_NUM_TONES_REQ] = {
+       {6, 10, 14, 18, 22, 24, 26, 27},
+       {10, 18, 26, 34, 41, 48, 53, 58},
+       {18, 34, 50, 66, 82, 98, 110, 122},
+       {18, 34, 66, 98, 130, 164, 224, 250}
+};
+
+static u8 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 void get_bitmap_boundaries(struct cl_chip *chip, u8 tcv_idx, u8 *start, u8 *max)
+{
+       if (cl_chip_is_6g(chip)) {
+               if (tcv_idx == TCV0) {
+                       *start = BITMAP_80X6_START_TCV0;
+                       *max = BITMAP_80X6_MAX_TCV0;
+               } else {
+                       *start = BITMAP_80X6_START_TCV1;
+                       *max = BITMAP_80X6_MAX_TCV1;
+               }
+       } else {
+               if (tcv_idx == TCV0) {
+                       *start = BITMAP_80X0_START_TCV0;
+                       *max = BITMAP_80X0_MAX_TCV0;
+               } else {
+                       *start = BITMAP_80X0_START_TCV1;
+                       *max = BITMAP_80X0_MAX_TCV1;
+               }
+       }
+}
+
+static u8 idx_to_arr_offset(u8 idx)
+{
+       /* Divide by 8 for array index */
+       return idx >> 3;
+}
+
+static u8 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 u8 count_bits(const u8 *bitmap)
+{
+       /*
+        * Count bits in a given u8 array ASSUMED ARRAY SIZE IS BIT_MAP_SIZE
+        * bitmap - pointer to u8 array (bitmap)
+        */
+       u8 i = 0, cnt = 0;
+
+       for (i = 0; i < BIT_MAP_SIZE; i++)
+               cnt += bits_cnt_table256[bitmap[i]];
+
+       return cnt;
+}
+
+static bool 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 bitmap_test_bit_idx(const u8 *bitmap, u8 idx)
+{
+       /* Check bit at a given index is set i.e. 1 */
+       u8 arr_idx = idx_to_arr_offset(idx), bit_idx = 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 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 bitmap_set_bit_idx(struct cl_hw *cl_hw, u8 *bitmap, u8 idx)
+{
+       /* Set bit at a given index */
+       u8 arr_idx = idx_to_arr_offset(idx), bit_idx = idx_to_bit_offset(idx);
+
+       if (arr_idx >= BIT_MAP_SIZE) {
+               cl_dbg_err(cl_hw, "invalid arr_idx (%u)\n", arr_idx);
+               return false;
+       }
+
+       bitmap[arr_idx] |= BIT(bit_idx);
+       return true;
+}
+
+static bool bitmap_clear_bit_idx(struct cl_hw *cl_hw, u8 *bitmap, u8 idx)
+{
+       /* Clear bit at a given index */
+       u8 arr_idx = idx_to_arr_offset(idx), bit_idx = idx_to_bit_offset(idx);
+
+       if (arr_idx >= BIT_MAP_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 bitmap_look_lsb_up(struct cl_hw *cl_hw, u8 *bitmap, u16 idx)
+{
+       /* Find closest ON(1) bit with index haigher than idx inside bitmap */
+       u16 curr_idx = idx;
+       u8 curr = 0;
+
+       while (++curr_idx < cl_channel_num(cl_hw)) {
+               curr = bitmap[idx_to_arr_offset(curr_idx)];
+               if (curr & (1ULL << 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)
+{
+       /* Find closest ON(1) bit with index lower than idx inside bitmap */
+       u16 curr_idx = idx;
+       u8 curr = 0;
+
+       if (idx >= cl_channel_num(cl_hw)) {
+               cl_dbg_err(cl_hw, "Invalid channel index [%u]\n", idx);
+               return idx;
+       }
+
+       while (curr_idx-- != 0) {
+               curr = bitmap[idx_to_arr_offset(curr_idx)];
+               if (curr & (1ULL << idx_to_bit_offset(curr_idx)))
+                       return curr_idx;
+       }
+
+       /* No matching bit found - return original index */
+       return idx;
+}
+
+static u8 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_CHAN_BMP))
+               return 0;
+
+       for (i = 0; i < get_bitmap_start_tcv1(chip); i++)
+               cnt += bitmap_test_bit_idx(bitmap, i);
+
+       return cnt;
+}
+
+static int 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 (!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 += bitmap_test_bit_idx(bitmap, i);
+
+       if (cl_hw_is_tcv1(cl_hw))
+               cnt += address_offset_tcv1(cl_hw);
+
+       pt->addr = ADDR_CALIB_PHY +
+               sizeof(struct eeprom_phy_calib) * (cnt * MAX_ANTENNAS + pt->phy);
+
+       return 0;
+}
+
+static bool 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 bool 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;
+       u16 freq0, freq1, freq2;
+
+       if (unlikely(is_vector_unset(bitmap)))
+               return false;
+
+       p1->idx = bitmap_look_lsb_up(cl_hw, bitmap, p0->idx);
+       p2->idx = bitmap_look_msb_down(cl_hw, bitmap, p0->idx);
+
+       /* 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, p2->idx);
+       if (p2->idx == p0->idx)
+               p2->idx = bitmap_look_lsb_up(cl_hw, bitmap, p1->idx);
+
+       /* Address from index */
+       if (point_idx_to_address(cl_hw, bitmap, p1) || p1->addr == INVALID_ADDR) {
+               cl_dbg_err(cl_hw, "Point calculation failed\n");
+               return false;
+       }
+
+       if (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 = 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 = 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 && 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 && 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, freq1 %u idx1 %u, freq2 %u idx2 %u\n",
+                          freq0, p0->idx, freq1, p1->idx, freq2, p2->idx);
+               return false;
+       }
+
+       return true;
+}
+
+static int 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_CHAN_BMP))
+               return -1;
+
+       /* Test if e2p was read succsefull since it is not ALL EMPTY */
+       if (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 = get_bitmap_start_tcv1(chip);
+
+               bitmap_shift(bitmap, bitmap_start);
+       }
+
+       return 0;
+}
+
+static int e2p_prepare(struct cl_hw *cl_hw, struct point *data, u8 *bitmap)
+{
+       int ret = read_validate_vector_bitmap(cl_hw, bitmap);
+
+       if (ret) {
+               cl_dbg_err(cl_hw, "read_validate_vector_bitmap failed\n");
+               return ret;
+       }
+
+       data->idx = cl_channel_to_index(cl_hw, data->chan);
+
+       return point_idx_to_address(cl_hw, bitmap, data);
+}
+
+static int 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 (!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 = cl_channel_idx_to_freq(cl_hw, tmp_pt.idx);
+
+               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;
+}
+
+int cl_calib_get(struct wiphy *wiphy, struct wireless_dev *wdev,
+                            const void *data, int data_len)
+{
+       /* Kernel space callback for handling E2P_GET_CALIB vendor subcmd */
+       int ret;
+       struct point *p0;
+       u8 e2p_bitmap[BIT_MAP_SIZE] = {0};
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+
+       if (!data) {
+               cl_dbg_err(cl_hw, "data is null\n");
+               return -1;
+       }
+
+       p0 = (struct point *)data;
+
+       ret = e2p_prepare(cl_hw, p0, e2p_bitmap);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Unable prepare e2p\n");
+               return ret;
+       }
+
+       ret = read_or_interpolate_point(cl_hw, e2p_bitmap, p0);
+       if (ret) {
+               cl_dbg_trace(cl_hw, "read_or_interpolate_point error\n");
+               return ret;
+       }
+
+       return cl_vendor_reply(cl_hw, &p0->calib, sizeof(p0->calib));
+}
+
+int cl_calib_set(struct wiphy *wiphy, struct wireless_dev *wdev,
+                            const void *data, int data_len)
+{
+       /* Kernel space callback for handling E2P_SET_CALIB vendor subcmd */
+       struct cl_hw *cl_hw = WIPHY_TO_CL_HW(wiphy);
+       struct point pt;
+       int ret;
+       u8 e2p_bitmap[BIT_MAP_SIZE] = {0};
+       u8 ch_idx = 0;
+
+       if (!data) {
+               cl_dbg_err(cl_hw, "data is null\n");
+               return -1;
+       }
+
+       pt = *(struct point *)data;
+
+       ret = e2p_prepare(cl_hw, &pt, e2p_bitmap);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Unable prepare e2p\n");
+               return ret;
+       }
+
+       if (pt.addr == INVALID_ADDR) {
+               cl_dbg_err(cl_hw, "Invalid address - permission denied\n");
+               return -EPERM;
+       }
+
+       if (pt.calib.pow < POWER_MIN_DB || pt.calib.pow > POWER_MAX_DB) {
+               cl_dbg_err(cl_hw, "Invalid power (%d). Valid range (%d - %d)\n",
+                          pt.calib.pow, POWER_MIN_DB, POWER_MAX_DB);
+               return -1;
+       }
+
+       if (pt.calib.offset < POWER_OFFSET_MIN_Q2 || pt.calib.offset > POWER_OFFSET_MAX_Q2) {
+               cl_dbg_err(cl_hw, "Invalid power offset (%d). Valid range (%d - %d)\n",
+                          pt.calib.offset, POWER_OFFSET_MIN_Q2, POWER_OFFSET_MAX_Q2);
+               return -1;
+       }
+
+       if (!bitmap_test_bit_idx(e2p_bitmap, pt.idx)) {
+               cl_dbg_err(cl_hw, "No permition to write to this channel %u\n", pt.idx);
+               return -EACCES;
+       }
+
+       /*
+        * Temperature is an optional argument for "e2p set calib" command.
+        * If value is 0x7f then temperature argument was not set, and it
+        * should be set by the driver.
+        */
+       if (pt.calib.tmp == S8_MAX)
+               pt.calib.tmp = cl_temperature_read(cl_hw, TEMP_MODE_INTERNAL);
+
+       if (cl_e2p_write(cl_hw->chip, (u8 *)&pt.calib, sizeof(struct eeprom_phy_calib), pt.addr))
+               return -1;
+
+       ch_idx = cl_channel_to_index(cl_hw, pt.chan);
+
+       if (ch_idx < MAX_CHANNELS && pt.phy < MAX_ANTENNAS) {
+               cl_hw->tx_pow_info[ch_idx][pt.phy].power = pt.calib.pow;
+               cl_hw->tx_pow_info[ch_idx][pt.phy].offset = pt.calib.offset;
+               cl_hw->tx_pow_info[ch_idx][pt.phy].temperature = pt.calib.tmp;
+               cl_hw->set_calib = true;
+       }
+
+       return 0;
+}
+
+static void cl_calib_power_reset(struct cl_hw *cl_hw)
+{
+       u8 ch_idx;
+       u16 phy;
+       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 < cl_channel_num(cl_hw); ch_idx++)
+               for (phy = 0; phy < MAX_ANTENNAS; phy++)
+                       cl_hw->tx_pow_info[ch_idx][phy] = default_info;
+}
+
+#define PHY0_OFFSET_FIX_Q2 -8 /* -2db */
+#define PHY3_OFFSET_FIX_Q2 14 /* +3.5db */
+
+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;
+
+       /* Initiate tx_pow_info struct to default values */
+       cl_calib_power_reset(cl_hw);
+
+       /* Vector not initiated set table to default values */
+       if (unlikely(read_validate_vector_bitmap(cl_hw, bitmap))) {
+               cl_dbg_trace(cl_hw, "initiate to default values\n");
+               return;
+       }
+
+       /* Perform only on calibrated boards - read_validate_vector_bitmap succeeded (0) */
+       for (*ch_idx = 0; *ch_idx < cl_channel_num(cl_hw); (*ch_idx)++)
+               for (*phy = 0; *phy < cl_hw->num_antennas; (*phy)++) {
+                       ret = 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;
+                       }
+
+                       ret = read_or_interpolate_point(cl_hw, bitmap, &curr_point);
+                       /* Unable to calculate new value ==> DON'T overwrite default values */
+                       if (unlikely(ret))
+                               continue;
+
+                       /*
+                        * 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(chip)) {
+                               u8 eeprom_version = chip->eeprom_cache->general.version;
+
+                               if (cl_band_is_5g(cl_hw) && eeprom_version == 0 && *phy == 3)
+                                       curr_point.calib.offset += PHY3_OFFSET_FIX_Q2;
+                               else if (cl_band_is_24g(cl_hw) && eeprom_version == 1)
+                                       curr_point.calib.offset += PHY0_OFFSET_FIX_Q2;
+                       }
+
+                       cl_hw->tx_pow_info[*ch_idx][*phy].power = curr_point.calib.pow;
+                       cl_hw->tx_pow_info[*ch_idx][*phy].offset = curr_point.calib.offset;
+                       cl_hw->tx_pow_info[*ch_idx][*phy].temperature = curr_point.calib.tmp;
+               }
+
+       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;
+       u8 chan_idx = cl_channel_to_index(cl_hw, channel);
+       s8 signed_offset;
+       struct cl_ate_db *ate_db = &cl_hw->ate_db;
+
+       if (chan_idx == INVALID_CHAN_IDX)
+               return;
+
+       /* In ATE mode, use values of 'ATE power_offset' if it was set */
+       if (ate_db->active && ate_db->tx_power_offset[0] != S8_MAX) {
+               for (i = 0; i < MAX_ANTENNAS; i++) {
+                       s8 pow_offset = ate_db->tx_power_offset[i];
+
+                       signed_offset = cl_power_offset_check_margin(cl_hw, bw, i, pow_offset);
+                       offset[i] = cl_convert_signed_to_reg_value(signed_offset);
+               }
+
+               return;
+       }
+
+       for (i = 0; i < MAX_ANTENNAS; i++) {
+               s8 pow_offset = cl_hw->tx_pow_info[chan_idx][i].offset;
+
+               signed_offset = cl_power_offset_check_margin(cl_hw, bw, i, pow_offset);
+               offset[i] = cl_convert_signed_to_reg_value(signed_offset);
+       }
+}
+
+static void pivot_channels_reset(struct cl_hw *cl_hw, u8 *bitmap)
+{
+       u8 i, start = 0, max = 0;
+
+       get_bitmap_boundaries(cl_hw->chip, cl_hw->tcv_idx, &start, &max);
+
+       for (i = start; i < max; i++)
+               bitmap_clear_bit_idx(cl_hw, bitmap, i);
+}
+
+static u8 count_num_pivots(struct cl_chip *chip, const u8 *bitmap, u8 tcv_idx)
+{
+       u8 i = 0, cnt = 0, start = 0, max = 0;
+
+       get_bitmap_boundaries(chip, tcv_idx, &start, &max);
+
+       for (i = start; i < max; i++)
+               if (bitmap_test_bit_idx(bitmap, i))
+                       cnt++;
+
+       return cnt;
+}
+
+int cl_calib_pivot_channels_set(struct cl_hw *cl_hw, const void *chan_list, u32 size)
+{
+       struct cl_chip *chip = cl_hw->chip;
+       u8 bitmap[BIT_MAP_SIZE] = {0};
+       u8 num_pivots = 0;
+       u8 idx = 0;
+
+       if (cl_e2p_read(chip, bitmap, BIT_MAP_SIZE, ADDR_CALIB_CHAN_BMP))
+               return -1;
+
+       num_pivots = count_num_pivots(chip, bitmap, cl_hw->tcv_idx);
+
+       if (num_pivots > 0) {
+               cl_dbg_err(cl_hw, "Vector already set\n");
+               return -EACCES;
+       }
+
+       while (size--) {
+               idx = cl_channel_to_index(cl_hw, ((u32 *)chan_list)[size]);
+
+               if (idx == INVALID_CHAN_IDX) {
+                       cl_dbg_err(cl_hw, "Bad channel index %u", idx);
+                       return -EINVAL;
+               }
+
+               if (cl_hw_is_tcv1(cl_hw))
+                       idx += get_bitmap_start_tcv1(chip);
+
+               if (!bitmap_set_bit_idx(cl_hw, bitmap, idx)) {
+                       cl_dbg_err(cl_hw, "Bad channel index %u", idx);
+                       return -EINVAL;
+               }
+       }
+
+       if (count_bits(bitmap) > NUM_OF_PIVOTS) {
+               cl_dbg_err(cl_hw, "Too much pivot channels chosen\n");
+               return -EINVAL;
+       }
+
+       if (cl_e2p_write(chip, bitmap, BIT_MAP_SIZE, ADDR_CALIB_CHAN_BMP))
+               return -1;
+
+       /*
+        * Pivots of tcv0 are located before the pivots of tcv1.
+        * If calibration of tcv1 was done before calibration of tcv0, we must move the
+        * calibration data of tcv1 so that there is room for the tcv0 calibration data.
+        */
+       if (cl_hw_is_tcv0(cl_hw)) {
+               u8 num_pivots_tcv0 = count_num_pivots(chip, bitmap, TCV0);
+               u8 num_pivots_tcv1 = count_num_pivots(chip, bitmap, TCV1);
+
+               if (num_pivots_tcv1 > 0) {
+                       struct eeprom_phy_calib phy_calib[NUM_PIVOT_PHYS] = { {0} };
+
+                       if (cl_e2p_read(chip, (u8 *)phy_calib, SIZE_CALIB_PHY, ADDR_CALIB_PHY))
+                               return -1;
+
+                       memmove(&phy_calib[num_pivots_tcv0 * MAX_ANTENNAS],
+                               &phy_calib[0],
+                               num_pivots_tcv1 * MAX_ANTENNAS * sizeof(struct eeprom_phy_calib));
+                       memset(&phy_calib[0],
+                              0,
+                              num_pivots_tcv0 * MAX_ANTENNAS * sizeof(struct eeprom_phy_calib));
+
+                       if (cl_e2p_write(chip, (u8 *)phy_calib, SIZE_CALIB_PHY, ADDR_CALIB_PHY))
+                               return -1;
+               }
+       }
+
+       return 0;
+}
+
+int cl_calib_pivot_channels_reset(struct cl_hw *cl_hw)
+{
+       /* Both eeprom and efuse are being set to 0 for reset */
+       struct cl_chip *chip = cl_hw->chip;
+       u8 bitmap[BIT_MAP_SIZE] = {0};
+       struct eeprom_phy_calib phy_calib[NUM_PIVOT_PHYS] = { {0} };
+       u8 num_pivots_tcv0 = 0;
+       u8 num_pivots_tcv1 = 0;
+
+       if (sizeof(phy_calib) != SIZE_CALIB_PHY) {
+               cl_dbg_err(cl_hw, "sizeof(phy_calib) != SIZE_CALIB_PHY\n");
+               return -1;
+       }
+
+       /* Read current bitmap and calibration data */
+       if (cl_e2p_read(chip, (u8 *)bitmap, BIT_MAP_SIZE, ADDR_CALIB_CHAN_BMP))
+               return -1;
+       if (cl_e2p_read(chip, (u8 *)phy_calib, SIZE_CALIB_PHY, ADDR_CALIB_PHY))
+               return -1;
+
+       /* Find number of pivots for each band */
+       num_pivots_tcv0 = count_num_pivots(chip, bitmap, TCV0);
+       num_pivots_tcv1 = count_num_pivots(chip, bitmap, TCV1);
+
+       /* Reset bitmap of this band */
+       pivot_channels_reset(cl_hw, bitmap);
+
+       /* Reset calibration data of this band */
+       if (cl_hw_is_tcv0(cl_hw)) {
+               if (num_pivots_tcv1 > 0) {
+                       /* For tcv0 shift calibration data of tcv1 to the beginning */
+                       memcpy(&phy_calib[0], &phy_calib[num_pivots_tcv0 * MAX_ANTENNAS],
+                              num_pivots_tcv1 * MAX_ANTENNAS * sizeof(struct eeprom_phy_calib));
+                       memset(&phy_calib[num_pivots_tcv1 * MAX_ANTENNAS], 0,
+                              num_pivots_tcv0 * MAX_ANTENNAS * sizeof(struct eeprom_phy_calib));
+               } else {
+                       memset(&phy_calib[0], 0,
+                              num_pivots_tcv0 * MAX_ANTENNAS * sizeof(struct eeprom_phy_calib));
+               }
+       } else {
+               memset(&phy_calib[num_pivots_tcv0 * MAX_ANTENNAS],
+                      0, num_pivots_tcv1 * MAX_ANTENNAS * sizeof(struct eeprom_phy_calib));
+       }
+
+       /* Write back modified bitmap and calibration data */
+       if (cl_e2p_write(chip, (u8 *)bitmap, BIT_MAP_SIZE, ADDR_CALIB_CHAN_BMP))
+               return -1;
+       if (cl_e2p_write(chip, (u8 *)phy_calib, SIZE_CALIB_PHY, ADDR_CALIB_PHY))
+               return -1;
+
+       /* Reset host calibration data */
+       cl_calib_power_reset(cl_hw);
+
+       return 0;
+}
+
+static void cl_calib_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;
+}
+
+static void cl_calib_save_channel(struct cl_hw *cl_hw, struct cl_calib_restore *calib_restore)
+{
+       calib_restore->bw = cl_hw->bw;
+       calib_restore->primary = cl_hw->primary_freq;
+       calib_restore->center = cl_hw->center_freq;
+       calib_restore->channel = ieee80211_frequency_to_channel(cl_hw->primary_freq);
+}
+
+static int cl_calib_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;
+       u8 is_prod = chip->conf->ce_production_mode;
+       bool tcv0_en = (cl_radio_is_on(cl_hw_tcv0) || (is_prod && cl_hw_tcv0->ate_db.active));
+       bool tcv1_en = (cl_radio_is_on(cl_hw_tcv1) || (is_prod && cl_hw_tcv1->ate_db.active));
+
+       if (!idle) {
+               if (tcv1_en)
+                       cl_msg_tx_set_idle(cl_hw_tcv1, MAC_ACTIVE);
+
+               if (tcv0_en)
+                       cl_msg_tx_set_idle(cl_hw_tcv0, MAC_ACTIVE);
+
+               return 0;
+       }
+
+       if (tcv1_en)
+               cl_msg_tx_idle_async(cl_hw_tcv1);
+
+       if (tcv0_en)
+               cl_msg_tx_set_idle(cl_hw_tcv0, MAC_IDLE_SYNC);
+
+       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;
+}
+
+static int _cl_calib_set_channel(struct cl_hw *cl_hw, u32 channel, u32 bw)
+{
+       u32 primary = 0;
+       u32 center = 0;
+       enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20;
+
+       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_verbose(cl_hw, "Calibrate channel %u bw %u\n", channel, BW_TO_MHZ(bw));
+
+       return _cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center, SET_CHANNEL_MODE_CALIB);
+}
+
+static void cl_calib_channels_6g(struct cl_hw *cl_hw)
+{
+       int i;
+
+       /* Calibrate channels: 1, 33, 65, 97, 129, 161, 193, 225 */
+       for (i = 0; i < CALIB_CHAN_6G_MAX; i += 2)
+               _cl_calib_set_channel(cl_hw, calib_channels_6g[i], CHNL_BW_160);
+
+       for (i = 0; i < CALIB_CHAN_6G_MAX; i++) {
+               _cl_calib_set_channel(cl_hw, calib_channels_6g[i], CHNL_BW_80);
+               _cl_calib_set_channel(cl_hw, calib_channels_6g[i], CHNL_BW_20);
+       }
+}
+
+static void cl_calib_channels_5g(struct cl_hw *cl_hw)
+{
+       int i;
+
+       _cl_calib_set_channel(cl_hw, 36, CHNL_BW_160);
+       _cl_calib_set_channel(cl_hw, 100, CHNL_BW_160);
+
+       for (i = 0; i < CALIB_CHAN_5G_MAX; i++) {
+               _cl_calib_set_channel(cl_hw, calib_channels_5g[i], CHNL_BW_80);
+               _cl_calib_set_channel(cl_hw, calib_channels_5g[i], CHNL_BW_20);
+       }
+}
+
+static void cl_calib_channels_24g(struct cl_hw *cl_hw)
+{
+       int i;
+
+       for (i = 0; i < CALIB_CHAN_24G_MAX; i++) {
+               _cl_calib_set_channel(cl_hw, calib_channels_24g[i], CHNL_BW_40);
+               _cl_calib_set_channel(cl_hw, calib_channels_24g[i], CHNL_BW_20);
+       }
+}
+
+static void cl_calib_scan_all_channels(struct cl_hw *cl_hw)
+{
+       if (cl_band_is_6g(cl_hw))
+               cl_calib_channels_6g(cl_hw);
+       else if (cl_band_is_5g(cl_hw))
+               cl_calib_channels_5g(cl_hw);
+       else
+               cl_calib_channels_24g(cl_hw);
+}
+
+static void cl_calib_restore_channel(struct cl_hw *cl_hw, struct cl_calib_restore *calib_restore)
+{
+       u8 bw = calib_restore->bw;
+       u32 primary = calib_restore->primary;
+       u32 center = calib_restore->center;
+       u8 channel = calib_restore->channel;
+
+       cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center);
+}
+
+static void cl_calib_print_errors(struct cl_hw *cl_hw)
+{
+       struct cl_calib_errors *errors = &cl_hw->chip->calib_db.errors[cl_hw->tcv_idx];
+
+       if (!errors->dcoc && !errors->lolc && !errors->iq_rx && !errors->iq_tx)
+               return;
+
+       pr_warn("Calibration errors: DCOC %u, LOLC %u, IQ RX %u, IQ TX %u\n",
+               errors->dcoc, errors->lolc, errors->iq_rx, errors->iq_tx);
+}
+
+static u8 cl_calib_channel_to_idx(struct cl_hw *cl_hw, u8 channel)
+{
+       u8 i = 0;
+
+       if (cl_band_is_6g(cl_hw)) {
+               for (i = 0; i < CALIB_CHAN_6G_MAX; i++)
+                       if (calib_channels_6g[i] == channel)
+                               return i;
+       } else if (cl_band_is_5g(cl_hw)) {
+               for (i = 0; i < CALIB_CHAN_5G_MAX; i++)
+                       if (calib_channels_5g[i] == channel)
+                               return i;
+       } else {
+               for (i = 0; i < CALIB_CHAN_24G_MAX; i++)
+                       if (calib_channels_24g[i] == channel)
+                               return i;
+       }
+
+       return 0;
+}
+
+static void cl_calib_check_err_dcoc(struct cl_hw *cl_hw, s16 calib_temperature,
+                                   int channel, u8 bw)
+{
+       struct cl_chip *chip = cl_hw->chip;
+       int lna, ant;
+       struct cl_dcoc_report *dcoc_calib_report_dma;
+       u8 dcoc_threshold = chip->conf->ci_dcoc_mv_thr[bw];
+       s16 i, q;
+
+       for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++) {
+               ant_for_each(ant) {
+                       dcoc_calib_report_dma =
+                               &cl_hw->iq_dcoc_data_info.iq_dcoc_data->report.dcoc[lna][ant];
+                       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) {
+                               chip->calib_db.errors[cl_hw->tcv_idx].dcoc++;
+                               cl_dbg_info(cl_hw,
+                                           "DCOC Error: lna = %u, ant = %u, "
+                                           "i (|%d|) > threshold (%d)\n",
+                                           lna, ant, i, dcoc_threshold);
+                       } else {
+                               cl_dbg_info(cl_hw,
+                                           "DCOC Valid: lna = %u, ant = %u, "
+                                           "i (|%d|) < threshold (%d)\n",
+                                           lna, ant, i, dcoc_threshold);
+                       }
+
+                       if (abs(q) > dcoc_threshold) {
+                               chip->calib_db.errors[cl_hw->tcv_idx].dcoc++;
+                               cl_dbg_info(cl_hw,
+                                           "DCOC Error: lna = %u, ant = %u, "
+                                           "q (|%d|) > threshold (%d)\n",
+                                           lna, ant, q, dcoc_threshold);
+                       } else {
+                               cl_dbg_info(cl_hw,
+                                           "DCOC Valid: lna = %u, ant = %u, "
+                                           "q (|%d|) < threshold (%d)\n",
+                                           lna, ant, q, dcoc_threshold);
+                       }
+               }
+       }
+}
+
+static void cl_calib_check_err_iq_lolc(struct cl_hw *cl_hw, s16 calib_temperature,
+                                      int channel, u8 bw, u8 plan_bitmap)
+{
+       struct cl_chip *chip = cl_hw->chip;
+       struct cl_iq_dcoc_report *report = &cl_hw->iq_dcoc_data_info.iq_dcoc_data->report;
+       int ant;
+       struct cl_lolc_report lolc_report_dma;
+       s16 lolc_threshold = chip->conf->ci_lolc_db_thr;
+       s32 lolc_qual = 0;
+
+       ant_for_each(ant) {
+               if ((plan_bitmap & (1 << ant)) == 0)
+                       continue;
+
+               lolc_report_dma = report->lolc_report[ant];
+               lolc_qual = (s16)le16_to_cpu(lolc_report_dma.lolc_qual) >> 8;
+
+               if (lolc_qual > lolc_threshold) {
+                       chip->calib_db.errors[cl_hw->tcv_idx].lolc++;
+
+                       cl_dbg_info(cl_hw,
+                                   "LOLC Error: ant = %u, n_iter = %u, "
+                                   "quality (%d) > threshold (%d)\n",
+                                   ant, lolc_report_dma.n_iter, lolc_qual, lolc_threshold);
+               } else {
+                       cl_dbg_info(cl_hw,
+                                   "LOLC Valid: ant = %u, n_iter = %u, "
+                                   "quality (%d) < threshold (%d)\n",
+                                   ant, lolc_report_dma.n_iter, lolc_qual, lolc_threshold);
+               }
+       }
+}
+
+static void cl_calib_check_err_iq(struct cl_hw *cl_hw, s16 calib_temperature,
+                                 u8 ch, u8 bw, u8 plan_bitmap)
+{
+       struct cl_chip *chip = cl_hw->chip;
+       u8 tcv_idx = cl_hw->tcv_idx;
+       u8 ant = 0;
+       struct cl_iq_report iq_report_dma;
+       s8 iq_threshold = cl_hw->chip->conf->ci_iq_db_thr;
+
+       ant_for_each(ant) {
+               if ((plan_bitmap & (1 << ant)) == 0)
+                       continue;
+
+               iq_report_dma = cl_hw->iq_dcoc_data_info.iq_dcoc_data->report.iq_tx[ant];
+
+               if (iq_report_dma.ir_db_avg_post > iq_threshold) {
+                       chip->calib_db.errors[tcv_idx].iq_tx++;
+                       cl_dbg_info(cl_hw, "IQ TX Error: ant = %u, ir (%d) > threshold (%d)\n",
+                                   ant, iq_report_dma.ir_db_avg_post, iq_threshold);
+               } else {
+                       cl_dbg_info(cl_hw, "IQ TX Valid: ant = %u, ir (%d) < threshold (%d)\n",
+                                   ant, iq_report_dma.ir_db_avg_post, iq_threshold);
+               }
+
+               iq_report_dma = cl_hw->iq_dcoc_data_info.iq_dcoc_data->report.iq_rx[ant];
+
+               if (iq_report_dma.ir_db_avg_post > iq_threshold) {
+                       chip->calib_db.errors[tcv_idx].iq_rx++;
+                       cl_dbg_info(cl_hw, "IQ RX Error: ant = %u, ir (%d) > threshold (%d)\n",
+                                   ant, iq_report_dma.ir_db_avg_post, iq_threshold);
+               } else {
+                       cl_dbg_info(cl_hw, "IQ RX Valid: ant = %u, ir (%d) < threshold (%d)\n",
+                                   ant, iq_report_dma.ir_db_avg_post, iq_threshold);
+               }
+       }
+}
+
+static u8 cl_calib_center_freq_to_idx(struct cl_hw *cl_hw, u32 center_freq)
+{
+       u8 i = 0;
+       u8 center_channel = ieee80211_frequency_to_channel(center_freq);
+
+       if (cl_band_is_6g(cl_hw)) {
+               for (i = 1; i < CALIB_CHAN_6G_MAX; i++)
+                       if (calib_channels_6g[i] > center_channel)
+                               return (i - 1);
+
+               return (CALIB_CHAN_6G_MAX - 1);
+       }
+
+       if (cl_band_is_5g(cl_hw)) {
+               for (i = 1; i < CALIB_CHAN_5G_MAX; i++)
+                       if (calib_channels_5g[i] > center_channel)
+                               return (i - 1);
+
+               return (CALIB_CHAN_5G_MAX - 1);
+       }
+
+       for (i = 0; i < CALIB_CHAN_24G_MAX; i++)
+               if (abs(calib_channels_24g[i] - center_channel) < 3)
+                       return i;
+
+       return (CALIB_CHAN_24G_MAX - 1);
+}
+
+static void cl_calib_fill_data_dcoc(struct cl_hw *cl_hw, struct cl_iq_dcoc_info *iq_dcoc_db)
+{
+       struct cl_chip *chip = cl_hw->chip;
+       u8 lna = 0, ant = 0;
+       u8 channel_idx = cl_calib_center_freq_to_idx(cl_hw, cl_hw->center_freq);
+       u8 bw = cl_hw->bw;
+       u8 tcv_idx = cl_hw->tcv_idx;
+       u8 sx = tcv_idx;
+
+       for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++)
+               ant_for_each(ant)
+                       iq_dcoc_db->dcoc[lna][ant] =
+                               chip->calib_db.dcoc[tcv_idx][channel_idx][bw][sx][ant][lna];
+}
+
+static void cl_calib_fill_data_iq(struct cl_hw *cl_hw, struct cl_iq_calib *iq_data,
+                                 struct cl_iq_calib *iq_chip_data)
+{
+       u8 ant = 0;
+
+       ant_for_each(ant) {
+               iq_data[ant].coef0 = cpu_to_le32(iq_chip_data[ant].coef0);
+               iq_data[ant].coef1 = cpu_to_le32(iq_chip_data[ant].coef1);
+               iq_data[ant].coef2 = cpu_to_le32(iq_chip_data[ant].coef2);
+               iq_data[ant].gain = cpu_to_le32(iq_chip_data[ant].gain);
+       }
+}
+
+static void cl_calib_fill_data_iq_lolc(struct cl_hw *cl_hw, __le32 *iq_lolc)
+{
+       struct cl_calib_db *calib_db = &cl_hw->chip->calib_db;
+       u8 ant = 0;
+       u8 chan_idx = cl_calib_center_freq_to_idx(cl_hw, cl_hw->center_freq);
+       u8 bw = cl_hw->bw;
+       u8 tcv_idx = cl_hw->tcv_idx;
+       u8 sx = tcv_idx;
+
+       ant_for_each(ant)
+               iq_lolc[ant] = cpu_to_le32(calib_db->iq_tx_lolc[tcv_idx][chan_idx][bw][sx][ant]);
+}
+
+static void cl_calib_handle_cfm_dcoc(struct cl_hw *cl_hw)
+{
+       struct cl_chip *chip = cl_hw->chip;
+       struct cl_dcoc_calib *dcoc_calib;
+       struct cl_dcoc_calib *dcoc_calib_dma;
+       struct calib_cfm *dcoc_iq_cfm =
+               &cl_hw->iq_dcoc_data_info.iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_DCOC];
+       int lna, ant;
+       u16 raw_bits = (le16_to_cpu(dcoc_iq_cfm->raw_bits_data_0) +
+                       le16_to_cpu(dcoc_iq_cfm->raw_bits_data_1)) / 2;
+       s16 calib_temperature = cl_temperature_calib_calc(cl_hw, raw_bits);
+       u8 tcv_idx = cl_hw->tcv_idx;
+       u8 sx = tcv_idx;
+       u8 channel = cl_hw->channel;
+       u8 bw = cl_hw->bw;
+       u8 channel_idx = cl_calib_channel_to_idx(cl_hw, channel);
+
+       for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++) {
+               ant_for_each(ant) {
+                       dcoc_calib = &chip->calib_db.dcoc[tcv_idx][channel_idx][bw][sx][ant][lna];
+                       dcoc_calib_dma =
+                               &cl_hw->iq_dcoc_data_info.iq_dcoc_data->iq_dcoc_db.dcoc[lna][ant];
+                       dcoc_calib->i = dcoc_calib_dma->i;
+                       dcoc_calib->q = dcoc_calib_dma->q;
+               }
+       }
+
+       cl_calib_check_err_dcoc(cl_hw, calib_temperature, channel, bw);
+
+       /*
+        * Set the default status to FAIL, to ensure FW is actually changing the value,
+        * if the calibration succeeded.
+        */
+       cl_hw->iq_dcoc_data_info.iq_dcoc_data->dcoc_iq_cfm[CALIB_CFM_DCOC].status = CALIB_FAIL;
+}
+
+static void cl_calib_handle_cfm_iq(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];
+       u16 raw_bits_data_0 = le16_to_cpu(dcoc_iq_cfm->raw_bits_data_0);
+       u16 raw_bits_data_1 = le16_to_cpu(dcoc_iq_cfm->raw_bits_data_1);
+       u16 raw_bits = (raw_bits_data_0 + raw_bits_data_1) / 2;
+       s16 calib_temperature = cl_temperature_calib_calc(cl_hw, raw_bits);
+       u8 channel = cl_hw->channel;
+       u8 bw = cl_hw->bw;
+       int ant;
+       u8 tcv_idx = cl_hw->tcv_idx;
+       u8 sx = tcv_idx;
+       u8 channel_idx = cl_calib_channel_to_idx(cl_hw, channel);
+
+       ant_for_each(ant) {
+               if ((plan_bitmap & (1 << ant)) == 0)
+                       continue;
+
+               cl_hw->chip->calib_db.iq_tx[tcv_idx][channel_idx][bw][sx][ant] =
+                       cl_hw->iq_dcoc_data_info.iq_dcoc_data->iq_dcoc_db.iq_tx[ant];
+
+               cl_hw->chip->calib_db.iq_rx[tcv_idx][channel_idx][bw][sx][ant] =
+                       cl_hw->iq_dcoc_data_info.iq_dcoc_data->iq_dcoc_db.iq_rx[ant];
+       }
+
+       cl_calib_check_err_iq(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;
+}
+
+static void cl_calib_handle_cfm_iq_lolc(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];
+       u16 raw_bits = (le16_to_cpu(dcoc_iq_cfm->raw_bits_data_0) +
+               le16_to_cpu(dcoc_iq_cfm->raw_bits_data_1)) / 2;
+       s16 calib_temperature = cl_temperature_calib_calc(cl_hw, raw_bits);
+       u8 channel = cl_hw->channel;
+       u8 channel_idx = cl_calib_channel_to_idx(cl_hw, channel);
+       u8 bw = cl_hw->bw;
+       u8 sx = cl_hw->tcv_idx;
+       int ant;
+
+       ant_for_each(ant) {
+               if ((plan_bitmap & (1 << ant)) == 0)
+                       continue;
+
+               cl_hw->chip->calib_db.iq_tx_lolc[cl_hw->tcv_idx][channel_idx][bw][sx][ant] =
+                       cl_hw->iq_dcoc_data_info.iq_dcoc_data->iq_dcoc_db.iq_tx_lolc[ant];
+       }
+
+       cl_calib_check_err_iq_lolc(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;
+}
+
+static void cl_calib_set_channel_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_start(cl_hw);
+
+       if (cl_chip_is_both_enabled(chip))
+               cl_calib_start(cl_hw_other);
+
+       chip->calib_db.scan_complete = true;
+}
+
+int cl_calib_start(struct cl_hw *cl_hw)
+{
+       u8 channel = cl_hw->conf->ha_channel;
+       u8 bw = cl_hw->conf->ce_channel_bandwidth;
+       enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20;
+       u32 primary = 0;
+       u32 center = 0;
+
+       if (cl_chandef_calc(cl_hw, channel, bw, &width, &primary, &center))
+               return -EINVAL;
+
+       return cl_calib_set_channel(cl_hw, channel, bw, primary, center);
+}
+
+void cl_calib_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 channel_idx = cl_calib_center_freq_to_idx(cl_hw, cl_hw->center_freq);
+       u8 bw = cl_hw->bw;
+       u8 tcv_idx = cl_hw->tcv_idx;
+
+       if (flags & SET_PHY_DATA_FLAGS_DCOC)
+               cl_calib_fill_data_dcoc(cl_hw, iq_dcoc_db);
+
+       if (flags & SET_PHY_DATA_FLAGS_IQ_TX_LOLC)
+               cl_calib_fill_data_iq_lolc(cl_hw, iq_dcoc_db->iq_tx_lolc);
+
+       if (flags & SET_PHY_DATA_FLAGS_IQ_TX)
+               cl_calib_fill_data_iq(cl_hw, iq_dcoc_db->iq_tx,
+                                     chip->calib_db.iq_tx[tcv_idx][channel_idx][bw][tcv_idx]);
+
+       if (flags & SET_PHY_DATA_FLAGS_IQ_RX)
+               cl_calib_fill_data_iq(cl_hw, iq_dcoc_db->iq_rx,
+                                     chip->calib_db.iq_rx[tcv_idx][channel_idx][bw][tcv_idx]);
+}
+
+int cl_calib_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 -1;
+
+       cl_hw->iq_dcoc_data_info.iq_dcoc_data = buf;
+       cl_hw->iq_dcoc_data_info.dma_addr = cpu_to_le32(phys_dma_addr);
+
+       cl_calib_init_cfm(cl_hw->iq_dcoc_data_info.iq_dcoc_data);
+
+       return 0;
+}
+
+void cl_calib_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 = le32_to_cpu(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;
+}
+
+bool cl_calib_is_needed(struct cl_hw *cl_hw, u8 channel, u8 bw)
+{
+       u8 channel_idx;
+       u8 tcv_idx = cl_hw->tcv_idx;
+       u8 ant;
+       u32 primary = 0;
+       u32 center_freq = 0;
+       enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20;
+
+       if (cl_chandef_calc(cl_hw, channel, bw, &width, &primary, &center_freq)) {
+               cl_dbg_err(cl_hw, "cl_chandef_calc failed\n");
+               return false;
+       }
+
+       channel_idx = cl_calib_center_freq_to_idx(cl_hw, center_freq);
+
+       /* Check if we already calibrated */
+       ant_for_each(ant) {
+               if (cl_hw->chip->calib_db.iq_tx_lolc[tcv_idx][channel_idx][bw][tcv_idx][ant])
+                       return false;
+       }
+
+       return true;
+}
+
+int cl_calib_set_channel(struct cl_hw *cl_hw, u8 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_restore calib_restore;
+       int ret = 0;
+       u8 fem_mode = cl_hw->fem_system_mode;
+       bool save_ch_other = !!cl_hw_other->primary_freq;
+
+       if (save_ch_other)
+               cl_calib_save_channel(cl_hw_other, &calib_restore);
+
+       ret = cl_calib_set_idle(cl_hw, true);
+       if (ret)
+               return ret;
+
+       cl_fem_set_system_mode(cl_hw, FEM_MODE_LNA_BYPASS_ONLY, U8_MAX);
+       cl_afe_cfg_calib(chip);
+
+       if (chip->conf->ce_calib_scan_en && !chip->calib_db.scan_complete && cl_hw->calib_ready)
+               cl_calib_scan_all_channels(cl_hw);
+       else
+               _cl_calib_set_channel(cl_hw, channel, bw);
+
+       cl_fem_set_system_mode(cl_hw, fem_mode, U8_MAX);
+       cl_afe_cfg_restore(chip);
+
+       _cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center, SET_CHANNEL_MODE_OPERETIONAL);
+
+       if (save_ch_other)
+               cl_calib_restore_channel(cl_hw_other, &calib_restore);
+
+       cl_calib_set_idle(cl_hw, false);
+
+       return ret;
+}
+
+void cl_calib_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_set_channel_start_work);
+       queue_work(cl_hw->drv_workqueue, &calib_work->ws);
+}
+
+int cl_calib_handle_cfm(struct cl_hw *cl_hw, u8 mode)
+{
+       struct cl_iq_dcoc_data *iq_dcoc_data = cl_hw->iq_dcoc_data_info.iq_dcoc_data;
+       struct cl_calib_errors *errors = &cl_hw->chip->calib_db.errors[cl_hw->tcv_idx];
+
+       /*
+        * 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 -1;
+       }
+
+       if (mode & SET_CHANNEL_MODE_CALIB_DCOC)
+               cl_calib_handle_cfm_dcoc(cl_hw);
+
+       if (mode & SET_CHANNEL_MODE_CALIB_IQ)
+               cl_calib_handle_cfm_iq(cl_hw, cl_hw->mask_num_antennas);
+
+       if (mode & SET_CHANNEL_MODE_CALIB_LOLC)
+               cl_calib_handle_cfm_iq_lolc(cl_hw, cl_hw->mask_num_antennas);
+
+       /* Print calibration errors counters */
+       cl_calib_print_errors(cl_hw);
+
+       memset(errors, 0, sizeof(*errors));
+
+       return 0;
+}
+
+int cl_calib_validate_ants(struct cl_hw *cl_hw)
+{
+       struct cl_tcv_conf *conf = cl_hw->conf;
+       u8 ant = 0;
+       int ret = 0;
+
+       for (ant = 0; ant < cl_hw->num_antennas; ant++) {
+               if (conf->ci_calib_ant_tx[ant] < cl_hw->first_ant ||
+                   conf->ci_calib_ant_tx[ant] > cl_hw->last_ant) {
+                       CL_DBG_ERROR(cl_hw,
+                                    "TX: Antenna [%u] value is out of boundaries [%u].\n"
+                                    "Minimum value allowed is: %u\n"
+                                    "Maximum value allowed is: %u\n",
+                                    ant, conf->ci_calib_ant_tx[ant], cl_hw->first_ant,
+                                    cl_hw->last_ant);
+                       ret = -1;
+               }
+
+               if (conf->ci_calib_ant_rx[ant] < cl_hw->first_ant ||
+                   conf->ci_calib_ant_rx[ant] > cl_hw->last_ant) {
+                       CL_DBG_ERROR(cl_hw,
+                                    "RX: Antenna [%u] value is out of boundaries [%u]."
+                                    "Minimum value allowed is: %u\n"
+                                    "Maximum value allowed is: %u\n",
+                                    ant, conf->ci_calib_ant_tx[ant], cl_hw->first_ant,
+                                    cl_hw->last_ant);
+                       ret = -1;
+               }
+       }
+
+       return ret;
+}
+
+void cl_calib_iq_get_tone_vector(u8 bw, u16 *tone_vector)
+{
+       u8 tone = 0;
+
+       for (tone = 0; tone < IQ_NUM_TONES_REQ; tone++)
+               tone_vector[tone] = cpu_to_le16((u16)tone_vector_arr[bw][tone]);
+}
+
+static int cl_calib_print_dcoc(struct cl_hw *cl_hw)
+{
+       struct cl_calib_db *calib_db = &cl_hw->chip->calib_db;
+       struct cl_dcoc_calib *dcoc_calib;
+       u8 lna = 0;
+       u8 ant = 0;
+       u8 channel_idx = cl_calib_center_freq_to_idx(cl_hw, cl_hw->center_freq);
+       u8 tcv_idx = cl_hw->tcv_idx;
+       u8 sx = tcv_idx;
+       u8 bw = cl_hw->bw;
+       char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       int err = 0;
+       int len = 0;
+
+       if (!buf)
+               return -ENOMEM;
+
+       len += snprintf(buf + len, PAGE_SIZE - len,
+                       "DCOC:\n"
+                       "LNA GAIN   ANTENNA   I    Q\n"
+                       "----------------------------\n");
+
+       for (lna = 0; lna < DCOC_LNA_GAIN_NUM; lna++) {
+               ant_for_each(ant) {
+                       dcoc_calib =
+                               &calib_db->dcoc[tcv_idx][channel_idx][bw][sx][ant][lna];
+
+                       len += snprintf(buf + len, PAGE_SIZE - len,
+                                       "%-11u%-10u%-5d%-5d\n", lna,
+                                       ant, dcoc_calib->i, dcoc_calib->q);
+               }
+       }
+
+       err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_calib_print_lolc(struct cl_hw *cl_hw)
+{
+       struct cl_calib_db *calib_db = &cl_hw->chip->calib_db;
+       u32 lolc_calib;
+       u8 ant = 0;
+       u8 channel_idx = cl_calib_center_freq_to_idx(cl_hw, cl_hw->center_freq);
+       u8 tcv_idx = cl_hw->tcv_idx;
+       u8 sx = tcv_idx;
+       u8 bw = cl_hw->bw;
+       char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       int err = 0;
+       int len = 0;
+
+       if (!buf)
+               return -ENOMEM;
+
+       len += snprintf(buf + len, PAGE_SIZE - len,
+                       "LOLC:\n"
+                       "ANTENNA   I     Q\n"
+                       "---------------------\n");
+
+       ant_for_each(ant) {
+               lolc_calib = calib_db->iq_tx_lolc[tcv_idx][channel_idx][bw][sx][ant];
+
+               len += snprintf(buf + len, PAGE_SIZE - len,
+                               "%-10u%-6d%-6d\n",
+                               ant, CAST_S12_TO_S32(lolc_calib & U12_BIT_MASK),
+                               CAST_S12_TO_S32((lolc_calib >> 2) & U12_BIT_MASK));
+       }
+
+       err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_calib_print_iq(struct cl_hw *cl_hw)
+{
+       struct cl_calib_db *calib_db = &cl_hw->chip->calib_db;
+       struct cl_iq_calib *iq;
+       u8 ant = 0;
+       u8 channel_idx = cl_calib_center_freq_to_idx(cl_hw, cl_hw->center_freq);
+       u8 tcv_idx = cl_hw->tcv_idx;
+       u8 sx = tcv_idx;
+       u8 bw = cl_hw->bw;
+       char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       int err = 0;
+       int len = 0;
+
+       if (!buf)
+               return -ENOMEM;
+
+       len += snprintf(buf + len, PAGE_SIZE - len,
+                       "IQ TX:\n"
+                       "ANTENNA COEF0      COEF1      COEF2      GAIN\n"
+                       "---------------------------------------------------\n");
+
+       ant_for_each(ant) {
+               iq = &calib_db->iq_tx[tcv_idx][channel_idx][bw][sx][ant];
+
+               len += snprintf(buf + len, PAGE_SIZE - len,
+                               "%-7u 0x%08x 0x%08x 0x%08x 0x%08x\n",
+                               ant, iq->coef0, iq->coef1, iq->coef2, iq->gain);
+       }
+
+       len += snprintf(buf + len, PAGE_SIZE - len,
+                       "IQ RX:\n"
+                       "ANTENNA COEF0      COEF1      COEF2      GAIN\n"
+                       "---------------------------------------------------\n");
+
+       ant_for_each(ant) {
+               iq = &calib_db->iq_rx[tcv_idx][channel_idx][bw][sx][ant];
+
+               len += snprintf(buf + len, PAGE_SIZE - len,
+                               "%-7u 0x%08x 0x%08x 0x%08x 0x%08x\n",
+                               ant, iq->coef0, iq->coef1, iq->coef2, iq->gain);
+       }
+       err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_calib_common_cli_help(struct cl_hw *cl_hw)
+{
+       char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       int err = 0;
+
+       if (!buf)
+               return -ENOMEM;
+
+       snprintf(buf, PAGE_SIZE,
+                "calib usage:\n"
+                "-d : Print DCOC coefficients\n"
+                "-i : Print IQ coefficients\n"
+                "-l : Print LOLC coefficients\n");
+
+       err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+       kfree(buf);
+
+       return err;
+}
+
+int cl_calib_cli(struct cl_hw *cl_hw, struct cli_params *cli_params)
+{
+       switch (cli_params->option) {
+       case 'd':
+               return cl_calib_print_dcoc(cl_hw);
+       case 'i':
+               return cl_calib_print_iq(cl_hw);
+       case 'l':
+               return cl_calib_print_lolc(cl_hw);
+       case '?':
+               return cl_calib_common_cli_help(cl_hw);
+       default:
+               cl_dbg_err(cl_hw, "Illegal option (%c) - try '?' for help\n",
+                          cli_params->option);
+               return 0;
+       }
+}