Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH mt76-next 25/29] wifi: mt76: mt7996: free vif links after clearing wcid entries on full reset
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>

mt7996_mac_reset_vif_iter() queues non-default vif links for kfree_rcu
while dev->wcid[] still holds pointers to the wcid embedded in each
freed link; mt76_reset_device() then dereferences those entries and
runs mt76_wcid_cleanup() on them. If a grace period elapses in between,
the cleanup operates on freed memory.

Run mt76_reset_device() first, so the wcid entries are cleaned up and
cleared while the links are still valid.

Fixes: ace5d3b6b49e ("wifi: mt76: mt7996: improve hardware restart reliability")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
 drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index 544da01b0411..ce8c5a5ea8cc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -2485,10 +2485,10 @@ mt7996_mac_full_reset(struct mt7996_dev *dev)
 		phy->omac_mask = 0;
 
 	ieee80211_iterate_stations_atomic(hw, mt7996_mac_reset_sta_iter, dev);
+	mt76_reset_device(&dev->mt76);
 	ieee80211_iterate_active_interfaces_atomic(hw,
 						   IEEE80211_IFACE_SKIP_SDATA_NOT_IN_DRIVER,
 						   mt7996_mac_reset_vif_iter, dev);
-	mt76_reset_device(&dev->mt76);
 
 	INIT_LIST_HEAD(&dev->sta_rc_list);
 	INIT_LIST_HEAD(&dev->twt_list);
-- 
2.53.0


^ permalink raw reply related

* [PATCH mt76-next 24/29] mt76: mt7915: trigger L1 SER on PLE MDP RIOC hang
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>

From: Chad Monroe <chad@monroe.io>

WM firmware can enter a partial failure state where RX is hung.

Detect this condition by monitoring SER_PLE_ERR_1 for MDP_RIOC_HANG_ERR
and trigger L1 SER to restore operation. Use transition detection on the
error bit to fire only once per new occurrence, preventing an infinite
SER loop when the bit remains set across checks.

Signed-off-by: Chad Monroe <chad@monroe.io>
Suggested-by: Ryder Lee <ryder.lee@mediatek.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
 drivers/net/wireless/mediatek/mt76/mt7915/mac.c    | 13 ++++++++++++-
 drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h |  1 +
 drivers/net/wireless/mediatek/mt76/mt7915/regs.h   |  1 +
 3 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index 350d4573ced2..df6359c88b18 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -1934,7 +1934,7 @@ void mt7915_mac_update_stats(struct mt7915_phy *phy)
 static void mt7915_mac_severe_check(struct mt7915_phy *phy)
 {
 	struct mt7915_dev *dev = phy->dev;
-	u32 trb;
+	u32 trb, ple_err;
 
 	if (!phy->omac_mask)
 		return;
@@ -1954,6 +1954,17 @@ static void mt7915_mac_severe_check(struct mt7915_phy *phy)
 				   phy->mt76->band_idx);
 
 	phy->trb_ts = trb;
+
+	ple_err = mt76_rr(dev, MT_SWDEF_PLE1_STATS);
+	if ((ple_err & MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR) &&
+	    !(dev->ple1_sts & MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR)) {
+		dev_warn(dev->mt76.dev,
+			 "band%d: PLE error 0x%x detected, triggering L1 SER\n",
+			 phy->mt76->band_idx, ple_err);
+		mt7915_mcu_set_ser(dev, SER_RECOVER, SER_SET_RECOVER_L1,
+				   phy->mt76->band_idx);
+	}
+	dev->ple1_sts = ple_err;
 }
 
 void mt7915_mac_sta_rc_work(struct work_struct *work)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h
index 92e0f9f0169c..0f7e871dc7a1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h
@@ -296,6 +296,7 @@ struct mt7915_dev {
 	spinlock_t reg_lock;
 
 	u32 hw_pattern;
+	u32 ple1_sts;
 
 	bool dbdc_support;
 	bool flash_mode;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/regs.h b/drivers/net/wireless/mediatek/mt76/mt7915/regs.h
index 307bf6a75674..dc7bb9a1f223 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/regs.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/regs.h
@@ -1044,6 +1044,7 @@ enum offs_rev {
 #define MT_SWDEF_SER_STATS		MT_SWDEF(0x040)
 #define MT_SWDEF_PLE_STATS		MT_SWDEF(0x044)
 #define MT_SWDEF_PLE1_STATS		MT_SWDEF(0x048)
+#define MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR	BIT(3)
 #define MT_SWDEF_PLE_AMSDU_STATS	MT_SWDEF(0x04C)
 #define MT_SWDEF_PSE_STATS		MT_SWDEF(0x050)
 #define MT_SWDEF_PSE1_STATS		MT_SWDEF(0x054)
-- 
2.53.0


^ permalink raw reply related

* [PATCH mt76-next 28/29] wifi: mt76: serialize scan-link teardown with dev->mutex
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>

From: Chad Monroe <chad@monroe.io>

The offchannel scan link is allocated in mt76_hw_scan() under
dev->mutex, but torn down without it: mt76_scan_complete() runs from
mt76_scan_work() on the mac80211 workqueue, or from mt76_abort_scan(),
and calls mt76_put_vif_phy_link(), whose vif_link_remove clears the
per-phy omac_mask and the device-wide vif_mask/mld_idx_mask with plain
read-modify-write. A vif link add or remove for another interface,
running concurrently under dev->mutex, can interleave with these
unlocked writes and lose an update: a cleared bit belonging to a live
link gets handed out again (two links sharing an omac/bss/wcid index,
breaking own-MAC unicast RX for the first one), or a freed bit stays
set until reboot and eventually exhausts the index space.

Take dev->mutex around the scan completion, mirroring the ROC teardown
in mt76_roc_complete_work()/mt76_abort_roc(), and switch the channel
restore to __mt76_set_channel() since the caller now holds the lock.
mt76_abort_scan() keeps cancelling the scan work before taking the
mutex, so the work-vs-abort ordering is unchanged.

Signed-off-by: Chad Monroe <chad@monroe.io>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
 drivers/net/wireless/mediatek/mt76/scan.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c
index 3cb11689d4bf..3594b599662d 100644
--- a/drivers/net/wireless/mediatek/mt76/scan.c
+++ b/drivers/net/wireless/mediatek/mt76/scan.c
@@ -11,6 +11,8 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort)
 		.aborted = abort,
 	};
 
+	lockdep_assert_held(&dev->mutex);
+
 	if (!phy)
 		return;
 
@@ -24,7 +26,7 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort)
 	    !test_bit(MT76_MCU_RESET, &dev->phy.state)) {
 		bool offchannel = phy->offchannel;
 
-		mt76_set_channel(phy, &phy->main_chandef, false);
+		__mt76_set_channel(phy, &phy->main_chandef, false);
 		if (offchannel)
 			mt76_offchannel_notify(phy, false);
 	}
@@ -41,7 +43,10 @@ void mt76_abort_scan(struct mt76_dev *dev)
 	spin_unlock_bh(&dev->scan_lock);
 
 	cancel_delayed_work_sync(&dev->scan_work);
+
+	mutex_lock(&dev->mutex);
 	mt76_scan_complete(dev, true);
+	mutex_unlock(&dev->mutex);
 }
 EXPORT_SYMBOL_GPL(mt76_abort_scan);
 
@@ -139,7 +144,9 @@ void mt76_scan_work(struct work_struct *work)
 		goto probe;
 
 	if (dev->scan.chan_idx >= req->n_channels) {
+		mutex_lock(&dev->mutex);
 		mt76_scan_complete(dev, false);
+		mutex_unlock(&dev->mutex);
 		return;
 	}
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH mt76-next 27/29] wifi: mt76: set MT76_SCANNING when starting a hw scan
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>

The scan state bit is cleared by mt76_scan_complete(), but nothing ever
sets it: mt76_sw_scan() is only called for drivers without hw scan
support. As a result, all MT76_SCANNING checks are inert for drivers
using mt76_hw_scan(), including the DFS state handling in
mt76_phy_dfs_state() and the guard against manually triggered radar
detection while scanning.

Set the bit when the scan request is accepted. Since every channel
programmed while scanning now evaluates the DFS state as disabled and
stops the radar detector, re-program the operating channel at scan
completion regardless of the off-channel state, after the scanning bit
has been cleared. Otherwise a scan whose last visited channel was the
operating channel, or one that returned to it early because of
associated stations, would leave radar detection stopped until the
next channel switch.

Fixes: 31083e38548f ("wifi: mt76: add code for emulating hardware scanning")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
 drivers/net/wireless/mediatek/mt76/scan.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c
index 325638d587c9..3cb11689d4bf 100644
--- a/drivers/net/wireless/mediatek/mt76/scan.c
+++ b/drivers/net/wireless/mediatek/mt76/scan.c
@@ -16,10 +16,17 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort)
 
 	clear_bit(MT76_SCANNING, &phy->state);
 
-	if (dev->scan.chan && phy->main_chandef.chan && phy->offchannel &&
+	/* Re-program the operating channel even when the scan never left it:
+	 * any channel set during the scan ran with MT76_SCANNING held, which
+	 * left DFS radar detection disabled
+	 */
+	if (phy->main_chandef.chan &&
 	    !test_bit(MT76_MCU_RESET, &dev->phy.state)) {
+		bool offchannel = phy->offchannel;
+
 		mt76_set_channel(phy, &phy->main_chandef, false);
-		mt76_offchannel_notify(phy, false);
+		if (offchannel)
+			mt76_offchannel_notify(phy, false);
 	}
 	mt76_put_vif_phy_link(phy, dev->scan.vif, dev->scan.mlink);
 	memset(&dev->scan, 0, sizeof(dev->scan));
@@ -211,6 +218,7 @@ int mt76_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 	dev->scan.vif = vif;
 	dev->scan.phy = phy;
 	dev->scan.mlink = mlink;
+	set_bit(MT76_SCANNING, &phy->state);
 	ieee80211_queue_delayed_work(dev->phy.hw, &dev->scan_work, 0);
 
 out:
-- 
2.53.0


^ permalink raw reply related

* [PATCH mt76-next 26/29] wifi: mt76: mt7996: clear stale link state on full reset
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>

After a full chip reset, mac80211 reconfig replays interface, link and
channel context setup. mt7996_vif_link_add() short-circuits when the
link_id is still marked in mvif->valid_links, a state introduced for
postponing link teardown to interface removal. The reset path frees the
link structures without clearing those bits, so the replayed setup never
re-creates dev_info/bss_info/STA records in the restarted firmware and
never re-registers the link wcid, leaving the device inoperative.

The reset path also leaks every allocated MLD index: per-link indices
and the per-vif group/remap indices are re-allocated from scratch during
reconfig, but the old bits stay set in the masks, so repeated full
resets exhaust the index space.

Clear valid_links in the reset vif iterator and reset the MLD index
masks alongside the existing omac_mask clearing.

Fixes: ace5d3b6b49e ("wifi: mt76: mt7996: improve hardware restart reliability")
Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
 drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index ce8c5a5ea8cc..4794dacb3756 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -2449,6 +2449,7 @@ mt7996_mac_reset_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
 		rcu_assign_pointer(mvif->link[i], NULL);
 		kfree_rcu(mlink, rcu_head);
 	}
+	mvif->valid_links = 0;
 	rcu_read_unlock();
 }
 
@@ -2483,6 +2484,8 @@ mt7996_mac_full_reset(struct mt7996_dev *dev)
 
 	mt7996_for_each_phy(dev, phy)
 		phy->omac_mask = 0;
+	dev->mld_idx_mask = 0;
+	dev->mld_remap_idx_mask = 0;
 
 	ieee80211_iterate_stations_atomic(hw, mt7996_mac_reset_sta_iter, dev);
 	mt76_reset_device(&dev->mt76);
-- 
2.53.0


^ permalink raw reply related

* [PATCH mt76-next 29/29] wifi: mt76: mt7996: hold dev->mutex in remove_interface teardown
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>

From: Chad Monroe <chad@monroe.io>

mt7996_remove_interface() destroys the remaining vif links, clearing
omac_mask, vif_mask and mld_idx_mask, with only the wiphy mutex held.
Those masks are modified under dev->mutex everywhere else, so the
unlocked clears can race the scan-link teardown and the reset work
and lose updates.

Take dev->mutex around the link destroy loop, matching
mt7996_add_interface() and mt7915_remove_interface(). The mutex is
released before mt76_vif_cleanup(), which aborts a pending scan and
takes the mutex itself.

Signed-off-by: Chad Monroe <chad@monroe.io>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
 drivers/net/wireless/mediatek/mt76/mt7996/main.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index ce7aede6201e..97be70c23753 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -606,6 +606,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
 	unsigned int link_id;
 	int i;
 
+	mutex_lock(&dev->mt76.mutex);
+
 	/* Remove all active links */
 	for_each_set_bit(link_id, &rem_links, IEEE80211_MLD_MAX_NUM_LINKS) {
 		struct mt7996_vif_link *link;
@@ -622,6 +624,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
 		mt7996_vif_link_destroy(phy, link, vif, NULL);
 	}
 
+	mutex_unlock(&dev->mt76.mutex);
+
 	ieee80211_iterate_active_interfaces_mtx(hw, 0, mt7996_remove_iter,
 						&rdata);
 	mt76_vif_cleanup(&dev->mt76, vif);
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v8 5/9] block: implement NVMEM provider
From: Christoph Hellwig @ 2026-07-24 13:10 UTC (permalink / raw)
  To: Loic Poulain
  Cc: Christoph Hellwig, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Bjorn Andersson, Konrad Dybcio, Jens Axboe,
	Johannes Berg, Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Srinivas Kandagatla, Andrew Lunn, Heiner Kallweit,
	Russell King, Saravana Kannan, Christian Marangi, linux-mmc,
	devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Bartosz Golaszewski
In-Reply-To: <CAFEp6-1_iariDyqWj0KezHAtULSO+piPHX0F=Z46Rx4KDNLmXw@mail.gmail.com>

On Fri, Jul 24, 2026 at 11:10:58AM +0200, Loic Poulain wrote:
> Right, I was planning to add partition support in a follow-up series.
> But since this is really supposed to be a block-agnostic feature,
> whether it's a disk or a partition, I'll include partition in v8.

Please include the partitions, as including them or not matters for
the interfaces.  What is holding that support back now?  fwnode
magic?

> > Also once you register the whole devices as nvmem provider, we really
> > should keep it from being used a block device, shouldn't we?
> 
> 
> I'm not sure, the whole concept is block backed NVMEM, so we still
> need to add that block as base layer, though we could discuss
> suppressing uevent for it. Preventing block usage would make the
> feature much more intrusive than other NVMEM providers like MTD, where
> the underlying device stays accessible. And we may genuinely still
> want to write the disk (e.g. for factory provisioning), updating the
> very data the cells point to, etc. There's also the layering aspect,
> if a disk (e.g. mmcblk0) has logical partitions on top (mmcblk0pX), we
> still register/expose the whole disk mmcblk0 as a block device
> regardless. So if a disk contains NVMEM cells, I think it should still
> be exposed as a block device as well.

So we now get two unsynchronized ways to access the same device?
At least we need to prevent exclusive openers on the block device
side.  And document the side channel very well.


^ permalink raw reply

* [PATCH rtw-next 00/14] wifi: rtw89: coex: add more command / event formats for coming RTL8922D
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601

To support BT-coexistence including dual Bluetooth for coming RTL8922D,
it adds more command and event formats to carry more information for
the more complex application combinations. More, introduce to show two
Bluetooth states for debugging.

Ching-Te Ku (14):
  wifi: rtw89: coex: Add chip initial info version 11 for RTL8922A/D
  wifi: rtw89: coex: Add Wi-Fi MLO info version 2 H2C command
  wifi: rtw89: coex: Add firmware 0.35.111.X support for RTL8922A/D
  wifi: rtw89: coex: Rearrange _ntfy_role_info info
  wifi: rtw89: coex: Separate _ntfy_role_info into two function
  wifi: rtw89: coex: Decrease Wi-Fi special packet protect time
  wifi: rtw89: coex: complete GPIO debug configuration handler
  wifi: rtw89: coex: Refine RF calibration notify flow
  wifi: rtw89: coex: Fix log dump format with proper newline
  wifi: rtw89: coex: Add BTC report version 8 support for BT sub-reports
  wifi: rtw89: coex: Add dual Bluetooth debug info dump
  wifi: rtw89: coex: Fix TDMA v8 handling to unblock BTC report parsing
  wifi: rtw89: coex: Fix H2C command redundant send & version fall
    through
  wifi: rtw89: coex: Handle Bluetooth LE-Audio related coexistence
    feature

 drivers/net/wireless/realtek/rtw89/coex.c     | 908 +++++++++++++++---
 drivers/net/wireless/realtek/rtw89/coex.h     |  56 +-
 drivers/net/wireless/realtek/rtw89/core.h     | 297 +++++-
 drivers/net/wireless/realtek/rtw89/fw.c       | 133 +++
 drivers/net/wireless/realtek/rtw89/fw.h       |  22 +
 drivers/net/wireless/realtek/rtw89/rtw8922a.c |  88 +-
 drivers/net/wireless/realtek/rtw89/rtw8922d.c |  40 +-
 7 files changed, 1322 insertions(+), 222 deletions(-)


base-commit: b0034a2e415603daa1cf00363b407cc145bbcd31
-- 
2.25.1


^ permalink raw reply

* [PATCH rtw-next 01/14] wifi: rtw89: coex: Add chip initial info version 11 for RTL8922A/D
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

The version 11 init info add current RF path control information.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c     |  2 +
 drivers/net/wireless/realtek/rtw89/coex.h     | 19 ++++
 drivers/net/wireless/realtek/rtw89/core.h     | 47 ++++++++++
 drivers/net/wireless/realtek/rtw89/fw.c       | 75 ++++++++++++++++
 drivers/net/wireless/realtek/rtw89/fw.h       |  6 ++
 drivers/net/wireless/realtek/rtw89/rtw8922a.c | 88 ++++++++++++++++---
 drivers/net/wireless/realtek/rtw89/rtw8922d.c | 40 ++++++---
 7 files changed, 251 insertions(+), 26 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 6a1888d8c476..f94866e1f43a 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -3199,6 +3199,8 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index)
 			rtw89_fw_h2c_cxdrv_init_v7(rtwdev, index);
 		else if (ver->fcxinit == 10)
 			rtw89_fw_h2c_cxdrv_init_v10(rtwdev, index);
+		else if (ver->fcxinit == 11)
+			rtw89_fw_h2c_cxdrv_init_v11(rtwdev, index);
 		else
 			rtw89_fw_h2c_cxdrv_init(rtwdev, index);
 		break;
diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h
index 3238c44429a9..41a68ab21d80 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.h
+++ b/drivers/net/wireless/realtek/rtw89/coex.h
@@ -127,6 +127,11 @@ enum btc_fddt_en {
 	  ((__rssi == BTC_RSSI_ST_LOW || \
 	    __rssi == BTC_RSSI_ST_HIGH) ? 1 : 0); })
 
+/* Antenna TX/RX path position masks and helpers */
+#define BTC_ANT_TX_MASK 0xf0
+#define BTC_ANT_RX_MASK 0x0f
+#define BTC_ANT_SHIFT 4
+
 enum btc_ant {
 	BTC_ANT_SHARED = 0,
 	BTC_ANT_DEDICATED,
@@ -473,4 +478,18 @@ void btc_dw2b(u8 *buf, size_t idx, u32 val)
 	buf[idx + 3] = u32_get_bits(val, MASKBYTE3);
 }
 
+static inline
+u8 _btc_get_rf_path_from_ant_num(struct rtw89_btc *btc, u8 antnum)
+{
+	switch (antnum) {
+	default:
+	case 1:
+		return btc->mdinfo.ant.single_pos;
+	case 2:
+		return RF_PATH_AB;
+	case 3:
+		return RF_PATH_ABC;
+	}
+}
+
 #endif
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index 7573f1965c98..7147e186b4a5 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -1537,6 +1537,21 @@ struct rtw89_btc_ant_info_v10 {
 	u8 ant_xmap[2][4];
 } __packed;
 
