blob: e1249607737f5c7f2605d3a3c4ff58a394510229 [file] [log] [blame]
From 83845703a898a65263040ded1c68dcf3734ea7c6 Mon Sep 17 00:00:00 2001
From: Ping-Ke Shih <pkshih@realtek.com>
Date: Mon, 23 Dec 2019 13:38:01 +0800
Subject: [PATCH] CHROMIUM: rtw88: vndcmd: sar: Apply SAR power limit via
vendor command
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>
(am from https://patchwork.kernel.org/patch/11370073/)
(also found at https://lkml.kernel.org/r/20200207092844.29175-4-yhchuang@realtek.com)
BUG=b:175157180
TEST=none
[brian: applied from rebase, on b/175157180]
Change-Id: I526824f73c129fa1455d5ceb655d2d35404b94ec
Signed-off-by: Brian Norris <briannorris@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/2596676
[rebase515(groeck): Context conflicts]
Signed-off-by: Guenter Roeck <groeck@google.com>
Change-Id: Iaa404e63a1455a0c4bec1b4fce994d81ff4f05ab
---
drivers/net/wireless/realtek/rtw88/Makefile | 1 +
drivers/net/wireless/realtek/rtw88/main.c | 3 +
drivers/net/wireless/realtek/rtw88/vndcmd.c | 131 ++++++++++++++++++++
drivers/net/wireless/realtek/rtw88/vndcmd.h | 10 ++
4 files changed, 145 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
--- a/drivers/net/wireless/realtek/rtw88/Makefile
+++ b/drivers/net/wireless/realtek/rtw88/Makefile
@@ -16,6 +16,7 @@ rtw88_core-y += main.o \
sec.o \
bf.o \
sar.o \
+ vndcmd.o \
regd.o
rtw88_core-$(CONFIG_PM) += wow.o
diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -18,6 +18,7 @@
#include "debug.h"
#include "bf.h"
#include "sar.h"
+#include "vndcmd.h"
bool rtw_disable_lps_deep_mode;
EXPORT_SYMBOL(rtw_disable_lps_deep_mode);
@@ -2065,6 +2066,8 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw)
return ret;
}
+ rtw_register_vndcmd(hw);
+
ret = ieee80211_register_hw(hw);
if (ret) {
rtw_err(rtwdev, "failed to register hw\n");
diff --git a/drivers/net/wireless/realtek/rtw88/vndcmd.c b/drivers/net/wireless/realtek/rtw88/vndcmd.c
new file mode 100644
--- /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
--- /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.35.0.rc0.227.g00780c9af4-goog