Linux bluetooth development
 help / color / mirror / Atom feed
From: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
To: linux-bluetooth@vger.kernel.org
Cc: chethan.tumkur.narayan@intel.com, kiran.k@intel.com,
	Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Subject: [PATCH 2/2] Bluetooth: btintel_pcie: Add TOP silent reset support
Date: Wed, 14 Oct 2026 17:17:51 +0530	[thread overview]
Message-ID: <20261014114752.174669-2-chandrashekar.devegowda@intel.com> (raw)
In-Reply-To: <20261014114752.174669-1-chandrashekar.devegowda@intel.com>

Add TOP (Total On-chip Platform) silent reset, a lighter recovery
than PLDR for HW >= SCP2 on subsystem PEP. Both BT- and
WiFi-initiated resets are supported, falling back to PLDR on
failure.

On a BT-initiated reset, hw_error() marks the per-BDF recovery
struct and triggers an FLR; the reprobe restores INTEL_TOP_RESET,
and btintel_pcie_do_top_reset() asserts the reset bit in
FUNC_SCRATCH and replays btintel_send_intel_reset() using the
cached boot_addr, then waits for the GP1 mailbox event and
BOOT_STAGE_REG to clear before a final FLR reprobes the device.

On a WiFi-initiated reset, the GP1 handler decodes the
TOP_SILENT_RESET sub-events from mbox2; if BT is the follower it
goes straight to PLDR instead.

Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
---
 drivers/bluetooth/btintel_pcie.c | 638 ++++++++++++++++++++++++++++---
 drivers/bluetooth/btintel_pcie.h |  58 ++-
 2 files changed, 644 insertions(+), 52 deletions(-)

diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 91b1ee9d6010..a513bba1d21c 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -67,6 +67,7 @@ MODULE_DEVICE_TABLE(pci, btintel_pcie_table);
 
 struct btintel_pcie_dev_recovery {
 	struct list_head list;
+	unsigned long flags;
 	u8 count;
 	time64_t last_error;
 	char name[];
@@ -87,6 +88,9 @@ struct btintel_pcie_dev_recovery {
 
 #define BTINTEL_PCIE_RESET_WINDOW_SECS		5
 #define BTINTEL_PCIE_FLR_MAX_RETRY	1
+#define BTINTEL_PCIE_TOP_RESET_INTERVAL_MS	250
+#define BTINTEL_PCIE_TOP_RESET_ROLE_SWITCH_MS	5000
+#define BTINTEL_PCIE_TOP_RESET_FALLBACK_MS	250
 
 /* Alive interrupt context */
 enum {
@@ -526,6 +530,14 @@ static inline void btintel_pcie_dump_debug_registers(struct hci_dev *hdev)
 	hci_recv_diag(hdev, skb);
 }
 
+static inline bool btintel_pcie_in_rom(struct btintel_pcie_data *data);
+static void btintel_pcie_reset_ia(struct btintel_pcie_data *data);
+static int btintel_pcie_start_rx(struct btintel_pcie_data *data);
+static struct btintel_pcie_dev_recovery *
+btintel_pcie_get_recovery(struct pci_dev *pdev);
+static bool btintel_pcie_request_reset(struct btintel_pcie_data *data,
+				       enum btintel_pcie_reset_type type);
+
 static int btintel_pcie_send_sync(struct btintel_pcie_data *data,
 				  struct sk_buff *skb, u32 pkt_type, u16 opcode)
 {
@@ -593,6 +605,33 @@ static int btintel_pcie_send_sync(struct btintel_pcie_data *data,
 	}
 
 	if (wait_on_alive) {
+		/* After TOP reset, device returns to ROM state. The normal
+		 * alive interrupt won't arrive since FW isn't loaded yet.
+		 * Reinitialize RX/TX queues and skip the alive wait so FW
+		 * re-download can proceed immediately.
+		 */
+		if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags) &&
+		    btintel_pcie_in_rom(data)) {
+			bool armed;
+
+			/* Claim the transition under irq_lock, same as the
+			 * dxstate fallback above, so a concurrent gp0 handler
+			 * run observing the same boot stage cannot also arm
+			 * RX and race this thread's reset_ia()/start_rx().
+			 */
+			spin_lock(&data->irq_lock);
+			armed = data->alive_intr_ctxt == BTINTEL_PCIE_FW_DL;
+			if (!armed)
+				data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL;
+			spin_unlock(&data->irq_lock);
+
+			if (!armed) {
+				btintel_pcie_reset_ia(data);
+				btintel_pcie_start_rx(data);
+			}
+			return 0;
+		}
+
 		ret = wait_event_timeout(data->gp0_wait_q,
 					 data->gp0_received,
 					 msecs_to_jiffies(BTINTEL_DEFAULT_INTR_TIMEOUT_MS));
@@ -1488,6 +1527,13 @@ static inline bool btintel_pcie_in_iml(struct btintel_pcie_data *data)
 		!(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_OPFW);
 }
 
+static inline bool btintel_pcie_in_rom(struct btintel_pcie_data *data)
+{
+	return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_ROM &&
+		!(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_IML) &&
+		!(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_OPFW);
+}
+
 static inline bool btintel_pcie_in_d3(struct btintel_pcie_data *data)
 {
 	return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY;
@@ -1870,27 +1916,172 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
 	return err;
 }
 
+/* Returns true when either the boot-stage register reads 0, or the
+ * recorded generation differs from @gen (the GP1 handler bumped it,
+ * meaning data->top_reset_deadline was extended and should be
+ * re-evaluated by the caller's wait loop).
+ */
+static inline bool btintel_pcie_top_reset_done(struct btintel_pcie_data *data,
+					       unsigned int gen)
+{
+	return btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_BOOT_STAGE_REG) == 0 ||
+	       READ_ONCE(data->top_reset_gen) != gen;
+}
+
+/* Handle a TOP silent reset MBOX2 sub-event. Called directly from the GP1
+ * handler as soon as it arrives, whether or not data->hdev is valid yet.
+ * bt_dev_*() is safe with a NULL hdev (bt_dev_name() falls back to
+ * "null"), and all state this function touches lives on 'data' (always
+ * allocated whenever MSI-X is live), not the separately-allocated
+ * recovery struct, so there is nothing here that depends on hdev being
+ * set or on a GFP_ATOMIC allocation succeeding.
+ */
+static void btintel_pcie_handle_top_reset_mbox2(struct btintel_pcie_data *data,
+						u32 mbox2)
+{
+	u32 expires = BTINTEL_PCIE_TOP_RESET_INTERVAL_MS;
+	u32 reg;
+
+	switch (mbox2) {
+	case BTINTEL_PCIE_TOP_RESET_WIFI_REQUEST:
+		bt_dev_info(data->hdev,
+			    "TOP reset request from WiFi, triggering PLDR");
+		if (!test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags)) {
+			bt_dev_info(data->hdev,
+				    "BT is follower, triggering PLDR");
+			set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+			if (!btintel_pcie_request_reset(data,
+							BTINTEL_PCIE_IOSF_PRR_PLDR)) {
+				/* Rejected because BTINTEL_PCIE_SETUP_DONE
+				 * is not set yet; retry once setup
+				 * completes instead of dropping the
+				 * WiFi-follower request (see
+				 * BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING
+				 * in btintel_pcie_setup()).
+				 */
+				bt_dev_warn(data->hdev,
+					    "TOP reset: PLDR request rejected, deferring until setup completes");
+				clear_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+				set_bit(BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING,
+					&data->flags);
+
+				/* Close the race with btintel_pcie_setup()'s
+				 * one-shot replay of this flag at
+				 * BTINTEL_PCIE_SETUP_DONE time: if SETUP_DONE
+				 * is already visible here, that replay may
+				 * already have run (and found nothing
+				 * pending, since we had not set the flag
+				 * yet) before this point, so replay it
+				 * ourselves instead of leaving it stuck until
+				 * a SETUP_DONE transition that already
+				 * happened. test_and_clear_bit() ensures only
+				 * one of the two sides actually replays if
+				 * both observe the race window.
+				 */
+				if (test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags) &&
+				    test_and_clear_bit(BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING,
+						       &data->flags)) {
+					bt_dev_info(data->hdev,
+						    "Replaying deferred WiFi-follower PLDR request");
+					set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+					if (!btintel_pcie_request_reset(data,
+									BTINTEL_PCIE_IOSF_PRR_PLDR))
+						clear_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+				}
+			}
+			return;
+		}
+		break;
+	case BTINTEL_PCIE_TOP_RESET_WIFI_ACK:
+		bt_dev_info(data->hdev, "TOP reset ack from WiFi");
+		break;
+	case BTINTEL_PCIE_TOP_RESET_WIFI_NACK:
+		bt_dev_info(data->hdev, "TOP reset NACK from WiFi");
+		set_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+		break;
+	case BTINTEL_PCIE_TOP_RESET_WIFI_REQ_ROLE_SWITCH:
+		bt_dev_info(data->hdev, "TOP reset WIFI_REQ_ROLE_SWITCH");
+		expires = BTINTEL_PCIE_TOP_RESET_ROLE_SWITCH_MS;
+		reg = btintel_pcie_rd_reg32(data,
+					    BTINTEL_PCIE_CSR_RESET_DEBUG_AD);
+		if (reg == BTINTEL_PCIE_TOP_RESET_DBG_RESET_REG)
+			bt_dev_dbg(data->hdev, "TOP reset initiated by WiFi");
+		break;
+	case BTINTEL_PCIE_TOP_RESET_WIFI_NO_RESPONSE:
+		bt_dev_err(data->hdev, "TOP reset WIFI_NO_RESPONSE");
+		set_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+		break;
+	case BTINTEL_PCIE_TOP_RESET_WIFI_RESPONSE_INVALID:
+		bt_dev_err(data->hdev, "TOP reset WIFI_RESPONSE_INVALID");
+		set_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+		break;
+	default:
+		bt_dev_err(data->hdev, "TOP reset with undefined MBOX2 0x%8.8x",
+			   mbox2);
+		/* An unrecognized sub-event means the negotiation outcome
+		 * is unknown; do not let a stale boot_stage==0 read be
+		 * mistaken for success further down and select FLR.
+		 */
+		set_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+		break;
+	}
+
+	data->top_reset_expires = expires;
+	/* Record an absolute deadline and bump the generation counter so
+	 * an in-progress second wait in btintel_pcie_do_top_reset() can
+	 * notice a later extension (e.g. WIFI_REQ_ROLE_SWITCH) instead of
+	 * timing out against a stale, shorter value.
+	 */
+	WRITE_ONCE(data->top_reset_deadline,
+		   jiffies + msecs_to_jiffies(expires));
+	WRITE_ONCE(data->top_reset_gen, data->top_reset_gen + 1);
+	set_bit(BTINTEL_PCIE_MBOX_TOP_RESET, &data->flags);
+	bt_dev_info(data->hdev, "TOP reset timer expires in %u ms", expires);
+	wake_up(&data->gp0_wait_q);
+}
+
 static void btintel_pcie_msix_gp1_handler(struct btintel_pcie_data *data)
 {
+	ktime_t calltime, delta, rettime;
+	unsigned long long duration;
+	bool top_reset_mbox = false;
 	bool target_access = false;
+	bool mbox2_status;
 	u32 addr = 0, size = 0;
 
+	calltime = ktime_get();
+
 	/* Read the Mail box status and registers */
 	data->mbox.mbox_status = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_STATUS_REG);
