wifi: mwifiex: add rgpower table loading support

Marvell/NXP Wi-Fi adapters allow fine-grained adjustment of the transmit
power levels and various other internal parameters. This is done by
sending command streams to the adapter. One storage format of these
command streams are the rgpower tables, which consist of multiple
command blocks in the following format:

command_block_1 = {
XX XX LL LL XX XX ..
}
command_block_n = {
XX XX LL LL XX XX XX ..
}

XX = raw byte as hex chars
LL = total length of the "raw" command block

These command blocks are parsed into their binary representation and
then send to the adapter. The parsing logic was adapted from NXP's
mwifiex driver[1].

The rgpower tables matching the currently set regulatory domain are
automatically requested and applied. If not found the existing device
tree provided power tables are tried as well.

[1]:
7a8beaa160/mlan/mlan_cmdevt.c (L812)

Signed-off-by: Stefan Kerkmann <s.kerkmann@pengutronix.de>
Link: https://patch.msgid.link/20250804-feature-mwifiex-rgpower-table-loading-v1-1-358e70a4d45e@pengutronix.de
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
pull/1354/merge
Stefan Kerkmann 2025-08-04 15:58:27 +02:00 committed by Johannes Berg
parent f90caeba1d
commit 7b6f16a258
4 changed files with 180 additions and 1 deletions

View File

@ -494,6 +494,11 @@ static void mwifiex_free_adapter(struct mwifiex_adapter *adapter)
return;
}
if (adapter->rgpower_data) {
release_firmware(adapter->rgpower_data);
adapter->rgpower_data = NULL;
}
mwifiex_unregister(adapter);
pr_debug("info: %s: free adapter\n", __func__);
}

View File