+struct rtw89_btc_ant_info_v11 {
+	u8 type;
+	u8 num;
+	u8 isolation;
+	u8 single_pos;
+
+	u8 stream_cnt;
+	u8 path_pos;   /* WL path position: Tx[7:4], Rx[3:0] */
+	u8 btg_pos;
+	u8 btg1_pos;
+	u8 func[5];
+	u8 ant_xmap[2][4];
+	u8 rsvd0[3];
+} __packed;
+
 struct rtw89_btc_ant_info {
 	u8 type;  /* shared, dedicated(non-shared) */
 	u8 num;   /* antenna count  */
@@ -1546,6 +1561,7 @@ struct rtw89_btc_ant_info {
 	u8 stream_cnt; /* spatial_stream count: Tx[7:4], Rx[3:0] */
 	u8 btg_pos;    /* BT0 btg-circuit at 0:WL-S0/1:WL-S1 */
 	u8 btg1_pos;   /* BT1 btg-circuit at 0:WL-S0/1:WL-S1 */
+	u8 path_pos;   /* WL path position: Tx[7:4], Rx[3:0] */
 	u8 func[5]; /* function at 1~5 Ant refer to enum btc_bt_func_type */
 	u8 ant_xmap[2][4];
 
@@ -2387,10 +2403,25 @@ struct rtw89_btc_module_v10 {
 	struct rtw89_btc_ant_info_v10 ant;
 } __packed;
 
+struct rtw89_btc_module_v11 {
+	u8 rfe_type;
+	u8 wa_type;
+	u8 kt_ver;
+	u8 kt_ver_adie;
+
+	u8 bt0_pos;
+	u8 bt0_sw_type;
+	u8 bt1_pos;
+	u8 bt1_sw_type;
+
+	struct rtw89_btc_ant_info_v11 ant;
+} __packed;
+
 union rtw89_btc_module_info {
 	struct rtw89_btc_module_v0 md_v0;
 	struct rtw89_btc_module_v7 md_v7;
 	struct rtw89_btc_module_v10 md_v10;
+	struct rtw89_btc_module_v11 md_v11;
 };
 
 struct rtw89_btc_module {
@@ -2473,11 +2504,26 @@ struct rtw89_btc_init_info_v10 {
 	struct rtw89_btc_module_v10 module;
 } __packed;
 
+struct rtw89_btc_init_info_v11 {
+	u8 endian_type; /* 0: little-endian, 1:big-endian */
+	u8 init_mode; /* refer to enum BTC_MODE_xxx  */
+	u8 wl_init_ok;
+	u8 bt0_function;
+
+	u8 bt1_function;
+	u8 bt2_function;
+	u8 pta_mode;
+	u8 pta_direction;
+
+	struct rtw89_btc_module_v11 module;
+};
+
 union rtw89_btc_init_info_u {
 	struct rtw89_btc_init_info_v0 init_v0;
 	struct rtw89_btc_init_info_v7 init_v7;
 	struct rtw89_btc_init_info_v10 init_v10;
 	struct rtw89_btc_init_info_v107 init_v107;
+	struct rtw89_btc_init_info_v11 init_v11;
 };
 
 struct rtw89_btc_init_info {
@@ -2547,6 +2593,7 @@ enum rtw89_btc_bt_func_type {
 #define RTW89_BTC_BT_DEF_BR_TX_PWR 4
 #define RTW89_BTC_BT_DEF_LE_TX_PWR 4
 #define RTW89_BTC_DEFAULT_ANISO 10
+#define RTW89_BTC_BT_DEF_LE_TX_PWR_1 10
 
 struct rtw89_btc_bt_scan_info_v1 {
 	__le16 win;
diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index 3b8c144e0e98..a3a04697e2e6 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -5973,6 +5973,81 @@ int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type)
 	return ret;
 }
 
+int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type)
+{
+	struct rtw89_btc *btc = &rtwdev->btc;
+	struct rtw89_btc_dm *dm = &btc->dm;
+	struct rtw89_btc_init_info *init = &dm->init_info;
+	struct rtw89_h2c_cxinit_v11 *h2c;
+	u32 len = sizeof(*h2c);
+	struct sk_buff *skb;
+	int ret;
+
+	skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len);
+	if (!skb) {
+		rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_init_v11\n");
+		return -ENOMEM;
+	}
+	skb_put(skb, len);
+	h2c = (struct rtw89_h2c_cxinit_v11 *)skb->data;
+
+	h2c->hdr.type = type;
+	h2c->hdr.ver = 11;
+	h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7;
+
+	h2c->init.endian_type = init->endian_type;
+	h2c->init.init_mode = init->init_mode;
+	h2c->init.wl_init_ok = init->wl_init_ok;
+	h2c->init.bt0_function = init->bt0_function;
+
+	h2c->init.bt1_function = init->bt1_function;
+	h2c->init.bt2_function = init->bt2_function;
+	h2c->init.pta_mode = init->pta_mode;
+	h2c->init.pta_direction = init->pta_direction;
+
+	h2c->init.module.rfe_type = init->module.rfe_type;
+	h2c->init.module.wa_type = init->module.wa_type;
+	h2c->init.module.kt_ver = init->module.kt_ver;
+	h2c->init.module.kt_ver_adie = init->module.kt_ver_adie;
+
+	h2c->init.module.bt0_pos = init->module.bt0_pos;
+	h2c->init.module.bt0_sw_type = init->module.bt0_sw_type;
+	h2c->init.module.bt1_pos = init->module.bt1_pos;
+	h2c->init.module.bt1_sw_type = init->module.bt1_sw_type;
+
+	h2c->init.module.ant.type = init->module.ant.type;
+	h2c->init.module.ant.num = init->module.ant.num;
+	h2c->init.module.ant.isolation = init->module.ant.isolation;
+	h2c->init.module.ant.single_pos = init->module.ant.single_pos;
+
+	h2c->init.module.ant.stream_cnt = init->module.ant.stream_cnt;
+	h2c->init.module.ant.path_pos = init->module.ant.path_pos;
+	h2c->init.module.ant.btg_pos = init->module.ant.btg_pos;
+	h2c->init.module.ant.btg1_pos = init->module.ant.btg1_pos;
+	memcpy(h2c->init.module.ant.func, init->module.ant.func,
+	       sizeof(init->module.ant.func));
+
+	memcpy(h2c->init.module.ant.ant_xmap, init->module.ant.ant_xmap,
+	       sizeof(init->module.ant.ant_xmap));
+
+	rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
+			      H2C_CAT_OUTSRC, BTFC_SET,
+			      SET_DRV_INFO, 0, 0,
+			      len);
+
+	ret = rtw89_h2c_tx(rtwdev, skb, false);
+	if (ret) {
+		rtw89_err(rtwdev, "failed to send h2c\n");
+		goto fail;
+	}
+
+	return 0;
+fail:
+	dev_kfree_skb_any(skb);
+
+	return ret;
+}
+
 #define PORT_DATA_OFFSET 4
 #define H2C_LEN_CXDRVINFO_ROLE_DBCC_LEN 12
 #define H2C_LEN_CXDRVINFO_ROLE_SIZE(max_role_num) \
diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h
index e86014ca249a..c0a00b721060 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.h
+++ b/drivers/net/wireless/realtek/rtw89/fw.h
@@ -2603,6 +2603,11 @@ struct rtw89_h2c_cxinit_v10 {
 	struct rtw89_btc_init_info_v10 init;
 } __packed;
 
+struct rtw89_h2c_cxinit_v11 {
+	struct rtw89_h2c_cxhdr_v7 hdr;
+	struct rtw89_btc_init_info_v11 init;
+} __packed;
+
 #define RTW89_H2C_CXROLE_V101_ROLE_STAT_CONNTECTED BIT(0)
 #define RTW89_H2C_CXROLE_V101_ROLE_STAT_PID GENMASK(3, 1)
 #define RTW89_H2C_CXROLE_V101_ROLE_STAT_PHY BIT(4)
@@ -5456,6 +5461,7 @@ int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type);
+int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_osi_info_v6(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type);
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c
index 2586fd2df8ab..2d225e009b28 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c
@@ -2721,25 +2721,33 @@ static u32 rtw8922a_chan_to_rf18_val(struct rtw89_dev *rtwdev,
 
 static void rtw8922a_btc_set_rfe(struct rtw89_dev *rtwdev)
 {
-	struct rtw89_btc_module *md = &rtwdev->btc.mdinfo;
+	struct rtw89_btc *btc = &rtwdev->btc;
+	struct rtw89_btc_module *md = &btc->mdinfo;
+	u8 i, j;
+	u8 tx_path_pos, rx_path_pos;
 
 	md->rfe_type = rtwdev->efuse.rfe_type;
 	md->kt_ver = rtwdev->hal.cv;
-	md->bt_solo = 0;
-	md->bt0_sw_type = BTC_SWITCH_INTERNAL;
+	md->kt_ver_adie = rtwdev->hal.acv;
 	md->wa_type = 0;
 
-	md->ant.type = BTC_ANT_SHARED;
 	md->ant.num = 2;
-	md->ant.isolation = 10;
-	md->ant.diversity = 0;
-	md->ant.single_pos = RF_PATH_A;
-	md->ant.btg_pos = RF_PATH_B;
+	md->ant.single_pos = BTC_RF_S0;
 
 	if (md->kt_ver <= 1)
 		md->wa_type |= BTC_WA_HFP_ZB;
 
-	rtwdev->btc.cx.bt_ext.func_type = BTC_3CX_NONE;
+	btc->cx.bt0.ant_iso_to_wl = md->ant.isolation;
+	btc->cx.bt1.ant_iso_to_wl =  md->ant.isolation;
+
+	md->ant.stream_cnt = 2;
+	md->ant.btg_pos = RF_PATH_B;
+	md->ant.single_pos = RF_PATH_A;
+
+	btc->cx.bt0.band_56G_support = 0;
+	btc->cx.bt1.band_56G_support = 0;
+	btc->cx.bt0.func_type = BTC_BTF_BT;
+	btc->cx.bt1.func_type = BTC_BTF_NONE;
 
 	if (md->rfe_type == 0) {
 		rtwdev->btc.dm.error.map.rfe_type0 = true;
@@ -2747,17 +2755,69 @@ static void rtw8922a_btc_set_rfe(struct rtw89_dev *rtwdev)
 	}
 
 	md->ant.num = (md->rfe_type % 2) ?  2 : 3;
-
 	if (md->kt_ver == 0)
 		md->ant.num = 2;
 
-	if (md->ant.num == 3) {
-		md->ant.type = BTC_ANT_DEDICATED;
-		md->bt0_pos = BTC_BT_ALONE;
-	} else {
+	memset(btc->dm.ant_xmap, 0, sizeof(btc->dm.ant_xmap));
+
+	switch (md->ant.num) {
+	case 1:
 		md->ant.type = BTC_ANT_SHARED;
 		md->bt0_pos = BTC_BT_BTG;
+		md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL;
+		md->ant.btg_pos = BTC_RF_S0;
+		btc->dm.ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1;
+		md->ant.func[0] = BTC_EFMAP_BT0;
+		break;
+	case 2: /* 2-Ant */
+	default:
+		md->ant.type = BTC_ANT_SHARED;
+		md->bt0_pos = BTC_BT_BTG;
+		md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL;
+		btc->dm.wl_trx_nss_en = 1;
+		btc->dm.ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1;
+		md->ant.func[1] = BTC_EFMAP_BT0;
+		break;
+	case 3: /* 3-Ant, 3 different BT-configuration */
+		/* WL-S0 + WL-S1 + BT0-S0 */
+		md->ant.type = BTC_ANT_DEDICATED;
+		md->bt0_pos = BTC_BT_ALONE;
+		md->bt0_sw_type = BTC_SWITCH_V1_NONE;
+		md->ant.func[2] = BTC_EFMAP_BT0;
+	}
+
+	tx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.tx_nss);
+	rx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.rx_nss);
+	/* Combine TX[7:4] and RX[3:0] into path_pos byte */
+	md->ant.path_pos = ((tx_path_pos << BTC_ANT_SHIFT) & BTC_ANT_TX_MASK) |
+			   (rx_path_pos & BTC_ANT_RX_MASK);
+
+	switch (btc->cx.bt_ext.chip_id) {
+	default:
+	case BTC_ESOC_NONE:
+		memset(&btc->cx.bt_ext, 0, sizeof(struct rtw89_btc_extsoc_info));
+		btc->cx.bt_ext.max_tx_pwr = RTW89_BTC_BT_DEF_LE_TX_PWR_1;
+		btc->cx.bt_ext.ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO;
+		break;
+	case BTC_ESOC_8771:
+		btc->cx.bt_ext.func_type = BTC_BTF_THREAD;
+		btc->cx.bt_ext.hw_coex = BTC_EXTSOC_INTF_PTA;
+		btc->cx.bt_ext.rf_band_map = 0x1; /* 2.4G only */
+		btc->cx.bt_ext.link_weight[BTC_BT_B2G] = 10;
+		btc->cx.bt_ext.profile_map[BTC_BT_B2G] |= BTC_BT_THREAD;
+		/* use GPIO 12~15 for Ext-4-wire-PTA */
+		btc->cx.bt_ext.hpta_cfg = BIT(12) | BIT(13) | BIT(14) | BIT(15);
+		md->ant.func[2] = BTC_EFMAP_ZB;
+		break;
 	}
+
+	btc->dm.ant_xmap[BTC_RF_S0][BTC_BT_EXT] = 0;
+	btc->dm.ant_xmap[BTC_RF_S1][BTC_BT_EXT] = 0;
+
+	for (i = BTC_RF_S0; i <= BTC_RF_S1; i++)
+		for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++)
+			md->ant.ant_xmap[i][j] = btc->dm.ant_xmap[i][j];
+
 	rtwdev->btc.btg_pos = md->ant.btg_pos;
 	rtwdev->btc.ant_type = md->ant.type;
 }
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
index 12c96425a6ff..142e693c87bf 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
@@ -3174,7 +3174,9 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
 	struct rtw89_btc_dm *dm = &btc->dm;
 	struct rtw89_btc_cx *cx = &btc->cx;
 	u8 efuse_bt_func, efuse_ant_info, bt_sw_gpio_pos;
-	u8 is_combo, is_bt_share;
+	u8 is_combo, is_bt_share, is_spdt;
+	u8 tx_path_pos, rx_path_pos;
+	u8 i, j;
 
 	rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s !!\n", __func__);
 
@@ -3192,7 +3194,8 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
 	cx->bt0.ant_iso_to_wl = md->ant.isolation;
 	cx->bt1.ant_iso_to_wl = md->ant.isolation;
 
-	md->ant.stream_cnt = 2;
+	md->ant.stream_cnt = (rtwdev->hal.tx_nss << BTC_ANT_SHIFT) +
+				rtwdev->hal.rx_nss;
 	md->ant.btg_pos = BTC_RF_S1; /* BTG0 at WL-S1 */
 	md->ant.btg1_pos = BTC_RF_S0; /* BTG1 at WL-S0 if Dual-BTGA */
 
@@ -3203,19 +3206,20 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
 
 	efuse_bt_func = rtwdev->efuse.bt_setting_2;
 
-	switch ((efuse_bt_func & 0xe0) >> 5) { /* 0xcd[7:5] */
+	is_spdt = !!(efuse_bt_func & BIT(5));
+
+	switch ((efuse_bt_func & 0xc0) >> 6) { /* 0xcd[7:6] */
 	default:
 	case 0:
-	case 1:
 		bt_sw_gpio_pos = 5;
 		break;
-	case 2:
+	case 1:
 		bt_sw_gpio_pos = 11;
 		break;
-	case 3:
+	case 2:
 		bt_sw_gpio_pos = 15;
 		break;
-	case 4:
+	case 3:
 		bt_sw_gpio_pos = 20;
 		break;
 	}
@@ -3233,9 +3237,8 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
 	case 1: /* 1-Ant WL-S0 only & BT0 only */
 		md->ant.type = BTC_ANT_SHARED;
 		md->bt0_pos = BTC_BT_BTG;
-		md->bt0_sw_type = BTC_SWITCH_INTERNAL;
+		md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL;
 		md->ant.btg_pos = BTC_RF_S0; /* BTG0 at WL-S0 */
-		md->ant.stream_cnt = 1;
 		md->ant.func[0] = BTC_EFMAP_BT0;
 		dm->ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1; /* BT0 shared with S0*/
 		dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 0; /* WL 1T1R no RF-S1 */
@@ -3265,13 +3268,13 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
 
 		if (md->ant.func[0] == BTC_EFMAP_BT1) { /* if 2nd BT exist */
 			dm->ant_xmap[BTC_RF_S0][BTC_BT_2ND] = 1;
-			if (md->rfe_type == 12) { /* WL-S0 & BT1 by SPDT */
+			if (is_spdt) { /* WL-S0 & BT1 by SPDT */
 				md->bt1_pos = BTC_BT_ALONE;
 				/* Todo: set SPDT GPIO-ctrl */
 				md->bt1_sw_type = bt_sw_gpio_pos;
 			} else { /* WL-S0 & BT1-S1 by BTGA */
 				md->bt1_pos = BTC_BT_BTG;
-				md->bt1_sw_type = BTC_SWITCH_INTERNAL;
+				md->bt1_sw_type = BTC_SWITCH_V1_INTERNAL;
 			}
 		}
 
@@ -3284,7 +3287,7 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
 			md->ant.func[2] = efuse_bt_func & (~BTC_EFMAP_BT0);
 			md->ant.type = BTC_ANT_SHARED;
 			md->bt0_pos = BTC_BT_BTG;
-			md->bt0_sw_type = BTC_SWITCH_INTERNAL;
+			md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL;
 			dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1;
 			dm->wl_trx_nss_en = 1; /* 1ss MIMO-PS capability */
 		} else {
@@ -3324,6 +3327,12 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
 		break;
 	}
 
+	tx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.tx_nss);
+	rx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.rx_nss);
+	/* Combine TX[7:4] and RX[3:0] into path_pos byte */
+	md->ant.path_pos = ((tx_path_pos << BTC_ANT_SHIFT) & BTC_ANT_TX_MASK) |
+			   (rx_path_pos & BTC_ANT_RX_MASK);
+
 	/*
 	 * if only BT0 at BTGA: 2-Ant, 3-Ant(BT1 used dedicated-ant)
 	 * can setup dm->wl_trx_nss_en = 1, WL MIMO-PS to 1T1R
@@ -3373,6 +3382,13 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
 
 	dm->ant_xmap[BTC_RF_S0][BTC_BT_EXT] = 0;
 	dm->ant_xmap[BTC_RF_S1][BTC_BT_EXT] = 0;
+
+	for (i = BTC_RF_S0; i <= BTC_RF_S1; i++)
+		for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++)
+			md->ant.ant_xmap[i][j] = dm->ant_xmap[i][j];
+
+	rtwdev->btc.btg_pos = md->ant.btg_pos;
+	rtwdev->btc.ant_type = md->ant.type;
 }
 
 static void rtw8922d_btc_init_cfg(struct rtw89_dev *rtwdev)
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 02/14] wifi: rtw89: coex: Add Wi-Fi MLO info version 2 H2C command
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

The info included MLO status, hardware status, firmware will set
corresponding register control to do coexistence (PTA slot priority,
RF switch etc.)

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c |  3 ++
 drivers/net/wireless/realtek/rtw89/core.h | 19 ++++++++
 drivers/net/wireless/realtek/rtw89/fw.c   | 58 +++++++++++++++++++++++
 drivers/net/wireless/realtek/rtw89/fw.h   |  6 +++
 4 files changed, 86 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index f94866e1f43a..0c5599a23b4a 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -3274,6 +3274,9 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index)
 		else
 			return;
 
+		if (ver->fcxmlo == 2)
+			rtw89_fw_h2c_cxdrv_mlo_v2(rtwdev, index);
+
 		break;
 	case CXDRVINFO_OSI:
 		if (!ver->fcxosi || ver->drvinfo_ver == 103)
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index 7147e186b4a5..0ec63f9510bd 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -1783,6 +1783,25 @@ struct rtw89_btc_wl_dbcc_info {
 	u8 role[RTW89_PHY_NUM]; /* role in each phy */
 };
 
