All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/8] rtw88: Add SAR implementation
@ 2020-02-07  9:28 yhchuang
  2020-02-07  9:28 ` [PATCH 1/8] rtw88: sar: add SAR of TX power limit yhchuang
                   ` (7 more replies)
  0 siblings, 8 replies; 10+ messages in thread
From: yhchuang @ 2020-02-07  9:28 UTC (permalink / raw)
  To: kvalo; +Cc: linux-wireless, briannorris, pkshih

From: Yan-Hsuan Chuang <yhchuang@realtek.com>

Add SAR implemention that allows driver to set SAR power from
three different sources:
  1. From vendor command
  2. From static SAR configuration
  3. From dynamic SAR

SAR sources will provide driver a list of target power in dBm
which should be translated into power indexes. Then driver
will just treat those indexes as another power limit to force
the power output being lower then SAR restriction.

For either vendor command, static or dynamic SAR, driver stores
the values in tx_pwr_sar_{2,5}g[regd][path][rs][ch]. Whenever
the channel is changed or a new SAR table is applied, driver
will configure the power indexes.

To check if SAR power is configured properly, append a column
to debugfs "tx_pwr_tbl". But for dynamic SAR, the profile ID
may change, so add a new debugfs "sar" to display SAR power.


Ping-Ke Shih (8):
  rtw88: sar: add SAR of TX power limit
  nl80211: vendor-cmd: realtek: Add vendor command to set SAR power
    limit
  rtw88: vndcmd: sar: Apply SAR power limit via vendor command
  rtw88: sar: Load static SAR table from ACPI WRDS method
  rtw88: sar: Load dynamic SAR table from ACPI methods
  rtw88: sar: apply dynamic SAR table to tx power limit
  rtw88: sar: add sar_work to poll if dynamic SAR table is changed
  rtw88: sar: dump sar information via debugfs

 drivers/net/wireless/realtek/rtw88/Makefile |   2 +
 drivers/net/wireless/realtek/rtw88/debug.c  |  30 +-
 drivers/net/wireless/realtek/rtw88/main.c   |  10 +
 drivers/net/wireless/realtek/rtw88/main.h   |  26 +
 drivers/net/wireless/realtek/rtw88/phy.c    | 173 ++++-
 drivers/net/wireless/realtek/rtw88/phy.h    |   3 +
 drivers/net/wireless/realtek/rtw88/sar.c    | 778 ++++++++++++++++++++
 drivers/net/wireless/realtek/rtw88/sar.h    |  15 +
 drivers/net/wireless/realtek/rtw88/vndcmd.c | 131 ++++
 drivers/net/wireless/realtek/rtw88/vndcmd.h |  10 +
 include/uapi/nl80211-vnd-realtek.h          |  72 ++
 11 files changed, 1218 insertions(+), 32 deletions(-)
 create mode 100644 drivers/net/wireless/realtek/rtw88/sar.c
 create mode 100644 drivers/net/wireless/realtek/rtw88/sar.h
 create mode 100644 drivers/net/wireless/realtek/rtw88/vndcmd.c
 create mode 100644 drivers/net/wireless/realtek/rtw88/vndcmd.h
 create mode 100644 include/uapi/nl80211-vnd-realtek.h

-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/8] rtw88: sar: add SAR of TX power limit
  2020-02-07  9:28 [PATCH 0/8] rtw88: Add SAR implementation yhchuang