+	mbox2_status = data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX2;
 	if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX1) {
-		data->mbox.mbox1 = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_1_REG);
-		if (data->mbox.mbox1 ==
-		    BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING) {
-			bt_dev_info(data->hdev,
-				    "mailbox for target access");
+		data->mbox.mbox1 = btintel_pcie_rd_reg32(data,
+							 BTINTEL_PCIE_CSR_MBOX_1_REG);
+		if (data->mbox.mbox1 == BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING) {
+			bt_dev_info(data->hdev, "mailbox for target access");
+			clear_bit(BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+				  &data->flags);
 			target_access = true;
+		} else if (data->mbox.mbox1 == BTINTEL_PCIE_TOP_SILENT_RESET) {
+			if (mbox2_status)
+				top_reset_mbox = true;
+			else
+				set_bit(BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+					&data->flags);
+		} else {
+			clear_bit(BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+				  &data->flags);
 		}
 	}
 
-	if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX2) {
-		data->mbox.mbox2 = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_2_REG);
+	if (mbox2_status) {
+		data->mbox.mbox2 = btintel_pcie_rd_reg32(data,
+							 BTINTEL_PCIE_CSR_MBOX_2_REG);
 		if (target_access)
 			addr = data->mbox.mbox2;
+		if (test_and_clear_bit(BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+				       &data->flags))
+			top_reset_mbox = true;
 	}
 
 	if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX3) {
@@ -1929,10 +2120,38 @@ static void btintel_pcie_msix_gp1_handler(struct btintel_pcie_data *data)
 		}
 	}
 
+	/* Handle SCU state change inline, just log it */
+	if ((data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX1) &&
+	    data->mbox.mbox1 == BTINTEL_PCIE_CNVI_SCU_REG_MAIN_FSM_STICKY &&
+	    mbox2_status) {
+		if (data->mbox.mbox2 == BTINTEL_PCIE_SCU_CORE_IDLE_SET)
+			bt_dev_dbg(data->hdev, "TOP reset SCU CORE IDLE set");
+		if (data->mbox.mbox2 == BTINTEL_PCIE_SCU_CORE_IDLE_RESET)
+			bt_dev_dbg(data->hdev, "TOP reset SCU CORE IDLE reset");
+	}
+
+	/*
+	 * Handle TOP silent reset mbox notification inline. MBOX1 =
+	 * TOP_SILENT_RESET signals a TOP reset event, but the actual
+	 * sub-event code lives in MBOX2. If MBOX1 arrived alone,
+	 * BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING pairs it with this MBOX2
+	 * interrupt instead of using stale mailbox data. Handled directly
+	 * here (not deferred) regardless of whether data->hdev is valid
+	 * yet: btintel_pcie_handle_top_reset_mbox2() only touches state on
+	 * 'data', which is always allocated whenever MSI-X is live.
+	 */
+	if (top_reset_mbox)
+		btintel_pcie_handle_top_reset_mbox2(data, data->mbox.mbox2);
+
 	/* Mailbox is read, ack to FW */
 	btintel_pcie_set_reg_bits(data,
 				  BTINTEL_PCIE_CSR_IPC_DOORBELL_VEC_REG,
 				  BTINTEL_PCIE_CSR_DOORBELL_MBOX_READ_CONFIRM);
+
+	rettime = ktime_get();
+	delta = ktime_sub(rettime, calltime);
+	duration = (unsigned long long)ktime_to_ns(delta) >> 10;
+	bt_dev_dbg(data->hdev, "Mailbox acked in %llu usecs", duration);
 }
 
 /* This function handles the MSI-X interrupt for gp0 cause (bit 0 in
@@ -1987,11 +2206,27 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data)
 		signal_waitq = true;
 		break;
 	case BTINTEL_PCIE_FW_DL:
-		/* Error case is already handled. Ideally control shall not
-		 * reach here
+		/* During a TOP silent reset the controller returns to
+		 * boot_stage 0 while alive_intr_ctxt is still FW_DL (set
+		 * by btintel_pcie_send_sync()'s post-TOP fast-path).
+		 * btintel_pcie_do_top_reset()'s second wait polls
+		 * BOOT_STAGE_REG == 0 on gp0_wait_q; without this wake
+		 * the wait only re-checks on timer expiry and the driver
+		 * needlessly waits the full 250 ms / 5000 ms timeout even
+		 * if the FW signalled ready earlier.
 		 */
+		if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags))
+			signal_waitq = true;
 		break;
 	case BTINTEL_PCIE_INTEL_HCI_RESET1:
+		/* As with the FW_DL case above, a TOP silent reset can
+		 * bring GP0 here while still waiting on BOOT_STAGE_REG == 0
+		 * in btintel_pcie_do_top_reset()'s second wait. Wake it so
+		 * it re-checks immediately instead of timing out.
+		 */
+		if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags))
+			signal_waitq = true;
+
 		if (btintel_pcie_in_op(data)) {
 			submit_rx = true;
 			signal_waitq = true;
@@ -2835,6 +3070,7 @@ static struct btintel_pcie_causes_list causes_list[] = {
 	{ BTINTEL_PCIE_MSIX_FH_INT_CAUSES_0,	BTINTEL_PCIE_CSR_MSIX_FH_INT_MASK,	0x00 },
 	{ BTINTEL_PCIE_MSIX_FH_INT_CAUSES_1,	BTINTEL_PCIE_CSR_MSIX_FH_INT_MASK,	0x01 },
 	{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0,	BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK,	0x20 },
+	{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP1,	BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK,	0x21 },
 	{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HWEXP, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK,	0x23 },
 	{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_FWTRIG, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK,	0x25 },
 };
@@ -3526,12 +3762,160 @@ static int btintel_pcie_setup_internal(struct hci_dev *hdev)
 	return err;
 }
 
