blob: 4829d35bf46687ea7f66956b43fa26003cdeb2db [file] [log] [blame]
From 9443665b4459780c945eff91ac20812a5336dc18 Mon Sep 17 00:00:00 2001
From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
Date: Fri, 24 Mar 2023 23:35:30 +0800
Subject: [PATCH 1001/1001] wifi: mt76: mt7996: add txpower support
Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
Change-Id: Ic3e7b17f3664fa7f774137572f885359fa2ec93b
---
mt7996/eeprom.c | 34 +++++++
mt7996/eeprom.h | 42 ++++++++
mt7996/mcu.h | 2 +
mt7996/mt7996.h | 3 +
mt7996/mtk_debugfs.c | 229 +++++++++++++++++++++++++++++++++++++++++++
mt7996/mtk_mcu.c | 23 +++++
mt7996/mtk_mcu.h | 78 +++++++++++++++
mt7996/regs.h | 17 +++-
8 files changed, 423 insertions(+), 5 deletions(-)
diff --git a/mt7996/eeprom.c b/mt7996/eeprom.c
index 7bff504..670e0db 100644
--- a/mt7996/eeprom.c
+++ b/mt7996/eeprom.c
@@ -284,3 +284,37 @@ s8 mt7996_eeprom_get_power_delta(struct mt7996_dev *dev, int band)
return val & MT_EE_RATE_DELTA_SIGN ? delta : -delta;
}
+
+const u8 mt7996_sku_group_len[] = {
+ [SKU_CCK] = 4,
+ [SKU_OFDM] = 8,
+ [SKU_HT20] = 8,
+ [SKU_HT40] = 9,
+ [SKU_VHT20] = 12,
+ [SKU_VHT40] = 12,
+ [SKU_VHT80] = 12,
+ [SKU_VHT160] = 12,
+ [SKU_HE26] = 12,
+ [SKU_HE52] = 12,
+ [SKU_HE106] = 12,
+ [SKU_HE242] = 12,
+ [SKU_HE484] = 12,
+ [SKU_HE996] = 12,
+ [SKU_HE2x996] = 12,
+ [SKU_EHT26] = 16,
+ [SKU_EHT52] = 16,
+ [SKU_EHT106] = 16,
+ [SKU_EHT242] = 16,
+ [SKU_EHT484] = 16,
+ [SKU_EHT996] = 16,
+ [SKU_EHT2x996] = 16,
+ [SKU_EHT4x996] = 16,
+ [SKU_EHT26_52] = 16,
+ [SKU_EHT26_106] = 16,
+ [SKU_EHT484_242] = 16,
+ [SKU_EHT996_484] = 16,
+ [SKU_EHT996_484_242] = 16,
+ [SKU_EHT2x996_484] = 16,
+ [SKU_EHT3x996] = 16,
+ [SKU_EHT3x996_484] = 16,
+};
diff --git a/mt7996/eeprom.h b/mt7996/eeprom.h
index f7497c9..b5852da 100644
--- a/mt7996/eeprom.h
+++ b/mt7996/eeprom.h
@@ -73,4 +73,46 @@ mt7996_get_channel_group_6g(int channel)
return DIV_ROUND_UP(channel - 29, 32);
}
+enum mt7996_sku_rate_group {
+ SKU_CCK,
+ SKU_OFDM,
+
+ SKU_HT20,
+ SKU_HT40,
+
+ SKU_VHT20,
+ SKU_VHT40,
+ SKU_VHT80,
+ SKU_VHT160,
+
+ SKU_HE26,
+ SKU_HE52,
+ SKU_HE106,
+ SKU_HE242,
+ SKU_HE484,
+ SKU_HE996,
+ SKU_HE2x996,
+
+ SKU_EHT26,
+ SKU_EHT52,
+ SKU_EHT106,
+ SKU_EHT242,
+ SKU_EHT484,
+ SKU_EHT996,
+ SKU_EHT2x996,
+ SKU_EHT4x996,
+ SKU_EHT26_52,
+ SKU_EHT26_106,
+ SKU_EHT484_242,
+ SKU_EHT996_484,
+ SKU_EHT996_484_242,
+ SKU_EHT2x996_484,
+ SKU_EHT3x996,
+ SKU_EHT3x996_484,
+
+ MAX_SKU_RATE_GROUP_NUM,
+};
+
+extern const u8 mt7996_sku_group_len[MAX_SKU_RATE_GROUP_NUM];
+
#endif
diff --git a/mt7996/mcu.h b/mt7996/mcu.h
index ebc6271..476e007 100644
--- a/mt7996/mcu.h
+++ b/mt7996/mcu.h
@@ -698,6 +698,7 @@ struct tx_power_ctrl {
bool ate_mode_enable;
bool percentage_ctrl_enable;
bool bf_backoff_enable;
+ u8 show_info_category;
u8 power_drop_level;
};
u8 band_idx;
@@ -711,6 +712,7 @@ enum {
UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL = 3,
UNI_TXPOWER_POWER_LIMIT_TABLE_CTRL = 4,
UNI_TXPOWER_ATE_MODE_CTRL = 6,
+ UNI_TXPOWER_SHOW_INFO = 7,
};
enum {
diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
index 8b76d69..d5be582 100644
--- a/mt7996/mt7996.h
+++ b/mt7996/mt7996.h
@@ -53,6 +53,8 @@
#define MT7996_CRIT_TEMP 110
#define MT7996_MAX_TEMP 120
+#define MT7996_SKU_RATE_NUM 417
+
struct mt7996_vif;
struct mt7996_sta;
struct mt7996_dfs_pulse;
@@ -518,6 +520,7 @@ int mt7996_mcu_fw_dbg_ctrl(struct mt7996_dev *dev, u32 module, u8 level);
void mt7996_mcu_rx_event(struct mt7996_dev *dev, struct sk_buff *skb);
void mt7996_mcu_exit(struct mt7996_dev *dev);
int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 data);
+int mt7996_mcu_get_tx_power_info(struct mt7996_phy *phy, u8 category, void *event);
static inline u8 mt7996_max_interface_num(struct mt7996_dev *dev)
{
diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
index 080f756..c05f846 100644
--- a/mt7996/mtk_debugfs.c
+++ b/mt7996/mtk_debugfs.c
@@ -2296,6 +2296,232 @@ static int mt7996_sta_info(struct seq_file *s, void *data)
return 0;
}
+static int
+mt7996_txpower_level_set(void *data, u64 val)
+{
+ struct mt7996_phy *phy = data;
+ int ret;
+
+ if (val > 100)
+ return -EINVAL;
+
+ ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_PERCENTAGE_CTRL, !!val);
+ if (ret)
+ return ret;
+
+ return mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_PERCENTAGE_DROP_CTRL, val);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_txpower_level, NULL,
+ mt7996_txpower_level_set, "%lld\n");
+
+static ssize_t
+mt7996_get_txpower_info(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct mt7996_phy *phy = file->private_data;
+ struct mt7996_mcu_txpower_event *event;
+ struct txpower_basic_info *basic_info;
+ static const size_t size = 2048;
+ int len = 0;
+ ssize_t ret;
+ char *buf;
+
+ buf = kzalloc(size, GFP_KERNEL);
+ event = kzalloc(sizeof(*event), GFP_KERNEL);
+ if (!buf || !event)
+ return -ENOMEM;
+
+ ret = mt7996_mcu_get_tx_power_info(phy, BASIC_INFO, event);
+ if (ret ||
+ le32_to_cpu(event->basic_info.category) != UNI_TXPOWER_BASIC_INFO)
+ goto out;
+
+ basic_info = &event->basic_info;
+
+ len += scnprintf(buf + len, size - len,
+ "======================== BASIC INFO ========================\n");
+ len += scnprintf(buf + len, size - len, " Band Index: %d, Channel Band: %d\n",
+ basic_info->band_idx, basic_info->band);
+ len += scnprintf(buf + len, size - len, " PA Type: %s\n",
+ basic_info->is_epa ? "ePA" : "iPA");
+ len += scnprintf(buf + len, size - len, " LNA Type: %s\n",
+ basic_info->is_elna ? "eLNA" : "iLNA");
+
+ len += scnprintf(buf + len, size - len,
+ "------------------------------------------------------------\n");
+ len += scnprintf(buf + len, size - len, " SKU: %s\n",
+ basic_info->sku_enable ? "enable" : "disable");
+ len += scnprintf(buf + len, size - len, " Percentage Control: %s\n",
+ basic_info->percentage_ctrl_enable ? "enable" : "disable");
+ len += scnprintf(buf + len, size - len, " Power Drop: %d [dBm]\n",
+ basic_info->power_drop_level >> 1);
+ len += scnprintf(buf + len, size - len, " Backoff: %s\n",
+ basic_info->bf_backoff_enable ? "enable" : "disable");
+ len += scnprintf(buf + len, size - len, " TX Front-end Loss: %d, %d, %d, %d\n",
+ basic_info->front_end_loss_tx[0], basic_info->front_end_loss_tx[1],
+ basic_info->front_end_loss_tx[2], basic_info->front_end_loss_tx[3]);
+ len += scnprintf(buf + len, size - len, " RX Front-end Loss: %d, %d, %d, %d\n",
+ basic_info->front_end_loss_rx[0], basic_info->front_end_loss_rx[1],
+ basic_info->front_end_loss_rx[2], basic_info->front_end_loss_rx[3]);
+ len += scnprintf(buf + len, size - len,
+ " MU TX Power Mode: %s\n",
+ basic_info->mu_tx_power_manual_enable ? "manual" : "auto");
+ len += scnprintf(buf + len, size - len,
+ " MU TX Power (Auto / Manual): %d / %d [0.5 dBm]\n",
+ basic_info->mu_tx_power_auto, basic_info->mu_tx_power_manual);
+ len += scnprintf(buf + len, size - len,
+ " Thermal Compensation: %s\n",
+ basic_info->thermal_compensate_enable ? "enable" : "disable");
+ len += scnprintf(buf + len, size - len,
+ " Theraml Compensation Value: %d\n",
+ basic_info->thermal_compensate_value);
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+out:
+ kfree(buf);
+ kfree(event);
+ return ret;
+}
+
+static const struct file_operations mt7996_txpower_info_fops = {
+ .read = mt7996_get_txpower_info,
+ .open = simple_open,
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+};
+
+#define mt7996_txpower_puts(rate) \
+({ \
+ len += scnprintf(buf + len, size - len, "%-21s:", #rate " (TMAC)"); \
+ for (i = 0; i < mt7996_sku_group_len[SKU_##rate]; i++, offs++) \
+ len += scnprintf(buf + len, size - len, " %6d", \
+ event->phy_rate_info.frame_power[offs][band_idx]); \
+ len += scnprintf(buf + len, size - len, "\n"); \
+})
+
+static ssize_t
+mt7996_get_txpower_sku(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct mt7996_phy *phy = file->private_data;
+ struct mt7996_dev *dev = phy->dev;
+ struct mt7996_mcu_txpower_event *event;
+ u8 band_idx = phy->mt76->band_idx;
+ static const size_t size = 5120;
+ int i, offs = 0, len = 0;
+ ssize_t ret;
+ char *buf;
+ u32 reg;
+
+ buf = kzalloc(size, GFP_KERNEL);
+ event = kzalloc(sizeof(*event), GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = mt7996_mcu_get_tx_power_info(phy, PHY_RATE_INFO, event);
+ if (ret ||
+ le32_to_cpu(event->phy_rate_info.category) != UNI_TXPOWER_PHY_RATE_INFO)
+ goto out;
+
+ len += scnprintf(buf + len, size - len,
+ "\nPhy %d TX Power Table (Channel %d)\n",
+ band_idx, phy->mt76->chandef.chan->hw_value);
+ len += scnprintf(buf + len, size - len, "%-21s %6s %6s %6s %6s\n",
+ " ", "1m", "2m", "5m", "11m");
+ mt7996_txpower_puts(CCK);
+
+ len += scnprintf(buf + len, size - len,
+ "%-21s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ " ", "6m", "9m", "12m", "18m", "24m", "36m", "48m",
+ "54m");
+ mt7996_txpower_puts(OFDM);
+
+ len += scnprintf(buf + len, size - len,
+ "%-21s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4",
+ "mcs5", "mcs6", "mcs7");
+ mt7996_txpower_puts(HT20);
+
+ len += scnprintf(buf + len, size - len,
+ "%-21s %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+ "mcs6", "mcs7", "mcs32");
+ mt7996_txpower_puts(HT40);
+
+ len += scnprintf(buf + len, size - len,
+ "%-21s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+ "mcs6", "mcs7", "mcs8", "mcs9", "mcs10", "mcs11");
+ mt7996_txpower_puts(VHT20);
+ mt7996_txpower_puts(VHT40);
+ mt7996_txpower_puts(VHT80);
+ mt7996_txpower_puts(VHT160);
+ mt7996_txpower_puts(HE26);
+ mt7996_txpower_puts(HE52);
+ mt7996_txpower_puts(HE106);
+ mt7996_txpower_puts(HE242);
+ mt7996_txpower_puts(HE484);
+ mt7996_txpower_puts(HE996);
+ mt7996_txpower_puts(HE2x996);
+
+ len += scnprintf(buf + len, size - len,
+ "%-21s %6s %6s %6s %6s %6s %6s %6s %6s ",
+ " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5", "mcs6", "mcs7");
+ len += scnprintf(buf + len, size - len,
+ "%6s %6s %6s %6s %6s %6s %6s %6s\n",
+ "mcs8", "mcs9", "mcs10", "mcs11", "mcs12", "mcs13", "mcs14", "mcs15");
+ mt7996_txpower_puts(EHT26);
+ mt7996_txpower_puts(EHT52);
+ mt7996_txpower_puts(EHT106);
+ mt7996_txpower_puts(EHT242);
+ mt7996_txpower_puts(EHT484);
+ mt7996_txpower_puts(EHT996);
+ mt7996_txpower_puts(EHT2x996);
+ mt7996_txpower_puts(EHT4x996);
+ mt7996_txpower_puts(EHT26_52);
+ mt7996_txpower_puts(EHT26_106);
+ mt7996_txpower_puts(EHT484_242);
+ mt7996_txpower_puts(EHT996_484);
+ mt7996_txpower_puts(EHT996_484_242);
+ mt7996_txpower_puts(EHT2x996_484);
+ mt7996_txpower_puts(EHT3x996);
+ mt7996_txpower_puts(EHT3x996_484);
+
+ len += scnprintf(buf + len, size - len, "\nePA Gain: %d\n",
+ event->phy_rate_info.epa_gain);
+ len += scnprintf(buf + len, size - len, "Max Power Bound: %d\n",
+ event->phy_rate_info.max_power_bound);
+ len += scnprintf(buf + len, size - len, "Min Power Bound: %d\n",
+ event->phy_rate_info.min_power_bound);
+
+ reg = MT_WF_PHYDFE_BAND_TPC_CTRL_STAT0(band_idx);
+ len += scnprintf(buf + len, size - len,
+ "BBP TX Power (target power from TMAC) : %6ld [0.5 dBm]\n",
+ mt76_get_field(dev, reg, MT_WF_PHY_TPC_POWER_TMAC));
+ len += scnprintf(buf + len, size - len,
+ "BBP TX Power (target power from RMAC) : %6ld [0.5 dBm]\n",
+ mt76_get_field(dev, reg, MT_WF_PHY_TPC_POWER_RMAC));
+ len += scnprintf(buf + len, size - len,
+ "BBP TX Power (TSSI module power input) : %6ld [0.5 dBm]\n",
+ mt76_get_field(dev, reg, MT_WF_PHY_TPC_POWER_TSSI));
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+out:
+ kfree(buf);
+ kfree(event);
+ return ret;
+}
+
+static const struct file_operations mt7996_txpower_sku_fops = {
+ .read = mt7996_get_txpower_sku,
+ .open = simple_open,
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+};
+
int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
{
struct mt7996_dev *dev = phy->dev;
@@ -2334,6 +2560,9 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
debugfs_create_devm_seqfile(dev->mt76.dev, "tr_info", dir,
mt7996_trinfo_read);
+ debugfs_create_file("txpower_level", 0600, dir, phy, &fops_txpower_level);
+ debugfs_create_file("txpower_info", 0600, dir, phy, &mt7996_txpower_info_fops);
+ debugfs_create_file("txpower_sku", 0600, dir, phy, &mt7996_txpower_sku_fops);
debugfs_create_devm_seqfile(dev->mt76.dev, "wtbl_info", dir,
mt7996_wtbl_read);
diff --git a/mt7996/mtk_mcu.c b/mt7996/mtk_mcu.c
index e887016..f772243 100644
--- a/mt7996/mtk_mcu.c
+++ b/mt7996/mtk_mcu.c
@@ -12,7 +12,30 @@
#ifdef CONFIG_MTK_DEBUG
+int mt7996_mcu_get_tx_power_info(struct mt7996_phy *phy, u8 category, void *event)
+{
+ struct mt7996_dev *dev = phy->dev;
+ struct tx_power_ctrl req = {
+ .tag = cpu_to_le16(UNI_TXPOWER_SHOW_INFO),
+ .len = cpu_to_le16(sizeof(req) - 4),
+ .power_ctrl_id = UNI_TXPOWER_SHOW_INFO,
+ .show_info_category = category,
+ .band_idx = phy->mt76->band_idx,
+ };
+ struct sk_buff *skb;
+ int ret;
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_WM_UNI_CMD_QUERY(TXPOWER),
+ &req, sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+ memcpy(event, skb->data, sizeof(struct mt7996_mcu_txpower_event));
+
+ dev_kfree_skb(skb);
+
+ return 0;
+}
#endif
diff --git a/mt7996/mtk_mcu.h b/mt7996/mtk_mcu.h
index e741aa2..1fa449e 100644
--- a/mt7996/mtk_mcu.h
+++ b/mt7996/mtk_mcu.h
@@ -10,6 +10,84 @@
#ifdef CONFIG_MTK_DEBUG
+struct txpower_basic_info {
+ u8 category;
+ u8 rsv1;
+
+ /* basic info */
+ u8 band_idx;
+ u8 band;
+
+ /* board type info */
+ bool is_epa;
+ bool is_elna;
+
+ /* power percentage info */
+ bool percentage_ctrl_enable;
+ u8 power_drop_level;
+
+ /* frond-end loss TX info */
+ u8 front_end_loss_tx[4];
+
+ /* frond-end loss RX info */
+ u8 front_end_loss_rx[4];
+
+ /* thermal info */
+ bool thermal_compensate_enable;
+ u8 thermal_compensate_value;
+ u8 rsv2;
+
+ /* TX power max/min limit info */
+ u8 max_power_bound;
+ u8 min_power_bound;
+
+ /* power limit info */
+ bool sku_enable;
+ bool bf_backoff_enable;
+
+ /* MU TX power info */
+ bool mu_tx_power_manual_enable;
+ u8 mu_tx_power_auto;
+ u8 mu_tx_power_manual;
+ u8 rsv3;
+};
+
+struct txpower_phy_rate_info {
+ u8 category;
+ u8 band_idx;
+ u8 band;
+ u8 epa_gain;
+
+ /* rate power info [dBm] */
+ s8 frame_power[MT7996_SKU_RATE_NUM][__MT_MAX_BAND];
+
+ /* TX power max/min limit info */
+ s8 max_power_bound;
+ s8 min_power_bound;
+ u8 rsv1;
+};
+
+struct mt7996_mcu_txpower_event {
+ u8 _rsv[4];
+
+ __le16 tag;
+ __le16 len;
+
+ union {
+ struct txpower_basic_info basic_info;
+ struct txpower_phy_rate_info phy_rate_info;
+ };
+};
+
+enum txpower_category {
+ BASIC_INFO,
+ PHY_RATE_INFO = 2,
+};
+
+enum txpower_event {
+ UNI_TXPOWER_BASIC_INFO = 0,
+ UNI_TXPOWER_PHY_RATE_INFO = 5,
+};
#endif
diff --git a/mt7996/regs.h b/mt7996/regs.h
index 0775ca5..03e8329 100644
--- a/mt7996/regs.h
+++ b/mt7996/regs.h
@@ -543,15 +543,22 @@ enum base_rev {
#define MT_PCIE1_MAC_INT_ENABLE MT_PCIE1_MAC(0x188)
-/* PHYRX CTRL */
-#define MT_WF_PHYRX_BAND_BASE 0x83080000
-#define MT_WF_PHYRX_BAND(_band, ofs) (MT_WF_PHYRX_BAND_BASE + \
+/* PHY CTRL */
+#define MT_WF_PHY_BAND_BASE 0x83080000
+#define MT_WF_PHY_BAND(_band, ofs) (MT_WF_PHY_BAND_BASE + \
((_band) << 20) + (ofs))
-#define MT_WF_PHYRX_BAND_RX_CTRL1(_band) MT_WF_PHYRX_BAND(_band, 0x2004)
+/* PHYRX CTRL */
+#define MT_WF_PHYRX_BAND_RX_CTRL1(_band) MT_WF_PHY_BAND(_band, 0x2004)
#define MT_WF_PHYRX_BAND_RX_CTRL1_IPI_EN GENMASK(2, 0)
#define MT_WF_PHYRX_BAND_RX_CTRL1_STSCNT_EN GENMASK(11, 9)
+/* PHYDFE CTRL */
+#define MT_WF_PHYDFE_BAND_TPC_CTRL_STAT0(_phy) MT_WF_PHY_BAND(_phy, 0xe7a0)
+#define MT_WF_PHY_TPC_POWER_TMAC GENMASK(15, 8)
+#define MT_WF_PHY_TPC_POWER_RMAC GENMASK(23, 16)
+#define MT_WF_PHY_TPC_POWER_TSSI GENMASK(31, 24)
+
/* PHYRX CSD */
#define MT_WF_PHYRX_CSD_BASE 0x83000000
#define MT_WF_PHYRX_CSD(_band, _wf, ofs) (MT_WF_PHYRX_CSD_BASE + \
@@ -560,7 +567,7 @@ enum base_rev {
#define MT_WF_PHYRX_CSD_IRPI(_band, _wf) MT_WF_PHYRX_CSD(_band, _wf, 0x1000)
/* PHYRX CSD BAND */
-#define MT_WF_PHYRX_CSD_BAND_RXTD12(_band) MT_WF_PHYRX_BAND(_band, 0x8230)
+#define MT_WF_PHYRX_CSD_BAND_RXTD12(_band) MT_WF_PHY_BAND(_band, 0x8230)
#define MT_WF_PHYRX_CSD_BAND_RXTD12_IRPI_SW_CLR_ONLY BIT(18)
#define MT_WF_PHYRX_CSD_BAND_RXTD12_IRPI_SW_CLR BIT(29)
--
2.18.0