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