+static int btintel_pcie_do_top_reset(struct btintel_pcie_data *data,
+				     struct hci_dev *hdev)
+{
+	struct btintel_data *intel_data = hci_get_priv(hdev);
+	long ret;
+	u32 reg;
+	int err;
+
+	/* Set scratch register to indicate TOP silent reset to FW */
+	btintel_pcie_set_reg_bits(data, BTINTEL_PCIE_CSR_FUNC_SCRATCH_CSR,
+				  BTINTEL_PCIE_CSR_FUNC_SCRATCH_TOP_SILENT_RESET);
+
+	/* Clear mbox flag before sending reset to avoid race with ISR.
+	 * Both flags live on data->flags (not the separately-allocated
+	 * recovery struct): they are scoped to this single call and do
+	 * not need to survive a device_reprobe(), so tracking them here
+	 * removes any dependency on btintel_pcie_get_recovery()'s
+	 * GFP_ATOMIC allocation succeeding.
+	 */
+	clear_bit(BTINTEL_PCIE_MBOX_TOP_RESET, &data->flags);
+	clear_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+
+	err = btintel_send_intel_reset(hdev, intel_data->boot_addr);
+	if (err) {
+		bt_dev_err(hdev, "Intel Soft Reset failed (%d)", err);
+		data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+		goto done;
+	}
+
+	/* First wait: wait for initial mbox event from FW */
+	ret = wait_event_timeout(data->gp0_wait_q,
+				 test_bit(BTINTEL_PCIE_MBOX_TOP_RESET, &data->flags),
+				 msecs_to_jiffies(BTINTEL_PCIE_TOP_RESET_FALLBACK_MS));
+
+	if (!ret) {
+		bt_dev_warn(hdev,
+			    "TOP reset: no mbox received within %u ms; doing PLDR",
+			    BTINTEL_PCIE_TOP_RESET_FALLBACK_MS);
+		data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+		goto done;
+	}
+
+	/* Second wait: mbox received, wait for boot_stage == 0 against the
+	 * absolute deadline in data->top_reset_deadline. The GP1 handler
+	 * can extend that deadline (e.g. on WIFI_REQ_ROLE_SWITCH) and bumps
+	 * top_reset_gen when it does; loop so such an extension is honored
+	 * instead of being capped by whatever remaining time was passed to
+	 * an already-running wait_event_timeout().
+	 */
+	for (;;) {
+		unsigned int gen = READ_ONCE(data->top_reset_gen);
+		long remaining = (long)READ_ONCE(data->top_reset_deadline) -
+				  (long)jiffies;
+
+		if (remaining <= 0) {
+			ret = 0;
+			break;
+		}
+
+		wait_event_timeout(data->gp0_wait_q,
+				   btintel_pcie_top_reset_done(data, gen),
+				   remaining);
+
+		reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_BOOT_STAGE_REG);
+		if (reg == 0) {
+			ret = 1;
+			break;
+		}
+		/* Either genuinely timed out, or woken by a generation bump
+		 * (deadline extended) with boot_stage still nonzero; either
+		 * way, loop back and recompute remaining time against the
+		 * latest deadline.
+		 */
+	}
+
+	reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_BOOT_STAGE_REG);
+	bt_dev_info(hdev, "After side band reset boot-stage: 0x%8.8x", reg);
+
+	/* A NACK / no-response / invalid-response from WiFi means the TOP
+	 * reset negotiation itself failed, regardless of what boot_stage
+	 * happens to read right now; do not treat that as success.
+	 */
+	if (test_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags)) {
+		bt_dev_warn(hdev,
+			    "TOP reset: WiFi reported failure, triggering PLDR");
+		data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+	} else if (!ret && reg != 0) {
+		bt_dev_warn(hdev,
+			    "TOP reset failed (boot_stage:0x%08x), triggering PLDR",
+			    reg);
+		data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+	} else {
+		data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR;
+	}
+
+done:
+	clear_bit(BTINTEL_PCIE_MBOX_TOP_RESET, &data->flags);
+	btintel_clear_flag(hdev, INTEL_TOP_RESET);
+	btintel_pcie_clr_reg_bits(data, BTINTEL_PCIE_CSR_FUNC_SCRATCH_CSR,
+				  BTINTEL_PCIE_CSR_FUNC_SCRATCH_TOP_SILENT_RESET);
+
+	/* BTINTEL_PCIE_TOP_RESET is still set on data->flags, allowing
+	 * btintel_pcie_request_reset() to proceed without SETUP_DONE.
+	 * The flag is cleared by reset_work after recovery completes.
+	 */
+	btintel_pcie_request_reset(data, data->reset_type);
+
+	/* Return -ECANCELED so btintel_pcie_setup() propagates failure
+	 * up to hci_dev_open_sync(). The reset_work scheduled above
+	 * will tear down this hdev instance via device_reprobe(); if
+	 * HCI init proceeded on the current instance it would race
+	 * with that teardown and enqueue commands to a controller that
+	 * is being reset.
+	 */
+	return -ECANCELED;
+}
+
 static int btintel_pcie_setup(struct hci_dev *hdev)
 {
-	int err, fw_dl_retry = 0;
 	struct btintel_pcie_data *data = hci_get_drvdata(hdev);
+	struct btintel_pcie_dev_recovery *rec;
+	int err, fw_dl_retry = 0;
+
+	/* Restore TOP_RESET intent from persistent recovery data across
+	 * device_reprobe(). The recovery struct is keyed by PCI BDF and
+	 * outlives the freed 'data' from the previous instance.
+	 */
+	rec = btintel_pcie_get_recovery(data->pdev);
+	if (rec && test_and_clear_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING,
+				      &rec->flags)) {
+		set_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+		btintel_set_flag(hdev, INTEL_TOP_RESET);
+	}
+
+	while ((err = btintel_pcie_setup_internal(hdev)) != 0) {
+		if (err == -ENODEV)
+			break;
+
+		/* Any failure while a TOP reset is pending means the
+		 * normal firmware-download retry below is not going to
+		 * help (the controller is in the silent-reset state the
+		 * TOP reset exists to recover from); dispatch it instead
+		 * of exhausting retries on a path that cannot succeed.
+		 * Checked on every failing iteration, not just via the
+		 * loop condition, so a TOP reset pending during the final
+		 * retry attempt (whatever error it returns, not only
+		 * -ECANCELED) is not missed.
+		 */
+		if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags))
+			return btintel_pcie_do_top_reset(data, hdev);
+
+		if (fw_dl_retry++ >= 1)
+			break;
 
