]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
mt76: mt7915: add bf backoff limit table support
authorSven Eckelmann (Plasma Cloud) <se@simonwunderlich.de>
Wed, 2 Jul 2025 15:00:03 +0000 (17:00 +0200)
committerHauke Mehrtens <hauke@hauke-m.de>
Sat, 4 Oct 2025 18:30:36 +0000 (20:30 +0200)
mt76 as support for generic rates power limits in the devicetree. But the
mt7915 supports beamforming and has another table for configuring the
backoff limits. These can be configured in the DT with the paths-*
properties. The path-*-bf are the ones relevant for beamforming and the
ones without -bf suffix for "traditional" path backoff.

Signed-off-by: Sven Eckelmann (Plasma Cloud) <se@simonwunderlich.de>
Link: https://github.com/openwrt/openwrt/pull/20152
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
package/kernel/mt76/patches/0002-wifi-mt76-mt7915-add-bf-backoff-limit-table-support.patch [new file with mode: 0644]

diff --git a/package/kernel/mt76/patches/0002-wifi-mt76-mt7915-add-bf-backoff-limit-table-support.patch b/package/kernel/mt76/patches/0002-wifi-mt76-mt7915-add-bf-backoff-limit-table-support.patch
new file mode 100644 (file)
index 0000000..029936c
--- /dev/null
@@ -0,0 +1,576 @@
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Mon, 5 Dec 2022 18:21:51 +0800
+Subject: wifi: mt76: mt7915: add bf backoff limit table support
+
+The commit 22b980badc0f ("mt76: add functions for parsing rate power limits
+from DT") introduced generic support for rates limits in the devicetree.
+But the mt7915 supports beamforming and has another table for configuring
+the backoff limits. These can be configured in the DT with the paths-*
+properties. The path-*-bf are the ones relevant for beamforming and the
+ones without -bf suffix for "traditional" path backoff.
+
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+Signed-off-by: Sven Eckelmann (Plasma Cloud) <se@simonwunderlich.de>
+Forwarded: https://lore.kernel.org/r/20250924-backoff-table-support-v1-3-20e50fbc59de@simonwunderlich.de
+
+--- a/debugfs.c
++++ b/debugfs.c
+@@ -93,9 +93,9 @@ void mt76_seq_puts_array(struct seq_file
+ {
+       int i;
+-      seq_printf(file, "%10s:", str);
++      seq_printf(file, "%16s:", str);
+       for (i = 0; i < len; i++)
+-              seq_printf(file, " %2d", val[i]);
++              seq_printf(file, " %4d", val[i]);
+       seq_puts(file, "\n");
+ }
+ EXPORT_SYMBOL_GPL(mt76_seq_puts_array);
+--- a/eeprom.c
++++ b/eeprom.c
+@@ -344,9 +344,10 @@ mt76_apply_array_limit(s8 *pwr, size_t p
+ static void
+ mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num,
+                            const s8 *data, size_t len, s8 target_power,
+-                           s8 nss_delta, s8 *max_power)
++                           s8 nss_delta)
+ {
+       int i, cur;
++      s8 max_power = -128;
+       if (!data)
+               return;
+@@ -357,7 +358,7 @@ mt76_apply_multi_array_limit(s8 *pwr, si
+                       break;
+               mt76_apply_array_limit(pwr + pwr_len * i, pwr_len, data + 1,
+-                                     target_power, nss_delta, max_power);
++                                     target_power, nss_delta, &max_power);
+               if (--cur > 0)
+                       continue;
+@@ -384,12 +385,16 @@ s8 mt76_get_rate_power_limits(struct mt7
+       char band;
+       size_t len;
+       s8 max_power = 0;
++      s8 max_power_backoff = -127;
+       s8 txs_delta;
++      int n_chains = hweight16(phy->chainmask);
++      s8 target_power_combine = target_power + mt76_tx_power_path_delta(n_chains);
+       if (!mcs_rates)
+               mcs_rates = 10;
+-      memset(dest, target_power, sizeof(*dest));
++      memset(dest, target_power, sizeof(*dest) - sizeof(dest->path));
++      memset(&dest->path, 0, sizeof(dest->path));
+       if (!IS_ENABLED(CONFIG_OF))
+               return target_power;
+@@ -435,12 +440,35 @@ s8 mt76_get_rate_power_limits(struct mt7
+       val = mt76_get_of_array_s8(np, "rates-mcs", &len, mcs_rates + 1);
+       mt76_apply_multi_array_limit(dest->mcs[0], ARRAY_SIZE(dest->mcs[0]),
+                                    ARRAY_SIZE(dest->mcs), val, len,
+-                                   target_power, txs_delta, &max_power);
++                                   target_power, txs_delta);
+       val = mt76_get_of_array_s8(np, "rates-ru", &len, ru_rates + 1);
+       mt76_apply_multi_array_limit(dest->ru[0], ARRAY_SIZE(dest->ru[0]),
+                                    ARRAY_SIZE(dest->ru), val, len,
+-                                   target_power, txs_delta, &max_power);
++                                   target_power, txs_delta);
++
++      max_power_backoff = max_power;
++      val = mt76_get_of_array_s8(np, "paths-cck", &len, ARRAY_SIZE(dest->path.cck));
++      mt76_apply_array_limit(dest->path.cck, ARRAY_SIZE(dest->path.cck), val,
++                             target_power_combine, txs_delta, &max_power_backoff);
++
++      val = mt76_get_of_array_s8(np, "paths-ofdm", &len, ARRAY_SIZE(dest->path.ofdm));
++      mt76_apply_array_limit(dest->path.ofdm, ARRAY_SIZE(dest->path.ofdm), val,
++                             target_power_combine, txs_delta, &max_power_backoff);
++
++      val = mt76_get_of_array_s8(np, "paths-ofdm-bf", &len, ARRAY_SIZE(dest->path.ofdm_bf));
++      mt76_apply_array_limit(dest->path.ofdm_bf, ARRAY_SIZE(dest->path.ofdm_bf), val,
++                             target_power_combine, txs_delta, &max_power_backoff);
++
++      val = mt76_get_of_array_s8(np, "paths-ru", &len, ARRAY_SIZE(dest->path.ru[0]) + 1);
++      mt76_apply_multi_array_limit(dest->path.ru[0], ARRAY_SIZE(dest->path.ru[0]),
++                                   ARRAY_SIZE(dest->path.ru), val, len,
++                                   target_power_combine, txs_delta);
++
++      val = mt76_get_of_array_s8(np, "paths-ru-bf", &len, ARRAY_SIZE(dest->path.ru_bf[0]) + 1);
++      mt76_apply_multi_array_limit(dest->path.ru_bf[0], ARRAY_SIZE(dest->path.ru_bf[0]),
++                                   ARRAY_SIZE(dest->path.ru_bf), val, len,
++                                   target_power_combine, txs_delta);
+       return max_power;
+ }
+--- a/mt76.h
++++ b/mt76.h
+@@ -1118,6 +1118,14 @@ struct mt76_power_limits {
+       s8 mcs[4][10];
+       s8 ru[7][12];
+       s8 eht[16][16];
++
++      struct {
++              s8 cck[4];
++              s8 ofdm[4];
++              s8 ofdm_bf[4];
++              s8 ru[7][10];
++              s8 ru_bf[7][10];
++      } path;
+ };
+ struct mt76_ethtool_worker_info {
+--- a/mt7915/debugfs.c
++++ b/mt7915/debugfs.c
+@@ -1008,7 +1008,7 @@ mt7915_rate_txpower_get(struct file *fil
+       if (!buf)
+               return -ENOMEM;
+-      ret = mt7915_mcu_get_txpower_sku(phy, txpwr, sizeof(txpwr));
++      ret = mt7915_mcu_get_txpower_sku(phy, txpwr, sizeof(txpwr), TX_POWER_INFO_RATE);
+       if (ret)
+               goto out;
+@@ -1118,7 +1118,7 @@ mt7915_rate_txpower_set(struct file *fil
+       mutex_lock(&dev->mt76.mutex);
+       ret = mt7915_mcu_get_txpower_sku(phy, req.txpower_sku,
+-                                       sizeof(req.txpower_sku));
++                                       sizeof(req.txpower_sku), TX_POWER_INFO_RATE);
+       if (ret)
+               goto out;
+@@ -1160,7 +1160,7 @@ out:
+       return ret ? ret : count;
+ }
+-static const struct file_operations mt7915_rate_txpower_fops = {
++static const struct file_operations mt7915_txpower_fops = {
+       .write = mt7915_rate_txpower_set,
+       .read = mt7915_rate_txpower_get,
+       .open = simple_open,
+@@ -1169,6 +1169,70 @@ static const struct file_operations mt79
+ };
+ static int
++mt7915_path_txpower_show(struct seq_file *file)
++{
++      struct mt7915_phy *phy = file->private;
++      s8 txpower[MT7915_SKU_PATH_NUM], *buf = txpower;
++      int ret;
++
++#define PATH_POWER_SHOW(_name, _len, _skip) do {                      \
++              size_t __len = (_len);                                  \
++              if (_skip) {                                            \
++                      buf -= 1;                                       \
++                      *buf = 0;                                       \
++              }                                                       \
++              mt76_seq_puts_array(file, _name, buf, __len);           \
++              buf += __len;                                           \
++      } while (0)
++
++      seq_printf(file, "\n%*c", 18, ' ');
++      seq_puts(file, "1T1S/2T1S/3T1S/4T1S/2T2S/3T2S/4T2S/3T3S/4T3S/4T4S\n");
++      ret = mt7915_mcu_get_txpower_sku(phy, txpower, sizeof(txpower),
++                                       TX_POWER_INFO_PATH);
++      if (ret)
++              return ret;
++
++      PATH_POWER_SHOW("CCK", 4, 0);
++      PATH_POWER_SHOW("OFDM", 4, 0);
++      PATH_POWER_SHOW("BF-OFDM", 4, 1);
++
++      PATH_POWER_SHOW("HT/VHT20", 10, 0);
++      PATH_POWER_SHOW("BF-HT/VHT20", 10, 1);
++      PATH_POWER_SHOW("HT/VHT40", 10, 0);
++      PATH_POWER_SHOW("BF-HT/VHT40", 10, 1);
++
++      PATH_POWER_SHOW("BW20/RU242", 10, 0);
++      PATH_POWER_SHOW("BF-BW20/RU242", 10, 1);
++      PATH_POWER_SHOW("BW40/RU484", 10, 0);
++      PATH_POWER_SHOW("BF-BW40/RU484", 10, 1);
++      PATH_POWER_SHOW("BW80/RU996", 10, 0);
++      PATH_POWER_SHOW("BF-BW80/RU996", 10, 1);
++      PATH_POWER_SHOW("BW160/RU2x996", 10, 0);
++      PATH_POWER_SHOW("BF-BW160/RU2x996", 10, 1);
++      PATH_POWER_SHOW("RU26", 10, 0);
++      PATH_POWER_SHOW("BF-RU26", 10, 0);
++      PATH_POWER_SHOW("RU52", 10, 0);
++      PATH_POWER_SHOW("BF-RU52", 10, 0);
++      PATH_POWER_SHOW("RU106", 10, 0);
++      PATH_POWER_SHOW("BF-RU106", 10, 0);
++#undef PATH_POWER_SHOW
++
++      return 0;
++}
++
++static int
++mt7915_txpower_path_show(struct seq_file *file, void *data)
++{
++      struct mt7915_phy *phy = file->private;
++
++      seq_printf(file, "\nBand %d\n", phy != &phy->dev->phy);
++
++      return mt7915_path_txpower_show(file);
++}
++
++DEFINE_SHOW_ATTRIBUTE(mt7915_txpower_path);
++
++static int
+ mt7915_twt_stats(struct seq_file *s, void *data)
+ {
+       struct mt7915_dev *dev = dev_get_drvdata(s->private);
+@@ -1254,7 +1318,9 @@ int mt7915_init_debugfs(struct mt7915_ph
+       debugfs_create_file("implicit_txbf", 0600, dir, dev,
+                           &fops_implicit_txbf);
+       debugfs_create_file("txpower_sku", 0400, dir, phy,
+-                          &mt7915_rate_txpower_fops);
++                          &mt7915_txpower_fops);
++      debugfs_create_file("txpower_path", 0400, dir, phy,
++                          &mt7915_txpower_path_fops);
+       debugfs_create_devm_seqfile(dev->mt76.dev, "twt_stats", dir,
+                                   mt7915_twt_stats);
+       debugfs_create_file("rf_regval", 0600, dir, dev, &fops_rf_regval);
+--- a/mt7915/init.c
++++ b/mt7915/init.c
+@@ -289,6 +289,8 @@ static void __mt7915_init_txpower(struct
+       int pwr_delta = mt7915_eeprom_get_power_delta(dev, sband->band);
+       struct mt76_power_limits limits;
++      phy->sku_limit_en = true;
++      phy->sku_path_en = true;
+       for (i = 0; i < sband->n_channels; i++) {
+               struct ieee80211_channel *chan = &sband->channels[i];
+               u32 target_power = 0;
+@@ -305,6 +307,11 @@ static void __mt7915_init_txpower(struct
+               target_power = mt76_get_rate_power_limits(phy->mt76, chan,
+                                                         &limits,
+                                                         target_power);
++
++              /* MT7915N can not enable Backoff table without setting value in dts */
++              if (!limits.path.ofdm[0])
++                      phy->sku_path_en = false;
++
+               target_power += path_delta;
+               target_power = DIV_ROUND_UP(target_power, 2);
+               chan->max_power = min_t(int, chan->max_reg_power,
+--- a/mt7915/main.c
++++ b/mt7915/main.c
+@@ -73,7 +73,7 @@ int mt7915_run(struct ieee80211_hw *hw)
+       if (ret)
+               goto out;
+-      ret = mt7915_mcu_set_sku_en(phy, true);
++      ret = mt7915_mcu_set_sku_en(phy);
+       if (ret)
+               goto out;
+--- a/mt7915/mcu.c
++++ b/mt7915/mcu.c
+@@ -3336,7 +3336,8 @@ int mt7915_mcu_set_txpower_frame(struct
+       int ret;
+       s8 txpower_sku[MT7915_SKU_RATE_NUM];
+-      ret = mt7915_mcu_get_txpower_sku(phy, txpower_sku, sizeof(txpower_sku));
++      ret = mt7915_mcu_get_txpower_sku(phy, txpower_sku, sizeof(txpower_sku),
++                                       TX_POWER_INFO_RATE);
+       if (ret)
+               return ret;
+@@ -3376,51 +3377,136 @@ int mt7915_mcu_set_txpower_frame(struct
+                                sizeof(req), true);
+ }
++static void
++mt7915_update_txpower(struct mt7915_phy *phy, int tx_power)
++{
++      struct mt76_phy *mphy = phy->mt76;
++      struct ieee80211_channel *chan = mphy->main_chandef.chan;
++      int chain_idx, val, e2p_power_limit = 0;
++
++      if (!chan) {
++              mphy->txpower_cur = tx_power;
++              return;
++      }
++
++      for (chain_idx = 0; chain_idx < hweight16(mphy->chainmask); chain_idx++) {
++              val = mt7915_eeprom_get_target_power(phy->dev, chan, chain_idx);
++              val += mt7915_eeprom_get_power_delta(phy->dev, chan->band);
++
++              e2p_power_limit = max_t(int, e2p_power_limit, val);
++      }
++
++      if (phy->sku_limit_en)
++              mphy->txpower_cur = min_t(int, e2p_power_limit, tx_power);
++      else
++              mphy->txpower_cur = e2p_power_limit;
++}
++
+ int mt7915_mcu_set_txpower_sku(struct mt7915_phy *phy)
+ {
++#define TX_POWER_LIMIT_TABLE_RATE     0
++#define TX_POWER_LIMIT_TABLE_PATH     1
+       struct mt7915_dev *dev = phy->dev;
+       struct mt76_phy *mphy = phy->mt76;
+       struct ieee80211_hw *hw = mphy->hw;
+-      struct mt7915_mcu_txpower_sku req = {
++      struct mt7915_sku_val {
++              u8 format_id;
++              u8 limit_type;
++              u8 band_idx;
++      } __packed hdr = {
+               .format_id = TX_POWER_LIMIT_TABLE,
++              .limit_type = TX_POWER_LIMIT_TABLE_RATE,
+               .band_idx = phy->mt76->band_idx,
+       };
+-      struct mt76_power_limits limits_array;
+-      s8 *la = (s8 *)&limits_array;
+-      int i, idx;
+-      int tx_power;
++      int i, ret, tx_power;
++      const u8 *len = mt7915_sku_group_len;
++      struct mt76_power_limits la = {};
++      struct sk_buff *skb;
+       tx_power = mt76_get_power_bound(mphy, hw->conf.power_level);
+-      tx_power = mt76_get_rate_power_limits(mphy, mphy->chandef.chan,
+-                                            &limits_array, tx_power);
+-      mphy->txpower_cur = tx_power;
+-
+-      for (i = 0, idx = 0; i < ARRAY_SIZE(mt7915_sku_group_len); i++) {
+-              u8 mcs_num, len = mt7915_sku_group_len[i];
+-              int j;
++      if (phy->sku_limit_en) {
++              tx_power = mt76_get_rate_power_limits(mphy, mphy->chandef.chan,
++                                                    &la, tx_power);
++              mt7915_update_txpower(phy, tx_power);
++      } else {
++              mt7915_update_txpower(phy, tx_power);
++              return 0;
++      }
+-              if (i >= SKU_HT_BW20 && i <= SKU_VHT_BW160) {
+-                      mcs_num = 10;
++      skb = mt76_mcu_msg_alloc(&dev->mt76, NULL,
++                               sizeof(hdr) + MT7915_SKU_RATE_NUM);
++      if (!skb)
++              return -ENOMEM;
++
++      skb_put_data(skb, &hdr, sizeof(hdr));
++      skb_put_data(skb, &la.cck, len[SKU_CCK] + len[SKU_OFDM]);
++      skb_put_data(skb, &la.mcs[0], len[SKU_HT_BW20]);
++      skb_put_data(skb, &la.mcs[1], len[SKU_HT_BW40]);
++
++      /* vht */
++      for (i = 0; i < 4; i++) {
++              skb_put_data(skb, &la.mcs[i], sizeof(la.mcs[i]));
++              skb_put_zero(skb, 2);  /* padding */
++      }
+-                      if (i == SKU_HT_BW20 || i == SKU_VHT_BW20)
+-                              la = (s8 *)&limits_array + 12;
+-              } else {
+-                      mcs_num = len;
+-              }
++      /* he */
++      skb_put_data(skb, &la.ru[0], sizeof(la.ru));
++      ret = mt76_mcu_skb_send_msg(&dev->mt76, skb,
++                                  MCU_EXT_CMD(TX_POWER_FEATURE_CTRL), true);
++      if (ret)
++              return ret;
++
++      /* only set per-path power table when it's configured */
++      if (!phy->sku_path_en)
++              return 0;
++
++      skb = mt76_mcu_msg_alloc(&dev->mt76, NULL,
++                               sizeof(hdr) + MT7915_SKU_PATH_NUM);
++      if (!skb)
++              return -ENOMEM;
++
++      hdr.limit_type = TX_POWER_LIMIT_TABLE_PATH;
++      skb_put_data(skb, &hdr, sizeof(hdr));
++      skb_put_data(skb, &la.path.cck, sizeof(la.path.cck));
++      skb_put_data(skb, &la.path.ofdm, sizeof(la.path.ofdm));
++      skb_put_data(skb, &la.path.ofdm_bf[1], sizeof(la.path.ofdm_bf) - 1);
++
++      /* HT20 and HT40 */
++      skb_put_data(skb, &la.path.ru[3], sizeof(la.path.ru[3]));
++      skb_put_data(skb, &la.path.ru_bf[3][1], sizeof(la.path.ru_bf[3]) - 1);
++      skb_put_data(skb, &la.path.ru[4], sizeof(la.path.ru[4]));
++      skb_put_data(skb, &la.path.ru_bf[4][1], sizeof(la.path.ru_bf[4]) - 1);
++
++      /* start from non-bf and bf fields of
++       * BW20/RU242, BW40/RU484, BW80/RU996, BW160/RU2x996,
++       * RU26, RU52, and RU106
++       */
++
++      for (i = 0; i < 8; i++) {
++              bool bf = i % 2;
++              u8 idx = (i + 6) / 2;
++              s8 *buf = bf ? la.path.ru_bf[idx] : la.path.ru[idx];
++              /* The non-bf fields of RU26 to RU106 are special cases */
++              if (bf)
++                      skb_put_data(skb, buf + 1, 9);
++              else
++                      skb_put_data(skb, buf, 10);
++      }
+-              for (j = 0; j < min_t(u8, mcs_num, len); j++)
+-                      req.txpower_sku[idx + j] = la[j];
++      for (i = 0; i < 6; i++) {
++              bool bf = i % 2;
++              u8 idx = i / 2;
++              s8 *buf = bf ? la.path.ru_bf[idx] : la.path.ru[idx];
+-              la += mcs_num;
+-              idx += len;
++              skb_put_data(skb, buf, 10);
+       }
+-      return mt76_mcu_send_msg(&dev->mt76,
+-                               MCU_EXT_CMD(TX_POWER_FEATURE_CTRL), &req,
+-                               sizeof(req), true);
++      return mt76_mcu_skb_send_msg(&dev->mt76, skb,
++                                   MCU_EXT_CMD(TX_POWER_FEATURE_CTRL), true);
+ }
+-int mt7915_mcu_get_txpower_sku(struct mt7915_phy *phy, s8 *txpower, int len)
++int mt7915_mcu_get_txpower_sku(struct mt7915_phy *phy, s8 *txpower, int len,
++                             u8 category)
+ {
+ #define RATE_POWER_INFO       2
+       struct mt7915_dev *dev = phy->dev;
+@@ -3431,10 +3517,9 @@ int mt7915_mcu_get_txpower_sku(struct mt
+               u8 _rsv;
+       } __packed req = {
+               .format_id = TX_POWER_LIMIT_INFO,
+-              .category = RATE_POWER_INFO,
++              .category = category,
+               .band_idx = phy->mt76->band_idx,
+       };
+-      s8 txpower_sku[MT7915_SKU_RATE_NUM][2];
+       struct sk_buff *skb;
+       int ret, i;
+@@ -3444,9 +3529,15 @@ int mt7915_mcu_get_txpower_sku(struct mt
+       if (ret)
+               return ret;
+-      memcpy(txpower_sku, skb->data + 4, sizeof(txpower_sku));
+-      for (i = 0; i < len; i++)
+-              txpower[i] = txpower_sku[i][req.band_idx];
++      if (category == TX_POWER_INFO_RATE) {
++              s8 res[MT7915_SKU_RATE_NUM][2];
++
++              memcpy(res, skb->data + 4, sizeof(res));
++              for (i = 0; i < len; i++)
++                      txpower[i] = res[i][req.band_idx];
++      } else if (category == TX_POWER_INFO_PATH) {
++              memcpy(txpower, skb->data + 4, len);
++      }
+       dev_kfree_skb(skb);
+@@ -3475,7 +3566,7 @@ int mt7915_mcu_set_test_param(struct mt7
+                                sizeof(req), false);
+ }
+-int mt7915_mcu_set_sku_en(struct mt7915_phy *phy, bool enable)
++int mt7915_mcu_set_sku_en(struct mt7915_phy *phy)
+ {
+       struct mt7915_dev *dev = phy->dev;
+       struct mt7915_sku {
+@@ -3484,10 +3575,21 @@ int mt7915_mcu_set_sku_en(struct mt7915_
+               u8 band_idx;
+               u8 rsv;
+       } __packed req = {
+-              .format_id = TX_POWER_LIMIT_ENABLE,
+               .band_idx = phy->mt76->band_idx,
+-              .sku_enable = enable,
+       };
++      int ret;
++
++      req.sku_enable = phy->sku_limit_en;
++      req.format_id = TX_POWER_LIMIT_ENABLE;
++
++      ret = mt76_mcu_send_msg(&dev->mt76,
++                              MCU_EXT_CMD(TX_POWER_FEATURE_CTRL), &req,
++                              sizeof(req), true);
++      if (ret)
++              return ret;
++
++      req.sku_enable = phy->sku_path_en;
++      req.format_id = TX_POWER_LIMIT_PATH_ENABLE;
+       return mt76_mcu_send_msg(&dev->mt76,
+                                MCU_EXT_CMD(TX_POWER_FEATURE_CTRL), &req,
+--- a/mt7915/mcu.h
++++ b/mt7915/mcu.h
+@@ -429,6 +429,7 @@ enum {
+ enum {
+       TX_POWER_LIMIT_ENABLE,
++      TX_POWER_LIMIT_PATH_ENABLE = 0x3,
+       TX_POWER_LIMIT_TABLE = 0x4,
+       TX_POWER_LIMIT_INFO = 0x7,
+       TX_POWER_LIMIT_FRAME = 0x11,
+@@ -436,6 +437,11 @@ enum {
+ };
+ enum {
++      TX_POWER_INFO_PATH = 1,
++      TX_POWER_INFO_RATE,
++};
++
++enum {
+       SPR_ENABLE = 0x1,
+       SPR_ENABLE_SD = 0x3,
+       SPR_ENABLE_MODE = 0x5,
+--- a/mt7915/mt7915.h
++++ b/mt7915/mt7915.h
+@@ -70,6 +70,7 @@
+ #define MT7915_CDEV_THROTTLE_MAX      99
+ #define MT7915_SKU_RATE_NUM           161
++#define MT7915_SKU_PATH_NUM           185
+ #define MT7915_MAX_TWT_AGRT           16
+ #define MT7915_MAX_STA_TWT_AGRT               8
+@@ -223,6 +224,9 @@ struct mt7915_phy {
+       struct mt76_mib_stats mib;
+       struct mt76_channel_state state_ts;
++      bool sku_limit_en:1;
++      bool sku_path_en:1;
++
+ #ifdef CONFIG_NL80211_TESTMODE
+       struct {
+               u32 *reg_backup;
+@@ -491,9 +495,10 @@ int mt7915_mcu_set_mac(struct mt7915_dev
+ int mt7915_mcu_set_test_param(struct mt7915_dev *dev, u8 param, bool test_mode,
+                             u8 en);
+ int mt7915_mcu_set_ser(struct mt7915_dev *dev, u8 action, u8 set, u8 band);
+-int mt7915_mcu_set_sku_en(struct mt7915_phy *phy, bool enable);
++int mt7915_mcu_set_sku_en(struct mt7915_phy *phy);
+ int mt7915_mcu_set_txpower_sku(struct mt7915_phy *phy);
+-int mt7915_mcu_get_txpower_sku(struct mt7915_phy *phy, s8 *txpower, int len);
++int mt7915_mcu_get_txpower_sku(struct mt7915_phy *phy, s8 *txpower, int len,
++                             u8 category);
+ int mt7915_mcu_set_txpower_frame_min(struct mt7915_phy *phy, s8 txpower);
+ int mt7915_mcu_set_txpower_frame(struct mt7915_phy *phy,
+                                struct ieee80211_vif *vif,
+--- a/mt7915/testmode.c
++++ b/mt7915/testmode.c
+@@ -409,7 +409,7 @@ mt7915_tm_init(struct mt7915_phy *phy, b
+       if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state))
+               return;
+-      mt7915_mcu_set_sku_en(phy, !en);
++      mt7915_mcu_set_sku_en(phy);
+       mt7915_tm_mode_ctrl(dev, en);
+       mt7915_tm_reg_backup_restore(phy);