[PATCH v4] wifi: mt76: mt7921: fix txpower reporting from rate power configuration
Lucid Duck
lucid_duck at justthetip.ca
Thu Mar 19 13:38:54 PDT 2026
The mt7921 driver never updates phy->txpower_cur from the rate power
configuration sent to firmware, causing mt76_get_txpower() to report
bogus values to userspace (typically 3 dBm) regardless of actual
regulatory or SAR limits.
Two issues are addressed:
1. The rate power loop in mt76_connac_mcu_rate_txpower_band() computes
the correct bounded TX power per channel via
mt76_get_rate_power_limits() but discards the return value. Capture
it and store to phy->txpower_cur when processing the current
channel, matching how mt7915 handles this in
mt7915_mcu_set_txpower_sku(). Subtract the multi-chain path delta
before storing, since mt76_get_txpower() adds it back when
reporting -- consistent with mt7915's use of mt76_get_power_bound()
which performs the same subtraction.
2. mt7921 uses the chanctx model but its add_chanctx callback does not
update phy->chandef, leaving it stale after association. The rate
power loop's channel comparison then fails silently. Sync
phy->chandef from ctx->def in add_chanctx and change_chanctx, and
recompute txpower_cur via a lightweight helper that performs the
same bounded power calculation for the current channel without
reprogramming firmware rate tables.
Tested on Alfa AWUS036AXML (MT7921AU), kernel 6.19.8, Canada:
Before: iw dev wlan0 info shows "txpower 3.00 dBm" (wrong)
After: 2.4GHz 36 dBm, 5GHz 23 dBm, 6GHz 12 dBm (match regulatory)
Cc: stable at vger.kernel.org
Fixes: 1c099ab44727 ("mt76: mt7921: add MAC support")
Signed-off-by: Lucid Duck <lucid_duck at justthetip.ca>
---
Changes since v3:
- Removed mt7921_set_tx_sar_pwr() from add_chanctx and change_chanctx.
Channel transitions don't change underlying power constraints, so
reprogramming the full rate table is unnecessary. Replaced with a
lightweight helper that recomputes txpower_cur locally.
- Removed IEEE80211_CONF_CHANGE_CHANNEL trigger from config().
- Removed BSS_CHANGED_TXPOWER handler from bss_info_changed(). Writing
per-vif txpower into per-HW hw->conf.power_level breaks multi-vif
semantics. User txpower limits need a different approach (follow-up).
- Subtracted path delta before storing txpower_cur. The connac rate
loop stores total bounded power, but mt76_get_txpower() adds the
multi-chain path delta when reporting. mt7915 accounts for this via
mt76_get_power_bound(), which subtracts the delta before storing.
Without the same subtraction, reported values were inflated by 3 dBm
on 2x2 devices.
.../wireless/mediatek/mt76/mt76_connac_mcu.c | 14 +++++++--
.../net/wireless/mediatek/mt76/mt7921/main.c | 30 +++++++++++++++++++
2 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
index 16db0f208..e26a2cb39 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
@@ -2193,14 +2193,22 @@ mt76_connac_mcu_rate_txpower_band(struct mt76_phy *phy,
.hw_value = ch_list[idx],
.band = band,
};
- s8 reg_power, sar_power;
+ s8 reg_power, sar_power, max_power;
reg_power = mt76_connac_get_ch_power(phy, &chan,
tx_power);
sar_power = mt76_get_sar_power(phy, &chan, reg_power);
- mt76_get_rate_power_limits(phy, &chan, limits,
- sar_power);
+ max_power = mt76_get_rate_power_limits(phy, &chan,
+ limits,
+ sar_power);
+
+ if (phy->chandef.chan &&
+ phy->chandef.chan->hw_value == ch_list[idx] &&
+ phy->chandef.chan->band == band)
+ phy->txpower_cur = max_power -
+ mt76_tx_power_path_delta(
+ hweight16(phy->chainmask));
tx_power_tlv.last_msg = ch_list[idx] == last_ch;
sku_tlbv.channel = ch_list[idx];
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 5881040ac..a77ae5791 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -1355,13 +1355,39 @@ mt7921_stop_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
mt792x_mutex_release(dev);
}
+static void mt7921_update_txpower_cur(struct mt76_phy *phy)
+{
+ struct mt76_power_limits limits;
+ struct ieee80211_channel *chan = phy->chandef.chan;
+ int n_chains = hweight16(phy->chainmask);
+ s8 reg_power, sar_power, max_power;
+ int tx_power;
+
+ if (!chan)
+ return;
+
+ tx_power = 2 * phy->hw->conf.power_level;
+ if (!tx_power)
+ tx_power = 127;
+
+ reg_power = mt76_connac_get_ch_power(phy, chan, tx_power);
+ sar_power = mt76_get_sar_power(phy, chan, reg_power);
+ max_power = mt76_get_rate_power_limits(phy, chan, &limits, sar_power);
+
+ phy->txpower_cur = max_power - mt76_tx_power_path_delta(n_chains);
+}
+
static int
mt7921_add_chanctx(struct ieee80211_hw *hw,
struct ieee80211_chanctx_conf *ctx)
{
struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ struct mt76_phy *mphy = hw->priv;
dev->new_ctx = ctx;
+ mphy->chandef = ctx->def;
+ mt7921_update_txpower_cur(mphy);
+
return 0;
}
@@ -1396,6 +1422,10 @@ mt7921_change_chanctx(struct ieee80211_hw *hw,
mt7921_mcu_config_sniffer(mvif, ctx);
else
mt76_connac_mcu_uni_set_chctx(mvif->phy->mt76, &mvif->bss_conf.mt76, ctx);
+
+ phy->mt76->chandef = ctx->def;
+ mt7921_update_txpower_cur(phy->mt76);
+
mt792x_mutex_release(phy->dev);
}
--
2.53.0
More information about the Linux-mediatek
mailing list