diff mbox series

[RFC,v1,133/256] cl8k: add power.c

Message ID 20210617160223.160998-134-viktor.barna@celeno.com (mailing list archive)
State RFC
Delegated to: Kalle Valo
Headers show
Series wireless: cl8k driver for Celeno IEEE 802.11ax devices | expand

Commit Message

Viktor Barna June 17, 2021, 4 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/power.c | 946 +++++++++++++++++++++++
 1 file changed, 946 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/power.c

--
2.30.0
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/power.c b/drivers/net/wireless/celeno/cl8k/power.c
new file mode 100644
index 000000000000..6662bc0ebd66
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/power.c
@@ -0,0 +1,946 @@ 
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include <linux/string.h>
+#include "power.h"
+#include "reg/reg_access.h"
+#include "chan_info.h"
+#include "utils/utils.h"
+#include "band.h"
+#include "utils/string.h"
+#include "channel.h"
+
+static s32 convert_str_int_q8(s8 *str)
+{
+       s32 x, y;
+
+       if (!str)
+               return 0;
+       if (sscanf(str, "%d.%2d", &x, &y) == 0)
+               return 0;
+       if (!strstr(str, "."))
+               return x << 8;
+       if (y < 10 && (*(strstr(str, ".") + 1) != '0'))
+               y *= 10;
+       return ((x * 100 + y) << 8) / 100;
+}
+
+u8 cl_power_tx_ant(struct cl_hw *cl_hw, enum cl_wrs_mode mode)
+{
+       if (mode == WRS_MODE_CCK)
+               return hweight8(cl_hw->conf->ce_cck_tx_ant_mask);
+
+       if (mode <= WRS_MODE_VHT)
+               return min_t(u8, cl_hw->num_antennas, MAX_ANTENNAS_OFDM_HT_VHT);
+
+       return cl_hw->num_antennas;
+}
+
+s32 cl_power_antenna_gain_q8(struct cl_hw *cl_hw)
+{
+       u8 channel = cl_hw->channel;
+
+       if (channel >= 36 && channel <= 64)
+               return convert_str_int_q8(cl_hw->conf->ce_ant_gain_36_64);
+       else if (channel >= 100 && channel <= 140)
+               return convert_str_int_q8(cl_hw->conf->ce_ant_gain_100_140);
+       else if (channel >= 149 && channel < 165)
+               return convert_str_int_q8(cl_hw->conf->ce_ant_gain_149_165);
+       else
+               return convert_str_int_q8(cl_hw->conf->ce_ant_gain); /* 2.4g and 6g */
+}
+
+s32 cl_power_antenna_gain_q1(struct cl_hw *cl_hw)
+{
+       return cl_power_antenna_gain_q8(cl_hw) >> 7;
+}
+
+s32 cl_power_array_gain_q8(struct cl_hw *cl_hw, u8 tx_ant)
+{
+       /*
+        * Format in NVRAM of ce_arr_gain=A,B,C,D,E,F
+        * A is the array gain with 1 tx_ant, B is with 2 tx_ant and so on...
+        */
+       int arr_gain_val = 0;
+       int arr_gain_len = 0;
+       int idx = 0;
+       s8 *arr_gain_cpy = NULL;
+       s8 *arr_gain_str = NULL;
+       s8 *arr_gain_tmp = NULL;
+
+       arr_gain_len = strlen(cl_hw->conf->ce_arr_gain) + 1;
+       arr_gain_cpy = kzalloc(arr_gain_len, GFP_ATOMIC);
+
+       if (!arr_gain_cpy)
+               return 0;
+
+       /* Copy cl_hw->conf->ce_arr_gain so its value won't be changed by cl_strtok_r() */
+       memcpy(arr_gain_cpy, cl_hw->conf->ce_arr_gain, arr_gain_len);
+
+       /* Arr_gain_str points to the array gain of 1 tx_ant */
+       arr_gain_str = cl_strtok_r(arr_gain_cpy, ",", &arr_gain_tmp);
+
+       /* Only a single value in ce_arr_gain - same value will be applied for all tx_ant */
+       if (!arr_gain_tmp) {
+               arr_gain_val = convert_str_int_q8(arr_gain_cpy);
+       } else {
+               /* Keep iterating until getting to the correct ant idx */
+               for (idx = 1; arr_gain_str && (idx < tx_ant); idx++)
+                       arr_gain_str = cl_strtok_r(NULL, ",", &arr_gain_tmp);
+
+               arr_gain_val = arr_gain_str ? convert_str_int_q8(arr_gain_str) : 0;
+       }
+
+       kfree(arr_gain_cpy);
+
+       return arr_gain_val;
+}
+
+s8 cl_power_array_gain_q2(struct cl_hw *cl_hw, u8 tx_ant)
+{
+       return (s8)(cl_power_array_gain_q8(cl_hw, tx_ant) >> 6);
+}
+
+s32 cl_power_array_gain_q1(struct cl_hw *cl_hw, u8 tx_ant)
+{
+       return cl_power_array_gain_q8(cl_hw, tx_ant) >> 7;
+}
+
+static s32 cl_power_bf_gain_q8(struct cl_hw *cl_hw, u8 tx_ant, u8 nss)
+{
+       /*
+        * Format in NVRAM of ce_bf_gain=A,B,C,D
+        * A is the bf gain with 1 NSS, B is with 2 NSS and so on...
+        */
+       int bf_gain_val = 0;
+       int bf_gain_len = 0;
+       int idx = 0;
+       s8 *bf_gain_ptr = NULL;
+       s8 *bf_gain_cpy = NULL;
+       s8 *bf_gain_str = NULL;
+       s8 *bf_gain_tmp = NULL;
+
+       if (tx_ant == 6) {
+               bf_gain_ptr = cl_hw->conf->ce_bf_gain_6_ant;
+       } else if (tx_ant == 5) {
+               bf_gain_ptr = cl_hw->conf->ce_bf_gain_5_ant;
+       } else if (tx_ant == 4) {
+               bf_gain_ptr = cl_hw->conf->ce_bf_gain_4_ant;
+       } else if (tx_ant == 3) {
+               bf_gain_ptr = cl_hw->conf->ce_bf_gain_3_ant;
+       } else if (tx_ant == 2) {
+               bf_gain_ptr = cl_hw->conf->ce_bf_gain_2_ant;
+       } else if (tx_ant == 1) {
+               goto out;
+       } else {
+               pr_err("[%s]: invalid tx_ant %u\n", __func__, tx_ant);
+               goto out;
+       }
+
+       bf_gain_len = strlen(bf_gain_ptr) + 1;
+       bf_gain_cpy = kzalloc(bf_gain_len, GFP_ATOMIC);
+
+       if (!bf_gain_cpy)
+               return 0;
+
+       /* Copy cl_hw->conf->ce_bf_gain_*_ant so its value won't be changed by cl_strtok_r() */
+       memcpy(bf_gain_cpy, bf_gain_ptr, bf_gain_len);
+
+       /* Bf_gain_str points to the bf gain of 1 SS */
+       bf_gain_str = cl_strtok_r(bf_gain_cpy, ",", &bf_gain_tmp);
+
+       /* Keep iterating until getting to the correct ss index */
+       for (idx = 0; bf_gain_str && (idx < nss); idx++)
+               bf_gain_str = cl_strtok_r(NULL, ",", &bf_gain_tmp);
+
+       bf_gain_val = bf_gain_str ? convert_str_int_q8(bf_gain_str) : 0;
+
+       kfree(bf_gain_cpy);
+ out:
+       return bf_gain_val;
+}
+
+s32 cl_power_bf_gain_q1(struct cl_hw *cl_hw, u8 tx_ant, u8 nss)
+{
+       return cl_power_bf_gain_q8(cl_hw, tx_ant, nss) >> 7;
+}
+
+static s32 cl_power_min_ant_q8(struct cl_hw *cl_hw)
+{
+       return convert_str_int_q8(cl_hw->conf->ci_min_ant_pwr);
+}
+
+s32 cl_power_min_ant_q1(struct cl_hw *cl_hw)
+{
+       return cl_power_min_ant_q8(cl_hw) >> 7;
+};
+
+s8 cl_power_bw_factor_q2(struct cl_hw *cl_hw, u8 bw)
+{
+       /*
+        * Format in NVRAM of ci_bw_factor=A,B,C,D
+        * A is the bw factor for bw 20MHz, B is for 40MHz and so on..
+        */
+       int bw_factor_val = 0;
+       int bw_factor_len = 0;
+       int idx = 0;
+       s8 *bw_factor_cpy = NULL;
+       s8 *bw_factor_str = NULL;
+       s8 *bw_factor_tmp = NULL;
+
+       bw_factor_len = strlen(cl_hw->conf->ci_bw_factor) + 1;
+       bw_factor_cpy = kzalloc(bw_factor_len, GFP_ATOMIC);
+
+       if (!bw_factor_cpy)
+               return 0;
+
+       /* Copy cl_hw->conf->ci_bw_factor so its value won't be changed by cl_strtok_r() */
+       memcpy(bw_factor_cpy, cl_hw->conf->ci_bw_factor, bw_factor_len);
+
+       /* Bw_factor_str points to the bw factor of 20MHz */
+       bw_factor_str = cl_strtok_r(bw_factor_cpy, ",", &bw_factor_tmp);
+
+       /* Only a single value in ci_bw_factor - same value will be applied for all bandwidths */
+       if (!bw_factor_tmp) {
+               bw_factor_val = convert_str_int_q8(bw_factor_cpy);
+       } else {
+               /* Keep iterating until getting to the correct bw index */
+               for (idx = 0; bw_factor_str && (idx < bw); idx++)
+                       bw_factor_str = cl_strtok_r(NULL, ",", &bw_factor_tmp);
+
+               bw_factor_val = bw_factor_str ? convert_str_int_q8(bw_factor_str) : 0;
+       }
+
+       kfree(bw_factor_cpy);
+
+       return (s8)(bw_factor_val >> 6);
+}
+
+static s32 cl_power_average_calib_q8(struct cl_hw *cl_hw, u8 ant_num)
+{
+       u8 ant = 0;
+       u8 chan_idx = cl_channel_to_index(cl_hw, cl_hw->channel);
+       s32 total_calib_pow = 0;
+
+       if (chan_idx == INVALID_CHAN_IDX)
+               return 0;
+
+       for (ant = 0; ant < ant_num; ant++)
+               total_calib_pow += cl_hw->tx_pow_info[chan_idx][ant].power;
+
+       return ((total_calib_pow << 8) / ant_num);
+}
+
+s32 cl_power_average_calib_q1(struct cl_hw *cl_hw, u8 ant_num)
+{
+       return cl_power_average_calib_q8(cl_hw, ant_num) >> 7;
+}
+
+static s32 cl_power_total_q8(struct cl_hw *cl_hw, s8 pwr_offset_q1, u8 tx_ant, u8 nss,
+                            enum cl_wrs_mode mode, bool is_auto_resp)
+{
+       s32 bf_gain_q8 =  0;
+       s32 antenna_gain_q8 = cl_power_antenna_gain_q8(cl_hw);
+       s32 array_gain_q8 = cl_power_array_gain_q8(cl_hw, tx_ant);
+       s32 pwr_offset_q8 = (s32)pwr_offset_q1 << 7;
+       s32 calib_power_q8 = cl_power_average_calib_q8(cl_hw, tx_ant);
+       s32 total_power_q8 = 0;
+
+       if (!is_auto_resp)
+               bf_gain_q8 = (mode > WRS_MODE_OFDM) ? cl_power_bf_gain_q8(cl_hw, tx_ant, nss) : 0;
+
+       total_power_q8 = calib_power_q8 + bf_gain_q8 + array_gain_q8 +
+               antenna_gain_q8 + pwr_offset_q8;
+
+       /* FCC calculation */
+       if (cl_hw->channel_info.standard == CL_STANDARD_FCC)
+               total_power_q8 -= min(bf_gain_q8 + antenna_gain_q8, 6 << 8);
+
+       return total_power_q8;
+}
+
+s32 cl_power_total_q1(struct cl_hw *cl_hw, s8 pwr_offset_q1, u8 tx_ant, u8 nss,
+                     enum cl_wrs_mode mode, bool is_auto_resp)
+{
+       return cl_power_total_q8(cl_hw, pwr_offset_q1, tx_ant, nss, mode, is_auto_resp) >> 7;
+}
+
+static s32 cl_power_eirp_delta_q1(struct cl_hw *cl_hw, u8 bw, s8 pwr_offset_q1, u8 tx_ant,
+                                 u8 nss, enum cl_wrs_mode mode, bool is_auto_resp)
+{
+       /* Calculate total TX power */
+       s32 total_power_q8 = cl_power_total_q8(cl_hw, pwr_offset_q1, tx_ant, nss,
+                                              mode, is_auto_resp);
+
+       /* EIRP power limit */
+       s32 eirp_power_limit_q8 = cl_chan_info_get_eirp_limit_q8(cl_hw, bw);
+
+       /* Delta between total TX power and EIRP limit */
+       return (total_power_q8 - eirp_power_limit_q8) >> 7;
+}
+
+static s8 cl_power_calc_q1(struct cl_hw *cl_hw, s8 mcs_offset_q1, u8 bw, u8 nss,
+                          enum cl_wrs_mode mode, bool is_auto_resp, u8 *trunc_pwr_q1)
+{
+       /* Result is in 0.5dBm resolution */
+       u8 tx_ant = cl_power_tx_ant(cl_hw, mode);
+       s32 calib_power_q1 = cl_power_average_calib_q1(cl_hw, tx_ant);
+       s32 res_q1 = calib_power_q1 + mcs_offset_q1;
+       s32 min_pwr_q1 = POWER_MIN_DB_Q1;
+
+       if (cl_hw->channel_info.use_channel_info && cl_hw->conf->ci_eirp_regulatory_en) {
+               s32 delta_power_q1 = cl_power_eirp_delta_q1(cl_hw, bw, mcs_offset_q1,
+                                                           tx_ant, nss, mode, is_auto_resp);
+
+               if (delta_power_q1 > 0) {
+                       /*
+                        * If tx power is greater than the limitation
+                        * subtract delta power from the result
+                        */
+                       res_q1 -= delta_power_q1;
+                       *trunc_pwr_q1 = delta_power_q1;
+               } else {
+                       *trunc_pwr_q1 = 0;
+               }
+       } else {
+               *trunc_pwr_q1 = 0;
+       }
+
+       if (is_auto_resp)
+               min_pwr_q1 += cl_power_min_ant_q1(cl_hw);
+
+       if (res_q1 < min_pwr_q1) {
+               *trunc_pwr_q1 = max((s32)(*trunc_pwr_q1) - min_pwr_q1 - res_q1, 0);
+               res_q1 = min_pwr_q1;
+       }
+
+       if (is_auto_resp)
+               res_q1 += cl_power_array_gain_q1(cl_hw, tx_ant);
+
+       return (s8)res_q1;
+}
+
+static s8 cl_power_offset_he(struct cl_hw *cl_hw, u8 bw, u8 mcs)
+{
+       u8 channel = cl_hw->channel;
+       s8 *ppmcs = NULL;
+
+       switch (cl_hw->conf->ci_band_num) {
+       case BAND_5G:
+               if (channel >= 36 && channel <= 64)
+                       ppmcs = cl_hw->conf->ce_ppmcs_offset_he_36_64;
+               else if (channel >= 100 && channel <= 140)
+                       ppmcs = cl_hw->conf->ce_ppmcs_offset_he_100_140;
+               else
+                       ppmcs = cl_hw->conf->ce_ppmcs_offset_he_149_165;
+               break;
+       case BAND_24G:
+               ppmcs = cl_hw->conf->ce_ppmcs_offset_he;
+               break;
+       case BAND_6G:
+               ppmcs = cl_hw->conf->ce_ppmcs_offset_he_6g;
+               break;
+       default:
+               return 0;
+       }
+
+       return ppmcs[mcs] + cl_hw->conf->ce_ppbw_offset[bw];
+}
+
+static s8 cl_power_offset_ht_vht(struct cl_hw *cl_hw, u8 bw, u8 mcs)
+{
+       u8 channel = cl_hw->channel;
+       s8 *ppmcs = NULL;
+
+       switch (cl_hw->conf->ci_band_num) {
+       case BAND_5G:
+               if (channel >= 36 && channel <= 64)
+                       ppmcs = cl_hw->conf->ce_ppmcs_offset_ht_vht_36_64;
+               else if (channel >= 100 && channel <= 140)
+                       ppmcs = cl_hw->conf->ce_ppmcs_offset_ht_vht_100_140;
+               else
+                       ppmcs = cl_hw->conf->ce_ppmcs_offset_ht_vht_149_165;
+               break;
+       case BAND_24G:
+               ppmcs = cl_hw->conf->ce_ppmcs_offset_ht;
+               break;
+       case BAND_6G:
+       default:
+               return 0;
+       }
+
+       return ppmcs[mcs] + cl_hw->conf->ce_ppbw_offset[bw];
+}
+
+static s8 cl_power_offset_ofdm(struct cl_hw *cl_hw, u8 mcs)
+{
+       u8 channel = cl_hw->channel;
+       s8 *ppmcs = NULL;
+
+       switch (cl_hw->conf->ci_band_num) {
+       case BAND_5G:
+               if (channel >= 36 && channel <= 64)
+                       ppmcs = cl_hw->conf->ce_ppmcs_offset_ofdm_36_64;
+               else if (channel >= 100 && channel <= 140)
+                       ppmcs = cl_hw->conf->ce_ppmcs_offset_ofdm_100_140;
+               else
+                       ppmcs = cl_hw->conf->ce_ppmcs_offset_ofdm_149_165;
+               break;
+       case BAND_24G:
+               ppmcs = cl_hw->conf->ce_ppmcs_offset_ofdm;
+               break;
+       case BAND_6G:
+       default:
+               return 0;
+       }
+
+       return ppmcs[mcs] + cl_hw->conf->ce_ppbw_offset[CHNL_BW_20];
+}
+
+static s8 cl_power_offset_cck(struct cl_hw *cl_hw, u8 mcs)
+{
+       s8 *ppmcs = cl_hw->conf->ce_ppmcs_offset_cck;
+
+       if (cl_band_is_24g(cl_hw))
+               return ppmcs[mcs] + cl_hw->conf->ce_ppbw_offset[CHNL_BW_20];
+
+       return 0;
+}
+
+s8 cl_power_offset_q1(struct cl_hw *cl_hw, u8 mode, u8 bw, u8 mcs)
+{
+       if (mode == WRS_MODE_HE)
+               return cl_power_offset_he(cl_hw, bw, mcs);
+       else if (mode == WRS_MODE_HT || mode == WRS_MODE_VHT)
+               return cl_power_offset_ht_vht(cl_hw, bw, mcs);
+       else if (mode == WRS_MODE_OFDM)
+               return cl_power_offset_ofdm(cl_hw, mcs);
+       else if (mode == WRS_MODE_CCK)
+               return cl_power_offset_cck(cl_hw, mcs);
+
+       return 0;
+}
+
+#define UPPER_POWER_MARGIN_Q2 (38 << 2)
+#define LOWER_POWER_MARGIN_Q2 (50 << 2)
+
+s8 cl_power_offset_check_margin(struct cl_hw *cl_hw, u8 bw, u8 ant_idx, s8 offset_q2)
+{
+       s8 new_offset_q2 = 0;
+       s8 bw_factor_q2 = cl_hw->power_db.bw_factor_q2[bw];
+       s8 ant_factor_q2 = cl_hw->power_db.ant_factor_q2[ant_idx];
+       s8 total_offset_upper_q2 = bw_factor_q2 + offset_q2;
+       s8 total_offset_lower_q2 = bw_factor_q2 + ant_factor_q2 + offset_q2;
+       bool upper_limit_valid = (total_offset_upper_q2 <= UPPER_POWER_MARGIN_Q2);
+       bool lower_limit_valid = (total_offset_lower_q2 <= LOWER_POWER_MARGIN_Q2);
+
+       if (upper_limit_valid && lower_limit_valid) {
+               return offset_q2;
+       } else if (!upper_limit_valid && lower_limit_valid) {
+               new_offset_q2 = UPPER_POWER_MARGIN_Q2 - bw_factor_q2;
+
+               return new_offset_q2;
+       } else if (upper_limit_valid && !lower_limit_valid) {
+               new_offset_q2 = LOWER_POWER_MARGIN_Q2 - bw_factor_q2 - ant_factor_q2;
+
+               return new_offset_q2;
+       }
+
+       new_offset_q2 = min(UPPER_POWER_MARGIN_Q2 - bw_factor_q2,
+                           LOWER_POWER_MARGIN_Q2 - bw_factor_q2 - ant_factor_q2);
+
+       return new_offset_q2;
+}
+
+static void cl_power_tables_update_cck(struct cl_hw *cl_hw,
+                                      struct cl_pwr_tables *pwr_tables)
+{
+       u8 mcs;
+       u8 trunc_value = 0;
+       s8 pwr_offset_q1;
+
+       /* CCK - Enforce EIRP limitations */
+       for (mcs = 0; mcs < WRS_MCS_MAX_CCK; mcs++) {
+               pwr_offset_q1 = cl_power_offset_q1(cl_hw, WRS_MODE_CCK, CHNL_BW_20, mcs);
+
+               pwr_tables->ant_pwr_cck[mcs] =
+                       cl_power_calc_q1(cl_hw, pwr_offset_q1, 0, 0,
+                                        WRS_MODE_CCK, false, &trunc_value);
+
+               cl_hw->pwr_trunc.cck[mcs] = trunc_value;
+
+               /* Auto response */
+               pwr_tables->pwr_auto_resp_cck[mcs] =
+                       cl_power_calc_q1(cl_hw, pwr_offset_q1, 0, 0,
+                                        WRS_MODE_CCK, true, &trunc_value);
+       }
+}
+
+static void cl_power_tables_update_ofdm(struct cl_hw *cl_hw,
+                                       struct cl_pwr_tables *pwr_tables)
+{
+       u8 mcs;
+       u8 trunc_value = 0;
+       s8 pwr_offset_q1;
+
+       /* OFDM - Enforce EIRP limitations */
+       for (mcs = 0; mcs < WRS_MCS_MAX_OFDM; mcs++) {
+               pwr_offset_q1 = cl_power_offset_q1(cl_hw, WRS_MODE_OFDM, CHNL_BW_20, mcs);
+
+               pwr_tables->ant_pwr_ofdm[mcs] =
+                       cl_power_calc_q1(cl_hw, pwr_offset_q1, 0, 0,
+                                        WRS_MODE_OFDM, false, &trunc_value);
+
+               cl_hw->pwr_trunc.ofdm[mcs] = trunc_value;
+
+               /* Auto response */
+               pwr_tables->pwr_auto_resp_ofdm[mcs] =
+                       cl_power_calc_q1(cl_hw, pwr_offset_q1, 0, 0,
+                                        WRS_MODE_OFDM, true, &trunc_value);
+       }
+}
+
+static u8 cl_power_tables_update_ht_vht(struct cl_hw *cl_hw,
+                                       struct cl_pwr_tables *pwr_tables)
+{
+       bool is_24g = cl_band_is_24g(cl_hw);
+       bool is_5g = cl_band_is_5g(cl_hw);
+       u8 bw;
+       u8 nss;
+       u8 mcs;
+       u8 trunc_value = 0;
+       u8 min_bw_idx_limit_vht = 0;
+       u8 max_mcs_ht_vht = (is_5g || (is_24g && cl_hw->conf->ci_vht_cap_24g)) ?
+               WRS_MCS_MAX_VHT : WRS_MCS_MAX_HT;
+       s8 pwr_offset_q1;
+       s16 min_bw_limit = 0;
+       s32 eirp_power_limit_q8;
+
+       for (bw = 0, min_bw_limit = 0xFFFF; bw < max_bw_idx(WRS_MODE_VHT, is_24g); bw++) {
+               if (!cl_hw->chip->conf->ce_production_mode &&
+                   !cl_chan_info_get(cl_hw, cl_hw->channel, bw))
+                       continue;
+
+               /* Find lowest EIRP power limitation among all bw for auto resp calculations */
+               eirp_power_limit_q8 = cl_chan_info_get_eirp_limit_q8(cl_hw, bw);
+               if (eirp_power_limit_q8 < min_bw_limit) {
+                       min_bw_limit = eirp_power_limit_q8;
+                       min_bw_idx_limit_vht = bw;
+               }
+
+               /* HT/VHT - Enforce EIRP limitations */
+               for (mcs = 0; mcs < max_mcs_ht_vht; mcs++) {
+                       pwr_offset_q1 = cl_power_offset_q1(cl_hw, WRS_MODE_VHT, bw, mcs);
+
+                       for (nss = 0; nss < PWR_TBL_VHT_BF_SIZE; nss++) {
+                               pwr_tables->ant_pwr_ht_vht[bw][mcs][nss] =
+                                       cl_power_calc_q1(cl_hw, pwr_offset_q1,
+                                                        bw, nss, WRS_MODE_VHT, false,
+                                                        &trunc_value);
+                               cl_hw->pwr_trunc.ht_vht[bw][mcs][nss] = trunc_value;
+                       }
+               }
+       }
+
+       /* Auto resp HT/VHT - Enforce EIRP limitations */
+       for (mcs = 0; mcs < max_mcs_ht_vht; mcs++) {
+               pwr_offset_q1 = cl_power_offset_q1(cl_hw, WRS_MODE_VHT, CHNL_BW_20, mcs);
+
+               pwr_tables->pwr_auto_resp_ht_vht[mcs] =
+                       cl_power_calc_q1(cl_hw, pwr_offset_q1,
+                                        min_bw_idx_limit_vht, 0, WRS_MODE_VHT,
+                                        true, &trunc_value);
+       }
+
+       return min_bw_idx_limit_vht;
+}
+
+static u8 cl_power_tables_update_he(struct cl_hw *cl_hw,
+                                   struct cl_pwr_tables *pwr_tables)
+{
+       bool is_24g = cl_band_is_24g(cl_hw);
+       u8 bw;
+       u8 nss;
+       u8 mcs;
+       u8 trunc_value = 0;
+       u8 min_bw_idx_limit_he = 0;
+       s8 pwr_offset_q1;
+       s16 min_bw_limit = 0;
+       s32 eirp_power_limit_q8;
+
+       for (bw = 0, min_bw_limit = 0xFFFF; bw < max_bw_idx(WRS_MODE_HE, is_24g); bw++) {
+               if (!cl_hw->chip->conf->ce_production_mode &&
+                   !cl_chan_info_get(cl_hw, cl_hw->channel, bw))
+                       continue;
+
+               /* Find lowest EIRP power limitation among all bw for auto resp calculations */
+               eirp_power_limit_q8 = cl_chan_info_get_eirp_limit_q8(cl_hw, bw);
+               if (eirp_power_limit_q8 < min_bw_limit) {
+                       min_bw_limit = eirp_power_limit_q8;
+                       min_bw_idx_limit_he = bw;
+               }
+
+               /* HE - Enforce EIRP limitations */
+               for (mcs = 0; mcs < WRS_MCS_MAX_HE; mcs++) {
+                       pwr_offset_q1 = cl_power_offset_q1(cl_hw, WRS_MODE_HE, bw, mcs);
+
+                       for (nss = 0; nss < PWR_TBL_HE_BF_SIZE; nss++) {
+                               pwr_tables->ant_pwr_he[bw][mcs][nss] =
+                                       cl_power_calc_q1(cl_hw, pwr_offset_q1,
+                                                        bw, nss, WRS_MODE_HE, false,
+                                                        &trunc_value);
+                               cl_hw->pwr_trunc.he[bw][mcs][nss] = trunc_value;
+                       }
+               }
+       }
+
+       /* Auto resp HE - Enforce EIRP limitations */
+       for (mcs = 0; mcs < WRS_MCS_MAX_HE; mcs++) {
+               pwr_offset_q1 = cl_power_offset_q1(cl_hw, WRS_MODE_HE, CHNL_BW_20, mcs);
+
+               pwr_tables->pwr_auto_resp_he[mcs] =
+                       cl_power_calc_q1(cl_hw, pwr_offset_q1, min_bw_idx_limit_he,
+                                        nss, WRS_MODE_HE, true, &trunc_value);
+       }
+
+       return min_bw_idx_limit_he;
+}
+
+static u8 cl_power_calc_max(struct cl_hw *cl_hw, u8 bw, enum cl_wrs_mode mode)
+{
+       u8 tx_ant = cl_power_tx_ant(cl_hw, mode);
+       /* Total TX power - pass is_auto_resp = true in order to ignore bf gain */
+       s32 total_power_q8 = cl_power_total_q8(cl_hw, 0, tx_ant, 0, mode, true);
+       /* EIRP power limit */
+       s32 eirp_power_limit_q8 = cl_chan_info_get_eirp_limit_q8(cl_hw, bw);
+
+       return (min(total_power_q8, eirp_power_limit_q8) >> 8);
+}
+
+static s8 cl_power_vns_calc_q1(struct cl_hw *cl_hw, u8 bw,
+                              enum cl_wrs_mode mode, bool is_auto_resp)
+{
+       u8 max_tx_pwr = cl_power_calc_max(cl_hw, bw, mode);
+       u8 tx_ant = cl_power_tx_ant(cl_hw, mode);
+       s32 vns_pwr_limit_q8 = min_t(u8, cl_hw->conf->ci_vns_pwr_limit, max_tx_pwr) << 8;
+       s32 antenna_gain_q8 = cl_power_antenna_gain_q8(cl_hw);
+       s32 array_gain_q8 = (is_auto_resp ? 0 : cl_power_array_gain_q8(cl_hw, tx_ant));
+       s32 min_ant_pwr_q8 = cl_power_min_ant_q8(cl_hw);
+       s32 min_pwr_q8 = is_auto_resp ? (POWER_MIN_DB_Q8 + min_ant_pwr_q8) : POWER_MIN_DB_Q8;
+       s32 res_q8 = vns_pwr_limit_q8 - antenna_gain_q8 - array_gain_q8;
+
+       if (res_q8 < min_pwr_q8)
+               res_q8 = min_pwr_q8;
+
+       /* Result should be in 0.5dBm resolution */
+       return (s8)(res_q8 >> 7);
+}
+
+static void cl_power_tables_update_vns(struct cl_hw *cl_hw,
+                                      struct cl_pwr_tables *pwr_tables,
+                                      u8 min_bw_idx_limit_vht,
+                                      u8 min_bw_idx_limit_he)
+{
+       /* VNS */
+       pwr_tables->ant_pwr_vns_he =
+               cl_power_vns_calc_q1(cl_hw, min_bw_idx_limit_he, WRS_MODE_HE, false);
+       pwr_tables->ant_pwr_vns_ht_vht =
+               cl_power_vns_calc_q1(cl_hw, min_bw_idx_limit_vht, WRS_MODE_VHT, false);
+       pwr_tables->ant_pwr_vns_ofdm =
+               cl_power_vns_calc_q1(cl_hw, 0, WRS_MODE_OFDM, false);
+       pwr_tables->ant_pwr_vns_cck =
+               cl_power_vns_calc_q1(cl_hw, 0, WRS_MODE_CCK, false);
+
+       /* Auto response VNS */
+       pwr_tables->pwr_auto_resp_vns_he =
+               cl_power_vns_calc_q1(cl_hw, min_bw_idx_limit_he, WRS_MODE_HE, true);
+       pwr_tables->pwr_auto_resp_vns_ht_vht =
+               cl_power_vns_calc_q1(cl_hw, min_bw_idx_limit_vht, WRS_MODE_VHT, true);
+       pwr_tables->pwr_auto_resp_vns_ofdm =
+               cl_power_vns_calc_q1(cl_hw, 0, WRS_MODE_OFDM, true);
+       pwr_tables->pwr_auto_resp_vns_cck =
+               cl_power_vns_calc_q1(cl_hw, 0, WRS_MODE_CCK, true);
+}
+
+static void cl_power_tables_update_by_offset(struct cl_hw *cl_hw,
+                                            struct cl_pwr_tables *pwr_tables,
+                                            s8 offset)
+{
+       u8 mcs = 0;
+       u8 bw = 0;
+       u8 nss = 0;
+
+       /* CCK - Enforce EIRP limitations */
+       for (mcs = 0; mcs < WRS_MCS_MAX_CCK; mcs++) {
+               pwr_tables->ant_pwr_cck[mcs] += offset;
+
+               /* Auto response */
+               pwr_tables->pwr_auto_resp_cck[mcs] += offset;
+       }
+
+       /* OFDM - Enforce EIRP limitations */
+       for (mcs = 0; mcs < WRS_MCS_MAX_OFDM; mcs++) {
+               pwr_tables->ant_pwr_ofdm[mcs] += offset;
+
+               /* Auto response */
+               pwr_tables->pwr_auto_resp_ofdm[mcs] += offset;
+       }
+
+       for (bw = 0; bw < CHNL_BW_MAX; bw++) {
+               /* HT/VHT - Enforce EIRP limitations */
+               for (mcs = 0; mcs < WRS_MCS_MAX_VHT; mcs++) {
+                       for (nss = 0; nss < PWR_TBL_VHT_BF_SIZE; nss++)
+                               pwr_tables->ant_pwr_ht_vht[bw][mcs][nss] += offset;
+
+                       /*
+                        * Auto response:
+                        * always with disabled BF so the offset of the last nss is used
+                        */
+                       pwr_tables->pwr_auto_resp_ht_vht[mcs] += offset;
+               }
+
+               /* HE - Enforce EIRP limitations */
+               for (mcs = 0; mcs < WRS_MCS_MAX_HE; mcs++) {
+                       for (nss = 0; nss < PWR_TBL_HE_BF_SIZE; nss++)
+                               pwr_tables->ant_pwr_he[bw][mcs][nss] += offset;
+
+                       /*
+                        * Auto response:
+                        * always with disabled BF so the offset of the last nss is used
+                        */
+                       pwr_tables->pwr_auto_resp_he[mcs] += offset;
+               }
+       }
+}
+
+static s8 cl_power_get_offset(u16 percentage)
+{
+       if (percentage >= 94)
+               return 0;
+       else if (percentage >= 84)
+               return -1; /* -0.5dBm */
+       else if (percentage >= 75)
+               return -2; /* -1dBm */
+       else if (percentage >= 67)
+               return -3; /* -1.5dBm */
+       else if (percentage >= 59)
+               return -4; /* -2dBm */
+       else if (percentage >= 54)
+               return -5; /* -2.5dBm */
+       else if (percentage >= 48)
+               return -6; /* -3dBm */
+       else if (percentage >= 43)
+               return -7; /* -3.5dBm */
+       else if (percentage >= 38)
+               return -8; /* -4dBm */
+       else if (percentage >= 34)
+               return -9; /* -4.5dBm */
+       else if (percentage >= 30)
+               return -10; /* -5dBm */
+       else if (percentage >= 27)
+               return -11; /* -5.5dBm */
+       else if (percentage >= 24)
+               return -12; /* -6dBm */
+       else if (percentage >= 22)
+               return -13; /* -6.5dBm */
+       else if (percentage >= 19)
+               return -14; /* -7dBm */
+       else if (percentage >= 17)
+               return -15; /* -7.5dBm */
+       else if (percentage >= 15)
+               return -16; /* -8dBm */
+       else if (percentage >= 14)
+               return -17; /* -8.5dBm */
+       else if (percentage >= 12)
+               return -18; /* -9dBm */
+       else if (percentage >= 11)
+               return -19; /* -9.5dBm */
+       else if (percentage >= 10)
+               return -20; /* -10dBm */
+       else if (percentage >= 9)
+               return -21; /* -10.5dBm */
+       else if (percentage >= 8)
+               return -22; /* -11dBm */
+       else if (percentage >= 7)
+               return -23; /* -11.5dBm */
+       else if (percentage >= 6)
+               return -24; /* -12dBm */
+       else if (percentage >= 5)
+               return -26; /* -13dBm */
+       else if (percentage >= 4)
+               return -28; /* -14dBm */
+       else if (percentage >= 3)
+               return -30; /* -15dBm */
+       else if (percentage >= 2)
+               return -34; /* -17dBm */
+       else if (percentage >= 1)
+               return -40; /* -20dBm */
+
+       /* Should not get here */
+       return 0;
+}
+
+static void cl_power_control_apply_percentage(struct cl_hw *cl_hw)
+{
+       struct cl_power_db *power_db = &cl_hw->power_db;
+       u8 percentage = cl_hw->conf->ce_tx_power_control;
+
+       power_db->curr_percentage = percentage;
+
+       if (percentage != 100) {
+               power_db->curr_offset = cl_power_get_offset(percentage);
+               cl_power_tables_update_by_offset(cl_hw,
+                                                &cl_hw->phy_data_info.data->pwr_tables,
+                                                power_db->curr_offset);
+       }
+}
+
+void cl_power_tables_update(struct cl_hw *cl_hw, struct cl_pwr_tables *pwr_tables)
+{
+       bool is_24g = cl_band_is_24g(cl_hw);
+       bool is_6g = cl_band_is_6g(cl_hw);
+       u8 min_bw_idx_limit_he = 0;
+       u8 min_bw_idx_limit_vht = 0;
+
+       /*
+        * If tx_power is set then we are in calibration process and
+        * need to set all values in power tables to this value x2.
+        */
+       if (cl_hw->ate_db.active &&
+           cl_hw->ate_db.tx_power >= POWER_MIN_DB &&
+           cl_hw->ate_db.tx_power <= POWER_MAX_DB) {
+               s8 tx_power_q1 = cl_hw->ate_db.tx_power << 1;
+
+               memset(pwr_tables, tx_power_q1, sizeof(struct cl_pwr_tables));
+               return;
+       }
+
+       memset(pwr_tables, 0, sizeof(struct cl_pwr_tables));
+
+       if (is_24g)
+               cl_power_tables_update_cck(cl_hw, pwr_tables);
+
+       if (!is_6g) {
+               cl_power_tables_update_ofdm(cl_hw, pwr_tables);
+               min_bw_idx_limit_vht = cl_power_tables_update_ht_vht(cl_hw, pwr_tables);
+       }
+
+       min_bw_idx_limit_he = cl_power_tables_update_he(cl_hw, pwr_tables);
+
+       cl_power_tables_update_vns(cl_hw, pwr_tables, min_bw_idx_limit_vht, min_bw_idx_limit_he);
+
+       cl_power_control_apply_percentage(cl_hw);
+}
+
+static s32 cl_power_get_max_cck(struct cl_hw *cl_hw)
+{
+       struct cl_pwr_tables *pwr_tables = &cl_hw->phy_data_info.data->pwr_tables;
+       u8 mcs = 0;
+       u8 tx_ant = cl_power_tx_ant(cl_hw, WRS_MODE_CCK);
+       s32 ant_gain_q1 = cl_power_antenna_gain_q1(cl_hw);
+       s32 arr_gain_q1 = cl_power_array_gain_q1(cl_hw, tx_ant);
+       s32 total_pwr_q1 = 0;
+       s32 max_pwr_q1 = 0;
+
+       for (mcs = 0; mcs < WRS_MCS_MAX_CCK; mcs++) {
+               total_pwr_q1 = pwr_tables->ant_pwr_cck[mcs] + ant_gain_q1 + arr_gain_q1;
+
+               if (total_pwr_q1 > max_pwr_q1)
+                       max_pwr_q1 = total_pwr_q1;
+       }
+
+       return max_pwr_q1;
+}
+
+static s32 cl_power_get_max_ofdm(struct cl_hw *cl_hw)
+{
+       struct cl_pwr_tables *pwr_tables = &cl_hw->phy_data_info.data->pwr_tables;
+       u8 mcs = 0;
+       u8 tx_ant = cl_power_tx_ant(cl_hw, WRS_MODE_OFDM);
+       s32 ant_gain_q1 = cl_power_antenna_gain_q1(cl_hw);
+       s32 arr_gain_q1 = cl_power_array_gain_q1(cl_hw, tx_ant);
+       s32 total_pwr_q1 = 0;
+       s32 max_pwr_q1 = 0;
+
+       for (mcs = 0; mcs < WRS_MCS_MAX_OFDM; mcs++) {
+               total_pwr_q1 = pwr_tables->ant_pwr_ofdm[mcs] + ant_gain_q1 + arr_gain_q1;
+
+               if (total_pwr_q1 > max_pwr_q1)
+                       max_pwr_q1 = total_pwr_q1;
+       }
+
+       return max_pwr_q1;
+}
+
+static s32 cl_power_get_max_ht_vht(struct cl_hw *cl_hw)
+{
+       struct cl_pwr_tables *pwr_tables = &cl_hw->phy_data_info.data->pwr_tables;
+       u8 tx_ant = cl_power_tx_ant(cl_hw, WRS_MODE_VHT);
+       u8 mcs = 0;
+       u8 bw = 0;
+       u8 bf = 0;
+       s32 ant_gain_q1 = cl_power_antenna_gain_q1(cl_hw);
+       s32 arr_gain_q1 = cl_power_array_gain_q1(cl_hw, tx_ant);
+       s32 total_pwr_q1 = 0;
+       s32 max_pwr_q1 = 0;
+
+       for (bw = 0; bw < CHNL_BW_MAX; bw++) {
+               for (mcs = 0; mcs < WRS_MCS_MAX_VHT; mcs++) {
+                       for (bf = 0; bf < PWR_TBL_VHT_BF_SIZE; bf++) {
+                               total_pwr_q1 = pwr_tables->ant_pwr_ht_vht[bw][mcs][bf] +
+                                       ant_gain_q1 + arr_gain_q1;
+
+                               if (total_pwr_q1 > max_pwr_q1)
+                                       max_pwr_q1 = total_pwr_q1;
+                       }
+               }
+       }
+
+       return max_pwr_q1;
+}
+
+static s32 cl_power_get_max_he(struct cl_hw *cl_hw)
+{
+       struct cl_pwr_tables *pwr_tables = &cl_hw->phy_data_info.data->pwr_tables;
+       u8 tx_ant = cl_power_tx_ant(cl_hw, WRS_MODE_HE);
+       u8 mcs = 0;
+       u8 bw = 0;
+       u8 bf = 0;
+       s32 ant_gain_q1 = cl_power_antenna_gain_q1(cl_hw);
+       s32 arr_gain_q1 = cl_power_array_gain_q1(cl_hw, tx_ant);
+       s32 total_pwr_q1 = 0;
+       s32 max_pwr_q1 = 0;
+
+       for (bw = 0; bw < CHNL_BW_MAX; bw++) {
+               for (mcs = 0; mcs < WRS_MCS_MAX_HE; mcs++) {
+                       for (bf = 0; bf < PWR_TBL_HE_BF_SIZE; bf++) {
+                               total_pwr_q1 = pwr_tables->ant_pwr_he[bw][mcs][bf] +
+                                       ant_gain_q1 + arr_gain_q1;
+
+                               if (total_pwr_q1 > max_pwr_q1)
+                                       max_pwr_q1 = total_pwr_q1;
+                       }
+               }
+       }
+
+       return max_pwr_q1;
+}
+
+s32 cl_power_get_max(struct cl_hw *cl_hw)
+{
+       bool is_24g = cl_band_is_24g(cl_hw);
+       bool is_6g = cl_band_is_6g(cl_hw);
+       s32 max_pwr_cck_q1 = is_24g ? cl_power_get_max_cck(cl_hw) : S32_MIN;
+       s32 max_pwr_ofdm_q1 = !is_6g ? cl_power_get_max_ofdm(cl_hw) : S32_MIN;
+       s32 max_pwr_ht_vht_q1 = !is_6g ? cl_power_get_max_ht_vht(cl_hw) : S32_MIN;
+       s32 max_pwr_he_q1 = cl_power_get_max_he(cl_hw);
+       s32 max_pwr_q1 = 0;
+
+       max_pwr_q1 = max(max_pwr_q1, max_pwr_cck_q1);
+       max_pwr_q1 = max(max_pwr_q1, max_pwr_ofdm_q1);
+       max_pwr_q1 = max(max_pwr_q1, max_pwr_ht_vht_q1);
+       max_pwr_q1 = max(max_pwr_q1, max_pwr_he_q1);
+
+       return (max_pwr_q1 >> 1);
+}
+