-	while ((err = btintel_pcie_setup_internal(hdev)) && fw_dl_retry++ < 1) {
 		bt_dev_err(hdev, "Firmware download retry count: %d",
 			   fw_dl_retry);
 		btintel_pcie_dump_debug_registers(hdev);
@@ -3554,30 +3938,53 @@ static int btintel_pcie_setup(struct hci_dev *hdev)
 		btintel_pcie_start_rx(data);
 	}
 
-	if (!err)
+	if (!err) {
 		set_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags);
+
+		/* Retry a WiFi-follower PLDR request that
+		 * btintel_pcie_handle_top_reset_mbox2() had to defer
+		 * because setup had not completed yet, instead of
+		 * leaving it dropped.
+		 */
+		if (test_and_clear_bit(BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING,
+				       &data->flags)) {
+			bt_dev_info(hdev,
+				    "Replaying deferred WiFi-follower PLDR request");
+			set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+			if (!btintel_pcie_request_reset(data,
+							BTINTEL_PCIE_IOSF_PRR_PLDR))
+				clear_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+		}
+	}
 	return err;
 }
 
 static struct btintel_pcie_dev_recovery *
-btintel_pcie_get_recovery(struct pci_dev *pdev, struct device *dev)
+btintel_pcie_get_recovery(struct pci_dev *pdev)
 {
 	struct btintel_pcie_dev_recovery *tmp, *data = NULL;
 	const char *name = pci_name(pdev);
 	const size_t name_len = strlen(name) + 1;
-	struct hci_dev *hdev = to_hci_dev(dev);
+	unsigned long flags;
 
-	spin_lock(&btintel_pcie_recovery_lock);
+	/*
+	 * This function runs both in process context (setup, reset_work,
+	 * request_reset, hw_error) and in hardirq context (GP1 MSI-X
+	 * handler). Use irqsave/irqrestore so a GP1 interrupt firing on
+	 * the same CPU while a process-context caller holds the lock
+	 * cannot spin waiting on itself and hard-lock the system.
+	 */
+	spin_lock_irqsave(&btintel_pcie_recovery_lock, flags);
 	list_for_each_entry(tmp, &btintel_pcie_recovery_list, list) {
 		if (strcmp(tmp->name, name))
 			continue;
 		data = tmp;
 		break;
 	}
-	spin_unlock(&btintel_pcie_recovery_lock);
+	spin_unlock_irqrestore(&btintel_pcie_recovery_lock, flags);
 
 	if (data) {
-		bt_dev_dbg(hdev, "Found restart data for BDF: %s", data->name);
+		dev_dbg(&pdev->dev, "Found restart data for BDF: %s", data->name);
 		return data;
 	}
 
@@ -3586,9 +3993,23 @@ btintel_pcie_get_recovery(struct pci_dev *pdev, struct device *dev)
 		return NULL;
 
 	strscpy(data->name, name, name_len);
-	spin_lock(&btintel_pcie_recovery_lock);
+	spin_lock_irqsave(&btintel_pcie_recovery_lock, flags);
+	/* Re-search under lock to avoid a TOCTOU race where a concurrent
+	 * caller (GP1 ISR, hw_error, setup or reset_work) inserted an
+	 * entry for the same BDF between the initial search and this
+	 * insertion. Without this check the list can end up with
+	 * duplicate entries and later lookups may return an orphaned
+	 * struct that no longer receives updates.
+	 */
+	list_for_each_entry(tmp, &btintel_pcie_recovery_list, list) {
+		if (strcmp(tmp->name, name))
+			continue;
+		spin_unlock_irqrestore(&btintel_pcie_recovery_lock, flags);
+		kfree(data);
+		return tmp;
+	}
 	list_add_tail(&data->list, &btintel_pcie_recovery_list);
-	spin_unlock(&btintel_pcie_recovery_lock);
+	spin_unlock_irqrestore(&btintel_pcie_recovery_lock, flags);
 
 	return data;
 }
@@ -3604,13 +4025,12 @@ static void btintel_pcie_free_restart_list(void)
 	}
 }
 
-static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev,
-					    struct device *dev)
+static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev)
 {
 	struct btintel_pcie_dev_recovery *data;
 	time64_t retry_window;
 
-	data = btintel_pcie_get_recovery(pdev, dev);
+	data = btintel_pcie_get_recovery(pdev);
 	if (!data)
 		return;
 
@@ -3681,7 +4101,16 @@ static int btintel_pcie_acpi_reset_method(struct btintel_pcie_data *data)
 	return ret;
 }
 
