/******************************************************************************
 *
 * Copyright(c) 2007 - 2022 Realtek Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 *****************************************************************************/
#define _HAL_PWR_TABLE_C_

#include <drv_types.h>
#include <hal_data.h>

#if CONFIG_TXPWR_LIMIT
const char *const _txpwr_lmt_rs_str[] = {
	[TXPWR_LMT_RS_CCK]	= "CCK",
	[TXPWR_LMT_RS_OFDM]	= "OFDM",
	[TXPWR_LMT_RS_HT]	= "HT",
	[TXPWR_LMT_RS_VHT]	= "VHT",
	[TXPWR_LMT_RS_NUM]	= "UNKNOWN",
};

void hal_txpwr_lmt_reg_exc_add_with_nlen(struct hal_com_data *hal_data, const char *country, u8 domain, const char *reg_name, u32 nlen)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	struct lmt_reg_exc *ent;

	if (!reg_name || !nlen) {
		rtw_warn_on(1);
		goto exit;
	}

	ent = rtw_zmalloc(sizeof(struct lmt_reg_exc) + nlen + 1);
	if (!ent)
		goto exit;

	_rtw_init_listhead(&ent->list);
	if (country)
		_rtw_memcpy(ent->country, country, 2);
	ent->domain = domain;
	_rtw_memcpy(ent->reg_name, reg_name, nlen);

	_rtw_mutex_lock_interruptible(&tb->lock);

	rtw_list_insert_tail(&ent->list, &tb->reg_exc_list);
	tb->reg_exc_num++;

	_rtw_mutex_unlock(&tb->lock);

exit:
	return;
}

void hal_txpwr_lmt_reg_exc_add(struct hal_com_data *hal_data, const char *country, u8 domain, const char *reg_name)
{
	hal_txpwr_lmt_reg_exc_add_with_nlen(hal_data, country, domain, reg_name, strlen(reg_name));
}

static struct lmt_reg_exc *_hal_txpwr_lmt_reg_exc_search(struct hal_com_data *hal_data, const char *country, u8 domain)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	struct lmt_reg_exc *ent;
	_list *cur, *head;
	u8 match = 0;

	head = &tb->reg_exc_list;
	cur = get_next(head);

	while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
		u8 has_country;

		ent = LIST_CONTAINOR(cur, struct lmt_reg_exc, list);
		cur = get_next(cur);
		has_country = (ent->country[0] == '\0' && ent->country[1] == '\0') ? 0 : 1;

		/* entry has country condition to match */
		if (has_country) {
			if (!country)
				continue;
			if (ent->country[0] != country[0]
				|| ent->country[1] != country[1])
				continue;
		}

		/* entry has domain condition to match */
		if (ent->domain != 0xFF) {
			if (domain == 0xFF)
				continue;
			if (ent->domain != domain)
				continue;
		}

		match = 1;
		break;
	}

	if (match)
		return ent;
	else
		return NULL;
}

struct lmt_reg_exc *hal_txpwr_lmt_reg_exc_search(struct hal_com_data *hal_data, const char *country, u8 domain)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	struct lmt_reg_exc *ent;

	_rtw_mutex_lock_interruptible(&tb->lock);
	ent = _hal_txpwr_lmt_reg_exc_search(hal_data, country, domain);
	_rtw_mutex_unlock(&tb->lock);

	return ent;
}

void hal_txpwr_lmt_reg_exc_list_free(struct hal_com_data *hal_data)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	struct lmt_reg_exc *ent;
	_list *cur, *head;

	_rtw_mutex_lock_interruptible(&tb->lock);

	head = &tb->reg_exc_list;
	cur = get_next(head);

	while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
		ent = LIST_CONTAINOR(cur, struct lmt_reg_exc, list);
		cur = get_next(cur);
		rtw_list_delete(&ent->list);
		rtw_mfree((u8 *)ent, sizeof(struct lmt_reg_exc) + strlen(ent->reg_name) + 1);
	}
	tb->reg_exc_num = 0;

	_rtw_mutex_unlock(&tb->lock);
}

static void _dump_txpwr_lmt_reg_exc_list(void *sel, struct hal_com_data *hal_data)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	struct lmt_reg_exc *ent;
	_list *cur, *head;

	RTW_PRINT_SEL(sel, "reg_exc_num:%u\n", tb->reg_exc_num);

	if (!tb->reg_exc_num)
		goto exit;

	RTW_PRINT_SEL(sel, "%-7s %-6s %-8s\n", "country", "domain", "reg_name");

	head = &tb->reg_exc_list;
	cur = get_next(head);

	while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
		u8 has_country;

		ent = LIST_CONTAINOR(cur, struct lmt_reg_exc, list);
		cur = get_next(cur);
		has_country = (ent->country[0] == '\0' && ent->country[1] == '\0') ? 0 : 1;

		RTW_PRINT_SEL(sel, "     %c%c   0x%02x %s\n"
			, has_country ? ent->country[0] : '-'
			, has_country ? ent->country[1] : '-'
			, ent->domain
			, ent->reg_name
		);
	}

exit:
	return;
}

void dump_txpwr_lmt_reg_exc_list(void *sel, struct hal_com_data *hal_data)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;

	_rtw_mutex_lock_interruptible(&tb->lock);
	_dump_txpwr_lmt_reg_exc_list(sel, hal_data);
	_rtw_mutex_unlock(&tb->lock);
}

/* search matcing first, if not found, alloc one */
void hal_txpwr_lmt_reg_add_with_nlen(struct hal_com_data *hal_data, const char *name, u32 nlen
	, u8 band, u8 bw, u8 tlrs, u8 ntx_idx, u8 ch_idx, s8 lmt)
{
	struct hal_spec_t *hal_spec = &hal_data->hal_spec;
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	struct lmt_reg *ent;
	_list *cur, *head;
	s8 pre_lmt;
	u8 ch;

	if (!name || !nlen) {
		rtw_warn_on(1);
		goto exit;
	}

	_rtw_mutex_lock_interruptible(&tb->lock);

	/* search for existed entry */
	head = &tb->reg_list;
	cur = get_next(head);
	while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
		ent = LIST_CONTAINOR(cur, struct lmt_reg, list);
		cur = get_next(cur);

		if (strlen(ent->name) == nlen
			&& _rtw_memcmp(ent->name, name, nlen) == _TRUE)
			goto chk_lmt_val;
	}

	/* alloc new one */
	ent = rtw_zvmalloc(sizeof(struct lmt_reg) + nlen + 1);
	if (!ent)
		goto release_lock;

	_rtw_init_listhead(&ent->list);
	_rtw_memcpy(ent->name, name, nlen);
	{
		u8 j, k, l, m;

		for (j = 0; j < MAX_2_4G_BANDWIDTH_NUM; ++j)
			for (k = 0; k < TXPWR_LMT_RS_NUM_2G; ++k)
				for (m = 0; m < CENTER_CH_2G_NUM; ++m)
					for (l = 0; l < MAX_TX_COUNT; ++l)
						ent->lmt_2g[j][k][m][l] = hal_spec->txgi_max;
		#if CONFIG_IEEE80211_BAND_5GHZ
		for (j = 0; j < MAX_5G_BANDWIDTH_NUM; ++j)
			for (k = 0; k < TXPWR_LMT_RS_NUM_5G; ++k)
				for (m = 0; m < CENTER_CH_5G_ALL_NUM; ++m)
					for (l = 0; l < MAX_TX_COUNT; ++l)
						ent->lmt_5g[j][k][m][l] = hal_spec->txgi_max;
		#endif
	}

	rtw_list_insert_tail(&ent->list, &tb->reg_list);
	tb->reg_num++;

chk_lmt_val:
	if (band == BAND_ON_2_4G) {
		pre_lmt = ent->lmt_2g[bw][tlrs][ch_idx][ntx_idx];
		ch = ch_idx + 1;
	}
	#if CONFIG_IEEE80211_BAND_5GHZ
	else if (band == BAND_ON_5G) {
		pre_lmt = ent->lmt_5g[bw][tlrs - 1][ch_idx][ntx_idx];
		ch = center_ch_5g_all[ch_idx];
	}
	#endif
	else
		goto release_lock;

	if (pre_lmt != hal_spec->txgi_max)
		RTW_PRINT("duplicate txpwr_lmt for [%s][%s][%s][%s][%uT][%d]\n"
			, name, band_str(band), ch_width_str(bw), txpwr_lmt_rs_str(tlrs), ntx_idx + 1, ch);

	lmt = rtw_min(pre_lmt, lmt);
	if (band == BAND_ON_2_4G)
		ent->lmt_2g[bw][tlrs][ch_idx][ntx_idx] = lmt;
	#if CONFIG_IEEE80211_BAND_5GHZ
	else if (band == BAND_ON_5G)
		ent->lmt_5g[bw][tlrs - 1][ch_idx][ntx_idx] = lmt;
	#endif

	if (0)
		RTW_PRINT("%s, %4s, %6s, %7s, %uT, ch%3d = %d\n"
			, name, band_str(band), ch_width_str(bw), txpwr_lmt_rs_str(tlrs), ntx_idx + 1
			, ch, lmt);

release_lock:
	_rtw_mutex_unlock(&tb->lock);

exit:
	return;
}

void hal_txpwr_lmt_reg_add(struct hal_com_data *hal_data, const char *name
	, u8 band, u8 bw, u8 tlrs, u8 ntx_idx, u8 ch_idx, s8 lmt)
{
	hal_txpwr_lmt_reg_add_with_nlen(hal_data, name, strlen(name)
		, band, bw, tlrs, ntx_idx, ch_idx, lmt);
}

struct lmt_reg *_hal_txpwr_lmt_reg_get_by_name(struct hal_com_data *hal_data, const char *name)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	struct lmt_reg *ent;
	_list *cur, *head;
	u8 found = 0;

	head = &tb->reg_list;
	cur = get_next(head);

	while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
		ent = LIST_CONTAINOR(cur, struct lmt_reg, list);
		cur = get_next(cur);

		if (strcmp(ent->name, name) == 0) {
			found = 1;
			break;
		}
	}

	if (found)
		return ent;
	return NULL;
}

struct lmt_reg *hal_txpwr_lmt_reg_get_by_name(struct hal_com_data *hal_data, const char *name)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	struct lmt_reg *ent;

	_rtw_mutex_lock_interruptible(&tb->lock);
	ent = _hal_txpwr_lmt_reg_get_by_name(hal_data, name);
	_rtw_mutex_unlock(&tb->lock);

	return ent;
}

static void hal_txpwr_clear_current_lmt_reg_names(struct hal_com_data *hal_data, enum band_type band)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;

	if (band >= BAND_MAX)
		return;

	if (tb->cur_reg_names[band]) {
		rtw_mfree(tb->cur_reg_names[band], tb->cur_reg_names_len[band]);
		tb->cur_reg_names[band] = NULL;
	}
	tb->cur_reg_names_len[band] = 0;
}

static void hal_txpwr_clear_all_current_lmt_reg_names(struct hal_com_data *hal_data)
{
	u8 band;

	for (band = 0; band < BAND_MAX; band++)
		hal_txpwr_clear_current_lmt_reg_names(hal_data, band);
}

void hal_txpwr_set_current_lmt_regs(struct hal_com_data *hal_data, enum band_type band, char *names, int names_len)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;

	if (band >= BAND_MAX)
		return;

	_rtw_mutex_lock_interruptible(&tb->lock);

	hal_txpwr_clear_current_lmt_reg_names(hal_data, band);

	if (names && names_len) {
		tb->cur_reg_names[band] = rtw_malloc(names_len);
		if (tb->cur_reg_names[band]) {
			_rtw_memcpy(tb->cur_reg_names[band], names, names_len);
			tb->cur_reg_names_len[band] = names_len;
		}
	}

	_rtw_mutex_unlock(&tb->lock);
}

void hal_txpwr_get_current_lmt_regs(struct hal_com_data *hal_data, enum band_type band, char **names, int *names_len)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;

	if (!names || !names_len)
		return;

	*names = NULL;
	*names_len = 0;

	if (band >= BAND_MAX)
		return;

	_rtw_mutex_lock_interruptible(&tb->lock);

	if (tb->cur_reg_names[band] && tb->cur_reg_names_len[band]) {
		*names = rtw_malloc(tb->cur_reg_names_len[band]);
		if (*names) {
			_rtw_memcpy(*names, tb->cur_reg_names[band], tb->cur_reg_names_len[band]);
			*names_len = tb->cur_reg_names_len[band];
		}
	}

	_rtw_mutex_unlock(&tb->lock);
}

bool hal_txpwr_is_current_lmt_reg(struct hal_com_data *hal_data, const char *name)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	int band;
	const char *reg_names, *pos;
	int reg_names_len;

	for (band = 0; band < BAND_MAX; band++) {
		reg_names = tb->cur_reg_names[band];
		reg_names_len = tb->cur_reg_names_len[band];
		if (!reg_names)
			continue;
		ustrs_for_each_str(reg_names, reg_names_len, pos) {
			if (strcmp(name, pos) == 0)
				return true;
		}
	}

	return false;
}

void hal_txpwr_lmt_reg_list_free(struct hal_com_data *hal_data)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	struct lmt_reg *ent;
	_list *cur, *head;

	_rtw_mutex_lock_interruptible(&tb->lock);

	head = &tb->reg_list;
	cur = get_next(head);

	while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
		ent = LIST_CONTAINOR(cur, struct lmt_reg, list);
		cur = get_next(cur);
		rtw_list_delete(&ent->list);
		rtw_vmfree((u8 *)ent, sizeof(struct lmt_reg) + strlen(ent->name) + 1);
	}
	tb->reg_num = 0;

	hal_txpwr_clear_all_current_lmt_reg_names(hal_data);

	_rtw_mutex_unlock(&tb->lock);
}

void hal_txpwr_lmt_tb_init(struct hal_com_data *hal_data)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;

	_rtw_mutex_init(&tb->lock);
	_rtw_init_listhead(&tb->reg_exc_list);
	_rtw_init_listhead(&tb->reg_list);
}

void hal_txpwr_lmt_tb_deinit(struct hal_com_data *hal_data)
{
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;

	hal_txpwr_lmt_reg_exc_list_free(hal_data);
	hal_txpwr_lmt_reg_list_free(hal_data);
	_rtw_mutex_free(&tb->lock);
}

