[PATCH v4] wifi: mt76: mt7921: fix txpower reporting from rate power configuration
Sean Wang
sean.wang at kernel.org
Sat Mar 21 01:58:15 PDT 2026
Hi,
On Thu, Mar 19, 2026 at 3:39 PM Lucid Duck <lucid_duck at justthetip.ca> wrote:
>
> 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);
> +
I don't think this is the right fix.
This mixes multiple things in one patch and duplicates the same power
limit calculation in shared connac code and mt7921-specific code.
The issue is in txpower reporting. A channel context is just a view of
the current PHY configuration and should not be used to overwrite PHY
state.
I think this needs to be reworked in a cleaner way, with the shared
logic kept in shared code and the reporting side fixed in the common
mt792x path so it can apply cleanly to both mt7921 and mt7925.
Given the points above, I'd prefer not to keep iterating on the current
approach for v5. I'll try to rework this along the lines above and send
an updated version.
> 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