@ -982,6 +982,7 @@ struct mwifiex_adapter {
u8 country_code[IEEE80211_COUNTRY_STRING_LEN];
u16 max_mgmt_ie_index;
const struct firmware *cal_data;
const struct firmware *rgpower_data;
struct device_node *dt_node;
/* 11AC */
@ -1579,6 +1580,8 @@ int mwifiex_11h_handle_event_chanswann(struct mwifiex_private *priv);
int mwifiex_dnld_dt_cfgdata(struct mwifiex_private *priv,
struct device_node *node, const char *prefix);
void mwifiex_dnld_txpwr_table(struct mwifiex_private *priv);
int mwifiex_send_rgpower_table(struct mwifiex_private *priv, const u8 *data,
const size_t size);
extern const struct ethtool_ops mwifiex_ethtool_ops;

View File

@ -1483,6 +1483,121 @@ int mwifiex_dnld_dt_cfgdata(struct mwifiex_private *priv,
return 0;
}
static int mwifiex_rgpower_table_advance_to_content(u8 **pos, const u8 *data,
const size_t size)
{
while (*pos - data < size) {
/* skip spaces, tabs and empty lines */
if (**pos == '\r' || **pos == '\n' || **pos == '\0' ||
isspace(**pos)) {
(*pos)++;
continue;
}
/* skip line comments */
if (**pos == '#') {
*pos = strchr(*pos, '\n');
if (!*pos)
return -EINVAL;
(*pos)++;
continue;
}
return 0;
}
return 0;
}
int mwifiex_send_rgpower_table(struct mwifiex_private *priv, const u8 *data,
const size_t size)
{
int ret = 0;
bool start_raw = false;
u8 *ptr, *token, *pos = NULL;
u8 *_data __free(kfree) = NULL;
struct mwifiex_adapter *adapter = priv->adapter;
struct mwifiex_ds_misc_cmd *hostcmd __free(kfree) = NULL;
hostcmd = kzalloc(sizeof(*hostcmd), GFP_KERNEL);
if (!hostcmd)
return -ENOMEM;
_data = kmemdup(data, size, GFP_KERNEL);
if (!_data) {
kfree(hostcmd);
return -ENOMEM;
}
pos = _data;
ptr = hostcmd->cmd;
while ((pos - _data) < size) {
ret = mwifiex_rgpower_table_advance_to_content(&pos, _data, size);
if (ret) {
mwifiex_dbg(
adapter, ERROR,
"%s: failed to advance to content in rgpower table\n",
__func__);
return ret;
}
if (*pos == '}' && start_raw) {
memcpy(&hostcmd->len, &hostcmd->cmd[2], sizeof(u16));
ret = mwifiex_send_cmd(priv, 0, 0, 0, hostcmd, false);
if (ret) {
mwifiex_dbg(adapter, ERROR,
"%s: failed to send hostcmd %d\n",
__func__, ret);
return ret;
}
memset(hostcmd->cmd, 0, MWIFIEX_SIZE_OF_CMD_BUFFER);
ptr = hostcmd->cmd;
start_raw = false;
pos++;
continue;
}
if (!start_raw) {
pos = strchr(pos, '=');
if (pos) {
pos = strchr(pos, '{');
if (pos) {
start_raw = true;
pos++;
continue;
}
}
mwifiex_dbg(adapter, ERROR,
"%s: syntax error in hostcmd\n", __func__);
return -EINVAL;
}
if (start_raw) {
while ((*pos != '\r' && *pos != '\n') &&
(token = strsep((char **)&pos, " "))) {
if (ptr - hostcmd->cmd >=
MWIFIEX_SIZE_OF_CMD_BUFFER) {
mwifiex_dbg(
adapter, ERROR,
"%s: hostcmd is larger than %d, aborting\n",
__func__, MWIFIEX_SIZE_OF_CMD_BUFFER);
return -ENOMEM;
}
ret = kstrtou8(token, 16, ptr);
if (ret < 0) {
mwifiex_dbg(
adapter, ERROR,
"%s: failed to parse hostcmd %d token: %s\n",
__func__, ret, token);
return ret;
}
ptr++;
}
}
}
return ret;
}
/* This function prepares command of set_cfg_data. */
static int mwifiex_cmd_cfg_data(struct mwifiex_private *priv,
struct host_cmd_ds_command *cmd, void *data_buf)

View File

@ -180,7 +180,7 @@ int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv,
return mwifiex_update_bss_desc_with_ie(priv->adapter, bss_desc);
}
void mwifiex_dnld_txpwr_table(struct mwifiex_private *priv)
static void mwifiex_dnld_dt_txpwr_table(struct mwifiex_private *priv)
{
if (priv->adapter->dt_node) {
char txpwr[] = {"marvell,00_txpwrlimit"};
@ -190,6 +190,62 @@ void mwifiex_dnld_txpwr_table(struct mwifiex_private *priv)
}
}
static int mwifiex_request_rgpower_table(struct mwifiex_private *priv)
{
struct mwifiex_802_11d_domain_reg *domain_info = &priv->adapter->domain_reg;
struct mwifiex_adapter *adapter = priv->adapter;
char rgpower_table_name[30];
char country_code[3];
strscpy(country_code, domain_info->country_code, sizeof(country_code));
/* World regulatory domain "00" has WW as country code */
if (strncmp(country_code, "00", 2) == 0)
strscpy(country_code, "WW", sizeof(country_code));
snprintf(rgpower_table_name, sizeof(rgpower_table_name),
"nxp/rgpower_%s.bin", country_code);
mwifiex_dbg(adapter, INFO, "info: %s: requesting regulatory power table %s\n",
__func__, rgpower_table_name);
if (adapter->rgpower_data) {
release_firmware(adapter->rgpower_data);
adapter->rgpower_data = NULL;
}
if ((request_firmware(&adapter->rgpower_data, rgpower_table_name,
adapter->dev))) {
mwifiex_dbg(
adapter, INFO,
"info: %s: failed to request regulatory power table\n",
__func__);
return -EIO;
}
return 0;
}
static int mwifiex_dnld_rgpower_table(struct mwifiex_private *priv)
{
int ret;
ret = mwifiex_request_rgpower_table(priv);
if (ret)
return ret;
return mwifiex_send_rgpower_table(priv, priv->adapter->rgpower_data->data,
priv->adapter->rgpower_data->size);
}
void mwifiex_dnld_txpwr_table(struct mwifiex_private *priv)
{
if (mwifiex_dnld_rgpower_table(priv) == 0)
return;
mwifiex_dnld_dt_txpwr_table(priv);
}
static int mwifiex_process_country_ie(struct mwifiex_private *priv,
struct cfg80211_bss *bss)
{