void dump_txpwr_lmt(void *sel, _adapter *adapter)
{
#define TMP_STR_LEN 16
	struct hal_com_data *hal_data = GET_HAL_DATA(adapter);
	struct hal_spec_t *hal_spec = GET_HAL_SPEC(adapter);
	struct txpwr_lmt_tb_t *tb = &hal_data->txpwr_lmt_tb;
	char fmt[16];
	char tmp_str[TMP_STR_LEN];
	s8 *lmt_idx = NULL;
	int bw, band, ch_num, tlrs, ntx_idx, rs, i, path;
	u8 ch, n, rfpath_num;

	_rtw_mutex_lock_interruptible(&tb->lock);

	_dump_txpwr_lmt_reg_exc_list(sel, hal_data);
	RTW_PRINT_SEL(sel, "\n");

	if (!tb->reg_num)
		goto release_lock;

	lmt_idx = rtw_malloc(sizeof(s8) * RF_PATH_MAX * tb->reg_num);
	if (!lmt_idx) {
		RTW_ERR("%s alloc fail\n", __func__);
		goto release_lock;
	}

	RTW_PRINT_SEL(sel, "lmt_2g_cck_ofdm_state:0x%02x\n", tb->lmt_2g_cck_ofdm_state);
	#if CONFIG_IEEE80211_BAND_5GHZ
	if (IS_HARDWARE_TYPE_JAGUAR_ALL(adapter)) {
		RTW_PRINT_SEL(sel, "lmt_5g_cck_ofdm_state:0x%02x\n", tb->lmt_5g_cck_ofdm_state);
		RTW_PRINT_SEL(sel, "lmt_5g_20_40_ref:0x%02x\n", tb->lmt_5g_20_40_ref);
	}
	#endif
	RTW_PRINT_SEL(sel, "\n");

	for (band = BAND_ON_2_4G; band <= BAND_ON_5G; band++) {
		if (!hal_is_band_support(adapter, band))
			continue;

		rfpath_num = (band == BAND_ON_2_4G ? hal_spec->rfpath_num_2g : hal_spec->rfpath_num_5g);

		for (bw = 0; bw < MAX_5G_BANDWIDTH_NUM; bw++) {

			if (bw >= CHANNEL_WIDTH_160)
				break;
			if (band == BAND_ON_2_4G && bw >= CHANNEL_WIDTH_80)
				break;

			if (band == BAND_ON_2_4G)
				ch_num = CENTER_CH_2G_NUM;
			#if CONFIG_IEEE80211_BAND_5GHZ
			else if (band == BAND_ON_5G)
				ch_num = center_chs_5g_num(bw);
			#endif
			else
				ch_num = 0;

			if (ch_num == 0) {
				rtw_warn_on(1);
				break;
			}

			for (tlrs = TXPWR_LMT_RS_CCK; tlrs < TXPWR_LMT_RS_NUM; tlrs++) {

				if (band == BAND_ON_2_4G && tlrs == TXPWR_LMT_RS_VHT)
					continue;
				if (band == BAND_ON_5G && tlrs == TXPWR_LMT_RS_CCK)
					continue;
				if (bw > CHANNEL_WIDTH_20 && (tlrs == TXPWR_LMT_RS_CCK || tlrs == TXPWR_LMT_RS_OFDM))
					continue;
				if (bw > CHANNEL_WIDTH_40 && tlrs == TXPWR_LMT_RS_HT)
					continue;
				if (tlrs == TXPWR_LMT_RS_VHT && !IS_HARDWARE_TYPE_JAGUAR_ALL(adapter))
					continue;

				for (ntx_idx = RF_1TX; ntx_idx < MAX_TX_COUNT; ntx_idx++) {
					struct lmt_reg *ent;
					_list *cur, *head;

					if (ntx_idx + 1 > hal_data->max_tx_cnt)
						continue;

					/* bypass CCK multi-TX is not defined */
					if (tlrs == TXPWR_LMT_RS_CCK && ntx_idx > RF_1TX) {
						if (band == BAND_ON_2_4G
							&& !(tb->lmt_2g_cck_ofdm_state & (TXPWR_LMT_HAS_CCK_1T << ntx_idx)))
							continue;
					}

					/* bypass OFDM multi-TX is not defined */
					if (tlrs == TXPWR_LMT_RS_OFDM && ntx_idx > RF_1TX) {
						if (band == BAND_ON_2_4G
							&& !(tb->lmt_2g_cck_ofdm_state & (TXPWR_LMT_HAS_OFDM_1T << ntx_idx)))
							continue;
						#if CONFIG_IEEE80211_BAND_5GHZ
						if (band == BAND_ON_5G
							&& !(tb->lmt_5g_cck_ofdm_state & (TXPWR_LMT_HAS_OFDM_1T << ntx_idx)))
							continue;
						#endif
					}

					/* bypass 5G 20M, 40M pure reference */
					#if CONFIG_IEEE80211_BAND_5GHZ
					if (band == BAND_ON_5G && (bw == CHANNEL_WIDTH_20 || bw == CHANNEL_WIDTH_40)) {
						if (tb->lmt_5g_20_40_ref == TXPWR_LMT_REF_HT_FROM_VHT) {
							if (tlrs == TXPWR_LMT_RS_HT)
								continue;
						} else if (tb->lmt_5g_20_40_ref == TXPWR_LMT_REF_VHT_FROM_HT) {
							if (tlrs == TXPWR_LMT_RS_VHT && bw <= CHANNEL_WIDTH_40)
								continue;
						}
					}
					#endif

					/* choose n-SS mapping rate section to get lmt diff value */
					if (tlrs == TXPWR_LMT_RS_CCK)
						rs = CCK;
					else if (tlrs == TXPWR_LMT_RS_OFDM)
						rs = OFDM;
					else if (tlrs == TXPWR_LMT_RS_HT)
						rs = HT_1SS + ntx_idx;
					else if (tlrs == TXPWR_LMT_RS_VHT)
						rs = VHT_1SS + ntx_idx;
					else {
						RTW_ERR("%s invalid tlrs %u\n", __func__, tlrs);
						continue;
					}

					RTW_PRINT_SEL(sel, "[%s][%s][%s][%uT]\n"
						, band_str(band)
						, ch_width_str(bw)
						, txpwr_lmt_rs_str(tlrs)
						, ntx_idx + 1
					);

					/* header for limit in db */
					RTW_PRINT_SEL(sel, "%3s ", "ch");

					head = &tb->reg_list;
					cur = get_next(head);
					while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
						ent = LIST_CONTAINOR(cur, struct lmt_reg, list);
						cur = get_next(cur);

						sprintf(fmt, "%%%zus%%s ", strlen(ent->name) >= 6 ? 1 : 6 - strlen(ent->name));
						snprintf(tmp_str, TMP_STR_LEN, fmt
							, hal_txpwr_is_current_lmt_reg(hal_data, ent->name) ? "*" : ""
							, ent->name);
						_RTW_PRINT_SEL(sel, "%s", tmp_str);
					}
					sprintf(fmt, "%%%zus%%s ", strlen(txpwr_lmt_str(TXPWR_LMT_WW)) >= 6 ? 1 : 6 - strlen(txpwr_lmt_str(TXPWR_LMT_WW)));
					snprintf(tmp_str, TMP_STR_LEN, fmt
						, hal_txpwr_is_current_lmt_reg(hal_data, txpwr_lmt_str(TXPWR_LMT_WW)) ? "*" : ""
						, txpwr_lmt_str(TXPWR_LMT_WW));
					_RTW_PRINT_SEL(sel, "%s", tmp_str);

					/* header for limit offset */
					for (path = 0; path < RF_PATH_MAX; path++) {
						if (path >= rfpath_num)
							break;
						_RTW_PRINT_SEL(sel, "|");
						head = &tb->reg_list;
						cur = get_next(head);
						while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
							ent = LIST_CONTAINOR(cur, struct lmt_reg, list);
							cur = get_next(cur);
							_RTW_PRINT_SEL(sel, "%3c "
								, hal_txpwr_is_current_lmt_reg(hal_data, ent->name) ? rf_path_char(path) : ' ');
						}
						_RTW_PRINT_SEL(sel, "%3c "
								, hal_txpwr_is_current_lmt_reg(hal_data, txpwr_lmt_str(TXPWR_LMT_WW)) ? rf_path_char(path) : ' ');
					}
					_RTW_PRINT_SEL(sel, "\n");

					for (n = 0; n < ch_num; n++) {
						s8 lmt;
						s8 lmt_offset;
						u8 base;

						if (band == BAND_ON_2_4G)
							ch = n + 1;
						#if CONFIG_IEEE80211_BAND_5GHZ
						else if (band == BAND_ON_5G)
							ch = center_chs_5g(bw, n);
						#endif
						else
							ch = 0;

						if (ch == 0) {
							rtw_warn_on(1);
							break;
						}

						/* dump limit in dBm */
						RTW_PRINT_SEL(sel, "%3u ", ch);
						head = &tb->reg_list;
						cur = get_next(head);
						while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
							ent = LIST_CONTAINOR(cur, struct lmt_reg, list);
							cur = get_next(cur);
							lmt = phy_get_txpwr_lmt(adapter, ent->name, band, bw, tlrs, ntx_idx, ch, 0);
							txpwr_idx_get_dbm_str(lmt, hal_spec->txgi_max, hal_spec->txgi_pdbm, strlen(ent->name), tmp_str, TMP_STR_LEN);
							_RTW_PRINT_SEL(sel, "%s ", tmp_str);
						}
						lmt = phy_get_txpwr_lmt(adapter, txpwr_lmt_str(TXPWR_LMT_WW), band, bw, tlrs, ntx_idx, ch, 0);
						txpwr_idx_get_dbm_str(lmt, hal_spec->txgi_max, hal_spec->txgi_pdbm, strlen(txpwr_lmt_str(TXPWR_LMT_WW)), tmp_str, TMP_STR_LEN);
						_RTW_PRINT_SEL(sel, "%s ", tmp_str);

						/* dump limit offset of each path */
						for (path = RF_PATH_A; path < RF_PATH_MAX; path++) {
							if (path >= rfpath_num)
								break;

							base = phy_get_target_txpwr(adapter, band, path, rs);

							_RTW_PRINT_SEL(sel, "|");
							head = &tb->reg_list;
							cur = get_next(head);
							i = 0;
							while ((rtw_end_of_queue_search(head, cur)) == _FALSE) {
								ent = LIST_CONTAINOR(cur, struct lmt_reg, list);
								cur = get_next(cur);
								lmt_offset = phy_get_txpwr_lmt_diff(adapter, ent->name, band, bw, path, rs, tlrs, ntx_idx, ch, 0);
								if (lmt_offset == hal_spec->txgi_max) {
									*(lmt_idx + i * RF_PATH_MAX + path) = hal_spec->txgi_max;
									_RTW_PRINT_SEL(sel, "%3s ", "NA");
								} else {
									*(lmt_idx + i * RF_PATH_MAX + path) = lmt_offset + base;
									_RTW_PRINT_SEL(sel, "%3d ", lmt_offset);
								}
								i++;
							}
							lmt_offset = phy_get_txpwr_lmt_diff(adapter, txpwr_lmt_str(TXPWR_LMT_WW), band, bw, path, rs, tlrs, ntx_idx, ch, 0);
							if (lmt_offset == hal_spec->txgi_max)
								_RTW_PRINT_SEL(sel, "%3s ", "NA");
							else
								_RTW_PRINT_SEL(sel, "%3d ", lmt_offset);

						}

						/* compare limit_idx of each path, print 'x' when mismatch */
						if (rfpath_num > 1) {
							for (i = 0; i < tb->reg_num; i++) {
								for (path = 0; path < RF_PATH_MAX; path++) {
									if (path >= rfpath_num)
										break;
									if (*(lmt_idx + i * RF_PATH_MAX + path) != *(lmt_idx + i * RF_PATH_MAX + ((path + 1) % rfpath_num)))
										break;
								}
								if (path >= rfpath_num)
									_RTW_PRINT_SEL(sel, " ");
								else
									_RTW_PRINT_SEL(sel, "x");
							}
						}
						_RTW_PRINT_SEL(sel, "\n");

					}
					RTW_PRINT_SEL(sel, "\n");
				}
			} /* loop for rate sections */
		} /* loop for bandwidths */
	} /* loop for bands */

	if (lmt_idx)
		rtw_mfree(lmt_idx, sizeof(s8) * RF_PATH_MAX * tb->reg_num);

release_lock:
	_rtw_mutex_unlock(&tb->lock);
}
#endif /* CONFIG_TXPWR_LIMIT */