@ 2020-02-07  9:28 ` yhchuang
  2020-04-16  7:39   ` Kalle Valo
  2020-02-07  9:28 ` [PATCH 2/8] nl80211: vendor-cmd: realtek: Add vendor command to set SAR " yhchuang
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 10+ messages in thread
From: yhchuang @ 2020-02-07  9:28 UTC (permalink / raw)
  To: kvalo; +Cc: linux-wireless, briannorris, pkshih

From: Ping-Ke Shih <pkshih@realtek.com>

Originally, there are three factors of TX power:
 1) TX base power, calibrated manually and programmed in efuse.
 2) By-rate power, an offset to increase power depends on TX rate.
 3) TX limit power, an offset to contraint the max power.

So, driver can get the TX power index by using:
    TX_POWER_IDX = tx_base_power + min(by-rate, limit)

To take SAR into consideration, we can treat it as another limit of
the TX power. Then driver can get the TX power by using:
    TX_POWER_IDX = tx_base_power + min(by-rate, limit, sar)

Note that the values stored in driver are not presented in dBm, so
driver needs to also convert the power limit indexes of SAR with
tx_scale_factor, then get the difference to the TX base power.
rtw_phy_set_tx_power_sar() will convert the values of SAR power in unit of
0.125d Bm (sar_q3) and store them in tx_pwr_sar_{2,5}g[regd][path][rs][ch].

Since certain SAR tables have single one table that isn't regulatory domain
specific, parser can apply to all 'regd' or only one domain RTW_REGD_WW
that SAR TX power limit can be applied no matter which regulatory domain
is selected. Because driver get 'sar' argument by
rtw_phy_get_tx_power_limit() with rule
    sar = is_existing(tx_pwr_sar_{2,5}g[regd]) ? tx_pwr_sar_{2,5}g[regd] :
          tx_pwr_sar_{2,5}g[RTW_REGD_WW];

There are various sources of SAR table, but it is expected to adopt only
one source. So, save current source of rtw_sar_sources to prevent more
than one source are adopted.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
---
 drivers/net/wireless/realtek/rtw88/debug.c |  14 +-
 drivers/net/wireless/realtek/rtw88/main.h  |  17 ++
 drivers/net/wireless/realtek/rtw88/phy.c   | 173 +++++++++++++++++----
 drivers/net/wireless/realtek/rtw88/phy.h   |   3 +
 4 files changed, 175 insertions(+), 32 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/debug.c b/drivers/net/wireless/realtek/rtw88/debug.c
index 5a181e01ebef..3ec15a49ecc9 100644
--- a/drivers/net/wireless/realtek/rtw88/debug.c
+++ b/drivers/net/wireless/realtek/rtw88/debug.c
@@ -530,8 +530,8 @@ static int rtw_debugfs_get_tx_pwr_tbl(struct seq_file *m, void *v)
 	u8 ch = hal->current_channel;
 	u8 regd = rtwdev->regd.txpwr_regd;
 
-	seq_printf(m, "%-4s %-10s %-3s%6s %-4s %4s (%-4s %-4s)\n",
-		   "path", "rate", "pwr", "", "base", "", "byr", "lmt");
+	seq_printf(m, "%-4s %-10s %-3s%6s %-4s %4s (%-4s %-4s %-4s)\n",
+		   "path", "rate", "pwr", "", "base", "", "byr", "lmt", "sar");
 
 	mutex_lock(&hal->tx_power_mutex);
 	for (path = RF_PATH_A; path <= RF_PATH_B; path++) {
@@ -553,13 +553,15 @@ static int rtw_debugfs_get_tx_pwr_tbl(struct seq_file *m, void *v)
 
 			seq_printf(m, "%4c ", path + 'A');
 			rtw_print_rate(m, rate);
-			seq_printf(m, " %3u(0x%02x) %4u %4d (%4d %4d)\n",
+			seq_printf(m, " %3u(0x%02x) %4u %4d (%4d %4d %4d)\n",
 				   hal->tx_pwr_tbl[path][rate],
 				   hal->tx_pwr_tbl[path][rate],
 				   pwr_param.pwr_base,
-				   min_t(s8, pwr_param.pwr_offset,
-					 pwr_param.pwr_limit),
-				   pwr_param.pwr_offset, pwr_param.pwr_limit);
+				   min3(pwr_param.pwr_offset,
+					pwr_param.pwr_limit,
+					pwr_param.pwr_sar),
+				   pwr_param.pwr_offset, pwr_param.pwr_limit,
+				   pwr_param.pwr_sar);
 		}
 	}
 
diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index f334d201bfb5..b4e9e18f89a5 100644
--- a/drivers/net/wireless/realtek/rtw88/main.h
+++ b/drivers/net/wireless/realtek/rtw88/main.h
@@ -386,6 +386,13 @@ enum rtw_wow_flags {
 	RTW_WOW_FLAG_MAX,
 };
 
+enum rtw_sar_sources {
+	RTW_SAR_SOURCE_NONE,
+	RTW_SAR_SOURCE_VNDCMD,
+	RTW_SAR_SOURCE_ACPI_STATIC,
+	RTW_SAR_SOURCE_ACPI_DYNAMIC,
+};
+
 /* the power index is represented by differences, which cck-1s & ht40-1s are
  * the base values, so for 1s's differences, there are only ht20 & ofdm
  */
@@ -1513,6 +1520,10 @@ struct rtw_fw_state {
 	u16 h2c_version;
 };
 
+struct rtw_sar {
+	enum rtw_sar_sources source;
+};
+
 struct rtw_hal {
 	u32 rcr;
 
@@ -1558,6 +1569,10 @@ struct rtw_hal {
 			  [RTW_CHANNEL_WIDTH_MAX]
 			  [RTW_RATE_SECTION_MAX]
 			  [RTW_MAX_CHANNEL_NUM_5G];
+	s8 tx_pwr_sar_2g[RTW_REGD_MAX][RTW_RF_PATH_MAX][RTW_RATE_SECTION_MAX]
+			[RTW_MAX_CHANNEL_NUM_2G];
+	s8 tx_pwr_sar_5g[RTW_REGD_MAX][RTW_RF_PATH_MAX][RTW_RATE_SECTION_MAX]
+			[RTW_MAX_CHANNEL_NUM_5G];
 	s8 tx_pwr_tbl[RTW_RF_PATH_MAX]
 		     [DESC_RATE_MAX];
 };
@@ -1630,6 +1645,8 @@ struct rtw_dev {
 	struct rtw_fw_state wow_fw;
 	struct rtw_wow_param wow;
 
+	struct rtw_sar sar;
+
 	/* hci related data, must be last */
 	u8 priv[0] __aligned(sizeof(void *));
 };
diff --git a/drivers/net/wireless/realtek/rtw88/phy.c b/drivers/net/wireless/realtek/rtw88/phy.c
index eea9d888fbf1..930757c07b46 100644
--- a/drivers/net/wireless/realtek/rtw88/phy.c
+++ b/drivers/net/wireless/realtek/rtw88/phy.c
@@ -1295,6 +1295,94 @@ static void rtw_phy_set_tx_power_limit(struct rtw_dev *rtwdev, u8 regd, u8 band,
 	}
 }
 
+static void
+rtw_phy_set_tx_power_sar_by_chidx(struct rtw_dev *rtwdev, u8 regd, u8 rfpath,
+				  u8 band, u8 rs, u8 ch_idx, s8 sar)
+{
+	struct rtw_hal *hal = &rtwdev->hal;
+	s8 base;
+	s8 ww_sar;
+	s8 s;
+
+	if (band == PHY_BAND_2G) {
+		base = hal->tx_pwr_by_rate_base_2g[rfpath][rs];
+		s = sar - base;
+		hal->tx_pwr_sar_2g[regd][rfpath][rs][ch_idx] = s;
+		if (regd == RTW_REGD_WW)
+			return;
+		ww_sar = hal->tx_pwr_sar_2g[RTW_REGD_WW][rfpath][rs][ch_idx];
+		ww_sar = min(ww_sar, s);
+		hal->tx_pwr_sar_2g[RTW_REGD_WW][rfpath][rs][ch_idx] = ww_sar;
+	} else {
+		base = hal->tx_pwr_by_rate_base_5g[rfpath][rs];
+		s = sar - base;
+		hal->tx_pwr_sar_5g[regd][rfpath][rs][ch_idx] = s;
+		if (regd == RTW_REGD_WW)
+			return;
+		ww_sar = hal->tx_pwr_sar_5g[RTW_REGD_WW][rfpath][rs][ch_idx];
+		ww_sar = min(ww_sar, s);
+		hal->tx_pwr_sar_5g[RTW_REGD_WW][rfpath][rs][ch_idx] = ww_sar;
+	}
+}
+
+static void
+rtw_phy_set_tx_power_sar_by_range(struct rtw_dev *rtwdev, u8 regd, u8 rfpath,
+				  u8 band, u8 chidx_start, u8 chidx_end, u8 sar_q3)
+{
+	u8 rs;
+	u8 ch_idx;
+	s8 sar;
+
+	if (regd >= RTW_REGD_MAX || rfpath >= RTW_RF_PATH_MAX)
+		return;
+
+	sar = sar_q3 >> (3 - (int)rtwdev->chip->txgi_factor);
+
+	for (ch_idx = chidx_start; ch_idx <= chidx_end; ch_idx++)
+		for (rs = 0; rs < RTW_RATE_SECTION_MAX; rs++)
+			rtw_phy_set_tx_power_sar_by_chidx(rtwdev, regd, rfpath,
+							  band, rs, ch_idx, sar);
+}
+
+void rtw_phy_set_tx_power_sar(struct rtw_dev *rtwdev, u8 regd, u8 rfpath,
+			      u8 ch_start, u8 ch_end, u8 sar_q3)
+{
+	u8 band_start, band_end;
+	int chidx_start, chidx_end;
+
+	band_start = ch_start <= 14 ? PHY_BAND_2G : PHY_BAND_5G;
+	band_end = ch_end <= 14 ? PHY_BAND_2G : PHY_BAND_5G;
+
+	if (band_start == band_end) {
+		chidx_start = rtw_channel_to_idx(band_start, ch_start);
+		chidx_end = rtw_channel_to_idx(band_start, ch_end);
+		if (chidx_start < 0 || chidx_end < 0)
+			goto err;
+		rtw_phy_set_tx_power_sar_by_range(rtwdev, regd, rfpath, band_start,
+						  chidx_start, chidx_end, sar_q3);
+		return;
+	}
+
+	chidx_start = rtw_channel_to_idx(PHY_BAND_2G, ch_start);
+	if (chidx_start < 0)
+		goto err;
+	rtw_phy_set_tx_power_sar_by_range(rtwdev, regd, rfpath, PHY_BAND_2G,
+					  chidx_start, RTW_MAX_CHANNEL_NUM_2G - 1,
+					  sar_q3);
+
+	chidx_end = rtw_channel_to_idx(PHY_BAND_5G, ch_end);
+	if (chidx_end < 0)
+		goto err;
+	rtw_phy_set_tx_power_sar_by_range(rtwdev, regd, rfpath, PHY_BAND_5G,
+					  0, chidx_end, sar_q3);
+
+	return;
+
+err:
+	rtw_warn(rtwdev, "SAR: invalid channel (start/end)=(%d/%d)\n",
+		 ch_start, ch_end);
+}
+
 /* cross-reference 5G power limits if values are not assigned */
 static void
 rtw_xref_5g_txpwr_lmt(struct rtw_dev *rtwdev, u8 regd,
@@ -1676,9 +1764,10 @@ static u8 rtw_phy_get_5g_tx_power_index(struct rtw_dev *rtwdev,
 	return tx_power;
 }
 
-static s8 rtw_phy_get_tx_power_limit(struct rtw_dev *rtwdev, u8 band,
-				     enum rtw_bandwidth bw, u8 rf_path,
-				     u8 rate, u8 channel, u8 regd)
+static void rtw_phy_get_tx_power_limit(struct rtw_dev *rtwdev, u8 band,
+				       enum rtw_bandwidth bw, u8 rf_path,
+				       u8 rate, u8 channel, u8 regd,
+				       struct rtw_power_params *pwr_param)
 {
 	struct rtw_hal *hal = &rtwdev->hal;
 	u8 *cch_by_bw = hal->cch_by_bw;
@@ -1687,9 +1776,10 @@ static s8 rtw_phy_get_tx_power_limit(struct rtw_dev *rtwdev, u8 band,
 	int ch_idx;
 	u8 cur_bw, cur_ch;
 	s8 cur_lmt;
+	s8 sar, sar_ww;
 
 	if (regd > RTW_REGD_WW)
-		return power_limit;
+		goto err;
 
 	if (rate >= DESC_RATE1M && rate <= DESC_RATE11M)
 		rs = RTW_RATE_SECTION_CCK;
@@ -1729,44 +1819,68 @@ static s8 rtw_phy_get_tx_power_limit(struct rtw_dev *rtwdev, u8 band,
 		power_limit = min_t(s8, cur_lmt, power_limit);
 	}
 
-	return power_limit;
+	ch_idx = rtw_channel_to_idx(band, channel);
+	if (ch_idx < 0)
+		goto err;
+
+	if (band == PHY_BAND_2G) {
+		sar = hal->tx_pwr_sar_2g[regd][rf_path][rs][ch_idx];
+		sar_ww = hal->tx_pwr_sar_2g[RTW_REGD_WW][rf_path][rs][ch_idx];
+	} else {
+		sar = hal->tx_pwr_sar_5g[regd][rf_path][rs][ch_idx];
+		sar_ww = hal->tx_pwr_sar_5g[RTW_REGD_WW][rf_path][rs][ch_idx];
+	}
+	if (sar >= rtwdev->chip->max_power_index)
+		sar = sar_ww;
+
+	pwr_param->pwr_sar = sar;
+	pwr_param->pwr_limit = power_limit;
+	return;
 
 err:
 	WARN(1, "invalid arguments, band=%d, bw=%d, path=%d, rate=%d, ch=%d\n",
 	     band, bw, rf_path, rate, channel);
-	return (s8)rtwdev->chip->max_power_index;
+	pwr_param->pwr_sar = (s8)rtwdev->chip->max_power_index;
+	pwr_param->pwr_limit = (s8)rtwdev->chip->max_power_index;
 }
 
-void rtw_get_tx_power_params(struct rtw_dev *rtwdev, u8 path, u8 rate, u8 bw,
-			     u8 ch, u8 regd, struct rtw_power_params *pwr_param)
+static void rtw_phy_get_tx_power_base(struct rtw_dev *rtwdev, u8 band,
+				      enum rtw_bandwidth bw, u8 path, u8 rate,
+				      u8 ch, struct rtw_power_params *pwr_param)
 {
 	struct rtw_hal *hal = &rtwdev->hal;
 	struct rtw_txpwr_idx *pwr_idx;
-	u8 group, band;
-	u8 *base = &pwr_param->pwr_base;
-	s8 *offset = &pwr_param->pwr_offset;
-	s8 *limit = &pwr_param->pwr_limit;
+	u8 group;
+	u8 base;
+	s8 offset;
 
 	pwr_idx = &rtwdev->efuse.txpwr_idx_table[path];
 	group = rtw_get_channel_group(ch);
 
 	/* base power index for 2.4G/5G */
-	if (IS_CH_2G_BAND(ch)) {
-		band = PHY_BAND_2G;
-		*base = rtw_phy_get_2g_tx_power_index(rtwdev,
-						      &pwr_idx->pwr_idx_2g,
-						      bw, rate, group);
-		*offset = hal->tx_pwr_by_rate_offset_2g[path][rate];
+	if (band == PHY_BAND_2G) {
+		base = rtw_phy_get_2g_tx_power_index(rtwdev,
+						     &pwr_idx->pwr_idx_2g,
+						     bw, rate, group);
+		offset = hal->tx_pwr_by_rate_offset_2g[path][rate];
 	} else {
-		band = PHY_BAND_5G;
-		*base = rtw_phy_get_5g_tx_power_index(rtwdev,
-						      &pwr_idx->pwr_idx_5g,
-						      bw, rate, group);
-		*offset = hal->tx_pwr_by_rate_offset_5g[path][rate];
+		base = rtw_phy_get_5g_tx_power_index(rtwdev,
+						     &pwr_idx->pwr_idx_5g,
+						     bw, rate, group);
+		offset = hal->tx_pwr_by_rate_offset_5g[path][rate];
 	}
 
-	*limit = rtw_phy_get_tx_power_limit(rtwdev, band, bw, path,
-					    rate, ch, regd);
+	pwr_param->pwr_base = base;
+	pwr_param->pwr_offset = offset;
+}
+
+void rtw_get_tx_power_params(struct rtw_dev *rtwdev, u8 path, u8 rate, u8 bw,
+			     u8 ch, u8 regd, struct rtw_power_params *pwr_param)
+{
+	u8 band = IS_CH_2G_BAND(ch) ? PHY_BAND_2G : PHY_BAND_5G;
+
+	rtw_phy_get_tx_power_base(rtwdev, band, bw, path, rate, ch, pwr_param);
+	rtw_phy_get_tx_power_limit(rtwdev, band, bw, path, rate, ch, regd, pwr_param);
 }
 
 u8
@@ -1781,7 +1895,8 @@ rtw_phy_get_tx_power_index(struct rtw_dev *rtwdev, u8 rf_path, u8 rate,
 				channel, regd, &pwr_param);
 
 	tx_power = pwr_param.pwr_base;
-	offset = min_t(s8, pwr_param.pwr_offset, pwr_param.pwr_limit);
+	offset = min3(pwr_param.pwr_offset, pwr_param.pwr_limit,
+		      pwr_param.pwr_sar);
 
 	if (rtwdev->chip->en_dis_dpd)
 		offset += rtw_phy_get_dis_dpd_by_rate_diff(rtwdev, rate);
@@ -1970,6 +2085,12 @@ void rtw_phy_init_tx_power(struct rtw_dev *rtwdev)
 			for (rs = 0; rs < RTW_RATE_SECTION_MAX; rs++)
 				rtw_phy_init_tx_power_limit(rtwdev, regd, bw,
 							    rs);
+
+	/* init tx power sar */
+	memset(hal->tx_pwr_sar_2g, rtwdev->chip->max_power_index,
+	       sizeof(hal->tx_pwr_sar_2g));
+	memset(hal->tx_pwr_sar_5g, rtwdev->chip->max_power_index,
+	       sizeof(hal->tx_pwr_sar_5g));
 }
 
 void rtw_phy_config_swing_table(struct rtw_dev *rtwdev,
diff --git a/drivers/net/wireless/realtek/rtw88/phy.h b/drivers/net/wireless/realtek/rtw88/phy.h
index af916d8784cd..ba9b28a6c0bc 100644
--- a/drivers/net/wireless/realtek/rtw88/phy.h
+++ b/drivers/net/wireless/realtek/rtw88/phy.h
@@ -139,12 +139,15 @@ struct rtw_power_params {
 	u8 pwr_base;
 	s8 pwr_offset;
 	s8 pwr_limit;
+	s8 pwr_sar;
 };
 
 void
 rtw_get_tx_power_params(struct rtw_dev *rtwdev, u8 path,
 			u8 rate, u8 bw, u8 ch, u8 regd,
 			struct rtw_power_params *pwr_param);
+void rtw_phy_set_tx_power_sar(struct rtw_dev *rtwdev, u8 regd, u8 rfpath,
+			      u8 ch_start, u8 ch_end, u8 sar_q3);
 
 enum rtw_phy_cck_pd_lv {
 	CCK_PD_LV0,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/8] nl80211: vendor-cmd: realtek: Add vendor command to set SAR power limit
  2020-02-07  9:28 [PATCH 0/8] rtw88: Add SAR implementation yhchuang
  2020-02-07  9:28 ` [PATCH 1/8] rtw88: sar: add SAR of TX power limit yhchuang
@ 2020-02-07  9:28 ` yhchuang
  2020-02-07  9:28 ` [PATCH 3/8] rtw88: vndcmd: sar: Apply SAR power limit via vendor command yhchuang
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: yhchuang @ 2020-02-07  9:28 UTC (permalink / raw)
  To: kvalo; +Cc: linux-wireless, briannorris, pkshih

From: Ping-Ke Shih <pkshih@realtek.com>

This is added to set SAR power limit, but doesn't replace existing
functionality.

Use 'iw' to set SAR power limit with ${p1,p2,p3,p4,p5} for five sub-bands:
sudo iw dev wlan0 vendor send 0x00E04C 0x88
0x68 0x00 0x01 0x80
0x14 0x00 0x01 0x80 0x08 0x00 0x02 0x00 0x00 0x00 0x00 0x00 0x05 0x00 0x03 0x00 $p1 0x00 0x00 0x00
0x14 0x00 0x02 0x80 0x08 0x00 0x02 0x00 0x01 0x00 0x00 0x00 0x05 0x00 0x03 0x00 $p2 0x00 0x00 0x00
0x14 0x00 0x03 0x80 0x08 0x00 0x02 0x00 0x02 0x00 0x00 0x00 0x05 0x00 0x03 0x00 $p3 0x00 0x00 0x00
0x14 0x00 0x04 0x80 0x08 0x00 0x02 0x00 0x03 0x00 0x00 0x00 0x05 0x00 0x03 0x00 $p4 0x00 0x00 0x00
0x14 0x00 0x05 0x80 0x08 0x00 0x02 0x00 0x04 0x00 0x00 0x00 0x05 0x00 0x03 0x00 $p5 0x00 0x00 0x00

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
---
 include/uapi/nl80211-vnd-realtek.h | 72 ++++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)
 create mode 100644 include/uapi/nl80211-vnd-realtek.h