-static void btintel_pcie_perform_pldr(struct btintel_pcie_data *data)
+/* Returns 0 if the ACPI _PRR reset succeeded and device_reprobe() tore
+ * down and re-probed 'data' (which is therefore no longer valid to
+ * touch), or a negative errno if the reset itself failed and 'data' is
+ * still alive and needs the caller's failure cleanup. Must not be
+ * inferred from device_reprobe()'s own return value: device_reprobe()
+ * always runs .remove() (freeing 'data') before attempting the fresh
+ * probe, so a nonzero device_reprobe() result does not mean 'data' is
+ * still alive.
+ */
+static int btintel_pcie_perform_pldr(struct btintel_pcie_data *data)
 {
 	struct pci_dev *pdev = data->pdev;
 	struct pci_dev *wifi = NULL;
@@ -3702,7 +4131,7 @@ static void btintel_pcie_perform_pldr(struct btintel_pcie_data *data)
 
 	bus = pdev->bus;
 	if (!bus)
-		return;
+		return -ENODEV;
 
 	list_for_each_entry(tmp, &bus->devices, bus_list) {
 		if (pci_match_id(wifi_device_ids, tmp)) {
@@ -3729,10 +4158,10 @@ static void btintel_pcie_perform_pldr(struct btintel_pcie_data *data)
 		pci_dev_put(wifi);
 	}
 
-	if (!ret) {
-		if (device_reprobe(&pdev->dev))
-			BT_ERR("BT reprobe failed for BDF:%s", pci_name(pdev));
-	}
+	if (!ret && device_reprobe(&pdev->dev))
+		BT_ERR("BT reprobe failed for BDF:%s", pci_name(pdev));
+
+	return ret;
 }
 
 /*
@@ -3769,6 +4198,27 @@ static int btintel_pcie_perform_flr(struct btintel_pcie_data *data)
 	return 0;
 }
 
+/* Restore the reset-serialization/bookkeeping state after a reset attempt
+ * failed with 'data' still alive (no device_reprobe() tore it down and
+ * started fresh), so a later recovery attempt is not permanently blocked.
+ * Does not touch BTINTEL_PCIE_CORE_HALTED, which is a separate one-shot
+ * latch intentionally cleared only by re-probe (see
+ * btintel_pcie_msix_hw_exp_handler()).
+ */
+static void btintel_pcie_reset_failed(struct btintel_pcie_data *data)
+{
+	struct btintel_pcie_dev_recovery *rec = btintel_pcie_get_recovery(data->pdev);
+
+	clear_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+	clear_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags);
+	if (rec)
+		clear_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING, &rec->flags);
+	enable_work(&data->coredump_work);
+	enable_work(&data->hwexp_work);
+	enable_work(&data->fwtrigger_work);
+	enable_work(&data->mbox_work);
+}
+
 static void btintel_pcie_reset_work(struct work_struct *wk)
 {
 	struct btintel_pcie_data *data =
@@ -3810,15 +4260,39 @@ static void btintel_pcie_reset_work(struct work_struct *wk)
 	 * or removed from the bus list while this code runs.
 	 */
 	if (data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR) {
-		btintel_pcie_perform_pldr(data);
+		if (btintel_pcie_perform_pldr(data))
+			btintel_pcie_reset_failed(data);
 		goto out;
 	}
 
 	if (btintel_pcie_perform_flr(data)) {
-		enable_work(&data->coredump_work);
-		enable_work(&data->hwexp_work);
-		enable_work(&data->fwtrigger_work);
-		enable_work(&data->mbox_work);
+		struct btintel_pcie_dev_recovery *rec;
+
+		rec = btintel_pcie_get_recovery(data->pdev);
+		if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags)) {
+			bt_dev_warn(data->hdev,
+				    "TOP reset FLR failed, falling back to PLDR");
+			clear_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+			if (rec)
+				clear_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING,
+					  &rec->flags);
+			data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+			/* The TOP_RESET bypass above was consumed by this
+			 * fallback attempt; if it also fails, run the
+			 * shared cleanup below instead of leaving the
+			 * device stuck with no further recovery.
+			 */
+			if (btintel_pcie_perform_pldr(data))
+				btintel_pcie_reset_failed(data);
+			goto out;
+		}
+
+		/* FLR failed: 'data' is still alive and no fresh probe
+		 * ran to consume the persistent TOP-reset intent. Restore
+		 * the bookkeeping used by the reset-serialization contract
+		 * so a later recovery can proceed.
+		 */
+		btintel_pcie_reset_failed(data);
 	}
 
 out:
@@ -3848,19 +4322,51 @@ static void btintel_pcie_reset_work(struct work_struct *wk)
  * tears down and re-probes 'data', so there is no "in-flight"
  * reset to follow up after device_reprobe() succeeds.
  */
-static void btintel_pcie_request_reset(struct btintel_pcie_data *data,
+static bool btintel_pcie_request_reset(struct btintel_pcie_data *data,
 				       enum btintel_pcie_reset_type type)
 {
-	if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags))
-		return;
+	struct btintel_pcie_dev_recovery *rec = NULL;
 
 	if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags))
-		return;
+		return false;
+
+	/* Normal path requires SETUP_DONE. TOP reset recovery calls this
+	 * from btintel_pcie_do_top_reset() before setup completes; the
+	 * BTINTEL_PCIE_TOP_RESET flag authorizes bypassing that gate.
+	 */
+	if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags) &&
+	    !test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags)) {
+		clear_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags);
+		return false;
+	}
+
+	if (type == BTINTEL_PCIE_IOSF_TOP_RESET) {
+		rec = btintel_pcie_get_recovery(data->pdev);
+		if (!rec) {
+			clear_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS,
+				  &data->flags);
+			return false;
+		}
+
+		set_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+		set_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING, &rec->flags);
+	}
 
 	data->reset_type = type;
 
 	pci_dev_get(data->pdev);
-	schedule_work(&data->reset_work);
+	if (!schedule_work(&data->reset_work)) {
+		pci_dev_put(data->pdev);
+		if (type == BTINTEL_PCIE_IOSF_TOP_RESET) {
+			clear_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+			clear_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING,
+				  &rec->flags);
+		}
+		clear_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags);
+		return false;
+	}
+
+	return true;
 }
 
 static void btintel_pcie_hci_reset(struct hci_dev *hdev)