+struct rtw89_btc_wl_mlo_info_v2 {
+	u8 wmode[RTW89_PHY_NUM];
+	u8 ch_type[RTW89_PHY_NUM];
+	u8 hwb_rf_band[RTW89_PHY_NUM];
+	u8 path_rf_band[RTW89_PHY_NUM];
+
+	u8 wtype;
+	u8 mrcx_mode;
+	u8 mrcx_act_hwb_map;
+	u8 mrcx_bt_slot_rsp;
+
+	u8 rf_combination;
+	u8 mlo_en;
+	u8 mlo_adie;
+	u8 dual_hw_band_en;
+
+	__le32 link_status;
+} __packed;
+
 struct rtw89_btc_wl_mlo_info {
 	u8 wmode[RTW89_PHY_NUM]; /* enum phl_mr_wmode */
 	u8 ch_type[RTW89_PHY_NUM]; /* enum phl_mr_ch_type */
diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index a3a04697e2e6..5ab80bda3ae5 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -6616,6 +6616,64 @@ int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type)
 	return ret;
 }
 
+int rtw89_fw_h2c_cxdrv_mlo_v2(struct rtw89_dev *rtwdev, u8 type)
+{
+	struct rtw89_btc_wl_mlo_info *mlo = &rtwdev->btc.cx.wl.mlo_info;
+	struct rtw89_h2c_cxmlo_v2 *h2c;
+	u32 len = sizeof(*h2c);
+	struct sk_buff *skb;
+	int ret;
+
+	skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len);
+	if (!skb) {
+		rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_mlo_v2\n");
+		return -ENOMEM;
+	}
+	skb_put(skb, len);
+	h2c = (struct rtw89_h2c_cxmlo_v2 *)skb->data;
+
+	h2c->hdr.type = type;
+	h2c->hdr.ver = 2;
+	h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7;
+
+	memcpy(h2c->mlo.wmode, mlo->wmode,
+	       sizeof(h2c->mlo.wmode));
+	memcpy(h2c->mlo.ch_type, mlo->ch_type,
+	       sizeof(h2c->mlo.ch_type));
+	memcpy(h2c->mlo.hwb_rf_band, mlo->hwb_rf_band,
+	       sizeof(h2c->mlo.hwb_rf_band));
+	memcpy(h2c->mlo.path_rf_band, mlo->path_rf_band,
+	       sizeof(h2c->mlo.path_rf_band));
+
+	h2c->mlo.wtype = mlo->wtype;
+	h2c->mlo.mrcx_mode = mlo->mrcx_mode;
+	h2c->mlo.mrcx_act_hwb_map = mlo->mrcx_act_hwb_map;
+	h2c->mlo.mrcx_bt_slot_rsp = mlo->mrcx_bt_slot_rsp;
+
+	h2c->mlo.rf_combination = mlo->rf_combination;
+	h2c->mlo.mlo_en = mlo->mlo_en;
+	h2c->mlo.mlo_adie = mlo->mlo_adie;
+	h2c->mlo.dual_hw_band_en = mlo->dual_hw_band_en;
+	h2c->mlo.link_status = cpu_to_le32(mlo->link_status);
+
+	rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
+			      H2C_CAT_OUTSRC, BTFC_SET,
+			      SET_DRV_INFO, 0, 0,
+			      len);
+
+	ret = rtw89_h2c_tx(rtwdev, skb, false);
+	if (ret) {
+		rtw89_err(rtwdev, "failed to send h2c\n");
+		goto fail;
+	}
+
+	return 0;
+fail:
+	dev_kfree_skb_any(skb);
+
+	return ret;
+}
+
 int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type)
 {
 	struct rtw89_btc *btc = &rtwdev->btc;
diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h
index c0a00b721060..538df0fb42cc 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.h
+++ b/drivers/net/wireless/realtek/rtw89/fw.h
@@ -2453,6 +2453,11 @@ struct rtw89_h2c_cxrole_v10 {
 	struct rtw89_btc_wl_role_info_v10 r;
 } __packed;
 
+struct rtw89_h2c_cxmlo_v2 {
+	struct rtw89_h2c_cxhdr_v7 hdr;
+	struct rtw89_btc_wl_mlo_info_v2 mlo;
+} __packed;
+
 struct rtw89_h2c_cxosi {
 	struct rtw89_h2c_cxhdr_v7 hdr;
 	struct rtw89_btc_fbtc_outsrc_set_info_v1 osi;
@@ -5462,6 +5467,7 @@ int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type);
+int rtw89_fw_h2c_cxdrv_mlo_v2(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_osi_info_v6(struct rtw89_dev *rtwdev, u8 type);
 int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type);
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 03/14] wifi: rtw89: coex: Add firmware 0.35.111.X support for RTL8922A/D
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

Add BTC version table entries for RTL8922A and RTL8922D. The new firmware
need driver provide more chip initial related parameters.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 0c5599a23b4a..d873b9482e49 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -133,6 +133,15 @@ static const u32 cxtbl[] = {
 
 static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = {
 	/* firmware version must be in decreasing order for each chip */
+	{RTL8922D, RTW89_FW_VER_CODE(0, 35, 111, 0),
+	 .fcxbtcrpt = 11, .fcxtdma = 8,    .fcxslots = 7, .fcxcysta = 8,
+	 .fcxstep = 7,    .fcxnullsta = 7, .fcxmreg = 7,  .fcxgpiodbg = 8,
+	 .fcxbtver = 8,   .fcxbtscan = 8,  .fcxbtafh = 8, .fcxbtdevinfo = 8,
+	 .fwlrole = 10,   .frptmap = 5,    .fcxctrl = 9,  .fcxinit = 11,
+	 .fwevntrptl = 1, .fwc2hfunc = 4, .drvinfo_ver = 3, .info_buf = 1800,
+	 .max_role_num = 6, .fcxosi = 6,   .fcxmlo = 2,   .bt_desired = 8,
+	 .fcxtrx = 9,
+	},
 	{RTL8922D, RTW89_FW_VER_CODE(0, 35, 94, 0),
 	 .fcxbtcrpt = 11, .fcxtdma = 8,    .fcxslots = 7, .fcxcysta = 8,
 	 .fcxstep = 7,    .fcxnullsta = 7, .fcxmreg = 7,  .fcxgpiodbg = 8,
@@ -169,6 +178,15 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = {
 	 .max_role_num = 6, .fcxosi = 0,  .fcxmlo = 0,   .bt_desired = 8,
 	 .fcxtrx = 0,
 	},
+	{RTL8922A, RTW89_FW_VER_CODE(0, 35, 111, 0),
+	 .fcxbtcrpt = 11, .fcxtdma = 8,    .fcxslots = 7, .fcxcysta = 8,
+	 .fcxstep = 7,    .fcxnullsta = 7, .fcxmreg = 7,  .fcxgpiodbg = 8,
+	 .fcxbtver = 8,   .fcxbtscan = 8,  .fcxbtafh = 8, .fcxbtdevinfo = 8,
+	 .fwlrole = 10,   .frptmap = 5,    .fcxctrl = 9,  .fcxinit = 11,
+	 .fwevntrptl = 1, .fwc2hfunc = 4, .drvinfo_ver = 3, .info_buf = 1800,
+	 .max_role_num = 6, .fcxosi = 6,   .fcxmlo = 2,   .bt_desired = 8,
+	 .fcxtrx = 9,
+	},
 	{RTL8922A, RTW89_FW_VER_CODE(0, 35, 71, 0),
 	 .fcxbtcrpt = 8, .fcxtdma = 7,    .fcxslots = 7, .fcxcysta = 7,
 	 .fcxstep = 7,   .fcxnullsta = 7, .fcxmreg = 7,  .fcxgpiodbg = 7,
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 04/14] wifi: rtw89: coex: Rearrange _ntfy_role_info info
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

rtw89_btc_wl_link_info is duplicated declaring in the function, remove one
of them. We need MAC Address only when Wi-Fi role is station, included the
copy operation into if statement.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 79 +++++++++++++++--------
 1 file changed, 53 insertions(+), 26 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index d873b9482e49..9da3b23b2d14 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -8675,13 +8675,14 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev,
 	const struct rtw89_chan *chan = rtw89_chan_get(rtwdev,
 						       rtwvif_link->chanctx_idx);
 	struct ieee80211_vif *vif = rtwvif_link_to_vif(rtwvif_link);
+	struct ieee80211_p2p_noa_attr *noa_attr;
+	struct ieee80211_p2p_noa_desc *noa_desc;
 	struct ieee80211_bss_conf *bss_conf;
 	struct ieee80211_link_sta *link_sta;
 	struct rtw89_btc *btc = &rtwdev->btc;
 	struct rtw89_btc_wl_info *wl = &btc->cx.wl;
 	struct rtw89_btc_wl_link_info r = {0};
-	struct rtw89_btc_wl_link_info *wlinfo = NULL;
-	u8 mode = 0;
+	u8 i, mode = 0;
 
 	rcu_read_lock();
 
@@ -8729,34 +8730,60 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev,
 	rtw89_debug(rtwdev, RTW89_DBG_BTC,
 		    "[BTC], wifi_role=%d\n", rtwvif_link->wifi_role);
 
-	wlinfo = &wl->rlink_info[rtwvif_link->port][rtwvif_link->phy_idx];
-
-	wlinfo->mode = mode;
-	wlinfo->role = rtwvif_link->wifi_role;
-	wlinfo->phy = rtwvif_link->phy_idx;
-	wlinfo->pid = rtwvif_link->port;
-	wlinfo->active = true;
-	wlinfo->connected = MLME_LINKED;
-	wlinfo->bcn_period = bss_conf->beacon_int;
-	wlinfo->dtim_period = bss_conf->dtim_period;
-	wlinfo->band = chan->band_type;
-	wlinfo->ch = chan->channel;
-	wlinfo->bw = chan->band_width;
-	wlinfo->chdef.band = chan->band_type;
-	wlinfo->chdef.center_ch = chan->channel;
-	wlinfo->chdef.bw = chan->band_width;
-	wlinfo->chdef.chan = chan->primary_channel;
-	ether_addr_copy(r.mac_addr, rtwvif_link->mac_addr);
+	if (vif->type == NL80211_IFTYPE_P2P_GO ||
+	    vif->type == NL80211_IFTYPE_P2P_CLIENT) {
+		noa_attr = &bss_conf->p2p_noa_attr;
 
-	rcu_read_unlock();
+		for (i = 0; i < RTW89_P2P_MAX_NOA_NUM; i++) {
+			noa_desc = &noa_attr->desc[i];
+			if (noa_desc->count != 0) {
+				r.noa = 1;
+				r.noa_duration = le32_to_cpu(noa_desc->duration);
+				break;
+			}
+		}
+	}
+
+	r.mode = mode;
+	r.role = rtwvif_link->wifi_role;
+	r.phy = rtwvif_link->phy_idx;
+	r.pid = rtwvif_link->port;
+	r.active = true;
+	if (vif->active_links) {
+		if (rtwvif_link->link_id < 16)
+			r.active = !!(vif->active_links & BIT(rtwvif_link->link_id));
+	}
+	r.bcn_period = bss_conf->beacon_int;
+	r.dtim_period = bss_conf->dtim_period;
+	r.band = chan->band_type;
+	r.ch = chan->channel;
+	r.bw = chan->band_width;
+	r.chdef.band = chan->band_type;
+	r.chdef.center_ch = chan->channel;
+	r.chdef.bw = chan->band_width;
+	r.chdef.chan = chan->primary_channel;
+
+	if (rtwsta_link && vif->type == NL80211_IFTYPE_STATION) {
+		r.mac_id = rtwsta_link->mac_id;
+		ether_addr_copy(r.mac_addr, rtwvif_link->mac_addr);
+	}
+
+	switch (state) {
+	case BTC_ROLE_MSTS_STA_CONN_END:
+	case BTC_ROLE_MSTS_AP_START:
+		r.connected = MLME_LINKED;
+		break;
+	default:
+		r.connected = MLME_NO_LINK;
+		break;
+	}
 
-	if (rtwsta_link && vif->type == NL80211_IFTYPE_STATION)
-		wlinfo->mac_id = rtwsta_link->mac_id;
+	rcu_read_unlock();
 
 	btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++;
 
-	if (wlinfo->role == RTW89_WIFI_ROLE_STATION &&
-	    wlinfo->connected == MLME_NO_LINK)
+	if (r.role == RTW89_WIFI_ROLE_STATION &&
+	    r.connected == MLME_NO_LINK)
 		btc->dm.leak_ap = 0;
 
 	if (state == BTC_ROLE_MSTS_STA_CONN_START) {
@@ -8774,7 +8801,7 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev,
 	    state == BTC_ROLE_MSTS_STA_CONN_END)
 		wl->status.map._4way = false;
 
-	_update_wl_info(rtwdev, wlinfo);
+	_update_wl_info(rtwdev, &r);
 
 	_run_coex(rtwdev, BTC_RSN_NTFY_ROLE_INFO);
 }
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 05/14] wifi: rtw89: coex: Separate _ntfy_role_info into two function
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

To make logic more clearly, separate _ntfy_role_info into two function by
data collecting and using.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 109 ++++++++++++++++------
 1 file changed, 80 insertions(+), 29 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 9da3b23b2d14..ae458917ecf7 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -7233,6 +7233,7 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev)
 		mode = _chk_dbcc(rtwdev, cid_ch, cid_phy, cid_role, cnt, notv10);
 		mode_v0 = wl_rinfo->link_mode_v0;
 	} else if (!b2g && b5g && notv10) {
+		mode = _get_role_link_mode(wl_rinfo, cid_role[0], notv10);
 		mode_v0 = BTC_WLINK_V0_5G;
 	} else if (b2g && b5g) {
 		mode = BTC_WLINK_DB_MCC;
@@ -7247,6 +7248,7 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev)
 		}
 	} else {
 		mode = _get_role_link_mode(wl_rinfo, cid_role[0], notv10);
+		mode_v0 = wl_rinfo->link_mode_v0;
 	}
 
 	wl_rinfo->link_mode = mode;
@@ -8667,6 +8669,78 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len)
 	_run_coex(rtwdev, BTC_RSN_UPDATE_BT_INFO);
 }
 
+static void _ntfy_role_info(struct rtw89_dev *rtwdev, u8 rid,
+			    struct rtw89_btc_wl_link_info *info,
+			    enum btc_role_state reason)
+{
+	struct rtw89_btc *btc = &rtwdev->btc;
+	struct rtw89_btc_wl_link_info *wlinfo = NULL;
+	struct rtw89_btc_wl_info *wl = &btc->cx.wl;
+	const struct rtw89_btc_ver *ver = btc->ver;
+	u8 rlink_id = info->phy; /* 1 role_id has 2 rlink_id(by HW_Band0/1) */
+	bool refresh_role = false;
+
+	rtw89_debug(rtwdev, RTW89_DBG_BTC,
+		    "[BTC], %s(), role_id=%d, reason=%d\n",
+		    __func__, rid, reason);
+
+	if (rid >= ver->max_role_num || rlink_id >= RTW89_PHY_NUM)
+		return;
+
+	btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++;
+
+	wlinfo = &wl->rlink_info[rid][rlink_id];
+	memcpy(wlinfo, info, sizeof(struct rtw89_btc_wl_link_info));
+
+	switch (reason) {
+	case BTC_ROLE_START:
+		wlinfo->active = true;
+		return;
+	case BTC_ROLE_STOP:
+		wlinfo->active = false;
+		return;
+	case BTC_ROLE_MSTS_STA_CONN_START:
+		wl->status.map.transacting = 1;
+		wiphy_delayed_work_cancel(rtwdev->hw->wiphy,
+					  &rtwdev->coex_act1_work);
+		wiphy_delayed_work_queue(rtwdev->hw->wiphy,
+					 &rtwdev->coex_act1_work,
+					 RTW89_COEX_ACT1_WORK_PERIOD);
+		break;
+	case BTC_ROLE_MSTS_STA_CONN_END:
+		wl->status.map.transacting = 0;
+		refresh_role = true;
+		break;
+	case BTC_ROLE_MSTS_STA_DIS_CONN:
+		wl->status.map.transacting = 1;
+		refresh_role = false;
+		wiphy_delayed_work_cancel(rtwdev->hw->wiphy,
+					  &rtwdev->coex_act1_work);
+		wiphy_delayed_work_queue(rtwdev->hw->wiphy,
+					 &rtwdev->coex_act1_work,
+					 RTW89_COEX_ACT1_WORK_PERIOD);
+		break;
+	case BTC_ROLE_MSTS_AP_START:
+	case BTC_ROLE_MSTS_AP_STOP:
+		refresh_role = true;
+		break;
+	case BTC_ROLE_STATE_UNKNOWN:
+		rtw89_debug(rtwdev, RTW89_DBG_BTC,
+			    "[BTC], %s(), role_id=%d, Unknown reason return!\n",
+			    __func__, rid);
+		return;
+	default:
+		return;
+	}
+
+	if (refresh_role) {
+		_update_wl_info(rtwdev, wlinfo);
+		_fw_set_drv_info(rtwdev, CXDRVINFO_ROLE);
+	}
+
+	_run_coex(rtwdev, BTC_RSN_NTFY_ROLE_INFO);
+}
+
 void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev,
 			      struct rtw89_vif_link *rtwvif_link,
 			      struct rtw89_sta_link *rtwsta_link,
@@ -8679,20 +8753,20 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev,
 	struct ieee80211_p2p_noa_desc *noa_desc;
 	struct ieee80211_bss_conf *bss_conf;
 	struct ieee80211_link_sta *link_sta;
-	struct rtw89_btc *btc = &rtwdev->btc;
-	struct rtw89_btc_wl_info *wl = &btc->cx.wl;
 	struct rtw89_btc_wl_link_info r = {0};
-	u8 i, mode = 0;
+	u8 i, role_id, mode = 0;
 
 	rcu_read_lock();
 
 	bss_conf = rtw89_vif_rcu_dereference_link(rtwvif_link, false);
 
+	role_id = rtwvif_link->port;
+
 	rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], state=%d\n", state);
 	rtw89_debug(rtwdev, RTW89_DBG_BTC,
 		    "[BTC], role is STA=%d\n",
 		    vif->type == NL80211_IFTYPE_STATION);
-	rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], port=%d\n", rtwvif_link->port);
+	rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], port=%d\n", role_id);
 	rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], band=%d ch=%d bw=%d\n",
 		    chan->band_type, chan->channel, chan->band_width);
 	rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], associated=%d\n",
@@ -8747,7 +8821,7 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev,
 	r.mode = mode;
 	r.role = rtwvif_link->wifi_role;
 	r.phy = rtwvif_link->phy_idx;
-	r.pid = rtwvif_link->port;
+	r.pid = role_id;
 	r.active = true;
 	if (vif->active_links) {
 		if (rtwvif_link->link_id < 16)
@@ -8780,30 +8854,7 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev,
 
 	rcu_read_unlock();
 
-	btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++;
-
-	if (r.role == RTW89_WIFI_ROLE_STATION &&
-	    r.connected == MLME_NO_LINK)
-		btc->dm.leak_ap = 0;
-
-	if (state == BTC_ROLE_MSTS_STA_CONN_START) {
-		wl->status.map.transacting = 1;
-		wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_act1_work);
-		wiphy_delayed_work_queue(rtwdev->hw->wiphy,
-					 &rtwdev->coex_act1_work,
-					 RTW89_COEX_ACT1_WORK_PERIOD);
-	} else {
-		wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_act1_work);
-		wl->status.map.transacting = 0;
-	}
-
-	if (state == BTC_ROLE_MSTS_STA_DIS_CONN ||
-	    state == BTC_ROLE_MSTS_STA_CONN_END)
-		wl->status.map._4way = false;
-
-	_update_wl_info(rtwdev, &r);
-
-	_run_coex(rtwdev, BTC_RSN_NTFY_ROLE_INFO);
+	_ntfy_role_info(rtwdev, role_id, &r, state);
 }
 
 void rtw89_btc_ntfy_radio_state(struct rtw89_dev *rtwdev, enum btc_rfctrl rf_state)
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 06/14] wifi: rtw89: coex: Decrease Wi-Fi special packet protect time
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