diff --git a/include/uapi/nl80211-vnd-realtek.h b/include/uapi/nl80211-vnd-realtek.h
new file mode 100644
index 000000000000..6b71de8d7cdf
--- /dev/null
+++ b/include/uapi/nl80211-vnd-realtek.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/* Copyright(c) 2018-2019  Realtek Corporation
+ */
+#ifndef _UAPI_NL80211_VND_REALTEK_H
+#define _UAPI_NL80211_VND_REALTEK_H
+
+/**
+ * This vendor ID is the value of atrribute %NL80211_ATTR_VENDOR_ID used by
+ * %NL80211_CMD_VENDOR to send vendor command.
+ */
+#define REALTEK_NL80211_VENDOR_ID	0x00E04C
+
+/**
+ * enum realtek_nl80211_vndcmd - supported vendor subcmds
+ *
+ * @REALTEK_NL80211_VNDCMD_SET_SAR: set SAR power limit
+ *	%realtek_vndcmd_sar_band within attribute %REALTEK_VNDCMD_ATTR_SAR_BAND
+ *	and corresponding power limit attribute %REALTEK_VNDCMD_ATTR_SAR_POWER.
+ *	The two attributes are in nested attribute %REALTEK_VNDCMD_ATTR_SAR_RULES.
+ */
+enum realtek_nl80211_vndcmd {
+	REALTEK_NL80211_VNDCMD_SET_SAR = 0x88,
+};
+
+/**
+ * enum realtek_vndcmd_sar_band - bands of SAR power limit
+ *
+ * @REALTEK_VNDCMD_SAR_BAND_2G: all channels of 2G band
+ * @REALTEK_VNDCMD_SAR_BAND_5G_BAND1: channels of 5G band1 (5.15~5.35G)
+ * @REALTEK_VNDCMD_SAR_BAND_5G_BAND2: channels of 5G band2 (5.35~5.47G)
+ *	5G band2 isn't used by rtw88 by now, so don't need to set SAR power
+ *	limit for this band. But we still enumerate this band as a placeholder
+ *	for the furture.
+ * @REALTEK_VNDCMD_SAR_BAND_5G_BAND3: channels of 5G band3 (5.47~5.725G)
+ * @REALTEK_VNDCMD_SAR_BAND_5G_BAND4: channels of 5G band4 (5.725~5.95G)
+ */
+enum realtek_vndcmd_sar_band {
+	REALTEK_VNDCMD_SAR_BAND_2G,
+	REALTEK_VNDCMD_SAR_BAND_5G_BAND1,
+	REALTEK_VNDCMD_SAR_BAND_5G_BAND2,
+	REALTEK_VNDCMD_SAR_BAND_5G_BAND3,
+	REALTEK_VNDCMD_SAR_BAND_5G_BAND4,
+
+	REALTEK_VNDCMD_SAR_BAND_NR,
+};
+
+/**
+ * enum realtek_vndcmd_sar_rule_attr - attributes of vendor command
+ *	%REALTEK_NL80211_VNDCMD_SET_SAR
+ *
+ * @REALTEK_VNDCMD_ATTR_SAR_RULES: nested attribute to hold SAR rules containing
+ *	band and corresponding power limit.
+ *
+ * @REALTEK_VNDCMD_ATTR_SAR_BAND: an attribute within %REALTEK_VNDCMD_ATTR_SAR_RULES,
+ *	and its value is %realtek_vndcmd_sar_band (u32 data type).
+ * @REALTEK_VNDCMD_ATTR_SAR_POWER: an attribute within %REALTEK_VNDCMD_ATTR_SAR_RULES.
+ *	SAR power limit is 'u8' type and in unit of 0.125 dBm, so its range is
+ *	0 to 31.875 dBm.
+ */
+enum realtek_vndcmd_sar_rule_attr {
+	__REALTEK_VNDCMD_SAR_RULE_ATTR_INVALID,
+
+	REALTEK_VNDCMD_ATTR_SAR_RULES,
+	REALTEK_VNDCMD_ATTR_SAR_BAND,
+	REALTEK_VNDCMD_ATTR_SAR_POWER,
+
+	/* keep last */
+	__REALTEK_VNDCMD_SAR_RULE_ATTR_AFTER_LAST,
+	REALTEK_VNDCMD_SAR_RULE_ATTR_MAX = __REALTEK_VNDCMD_SAR_RULE_ATTR_AFTER_LAST - 1,
+};
+
+#endif /* _UAPI_NL80211_VND_REALTEK_H */
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/8] rtw88: vndcmd: sar: Apply SAR power limit via vendor command
  2020-02-07  9:28 [PATCH 0/8] rtw88: Add SAR implementation yhchuang
  2020-02-07  9:28 ` [PATCH 1/8] rtw88: sar: add SAR of TX power limit yhchuang
  2020-02-07  9:28 ` [PATCH 2/8] nl80211: vendor-cmd: realtek: Add vendor command to set SAR " yhchuang
@ 2020-02-07  9:28 ` yhchuang
  2020-02-07  9:28 ` [PATCH 4/8] rtw88: sar: Load static SAR table from ACPI WRDS method yhchuang
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: yhchuang @ 2020-02-07  9:28 UTC (permalink / raw)
  To: kvalo; +Cc: linux-wireless, briannorris, pkshih

From: Ping-Ke Shih <pkshih@realtek.com>

Use 'iw' vendor command to send SAR power limit table to driver that
converts power limit unit from dBm to rtw88's power_index. When channel is
changed, SAR power limit plays as a constraint to calculate TX power.

When a vendor command is recevied, kernel log shows like
  rtw_pci 0000:03:00.0: set SAR power limit 16.500 on band 0
  rtw_pci 0000:03:00.0: set SAR power limit 13.125 on band 1
  rtw_pci 0000:03:00.0: set SAR power limit 18.000 on band 3
  rtw_pci 0000:03:00.0: set SAR power limit 19.000 on band 4

Then, apply SAR power limit to all regions if no other SAR source is in use.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
---
 drivers/net/wireless/realtek/rtw88/Makefile |   1 +
 drivers/net/wireless/realtek/rtw88/main.c   |   2 +
 drivers/net/wireless/realtek/rtw88/vndcmd.c | 131 ++++++++++++++++++++
 drivers/net/wireless/realtek/rtw88/vndcmd.h |  10 ++
 4 files changed, 144 insertions(+)
 create mode 100644 drivers/net/wireless/realtek/rtw88/vndcmd.c
 create mode 100644 drivers/net/wireless/realtek/rtw88/vndcmd.h

diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile
index cac148d13cf1..935333f734a9 100644
--- a/drivers/net/wireless/realtek/rtw88/Makefile
+++ b/drivers/net/wireless/realtek/rtw88/Makefile
@@ -16,6 +16,7 @@ rtw88-y += main.o \
 	   sec.o \
 	   bf.o \
 	   wow.o \
+	   vndcmd.o \
 	   regd.o
 
 rtw88-$(CONFIG_RTW88_8822BE)	+= rtw8822b.o rtw8822b_table.o
diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index 2845d2838f7b..7156a06eea74 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -15,6 +15,7 @@
 #include "tx.h"
 #include "debug.h"
 #include "bf.h"
+#include "vndcmd.h"
 
 unsigned int rtw_fw_lps_deep_mode;
 EXPORT_SYMBOL(rtw_fw_lps_deep_mode);
@@ -1498,6 +1499,7 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw)
 	SET_IEEE80211_PERM_ADDR(hw, rtwdev->efuse.addr);
 
 	rtw_regd_init(rtwdev, rtw_regd_notifier);
+	rtw_register_vndcmd(hw);
 
 	ret = ieee80211_register_hw(hw);
 	if (ret) {
diff --git a/drivers/net/wireless/realtek/rtw88/vndcmd.c b/drivers/net/wireless/realtek/rtw88/vndcmd.c
new file mode 100644
index 000000000000..64c68239d28d
--- /dev/null
+++ b/drivers/net/wireless/realtek/rtw88/vndcmd.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/* Copyright(c) 2018-2019  Realtek Corporation
+ */
+
+#include <uapi/nl80211-vnd-realtek.h>
+
+#include "main.h"
+#include "phy.h"
+#include "debug.h"
+
+static const struct nla_policy
+rtw_sar_rule_policy[REALTEK_VNDCMD_SAR_RULE_ATTR_MAX + 1] = {
+	[REALTEK_VNDCMD_ATTR_SAR_RULES] = { .type = NLA_NESTED_ARRAY },
+	[REALTEK_VNDCMD_ATTR_SAR_BAND]	= { .type = NLA_U32 },
+	[REALTEK_VNDCMD_ATTR_SAR_POWER]	= { .type = NLA_U8 },
+};
+
+static const struct sar_band2ch {
+	u8 ch_start;
+	u8 ch_end;
+} sar_band2chs[REALTEK_VNDCMD_SAR_BAND_NR] = {
+	[REALTEK_VNDCMD_SAR_BAND_2G]	   = { .ch_start = 1,   .ch_end = 14 },
+	[REALTEK_VNDCMD_SAR_BAND_5G_BAND1] = { .ch_start = 36,  .ch_end = 64 },
+	/* REALTEK_VNDCMD_SAR_BAND_5G_BAND2 isn't used by now. */
+	[REALTEK_VNDCMD_SAR_BAND_5G_BAND3] = { .ch_start = 100, .ch_end = 144 },
+	[REALTEK_VNDCMD_SAR_BAND_5G_BAND4] = { .ch_start = 149, .ch_end = 165 },
+};
+
+static int rtw_apply_vndcmd_sar(struct rtw_dev *rtwdev, u32 band, u8 power)
+{
+	const struct sar_band2ch *sar_band2ch;
+	u8 path, rd;
+
+	if (band >= REALTEK_VNDCMD_SAR_BAND_NR)
+		return -EINVAL;
+
+	sar_band2ch = &sar_band2chs[band];
+	if (!sar_band2ch->ch_start || !sar_band2ch->ch_end)
+		return 0;
+
+	/* SAR values from vendor command apply to all regulatory domains,
+	 * and we can still ensure TX power under power limit because of
+	 * "tx_power = base + min(by_rate, limit, sar)".
+	 */
+	for (path = 0; path < rtwdev->hal.rf_path_num; path++)
+		for (rd = 0; rd < RTW_REGD_MAX; rd++)
+			rtw_phy_set_tx_power_sar(rtwdev, rd, path,
+						 sar_band2ch->ch_start,
+						 sar_band2ch->ch_end, power);
+
+	rtw_info(rtwdev, "set SAR power limit %u.%03u on band %u\n",
+		 power >> 3, (power & 7) * 125, band);
+
+	rtwdev->sar.source = RTW_SAR_SOURCE_VNDCMD;
+
+	return 0;
+}
+
+static int rtw_vndcmd_set_sar(struct wiphy *wiphy, struct wireless_dev *wdev,
+			      const void *data, int data_len)
+{
+	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+	struct rtw_dev *rtwdev = hw->priv;
+	struct rtw_hal *hal = &rtwdev->hal;
+	struct nlattr *tb_root[REALTEK_VNDCMD_SAR_RULE_ATTR_MAX + 1];
+	struct nlattr *tb[REALTEK_VNDCMD_SAR_RULE_ATTR_MAX + 1];
+	struct nlattr *nl_sar_rule;
+	int rem_sar_rules, r;
+	u32 band;
+	u8 power;
+
+	if (rtwdev->sar.source != RTW_SAR_SOURCE_NONE &&
+	    rtwdev->sar.source != RTW_SAR_SOURCE_VNDCMD) {
+		rtw_info(rtwdev, "SAR source 0x%x is in use", rtwdev->sar.source);
+		return -EBUSY;
+	}
+
+	r = nla_parse(tb_root, REALTEK_VNDCMD_SAR_RULE_ATTR_MAX, data, data_len,
+		      rtw_sar_rule_policy, NULL);
+	if (r) {
+		rtw_warn(rtwdev, "invalid SAR attr\n");
+		return r;
+	}
+
+	if (!tb_root[REALTEK_VNDCMD_ATTR_SAR_RULES]) {
+		rtw_warn(rtwdev, "no SAR rule attr\n");
+		return -EINVAL;
+	}
+
+	nla_for_each_nested(nl_sar_rule, tb_root[REALTEK_VNDCMD_ATTR_SAR_RULES],
+			    rem_sar_rules) {
+		r = nla_parse_nested(tb, REALTEK_VNDCMD_SAR_RULE_ATTR_MAX,
+				     nl_sar_rule, rtw_sar_rule_policy, NULL);
+		if (r)
+			return r;
+		if (!tb[REALTEK_VNDCMD_ATTR_SAR_BAND])
+			return -EINVAL;
+		if (!tb[REALTEK_VNDCMD_ATTR_SAR_POWER])
+			return -EINVAL;
+
+		band = nla_get_u32(tb[REALTEK_VNDCMD_ATTR_SAR_BAND]);
+		power = nla_get_u8(tb[REALTEK_VNDCMD_ATTR_SAR_POWER]);
+
+		r = rtw_apply_vndcmd_sar(rtwdev, band, power);
+		if (r)
+			return r;
+	}
+
+	rtw_phy_set_tx_power_level(rtwdev, hal->current_channel);
+
+	return 0;
+}
+
+static const struct wiphy_vendor_command rtw88_vendor_commands[] = {
+	{
+		.info = {
+			.vendor_id = REALTEK_NL80211_VENDOR_ID,
+			.subcmd = REALTEK_NL80211_VNDCMD_SET_SAR,
+		},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
+		.doit = rtw_vndcmd_set_sar,
+		.policy = rtw_sar_rule_policy,
+		.maxattr = REALTEK_VNDCMD_SAR_RULE_ATTR_MAX,
+	}
+};
+
+void rtw_register_vndcmd(struct ieee80211_hw *hw)
+{
+	hw->wiphy->vendor_commands = rtw88_vendor_commands;
+	hw->wiphy->n_vendor_commands = ARRAY_SIZE(rtw88_vendor_commands);
+}
diff --git a/drivers/net/wireless/realtek/rtw88/vndcmd.h b/drivers/net/wireless/realtek/rtw88/vndcmd.h
new file mode 100644
index 000000000000..c56f69f9bf36
--- /dev/null
+++ b/drivers/net/wireless/realtek/rtw88/vndcmd.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/* Copyright(c) 2018-2019  Realtek Corporation
+ */
+
+#ifndef __RTW_VNDCMD_H__
+#define __RTW_VNDCMD_H__
+
+void rtw_register_vndcmd(struct ieee80211_hw *hw);
+
+#endif /* __RTW_VNDCMD_H__ */
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 4/8] rtw88: sar: Load static SAR table from ACPI WRDS method
  2020-02-07  9:28 [PATCH 0/8] rtw88: Add SAR implementation yhchuang
                   ` (2 preceding siblings ...)
  2020-02-07  9:28 ` [PATCH 3/8] rtw88: vndcmd: sar: Apply SAR power limit via vendor command yhchuang
@ 2020-02-07  9:28 ` yhchuang
  2020-02-07  9:28 ` [PATCH 5/8] rtw88: sar: Load dynamic SAR table from ACPI methods yhchuang
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: yhchuang @ 2020-02-07  9:28 UTC (permalink / raw)
  To: kvalo; +Cc: linux-wireless, briannorris, pkshih

From: Ping-Ke Shih <pkshih@realtek.com>

ACPI WRDS method returns static SAR table that contains two chains (RF paths)
and five power limit data for each chain. The limit data are corresponding
to certain ranges of frequency, such as 2.4G band, 5.15~5.35G etc.

The data is in Q.3 notation that is the same with SAR entry function, so
we don't need to convert its quantity.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
---
 drivers/net/wireless/realtek/rtw88/Makefile |   1 +
 drivers/net/wireless/realtek/rtw88/main.c   |   2 +
 drivers/net/wireless/realtek/rtw88/sar.c    | 200 ++++++++++++++++++++
 drivers/net/wireless/realtek/rtw88/sar.h    |  10 +
 4 files changed, 213 insertions(+)
 create mode 100644 drivers/net/wireless/realtek/rtw88/sar.c
 create mode 100644 drivers/net/wireless/realtek/rtw88/sar.h

diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile
index 935333f734a9..0e141edfd174 100644
--- a/drivers/net/wireless/realtek/rtw88/Makefile
+++ b/drivers/net/wireless/realtek/rtw88/Makefile
@@ -17,6 +17,7 @@ rtw88-y += main.o \
 	   bf.o \
 	   wow.o \
 	   vndcmd.o \
+	   sar.o \
 	   regd.o
 
 rtw88-$(CONFIG_RTW88_8822BE)	+= rtw8822b.o rtw8822b_table.o
diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index 7156a06eea74..23cbb00e16b1 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -16,6 +16,7 @@
 #include "debug.h"
 #include "bf.h"
 #include "vndcmd.h"
+#include "sar.h"
 
 unsigned int rtw_fw_lps_deep_mode;
 EXPORT_SYMBOL(rtw_fw_lps_deep_mode);
@@ -1305,6 +1306,7 @@ static int rtw_chip_board_info_setup(struct rtw_dev *rtwdev)
 	rtw_load_table(rtwdev, rfe_def->txpwr_lmt_tbl);
 	rtw_phy_tx_power_by_rate_config(hal);
 	rtw_phy_tx_power_limit_config(hal);
+	rtw_sar_load_table(rtwdev);
 
 	return 0;
 }
diff --git a/drivers/net/wireless/realtek/rtw88/sar.c b/drivers/net/wireless/realtek/rtw88/sar.c
new file mode 100644
index 000000000000..f15366ce1046
--- /dev/null
+++ b/drivers/net/wireless/realtek/rtw88/sar.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/* Copyright(c) 2018-2019  Realtek Corporation
+ */
+
+#include <linux/acpi.h>
+#include "main.h"
+#include "debug.h"
+#include "phy.h"
+#include "sar.h"
+
+#define RTW_SAR_WRDS_CHAIN_NR	2
+
+enum rtw_sar_limit_index {
+	RTW_SAR_LMT_CH1_14,
+	RTW_SAR_LMT_CH36_64,
+	RTW_SAR_LMT_UND1,
+	RTW_SAR_LMT_CH100_144,
+	RTW_SAR_LMT_CH149_165,
+
+	RTW_SAR_LMT_TOTAL_NR,
+};
+
+struct rtw_sar_limits {
+	s8 limit[RTW_SAR_LMT_TOTAL_NR];
+};
+
+struct rtw_sar_wrds {
+	struct rtw_sar_limits chain[RTW_SAR_WRDS_CHAIN_NR];
+};
+
+#define ACPI_WRDS_METHOD	"WRDS"
+#define ACPI_WRDS_SIZE		sizeof(struct rtw_sar_wrds)
+#define ACPI_WRDS_TOTAL_SIZE	(sizeof(struct rtw_sar_wrds) + 2)
+#define ACPI_WIFI_DOMAIN	0x07
+
+#ifdef CONFIG_ACPI
+static union acpi_object *rtw_sar_get_acpiobj(struct rtw_dev *rtwdev,
+					      const char *method)
+{
+	struct device *dev = rtwdev->dev;
+	acpi_handle root_handle;
+	acpi_handle handle;
+	acpi_status status;
+	struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL};
+
+	/* Check device handler */
+	root_handle = ACPI_HANDLE(dev);
+	if (!root_handle) {
+		rtw_dbg(rtwdev, RTW_DBG_REGD,
+			"SAR: Could not retireve root port ACPI handle\n");
+		return NULL;
+	}
+
+	/* Get method's handler */
+	status = acpi_get_handle(root_handle, (acpi_string)method, &handle);
+	if (ACPI_FAILURE(status)) {
+		rtw_dbg(rtwdev, RTW_DBG_REGD, "SAR: %s method not found (0x%x)\n",
+			method, status);
+		return NULL;
+	}
+
+	/* Call specific method with no argument */
+	status = acpi_evaluate_object(handle, NULL, NULL, &buf);
+	if (ACPI_FAILURE(status)) {
+		rtw_dbg(rtwdev, RTW_DBG_REGD,
+			"SAR: %s invocation failed (0x%x)\n", method, status);
+		return NULL;
+	}
+
+	return buf.pointer;
+}
+
+static union acpi_object *rtw_sar_get_wifi_pkt(struct rtw_dev *rtwdev,
+					       union acpi_object *obj,
+					       u32 element_count)
+{
+	union acpi_object *wifi_pkg;
+	u32 i;
+
+	if (obj->type != ACPI_TYPE_PACKAGE ||
+	    obj->package.count < 2 ||
+	    obj->package.elements[0].type != ACPI_TYPE_INTEGER ||
+	    obj->package.elements[0].integer.value != 0) {
+		rtw_dbg(rtwdev, RTW_DBG_REGD,
+			"SAR: Unsupported wifi package structure\n");
+		return NULL;
+	}
+
+	/* loop through all the packages to find the one for WiFi */
+	for (i = 1; i < obj->package.count; i++) {
+		union acpi_object *domain;
+
+		wifi_pkg = &obj->package.elements[i];
+
+		/* Skip anything that is not a package with the right amount of
+		 * elements (i.e. domain_type, enabled/disabled plus the sar
+		 * table size.)
+		 */
+		if (wifi_pkg->type != ACPI_TYPE_PACKAGE ||
+		    wifi_pkg->package.count != element_count)
+			continue;
+
+		domain = &wifi_pkg->package.elements[0];
+		if (domain->type == ACPI_TYPE_INTEGER &&
+		    domain->integer.value == ACPI_WIFI_DOMAIN)
+			return wifi_pkg;
+	}
+
+	return NULL;
+}
+
+static void *rtw_sar_get_wrds_table(struct rtw_dev *rtwdev)
+{
+	union acpi_object *wrds, *wrds_pkg;
+	int i, idx = 2;
+	u8 *wrds_raw = NULL;
+
+	wrds = rtw_sar_get_acpiobj(rtwdev, ACPI_WRDS_METHOD);
+	if (!wrds)
+		return NULL;
+
+	wrds_pkg = rtw_sar_get_wifi_pkt(rtwdev, wrds, ACPI_WRDS_TOTAL_SIZE);
+	if (!wrds_pkg)
+		goto out;
+
+	/* WiFiSarEnable 0: ignore BIOS config; 1: use BIOS config */
+	if (wrds_pkg->package.elements[1].type != ACPI_TYPE_INTEGER ||
+	    wrds_pkg->package.elements[1].integer.value == 0)
+		goto out;
+
+	wrds_raw = kmalloc(ACPI_WRDS_SIZE, GFP_KERNEL);
+	if (!wrds_raw)
+		goto out;
+
+	/* read elements[2~11] */
+	for (i = 0; i < ACPI_WRDS_SIZE; i++) {
+		union acpi_object *entry;
+
+		entry = &wrds_pkg->package.elements[idx++];
+		if (entry->type != ACPI_TYPE_INTEGER ||
+		    entry->integer.value > U8_MAX) {
+			kfree(wrds_raw);
+			wrds_raw = NULL;
+			goto out;
+		}
+
+		wrds_raw[i] = entry->integer.value;
+	}
+out:
+	kfree(wrds);
+
+	return wrds_raw;
+}
+
+static void rtw_sar_apply_wrds(struct rtw_dev *rtwdev,
+			       const struct rtw_sar_wrds *wrds)
+{
+	int path;
+
+	for (path = 0; path < RTW_SAR_WRDS_CHAIN_NR; path++) {
+		rtw_phy_set_tx_power_sar(rtwdev, RTW_REGD_WW, path, 1, 14,
+					 wrds->chain[path].limit[RTW_SAR_LMT_CH1_14]);
+		rtw_phy_set_tx_power_sar(rtwdev, RTW_REGD_WW, path, 36, 64,
+					 wrds->chain[path].limit[RTW_SAR_LMT_CH36_64]);
+		rtw_phy_set_tx_power_sar(rtwdev, RTW_REGD_WW, path, 100, 144,
+					 wrds->chain[path].limit[RTW_SAR_LMT_CH100_144]);
+		rtw_phy_set_tx_power_sar(rtwdev, RTW_REGD_WW, path, 149, 165,
+					 wrds->chain[path].limit[RTW_SAR_LMT_CH149_165]);
+	}
+
+	rtwdev->sar.source = RTW_SAR_SOURCE_ACPI_STATIC;
+}
+
+static int rtw_sar_load_static_tables(struct rtw_dev *rtwdev)
+{
+	struct rtw_sar_wrds *wrds;
+
+	wrds = rtw_sar_get_wrds_table(rtwdev);
+	if (!wrds)
+		return -ENOENT;
+
+	rtw_dbg(rtwdev, RTW_DBG_REGD,
+		"SAR: Apply WRDS to TX power\n");
+
+	rtw_sar_apply_wrds(rtwdev, wrds);
+	kfree(wrds);
+
+	return 0;
+}
+#else
+static int rtw_sar_load_static_tables(struct rtw_dev *rtwdev)
+{
+	return -ENOENT;
+}
+#endif /* CONFIG_ACPI */
+
+void rtw_sar_load_table(struct rtw_dev *rtwdev)
+{
+	rtw_sar_load_static_tables(rtwdev);
+}
diff --git a/drivers/net/wireless/realtek/rtw88/sar.h b/drivers/net/wireless/realtek/rtw88/sar.h
new file mode 100644
index 000000000000..632de7ed58c3
--- /dev/null
+++ b/drivers/net/wireless/realtek/rtw88/sar.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/* Copyright(c) 2018-2019  Realtek Corporation
+ */
+
+#ifndef __RTW_SAR_H_
+#define __RTW_SAR_H_
+
+void rtw_sar_load_table(struct rtw_dev *rtwdev);
+
+#endif
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 5/8] rtw88: sar: Load dynamic SAR table from ACPI methods
  2020-02-07  9:28 [PATCH 0/8] rtw88: Add SAR implementation yhchuang
                   ` (3 preceding siblings ...)
  2020-02-07  9:28 ` [PATCH 4/8] rtw88: sar: Load static SAR table from ACPI WRDS method yhchuang
@ 2020-02-07  9:28 ` yhchuang
  2020-02-07  9:28 ` [PATCH 6/8] rtw88: sar: apply dynamic SAR table to tx power limit yhchuang
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: yhchuang @ 2020-02-07  9:28 UTC (permalink / raw)
  To: kvalo; +Cc: linux-wireless, briannorris, pkshih

From: Ping-Ke Shih <pkshih@realtek.com>

Three tables RWRD, RWSI and RWGS are defined to support SAR power limit
proposed by Realtek. RWRD describes main power limit values that can support
more than one mode, tablet, lid close and etc. RWSI is used to indicate
which mode is operating, so driver must apply SAR power limit corresponding
to the mode. Since each country (geography) has some different SAR power
limit values, RWGS is introduced to adjust power limit mentioned in RWRD
if stack hints driver that regulatory domain is changed.

RWRD contains customer ID, SAR enable, table count and SAR power limit.
With different customer ID, the formats of RWRD, RWSI and RWGS are
different, such as the number of fields in table and precision of power
limit value (in Q-notation). By now, two customer IDs are supported, RT
and HP. 'table count' indicates total number of tables corresponding to
operating modes, and selected by WRSI.

To validate RWSI and RWGS tables, we check if read length and sizeof() are
equal. But these checking statements depend on RWRD's ID are little
verbose, so I use two predefined values, rwsi_sz and rwgs_sz, would be easy
to understand the code.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
---
 drivers/net/wireless/realtek/rtw88/main.c |   2 +
 drivers/net/wireless/realtek/rtw88/main.h |   8 +
 drivers/net/wireless/realtek/rtw88/sar.c  | 301 ++++++++++++++++++++++
 drivers/net/wireless/realtek/rtw88/sar.h  |   1 +
 4 files changed, 312 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index 23cbb00e16b1..039703f1ccb9 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -1449,6 +1449,8 @@ void rtw_core_deinit(struct rtw_dev *rtwdev)
 		kfree(rsvd_pkt);
 	}
 
+	rtw_sar_release_table(rtwdev);
+
 	mutex_destroy(&rtwdev->mutex);
 	mutex_destroy(&rtwdev->coex.mutex);
 	mutex_destroy(&rtwdev->hal.tx_power_mutex);
diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index b4e9e18f89a5..bf5e66930424 100644
--- a/drivers/net/wireless/realtek/rtw88/main.h
+++ b/drivers/net/wireless/realtek/rtw88/main.h
@@ -46,6 +46,10 @@ extern struct rtw_chip_info rtw8822c_hw_spec;
 #define RTW_MAX_CHANNEL_NUM_5G 49
 
 struct rtw_dev;
+struct rtw_sar_rwrd;
+union rtw_sar_rwsi;
+union rtw_sar_rwgs;
+struct rtw_sar_read;
 
 enum rtw_hci_type {
 	RTW_HCI_TYPE_PCIE,
@@ -1522,6 +1526,10 @@ struct rtw_fw_state {
 
 struct rtw_sar {
 	enum rtw_sar_sources source;
+	struct rtw_sar_rwrd *rwrd;
+	union rtw_sar_rwsi *rwsi;
+	union rtw_sar_rwgs *rwgs;
+	const struct rtw_sar_read *read;
 };
 
 struct rtw_hal {
diff --git a/drivers/net/wireless/realtek/rtw88/sar.c b/drivers/net/wireless/realtek/rtw88/sar.c
index f15366ce1046..d81a6511f138 100644
--- a/drivers/net/wireless/realtek/rtw88/sar.c
+++ b/drivers/net/wireless/realtek/rtw88/sar.c
@@ -187,14 +187,315 @@ static int rtw_sar_load_static_tables(struct rtw_dev *rtwdev)
 
 	return 0;
 }
+
+#define ACPI_RWRD_METHOD	"RWRD"
+#define ACPI_RWSI_METHOD	"RWSI"
+#define ACPI_RWGS_METHOD	"RWGS"
+
+#define RTW_SAR_RWRD_ID_HP	0x5048
+#define RTW_SAR_RWRD_ID_RT	0x5452
+
+#define RTW_SAR_RWRD_CHAIN_NR	4
+
+struct rtw_sar_rwrd {
+	u16 id;
+	u8 en;
+	u8 count;
+	struct {
+		struct rtw_sar_limits chain[RTW_SAR_RWRD_CHAIN_NR];
+	} mode[0];
+} __packed;
+
+struct rtw_sar_rwsi_hp {
+	u8 index[RTW_SAR_RWRD_CHAIN_NR];
+} __packed;
+
+struct rtw_sar_rwsi_rt {
+	u8 index;
+} __packed;
+
+union rtw_sar_rwsi {
+	struct rtw_sar_rwsi_hp hp;
+	struct rtw_sar_rwsi_rt rt;
+};
+
+enum rtw_sar_rwgs_band {
+	RTW_SAR_RWGS_2G,
+	RTW_SAR_RWGS_5G,
+	RTW_SAR_RWGS_BAND_NR,
+};
+
+enum rtw_sar_rwgs_geo_hp {
+	RTW_SAR_RWGS_HP_FCC_IC,
+	RTW_SAR_RWGS_HP_ETSI_MKK,
+	RTW_SAR_RWGS_HP_WW_KCC,
+
+	RTW_SAR_RWGS_HP_NR,
+};
+
+struct rtw_sar_rwgs_hp {
+	struct {
+		struct {
+			s8 max;		/* Q1 + 10 */
+			s8 delta[4];	/* Q1 */
+		} band[RTW_SAR_RWGS_BAND_NR];
+	} geo[RTW_SAR_RWGS_HP_NR];
+} __packed;
+
+enum rtw_sar_rwgs_geo_rt {
+	RTW_SAR_RWGS_RT_FCC,
+	RTW_SAR_RWGS_RT_CE,
+	RTW_SAR_RWGS_RT_MKK,
+	RTW_SAR_RWGS_RT_IC,
+	RTW_SAR_RWGS_RT_KCC,
+	RTW_SAR_RWGS_RT_WW,
+
+	RTW_SAR_RWGS_RT_NR,
+};
+
+struct rtw_sar_rwgs_rt {
+	struct {
+		struct {
+			u8 max;		/* Q3 */
+			s8 delta;	/* Q1 */
+		} band[RTW_SAR_RWGS_BAND_NR];
+	} geo[RTW_SAR_RWGS_RT_NR];
+} __packed;
+
+union rtw_sar_rwgs {
+	struct rtw_sar_rwgs_hp hp;
+	struct rtw_sar_rwgs_rt rt;
+};
+
+struct rtw_sar_read {
+	int rwsi_sz;
+	int rwgs_sz;
+};
+
+static const struct rtw_sar_read sar_read_hp = {
+	.rwsi_sz = sizeof(struct rtw_sar_rwsi_hp),
+	.rwgs_sz = sizeof(struct rtw_sar_rwgs_hp),
+};
+
+static const struct rtw_sar_read sar_read_rt = {
+	.rwsi_sz = sizeof(struct rtw_sar_rwsi_rt),
+	.rwgs_sz = sizeof(struct rtw_sar_rwgs_rt),
+};
+
+static u8 *rtw_sar_get_raw_package(struct rtw_dev *rtwdev,
+				   union acpi_object *obj, int *len)
+{
+	u8 *raw;
+	u32 i;
+
+	if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count <= 0) {
+		rtw_dbg(rtwdev, RTW_DBG_REGD,
+			"SAR: Unsupported obj to dump\n");
+		return NULL;
+	}
+
+	raw = kmalloc(obj->package.count, GFP_KERNEL);
+	if (!raw)
+		return NULL;
+
+	for (i = 0; i < obj->package.count; i++) {
+		union acpi_object *element;
+
+		element = &obj->package.elements[i];
+
+		if (element->type != ACPI_TYPE_INTEGER) {
+			rtw_dbg(rtwdev, RTW_DBG_REGD,
+				"SAR: Unexpected element type\n");
+			kfree(raw);
+			return NULL;
+		}
+
+		raw[i] = (u8)element->integer.value;
+	}
+
+	*len = obj->package.count;
+
+	return raw;
+}
+
+static void *rtw_sar_get_raw_table(struct rtw_dev *rtwdev, const char *method,
+				   int *len)
+{
+	union acpi_object *obj;
+	u8 *raw;
+
+	obj = rtw_sar_get_acpiobj(rtwdev, method);
+	if (!obj)
+		return NULL;
+
+	raw = rtw_sar_get_raw_package(rtwdev, obj, len);
+	kfree(obj);
+
+	return raw;
+}
+
+static bool is_valid_rwrd(struct rtw_dev *rtwdev, const struct rtw_sar_rwrd *rwrd,
+			  int len)
+{
+	if (len < sizeof(*rwrd)) {
+		rtw_dbg(rtwdev, RTW_DBG_REGD,
+			"SAR: RWRD: len %d is too short\n", len);
+		return false;
+	}
+
+	switch (rwrd->id) {
+	case RTW_SAR_RWRD_ID_HP:
+		rtwdev->sar.read = &sar_read_hp;
+		break;
+	case RTW_SAR_RWRD_ID_RT:
+		rtwdev->sar.read = &sar_read_rt;
+		break;
+	default:
+		rtw_dbg(rtwdev, RTW_DBG_REGD,
+			"SAR: RWRD: ID %04x isn't supported\n", rwrd->id);
+		return false;
+	}
+
+	if (sizeof(*rwrd) + rwrd->count * sizeof(rwrd->mode[0]) != len) {
+		rtw_dbg(rtwdev, RTW_DBG_REGD,
+			"SAR: RWRD: len(%d) doesn't match count(%d)\n",
+			len, rwrd->count);
+		return false;
+	}
+
+	return true;
+}
+
+static bool is_valid_rwsi_idx(struct rtw_dev *rtwdev, const struct rtw_sar_rwrd *rwrd,
+			      const u8 index[], int len)
+{
+	/* index range is one based. i.e. 1 <= index[] <= rwrd->count */
+	int i;
+
+	for (i = 0; i < len; i++)
+		if (index[i] < 1 || index[i] > rwrd->count) {
+			rtw_dbg(rtwdev, RTW_DBG_REGD,
+				"SAR: RWSI: index is out of range\n");
+			return false;
+		}
+
+	return true;
+}
+
+static bool is_valid_rwsi(struct rtw_dev *rtwdev, const struct rtw_sar_rwrd *rwrd,
+			  const union rtw_sar_rwsi *rwsi, int len)
+{
+	const struct rtw_sar_read *r = rtwdev->sar.read;
+
+	if (r->rwsi_sz != len)
+		goto err;
+
+	if (rwrd->id == RTW_SAR_RWRD_ID_HP &&
+	    is_valid_rwsi_idx(rtwdev, rwrd, rwsi->hp.index, RTW_SAR_RWRD_CHAIN_NR))
+		return true;
+
+	if (rwrd->id == RTW_SAR_RWRD_ID_RT &&
+	    is_valid_rwsi_idx(rtwdev, rwrd, &rwsi->rt.index, 1)) {
+		return true;
+	}
+
+err:
+	rtw_dbg(rtwdev, RTW_DBG_REGD,
+		"SAR: RWSI: len doesn't match struct size\n");
+
+	return false;
+}
+
+static bool is_valid_rwgs(struct rtw_dev *rtwdev, const struct rtw_sar_rwrd *rwrd,
+			  const union rtw_sar_rwgs *rwgs, int len)
+{
+	const struct rtw_sar_read *r = rtwdev->sar.read;
+
+	if (r->rwgs_sz == len)
+		return true;
+
+	rtw_dbg(rtwdev, RTW_DBG_REGD,
+		"SAR: RWGS: len doesn't match struct size\n");
+
+	return false;
+}
+
+static int rtw_sar_load_dynamic_tables(struct rtw_dev *rtwdev)
+{
+	struct rtw_sar_rwrd *rwrd;
+	union rtw_sar_rwsi *rwsi;
+	union rtw_sar_rwgs *rwgs;
+	int len;
+	bool valid;
+
+	rwrd = rtw_sar_get_raw_table(rtwdev, ACPI_RWRD_METHOD, &len);
+	if (!rwrd)
+		goto out;
+	valid = is_valid_rwrd(rtwdev, rwrd, len);
+	if (!valid)
+		goto out_rwrd;
+	if (!rwrd->en) {
+		rtw_dbg(rtwdev, RTW_DBG_REGD, "SAR: RWRD isn't enabled\n");
+		goto out_rwrd;
+	}
+
+	rwsi = rtw_sar_get_raw_table(rtwdev, ACPI_RWSI_METHOD, &len);
+	if (!rwsi)
+		goto out_rwrd;
+	valid = is_valid_rwsi(rtwdev, rwrd, rwsi, len);
+	if (!valid)
+		goto out_rwsi;
+
+	rwgs = rtw_sar_get_raw_table(rtwdev, ACPI_RWGS_METHOD, &len);
+	if (!rwgs)
+		goto out_rwsi;
+	valid = is_valid_rwgs(rtwdev, rwrd, rwgs, len);
+	if (!valid)
+		goto out_rwgs;
+
+	rtwdev->sar.rwrd = rwrd;
+	rtwdev->sar.rwsi = rwsi;
+	rtwdev->sar.rwgs = rwgs;
+
+	rtw_dbg(rtwdev, RTW_DBG_REGD, "SAR: RWRD/RWSI/RWGS is adopted\n");
+
+	return 0;
+
+out_rwgs:
+	kfree(rwgs);
+out_rwsi:
+	kfree(rwsi);
+out_rwrd:
+	kfree(rwrd);
+out:
+	return -ENOENT;
+}
 #else
 static int rtw_sar_load_static_tables(struct rtw_dev *rtwdev)
 {
 	return -ENOENT;
 }
+
+static int rtw_sar_load_dynamic_tables(struct rtw_dev *rtwdev)
+{
+	return -ENOENT;
+}
 #endif /* CONFIG_ACPI */
 
 void rtw_sar_load_table(struct rtw_dev *rtwdev)
 {
+	int ret;
+
+	ret = rtw_sar_load_dynamic_tables(rtwdev);
+	if (!ret)
+		return;	/* if dynamic SAR table is loaded, ignore static SAR table */
+
 	rtw_sar_load_static_tables(rtwdev);
 }
+
+void rtw_sar_release_table(struct rtw_dev *rtwdev)
+{
+	kfree(rtwdev->sar.rwrd);
+	kfree(rtwdev->sar.rwsi);
+	kfree(rtwdev->sar.rwgs);
+}
diff --git a/drivers/net/wireless/realtek/rtw88/sar.h b/drivers/net/wireless/realtek/rtw88/sar.h
index 632de7ed58c3..16ceae5bf79e 100644
--- a/drivers/net/wireless/realtek/rtw88/sar.h
+++ b/drivers/net/wireless/realtek/rtw88/sar.h
@@ -6,5 +6,6 @@
 #define __RTW_SAR_H_
 
 void rtw_sar_load_table(struct rtw_dev *rtwdev);
+void rtw_sar_release_table(struct rtw_dev *rtwdev);
 
 #endif
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 6/8] rtw88: sar: apply dynamic SAR table to tx power limit
  2020-02-07  9:28 [PATCH 0/8] rtw88: Add SAR implementation yhchuang
                   ` (4 preceding siblings ...)
  2020-02-07  9:28 ` [PATCH 5/8] rtw88: sar: Load dynamic SAR table from ACPI methods yhchuang
@ 2020-02-07  9:28 ` yhchuang
  2020-02-07  9:28 ` [PATCH 7/8] rtw88: sar: add sar_work to poll if dynamic SAR table is changed yhchuang
  2020-02-07  9:28 ` [PATCH 8/8] rtw88: sar: dump sar information via debugfs yhchuang
  7 siblings, 0 replies; 10+ messages in thread
From: yhchuang @ 2020-02-07  9:28 UTC (permalink / raw)
  To: kvalo; +Cc: linux-wireless, briannorris, pkshih

From: Ping-Ke Shih <pkshih@realtek.com>

We apply four frequency ranges to calculate TX power, though RWRD defines
five ranges. RWGS is used to adjust SAR power limit value and define the
upper bound corresponding to geography.

Some sar_read::ops are added to convert to proper unit, because the units
and fields of SAR power limit for each customer ID are different.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
---
 drivers/net/wireless/realtek/rtw88/sar.c | 129 +++++++++++++++++++++++
 1 file changed, 129 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw88/sar.c b/drivers/net/wireless/realtek/rtw88/sar.c
index d81a6511f138..80b8913d1a49 100644
--- a/drivers/net/wireless/realtek/rtw88/sar.c
+++ b/drivers/net/wireless/realtek/rtw88/sar.c
@@ -267,17 +267,101 @@ union rtw_sar_rwgs {
 	struct rtw_sar_rwgs_rt rt;
 };
 
+struct rtw_sar_geo_map {
+	int idx;	/* index of rwgs.geo[] */
+	int rd;		/* RTW_REGD_xxx */
+};
+
+static const struct rtw_sar_geo_map geo_map_hp[] = {
+	{RTW_SAR_RWGS_HP_FCC_IC,   RTW_REGD_FCC},
+	{RTW_SAR_RWGS_HP_FCC_IC,   RTW_REGD_IC},
+	{RTW_SAR_RWGS_HP_ETSI_MKK, RTW_REGD_ETSI},
+	{RTW_SAR_RWGS_HP_ETSI_MKK, RTW_REGD_MKK},
+	{RTW_SAR_RWGS_HP_WW_KCC,   RTW_REGD_KCC},
+	{RTW_SAR_RWGS_HP_WW_KCC,   RTW_REGD_WW},
+};
+
+static const struct rtw_sar_geo_map geo_map_rt[] = {
+	{RTW_SAR_RWGS_RT_FCC, RTW_REGD_FCC},
+	{RTW_SAR_RWGS_RT_CE,  RTW_REGD_ETSI},
+	{RTW_SAR_RWGS_RT_MKK, RTW_REGD_MKK},
+	{RTW_SAR_RWGS_RT_IC,  RTW_REGD_IC},
+	{RTW_SAR_RWGS_RT_KCC, RTW_REGD_KCC},
+	{RTW_SAR_RWGS_RT_WW,  RTW_REGD_WW},
+};
+
 struct rtw_sar_read {
+	int (*rwsi_mode)(struct rtw_dev *rtwdev, int path);
+	int (*rwrd_base_q3)(struct rtw_dev *rtwdev, int mode, int path, int chidx);
+	int (*rwgs_delta_q3)(struct rtw_dev *rtwdev, int gi, int path, int band);
+	int (*rwgs_max_q3)(struct rtw_dev *rtwdev, int gi, int band);
+	const struct rtw_sar_geo_map *gm, *gm_end;
 	int rwsi_sz;
 	int rwgs_sz;
 };
 
+static int rwsi_mode_hp(struct rtw_dev *rtwdev, int path)
+{
+	return rtwdev->sar.rwsi->hp.index[path] - 1;
+}
+
+static int rwrd_base_q3_hp(struct rtw_dev *rtwdev, int mode, int path, int chidx)
+{
+	int sar;
+
+	sar = rtwdev->sar.rwrd->mode[mode].chain[path].limit[chidx];
+
+	return (10 << 3) + (sar << 2);
+}
+
+static int rwgs_delta_q3_hp(struct rtw_dev *rtwdev, int gi, int path, int band)
+{
+	return rtwdev->sar.rwgs->hp.geo[gi].band[band].delta[path] << 2;
+}
+
+static int rwgs_max_q3_hp(struct rtw_dev *rtwdev, int gi, int band)
+{
+	return (10 << 3) + (rtwdev->sar.rwgs->hp.geo[gi].band[band].max << 2);
+}
+
 static const struct rtw_sar_read sar_read_hp = {
+	.rwsi_mode = rwsi_mode_hp,
+	.rwrd_base_q3 = rwrd_base_q3_hp,
+	.rwgs_delta_q3 = rwgs_delta_q3_hp,
+	.rwgs_max_q3 = rwgs_max_q3_hp,
+	.gm = geo_map_hp,
+	.gm_end = geo_map_hp + ARRAY_SIZE(geo_map_hp),
 	.rwsi_sz = sizeof(struct rtw_sar_rwsi_hp),
 	.rwgs_sz = sizeof(struct rtw_sar_rwgs_hp),
 };
 
+static int rwsi_mode_rt(struct rtw_dev *rtwdev, int path)
+{
+	return rtwdev->sar.rwsi->rt.index - 1;
+}
+
+static int rwrd_base_q3_rt(struct rtw_dev *rtwdev, int mode, int path, int chidx)
+{
+	return rtwdev->sar.rwrd->mode[mode].chain[path].limit[chidx] << 3;
+}
+
+static int rwgs_delta_q3_rt(struct rtw_dev *rtwdev, int gi, int path, int band)
+{
+	return rtwdev->sar.rwgs->rt.geo[gi].band[band].delta << 2;
+}
+
+static int rwgs_max_q3_rt(struct rtw_dev *rtwdev, int gi, int band)
+{
+	return rtwdev->sar.rwgs->rt.geo[gi].band[band].max;
+}
+
 static const struct rtw_sar_read sar_read_rt = {
+	.rwsi_mode = rwsi_mode_rt,
+	.rwrd_base_q3 = rwrd_base_q3_rt,
+	.rwgs_delta_q3 = rwgs_delta_q3_rt,
+	.rwgs_max_q3 = rwgs_max_q3_rt,
+	.gm = geo_map_rt,
+	.gm_end = geo_map_rt + ARRAY_SIZE(geo_map_rt),
 	.rwsi_sz = sizeof(struct rtw_sar_rwsi_rt),
 	.rwgs_sz = sizeof(struct rtw_sar_rwgs_rt),
 };
@@ -420,6 +504,49 @@ static bool is_valid_rwgs(struct rtw_dev *rtwdev, const struct rtw_sar_rwrd *rwr
 	return false;
 }
 
+static void rtw_sar_apply_dynamic_tables(struct rtw_dev *rtwdev)
+{
+	struct rtw_hal *hal = &rtwdev->hal;
+	const struct rtw_sar_read *r = rtwdev->sar.read;
+	const struct rtw_sar_geo_map *gm = r->gm;
+	const struct rtw_sar_geo_map *gm_end = r->gm_end;
+	int path_num = min_t(int, RTW_SAR_RWRD_CHAIN_NR, hal->rf_path_num);
+	int path, mode;
+	int sar, delta, max;
+
+	for (; gm < gm_end; gm++) {
+		for (path = 0; path < path_num; path++) {
+			mode = r->rwsi_mode(rtwdev, path);
+
+			/* 2.4G part */
+			delta = r->rwgs_delta_q3(rtwdev, gm->idx, path, RTW_SAR_RWGS_2G);
+			max = r->rwgs_max_q3(rtwdev, gm->idx, RTW_SAR_RWGS_2G);
+
+			sar = r->rwrd_base_q3(rtwdev, mode, path, RTW_SAR_LMT_CH1_14);
+			sar = min(sar + delta, max);
+			rtw_phy_set_tx_power_sar(rtwdev, gm->rd, path, 1, 14, sar);
+
+			/* 5G part */
+			delta = r->rwgs_delta_q3(rtwdev, gm->idx, path, RTW_SAR_RWGS_5G);
+			max = r->rwgs_max_q3(rtwdev, gm->idx, RTW_SAR_RWGS_5G);
+
+			sar = r->rwrd_base_q3(rtwdev, mode, path, RTW_SAR_LMT_CH36_64);
+			sar = min(sar + delta, max);
+			rtw_phy_set_tx_power_sar(rtwdev, gm->rd, path, 36, 64, sar);
+
+			sar = r->rwrd_base_q3(rtwdev, mode, path, RTW_SAR_LMT_CH100_144);
+			sar = min(sar + delta, max);
+			rtw_phy_set_tx_power_sar(rtwdev, gm->rd, path, 100, 144, sar);
+
+			sar = r->rwrd_base_q3(rtwdev, mode, path, RTW_SAR_LMT_CH149_165);
+			sar = min(sar + delta, max);
+			rtw_phy_set_tx_power_sar(rtwdev, gm->rd, path, 149, 165, sar);
+		}
+	}
+
+	rtwdev->sar.source = RTW_SAR_SOURCE_ACPI_DYNAMIC;
+}
+
 static int rtw_sar_load_dynamic_tables(struct rtw_dev *rtwdev)
 {
 	struct rtw_sar_rwrd *rwrd;
@@ -457,6 +584,8 @@ static int rtw_sar_load_dynamic_tables(struct rtw_dev *rtwdev)
 	rtwdev->sar.rwsi = rwsi;
 	rtwdev->sar.rwgs = rwgs;
 
+	rtw_sar_apply_dynamic_tables(rtwdev);
+
 	rtw_dbg(rtwdev, RTW_DBG_REGD, "SAR: RWRD/RWSI/RWGS is adopted\n");
 
 	return 0;
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 7/8] rtw88: sar: add sar_work to poll if dynamic SAR table is changed
  2020-02-07  9:28 [PATCH 0/8] rtw88: Add SAR implementation yhchuang
                   ` (5 preceding siblings ...)
  2020-02-07  9:28 ` [PATCH 6/8] rtw88: sar: apply dynamic SAR table to tx power limit yhchuang
@ 2020-02-07  9:28 ` yhchuang
  2020-02-07  9:28 ` [PATCH 8/8] rtw88: sar: dump sar information via debugfs yhchuang
  7 siblings, 0 replies; 10+ messages in thread
From: yhchuang @ 2020-02-07  9:28 UTC (permalink / raw)
  To: kvalo; +Cc: linux-wireless, briannorris, pkshih

From: Ping-Ke Shih <pkshih@realtek.com>

RWSI is used to tell driver operating mode is changed. For example, a
notebook PC can also play as a tablet. Driver detects RWSI in period of
10 seconds, and reconfigure SAR power limit if RWSI values are changed.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
---
 drivers/net/wireless/realtek/rtw88/main.c |  4 ++
 drivers/net/wireless/realtek/rtw88/main.h |  1 +
 drivers/net/wireless/realtek/rtw88/sar.c  | 56 +++++++++++++++++++++++
 drivers/net/wireless/realtek/rtw88/sar.h  |  3 ++
 4 files changed, 64 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index 039703f1ccb9..bb90dce0a70d 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -892,6 +892,8 @@ int rtw_core_start(struct rtw_dev *rtwdev)
 
 	ieee80211_queue_delayed_work(rtwdev->hw, &rtwdev->watch_dog_work,
 				     RTW_WATCH_DOG_DELAY_TIME);
+	ieee80211_queue_delayed_work(rtwdev->hw, &rtwdev->sar.work,
+				     RTW_SAR_DELAY_TIME);
 
 	set_bit(RTW_FLAG_RUNNING, rtwdev->flags);
 
@@ -912,6 +914,7 @@ void rtw_core_stop(struct rtw_dev *rtwdev)
 	clear_bit(RTW_FLAG_FW_RUNNING, rtwdev->flags);
 
 	cancel_delayed_work_sync(&rtwdev->watch_dog_work);
+	cancel_delayed_work_sync(&rtwdev->sar.work);
 	cancel_delayed_work_sync(&coex->bt_relink_work);
 	cancel_delayed_work_sync(&coex->bt_reenable_work);
 	cancel_delayed_work_sync(&coex->defreeze_work);
@@ -1370,6 +1373,7 @@ int rtw_core_init(struct rtw_dev *rtwdev)
 		     (unsigned long)rtwdev);
 
 	INIT_DELAYED_WORK(&rtwdev->watch_dog_work, rtw_watch_dog_work);
+	INIT_DELAYED_WORK(&rtwdev->sar.work, rtw_sar_work);
 	INIT_DELAYED_WORK(&coex->bt_relink_work, rtw_coex_bt_relink_work);
 	INIT_DELAYED_WORK(&coex->bt_reenable_work, rtw_coex_bt_reenable_work);
 	INIT_DELAYED_WORK(&coex->defreeze_work, rtw_coex_defreeze_work);
diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index bf5e66930424..ae7a4a080cfa 100644
--- a/drivers/net/wireless/realtek/rtw88/main.h
+++ b/drivers/net/wireless/realtek/rtw88/main.h
@@ -1530,6 +1530,7 @@ struct rtw_sar {
 	union rtw_sar_rwsi *rwsi;
 	union rtw_sar_rwgs *rwgs;
 	const struct rtw_sar_read *read;
+	struct delayed_work work;
 };
 
 struct rtw_hal {
diff --git a/drivers/net/wireless/realtek/rtw88/sar.c b/drivers/net/wireless/realtek/rtw88/sar.c
index 80b8913d1a49..2bc6da4e5fcf 100644
--- a/drivers/net/wireless/realtek/rtw88/sar.c
+++ b/drivers/net/wireless/realtek/rtw88/sar.c
@@ -547,6 +547,45 @@ static void rtw_sar_apply_dynamic_tables(struct rtw_dev *rtwdev)
 	rtwdev->sar.source = RTW_SAR_SOURCE_ACPI_DYNAMIC;
 }
 
+static bool rtw_sar_is_rwsi_changed(struct rtw_dev *rtwdev)
+{
+	union rtw_sar_rwsi *rwsi, *old;
+	bool valid;
+	int len;
+
+	if (rtwdev->sar.source != RTW_SAR_SOURCE_ACPI_DYNAMIC)
+		return false;
+
+	if (!rtwdev->sar.rwrd || !rtwdev->sar.rwsi || !rtwdev->sar.rwgs)
+		return false;
+
+	rwsi = rtw_sar_get_raw_table(rtwdev, ACPI_RWSI_METHOD, &len);
+	if (!rwsi)
+		return false;
+	valid = is_valid_rwsi(rtwdev, rtwdev->sar.rwrd, rwsi, len);
+	if (!valid) {
+		kfree(rwsi);
+		return false;
+	}
+
+	if (memcmp(rwsi, rtwdev->sar.rwsi, len) == 0) {
+		kfree(rwsi);
+		return true;
+	}
+
+	old = rtwdev->sar.rwsi;
+	rtwdev->sar.rwsi = rwsi;
+	kfree(old);
+
+	rtw_dbg(rtwdev, RTW_DBG_REGD, "SAR: RWSI is changed\n");
+
+	rtw_sar_apply_dynamic_tables(rtwdev);
+
+	rtw_phy_set_tx_power_level(rtwdev, rtwdev->hal.current_channel);
+
+	return true;
+}
+
 static int rtw_sar_load_dynamic_tables(struct rtw_dev *rtwdev)
 {
 	struct rtw_sar_rwrd *rwrd;
@@ -605,6 +644,11 @@ static int rtw_sar_load_static_tables(struct rtw_dev *rtwdev)
 	return -ENOENT;
 }
 
+static bool rtw_sar_is_rwsi_changed(struct rtw_dev *rtwdev)
+{
+	return false;
+}
+
 static int rtw_sar_load_dynamic_tables(struct rtw_dev *rtwdev)
 {
 	return -ENOENT;
@@ -628,3 +672,15 @@ void rtw_sar_release_table(struct rtw_dev *rtwdev)
 	kfree(rtwdev->sar.rwsi);
 	kfree(rtwdev->sar.rwgs);
 }
+
+void rtw_sar_work(struct work_struct *work)
+{
+	struct rtw_dev *rtwdev = container_of(work, struct rtw_dev,
+					      sar.work.work);
+
+	if (!rtw_sar_is_rwsi_changed(rtwdev))
+		return;
+
+	ieee80211_queue_delayed_work(rtwdev->hw, &rtwdev->sar.work,
+				     RTW_SAR_DELAY_TIME);
+}
diff --git a/drivers/net/wireless/realtek/rtw88/sar.h b/drivers/net/wireless/realtek/rtw88/sar.h
index 16ceae5bf79e..154f7bce6759 100644
--- a/drivers/net/wireless/realtek/rtw88/sar.h
+++ b/drivers/net/wireless/realtek/rtw88/sar.h
@@ -7,5 +7,8 @@
 
 void rtw_sar_load_table(struct rtw_dev *rtwdev);
 void rtw_sar_release_table(struct rtw_dev *rtwdev);
+void rtw_sar_work(struct work_struct *work);
+
+#define RTW_SAR_DELAY_TIME	(10 * HZ)
 
 #endif
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 8/8] rtw88: sar: dump sar information via debugfs
  2020-02-07  9:28 [PATCH 0/8] rtw88: Add SAR implementation yhchuang
                   ` (6 preceding siblings ...)
  2020-02-07  9:28 ` [PATCH 7/8] rtw88: sar: add sar_work to poll if dynamic SAR table is changed yhchuang
@ 2020-02-07  9:28 ` yhchuang
  7 siblings, 0 replies; 10+ messages in thread