@@ -3907,10 +4413,41 @@ static struct attribute *btintel_pcie_attrs[] = {
 
 ATTRIBUTE_GROUPS(btintel_pcie);
 
+static const char *btintel_pcie_resettype_to_str(u8 reset_type)
+{
+	switch (reset_type) {
+	case BTINTEL_PCIE_IOSF_PRR_FLR:
+		return "FLR";
+	case BTINTEL_PCIE_IOSF_PRR_PLDR:
+		return "PLDR";
+	case BTINTEL_PCIE_IOSF_TOP_RESET:
+		return "TOP_RESET";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+static bool btintel_pcie_top_reset_supported(struct btintel_pcie_data *data)
+{
+	struct btintel_data *cnvi_data = hci_get_priv(data->hdev);
+	u8 hw_variant = INTEL_HW_VARIANT(cnvi_data->cnvi_bt);
+
+	if (hw_variant >= BTINTEL_HWID_SCP2 &&
+	    data->pdev->subsystem_device == BTINTEL_PCIE_SUBSYSTEM_ID_PEP) {
+		bt_dev_info(data->hdev, "HW variant 0x%02x supports top-reset",
+			    hw_variant);
+		return true;
+	}
+
+	bt_dev_dbg(data->hdev,
+		   "HW variant 0x%02x does not support top-reset", hw_variant);
+	return false;
+}
+
 static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code)
 {
-	struct btintel_pcie_dev_recovery *rec;
 	struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev);
+	struct btintel_pcie_dev_recovery *rec;
 	struct pci_dev *pdev = dev_data->pdev;
 	enum btintel_pcie_reset_type type;
 	time64_t retry_window;
@@ -3920,15 +4457,18 @@ static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code)
 
 	btintel_pcie_dump_debug_registers(hdev);
 
-	rec = btintel_pcie_get_recovery(pdev, &hdev->dev);
+	rec = btintel_pcie_get_recovery(pdev);
 	if (!rec)
 		return;
 
-	type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR
-			      : BTINTEL_PCIE_IOSF_PRR_FLR;
+	if (code == 0x13 && btintel_pcie_top_reset_supported(dev_data))
+		type = BTINTEL_PCIE_IOSF_TOP_RESET;
+	else
+		type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR
+				      : BTINTEL_PCIE_IOSF_PRR_FLR;
 
 	bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code,
-		   type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR");
+		   btintel_pcie_resettype_to_str(type));
 	retry_window = ktime_get_boottime_seconds() - rec->last_error;
 
 	if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS &&
@@ -3941,8 +4481,9 @@ static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code)
 			   rec->last_error);
 		return;
 	}
-	btintel_pcie_inc_recovery_count(pdev, &hdev->dev);
-	btintel_pcie_request_reset(dev_data, type);
+
+	if (btintel_pcie_request_reset(dev_data, type))
+		btintel_pcie_inc_recovery_count(pdev);
 }
 
 static bool btintel_pcie_wakeup(struct hci_dev *hdev)
@@ -4045,10 +4586,15 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data)
 
 	/* Publish hdev only after successful registration; the coredump
 	 * worker bails on !data->hdev, so it never observes a half-set-up
-	 * device.
+	 * device. A TOP reset MBOX2 event racing this publish is handled
+	 * directly by the GP1 handler regardless of ordering (see
+	 * btintel_pcie_handle_top_reset_mbox2()), so no synchronization is
+	 * needed here.
 	 */
 	data->hdev = hdev;
+
 	data->dmp_hdr.driver_name = KBUILD_MODNAME;
+
 	return 0;
 }
 
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 3030b4e8b750..3911bfcd8f6f 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -10,6 +10,7 @@
 #define BTINTEL_PCIE_CSR_BASE			(0x000)
 #define BTINTEL_PCIE_CSR_FUNC_CTRL_REG		(BTINTEL_PCIE_CSR_BASE + 0x024)
 #define BTINTEL_PCIE_CSR_HW_REV_REG		(BTINTEL_PCIE_CSR_BASE + 0x028)
+#define BTINTEL_PCIE_CSR_FUNC_SCRATCH_CSR	(BTINTEL_PCIE_CSR_BASE + 0x02C)
 #define BTINTEL_PCIE_CSR_RF_ID_REG		(BTINTEL_PCIE_CSR_BASE + 0x09C)
 #define BTINTEL_PCIE_CSR_BOOT_STAGE_REG		(BTINTEL_PCIE_CSR_BASE + 0x108)
 #define BTINTEL_PCIE_CSR_IPC_CONTROL_REG	(BTINTEL_PCIE_CSR_BASE + 0x10C)
@@ -24,6 +25,7 @@
 #define BTINTEL_PCIE_CSR_MBOX_3_REG		(BTINTEL_PCIE_CSR_BASE + 0x178)
 #define BTINTEL_PCIE_CSR_MBOX_4_REG		(BTINTEL_PCIE_CSR_BASE + 0x17C)
 #define BTINTEL_PCIE_CSR_MBOX_STATUS_REG	(BTINTEL_PCIE_CSR_BASE + 0x180)
+#define BTINTEL_PCIE_CSR_RESET_DEBUG_AD	(BTINTEL_PCIE_CSR_BASE + 0x220)
 #define BTINTEL_PCIE_PRPH_DEV_ADDR_REG		(BTINTEL_PCIE_CSR_BASE + 0x440)
 #define BTINTEL_PCIE_PRPH_DEV_RD_REG		(BTINTEL_PCIE_CSR_BASE + 0x458)
 #define BTINTEL_PCIE_CSR_HBUS_TARG_WRPTR	(BTINTEL_PCIE_CSR_BASE + 0x460)