While Wi-Fi is doing special packet handshake, or going into some
transient state, BT-Coexistence will held timer to fix control logic
to protect the segment. Set the protection duration to 1 second,
it is enough to cover the situation.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h
index 41a68ab21d80..355784941edd 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.h
+++ b/drivers/net/wireless/realtek/rtw89/coex.h
@@ -69,7 +69,7 @@ enum btc_wl_rfk_type {
 #define NM_EXEC false
 #define FC_EXEC true
 
-#define RTW89_COEX_ACT1_WORK_PERIOD	round_jiffies_relative(HZ * 4)
+#define RTW89_COEX_ACT1_WORK_PERIOD	round_jiffies_relative(HZ)
 #define RTW89_COEX_BT_DEVINFO_WORK_PERIOD	round_jiffies_relative(HZ * 16)
 #define RTW89_COEX_RFK_CHK_WORK_PERIOD	msecs_to_jiffies(300)
 #define BTC_RFK_PATH_MAP GENMASK(3, 0)
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 07/14] wifi: rtw89: coex: complete GPIO debug configuration handler
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

Complete the implementation of _fw_set_gpio() function to support all
GPIO control configuration types for coexistence. Included debug signal,
antenna switch, external I2C mailbox, external PTA related GPIO
configuration. This function is called during initialization and when
BT re-enables.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 234 +++++++++++++++++++++-
 drivers/net/wireless/realtek/rtw89/coex.h |  35 ----
 drivers/net/wireless/realtek/rtw89/core.h | 162 ++++++++++++++-
 drivers/net/wireless/realtek/rtw89/fw.h   |  10 +
 4 files changed, 392 insertions(+), 49 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index ae458917ecf7..96ec5fa20422 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -3206,6 +3206,212 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type,
 		rtw89_set_coex_ctrl_lps(rtwdev, btc->btc_ctrl_lps);
 }
 
+static u8 _get_gpiosig_for_ver(struct rtw89_dev *rtwdev, u8 sig_v8)
+{
+	const struct rtw89_btc_ver *ver = rtwdev->btc.ver;
+
+	/* v8: gpio_ver >= 8, directly use v8 signal ID */
+	if (ver->fcxgpiodbg >= 8)
+		return sig_v8;
+
+	/* v7: gpio_ver < 8, convert v8 signal ID to v7 signal ID */
+	if (sig_v8 <= BTC_DBG_GNT_WL) {
+		return sig_v8;
+	} else if (sig_v8 <= BTC_DBG_GNT_WL1) {
+		rtw89_debug(rtwdev, RTW89_DBG_BTC,
+			    "[BTC], %s(): sig %d not supported in v7\n",
+			    __func__, sig_v8);
+		return BTC_DBG_NUM;
+	} else if (sig_v8 >= BTC_DBG_NUM) {
+		rtw89_debug(rtwdev, RTW89_DBG_BTC,
+			    "[BTC], %s(): sig %d not available\n",
+			    __func__, sig_v8);
+		return BTC_DBG_NUM;
+	} else {
+		return sig_v8 - 2;
+	}
+}
+
+static u32 _convert_gpio_enmap_to_ver(struct rtw89_dev *rtwdev, u32 en_map_v8)
+{
+	const struct rtw89_btc_ver *ver = rtwdev->btc.ver;
+	u32 en_map_v7 = 0;
+	u32 bit;
+
+	/* v8: gpio_ver >= 8, directly use v8 en_map */
+	if (ver->fcxgpiodbg >= 8)
+		return en_map_v8;
+
+	/* v7: gpio_ver < 8, convert v8 en_map bitmap to v7 en_map bitmap */
+	/* bit 0-1: GNT_BT, GNT_WL - same position in both versions */
+	en_map_v7 |= en_map_v8 & GENMASK(1, 0);
+
+	/* bit 2-3: GNT_BT1, GNT_WL1 - not supported in v7, skip these bits */
+
+	/* bit 4-31: shift right by 2 positions (become bit 2-29 in v7) */
+	for (bit = BTC_DBG_BCN_EARLY; bit < BTC_DBG_NUM; bit++) {
+		if (en_map_v8 & BIT(bit))
+			en_map_v7 |= BIT(bit - 2);
+	}
+
+	return en_map_v7;
+}
+
+static u32 _convert_gpio_enmap_from_ver(struct rtw89_dev *rtwdev, u32 en_map)
+{
+	const struct rtw89_btc_ver *ver = rtwdev->btc.ver;
+	u32 en_map_v8 = 0;
+	u32 bit;
+
+	if (ver->fcxgpiodbg >= 8)
+		return en_map;
+
+	/* bit 0-1: GNT_BT, GNT_WL - same position in both versions */
+	en_map_v8 |= en_map & GENMASK(1, 0);
+
+	/* bit 2-29: shift left by 2 positions (become bit 4-31 in v8) */
+	for (bit = BTC_DBG_GNT_BT1; bit < BTC_DBG_NUM - 2; bit++) {
+		if (en_map & BIT(bit))
+			en_map_v8 |= BIT(bit + 2);
+	}
+
+	return en_map_v8;
+}
+
+static void _fw_set_gpio(struct rtw89_dev *rtwdev, u8 type, u32 val)
+{
+	struct rtw89_btc *btc = &rtwdev->btc;
+	struct rtw89_fbtc_h2c_set_gpio *gpio = &btc->gpio;
+	const struct rtw89_btc_ver *ver = btc->ver;
+	struct rtw89_fbtc_h2c_set_gpio_2b l2_h2c;
+	struct rtw89_fbtc_h2c_set_gpio_4b l4_h2c;
+	u8 gpio_ver = ver->fcxgpiodbg;
+	u8 buf[sizeof(l4_h2c)];
+	u8 len;
+
+	if (gpio_ver < 8 && type > CXDGPIO_MUX_MAP)
+		return;
+
+	if (type >= CXDGPIO_MAX)
+		return;
+
+	switch (type) {
+	case CXDGPIO_EN_MAP: /* GPIO debug signal en-map 0~31 */
+		val = _convert_gpio_enmap_to_ver(rtwdev, val);
+		gpio->en_map.data.type = CXDGPIO_EN_MAP;
+		gpio->en_map.data.fver = gpio_ver;
+		gpio->en_map.data.dlen = CXDGPIO_SET_L4;
+		gpio->en_map.data.en_map = val;
+		l4_h2c = gpio->en_map.fmt;
+		put_unaligned_le32(val, (u32 *)l4_h2c.data);
+		memcpy(buf, &l4_h2c, sizeof(l4_h2c));
+		len = sizeof(l4_h2c);
+		break;
+	case CXDGPIO_MUX_MAP: /* GPIO dbg: Signal to GPIO Mux */
+		gpio->mux.data.type = CXDGPIO_MUX_MAP;
+		gpio->mux.data.fver = gpio_ver;
+		gpio->mux.data.dlen = CXDGPIO_SET_L2;
+		gpio->mux.data.sig = _get_gpiosig_for_ver(rtwdev,
+							  FIELD_GET(GENMASK(7, 0), val));
+		if (gpio->mux.data.sig == 0xff)
+			return;
+		gpio->mux.data.gpio = FIELD_GET(GENMASK(15, 8), val);
+		l2_h2c = gpio->mux.fmt;
+		memcpy(buf, &l2_h2c, sizeof(l2_h2c));
+		len = sizeof(l2_h2c);
+		break;
+	case CXDGPIO_EXT_HPTA: /* GPIO config for Ext HW-PTA */
+		gpio->ext_pta.data.type = CXDGPIO_EXT_HPTA;
+		gpio->ext_pta.data.fver = gpio_ver;
+		gpio->ext_pta.data.dlen = CXDGPIO_SET_L2;
+		gpio->ext_pta.data.map_low = FIELD_GET(GENMASK(7, 0), val);
+		gpio->ext_pta.data.map_high = FIELD_GET(GENMASK(15, 8), val);
+		l2_h2c = gpio->ext_pta.fmt;
+		memcpy(buf, &l2_h2c, sizeof(l2_h2c));
+		len = sizeof(l2_h2c);
+		break;
+	case CXDGPIO_EXT_HMBX: /* GPIO config for Ext HW-mailbox */
+		gpio->ext_mb.data.type = CXDGPIO_EXT_HMBX;
+		gpio->ext_mb.data.fver = gpio_ver;
+		gpio->ext_mb.data.dlen = CXDGPIO_SET_L2;
+		gpio->ext_mb.data.map_low = FIELD_GET(GENMASK(7, 0), val);
+		gpio->ext_mb.data.map_high = FIELD_GET(GENMASK(15, 8), val);
+		l2_h2c = gpio->ext_mb.fmt;
+		memcpy(buf, &l2_h2c, sizeof(l2_h2c));
+		len = sizeof(l2_h2c);
+		break;
+	case CXDGPIO_EXT_SWOUT: /* GPIO config for Ext SW output (wlan_act) */
+		gpio->ext_swout.data.type = CXDGPIO_EXT_SWOUT;
+		gpio->ext_swout.data.fver = gpio_ver;
+		gpio->ext_swout.data.dlen = CXDGPIO_SET_L2;
+		gpio->ext_swout.data.map_low = FIELD_GET(GENMASK(7, 0), val);
+		gpio->ext_swout.data.map_high = FIELD_GET(GENMASK(15, 8), val);
+		l2_h2c = gpio->ext_swout.fmt;
+		memcpy(buf, &l2_h2c, sizeof(l2_h2c));
+		len = sizeof(l2_h2c);
+		break;
+	case CXDGPIO_EXT_SWIN: /* GPIO config for Ext SW input control */
+		gpio->ext_swin.data.type = CXDGPIO_EXT_SWIN;
+		gpio->ext_swin.data.fver = gpio_ver;
+		gpio->ext_swin.data.dlen = CXDGPIO_SET_L4;
+		gpio->ext_swin.data.in_map_low = FIELD_GET(GENMASK(7, 0), val);
+		gpio->ext_swin.data.in_map_high = FIELD_GET(GENMASK(15, 8), val);
+		gpio->ext_swin.data.int_map_low = FIELD_GET(GENMASK(23, 16), val);
+		gpio->ext_swin.data.int_map_high = FIELD_GET(GENMASK(31, 24), val);
+		l4_h2c = gpio->ext_swin.fmt;
+		put_unaligned_le32(val, (u32 *)l4_h2c.data);
+		memcpy(buf, &l4_h2c, sizeof(l4_h2c));
+		len = sizeof(l4_h2c);
+		break;
+	default:
+		return;
+	}
+
+	_send_fw_cmd(rtwdev, BTFC_SET, SET_GPIO_DBG, buf, len);
+}
+
+static void _set_ext_interface(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_btc *btc = &rtwdev->btc;
+	struct rtw89_btc_cx *cx = &btc->cx;
+	u8 bt1_sw_type = BTC_SWITCH_INTERNAL;
+	u32 val;
+
+	if (btc->ver->fcxgpiodbg < 7)
+		return;
+
+	/* if BT1+WL-S0, route DBG_GNT_BT1 to control SPDT */
+	if ((rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) &&
+	    btc->ver->fcxinit >= 10) {
+		if (btc->ver->fcxinit >= 10)
+			bt1_sw_type = btc->mdinfo.bt1_sw_type;
+		else
+			return;
+
+		if (bt1_sw_type > BTC_SWITCH_INTERNAL) {
+			_fw_set_gpio(rtwdev, CXDGPIO_EN_MAP,
+				     BIT(BTC_DBG_GNT_BT1));
+
+			val = (bt1_sw_type << 8) + BTC_DBG_GNT_BT1;
+			_fw_set_gpio(rtwdev, CXDGPIO_MUX_MAP, val);
+		}
+	}
+
+	/* set GPIO as Ext-PTA interface wire */
+	if (cx->bt_ext.hw_coex & BTC_EXTSOC_INTF_PTA)
+		_fw_set_gpio(rtwdev, CXDGPIO_EXT_HPTA, cx->bt_ext.hpta_cfg);
+
+	/* set GPIO as Ext-mailbox interface wire */
+	if (cx->bt_ext.hw_coex & BTC_EXTSOC_INTF_MBX)
+		_fw_set_gpio(rtwdev, CXDGPIO_EXT_HMBX, cx->bt_ext.hmbx_cfg);
+
+	/* set GPIO as Ext-SWIO interface wire */
+	if (cx->bt_ext.hw_coex & BTC_EXTSOC_INTF_SWIO) {
+		_fw_set_gpio(rtwdev, CXDGPIO_EXT_SWOUT, cx->bt_ext.swout_cfg);
+		_fw_set_gpio(rtwdev, CXDGPIO_EXT_SWIN, cx->bt_ext.swin_cfg);
+	}
+}
+
 static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index)
 {
 	struct rtw89_btc *btc = &rtwdev->btc;
@@ -8251,6 +8457,7 @@ static void _set_init_info(struct rtw89_dev *rtwdev)
 	_fw_set_drv_info(rtwdev, CXDRVINFO_CTRL);
 	rtw89_btc_fw_set_slots(rtwdev);
 	btc_fw_set_monreg(rtwdev);
+	_set_ext_interface(rtwdev);
 	_set_wl_tx_power(rtwdev, RTW89_BTC_WL_DEF_TX_PWR, RTW89_PHY_0);
 }
 
@@ -9779,6 +9986,8 @@ static const char *id_to_gdbg(u32 id)
 	switch (id) {
 	CASE_BTC_GDBG_STR(GNT_BT);
 	CASE_BTC_GDBG_STR(GNT_WL);
+	CASE_BTC_GDBG_STR(GNT_BT1);
+	CASE_BTC_GDBG_STR(GNT_WL1);
 	CASE_BTC_GDBG_STR(BCN_EARLY);
 	CASE_BTC_GDBG_STR(WL_NULL0);
 	CASE_BTC_GDBG_STR(WL_NULL1);
@@ -9807,8 +10016,6 @@ static const char *id_to_gdbg(u32 id)
 	CASE_BTC_GDBG_STR(SLOT_B1FDD);
 	CASE_BTC_GDBG_STR(BT_CHANGE);
 	CASE_BTC_GDBG_STR(WL_CCA);
-	CASE_BTC_GDBG_STR(BT_LEAUDIO);
-	CASE_BTC_GDBG_STR(USER_DEF);
 	default:
 		return "unknown";
 	}
@@ -11487,8 +11694,8 @@ static int _show_gpio_dbg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 	struct rtw89_btc_rpt_cmn_info *pcinfo = NULL;
 	union rtw89_btc_fbtc_gpio_dbg *gdbg = NULL;
 	char *p = buf, *end = buf + bufsz;
-	u8 *gpio_map, i;
-	u32 en_map;
+	u32 en_map_raw, en_map;
+	u8 *gpio_map, i, sig;
 
 	pcinfo = &pfwinfo->rpt_fbtc_gpio_dbg.cinfo;
 	gdbg = &rtwdev->btc.fwinfo.rpt_fbtc_gpio_dbg.finfo;
@@ -11499,27 +11706,32 @@ static int _show_gpio_dbg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		goto out;
 	}
 
-	if (ver->fcxgpiodbg == 7) {
-		en_map = le32_to_cpu(gdbg->v7.en_map);
+	if (ver->fcxgpiodbg == 7 || ver->fcxgpiodbg == 8) {
+		en_map_raw = le32_to_cpu(gdbg->v7.en_map);
 		gpio_map = gdbg->v7.gpio_map;
 	} else {
-		en_map = le32_to_cpu(gdbg->v1.en_map);
+		en_map_raw = le32_to_cpu(gdbg->v1.en_map);
 		gpio_map = gdbg->v1.gpio_map;
 	}
 
+	en_map = _convert_gpio_enmap_from_ver(rtwdev, en_map_raw);
 	if (!en_map)
 		goto out;
 
-	p += scnprintf(p, end - p, " %-15s : enable_map:0x%08x",
+	p += scnprintf(p, end - p, "\n\r %-15s : enable_map:0x%08x",
 		       "[gpio_dbg]", en_map);
 
-	for (i = 0; i < BTC_DBG_MAX1; i++) {
+	for (i = 0; i < BTC_DBG_NUM; i++) {
 		if (!(en_map & BIT(i)))
 			continue;
+
+		sig = ver->fcxgpiodbg >= 8 ? i : _get_gpiosig_for_ver(rtwdev, i);
+		if (sig >= BTC_DBG_NUM)
+			continue;
+
 		p += scnprintf(p, end - p, ", %s->GPIO%d", id_to_gdbg(i),
-			       gpio_map[i]);
+			       gpio_map[sig]);
 	}
-	p += scnprintf(p, end - p, "\n");
 
 out:
 	return p - buf;
diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h
index 355784941edd..3e9e1510c44f 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.h
+++ b/drivers/net/wireless/realtek/rtw89/coex.h
@@ -315,41 +315,6 @@ enum btc_mlo_rf_combin {
 	BTC_MLO_RF_2_PLUS_2 = 3,
 };
 
-enum btc_wl_gpio_debug {
-	BTC_DBG_GNT_BT = 0,
-	BTC_DBG_GNT_WL = 1,
-	BTC_DBG_BCN_EARLY = 2,
-	BTC_DBG_WL_NULL0 = 3,
-	BTC_DBG_WL_NULL1 = 4,
-	BTC_DBG_WL_RXISR = 5,
-	BTC_DBG_TDMA_ENTRY = 6,
-	BTC_DBG_A2DP_EMPTY = 7,
-	BTC_DBG_BT_RETRY = 8,
-	BTC_DBG_BT_RELINK = 9,
-	BTC_DBG_SLOT_WL = 10,
-	BTC_DBG_SLOT_BT = 11,
-	BTC_DBG_WL_ERR = 12,
-	BTC_DBG_WL_OK = 13,
-	BTC_DBG_SLOT_B2W = 14,
-	BTC_DBG_SLOT_W1 = 15,
-	BTC_DBG_SLOT_W2 = 16,
-	BTC_DBG_SLOT_W2B = 17,
-	BTC_DBG_SLOT_B1 = 18,
-	BTC_DBG_SLOT_B2 = 19,
-	BTC_DBG_SLOT_B3 = 20,
-	BTC_DBG_SLOT_B4 = 21,
-	BTC_DBG_SLOT_LK = 22,
-	BTC_DBG_SLOT_E2G = 23,
-	BTC_DBG_SLOT_E5G = 24,
-	BTC_DBG_SLOT_EBT = 25,
-	BTC_DBG_SLOT_WLK = 26,
-	BTC_DBG_SLOT_B1FDD = 27,
-	BTC_DBG_BT_CHANGE = 28,
-	BTC_DBG_WL_CCA = 29,
-	BTC_DBG_BT_LEAUDIO = 30,
-	BTC_DBG_USER_DEF = 31,
-};
-
 void rtw89_btc_init(struct rtw89_dev *rtwdev);
 void rtw89_btc_ntfy_poweron(struct rtw89_dev *rtwdev);
 void rtw89_btc_ntfy_poweroff(struct rtw89_dev *rtwdev);
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index 0ec63f9510bd..ba3a9b0d4aa2 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -3074,14 +3074,55 @@ enum rtw89_btc_afh_map_type { /*AFH MAP TYPE */
 	RPT_BT_AFH_SEQ_LE = 0x20
 };
 
-#define BTC_DBG_MAX1  32
+enum btc_wl_gpio_debug {
+	BTC_DBG_GNT_BT = 0,
+	BTC_DBG_GNT_WL = 1,
+	BTC_DBG_GNT_BT1 = 2,
+	BTC_DBG_GNT_WL1 = 3,
+	/* The following signals should 0-1 tiggle by each function-call */
+	BTC_DBG_BCN_EARLY = 4,
+	BTC_DBG_WL_NULL0 = 5,
+	BTC_DBG_WL_NULL1 = 6,
+	BTC_DBG_WL_RXISR = 7,
+	BTC_DBG_TDMA_ENTRY = 8,
+	BTC_DBG_A2DP_EMPTY = 9,
+	BTC_DBG_BT_RETRY = 10,
+	/* The following signals should 0-1 tiggle by state L/H */
+	BTC_DBG_BT_RELINK = 11,
+	BTC_DBG_SLOT_WL = 12,
+	BTC_DBG_SLOT_BT = 13,
+	/* The following signals should 0-1 tiggle by external*/
+	BTC_DBG_WL_ERR = 14,
+	BTC_DBG_WL_OK = 15,
+	/* The following signals appear only 1-active at same time*/
+	BTC_DBG_SLOT_B2W = 16,
+	BTC_DBG_SLOT_W1 = 17,
+	BTC_DBG_SLOT_W2 = 18,
+	BTC_DBG_SLOT_W2B = 19,
+	BTC_DBG_SLOT_B1 = 20,
+	BTC_DBG_SLOT_B2 = 21,
+	BTC_DBG_SLOT_B3 = 22,
+	BTC_DBG_SLOT_B4 = 23,
+	BTC_DBG_SLOT_LK = 24,
+	BTC_DBG_SLOT_E2G = 25,
+	BTC_DBG_SLOT_E5G = 26,
+	BTC_DBG_SLOT_EBT = 27,
+	BTC_DBG_SLOT_WLK = 28,
+	BTC_DBG_SLOT_B1FDD = 29,
+	BTC_DBG_BT_CHANGE = 30,
+	/* The following signals should 0-1 tiggle by external*/
+	BTC_DBG_WL_CCA = 31,
+
+	BTC_DBG_NUM,
+};
+
 struct rtw89_btc_fbtc_gpio_dbg_v1 {
 	u8 fver; /* btc_ver::fcxgpiodbg */
 	u8 rsvd;
 	__le16 rsvd2;
 	__le32 en_map; /* which debug signal (see btc_wl_gpio_debug) is enable */
 	__le32 pre_state; /* the debug signal is 1 or 0  */
-	u8 gpio_map[BTC_DBG_MAX1]; /*the debug signals to GPIO-Position */
+	u8 gpio_map[BTC_DBG_NUM]; /*the debug signals to GPIO-Position */
 } __packed;
 
 struct rtw89_btc_fbtc_gpio_dbg_v7 {
@@ -3090,7 +3131,7 @@ struct rtw89_btc_fbtc_gpio_dbg_v7 {
 	u8 rsvd1;
 	u8 rsvd2;
 
-	u8 gpio_map[BTC_DBG_MAX1];
+	u8 gpio_map[BTC_DBG_NUM];
 
 	__le32 en_map;
 	__le32 pre_state;
@@ -3101,6 +3142,120 @@ union rtw89_btc_fbtc_gpio_dbg {
 	struct rtw89_btc_fbtc_gpio_dbg_v7 v7;
 };
 
+/*
+ * SET_GPIO_CTRL payload (max len = 7 bytes)
+ *
+ * type = CXDGPIO_EN_MAP
+ *   data.val[31:0] = debug signal enable map
+ *
+ * type = CXDGPIO_MUX_MAP
+ *   data.mux.sig  = debug signal id
+ *   data.mux.gpio = GPIO id
+ *
+ * type = CXDGPIO_EXT_HPTA / CXDGPIO_EXT_HMBX / CXDGPIO_EXT_SWOUT
+ *   data.map.map_low  = GPIO 7~0 map
+ *   data.map.map_high = GPIO 15~8 map
+ *
+ * type = CXDGPIO_EXT_SWIN
+ *   data.swin.in_map_low  = GPIO 7~0 input-en-map
+ *   data.swin.in_map_high = GPIO 15~8 input-en-map
+ *   data.swin.int_map_low = GPIO 7~0 interrupt source map
+ *   data.swin.int_map_high = GPIO 15~8 interrupt source map
+ */
+#define CXDGPIO_SET_L4 4
+#define CXDGPIO_SET_L2 2
+struct rtw89_fbtc_h2c_set_gpio_en_map {
+	u8 type; /* gpio_type */
+	u8 fver; /* FCX_VER_GPIODBG */
+	u8 dlen;
+	u32 en_map;
+};
+
+struct rtw89_fbtc_h2c_set_gpio_mux {
+	u8 type; /* gpio_type */
+	u8 fver; /* FCX_VER_GPIODBG */
+	u8 dlen;
+	u8 sig;
+	u8 gpio;
+};
+
+struct rtw89_fbtc_h2c_set_gpio_ext_pta {
+	u8 type; /* gpio_type */
+	u8 fver; /* FCX_VER_GPIODBG */
+	u8 dlen;
+	u8 map_low;
+	u8 map_high;
+};
+
+struct rtw89_fbtc_h2c_set_gpio_ext_mb {
+	u8 type; /* gpio_type */
+	u8 fver; /* FCX_VER_GPIODBG */
+	u8 dlen;
+	u8 map_low;
+	u8 map_high;
+};
+
+struct rtw89_fbtc_h2c_set_gpio_ext_swout {
+	u8 type; /* gpio_type */
+	u8 fver; /* FCX_VER_GPIODBG */
+	u8 dlen;
+	u8 map_low;
+	u8 map_high;
+};
+
+struct rtw89_fbtc_h2c_set_gpio_ext_swin {
+	u8 type; /* gpio_type */
+	u8 fver; /* FCX_VER_GPIODBG */
+	u8 dlen;
+	u8 in_map_low;
+	u8 in_map_high;
+	u8 int_map_low;
+	u8 int_map_high;
+};
+
+struct rtw89_fbtc_h2c_set_gpio_2b {
+	u8 type; /* gpio_type */
+	u8 fver; /* FCX_VER_GPIODBG */
+	u8 dlen;
+	u8 data[CXDGPIO_SET_L2];
+} __packed;
+
+struct rtw89_fbtc_h2c_set_gpio_4b {
+	u8 type; /* gpio_type */
+	u8 fver; /* FCX_VER_GPIODBG */
+	u8 dlen;
+	u8 data[CXDGPIO_SET_L4];
+} __packed;
+
+union rtw89_fbtc_h2c_set_gpio_en_map_u {
+	struct rtw89_fbtc_h2c_set_gpio_4b fmt;
+	struct rtw89_fbtc_h2c_set_gpio_en_map data;
+};
+
+union rtw89_fbtc_h2c_set_gpio_mux_u {
+	struct rtw89_fbtc_h2c_set_gpio_2b fmt;
+	struct rtw89_fbtc_h2c_set_gpio_mux data;
+};
+
+union rtw89_fbtc_h2c_set_gpio_ext_pta_u {
+	struct rtw89_fbtc_h2c_set_gpio_2b fmt;
+	struct rtw89_fbtc_h2c_set_gpio_ext_pta data;
+};
+
+union rtw89_fbtc_h2c_set_gpio_ext_swin_u {
+	struct rtw89_fbtc_h2c_set_gpio_4b fmt;
+	struct rtw89_fbtc_h2c_set_gpio_ext_swin data;
+};
+
+struct rtw89_fbtc_h2c_set_gpio {
+	union rtw89_fbtc_h2c_set_gpio_en_map_u en_map;
+	union rtw89_fbtc_h2c_set_gpio_mux_u mux;
+	union rtw89_fbtc_h2c_set_gpio_ext_pta_u ext_pta;
+	union rtw89_fbtc_h2c_set_gpio_ext_pta_u ext_mb;
+	union rtw89_fbtc_h2c_set_gpio_ext_pta_u ext_swout;
+	union rtw89_fbtc_h2c_set_gpio_ext_swin_u ext_swin;
+};
+
 struct rtw89_btc_fbtc_mreg_val_v1 {
 	u8 fver; /* btc_ver::fcxmreg */
 	u8 reg_num;
@@ -4178,6 +4333,7 @@ struct rtw89_btc {
 	struct rtw89_btc_module mdinfo;
 	struct rtw89_btc_btf_fwinfo fwinfo;
 	struct rtw89_btc_dbg dbg;
+	struct rtw89_fbtc_h2c_set_gpio gpio;
 
 	struct wiphy_work eapol_notify_work;
 	struct wiphy_work arp_notify_work;
diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h
index 538df0fb42cc..6147960521ee 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.h
+++ b/drivers/net/wireless/realtek/rtw89/fw.h
@@ -2391,6 +2391,16 @@ enum rtw89_btc_cxdrvinfo {
 	CXDRVINFO_MAX,
 };
 
+enum rtw89_fbtc_gpio_type {
+	CXDGPIO_EN_MAP = 0x0,
+	CXDGPIO_MUX_MAP = 0x1,
+	CXDGPIO_EXT_HPTA = 0x2,
+	CXDGPIO_EXT_HMBX = 0x3,
+	CXDGPIO_EXT_SWOUT = 0x4,
+	CXDGPIO_EXT_SWIN = 0x5,
+	CXDGPIO_MAX,
+};
+
 enum rtw89_scan_mode {
 	RTW89_SCAN_IMMEDIATE,
 	RTW89_SCAN_DELAY,
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 08/14] wifi: rtw89: coex: Refine RF calibration notify flow
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

BT-coexistence only needs to record RF calibration is doing or not, don't
need to record the status of the calibration steps.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 96ec5fa20422..187e7e70e86d 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -9144,14 +9144,13 @@ static bool _ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_path,
 		wl->rfk_info.state = BTC_WRFK_START;
 
 		btc->cx.wl.wcnt[BTC_WCNT_RFK_REQ]++;
-		btc->cx.wl.wcnt[BTC_WCNT_RFK_GO]++;
 		btc->dm.cnt_notify[BTC_NCNT_WL_RFK]++;
 
 		_write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLRFK, true);
 		break;
 	case BTC_WRFK_ONESHOT_START:
 	case BTC_WRFK_ONESHOT_STOP:
-		wl->rfk_info.state = state;
+		btc->cx.wl.wcnt[BTC_WCNT_RFK_GO]++;
 		if (type != BTC_WRFKT_RXDCK)
 			return BTC_WRFK_ALLOW;
 		break;
@@ -9165,7 +9164,7 @@ static bool _ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_path,
 	default:
 		rtw89_debug(rtwdev, RTW89_DBG_BTC,
 			    "[BTC], %s() warning state=%d\n", __func__, state);
-		break;
+		return result;
 	}
 
 	if (result == BTC_WRFK_ALLOW) {
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 09/14] wifi: rtw89: coex: Fix log dump format with proper newline
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

Fix the log output format in _show_mreg_v7() where the phy-0 gnt_status
line was missing the proper field label and newline. Use the standard
" %-15s : " format with "[gnt_status]" label consistent with the rest
of the dump output.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 167 ++++++++++++----------
 1 file changed, 88 insertions(+), 79 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 187e7e70e86d..5aa06ef430c8 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -9656,8 +9656,10 @@ static int _show_wl_role_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 				       plink->client_cnt - 1, plink->mode,
 				       plink->ch, plink->bw);
 
-			if (plink->connected == MLME_NO_LINK)
+			if (plink->connected == MLME_NO_LINK) {
+				p += scnprintf(p, end - p, "\n");
 				continue;
+			}
 
 			p += scnprintf(p, end - p,
 				       ", mac_id=%d, max_tx_time=%dus, max_tx_retry=%d\n",
@@ -9747,7 +9749,7 @@ static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t buf
 
 	if (hid.exist) {
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : type:%s%s%s%s%s pair-cnt:%d, sut_pwr:%d, golden-rx:%d\n",
+			       "\n %-15s : type:%s%s%s%s%s pair-cnt:%d, sut_pwr:%d, golden-rx:%d\n",
 			       "[HID]",
 			       hid.type & BTC_HID_218 ? "2/18," : "",
 			       hid.type & BTC_HID_418 ? "4/18," : "",
@@ -10345,7 +10347,7 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 			"" : "(Mismatch!!)"));
 
 	p += scnprintf(p, end - p,
-		       " %-15s : wl[rssi_lvl:%d/para:%d/tx_pwr:[%d %d]/rx_lvl:[%d %d]/lna2:%d/stb_chg:%d]\n ",
+		       " %-15s : wl[rssi_lvl:%d/para:%d/tx_pwr:[%d %d]/rx_lvl:[%d %d]/lna2:%d/stb_chg:%d]\n",
 		       "[dm_rf_ctrl]",
 		       wl->rssi_level, dm->trx_para_level,
 		       dm->rf_trx_para.wl_tx_power[RTW89_PHY_0],
@@ -10355,7 +10357,7 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		       dm->wl_lna2, dm->wl_stb_chg);
 
 	p += scnprintf(p, end - p,
-		       " %-15s : pre_agc:%d, btg_rx:%d\n ",
+		       " %-15s : pre_agc:%d, btg_rx:%d\n",
 		       "[dm_bb_ctrl]", dm->wl_pre_agc, dm->wl_btg_rx);
 
 	p += scnprintf(p, end - p,
@@ -10467,11 +10469,9 @@ static int _show_fbtc_tdma(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		       t->bind, t->leak_n, t->ext_ctrl);
 
 	p += scnprintf(p, end - p,
-		       "policy_type:%d",
+		       "policy_type:%d\n",
 		       (u32)btc->policy_type);
 
-	p += scnprintf(p, end - p, "\n");
-
 	return p - buf;
 }
 
@@ -10778,11 +10778,9 @@ static int _show_fbtc_cysta_v3(struct rtw89_dev *rtwdev, char *buf, size_t bufsz
 			       le16_to_cpu(pcysta->a2dp_ept.cnt),
 			       le16_to_cpu(pcysta->a2dp_ept.cnt_timeout));
 
-		p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d",
+		p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n",
 			       le16_to_cpu(pcysta->a2dp_ept.tavg),
 			       le16_to_cpu(pcysta->a2dp_ept.tmax));
-
-		p += scnprintf(p, end - p, "\n");
 	}
 
 out:
@@ -10917,11 +10915,9 @@ static int _show_fbtc_cysta_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz
 			       le16_to_cpu(pcysta->a2dp_ept.cnt),
 			       le16_to_cpu(pcysta->a2dp_ept.cnt_timeout));
 
-		p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d",
+		p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n",
 			       le16_to_cpu(pcysta->a2dp_ept.tavg),
 			       le16_to_cpu(pcysta->a2dp_ept.tmax));
-
-		p += scnprintf(p, end - p, "\n");
 	}
 
 out:
@@ -11055,11 +11051,9 @@ static int _show_fbtc_cysta_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz
 			       le16_to_cpu(pcysta->a2dp_ept.cnt),
 			       le16_to_cpu(pcysta->a2dp_ept.cnt_timeout));
 
-		p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d",
+		p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n",
 			       le16_to_cpu(pcysta->a2dp_ept.tavg),
 			       le16_to_cpu(pcysta->a2dp_ept.tmax));
-
-		p += scnprintf(p, end - p, "\n");
 	}
 
 out:
@@ -11193,11 +11187,9 @@ static int _show_fbtc_cysta_v105(struct rtw89_dev *rtwdev, char *buf, size_t buf
 			       le16_to_cpu(pcysta->a2dp_ept.cnt),
 			       le16_to_cpu(pcysta->a2dp_ept.cnt_timeout));
 
-		p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d",
+		p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n",
 			       le16_to_cpu(pcysta->a2dp_ept.tavg),
 			       le16_to_cpu(pcysta->a2dp_ept.tmax));
-
-		p += scnprintf(p, end - p, "\n");
 	}
 
 out:
@@ -11246,7 +11238,7 @@ static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz
 			       le16_to_cpu(pcysta->skip_cnt));
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : avg_t[wl:%d/bt:%d/lk:%d.%03d]",
+		       "\n %-15s : avg_t[wl:%d/bt:%d/lk:%d.%03d]",
 		       "[cycle_stat]",
 		       le16_to_cpu(pcysta->cycle_time.tavg[CXT_WL]),
 		       le16_to_cpu(pcysta->cycle_time.tavg[CXT_BT]),