From: yhchuang @ 2020-02-07  9:28 UTC (permalink / raw)
  To: kvalo; +Cc: linux-wireless, briannorris, pkshih

From: Ping-Ke Shih <pkshih@realtek.com>

To know detail of SAR information, we add a debugfs entry to dump the raw
content written in ACPI, and also dump the translated data (real number in
decimal). The output looks like

  Customer ID: 0x5048
  WiFiEnable: 0x1
  Total SAR Table Count: 3
  Current SAR Table Index: (02 02 02 02)

  Dump RWRD SAR RAW DATA. (Total Count: 60)
  01: 0e 0c 0c 0c 0c 0e 0c 0c 0c 0c 0e 0c 0c 0c 0c 12 10 10 12 12
  02: 08 03 03 03 03 08 03 03 03 03 08 03 03 03 03 12 10 10 12 12
  03: 04 ff ff ff ff 04 ff ff ff ff 04 ff ff ff ff 12 10 10 12 12

  Show SAR PowerLimit:
  2.4G Antenna 0: [14.0] dBm
  2.4G Antenna 1: [14.0] dBm

  5G Antenna 0: [11.500, 11.500, 11.500, 11.500, ] dBm
  5G Antenna 1: [11.500, 11.500, 11.500, 11.500, ] dBm

  Dump Geo-SAR Table RAW DATA. (Total Count: 30)
  geo-0: 10 04 08 01 01 0f 04 0c 01 01
  geo-1: 0c 02 06 03 03 06 04 08 03 03
  geo-2: 10 03 03 03 03 0f 03 03 03 03

  Show Geo-SAR PowerLimit:
  2G Geo Table Index: 1
  5G Geo Table Index: 1

  2GHz:
  Max Power: [16.0] dBm
  Ant-0 delta value: [1.0] dB
  Ant-1 delta value: [3.0] dB

  5GHz:
  Max Power: [13.0] dBm
  Ant-0 delta value: [2.0] dB
  Ant-1 delta value: [4.0] dB

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
---
 drivers/net/wireless/realtek/rtw88/debug.c | 16 ++++
 drivers/net/wireless/realtek/rtw88/sar.c   | 92 ++++++++++++++++++++++
 drivers/net/wireless/realtek/rtw88/sar.h   |  1 +
 3 files changed, 109 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw88/debug.c b/drivers/net/wireless/realtek/rtw88/debug.c
index 3ec15a49ecc9..ee73b125ac0e 100644
--- a/drivers/net/wireless/realtek/rtw88/debug.c
+++ b/drivers/net/wireless/realtek/rtw88/debug.c
@@ -9,6 +9,7 @@
 #include "fw.h"
 #include "debug.h"
 #include "phy.h"
+#include "sar.h"
 
 #ifdef CONFIG_RTW88_DEBUGFS
 
@@ -696,6 +697,16 @@ static int rtw_debugfs_get_phy_info(struct seq_file *m, void *v)
 	return 0;
 }
 
+static int rtw_debugfs_get_sar(struct seq_file *m, void *v)
+{
+	struct rtw_debugfs_priv *debugfs_priv = m->private;
+	struct rtw_dev *rtwdev = debugfs_priv->rtwdev;
+
+	rtw_sar_dump_via_debugfs(rtwdev, m);
+
+	return 0;
+}
+
 #define rtw_debug_impl_mac(page, addr)				\
 static struct rtw_debugfs_priv rtw_debug_priv_mac_ ##page = {	\
 	.cb_read = rtw_debug_get_mac_page,			\
@@ -786,6 +797,10 @@ static struct rtw_debugfs_priv rtw_debug_priv_phy_info = {
 	.cb_read = rtw_debugfs_get_phy_info,
 };
 
