diff mbox series

[RFC,v2,54/96] cl8k: add power.c

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

Commit Message

Viktor Barna May 24, 2022, 11:34 a.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 | 1123 ++++++++++++++++++++++
 1 file changed, 1123 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/power.c
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..ef62c4b7a332
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/power.c
@@ -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);
+}
+