@@ -11267,7 +11259,7 @@ static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz
 
 	if (a2dp->exist) {
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : a2dp_ept:%d, a2dp_late:%d(streak 2S:%d/max:%d)",
+			       "\n %-15s : a2dp_ept:%d, a2dp_late:%d(streak 2S:%d/max:%d)",
 			       "[a2dp_stat]",
 			       le16_to_cpu(pcysta->a2dp_ept.cnt),
 			       le16_to_cpu(pcysta->a2dp_ept.cnt_timeout),
@@ -11306,10 +11298,10 @@ static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz
 
 		if (cnt % divide_cnt == 1) {
 			if (a2dp->exist)
-				p += scnprintf(p, end - p, "\n\r %-15s : ",
+				p += scnprintf(p, end - p, "\n %-15s : ",
 					       "[slotT_wermtan]");
 			else
-				p += scnprintf(p, end - p, "\n\r %-15s : ",
+				p += scnprintf(p, end - p, "\n %-15s : ",
 					       "[slotT_rxerr]");
 		}
 
@@ -11370,7 +11362,7 @@ static int _show_fbtc_nullsta(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 	ns = &pfwinfo->rpt_fbtc_nullsta.finfo;
 	if (ver->fcxnullsta == 1) {
 		for (i = 0; i < 2; i++) {
-			p += scnprintf(p, end - p, " %-15s : ", "\n[NULL-STA]");
+			p += scnprintf(p, end - p, "\n %-15s : ", "[NULL-STA]");
 			p += scnprintf(p, end - p, "null-%d", i);
 			p += scnprintf(p, end - p, "[ok:%d/",
 				       le32_to_cpu(ns->v1.result[i][1]));
@@ -11389,7 +11381,7 @@ static int _show_fbtc_nullsta(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		}
 	} else if (ver->fcxnullsta == 7) {
 		for (i = 0; i < 2; i++) {
-			p += scnprintf(p, end - p, " %-15s : ", "\n[NULL-STA]");
+			p += scnprintf(p, end - p, "\n %-15s : ", "[NULL-STA]");
 			p += scnprintf(p, end - p, "null-%d", i);
 			p += scnprintf(p, end - p, "[Tx:%d/",
 				       le32_to_cpu(ns->v7.result[i][4]));
@@ -11410,7 +11402,7 @@ static int _show_fbtc_nullsta(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		}
 	} else {
 		for (i = 0; i < 2; i++) {
-			p += scnprintf(p, end - p, " %-15s : ", "\n[NULL-STA]");
+			p += scnprintf(p, end - p, "\n %-15s : ", "[NULL-STA]");
 			p += scnprintf(p, end - p, "null-%d", i);
 			p += scnprintf(p, end - p, "[Tx:%d/",
 				       le32_to_cpu(ns->v2.result[i][4]));
@@ -11717,7 +11709,7 @@ static int _show_gpio_dbg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 	if (!en_map)
 		goto out;
 
-	p += scnprintf(p, end - p, "\n\r %-15s : enable_map:0x%08x",
+	p += scnprintf(p, end - p, "\n %-15s : enable_map:0x%08x",
 		       "[gpio_dbg]", en_map);
 
 	for (i = 0; i < BTC_DBG_NUM; i++) {
@@ -11943,7 +11935,7 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 	if (!(dm->coex_info_map & BTC_COEX_INFO_MREG))
 		return 0;
 
-	p += scnprintf(p, end - p, "\n\r========== [HW Status] ==========");
+	p += scnprintf(p, end - p, "\n========== [HW Status] ==========\n");
 
 	p += scnprintf(p, end - p,
 		       " %-15s : WL->BT0:0x%08x(cnt:%d), BT0->WL:0x%08x(total:%d, bt_update:%d)\n",
@@ -11965,18 +11957,31 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 	dm->pta_owner = rtw89_mac_get_ctrl_path(rtwdev);
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : pta_owner:%s, pta_req_mac:MAC%d, rf_gnt_source: polut_type:%s",
+		       " %-15s : pta_owner:%s, pta_req_mac:MAC%d, rf_gnt_source: polut_type:%s\n",
 		       "[gnt_status]",
 		       rtwdev->chip->para_ver & BTC_FEAT_PTA_ONOFF_CTRL ? "HW" :
 		       dm->pta_owner == BTC_CTRL_BY_WL ? "WL" : "BT",
 		       wl->pta_req_mac,
 		       id_to_polut(wl->bt_polut_type[wl->pta_req_mac]));
 
-	p += scnprintf(p, end - p, ", phy-0[gnt_wl:%s-%d/gnt_bt:%s-%d]",
-		      dm->gnt_set[RTW89_PHY_0].gnt_wl_sw_en ? "SW" : "HW",
-		      dm->gnt_set[RTW89_PHY_0].gnt_wl,
-		      dm->gnt_set[RTW89_PHY_0].gnt_bt0_sw_en ? "SW" : "HW",
-		      dm->gnt_set[RTW89_PHY_0].gnt_bt0);
+	if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)
+		p += scnprintf(p, end - p,
+			       " %-15s : phy-0[gnt_wl:%s-%d/gnt_bt0:%s-%d/gnt_bt1:%s-%d]",
+			       "[gnt_status]",
+			       dm->gnt_set[RTW89_PHY_0].gnt_wl_sw_en ? "SW" : "HW",
+			       dm->gnt_set[RTW89_PHY_0].gnt_wl,
+			       dm->gnt_set[RTW89_PHY_0].gnt_bt0_sw_en ? "SW" : "HW",
+			       dm->gnt_set[RTW89_PHY_0].gnt_bt0,
+			       dm->gnt_set[RTW89_PHY_0].gnt_bt1_sw_en ? "SW" : "HW",
+			       dm->gnt_set[RTW89_PHY_0].gnt_bt1);
+	else
+		p += scnprintf(p, end - p,
+			       " %-15s : phy-0[gnt_wl:%s-%d/gnt_bt:%s-%d]",
+			       "[gnt_status]",
+			       dm->gnt_set[RTW89_PHY_0].gnt_wl_sw_en ? "SW" : "HW",
+			       dm->gnt_set[RTW89_PHY_0].gnt_wl,
+			       dm->gnt_set[RTW89_PHY_0].gnt_bt0_sw_en ? "SW" : "HW",
+			       dm->gnt_set[RTW89_PHY_0].gnt_bt0);
 
 	if (rtwdev->dbcc_en) {
 		p += scnprintf(p, end - p,
@@ -12000,7 +12005,7 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 
 		if (cnt % 6 == 0)
 			p += scnprintf(p, end - p,
-				       "\n\r %-15s : %s_0x%x=0x%x", "[reg]",
+				       "\n %-15s : %s_0x%x=0x%x", "[reg]",
 				       id_to_regtype(type), offset, val);
 		else
 			p += scnprintf(p, end - p, ", %s_0x%x=0x%x",
@@ -12336,12 +12341,11 @@ static int _show_summary_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		       cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]);
 
 	p += scnprintf(p, end - p,
-		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d",
+		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n",
 		       cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE],
 		       cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK],
 		       cnt[BTC_NCNT_WL_STA]);
 
-	p += scnprintf(p, end - p, "\n");
 	p += scnprintf(p, end - p,
 		       " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, special_pkt=%d, ",
 		       "[notify_cnt]",
@@ -12457,12 +12461,11 @@ static int _show_summary_v105(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		       cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]);
 
 	p += scnprintf(p, end - p,
-		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d",
+		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n",
 		       cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE],
 		       cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK],
 		       cnt[BTC_NCNT_WL_STA]);
 
-	p += scnprintf(p, end - p, "\n");
 	p += scnprintf(p, end - p,
 		       " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, special_pkt=%d, ",
 		       "[notify_cnt]",
@@ -12495,7 +12498,7 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		return 0;
 
 	p += scnprintf(p, end - p, "%s",
-		       "\n\r========== [Statistics] ==========");
+		       "\n========== [Statistics] ==========\n");
 
 	pcinfo = &pfwinfo->rpt_ctrl.cinfo;
 	if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF &&
@@ -12503,7 +12506,7 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		prptctrl = &pfwinfo->rpt_ctrl.finfo.v7;
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d),"
+			       " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d),"
 			       "c2h_cnt=%d(fw_send:%d, len:%d, max:%d), ",
 			       "[summary]", pfwinfo->cnt_h2c,
 			       pfwinfo->cnt_h2c_fail,
@@ -12521,16 +12524,17 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 
 		if (dm->error.map.wl_fw_hang)
 			p += scnprintf(p, end - p, " (WL FW Hang!!)");
+		p += scnprintf(p, end - p, "\n");
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ",
+			       " %-15s : send_ok:%d, send_fail:%d, recv:%d, ",
 			       "[mailbox]",
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok),
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail),
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv));
 
 		p += scnprintf(p, end - p,
-			       "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)",
+			       "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n",
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty),
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl),
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx),
@@ -12538,7 +12542,7 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack));
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]",
+			       " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]",
 			       "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ],
 			       wl->wcnt[BTC_WCNT_RFK_GO],
 			       wl->wcnt[BTC_WCNT_RFK_REJECT],
@@ -12548,12 +12552,12 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		p += scnprintf(p, end - p, ", bt_rfk[req:%d]",
 			       le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ]));
 
-		p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]",
+		p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n",
 			       le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on),
 			       le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off));
 	} else {
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)",
+			       " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n",
 			       "[summary]",
 			       pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail,
 			       pfwinfo->cnt_c2h,
@@ -12564,19 +12568,19 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		cnt_sum += dm->cnt_notify[i];
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ",
+		       " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ",
 		       "[notify_cnt]",
 		       cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO],
 		       cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]);
 
 	p += scnprintf(p, end - p,
-		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d",
+		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n",
 		       cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE],
 		       cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK],
 		       cnt[BTC_NCNT_WL_STA]);
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ",
+		       " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ",
 		       "[notify_cnt]",
 		       cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH],
 		       cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW],
@@ -12608,7 +12612,7 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		return 0;
 
 	p += scnprintf(p, end - p, "%s",
-		       "\n\r========== [Statistics] ==========");
+		       "\n========== [Statistics] ==========\n");
 
 	pcinfo = &pfwinfo->rpt_ctrl.cinfo;
 	if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF &&
@@ -12616,7 +12620,7 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		prptctrl = &pfwinfo->rpt_ctrl.finfo.v8;
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ",
+			       " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ",
 			       "[summary]", pfwinfo->cnt_h2c,
 			       pfwinfo->cnt_h2c_fail,
 			       le16_to_cpu(prptctrl->rpt_info.cnt_h2c),
@@ -12634,16 +12638,17 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 
 		if (dm->error.map.wl_fw_hang)
 			p += scnprintf(p, end - p, " (WL FW Hang!!)");
+		p += scnprintf(p, end - p, "\n");
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ",
+			       " %-15s : send_ok:%d, send_fail:%d, recv:%d, ",
 			       "[mailbox]",
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok),
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail),
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv));
 
 		p += scnprintf(p, end - p,
-			       "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)",
+			       "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n",
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty),
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl),
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx),
@@ -12651,7 +12656,7 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack));
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]",
+			       " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]",
 			       "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ],
 			       wl->wcnt[BTC_WCNT_RFK_GO],
 			       wl->wcnt[BTC_WCNT_RFK_REJECT],
@@ -12661,12 +12666,12 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		p += scnprintf(p, end - p, ", bt_rfk[req:%d]",
 			       le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ]));
 
-		p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]",
+		p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n",
 			       le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on),
 			       le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off));
 	} else {
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)",
+			       " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n",
 			       "[summary]",
 			       pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail,
 			       pfwinfo->cnt_c2h,
@@ -12677,19 +12682,19 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		cnt_sum += dm->cnt_notify[i];
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ",
+		       " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ",
 		       "[notify_cnt]",
 		       cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO],
 		       cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]);
 
 	p += scnprintf(p, end - p,
-		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d",
+		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n",
 		       cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE],
 		       cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK],
 		       cnt[BTC_NCNT_WL_STA]);
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ",
+		       " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ",
 		       "[notify_cnt]",
 		       cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH],
 		       cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW],
@@ -12721,7 +12726,7 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		return 0;
 
 	p += scnprintf(p, end - p, "%s",
-		       "\n\r========== [Statistics] ==========");
+		       "\n========== [Statistics] ==========\n");
 
 	pcinfo = &pfwinfo->rpt_ctrl.cinfo;
 	if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF &&
@@ -12729,7 +12734,7 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		prptctrl = &pfwinfo->rpt_ctrl.finfo.v9;
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, ",
+			       " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, ",
 			       "[summary]", pfwinfo->cnt_h2c,
 			       pfwinfo->cnt_h2c_fail,
 			       le16_to_cpu(prptctrl->rpt_info.cnt_h2c),
@@ -12745,16 +12750,17 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 
 		if (dm->error.map.wl_fw_hang)
 			p += scnprintf(p, end - p, " (WL FW Hang!!)");
+		p += scnprintf(p, end - p, "\n");
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ",
+			       " %-15s : send_ok:%d, send_fail:%d, recv:%d, ",
 			       "[mailbox]",
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok),
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail),
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv));
 
 		p += scnprintf(p, end - p,
-			       "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)",
+			       "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n",
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty),
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl),
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx),
@@ -12762,7 +12768,7 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack));
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]",
+			       " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]",
 			       "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ],
 			       wl->wcnt[BTC_WCNT_RFK_GO],
 			       wl->wcnt[BTC_WCNT_RFK_REJECT],
@@ -12772,12 +12778,12 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		p += scnprintf(p, end - p, ", bt_rfk[req:%d]",
 			       le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ]));
 
-		p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]",
+		p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n",
 			       le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on),
 			       le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off));
 	} else {
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)",
+			       " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n",
 			       "[summary]",
 			       pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail,
 			       pfwinfo->cnt_c2h,
@@ -12788,19 +12794,19 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		cnt_sum += dm->cnt_notify[i];
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ",
+		       " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ",
 		       "[notify_cnt]",
 		       cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO],
 		       cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]);
 
 	p += scnprintf(p, end - p,
-		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d",
+		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n",
 		       cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE],
 		       cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK],
 		       cnt[BTC_NCNT_WL_STA]);
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ",
+		       " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ",
 		       "[notify_cnt]",
 		       cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH],
 		       cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW],
@@ -12832,7 +12838,7 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		return 0;
 
 	p += scnprintf(p, end - p, "%s",
-		       "\n\r========== [Statistics] ==========");
+		       "\n========== [Statistics] ==========\n");
 
 	pcinfo = &pfwinfo->rpt_ctrl.cinfo;
 	if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF &&
@@ -12840,7 +12846,7 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		prptctrl = &pfwinfo->rpt_ctrl.finfo.v11;
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ",
+			       " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ",
 			       "[summary]", pfwinfo->cnt_h2c,
 			       pfwinfo->cnt_h2c_fail,
 			       le16_to_cpu(prptctrl->rpt_info.cnt_h2c),
@@ -12858,16 +12864,17 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 
 		if (dm->error.map.wl_fw_hang)
 			p += scnprintf(p, end - p, " (WL FW Hang!!)");
+		p += scnprintf(p, end - p, "\n");
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ",
+			       " %-15s : send_ok:%d, send_fail:%d, recv:%d, ",
 			       "[mailbox]",
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok),
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail),
 			       le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv));
 
 		p += scnprintf(p, end - p,
-			       "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)",
+			       "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n",
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty),
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl),
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx),
@@ -12875,19 +12882,19 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 			       le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack));
 
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]",
+			       " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]",
 			       "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ],
 			       wl->wcnt[BTC_WCNT_RFK_GO],
 			       wl->wcnt[BTC_WCNT_RFK_REJECT],
 			       wl->wcnt[BTC_WCNT_RFK_TIMEOUT],
 			       wl->rfk_info.proc_time);
 
-		p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]",
+		p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n",
 			       le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on),
 			       le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off));
 	} else {
 		p += scnprintf(p, end - p,
-			       "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)",
+			       " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n",
 			       "[summary]",
 			       pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail,
 			       pfwinfo->cnt_c2h,
@@ -12898,19 +12905,19 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		cnt_sum += dm->cnt_notify[i];
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ",
+		       " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ",
 		       "[notify_cnt]",
 		       cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO],
 		       cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]);
 
 	p += scnprintf(p, end - p,
-		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d",
+		       "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n",
 		       cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE],
 		       cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK],
 		       cnt[BTC_NCNT_WL_STA]);
 
 	p += scnprintf(p, end - p,
-		       "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ",
+		       " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ",
 		       "[notify_cnt]",
 		       cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH],
 		       cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW],
@@ -12993,6 +13000,8 @@ ssize_t rtw89_btc_dump_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 	else if (ver->fcxbtcrpt == 11)
 		p += _show_summary_v11(rtwdev, p, end - p);
 
+	p += scnprintf(p, end - p, "\n");
+
 	return p - buf;
 }
 
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 10/14] wifi: rtw89: coex: Add BTC report version 8 support for BT sub-reports
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

RTL8922A (FW >= 0.35.111) and RTL8922D (FW >= 0.35.94) set fcxbtver,
fcxbtscan and fcxbtafh to 8, but the handler in _chk_btc_report only
had branches for version 1 and 7.  When version 8 arrived pfinfo was
left NULL and pcinfo->req_len was left at zero, so the length check at
validation stage rejected the report and bt->ver_info.fw was never
written, causing BT_FW:0x0 in the BTC dump.

BT-scan and BT-afh version 8 hit the goto err path for the same reason,
making all BT sub-reports silently broken on these chips.

The structural change in version 8 is that the previously reserved
second byte in each struct is now bt_id (0 = BT0, 1 = BT1), allowing
firmware to send separate reports for each Bluetooth device. All three
structs are otherwise layout-compatible with version 7.

Add rtw89_btc_fbtc_btver_v8, rtw89_btc_fbtc_btscan_v8 and
rtw89_btc_fbtc_btafh_v8 structs with the bt_id field, extend the
corresponding unions, add version 8 branches to _chk_btc_report, and
update _update_bt_report to route each report to BT0 or BT1 according
to BT ID.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 52 ++++++++++++++++++++++-
 drivers/net/wireless/realtek/rtw89/core.h | 34 +++++++++++++++
 2 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 5aa06ef430c8..96bb68f09a01 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -1529,7 +1529,14 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo)
 
 	switch (rpt_type) {
 	case BTC_RPT_TYPE_BT_VER:
-		if (ver->fcxbtver == 7) {
+		if (ver->fcxbtver == 8) {
+			pver->v8 = *(struct rtw89_btc_fbtc_btver_v8 *)pfinfo;
+			bt = pver->v8.bt_id ? &btc->cx.bt1 : &btc->cx.bt0;
+			bt->ver_info.fw = le32_to_cpu(pver->v8.fw_ver);
+			bt->ver_info.fw_coex = le32_get_bits(pver->v8.coex_ver,
+							     GENMASK(7, 0));
+			bt->feature = le32_to_cpu(pver->v8.feature);
+		} else if (ver->fcxbtver == 7) {
 			pver->v7 = *(struct rtw89_btc_fbtc_btver_v7 *)pfinfo;
 			bt->ver_info.fw = le32_to_cpu(pver->v7.fw_ver);
 			bt->ver_info.fw_coex = le32_get_bits(pver->v7.coex_ver,
@@ -1570,6 +1577,19 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo)
 				    pscan_v7->para[i].intvl == 0)
 					scan_update = false;
 			}
+		} else if (ver->fcxbtscan == 8) {
+			struct rtw89_btc_fbtc_btscan_v8 *pscan_v8 =
+				(struct rtw89_btc_fbtc_btscan_v8 *)pfinfo;
+			struct rtw89_btc_bt_info *tbt =
+				pscan_v8->bt_id ? &btc->cx.bt1 : &btc->cx.bt0;
+
+			for (i = 0; i < CXSCAN_MAX; i++) {
+				tbt->scan_info_v2[i] = pscan_v8->para[i];
+				if ((pscan_v8->type & BIT(i)) &&
+				    pscan_v8->para[i].win == 0 &&
+				    pscan_v8->para[i].intvl == 0)
+					scan_update = false;
+			}
 		}
 		if (scan_update)
 			bt->scan_info_update = 1;
@@ -1597,6 +1617,22 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo)
 				memcpy(&bt_linfo->afh_map_le[0], pafh_v7->afh_le_a, 4);
 				memcpy(&bt_linfo->afh_map_le[4], pafh_v7->afh_le_b, 1);
 			}
+		} else if (ver->fcxbtafh == 8) {
+			struct rtw89_btc_fbtc_btafh_v8 *pafh_v8 =
+				(struct rtw89_btc_fbtc_btafh_v8 *)pfinfo;
+			struct rtw89_btc_bt_info *tbt =
+				pafh_v8->bt_id ? &btc->cx.bt1 : &btc->cx.bt0;
+			struct rtw89_btc_bt_link_info *tbt_linfo = &tbt->link_info;
+
+			if (pafh_v8->map_type & RPT_BT_AFH_SEQ_LEGACY) {
+				memcpy(&tbt_linfo->afh_map[0], pafh_v8->afh_l, 4);
+				memcpy(&tbt_linfo->afh_map[4], pafh_v8->afh_m, 4);
+				memcpy(&tbt_linfo->afh_map[8], pafh_v8->afh_h, 2);
+			}
+			if (pafh_v8->map_type & RPT_BT_AFH_SEQ_LE) {
+				memcpy(&tbt_linfo->afh_map_le[0], pafh_v8->afh_le_a, 4);
+				memcpy(&tbt_linfo->afh_map_le[4], pafh_v8->afh_le_b, 1);
+			}
 		} else if (ver->fcxbtafh == 1) {
 			pafh_v1 = (struct rtw89_btc_fbtc_btafh *)pfinfo;
 			memcpy(&bt_linfo->afh_map[0], pafh_v1->afh_l, 4);
@@ -1882,6 +1918,12 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev,
 			pfinfo = &pfwinfo->rpt_fbtc_btver.finfo.v7;
 			pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btver.finfo.v7);
 			fwsubver->fcxbtver = pfwinfo->rpt_fbtc_btver.finfo.v7.fver;
+		} else if (ver->fcxbtver == 8) {
+			pfinfo = &pfwinfo->rpt_fbtc_btver.finfo.v8;
+			pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btver.finfo.v8);
+			fwsubver->fcxbtver = pfwinfo->rpt_fbtc_btver.finfo.v8.fver;
+		} else {
+			goto err;
 		}
 		pcinfo->req_fver = ver->fcxbtver;
 		break;
@@ -1899,6 +1941,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev,
 			pfinfo = &pfwinfo->rpt_fbtc_btscan.finfo.v7;
 			pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btscan.finfo.v7);
 			fwsubver->fcxbtscan = pfwinfo->rpt_fbtc_btscan.finfo.v7.fver;
+		} else if (ver->fcxbtscan == 8) {
+			pfinfo = &pfwinfo->rpt_fbtc_btscan.finfo.v8;
+			pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btscan.finfo.v8);
+			fwsubver->fcxbtscan = pfwinfo->rpt_fbtc_btscan.finfo.v8.fver;
 		} else {
 			goto err;
 		}
@@ -1918,6 +1964,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev,
 			pfinfo = &pfwinfo->rpt_fbtc_btafh.finfo.v7;
 			pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btafh.finfo.v7);
 			fwsubver->fcxbtafh = pfwinfo->rpt_fbtc_btafh.finfo.v7.fver;