@@ -125,6 +127,13 @@
 #define BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2	0xD346
 #define BTINTEL_PCIE_DEVICE_ID_PTL_FMP2		0xE476
 
+#define BTINTEL_PCIE_SUBSYSTEM_ID_PEP	0x0012
+
+#define BTINTEL_PCIE_TOP_RESET_DBG_RESET_REG		0x0AAAFFFF
+#define BTINTEL_PCIE_CSR_FUNC_SCRATCH_TOP_SILENT_RESET	BIT(9)
+#define BTINTEL_PCIE_SCU_CORE_IDLE_SET			0x0000000F
+#define BTINTEL_PCIE_SCU_CORE_IDLE_RESET		0x0000000E
+
 /* Causes for the FH register interrupts */
 enum msix_fh_int_causes {
 	BTINTEL_PCIE_MSIX_FH_INT_CAUSES_0	= BIT(0),	/* cause 0 */
@@ -158,7 +167,23 @@ enum {
 	BTINTEL_PCIE_SETUP_DONE,
 	BTINTEL_PCIE_MAIL_BOX_INTR,
 	BTINTEL_PCIE_MBOX_PARSE_PENDING,
-	BTINTEL_PCIE_MBOX_PARSE_READY
+	BTINTEL_PCIE_MBOX_PARSE_READY,
+	BTINTEL_PCIE_TOP_RESET,
+	BTINTEL_PCIE_MBOX_TOP_RESET,
+	BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+	/* Set on data->flags: a WiFi-follower PLDR request was rejected by
+	 * btintel_pcie_request_reset() because setup had not completed yet;
+	 * retried once BTINTEL_PCIE_SETUP_DONE is set.
+	 */
+	BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING,
+	/* Only BTINTEL_PCIE_REC_TOP_RESET_PENDING below lives on rec->flags
+	 * (it must survive device_reprobe(), which frees 'data'); the rest
+	 * of the TOP reset mbox negotiation state is scoped to a single
+	 * btintel_pcie_do_top_reset() call and lives on data->flags so it
+	 * never depends on the recovery struct's GFP_ATOMIC allocation.
+	 */
+	BTINTEL_PCIE_REC_TOP_RESET_PENDING,
+	BTINTEL_PCIE_TOP_RESET_FAILED,
 };
 
 enum btintel_pcie_tlv_type {
@@ -188,14 +213,25 @@ enum msix_mbox_int_causes {
 enum btintel_pcie_reset_type {
 	BTINTEL_PCIE_IOSF_PRR_FLR = 0,
 	BTINTEL_PCIE_IOSF_PRR_PLDR = 1,
+	BTINTEL_PCIE_IOSF_TOP_RESET = 2,
 };
 
-enum btintel_pcie_mbox_msg {
+enum btintel_pcie_mbox1_msg {
 	BTINTEL_PCIE_NO_USE = 0,
-	BTINTEL_PCIE_TOP_SILENT_RESET,
-	BTINTEL_PCIE_SB_AUDIO_DEVICE_REPORT,
-	BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING,
-	BTINTEL_PCIE_LAST_MESSAGE = 4095
+	BTINTEL_PCIE_TOP_SILENT_RESET = 1,
+	BTINTEL_PCIE_SB_AUDIO_DEVICE_REPORT = 2,
+	BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING = 3,
+	BTINTEL_PCIE_CNVI_SCU_REG_MAIN_FSM_STICKY = 0xC08,
+	BTINTEL_PCIE_LAST_MESSAGE = 0xFFF,
+};
+
+enum btintel_pcie_mbox2_msg {
+	BTINTEL_PCIE_TOP_RESET_WIFI_REQUEST = 257, /* 0x101 */
+	BTINTEL_PCIE_TOP_RESET_WIFI_ACK = 513, /* 0x201 */
+	BTINTEL_PCIE_TOP_RESET_WIFI_NACK = 514, /* 0x202 */
+	BTINTEL_PCIE_TOP_RESET_WIFI_REQ_ROLE_SWITCH = 515, /* 0x203 */
+	BTINTEL_PCIE_TOP_RESET_WIFI_NO_RESPONSE = 771, /* 0x303 */
+	BTINTEL_PCIE_TOP_RESET_WIFI_RESPONSE_INVALID = 4095, /* 0xFFF */
 };
 
 #define BTINTEL_PCIE_MSIX_NON_AUTO_CLEAR_CAUSE	BIT(7)
@@ -783,6 +819,16 @@ struct btintel_pcie_data {
 	wait_queue_head_t	mbox_parse_wait_q;
 	/* Timestamp captured in GP1 handler when mbox interrupt is received */
 	ktime_t	mbox_intr_ts;
+	wait_queue_head_t	coredump_wait_q;
+	u32			top_reset_expires;
+	/* Absolute jiffies deadline for the second wait in
+	 * btintel_pcie_do_top_reset(); top_reset_gen is bumped every
+	 * time the GP1 handler updates the deadline so an in-progress
+	 * wait_event_timeout() notices the change instead of timing
+	 * out against a stale value (e.g. on WIFI_REQ_ROLE_SWITCH).
+	 */
+	unsigned long		top_reset_deadline;
+	unsigned int		top_reset_gen;
 };
 
 static inline u32 btintel_pcie_rd_reg32(struct btintel_pcie_data *data,
-- 
2.43.0


  reply	other threads:[~2026-10-14 11:48 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 11:47 [PATCH 1/2] Bluetooth: btintel: Add TOP silent reset hooks Chandrashekar Devegowda
2026-10-14 11:47 ` Chandrashekar Devegowda [this message]
2026-10-14 12:55 ` [1/2] " bluez.test.bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261014114752.174669-2-chandrashekar.devegowda@intel.com \
    --to=chandrashekar.devegowda@intel.com \
    --cc=chethan.tumkur.narayan@intel.com \
    --cc=kiran.k@intel.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox