new file mode 100644
@@ -0,0 +1,1123 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/string.h>
+
+#include "reg/reg_access.h"
+#include "channel.h"
+#include "debug.h"
+#include "utils.h"
+#include "e2p.h"
+#include "power.h"
+
+static u8 cl_power_table_read(struct cl_hw *cl_hw)
+{
+ u8 pwr_table_id = 0;
+
+ if (cl_e2p_read(cl_hw->chip, &pwr_table_id, 1, ADDR_GEN_PWR_TABLE_ID + cl_hw->tcv_idx))
+ return U8_MAX;
+
+ return pwr_table_id;
+}
+
+static int cl_power_table_fill(struct cl_hw *cl_hw)
+{
+ u8 pwr_table_id = cl_power_table_read(cl_hw);
+ u8 platform_idx = cl_hw->chip->platform.idx;
+ struct cl_platform_table *table = NULL;
+
+ table = cl_platform_get_active_table(cl_hw->chip, platform_idx);
+ if (!table)
+ return cl_hw->chip->conf->ce_production_mode ? 0 : -1;
+
+ switch (pwr_table_id) {
+ case 0:
+ if (cl_band_is_5g(cl_hw)) {
+ memcpy(cl_hw->power_table_info.data->conv_table,
+ table->power_conv_table_5,
+ NUM_POWER_WORDS);
+ cl_hw->tx_power_version = 5;
+ } else if (IS_REAL_PHY(cl_hw->chip)) {
+ CL_DBG_ERROR(cl_hw, "Power table ID (%u) is valid for 5g only\n",
+ pwr_table_id);
+
+ if (!cl_hw_is_prod_or_listener(cl_hw))
+ return -EINVAL;
+ }
+ break;
+ case 1:
+ if (cl_band_is_24g(cl_hw)) {
+ memcpy(cl_hw->power_table_info.data->conv_table,
+ table->power_conv_table_2,
+ NUM_POWER_WORDS);
+ cl_hw->tx_power_version = 25;
+ } else if (IS_REAL_PHY(cl_hw->chip)) {
+ CL_DBG_ERROR(cl_hw, "Power table ID (%u) is valid for 2.4g only\n",
+ pwr_table_id);
+
+ if (!cl_hw_is_prod_or_listener(cl_hw))
+ return -1;
+ }
+ break;
+ case 2:
+ if (cl_band_is_6g(cl_hw)) {
+ memcpy(cl_hw->power_table_info.data->conv_table,
+ table->power_conv_table_6,
+ NUM_POWER_WORDS);
+ cl_hw->tx_power_version = 1;
+ } else if (IS_REAL_PHY(cl_hw->chip)) {
+ CL_DBG_ERROR(cl_hw, "Power table ID (%u) is valid for 6g only\n",
+ pwr_table_id);
+
+ if (!cl_hw_is_prod_or_listener(cl_hw))
+ return -1;
+ }
+ break;
+ default:
+ if (IS_REAL_PHY(cl_hw->chip)) {
+ CL_DBG_ERROR(cl_hw, "Power table ID is not configured in EEPROM\n");
+
+ if (!cl_hw_is_prod_or_listener(cl_hw))
+ return -1;
+ }
+ }
+
+ cl_dbg_verbose(cl_hw, "Power table ID %u (V%u)\n", pwr_table_id, cl_hw->tx_power_version);
+
+ return 0;
+}
+
+int cl_power_table_alloc(struct cl_hw *cl_hw)
+{
+ struct cl_power_table_data *buf = NULL;
+ u32 len = sizeof(struct cl_power_table_data);
+ dma_addr_t phys_dma_addr;
+
+ buf = dma_alloc_coherent(cl_hw->chip->dev, len, &phys_dma_addr, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ cl_hw->power_table_info.data = buf;
+ cl_hw->power_table_info.dma_addr = phys_dma_addr;
+
+ return cl_power_table_fill(cl_hw);
+}
+
+void cl_power_table_free(struct cl_hw *cl_hw)
+{
+ struct cl_power_table_info *power_table_info = &cl_hw->power_table_info;
+ u32 len = sizeof(struct cl_power_table_data);
+ dma_addr_t phys_dma_addr = power_table_info->dma_addr;
+
+ if (!power_table_info->data)
+ return;
+
+ dma_free_coherent(cl_hw->chip->dev, len, (void *)power_table_info->data, phys_dma_addr);
+ power_table_info->data = NULL;
+}
+
+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;
+ char *arr_gain_cpy = NULL;
+ char *arr_gain_cpy_p = NULL;
+ char *arr_gain_str = NULL;
+
+ arr_gain_len = strlen(cl_hw->conf->ce_arr_gain) + 1;
+ arr_gain_cpy_p = kzalloc(arr_gain_len, GFP_ATOMIC);
+ arr_gain_cpy = arr_gain_cpy_p;
+
+ if (!arr_gain_cpy)
+ return 0;
+
+ /* Copy cl_hw->conf->ce_arr_gain so its value won't be changed by strsep() */
+ 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 = strsep(&arr_gain_cpy, ",");
+
+ /* Only a single value in ce_arr_gain - same value will be applied for all tx_ant */
+ if (!arr_gain_cpy) {
+ arr_gain_val = convert_str_int_q8(arr_gain_str);
+ } else {
+ /* Keep iterating until getting to the correct ant idx */
+ for (idx = 1; arr_gain_str && (idx < tx_ant); idx++)
+ arr_gain_str = strsep(&arr_gain_cpy, ",");
+
+ arr_gain_val = arr_gain_str ? convert_str_int_q8(arr_gain_str) : 0;
+ }
+
+ kfree(arr_gain_cpy_p);
+
+ 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;
+ char *bf_gain_cpy = NULL;
+ char *bf_gain_cpy_p = NULL;
+ char *bf_gain_str = NULL;
+ s8 *bf_gain_ptr = 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_p = kzalloc(bf_gain_len, GFP_ATOMIC);
+ bf_gain_cpy = bf_gain_cpy_p;
+
+ if (!bf_gain_cpy)
+ return 0;
+
+ /* Copy cl_hw->conf->ce_bf_gain_*_ant so its value won't be changed by strsep() */
+ memcpy(bf_gain_cpy, bf_gain_ptr, bf_gain_len);
+
+ /* Bf_gain_str points to the bf gain of 1 SS */
+ bf_gain_str = strsep(&bf_gain_cpy, ",");
+
+ /* Keep iterating until getting to the correct ss index */
+ for (idx = 0; bf_gain_str && (idx < nss); idx++)
+ bf_gain_str = strsep(&bf_gain_cpy, ",");
+
+ bf_gain_val = bf_gain_str ? convert_str_int_q8(bf_gain_str) : 0;
+
+ kfree(bf_gain_cpy_p);
+ 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;
+ char *bw_factor_cpy = NULL;
+ char *bw_factor_cpy_p = NULL;
+ char *bw_factor_str = NULL;
+
+ bw_factor_len = strlen(cl_hw->conf->ci_bw_factor) + 1;
+ bw_factor_cpy = kzalloc(bw_factor_len, GFP_ATOMIC);
+ bw_factor_cpy = bw_factor_cpy_p;
+
+ if (!bw_factor_cpy)
+ return 0;
+
+ /* Copy cl_hw->conf->ci_bw_factor so its value won't be changed by strsep() */
+ 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 = strsep(&bw_factor_cpy, ",");
+
+ /* Only a single value in ci_bw_factor - same value will be applied for all bandwidths */
+ if (!bw_factor_cpy) {
+ bw_factor_val = convert_str_int_q8(bw_factor_str);
+ } else {
+ /* Keep iterating until getting to the correct bw index */
+ for (idx = 0; bw_factor_str && (idx < bw); idx++)
+ bw_factor_str = strsep(&bw_factor_cpy, ",");
+
+ bw_factor_val = bw_factor_str ? convert_str_int_q8(bw_factor_str) : 0;
+ }
+
+ kfree(bw_factor_cpy_p);
+
+ return (s8)(bw_factor_val >> 6);
+}
+
+static s32 cl_power_average_calib_q8(struct cl_hw *cl_hw, u8 ant_num)
+{
+ u8 ant = 0, ant_cnt = 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 < MAX_ANTENNAS && ant_cnt < ant_num; ant++) {
+ if (!(cl_hw->mask_num_antennas & BIT(ant)))
+ continue;
+
+ total_calib_pow += cl_hw->tx_pow_info[chan_idx][ant].power;
+ ant_cnt++;
+ }
+
+ 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 == NL80211_DFS_FCC)
+ total_power_q8 -= min(bf_gain_q8 + antenna_gain_q8, 6 << 8);
+
+ return total_power_q8;
+}
+
+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;
+ u32 trunc_pwr_val_q1 = 0;
+ bool eirp_regulatory_en = cl_hw->chip->conf->ce_production_mode ?
+ cl_hw->conf->ce_eirp_regulatory_prod_en : cl_hw->conf->ce_eirp_regulatory_op_en;
+
+ if (cl_hw->channel_info.use_channel_info && 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_val_q1 = delta_power_q1;
+ }
+ }
+
+ if (is_auto_resp)
+ min_pwr_q1 += cl_power_min_ant_q1(cl_hw);
+
+ if (res_q1 < min_pwr_q1) {
+ trunc_pwr_val_q1 = max((s32)trunc_pwr_val_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);
+
+ if (trunc_pwr_q1)
+ *trunc_pwr_q1 = (u8)trunc_pwr_val_q1;
+
+ 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 s32 cl_power_calc_total_from_eirp_q1(struct cl_hw *cl_hw, s32 tx_power, u8 nss,
+ enum cl_wrs_mode mode, u8 *trunc_pwr_q1)
+{
+ s32 pwr_q1, total_pwr_q1, delta_pwr_q1 = 0;
+ u8 tx_ant;
+ s32 antenna_gain_q1;
+ s32 array_gain_q1;
+ s32 bf_gain_q1;
+ bool eirp_regulatory_en = cl_hw->chip->conf->ce_production_mode ?
+ cl_hw->conf->ce_eirp_regulatory_prod_en : cl_hw->conf->ce_eirp_regulatory_op_en;
+
+ pwr_q1 = tx_power << 1;
+
+ tx_ant = cl_power_tx_ant(cl_hw, mode);
+ array_gain_q1 = cl_power_array_gain_q1(cl_hw, tx_ant);
+ antenna_gain_q1 = cl_power_antenna_gain_q1(cl_hw);
+ /* bf gain is not used for CCK or OFDM */
+ bf_gain_q1 = (mode > WRS_MODE_OFDM) ? cl_power_bf_gain_q1(cl_hw, tx_ant, nss) : 0;
+
+ /* FCC calculation */
+ if (cl_hw->channel_info.standard == NL80211_DFS_FCC)
+ pwr_q1 -= min(bf_gain_q1 + antenna_gain_q1, 6 << 1);
+
+ if (cl_hw->channel_info.use_channel_info && eirp_regulatory_en) {
+ s32 eirp_pwr_limit_q1;
+
+ eirp_pwr_limit_q1 = cl_chan_info_get_eirp_limit_q8(cl_hw, 0) >> 7;
+ if (pwr_q1 > eirp_pwr_limit_q1) {
+ delta_pwr_q1 = pwr_q1 - eirp_pwr_limit_q1;
+ pwr_q1 = eirp_pwr_limit_q1;
+ }
+ }
+
+ total_pwr_q1 = pwr_q1 - antenna_gain_q1 - array_gain_q1 - bf_gain_q1;
+ if (total_pwr_q1 < POWER_MIN_DB_Q1) {
+ delta_pwr_q1 = max(delta_pwr_q1 - (POWER_MIN_DB_Q1 - total_pwr_q1), 0);
+ total_pwr_q1 = POWER_MIN_DB_Q1;
+ }
+
+ if (trunc_pwr_q1)
+ *trunc_pwr_q1 = (u8)delta_pwr_q1;
+
+ return total_pwr_q1;
+}
+
+static s32 cl_power_calc_auto_resp_from_eirp_q1(struct cl_hw *cl_hw, s32 tx_power, u8 nss,
+ enum cl_wrs_mode mode)
+{
+ s32 auto_resp_total_pwr_q1, auto_resp_min_pwr_q1;
+ u8 tx_ant;
+ s32 array_gain_q1;
+ s32 total_pwr_q1;
+
+ auto_resp_min_pwr_q1 = POWER_MIN_DB_Q1 + cl_power_min_ant_q1(cl_hw);
+ tx_ant = cl_power_tx_ant(cl_hw, mode);
+ array_gain_q1 = cl_power_array_gain_q1(cl_hw, tx_ant);
+ total_pwr_q1 = cl_power_calc_total_from_eirp_q1(cl_hw, tx_power, nss, mode, NULL);
+
+ auto_resp_total_pwr_q1 = array_gain_q1 + total_pwr_q1;
+ if (auto_resp_total_pwr_q1 < auto_resp_min_pwr_q1)
+ auto_resp_total_pwr_q1 = auto_resp_min_pwr_q1;
+
+ return auto_resp_total_pwr_q1;
+}
+
+static s8 cl_calc_ant_pwr_q1(struct cl_hw *cl_hw, u8 bw, u8 nss, u8 mcs,
+ enum cl_wrs_mode mode, u8 *trunc_val)
+{
+ s32 eirp_pwr = 0;
+ s8 ant_pwr_q1;
+
+ eirp_pwr = cl_hw->new_tx_power;
+ if (eirp_pwr) {
+ ant_pwr_q1 = cl_power_calc_total_from_eirp_q1(cl_hw, eirp_pwr, nss,
+ mode, trunc_val);
+ } else {
+ s8 pwr_offset_q1;
+
+ pwr_offset_q1 = cl_power_offset_q1(cl_hw, mode, bw, mcs);
+ ant_pwr_q1 = cl_power_calc_q1(cl_hw, pwr_offset_q1, bw, nss,
+ mode, false, trunc_val);
+ }
+ return ant_pwr_q1;
+}
+
+static s8 cl_calc_auto_resp_pwr_q1(struct cl_hw *cl_hw, u8 bw, u8 nss, u8 mcs,
+ enum cl_wrs_mode mode)
+{
+ s32 eirp_pwr = 0;
+ s8 auto_resp_pwr_q1;
+
+ eirp_pwr = cl_hw->new_tx_power;
+ if (eirp_pwr) {
+ auto_resp_pwr_q1 = cl_power_calc_auto_resp_from_eirp_q1(cl_hw, eirp_pwr,
+ nss, mode);
+ } else {
+ s8 pwr_offset_q1;
+
+ pwr_offset_q1 = cl_power_offset_q1(cl_hw, mode, bw, mcs);
+ auto_resp_pwr_q1 = cl_power_calc_q1(cl_hw, pwr_offset_q1, bw, nss,
+ mode, true, NULL);
+ }
+ return auto_resp_pwr_q1;
+}
+
+static void cl_power_tables_update_cck(struct cl_hw *cl_hw,
+ struct cl_pwr_tables *pwr_tables)
+{
+ u8 mcs;
+ u8 trunc_value = 0;
+
+ /* CCK - Enforce EIRP limitations */
+ for (mcs = 0; mcs < WRS_MCS_MAX_CCK; mcs++) {
+ pwr_tables->ant_pwr_cck[mcs] = cl_calc_ant_pwr_q1(cl_hw, 0, 0, mcs, WRS_MODE_CCK,
+ &trunc_value);
+
+ cl_hw->pwr_trunc.cck[mcs] = trunc_value;
+
+ /* Auto response */
+ pwr_tables->pwr_auto_resp_cck[mcs] = cl_calc_auto_resp_pwr_q1(cl_hw, 0, 0, mcs,
+ WRS_MODE_CCK);
+ }
+}
+
+static void cl_power_tables_update_ofdm(struct cl_hw *cl_hw,
+ struct cl_pwr_tables *pwr_tables)
+{
+ u8 mcs;
+ u8 trunc_value = 0;
+
+ /* OFDM - Enforce EIRP limitations */
+ for (mcs = 0; mcs < WRS_MCS_MAX_OFDM; mcs++) {
+ pwr_tables->ant_pwr_ofdm[mcs] = cl_calc_ant_pwr_q1(cl_hw, 0, 0, mcs, WRS_MODE_OFDM,
+ &trunc_value);
+ cl_hw->pwr_trunc.ofdm[mcs] = trunc_value;
+
+ /* Auto response */
+ pwr_tables->pwr_auto_resp_ofdm[mcs] = cl_calc_auto_resp_pwr_q1(cl_hw, 0, 0, mcs,
+ WRS_MODE_OFDM);
+ }
+}
+
+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;
+ s16 min_bw_limit = 0;
+ s32 eirp_power_limit_q8;
+
+ for (bw = 0, min_bw_limit = 0xFFFF; bw < cl_max_bw_idx(WRS_MODE_VHT, is_24g); bw++) {
+ if (!cl_hw_is_prod_or_listener(cl_hw) &&
+ !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++) {
+ for (nss = 0; nss < PWR_TBL_VHT_BF_SIZE; nss++) {
+ pwr_tables->ant_pwr_ht_vht[bw][mcs][nss] =
+ cl_calc_ant_pwr_q1(cl_hw, bw, nss, mcs, WRS_MODE_VHT,
+ &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_tables->pwr_auto_resp_ht_vht[mcs] =
+ cl_calc_auto_resp_pwr_q1(cl_hw, min_bw_idx_limit_vht, 0, mcs,
+ WRS_MODE_VHT);
+
+ 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;
+ s16 min_bw_limit = 0;
+ s32 eirp_power_limit_q8;
+
+ for (bw = 0, min_bw_limit = 0xFFFF; bw < cl_max_bw_idx(WRS_MODE_HE, is_24g); bw++) {
+ if (!cl_hw_is_prod_or_listener(cl_hw) &&
+ !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++) {
+ for (nss = 0; nss < PWR_TBL_HE_BF_SIZE; nss++) {
+ pwr_tables->ant_pwr_he[bw][mcs][nss] =
+ cl_calc_ant_pwr_q1(cl_hw, bw, nss, mcs, WRS_MODE_HE,
+ &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_tables->pwr_auto_resp_he[mcs] =
+ cl_calc_auto_resp_pwr_q1(cl_hw, min_bw_idx_limit_he, 0, mcs, WRS_MODE_HE);
+
+ 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;
+
+ memset(pwr_tables, 0, sizeof(struct cl_pwr_tables));
+
+ if (is_24g)
+ cl_power_tables_update_cck(cl_hw, pwr_tables);
+
+ cl_power_tables_update_ofdm(cl_hw, pwr_tables);
+
+ if (!is_6g)
+ 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_hw->new_tx_power = 0;
+
+ 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 = cl_power_get_max_ofdm(cl_hw);
+ 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);
+}
+