+		} else if (ver->fcxbtafh == 8) {
+			pfinfo = &pfwinfo->rpt_fbtc_btafh.finfo.v8;
+			pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btafh.finfo.v8);
+			fwsubver->fcxbtafh = pfwinfo->rpt_fbtc_btafh.finfo.v8.fver;
 		} else {
 			goto err;
 		}
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index ba3a9b0d4aa2..aaed155ea423 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -2647,10 +2647,19 @@ struct rtw89_btc_fbtc_btscan_v7 {
 	struct rtw89_btc_bt_scan_info_v2 para[CXSCAN_MAX];
 } __packed;
 
+struct rtw89_btc_fbtc_btscan_v8 {
+	u8 fver; /* btc_ver::fcxbtscan */
+	u8 type;
+	u8 bt_id; /* 0:BT0, 1:BT1 */
+	u8 rsvd1;
+	struct rtw89_btc_bt_scan_info_v2 para[CXSCAN_MAX];
+} __packed;
+
 union rtw89_btc_fbtc_btscan {
 	struct rtw89_btc_fbtc_btscan_v1 v1;
 	struct rtw89_btc_fbtc_btscan_v2 v2;
 	struct rtw89_btc_fbtc_btscan_v7 v7;
+	struct rtw89_btc_fbtc_btscan_v8 v8;
 };
 
 struct rtw89_btc_bt_info {
@@ -3683,9 +3692,21 @@ struct rtw89_btc_fbtc_btver_v7 {
 	__le32 feature;
 } __packed;
 
+struct rtw89_btc_fbtc_btver_v8 {
+	u8 fver;
+	u8 bt_id; /* 0:BT0, 1:BT1 */
+	u8 rsvd1;
+	u8 rsvd2;
+
+	__le32 coex_ver; /*bit[15:8]->shared, bit[7:0]->non-shared */
+	__le32 fw_ver;
+	__le32 feature;
+} __packed;
+
 union rtw89_btc_fbtc_btver {
 	struct rtw89_btc_fbtc_btver_v1 v1;
 	struct rtw89_btc_fbtc_btver_v7 v7;
+	struct rtw89_btc_fbtc_btver_v8 v8;
 } __packed;
 
 struct rtw89_btc_fbtc_btafh {
@@ -3721,6 +3742,18 @@ struct rtw89_btc_fbtc_btafh_v7 {
 	u8 afh_le_b[4];
 } __packed;
 
+struct rtw89_btc_fbtc_btafh_v8 {
+	u8 fver;
+	u8 map_type;
+	u8 bt_id; /* 0:BT0, 1:BT1 */
+	u8 rsvd1;
+	u8 afh_l[4]; /*bit0:2402, bit1:2403.... bit31:2433 */
+	u8 afh_m[4]; /*bit0:2434, bit1:2435.... bit31:2465 */
+	u8 afh_h[4]; /*bit0:2466, bit1:2467.....bit14:2480 */
+	u8 afh_le_a[4];
+	u8 afh_le_b[4];
+} __packed;
+
 struct rtw89_btc_fbtc_btdevinfo {
 	u8 fver; /* btc_ver::fcxbtdevinfo */
 	u8 rsvd;
@@ -4194,6 +4227,7 @@ union rtw89_btc_fbtc_btafh_info {
 	struct rtw89_btc_fbtc_btafh v1;
 	struct rtw89_btc_fbtc_btafh_v2 v2;
 	struct rtw89_btc_fbtc_btafh_v7 v7;
+	struct rtw89_btc_fbtc_btafh_v8 v8;
 };
 
 struct rtw89_btc_report_ctrl_state {
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 11/14] wifi: rtw89: coex: Add dual Bluetooth debug info dump
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

As RTL8922D support dual Bluetooth, add BT debug info dump for it.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 51 ++++++++++++++++-------
 1 file changed, 37 insertions(+), 14 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 96bb68f09a01..6a718c54de67 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -9779,10 +9779,13 @@ enum btc_bt_a2dp_type {
 	BTC_A2DP_TWS_RELAY = 2,
 };
 
-static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
+static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf,
+				 size_t bufsz, u8 bid)
 {
 	struct rtw89_btc *btc = &rtwdev->btc;
-	struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info;
+	struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ?
+				       &btc->cx.bt0 : &btc->cx.bt1;
+	struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info;
 	struct rtw89_btc_bt_hfp_desc hfp = bt_linfo->hfp_desc;
 	struct rtw89_btc_bt_hid_desc hid = bt_linfo->hid_desc;
 	struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc;
@@ -9835,15 +9838,16 @@ static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t buf
 	return p - buf;
 }
 
-static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
+static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz, u8 bid)
 {
 	struct rtw89_btc *btc = &rtwdev->btc;
 	const struct rtw89_btc_ver *ver = btc->ver;
 	struct rtw89_btc_cx *cx = &btc->cx;
-	struct rtw89_btc_bt_info *bt = &cx->bt0;
+	struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1;
 	struct rtw89_btc_wl_info *wl = &cx->wl;
 	struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info;
 	struct rtw89_btc_module *md = &btc->mdinfo;
+	u8 bt_pos = (bid == BTC_BT_1ST) ? md->bt0_pos : md->bt1_pos;
 	s8 br_dbm = bt->link_info.bt_txpwr_desc.br_dbm;
 	s8 le_dbm = bt->link_info.bt_txpwr_desc.le_dbm;
 	u8 hw_band = wl->role_info.pta_req_band;
@@ -9855,13 +9859,21 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 	if (!(btc->dm.coex_info_map & BTC_COEX_INFO_BT))
 		return 0;
 
-	p += scnprintf(p, end - p, "========== [BT Status] ==========\n");
+	if (bid == BTC_BT_2ND) {
+		if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT))
+			return 0;
+		if (!bt->enable.now)
+			return 0;
+	}
+
+	p += scnprintf(p, end - p, "========== [BT_%s Status] ==========\n",
+		       bid == BTC_BT_1ST ? "1ST" : "2ND");
 
 	p += scnprintf(p, end - p,
 		       " %-15s : enable:%s, btg:%s%s, connect:%s, ",
 		       "[status]", bt->enable.now ? "Y" : "N",
 		       bt->btg_type ? "Y" : "N",
-		       (bt->enable.now && (bt->btg_type != md->bt0_pos) ?
+		       (bt->enable.now && (bt->btg_type != bt_pos) ?
 			"(efuse-mismatch!!)" : ""),
 		       (bt_linfo->status.map.connect ? "Y" : "N"));
 
@@ -9927,7 +9939,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 		       bt->bcnt[BTC_BCNT_INQPAG], bt->bcnt[BTC_BCNT_INQ],
 		       bt->bcnt[BTC_BCNT_PAGE], bt->bcnt[BTC_BCNT_IGNOWL]);
 
-	p += _show_bt_profile_info(rtwdev, p, end - p);
+	p += _show_bt_profile_info(rtwdev, p, end - p, bid);
 
 	p += scnprintf(p, end - p,
 		       " %-15s : raw_data[%02x %02x %02x %02x %02x %02x] (type:%s/cnt:%d/same:%d)\n",
@@ -12034,12 +12046,22 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 			       dm->gnt_set[RTW89_PHY_0].gnt_bt0);
 
 	if (rtwdev->dbcc_en) {
-		p += scnprintf(p, end - p,
-			       ", phy-1[gnt_wl:%s-%d/gnt_bt:%s-%d]",
-			       dm->gnt_set[RTW89_PHY_1].gnt_wl_sw_en ? "SW" : "HW",
-			       dm->gnt_set[RTW89_PHY_1].gnt_wl,
-			       dm->gnt_set[RTW89_PHY_1].gnt_bt0_sw_en ? "SW" : "HW",
-			       dm->gnt_set[RTW89_PHY_1].gnt_bt0);
+		if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)
+			p += scnprintf(p, end - p,
+				       ", phy-1[gnt_wl:%s-%d/gnt_bt0:%s-%d/gnt_bt1:%s-%d]",
+				       dm->gnt_set[RTW89_PHY_1].gnt_wl_sw_en ? "SW" : "HW",
+				       dm->gnt_set[RTW89_PHY_1].gnt_wl,
+				       dm->gnt_set[RTW89_PHY_1].gnt_bt0_sw_en ? "SW" : "HW",
+				       dm->gnt_set[RTW89_PHY_1].gnt_bt0,
+				       dm->gnt_set[RTW89_PHY_1].gnt_bt1_sw_en ? "SW" : "HW",
+				       dm->gnt_set[RTW89_PHY_1].gnt_bt1);
+		else
+			p += scnprintf(p, end - p,
+				       ", phy-1[gnt_wl:%s-%d/gnt_bt:%s-%d]",
+				       dm->gnt_set[RTW89_PHY_1].gnt_wl_sw_en ? "SW" : "HW",
+				       dm->gnt_set[RTW89_PHY_1].gnt_wl,
+				       dm->gnt_set[RTW89_PHY_1].gnt_bt0_sw_en ? "SW" : "HW",
+				       dm->gnt_set[RTW89_PHY_1].gnt_bt0);
 	}
 
 	pcinfo = &pfwinfo->rpt_fbtc_mregval.cinfo;
@@ -13020,7 +13042,8 @@ ssize_t rtw89_btc_dump_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 
 	p += _show_cx_info(rtwdev, p, end - p);
 	p += _show_wl_info(rtwdev, p, end - p);
-	p += _show_bt_info(rtwdev, p, end - p);
+	p += _show_bt_info(rtwdev, p, end - p, BTC_BT_1ST);
+	p += _show_bt_info(rtwdev, p, end - p, BTC_BT_2ND);
 	p += _show_dm_info(rtwdev, p, end - p);
 	p += _show_fw_dm_msg(rtwdev, p, end - p);
 
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 12/14] wifi: rtw89: coex: Fix TDMA v8 handling to unblock BTC report parsing
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

fcxtdma=8 was not handled in _chk_btc_report(), causing the parser to
hit 'goto err' and return 0 when processing the TDMA sub-report. This
broke the _parse_btc_report() loop before reaching BT_VER (type=9),
leaving bt->ver_info.fw always zero on RTL8922A/D.

TDMA v8 uses the same struct layout as v3/v4/v7 (rtw89_btc_fbtc_tdma_v3,
12 bytes), so add it to the existing v3/v4/v7 branch in both switch cases.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 6a718c54de67..7ab22c60a420 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -1769,7 +1769,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev,
 			pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_tdma.finfo.v1);
 			fwsubver->fcxtdma = 0;
 		} else if (ver->fcxtdma == 3 || ver->fcxtdma == 4 ||
-			   ver->fcxtdma == 7) {
+			   ver->fcxtdma == 7 || ver->fcxtdma == 8) {
 			pfinfo = &pfwinfo->rpt_fbtc_tdma.finfo.v3;
 			pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_tdma.finfo.v3);
 			fwsubver->fcxtdma = pfwinfo->rpt_fbtc_tdma.finfo.v3.fver;
@@ -2300,7 +2300,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev,
 					    &pfwinfo->rpt_fbtc_tdma.finfo.v1,
 					    sizeof(dm->tdma_now)));
 		else if (ver->fcxtdma == 3 || ver->fcxtdma == 4 ||
-			 ver->fcxtdma == 7)
+			 ver->fcxtdma == 7 || ver->fcxtdma == 8)
 			_chk_btc_err(rtwdev, BTC_DCNT_TDMA_NONSYNC,
 				     memcmp(&dm->tdma_now,
 					    &pfwinfo->rpt_fbtc_tdma.finfo.v3.tdma,
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 13/14] wifi: rtw89: coex: Fix H2C command redundant send & version fall through
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

I/O offload higher priority sending event didn't return after H2C command
was sent, add a return to prevent send twice in the same time. Update
driver info entry which is handling module control info didn't handle the
version 9 command format, add if condition to handle it. TX power update
H2C command result checker logic was reversed, it will lead to the TX
power value never update again after first update, fix the issue.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 7ab22c60a420..6b3e199398cf 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -1073,6 +1073,7 @@ static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func,
 		}
 
 		btc->fwinfo.cnt_h2c++;
+		return 0;
 	} else { /* Fill H2C MACRO buffer(TLV format) temporarily */
 		if (btc->hbuf_cnt == 0)
 			_reset_h2c_macro(btc);
@@ -3500,6 +3501,8 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index)
 
 		if (ver->fcxctrl == 7)
 			rtw89_fw_h2c_cxdrv_ctrl_v7(rtwdev, index);
+		else if (ver->fcxctrl == 9)
+			rtw89_fw_h2c_cxdrv_ctrl_v9(rtwdev, index);
 		else
 			rtw89_fw_h2c_cxdrv_ctrl(rtwdev, index);
 		break;
@@ -3893,7 +3896,7 @@ static void _set_bt_tx_power(struct rtw89_dev *rtwdev, bool force_exec, u8 bid,
 		if (rtwdev->chip->chip_gen == RTW89_CHIP_AX)
 			len = SET_RF_PARA_AX_LEN;
 
-		if (_send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, len)) {
+		if (!_send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, len)) {
 			btc->dm.rf_trx_para.bt_tx_power[i] = level;
 			if (rf_band == RTW89_BAND_2G)
 				bt->tx_power_now = level;
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 14/14] wifi: rtw89: coex: Handle Bluetooth LE-Audio related coexistence feature
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
  To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>

From: Ching-Te Ku <ku920601@realtek.com>

Implement event handler of BTF_EVNT_BT_LEAUDIO_INFO C2H command, and
related coexistence mechanism for Bluetooth LE-Audio feature.

Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/coex.c | 199 ++++++++++++++++++++++
 drivers/net/wireless/realtek/rtw89/core.h |  35 +++-
 2 files changed, 233 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 6b3e199398cf..0e42c720819a 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -951,6 +951,7 @@ enum btc_reason_and_action {
 	BTC_RSN_ACT1_WORK,
 	BTC_RSN_BT_DEVINFO_WORK,
 	BTC_RSN_RFK_CHK_WORK,
+	BTC_RSN_UPDATE_BT_LEAUDINFO,
 	BTC_RSN_NUM,
 	BTC_ACT_NONE = 100,
 	BTC_ACT_WL_ONLY,
@@ -975,6 +976,8 @@ enum btc_reason_and_action {
 	BTC_ACT_BT_A2DP_PAN,
 	BTC_ACT_BT_PAN_HID,
 	BTC_ACT_BT_A2DP_PAN_HID,
+	BTC_ACT_BT_BIS,
+	BTC_ACT_BT_CIS,
 	BTC_ACT_WL_25G_MCC,
 	BTC_ACT_WL_2G_MCC,
 	BTC_ACT_WL_2G_SCC,
@@ -6076,6 +6079,43 @@ static void _action_bt_pan_hid(struct rtw89_dev *rtwdev)
 	}
 }
 
+static void _action_bt_bis(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_btc *btc = &rtwdev->btc;
+	struct rtw89_btc_dm *dm = &btc->dm;
+	u16 policy_type;
+
+	if (dm->bis_tdma)
+		policy_type = BTC_CXP_OFFE_2GBWISOB;
+	else
+		policy_type = BTC_CXP_OFF_EQ0;
+
+	_set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_PTA);
+	_set_policy(rtwdev, policy_type, BTC_ACT_BT_BIS);
+}
+
+static void _action_bt_cis(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_btc *btc = &rtwdev->btc;
+	struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info;
+	struct rtw89_btc_wl_info *wl = &btc->cx.wl;
+	struct rtw89_btc_dm *dm = &btc->dm;
+	u16 policy_type;
+
+	if (bt_linfo->a2dp_desc.active) {
+		dm->slot_dur[CXST_W1] = 80;
+		dm->slot_dur[CXST_B1] = 20;
+		policy_type = BTC_CXP_PFIX_TDW1B1;
+	} else if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) {
+		policy_type = BTC_CXP_OFF_BWB0;
+	} else {
+		policy_type = BTC_CXP_OFF_BWB1;
+	}
+
+	_set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_PTA);
+	_set_policy(rtwdev, policy_type, BTC_ACT_BT_CIS);
+}
+
 static void _action_bt_a2dp_pan_hid(struct rtw89_dev *rtwdev)
 {
 	struct rtw89_btc *btc = &rtwdev->btc;
@@ -6692,6 +6732,7 @@ static void _action_by_bt(struct rtw89_dev *rtwdev)
 	struct rtw89_btc *btc = &rtwdev->btc;
 	struct rtw89_btc_bt_info *bt = &btc->cx.bt0;
 	struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info;
+	struct rtw89_btc_bt_leaudio_desc leaudio = bt_linfo->leaudio_desc;
 	struct rtw89_btc_bt_hid_desc hid = bt_linfo->hid_desc;
 	struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc;
 	struct rtw89_btc_bt_pan_desc pan = bt_linfo->pan_desc;
@@ -6715,6 +6756,12 @@ static void _action_by_bt(struct rtw89_dev *rtwdev)
 	if (bt_linfo->pan_desc.exist)
 		profile_map |= BTC_BT_PAN;
 
+	if (leaudio.bis_exist)
+		profile_map |= BTC_BT_BIS;
+
+	if (leaudio.cis_exist)
+		profile_map |= BTC_BT_CIS;
+
 	switch (profile_map) {
 	case BTC_BT_NOPROFILE:
 		if (pan.active)
@@ -6740,6 +6787,13 @@ static void _action_by_bt(struct rtw89_dev *rtwdev)
 	case BTC_BT_PAN:
 		_action_bt_pan(rtwdev);
 		break;
+	case BTC_BT_BIS:
+		_action_bt_bis(rtwdev);
+		break;
+	case BTC_BT_CIS:
+	case BTC_BT_CIS | BTC_BT_HID:
+		_action_bt_cis(rtwdev);
+		break;
 	case BTC_BT_A2DP | BTC_BT_HFP:
 	case BTC_BT_A2DP | BTC_BT_HID:
 	case BTC_BT_A2DP | BTC_BT_HFP | BTC_BT_HID:
@@ -7909,6 +7963,140 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid)
 	}
 }
 
