diff mbox series

[RFC,v1,135/256] cl8k: add power_cli.c

Message ID 20210617160223.160998-136-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_cli.c | 878 +++++++++++++++++++
 1 file changed, 878 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/power_cli.c

--
2.30.0
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/power_cli.c b/drivers/net/wireless/celeno/cl8k/power_cli.c
new file mode 100644
index 000000000000..7adcb93cbf9c
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/power_cli.c
@@ -0,0 +1,878 @@ 
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "power_cli.h"
+#include "power.h"
+#include "band.h"
+#include "chip.h"
+#include "utils/utils.h"
+
+static void cl_power_float_to_buf(char **buf, int *len, ssize_t *buf_size,
+                                 s32 x, s32 y, bool zero_pad)
+{
+       bool sign = (x >= 0 && y > 0) || (x < 0 && y < 0);
+       s32 absx = abs(x);
+       s32 absy = abs(y);
+       s32 abs_integer = 0;
+       s32 fraction = 0;
+       s32 signed_integer = 0;
+
+       if (x != 0 && y != 0) {
+               abs_integer = (absx / absy);
+               if (y == 2)
+                       fraction = (10 * (absx - absy * abs_integer) / absy);
+               else
+                       fraction = (100 * (absx - absy * abs_integer) / absy);
+               signed_integer = sign ? abs_integer : -abs_integer;
+       }
+
+       if (y == 2) {
+               if (signed_integer == 0 && !sign)
+                       cl_snprintf(buf, len, buf_size, "-0.%d", fraction);
+               else if (zero_pad)
+                       cl_snprintf(buf, len, buf_size, "%2d.%d", signed_integer, fraction);
+               else
+                       cl_snprintf(buf, len, buf_size, "%d.%d", signed_integer, fraction);
+       } else {
+               if (signed_integer == 0 && !sign)
+                       cl_snprintf(buf, len, buf_size, "-0.%02d", fraction);
+               else if (zero_pad)
+                       cl_snprintf(buf, len, buf_size, "%2d.%02d", signed_integer, fraction);
+               else
+                       cl_snprintf(buf, len, buf_size, "%d.%02d", signed_integer, fraction);
+       }
+}
+
+static void cl_power_q1_to_buf(char **buf, int *len, ssize_t *buf_size, const s8 *prefix, s32 x,
+                              const s8 *suffix, bool zero_pad)
+{
+       cl_snprintf(buf, len, buf_size, "%s", prefix);
+       cl_power_float_to_buf(buf, len, buf_size, x, 2, zero_pad);
+       cl_snprintf(buf, len, buf_size, "%s", suffix);
+}
+
+static void cl_power_q8_to_buf(char **buf, int *len, ssize_t *buf_size, const s8 *prefix, s32 x,
+                              const s8 *suffix, bool zero_pad)
+{
+       cl_snprintf(buf, len, buf_size, "%s", prefix);
+       cl_power_float_to_buf(buf, len, buf_size, x, 256, zero_pad);
+       cl_snprintf(buf, len, buf_size, "%s", suffix);
+}
+
+static void multi_print(char **buf, int *len, ssize_t *buf_size, u8 num_prints, const s8 *str)
+{
+       u8 i;
+
+       for (i = 0; i < num_prints; i++)
+               cl_snprintf(buf, len, buf_size, "%s", str);
+
+       cl_snprintf(buf, len, buf_size, "\n");
+}
+
+static void cl_power_offset_to_buf(struct cl_hw *cl_hw, char **buf,
+                                  int *len, ssize_t *buf_size,
+                                  u8 mode, u8 max_bw, u8 max_mcs)
+{
+       u8 bw, mcs;
+       s8 offset_q1;
+
+       cl_snprintf(buf, len, buf_size, "\nPower Offset per BW & MCS\n");
+
+       cl_snprintf(buf, len, buf_size, "-----");
+       multi_print(buf, len, buf_size, max_mcs, "-----");
+
+       cl_snprintf(buf, len, buf_size, "|   ");
+       for (mcs = 0; mcs < max_mcs; mcs++)
+               cl_snprintf(buf, len, buf_size, "|%4u", mcs);
+
+       cl_snprintf(buf, len, buf_size, "|\n");
+
+       cl_snprintf(buf, len, buf_size, "|---");
+       for (mcs = 0; mcs < max_mcs - 1; mcs++)
+               cl_snprintf(buf, len, buf_size, "+----");
+
+       cl_snprintf(buf, len, buf_size, "+----|\n");
+
+       for (bw = 0; bw < max_bw; bw++) {
+               cl_snprintf(buf, len, buf_size, "|%3u", BW_TO_MHZ(bw));
+
+               for (mcs = 0; mcs < max_mcs; mcs++) {
+                       offset_q1 = cl_power_offset_q1(cl_hw, mode, bw, mcs);
+                       cl_power_q1_to_buf(buf, len, buf_size, "|", offset_q1, "", true);
+               }
+
+               cl_snprintf(buf, len, buf_size, "|\n");
+       }
+
+       cl_snprintf(buf, len, buf_size, "-----");
+       multi_print(buf, len, buf_size, max_mcs, "-----");
+}
+
+static void cl_power_bf_gain_to_buf(char **buf, int *len, ssize_t *buf_size,
+                                   u8 max_nss, s32 *bf_gain_q1)
+{
+       u8 nss;
+
+       cl_snprintf(buf, len, buf_size, "BF gain per NSS = ");
+
+       for (nss = 0; nss < max_nss; nss++) {
+               if (nss == max_nss - 1)
+                       cl_power_q1_to_buf(buf, len, buf_size, "", bf_gain_q1[nss], "\n", false);
+               else
+                       cl_power_q1_to_buf(buf, len, buf_size, "", bf_gain_q1[nss], ",", false);
+       }
+}
+
+static void cl_power_table_ht_vht_he_to_buf(struct cl_hw *cl_hw, char **buf, int *len,
+                                           ssize_t *buf_size, u8 max_bw, u8 max_nss,
+                                           u8 max_mcs, u8 max_nss_arr,
+                                           u8 max_mcs_arr, s8 *ant_pwr_q1,
+                                           s32 *bf_gain_q1, s32 arr_gain_q1,
+                                           s32 ant_gain_q1)
+{
+       u8 bw, nss, mcs, one_d_idx;
+       s32 conducted_q1, final_q1;
+
+       cl_snprintf(buf, len, buf_size, "\nPower Table\n");
+
+       cl_snprintf(buf, len, buf_size, "---------");
+       multi_print(buf, len, buf_size, max_bw, "----------");
+
+       cl_snprintf(buf, len, buf_size, "|MCS|BF |");
+       for (bw = 0; bw < max_bw; bw++)
+               cl_snprintf(buf, len, buf_size, " %3uMHz  |", BW_TO_MHZ(bw));
+
+       cl_snprintf(buf, len, buf_size, "\n");
+
+       cl_snprintf(buf, len, buf_size, "|   |NSS|");
+       multi_print(buf, len, buf_size, max_bw, "Cond|Finl|");
+
+       for (mcs = 0; mcs < max_mcs; mcs++) {
+               cl_snprintf(buf, len, buf_size, "|---+---|");
+               multi_print(buf, len, buf_size, max_bw, "----+----|");
+
+               for (nss = 0; nss < max_nss; nss++) {
+                       cl_snprintf(buf, len, buf_size, "|%3u|%3u|", mcs, nss);
+
+                       for (bw = 0; bw < max_bw; bw++) {
+                               one_d_idx = (bw * max_mcs_arr + mcs) * max_nss_arr + nss;
+                               conducted_q1 = ant_pwr_q1[one_d_idx];
+                               final_q1 = conducted_q1 + ant_gain_q1 +
+                                       arr_gain_q1 + bf_gain_q1[nss];
+
+                               if (cl_hw->channel_info.standard == CL_STANDARD_FCC)
+                                       final_q1 -= min(bf_gain_q1[nss] + ant_gain_q1, 6 << 1);
+
+                               cl_power_q1_to_buf(buf, len, buf_size, "", conducted_q1, "|", true);
+                               cl_power_q1_to_buf(buf, len, buf_size, "", final_q1, "|", true);
+                       }
+
+                       cl_snprintf(buf, len, buf_size, "\n");
+               }
+       }
+
+       cl_snprintf(buf, len, buf_size, "---------");
+       multi_print(buf, len, buf_size, max_bw, "----------");
+
+       cl_snprintf(buf, len, buf_size, "(*) BF disabled = BF NSS #%u", max_nss - 1);
+}
+
+static void cl_power_table_cck_ofdm_to_buf(struct cl_hw *cl_hw, char **buf,
+                                          int *len, ssize_t *buf_size,
+                                          u8 max_mcs, s8 *ant_pwr_q1,
+                                          s32 arr_gain_q1, s32 ant_gain_q1)
+{
+       u8 mcs;
+       s32 conducted_q1, final_q1;
+
+       cl_snprintf(buf, len, buf_size,
+                   "\nPower Table\n"
+                   "|-------------|\n"
+                   "|MCS|Cond|Finl|\n"
+                   "|---+----+----|\n");
+
+       for (mcs = 0; mcs < max_mcs; mcs++) {
+               conducted_q1 = ant_pwr_q1[mcs];
+               final_q1 = conducted_q1 + ant_gain_q1 + arr_gain_q1;
+
+               if (cl_hw->channel_info.standard == CL_STANDARD_FCC)
+                       final_q1 -= min(ant_gain_q1, 6 << 1);
+
+               cl_snprintf(buf, len, buf_size, "|%3u|", mcs);
+               cl_power_q1_to_buf(buf, len, buf_size, "", conducted_q1, "|", true);
+               cl_power_q1_to_buf(buf, len, buf_size, "",  final_q1, "|\n", true);
+       }
+
+       cl_snprintf(buf, len, buf_size, "|-------------|\n");
+}
+
+static void cl_power_trunc_ht_vht_he_to_buf(struct cl_hw *cl_hw, char **buf,
+                                           int *len, ssize_t *buf_size,
+                                           enum cl_wrs_mode mode, u8 max_bw,
+                                           u8 max_nss, u8 max_mcs,
+                                           u8 max_nss_arr, u8 max_mcs_arr,
+                                           u8 *trunc_pwr_q1)
+{
+       u8 bw, nss, mcs, one_d_idx;
+       u8 tx_ant = cl_power_tx_ant(cl_hw, mode);
+       s8 pwr_offset_q1;
+       s32 truncate_q1, total_q1;
+
+       cl_snprintf(buf, len, buf_size, "\nTruncate Table\n");
+
+       cl_snprintf(buf, len, buf_size, "---------");
+       multi_print(buf, len, buf_size, max_bw, "----------");
+
+       cl_snprintf(buf, len, buf_size, "|MCS|BF |");
+       for (bw = 0; bw < max_bw; bw++)
+               cl_snprintf(buf, len, buf_size, " %3uMHz  |", BW_TO_MHZ(bw));
+
+       cl_snprintf(buf, len, buf_size, "\n");
+
+       cl_snprintf(buf, len, buf_size, "|   |NSS|");
+       multi_print(buf, len, buf_size, max_bw, "Totl|Trnc|");
+
+       for (mcs = 0; mcs < max_mcs; mcs++) {
+               cl_snprintf(buf, len, buf_size, "|---+---|");
+               multi_print(buf, len, buf_size, max_bw, "----+----|");
+
+               for (nss = 0; nss < max_nss; nss++) {
+                       cl_snprintf(buf, len, buf_size, "|%3u|%3u|", mcs, nss);
+
+                       for (bw = 0; bw < max_bw; bw++) {
+                               one_d_idx = (bw * max_mcs_arr + mcs) * max_nss_arr + nss;
+                               truncate_q1 = (s32)trunc_pwr_q1[one_d_idx];
+                               pwr_offset_q1 = cl_power_offset_q1(cl_hw, mode, bw, mcs);
+                               total_q1 = cl_power_total_q1(cl_hw, pwr_offset_q1,
+                                                            tx_ant, nss, mode, false);
+
+                               cl_power_q1_to_buf(buf, len, buf_size, "", total_q1, "|", true);
+                               cl_power_q1_to_buf(buf, len, buf_size, "", truncate_q1, "|", true);
+                       }
+
+                       cl_snprintf(buf, len, buf_size, "\n");
+               }
+       }
+
+       cl_snprintf(buf, len, buf_size, "---------");
+       multi_print(buf, len, buf_size, max_bw, "----------");
+}
+
+static void cl_power_trunc_cck_ofdm_to_buf(struct cl_hw *cl_hw, char **buf,
+                                          int *len, ssize_t *buf_size,
+                                          enum cl_wrs_mode mode, u8 max_mcs, u8 *trunc_pwr_q1)
+{
+       u8 mcs;
+       u8 tx_ant = cl_power_tx_ant(cl_hw, mode);
+       s8 pwr_offset_q1;
+       s32 truncate_q1, total_q1;
+
+       cl_snprintf(buf, len, buf_size,
+                   "\nTruncate Table\n"
+                   "|-------------|\n"
+                   "|MCS|Totl|Trnc|\n"
+                   "|---+----+----|\n");
+
+       for (mcs = 0; mcs < max_mcs; mcs++) {
+               truncate_q1 = (s32)trunc_pwr_q1[mcs];
+               pwr_offset_q1 = cl_power_offset_q1(cl_hw, mode, CHNL_BW_20, mcs);
+               total_q1 = cl_power_total_q1(cl_hw, pwr_offset_q1, tx_ant, 0, mode, false);
+
+               cl_snprintf(buf, len, buf_size, "|%3u|", mcs);
+               cl_power_q1_to_buf(buf, len, buf_size, "", total_q1, "|", true);
+               cl_power_q1_to_buf(buf, len, buf_size, "", truncate_q1, "|\n", true);
+       }
+
+       cl_snprintf(buf, len, buf_size, "|-------------|\n");
+}
+
+static void cl_power_config_to_buf(char **buf, int *len,
+                                  ssize_t *buf_size,
+                                  char *mode,
+                                  u8 tx_ant_num,
+                                  s32 arr_gain_q1,
+                                  s32 calib_power_q1,
+                                  s8 ant_pwr_vns,
+                                  s8 pwr_auto_resp_vns,
+                                  s32 vns_actual_q1)
+{
+       cl_snprintf(buf, len, buf_size,
+                   "%s power debug info\n", mode);
+       cl_snprintf(buf, len, buf_size,
+                   "-----------------------\n");
+       cl_snprintf(buf, len, buf_size,
+                   "Tx antenna = %u\n", tx_ant_num);
+       cl_power_q1_to_buf(buf, len, buf_size, "Array gain = ",
+                          arr_gain_q1, "\n", false);
+       cl_power_q1_to_buf(buf, len, buf_size, "Calib power = ",
+                          calib_power_q1, "\n", false);
+       cl_power_q1_to_buf(buf, len, buf_size, "VNS power = ",
+                          ant_pwr_vns, "\n", false);
+       cl_power_q1_to_buf(buf, len, buf_size, "VNS auto resp power = ",
+                          pwr_auto_resp_vns, "\n", false);
+       cl_power_q1_to_buf(buf, len, buf_size, "VNS actual = ",
+                          vns_actual_q1, "\n", false);
+}
+
+static void _cl_power_table_print_he(struct cl_hw *cl_hw, char **buf, int *len, ssize_t *buf_size)
+{
+       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 max_bw = max_bw_idx(WRS_MODE_HE, cl_band_is_24g(cl_hw));
+       u8 max_nss = min_t(u8, tx_ant, PWR_TBL_HE_BF_SIZE);
+       u8 nss;
+       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 calib_power_q1 = cl_power_average_calib_q1(cl_hw, tx_ant);
+       s32 bf_gain_tbl_q1[PWR_TBL_HE_BF_SIZE] = {0};
+       s32 vns_actual_q1 = pwr_tables->ant_pwr_vns_he + ant_gain_q1 + arr_gain_q1;
+
+       for (nss = 0; nss < max_nss; nss++)
+               bf_gain_tbl_q1[nss] = cl_power_bf_gain_q1(cl_hw, tx_ant, nss);
+
+       cl_power_config_to_buf(buf, len, buf_size, "HE", tx_ant, arr_gain_q1,
+                              calib_power_q1, pwr_tables->ant_pwr_vns_he,
+                              pwr_tables->pwr_auto_resp_vns_he, vns_actual_q1);
+
+       cl_power_bf_gain_to_buf(buf, len, buf_size, max_nss, bf_gain_tbl_q1);
+
+       cl_power_offset_to_buf(cl_hw, buf, len, buf_size, WRS_MODE_HE,
+                              CHNL_BW_MAX_HE, WRS_MCS_MAX_HE);
+
+       cl_power_table_ht_vht_he_to_buf(cl_hw, buf, len, buf_size,
+                                       max_bw, max_nss, WRS_MCS_MAX_HE,
+                                       PWR_TBL_HE_BF_SIZE, WRS_MCS_MAX_HE,
+                                       &pwr_tables->ant_pwr_he[0][0][0],
+                                       bf_gain_tbl_q1, arr_gain_q1, ant_gain_q1);
+}
+
+static void _cl_power_table_print_ht_vht(struct cl_hw *cl_hw, char **buf,
+                                        int *len, ssize_t *buf_size)
+{
+       struct cl_pwr_tables *pwr_tables = &cl_hw->phy_data_info.data->pwr_tables;
+       bool is_24g = cl_band_is_24g(cl_hw);
+       bool is_5g = cl_band_is_5g(cl_hw);
+       u8 tx_ant = cl_power_tx_ant(cl_hw, WRS_MODE_VHT);
+       u8 max_bw = max_bw_idx(WRS_MODE_VHT, is_24g);
+       u8 max_nss = min_t(u8, tx_ant, PWR_TBL_VHT_BF_SIZE);
+       u8 max_mcs = (is_5g || (is_24g && cl_hw->conf->ci_vht_cap_24g)) ?
+               WRS_MCS_MAX_VHT : WRS_MCS_MAX_HT;
+       u8 nss;
+       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 calib_power_q1 = cl_power_average_calib_q1(cl_hw, tx_ant);
+       s32 bf_gain_tbl_q1[PWR_TBL_VHT_BF_SIZE] = {0};
+       s32 vns_actual_q1 = pwr_tables->ant_pwr_vns_ht_vht + ant_gain_q1 + arr_gain_q1;
+
+       for (nss = 0; nss < max_nss; nss++)
+               bf_gain_tbl_q1[nss] = cl_power_bf_gain_q1(cl_hw, tx_ant, nss);
+
+       cl_power_config_to_buf(buf, len, buf_size, "HT/VHT", tx_ant, arr_gain_q1,
+                              calib_power_q1, pwr_tables->ant_pwr_vns_ht_vht,
+                              pwr_tables->pwr_auto_resp_vns_ht_vht, vns_actual_q1);
+
+       cl_power_bf_gain_to_buf(buf, len, buf_size, max_nss, bf_gain_tbl_q1);
+
+       cl_power_offset_to_buf(cl_hw, buf, len, buf_size,
+                              WRS_MODE_VHT, CHNL_BW_MAX_VHT, WRS_MCS_MAX_VHT);
+
+       cl_power_table_ht_vht_he_to_buf(cl_hw, buf, len, buf_size,
+                                       max_bw, max_nss, max_mcs,
+                                       PWR_TBL_VHT_BF_SIZE, WRS_MCS_MAX_VHT,
+                                       &pwr_tables->ant_pwr_ht_vht[0][0][0],
+                                       bf_gain_tbl_q1, arr_gain_q1, ant_gain_q1);
+}
+
+static void _cl_power_table_print_ofdm(struct cl_hw *cl_hw, char **buf,
+                                      int *len, ssize_t *buf_size)
+{
+       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_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 calib_power_q1 = cl_power_average_calib_q1(cl_hw, tx_ant);
+       s32 vns_actual_q1 = pwr_tables->ant_pwr_vns_ofdm + ant_gain_q1 + arr_gain_q1;
+
+       cl_power_config_to_buf(buf, len, buf_size, "OFDM", tx_ant, arr_gain_q1,
+                              calib_power_q1, pwr_tables->ant_pwr_vns_ofdm,
+                              pwr_tables->pwr_auto_resp_vns_ofdm, vns_actual_q1);
+
+       cl_power_offset_to_buf(cl_hw, buf, len, buf_size, WRS_MODE_OFDM,
+                              CHNL_BW_MAX_OFDM, WRS_MCS_MAX_OFDM);
+
+       cl_power_table_cck_ofdm_to_buf(cl_hw, buf, len, buf_size, WRS_MCS_MAX_OFDM,
+                                      pwr_tables->ant_pwr_ofdm,
+                                      arr_gain_q1, ant_gain_q1);
+}
+
+static void _cl_power_table_print_cck(struct cl_hw *cl_hw, char **buf,
+                                     int *len, ssize_t *buf_size)
+{
+       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_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 calib_power_q1 = cl_power_average_calib_q1(cl_hw, tx_ant);
+       s32 vns_actual_q1 = pwr_tables->ant_pwr_vns_cck + ant_gain_q1 + arr_gain_q1;
+
+       cl_power_config_to_buf(buf, len, buf_size, "CCK", tx_ant, arr_gain_q1,
+                              calib_power_q1, pwr_tables->ant_pwr_vns_cck,
+                              pwr_tables->pwr_auto_resp_vns_cck,
+                              vns_actual_q1);
+
+       cl_power_offset_to_buf(cl_hw, buf, len, buf_size, WRS_MODE_CCK,
+                              CHNL_BW_MAX_CCK, WRS_MCS_MAX_CCK);
+
+       cl_power_table_cck_ofdm_to_buf(cl_hw, buf, len, buf_size, WRS_MCS_MAX_CCK,
+                                      pwr_tables->ant_pwr_cck,
+                                      arr_gain_q1, ant_gain_q1);
+}
+
+static void _cl_power_trunc_print_he(struct cl_hw *cl_hw, char **buf,
+                                    int *len, ssize_t *buf_size)
+{
+       u8 tx_ant = cl_power_tx_ant(cl_hw, WRS_MODE_HE);
+       u8 max_bw = max_bw_idx(WRS_MODE_HE, cl_band_is_24g(cl_hw));
+       u8 max_nss = min_t(u8, tx_ant, PWR_TBL_HE_BF_SIZE);
+
+       cl_power_trunc_ht_vht_he_to_buf(cl_hw, buf, len, buf_size, WRS_MODE_HE,
+                                       max_bw, max_nss, WRS_MCS_MAX_HE,
+                                       PWR_TBL_HE_BF_SIZE, WRS_MCS_MAX_HE,
+                                       &cl_hw->pwr_trunc.he[0][0][0]);
+}
+
+static void _cl_power_trunc_print_ht_vht(struct cl_hw *cl_hw, char **buf,
+                                        int *len, ssize_t *buf_size)
+{
+       bool is_24g = cl_band_is_24g(cl_hw);
+       bool is_5g = cl_band_is_5g(cl_hw);
+       enum cl_wrs_mode mode;
+       u8 tx_ant, max_bw, max_nss, max_mcs;
+
+       if (is_5g || (is_24g && cl_hw->conf->ci_vht_cap_24g)) {
+               mode = WRS_MODE_VHT;
+               max_bw = CHNL_BW_MAX_VHT;
+               max_mcs = WRS_MCS_MAX_VHT;
+       } else {
+               mode = WRS_MODE_HT;
+               max_bw = CHNL_BW_MAX_HT;
+               max_mcs = WRS_MCS_MAX_HT;
+       }
+
+       tx_ant = cl_power_tx_ant(cl_hw, mode);
+       max_nss = min_t(u8, tx_ant, PWR_TBL_VHT_BF_SIZE);
+
+       cl_power_trunc_ht_vht_he_to_buf(cl_hw, buf, len, buf_size, mode,
+                                       max_bw, max_nss, max_mcs,
+                                       PWR_TBL_VHT_BF_SIZE, WRS_MCS_MAX_VHT,
+                                       &cl_hw->pwr_trunc.ht_vht[0][0][0]);
+}
+
+static void _cl_power_trunc_print_ofdm(struct cl_hw *cl_hw, char **buf,
+                                      int *len, ssize_t *buf_size)
+{
+       cl_power_trunc_cck_ofdm_to_buf(cl_hw, buf, len, buf_size,
+                                      WRS_MODE_OFDM, WRS_MCS_MAX_OFDM,
+                                      cl_hw->pwr_trunc.ofdm);
+}
+
+static void _cl_power_trunc_print_cck(struct cl_hw *cl_hw, char **buf,
+                                     int *len, ssize_t *buf_size)
+{
+       cl_power_trunc_cck_ofdm_to_buf(cl_hw, buf, len, buf_size,
+                                      WRS_MODE_CCK, WRS_MCS_MAX_CCK,
+                                      cl_hw->pwr_trunc.cck);
+}
+
+static void cl_power_print_limits(struct cl_hw *cl_hw, char **buf,
+                                 int *len, ssize_t *buf_size, u8 channel)
+{
+       bool is_24g = cl_band_is_24g(cl_hw);
+       bool country_limit = cl_hw->channel_info.use_channel_info;
+       bool hardware_limit = strlen(cl_hw->conf->ce_hardware_power_table) > 0;
+       bool eirp_limit = cl_hw->conf->ci_eirp_regulatory_en;
+       u8 bw = 0;
+       s16 country_val_q8 = 0;
+       s16 hardware_val_q8 = 0;
+       s16 eirp_val_q8 = 0;
+
+       cl_snprintf(buf, len, buf_size,
+                   "\nPower regulation/limitations\n"
+                   "============================\n");
+       cl_snprintf(buf, len, buf_size,
+                   "Country regulation  = %s\n", country_limit ?
+                   "On" : "Off");
+       cl_snprintf(buf, len, buf_size,
+                   "Hardware limitation = %s\n", hardware_limit ?
+                   "On" : "Off");
+       cl_snprintf(buf, len, buf_size,
+                   "EIRP enable         = %s\n\n", eirp_limit ?
+                   "True" : "False");
+
+       if (!eirp_limit || (!country_limit && !hardware_limit))
+               return;
+
+       cl_snprintf(buf, len, buf_size,
+                   "|-----------------------------------|\n"
+                   "| BW  | COUNTRY | HARDWARE |  EIRP  |\n"
+                   "|-----------------------------------|\n");
+
+       for (bw = 0; bw < max_bw_idx(WRS_MODE_HE, is_24g); bw++) {
+               country_val_q8 = cl_chan_info_get_country_limit_q8(cl_hw, channel, bw);
+               hardware_val_q8 = cl_chan_info_get_hardware_limit_q8(cl_hw, channel, bw);
+               eirp_val_q8 = cl_chan_info_get_eirp_limit_q8(cl_hw, bw);
+
+               cl_snprintf(buf, len, buf_size, "| %3u |", BW_TO_MHZ(bw));
+
+               if (country_limit)
+                       cl_power_q8_to_buf(buf, len, buf_size, "   ",
+                                          country_val_q8, " |", true);
+               else
+                       cl_snprintf(buf, len, buf_size, "    X    |");
+
+               if (hardware_limit)
+                       cl_power_q8_to_buf(buf, len, buf_size, "    ",
+                                          hardware_val_q8, " |", true);
+               else
+                       cl_snprintf(buf, len, buf_size, "    X     |");
+
+               cl_power_q8_to_buf(buf, len, buf_size, "  ", eirp_val_q8, " |\n", true);
+       }
+       cl_snprintf(buf, len, buf_size,
+                   "|-----------------------------------|\n"
+                   "(*) EIRP = MIN(COUNTRY, HARDWARE)\n\n");
+}
+
+static int cl_power_general_print(struct cl_hw *cl_hw)
+{
+       u8 channel = cl_hw->channel;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "Power general information\n"
+                   "=========================\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "Channel               = %u\n", channel);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "Standard              = %s\n",
+                   (cl_hw->channel_info.standard == CL_STANDARD_FCC) ?
+                   "FCC" : "ETSI");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "Country code          = %s\n",
+                   cl_hw->chip->conf->ce_country_code);
+       cl_power_q8_to_buf(&buf, &len, &buf_size, "Antenna gain          = ",
+                          cl_power_antenna_gain_q8(cl_hw), "\n", false);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "Power control percent = %u%%\n",
+                   cl_hw->power_db.curr_percentage);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "Power control offset  = %d\n",
+                   cl_hw->power_db.curr_offset);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "VNS mode              = %u\n",
+                   cl_hw->conf->ci_vns_pwr_mode);
+
+       if (cl_hw->conf->ci_vns_pwr_mode)
+               cl_snprintf(&buf, &len, &buf_size,
+                           "VNS limit             = %d\n",
+                           cl_hw->conf->ci_vns_pwr_limit);
+
+       cl_power_print_limits(cl_hw, &buf, &len, &buf_size, channel);
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "!!! Notice:\n"
+                   "Conducted power = Calibrated power + PPMCS offset - EIRP delta value\n"
+                   "where EIRP delata value = Total - Truncated !!!\n");
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_power_table_print_he(struct cl_hw *cl_hw)
+{
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       _cl_power_table_print_he(cl_hw, &buf, &len, &buf_size);
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_power_table_print_ht_vht(struct cl_hw *cl_hw)
+{
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       if (cl_band_is_6g(cl_hw))
+               return 0;
+
+       _cl_power_table_print_ht_vht(cl_hw, &buf, &len, &buf_size);
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_power_table_print_ofdm(struct cl_hw *cl_hw)
+{
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       if (cl_band_is_6g(cl_hw))
+               return 0;
+
+       _cl_power_table_print_ofdm(cl_hw, &buf, &len, &buf_size);
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_power_table_print_cck(struct cl_hw *cl_hw)
+{
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       if (!cl_band_is_24g(cl_hw))
+               return 0;
+
+       _cl_power_table_print_cck(cl_hw, &buf, &len, &buf_size);
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_power_trunc_print_he(struct cl_hw *cl_hw)
+{
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       _cl_power_trunc_print_he(cl_hw, &buf, &len, &buf_size);
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_power_trunc_print_ht_vht(struct cl_hw *cl_hw)
+{
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       if (cl_band_is_6g(cl_hw))
+               return 0;
+
+       _cl_power_trunc_print_ht_vht(cl_hw, &buf, &len, &buf_size);
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_power_trunc_print_ofdm(struct cl_hw *cl_hw)
+{
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       if (cl_band_is_6g(cl_hw))
+               return 0;
+
+       _cl_power_trunc_print_ofdm(cl_hw, &buf, &len, &buf_size);
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_power_trunc_print_cck(struct cl_hw *cl_hw)
+{
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       if (!cl_band_is_24g(cl_hw))
+               return 0;
+
+       _cl_power_trunc_print_cck(cl_hw, &buf, &len, &buf_size);
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static void cl_power_auto_resp_to_buf(struct cl_hw *cl_hw, char **buf,
+                                     int *len, ssize_t *buf_size, u8 mode,
+                                     u8 max_mcs, s8 *pwr_auto_resp, const char *text)
+{
+       u8 mcs;
+       u8 tx_ant_num = cl_power_tx_ant(cl_hw, mode);
+       s32 ant_gain_q1 = cl_power_antenna_gain_q1(cl_hw);
+       s32 arr_gain_q1 = cl_power_array_gain_q1(cl_hw, tx_ant_num);
+       s32 conducted_q1, truncated_q1;
+
+       cl_snprintf(buf, len, buf_size,
+                   "\n%s Auto response\n"
+                   "|------------------|\n"
+                   "|MCS|Cond|Tot |Tab |\n"
+                   "|---+----+----+----|\n",
+                   text);
+
+       for (mcs = 0; mcs < max_mcs; mcs++) {
+               conducted_q1 = pwr_auto_resp[mcs] - arr_gain_q1;
+               truncated_q1 = pwr_auto_resp[mcs] + ant_gain_q1;
+
+               cl_snprintf(buf, len, buf_size,
+                           "|%3u|", mcs);
+               cl_power_q1_to_buf(buf, len, buf_size, "",
+                                  conducted_q1, "|", true);
+               cl_power_q1_to_buf(buf, len, buf_size, "",
+                                  truncated_q1, "|", true);
+               cl_power_q1_to_buf(buf, len, buf_size, "",
+                                  pwr_auto_resp[mcs], "|\n", true);
+       }
+
+       cl_snprintf(buf, len, buf_size,
+                   "|------------------|\n");
+}
+
+static int cl_power_auto_response_print(struct cl_hw *cl_hw)
+{
+       struct cl_pwr_tables *pwr_tables = &cl_hw->phy_data_info.data->pwr_tables;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int len = 0;
+       int err = 0;
+       bool is_24g = cl_band_is_24g(cl_hw);
+       bool is_5g = cl_band_is_5g(cl_hw);
+       bool is_6g = cl_band_is_6g(cl_hw);
+
+       cl_power_auto_resp_to_buf(cl_hw, &buf, &len, &buf_size, WRS_MODE_HE, WRS_MCS_MAX_HE,
+                                 pwr_tables->pwr_auto_resp_he, "HE");
+
+       if (is_6g)
+               goto out;
+
+       if (is_5g || (is_24g && cl_hw->conf->ci_vht_cap_24g))
+               cl_power_auto_resp_to_buf(cl_hw, &buf, &len, &buf_size,
+                                         WRS_MODE_VHT, WRS_MCS_MAX_VHT,
+                                         pwr_tables->pwr_auto_resp_ht_vht, "VHT/HT");
+       else
+               cl_power_auto_resp_to_buf(cl_hw, &buf, &len, &buf_size,
+                                         WRS_MODE_HT, WRS_MCS_MAX_HT,
+                                         pwr_tables->pwr_auto_resp_ht_vht, "HT");
+
+       cl_power_auto_resp_to_buf(cl_hw, &buf, &len, &buf_size,
+                                 WRS_MODE_OFDM, WRS_MCS_MAX_OFDM,
+                                 pwr_tables->pwr_auto_resp_ofdm, "OFDM");
+
+       if (is_24g)
+               cl_power_auto_resp_to_buf(cl_hw, &buf, &len, &buf_size,
+                                         WRS_MODE_CCK, WRS_MCS_MAX_CCK,
+                                         pwr_tables->pwr_auto_resp_cck, "CCK");
+
+out:
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_power_cli_help(struct cl_hw *cl_hw)
+{
+       char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       int err = 0;
+
+       if (!buf)
+               return -ENOMEM;
+
+       snprintf(buf, PAGE_SIZE,
+                "usage:\n"
+                "-a : General power information\n"
+                "-b : HE power table\n"
+                "-c : HT/VHT power table\n"
+                "-d : OFDM power table\n"
+                "-e : CCK power table\n"
+                "-f : HE power truncate\n"
+                "-g : HT/VHT power truncate\n"
+                "-h : OFDM power truncate\n"
+                "-i : CCK power truncate\n"
+                "-j : Auto-response power tables\n");
+
+       err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+       kfree(buf);
+
+       return err;
+}
+
+int cl_power_cli(struct cl_hw *cl_hw, struct cli_params *cli_params)
+{
+       if (cli_params->num_params != 0)
+               goto err_num_params;
+
+       switch (cli_params->option) {
+       case 'a':
+               return cl_power_general_print(cl_hw);
+       case 'b':
+               return cl_power_table_print_he(cl_hw);
+       case 'c':
+               return cl_power_table_print_ht_vht(cl_hw);
+       case 'd':
+               return cl_power_table_print_ofdm(cl_hw);
+       case 'e':
+               return cl_power_table_print_cck(cl_hw);
+       case 'f':
+               return cl_power_trunc_print_he(cl_hw);
+       case 'g':
+               return cl_power_trunc_print_ht_vht(cl_hw);
+       case 'h':
+               return cl_power_trunc_print_ofdm(cl_hw);
+       case 'i':
+               return cl_power_trunc_print_cck(cl_hw);
+       case 'j':
+               return cl_power_auto_response_print(cl_hw);
+       case '?':
+               return cl_power_cli_help(cl_hw);
+       default:
+               cl_dbg_err(cl_hw, "Illegal option (%c) - try '?' for help\n", cli_params->option);
+               break;
+       }
+
+       return 0;
+
+err_num_params:
+       cl_dbg_err(cl_hw, "Wrong number of arguments\n");
+
+       return 0;
+}
+