+static struct rtw_debugfs_priv rtw_debug_priv_sar = {
+	.cb_read = rtw_debugfs_get_sar,
+};
+
 #define rtw_debugfs_add_core(name, mode, fopname, parent)		\
 	do {								\
 		rtw_debug_priv_ ##name.rtwdev = rtwdev;			\
@@ -816,6 +831,7 @@ void rtw_debugfs_init(struct rtw_dev *rtwdev)
 	rtw_debugfs_add_rw(dump_cam);
 	rtw_debugfs_add_rw(rsvd_page);
 	rtw_debugfs_add_r(phy_info);
+	rtw_debugfs_add_r(sar);
 	rtw_debugfs_add_r(mac_0);
 	rtw_debugfs_add_r(mac_1);
 	rtw_debugfs_add_r(mac_2);
diff --git a/drivers/net/wireless/realtek/rtw88/sar.c b/drivers/net/wireless/realtek/rtw88/sar.c
index 2bc6da4e5fcf..62689c002e25 100644
--- a/drivers/net/wireless/realtek/rtw88/sar.c
+++ b/drivers/net/wireless/realtek/rtw88/sar.c
@@ -298,6 +298,7 @@ struct rtw_sar_read {
 	const struct rtw_sar_geo_map *gm, *gm_end;
 	int rwsi_sz;
 	int rwgs_sz;
+	int rwgs_geos;
 };
 
 static int rwsi_mode_hp(struct rtw_dev *rtwdev, int path)
@@ -333,6 +334,7 @@ static const struct rtw_sar_read sar_read_hp = {
 	.gm_end = geo_map_hp + ARRAY_SIZE(geo_map_hp),
 	.rwsi_sz = sizeof(struct rtw_sar_rwsi_hp),
 	.rwgs_sz = sizeof(struct rtw_sar_rwgs_hp),
+	.rwgs_geos = RTW_SAR_RWGS_HP_NR,
 };
 
 static int rwsi_mode_rt(struct rtw_dev *rtwdev, int path)
@@ -364,6 +366,7 @@ static const struct rtw_sar_read sar_read_rt = {
 	.gm_end = geo_map_rt + ARRAY_SIZE(geo_map_rt),
 	.rwsi_sz = sizeof(struct rtw_sar_rwsi_rt),
 	.rwgs_sz = sizeof(struct rtw_sar_rwgs_rt),
+	.rwgs_geos = RTW_SAR_RWGS_RT_NR,
 };
 
 static u8 *rtw_sar_get_raw_package(struct rtw_dev *rtwdev,
@@ -504,6 +507,88 @@ static bool is_valid_rwgs(struct rtw_dev *rtwdev, const struct rtw_sar_rwrd *rwr
 	return false;
 }
 
+#ifdef CONFIG_RTW88_DEBUGFS
+void rtw_sar_dump_via_debugfs(struct rtw_dev *rtwdev, struct seq_file *m)
+{
+#define q3_int(q3)	((q3) >> 3)
+#define q3_fra(q3)	(((q3) & 0x7) * 125)
+
+	const struct rtw_sar_rwrd *rwrd = rtwdev->sar.rwrd;
+	const union rtw_sar_rwsi *rwsi = rtwdev->sar.rwsi;
+	const union rtw_sar_rwgs *rwgs = rtwdev->sar.rwgs;
+	const struct rtw_sar_read *r = rtwdev->sar.read;
+	int q3;
+	int mode;
+	int path;
+	int chidx;
+	int gi;
+	int band;
+
+	if (!rwrd || !rwsi || !rwgs || !r) {
+		seq_puts(m, "(No SAR data)\n");
+		return;
+	}
+
+	seq_printf(m, "Customer ID: 0x%04x\n", rwrd->id);
+	seq_printf(m, "WiFiEnable: 0x%x\n", rwrd->en);
+	seq_printf(m, "Total SAR Table Count: %d\n", rwrd->count);
+	seq_printf(m, "Current SAR Table Index: (%*ph)\n", r->rwsi_sz, rwsi);
+	seq_puts(m, "\n");
+
+	seq_printf(m, "Dump RWRD SAR RAW DATA. (Total Count: %ld)\n",
+		   rwrd->count * sizeof(rwrd->mode[0]));
+	for (mode = 0; mode < rwrd->count; mode++)
+		seq_printf(m, "%02x: %20ph\n", mode + 1, &rwrd->mode[mode]);
+	seq_puts(m, "\n");
+
+	seq_puts(m, "Show SAR PowerLimit:\n");
+	for (path = 0; path < 2; path++) {
+		mode = r->rwsi_mode(rtwdev, path);
+		q3 = r->rwrd_base_q3(rtwdev, mode, path, RTW_SAR_LMT_CH1_14);
+		seq_printf(m, "2.4G Antenna %d: [%d.%d] dBm\n", path,
+			   q3_int(q3), q3_fra(q3));
+	}
+	seq_puts(m, "\n");
+
+	for (path = 0; path < 2; path++) {
+		mode = r->rwsi_mode(rtwdev, path);
+		seq_printf(m, "5G Antenna %d: [", path);
+		for (chidx = RTW_SAR_LMT_CH36_64; chidx <= RTW_SAR_LMT_CH149_165;
+		     chidx++) {
+			q3 = r->rwrd_base_q3(rtwdev, mode, path, chidx);
+			seq_printf(m, "%d.%d, ", q3_int(q3), q3_fra(q3));
+		}
+		seq_puts(m, "] dBm\n");
+	}
+	seq_puts(m, "\n");
+
+	seq_printf(m, "Dump Geo-SAR Table RAW DATA. (Total Count: %d)\n",
+		   r->rwgs_sz);
+	for (gi = 0; gi < r->rwgs_geos; gi++) {
+		seq_printf(m, "geo-%d: %*ph\n", gi, r->rwgs_sz / r->rwgs_geos,
+			   (u8 *)rwgs + gi * (r->rwgs_sz / r->rwgs_geos));
+	}
+	seq_puts(m, "\n");
+
+	gi = 1;	/* take index 1 as an example */
+	seq_puts(m, "Show Geo-SAR PowerLimit:\n");
+	seq_printf(m, "2G Geo Table Index: %d\n", gi);
+	seq_printf(m, "5G Geo Table Index: %d\n", gi);
+	for (band = RTW_SAR_RWGS_2G; band < RTW_SAR_RWGS_BAND_NR; band++) {
+		seq_puts(m, "\n");
+		seq_printf(m, "%dGHz:\n", band == 0 ? 2 : 5);
+		q3 = r->rwgs_max_q3(rtwdev, gi, band);
+		seq_printf(m, "Max Power: [%d.%d] dBm\n", q3_int(q3),
+			   q3_fra(q3));
+		for (path = 0; path < 2; path++) {
+			q3 = r->rwgs_delta_q3(rtwdev, gi, path, band);
+			seq_printf(m, "Ant-%d delta value: [%d.%d] dB\n", path,
+				   q3_int(q3), q3_fra(q3));
+		}
+	}
+}
+#endif
+
 static void rtw_sar_apply_dynamic_tables(struct rtw_dev *rtwdev)
 {
 	struct rtw_hal *hal = &rtwdev->hal;
@@ -653,6 +738,13 @@ static int rtw_sar_load_dynamic_tables(struct rtw_dev *rtwdev)
 {
 	return -ENOENT;
 }
+
+#ifdef CONFIG_RTW88_DEBUGFS
+void rtw_sar_dump_via_debugfs(struct rtw_dev *rtwdev, struct seq_file *m)
+{
+	seq_puts(m, "(No SAR data)\n");
+}
+#endif
 #endif /* CONFIG_ACPI */
 
 void rtw_sar_load_table(struct rtw_dev *rtwdev)
diff --git a/drivers/net/wireless/realtek/rtw88/sar.h b/drivers/net/wireless/realtek/rtw88/sar.h
index 154f7bce6759..1f7d877a3797 100644
--- a/drivers/net/wireless/realtek/rtw88/sar.h
+++ b/drivers/net/wireless/realtek/rtw88/sar.h
@@ -8,6 +8,7 @@
 void rtw_sar_load_table(struct rtw_dev *rtwdev);
 void rtw_sar_release_table(struct rtw_dev *rtwdev);
 void rtw_sar_work(struct work_struct *work);
+void rtw_sar_dump_via_debugfs(struct rtw_dev *rtwdev, struct seq_file *m);
 
 #define RTW_SAR_DELAY_TIME	(10 * HZ)
 
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/8] rtw88: sar: add SAR of TX power limit
  2020-02-07  9:28 ` [PATCH 1/8] rtw88: sar: add SAR of TX power limit yhchuang
@ 2020-04-16  7:39   ` Kalle Valo
  0 siblings, 0 replies; 10+ messages in thread
From: Kalle Valo @ 2020-04-16  7:39 UTC (permalink / raw)
  To: yhchuang; +Cc: linux-wireless, briannorris, pkshih

<yhchuang@realtek.com> wrote:

> From: Ping-Ke Shih <pkshih@realtek.com>
> 
> Originally, there are three factors of TX power:
>  1) TX base power, calibrated manually and programmed in efuse.
>  2) By-rate power, an offset to increase power depends on TX rate.
>  3) TX limit power, an offset to contraint the max power.
> 
> So, driver can get the TX power index by using:
>     TX_POWER_IDX = tx_base_power + min(by-rate, limit)
> 
> To take SAR into consideration, we can treat it as another limit of
> the TX power. Then driver can get the TX power by using:
>     TX_POWER_IDX = tx_base_power + min(by-rate, limit, sar)
> 
> Note that the values stored in driver are not presented in dBm, so
> driver needs to also convert the power limit indexes of SAR with
> tx_scale_factor, then get the difference to the TX base power.
> rtw_phy_set_tx_power_sar() will convert the values of SAR power in unit of
> 0.125d Bm (sar_q3) and store them in tx_pwr_sar_{2,5}g[regd][path][rs][ch].
> 
> Since certain SAR tables have single one table that isn't regulatory domain
> specific, parser can apply to all 'regd' or only one domain RTW_REGD_WW
> that SAR TX power limit can be applied no matter which regulatory domain
> is selected. Because driver get 'sar' argument by
> rtw_phy_get_tx_power_limit() with rule
>     sar = is_existing(tx_pwr_sar_{2,5}g[regd]) ? tx_pwr_sar_{2,5}g[regd] :
>           tx_pwr_sar_{2,5}g[RTW_REGD_WW];
> 
> There are various sources of SAR table, but it is expected to adopt only
> one source. So, save current source of rtw_sar_sources to prevent more
> than one source are adopted.
> 
> Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
> Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>

I'll drop these from my queue now. Please resend once there's a
concensus what interface to use.

7 patches set to Changes Requested.

11370071 [1/8] rtw88: sar: add SAR of TX power limit
11370073 [3/8] rtw88: vndcmd: sar: Apply SAR power limit via vendor command
11370075 [4/8] rtw88: sar: Load static SAR table from ACPI WRDS method
11370083 [5/8] rtw88: sar: Load dynamic SAR table from ACPI methods
11370079 [6/8] rtw88: sar: apply dynamic SAR table to tx power limit
11370081 [7/8] rtw88: sar: add sar_work to poll if dynamic SAR table is changed
11370085 [8/8] rtw88: sar: dump sar information via debugfs

-- 
https://patchwork.kernel.org/patch/11370071/

https://wireless.wiki.kernel.org/en/developers/documentation/submittingpatches

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2020-04-16  7:40 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-02-07  9:28 [PATCH 0/8] rtw88: Add SAR implementation yhchuang
2020-02-07  9:28 ` [PATCH 1/8] rtw88: sar: add SAR of TX power limit yhchuang
2020-04-16  7:39   ` Kalle Valo
2020-02-07  9:28 ` [PATCH 2/8] nl80211: vendor-cmd: realtek: Add vendor command to set SAR " yhchuang
2020-02-07  9:28 ` [PATCH 3/8] rtw88: vndcmd: sar: Apply SAR power limit via vendor command yhchuang
2020-02-07  9:28 ` [PATCH 4/8] rtw88: sar: Load static SAR table from ACPI WRDS method yhchuang
2020-02-07  9:28 ` [PATCH 5/8] rtw88: sar: Load dynamic SAR table from ACPI methods yhchuang
2020-02-07  9:28 ` [PATCH 6/8] rtw88: sar: apply dynamic SAR table to tx power limit yhchuang
2020-02-07  9:28 ` [PATCH 7/8] rtw88: sar: add sar_work to poll if dynamic SAR table is changed yhchuang
2020-02-07  9:28 ` [PATCH 8/8] rtw88: sar: dump sar information via debugfs yhchuang

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.