+static void _update_bt_link_cnt(struct rtw89_dev *rtwdev,
+				struct rtw89_btc_bt_info *bt, bool b56g)
+{
+	struct rtw89_btc_bt_link_info *b = b56g ? &bt->link_info_56g :
+						  &bt->link_info;
+	struct rtw89_btc_bt_leaudio_desc *leaudio = &b->leaudio_desc;
+	struct rtw89_btc_bt_a2dp_desc *a2dp = &b->a2dp_desc;
+	struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc;
+	struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc;
+	struct rtw89_btc_bt_pan_desc *pan = &b->pan_desc;
+
+	b->link_cnt.last = b->link_cnt.now;
+	b->link_cnt.now = 0;
+	b->link_cnt.chg = 0;
+
+	b->link_cnt.now += hfp->exist;
+	b->link_cnt.now += hid->exist;
+	b->link_cnt.now += a2dp->exist;
+	b->link_cnt.now += pan->exist;
+	b->link_cnt.now += leaudio->bis_exist;
+	b->link_cnt.now += leaudio->cis_exist;
+}
+
+static void _update_bt_leaudio_info(struct rtw89_dev *rtwdev, u8 bid,
+				    void *buf, u32 len)
+{
+	const struct rtw89_chip_info *chip = rtwdev->chip;
+	struct rtw89_btc *btc = &rtwdev->btc;
+	struct rtw89_btc_cx *cx = &btc->cx;
+	struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1;
+	struct rtw89_btc_bt_link_info *b = &bt->link_info;
+	struct rtw89_btc_bt_leaudio_info *src = buf;
+	struct rtw89_btc_bt_leaudio_desc *leaudio;
+	bool bt_56g = false, cis_exist, bis_exist;
+	u8 bis_cnt, cis_cnt;
+
+	if ((src->len & BIT(7)) && bt->band_56G_support) {
+		b = &bt->link_info_56g;
+		bt_56g = true;
+	}
+
+	leaudio = &b->leaudio_desc;
+
+	if (!memcmp(b->leaudio_raw_info, src, BTC_BTINFO_MAX)) {
+		rtw89_debug(rtwdev, RTW89_DBG_BTC,
+			    "[BTC], %s return by leaudio-info duplicate!\n",
+			    __func__);
+		bt->bcnt[BTC_BCNT_LEAUDIO_INFOSAME]++;
+		return;
+	}
+
+	memcpy(b->leaudio_raw_info, src, BTC_BTINFO_MAX);
+
+	bis_exist = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_BIS_EX);
+	cis_exist = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_CIS_EX);
+	bis_cnt = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_BIS_CNT);
+	cis_cnt = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_CIS_CNT);
+
+	rtw89_debug(rtwdev, RTW89_DBG_BTC,
+		    "[BTC], %s: bis_exist:%d bis_cnt:%d cis_exist:%d cis_cnt:%d rssi:%d\n",
+		    __func__, bis_exist, bis_cnt, cis_exist, cis_cnt, src->rssi);
+
+	if (!bis_exist && !cis_exist)
+		memset(leaudio, 0, sizeof(*leaudio));
+
+	leaudio->bis_cnt = bis_cnt;
+	leaudio->bis_exist = bis_exist;
+	leaudio->cis_cnt = cis_cnt;
+	leaudio->cis_exist = cis_exist;
+
+	if (leaudio->bis_exist)
+		b->status.map.profile_map |= BTC_BT_BIS;
+	else
+		b->status.map.profile_map &= ~BTC_BT_BIS;
+
+	if (leaudio->cis_exist)
+		b->status.map.profile_map |= BTC_BT_CIS;
+	else
+		b->status.map.profile_map &= ~BTC_BT_CIS;
+
+	_update_bt_link_cnt(rtwdev, bt, bt_56g);
+
+	leaudio->rssi = chip->ops->btc_get_bt_rssi(rtwdev, src->rssi);
+
+	leaudio->bis_cnt_last = leaudio->bis_cnt;
+	leaudio->bis_exist_last = leaudio->bis_exist;
+	leaudio->cis_cnt_last = leaudio->cis_cnt;
+	leaudio->cis_exist_last = leaudio->cis_exist;
+}
+
+static void _update_bt_bistdma_info(struct rtw89_dev *rtwdev, u8 bid,
+				    void *buf, u32 len)
+{
+	struct rtw89_btc *btc = &rtwdev->btc;
+	struct rtw89_btc_cx *cx = &btc->cx;
+	struct rtw89_btc_dm *dm = &btc->dm;
+	struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1;
+	struct rtw89_btc_bt_link_info *b = &bt->link_info;
+	struct rtw89_btc_bt_bistdma_info_le *src = buf;
+	struct rtw89_btc_bt_leaudio_desc *leaudio;
+
+	if ((src->len & BIT(7)) && bt->band_56G_support)
+		b = &bt->link_info_56g;
+
+	leaudio = &b->leaudio_desc;
+
+	if (!memcmp(b->bistdma_raw_info, src, BTC_BTINFO_MAX)) {
+		rtw89_debug(rtwdev, RTW89_DBG_BTC,
+			    "[BTC], %s return by bistdma-info duplicate!\n",
+			    __func__);
+		bt->bcnt[BTC_BCNT_LEAUDIO_INFOSAME]++;
+		return;
+	}
+
+	memcpy(b->bistdma_raw_info, src, BTC_BTINFO_MAX);
+
+	rtw89_debug(rtwdev, RTW89_DBG_BTC,
+		    "[BTC], %s: bis_start_end:%d bis_trx:%d\n",
+		    __func__,
+		    u8_get_bits(src->bis, RTW89_BTC_BIS_INFO_L2_START_END),
+		    u8_get_bits(src->bis, RTW89_BTC_BIS_INFO_L2_TRX));
+
+	leaudio->bis_trx = u8_get_bits(src->bis, RTW89_BTC_BIS_INFO_L2_TRX);
+	leaudio->bis_start_end = u8_get_bits(src->bis,
+					     RTW89_BTC_BIS_INFO_L2_START_END);
+
+	leaudio->diff_t = (src->diff_t_hb * 256 + src->diff_t_lb) * 625 / 1000;
+
+	if (btc->ant_type == BTC_ANT_SHARED && leaudio->diff_t > 13)
+		dm->bis_tdma = true;
+	else
+		dm->bis_tdma = false;
+}
+
 #define BTC_BTINFO_PWR_LEN 5
 static void _update_bt_txpwr_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len)
 {
@@ -9606,6 +9794,14 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb,
 	case BTF_EVNT_CX_RUNINFO:
 		btc->dm.cnt_dm[BTC_DCNT_CX_RUNINFO]++;
 		break;
+	case BTF_EVNT_BT_LEAUDIO_INFO:
+		bt->bcnt[BTC_BCNT_LEAUDIO_INFOUPDATE]++;
+		if (buf[BTC_BTINFO_L0] == BTC_BTINFO_BISTDMA)
+			_update_bt_bistdma_info(rtwdev, bid, buf, len);
+		else
+			_update_bt_leaudio_info(rtwdev, bid, buf, len);
+		_run_coex(rtwdev, BTC_RSN_UPDATE_BT_LEAUDINFO);
+		break;
 	case BTF_EVNT_BT_QUERY_TXPWR:
 		bt->bcnt[BTC_BCNT_TXPWR_UPDATE]++;
 		_update_bt_txpwr_info(rtwdev, buf, len);
@@ -10107,6 +10303,7 @@ static const char *steps_to_str(u16 step)
 	CASE_BTC_RSN_STR(ACT1_WORK);
 	CASE_BTC_RSN_STR(BT_DEVINFO_WORK);
 	CASE_BTC_RSN_STR(RFK_CHK_WORK);
+	CASE_BTC_RSN_STR(UPDATE_BT_LEAUDINFO);
 
 	CASE_BTC_ACT_STR(NONE);
 	CASE_BTC_ACT_STR(WL_ONLY);
@@ -10131,6 +10328,8 @@ static const char *steps_to_str(u16 step)
 	CASE_BTC_ACT_STR(BT_A2DP_PAN);
 	CASE_BTC_ACT_STR(BT_PAN_HID);
 	CASE_BTC_ACT_STR(BT_A2DP_PAN_HID);
+	CASE_BTC_ACT_STR(BT_BIS);
+	CASE_BTC_ACT_STR(BT_CIS);
 	CASE_BTC_ACT_STR(WL_25G_MCC);
 	CASE_BTC_ACT_STR(WL_2G_MCC);
 	CASE_BTC_ACT_STR(WL_2G_SCC);
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index aaed155ea423..09c19a6b9058 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -1383,6 +1383,8 @@ enum rtw89_btc_btinfo {
 	BTC_BTINFO_MAX
 };
 
+#define BTC_BTINFO_BISTDMA 0x48 /* cmd value that identifies BISTDMA data */
+
 enum rtw89_btc_dcnt {
 	BTC_DCNT_RUN = 0x0,
 	BTC_DCNT_CX_RUNINFO,
@@ -2167,6 +2169,31 @@ struct rtw89_btc_bt_txpwr_desc {
 	u8 le_gain_index;
 };
 
+struct rtw89_btc_bt_leaudio_info {
+	u8 cmd;
+	u8 len;
+	u8 bis_cis;
+#define RTW89_BTC_LEAU_INFO_L2_BIS_EX BIT(0)
+#define RTW89_BTC_LEAU_INFO_L2_BIS_CNT GENMASK(3, 1)
+#define RTW89_BTC_LEAU_INFO_L2_CIS_EX BIT(4)
+#define RTW89_BTC_LEAU_INFO_L2_CIS_CNT GENMASK(7, 5)
+	u8 rssi;
+	__le32 hbrsvd;
+} __packed;
+
+struct rtw89_btc_bt_bistdma_info_le {
+	u8 cmd;
+	u8 len;
+	u8 bis; /* BIT(2) ~ BIT(7) is rsvd */
+#define RTW89_BTC_BIS_INFO_L2_START_END BIT(0) /* 0: BIS start, 1: BIS end */
+#define RTW89_BTC_BIS_INFO_L2_TRX BIT(1) /* 0: BIS Tx, 1: BIS Rx */
+	u8 diff_t_lb;
+	u8 diff_t_hb; /* diff_t = (diff_t_hb * 256 + diff_t_lb) * 0.625 ms */
+	u8 hb1rsvd;
+	u8 hb2rsvd;
+	u8 hb3rsvd;
+} __packed;
+
 struct rtw89_btc_bt_leaudio_desc {
 	u32 bis_exist: 1;
 	u32 bis_exist_last: 1;
@@ -2177,7 +2204,9 @@ struct rtw89_btc_bt_leaudio_desc {
 	u32 rssi: 8;
 	u32 bis_cnt_last: 3;
 	u32 cis_cnt_last: 3;
-	u32 rsvd: 8;
+	u32 bis_trx: 1;
+	u32 bis_start_end: 1;
+	u32 rsvd: 6;
 
 	u16 diff_t;
 };
@@ -2213,6 +2242,9 @@ struct rtw89_btc_bt_link_info {
 	u8 ble_scan_en: 1;
 	u8 reinit: 1;
 	u8 rsvd: 6;
+
+	u8 leaudio_raw_info[BTC_BTINFO_MAX]; /* raw LE audio info from BT mailbox */
+	u8 bistdma_raw_info[BTC_BTINFO_MAX]; /* raw BIS-TDMA info from BT mailbox */
 };
 
 struct rtw89_btc_bind_bt_status {
@@ -4085,6 +4117,7 @@ struct rtw89_btc_dm {
 	u8 lps_ctrl_scbd: 1;
 	u8 lps_ctrl_scbd_last: 1;
 	u8 lps_ctrl_change: 1;
+	u8 bis_tdma: 1; /* BIS TDMA mode active */
 	u8 scbd_write_instant;
 	bool scbd_b2w_update;
 	bool scbd_w2b_update;
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 0/3] wifi: rtw89: support hw_scan v3 format for WiFI 7 chips
From: Ping-Ke Shih @ 2026-07-24 14:22 UTC (permalink / raw)
  To: linux-wireless; +Cc: dian_syuan0116, phhuang

Due to firmware change the hw_scan format, update the format accordingly
to support current new firmware. (By patch 2/3 and 3/3)

The patch 1/3 is to store RX filter settings by a variable, so hardware
leaves from idle can restore the setting from the variable. Otherwise,
the RX filter is unexpected, and AP mode can't receive certain types of
frames.

Dian-Syuan Yang (1):
  wifi: rtw89: refine RX filter configuration for WiFi 7 generation

Po-Hao Huang (2):
  wifi: rtw89: fix scan offload version check logic
  wifi: rtw89: change scan offload format to V3 for WiFi 7 IC

 drivers/net/wireless/realtek/rtw89/core.c   |   3 +-
 drivers/net/wireless/realtek/rtw89/core.h   |   4 +
 drivers/net/wireless/realtek/rtw89/fw.c     | 126 ++++++++++++++++++--
 drivers/net/wireless/realtek/rtw89/fw.h     |   1 +
 drivers/net/wireless/realtek/rtw89/mac.c    |   1 +
 drivers/net/wireless/realtek/rtw89/mac.h    |   1 +
 drivers/net/wireless/realtek/rtw89/mac_be.c |   7 +-
 drivers/net/wireless/realtek/rtw89/reg.h    |   6 +-
 8 files changed, 131 insertions(+), 18 deletions(-)


base-commit: b0034a2e415603daa1cf00363b407cc145bbcd31
-- 
2.25.1


^ permalink raw reply

* [PATCH rtw-next 1/3] wifi: rtw89: refine RX filter configuration for WiFi 7 generation
From: Ping-Ke Shih @ 2026-07-24 14:22 UTC (permalink / raw)
  To: linux-wireless; +Cc: dian_syuan0116, phhuang
In-Reply-To: <20260724142246.3195274-1-pkshih@realtek.com>

From: Dian-Syuan Yang <dian_syuan0116@realtek.com>

When an AP interface is registered and starts AP, mac80211 calls the
configure_filter() to clear B_AX_A_UC_CAM_MATCH and B_AX_A_BC_CAM_MATCH
so that frames from un-associated stations can be received. However, for
a dedicated AP interface created via iw command, configure_filter() is
only triggered on the initial AP startup. Since the interface remains up
even after hostapd stops, and it isn't triggered again on later restarts.
Additionally, each AP start causes IPS leave, reverting the RX filter to
its default value.

For WiFi 7 chips, the default value is hardcoded in rx_fltr_init_be(),
so the reverted value does not match hal.rx_fltr. Therefore, refine
the behavior of WiFi 7 chips to align with the WiFi 6 implementation.

Signed-off-by: Dian-Syuan Yang <dian_syuan0116@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.c   | 3 ++-
 drivers/net/wireless/realtek/rtw89/mac.c    | 1 +
 drivers/net/wireless/realtek/rtw89/mac.h    | 1 +
 drivers/net/wireless/realtek/rtw89/mac_be.c | 7 ++-----
 drivers/net/wireless/realtek/rtw89/reg.h    | 6 +++++-
 5 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index 77a0e2582dbe..097e9b0312a1 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -6960,6 +6960,7 @@ void rtw89_sta_unset_link(struct rtw89_sta *rtwsta, unsigned int link_id)
 
 int rtw89_core_init(struct rtw89_dev *rtwdev)
 {
+	const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def;
 	struct rtw89_btc *btc = &rtwdev->btc;
 	u8 band;
 
@@ -7016,7 +7017,7 @@ int rtw89_core_init(struct rtw89_dev *rtwdev)
 	rtw89_core_ppdu_sts_init(rtwdev);
 	rtw89_traffic_stats_init(rtwdev, &rtwdev->stats);
 
-	rtwdev->hal.rx_fltr = DEFAULT_AX_RX_FLTR;
+	rtwdev->hal.rx_fltr = mac->default_rx_fltr;
 	rtwdev->dbcc_en = false;
 	rtwdev->mlo_dbcc_mode = MLO_DBCC_NOT_SUPPORT;
 	rtwdev->mac.qta_mode = RTW89_QTA_SCC;
diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c
index db9fca828829..ca08fe520eaa 100644
--- a/drivers/net/wireless/realtek/rtw89/mac.c
+++ b/drivers/net/wireless/realtek/rtw89/mac.c
@@ -7458,6 +7458,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_ax = {
 	.mem_base_addrs = rtw89_mac_mem_base_addrs_ax,
 	.mem_page_size = MAC_MEM_DUMP_PAGE_SIZE_AX,
 	.rx_fltr = R_AX_RX_FLTR_OPT,
+	.default_rx_fltr = DEFAULT_AX_RX_FLTR,
 	.port_base = &rtw89_port_base_ax,
 	.agg_len_ht = R_AX_AGG_LEN_HT_0,
 	.ps_status = R_AX_PPWRBIT_SETTING,
diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h
index 493d2b9626a6..8419bcd3956a 100644
--- a/drivers/net/wireless/realtek/rtw89/mac.h
+++ b/drivers/net/wireless/realtek/rtw89/mac.h
@@ -1063,6 +1063,7 @@ struct rtw89_mac_gen_def {
 	const u32 *mem_base_addrs;
 	u32 mem_page_size;
 	u32 rx_fltr;
+	u32 default_rx_fltr;
 	const struct rtw89_port_reg *port_base;
 	u32 agg_len_ht;
 	u32 ps_status;
diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c
index 8de0fe5a3b1d..413c214d968a 100644
--- a/drivers/net/wireless/realtek/rtw89/mac_be.c
+++ b/drivers/net/wireless/realtek/rtw89/mac_be.c
@@ -1332,11 +1332,7 @@ static int rx_fltr_init_be(struct rtw89_dev *rtwdev, u8 mac_idx)
 	rtw89_mac_typ_fltr_opt_be(rtwdev, RTW89_DATA, RTW89_FWD_TO_HOST, mac_idx);
 
 	reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_RX_FLTR_OPT, mac_idx);
-	val = B_BE_A_BC_CAM_MATCH | B_BE_A_UC_CAM_MATCH | B_BE_A_MC |
-	      B_BE_A_BC | B_BE_A_A1_MATCH |
-	      u32_encode_bits(15, B_BE_UID_FILTER_MASK);
-	rtw89_write32(rtwdev, reg, val);
-	u32p_replace_bits(&rtwdev->hal.rx_fltr, 15, B_BE_UID_FILTER_MASK);
+	rtw89_write32(rtwdev, reg, rtwdev->hal.rx_fltr);
 
 	reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_PLCP_HDR_FLTR, mac_idx);
 	val = B_BE_HE_SIGB_CRC_CHK | B_BE_VHT_MU_SIGB_CRC_CHK |
@@ -3280,6 +3276,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_be = {
 	.mem_base_addrs = rtw89_mac_mem_base_addrs_be,
 	.mem_page_size = MAC_MEM_DUMP_PAGE_SIZE_BE,
 	.rx_fltr = R_BE_RX_FLTR_OPT,
+	.default_rx_fltr = DEFAULT_BE_RX_FLTR,
 	.port_base = &rtw89_port_base_be,
 	.agg_len_ht = R_BE_AGG_LEN_HT_0,
 	.ps_status = R_BE_WMTX_POWER_BE_BIT_CTL,
diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h
index 756b94dcd475..abeeb61007dc 100644
--- a/drivers/net/wireless/realtek/rtw89/reg.h
+++ b/drivers/net/wireless/realtek/rtw89/reg.h
@@ -3348,7 +3348,7 @@
 #define DEFAULT_AX_RX_FLTR (B_AX_A_A1_MATCH | B_AX_A_BC | B_AX_A_MC |	       \
 			    B_AX_A_UC_CAM_MATCH | B_AX_A_BC_CAM_MATCH |	       \
 			    B_AX_A_PWR_MGNT | B_AX_A_FTM_REQ |		       \
-			    u32_encode_bits(3, B_AX_UID_FILTER_MASK) |	       \
+			    FIELD_PREP_CONST(B_AX_UID_FILTER_MASK, 3) |       \
 			    B_AX_A_BCN_CHK_EN)
 #define B_AX_RX_FLTR_CFG_MASK ((u32)~B_AX_RX_MPDU_MAX_LEN_MASK)
 
@@ -8209,6 +8209,10 @@
 #define B_BE_A_BC BIT(2)
 #define B_BE_A_A1_MATCH BIT(1)
 #define B_BE_SNIFFER_MODE BIT(0)
+#define DEFAULT_BE_RX_FLTR (B_BE_A_BC_CAM_MATCH | B_BE_A_UC_CAM_MATCH |        \
+			    B_BE_A_MC | B_BE_A_BC | B_BE_A_A1_MATCH |          \
+			    FIELD_PREP_CONST(B_BE_UID_FILTER_MASK, 15) |       \
+			    B_BE_A_FTM_REQ | B_BE_A_BCN_CHK_EN)
 
 #define R_BE_CTRL_FLTR 0x11424
 #define R_BE_CTRL_FLTR_C1 0x15424
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 2/3] wifi: rtw89: fix scan offload version check logic
From: Ping-Ke Shih @ 2026-07-24 14:22 UTC (permalink / raw)
  To: linux-wireless; +Cc: dian_syuan0116, phhuang
In-Reply-To: <20260724142246.3195274-1-pkshih@realtek.com>

From: Po-Hao Huang <phhuang@realtek.com>

The version check with less than should check for smallest upper-bound
first, or some branch will be unreachable.

Signed-off-by: Po-Hao Huang <phhuang@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/fw.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index 3b8c144e0e98..7d68ee870ed5 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -7615,12 +7615,12 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev,
 
 	rtw89_scan_get_6g_disabled_chan(rtwdev, option);
 
-	if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V1, &rtwdev->fw)) {
-		cfg_len = offsetofend(typeof(*h2c), w9);
-		scan_offload_ver = 1;
-	} else if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V0, &rtwdev->fw)) {
+	if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V0, &rtwdev->fw)) {
 		cfg_len = offsetofend(typeof(*h2c), w8);
 		scan_offload_ver = 0;
+	} else if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V1, &rtwdev->fw)) {
+		cfg_len = offsetofend(typeof(*h2c), w9);
+		scan_offload_ver = 1;
 	}
 
 	len = cfg_len + macc_role_size + opch_size;
-- 
2.25.1


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox