[SRU][Questing][PATCH 5/5] Bluetooth: btintel_pcie: Suspend/Resume: Controller doorbell interrupt handling

You-Sheng Yang vicamo.yang at canonical.com
Tue Jan 20 08:38:03 UTC 2026


From: Ravindra <ravindra at intel.com>

BugLink: https://bugs.launchpad.net/bugs/2136804

Due to a hardware bug during suspend/resume, the controller may miss a
doorbell interrupt. To address this, a retry mechanism has been added to
inform the controller before reporting a failure.

Test case:
- run suspend and resume cycles.

Signed-off-by: Ravindra <ravindra at intel.com>
Signed-off-by: Kiran K <kiran.k at intel.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz at intel.com>
(cherry picked from commit 88c6216a52ea592ec351dddd042ecb0247325be5)
Signed-off-by: You-Sheng Yang <vicamo.yang at canonical.com>
Acked-by: Kuan-Ying Lee <kuan-ying.lee at canonical.com>
Acked-by: Chia-Lin Kao (AceLan) <chia-lin.kao at canonical.com>
Signed-off-by: Kuan-Ying Lee <kuan-ying.lee at canonical.com>
---
 drivers/bluetooth/btintel_pcie.c | 113 +++++++++++++++++++------------
 drivers/bluetooth/btintel_pcie.h |   2 +
 2 files changed, 73 insertions(+), 42 deletions(-)

diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index f280bcc61bbfb..b0ad3c759ef50 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -2524,6 +2524,48 @@ static void btintel_pcie_coredump(struct device *dev)
 }
 #endif
 
+static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate)
+{
+	int retry = 0, status;
+	u32 dx_intr_timeout_ms = 200;
+
+	do {
+		data->gp0_received = false;
+
+		btintel_pcie_wr_sleep_cntrl(data, dxstate);
+
+		status = wait_event_timeout(data->gp0_wait_q, data->gp0_received,
+			msecs_to_jiffies(dx_intr_timeout_ms));
+
+		if (status)
+			return 0;
+
+		bt_dev_warn(data->hdev,
+			   "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d",
+			   dx_intr_timeout_ms, dxstate, retry);
+
+		/* clear gp0 cause */
+		btintel_pcie_clr_reg_bits(data,
+					  BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES,
+					  BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0);
+
+		/* A hardware bug may cause the alive interrupt to be missed.
+		 * Check if the controller reached the expected state and retry
+		 * the operation only if it hasn't.
+		 */
+		if (dxstate == BTINTEL_PCIE_STATE_D0) {
+			if (btintel_pcie_in_d0(data))
+				return 0;
+		} else {
+			if (btintel_pcie_in_d3(data))
+				return 0;
+		}
+
+	} while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES);
+
+	return -EBUSY;
+}
+
 static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg)
 {
 	struct pci_dev *pdev = to_pci_dev(dev);
@@ -2539,26 +2581,18 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg)
 
 	data->pm_sx_event = mesg.event;
 
-	data->gp0_received = false;
-
 	start = ktime_get();
 
 	/* Refer: 6.4.11.7 -> Platform power management */
-	btintel_pcie_wr_sleep_cntrl(data, dxstate);
-	err = wait_event_timeout(data->gp0_wait_q, data->gp0_received,
-				 msecs_to_jiffies(BTINTEL_DEFAULT_INTR_TIMEOUT_MS));
-	if (err == 0) {
-		bt_dev_err(data->hdev,
-			   "Timeout (%u ms) on alive interrupt for D3 entry",
-			   BTINTEL_DEFAULT_INTR_TIMEOUT_MS);
-		return -EBUSY;
-	}
+	err = btintel_pcie_set_dxstate(data, dxstate);
+
+	if (err)
+		return err;
 
 	bt_dev_dbg(data->hdev,
 		   "device entered into d3 state from d0 in %lld us",
 		   ktime_to_us(ktime_get() - start));
-
-	return 0;
+	return err;
 }
 
 static int btintel_pcie_suspend(struct device *dev)
@@ -2603,40 +2637,35 @@ static int btintel_pcie_resume(struct device *dev)
 	}
 
 	/* Refer: 6.4.11.7 -> Platform power management */
-	btintel_pcie_wr_sleep_cntrl(data, BTINTEL_PCIE_STATE_D0);
-	err = wait_event_timeout(data->gp0_wait_q, data->gp0_received,
-				 msecs_to_jiffies(BTINTEL_DEFAULT_INTR_TIMEOUT_MS));
+	err = btintel_pcie_set_dxstate(data, BTINTEL_PCIE_STATE_D0);
+
 	if (err == 0) {
-		bt_dev_err(data->hdev,
-			   "Timeout (%u ms) on alive interrupt for D0 entry",
-			   BTINTEL_DEFAULT_INTR_TIMEOUT_MS);
+		bt_dev_dbg(data->hdev,
+			   "device entered into d0 state from d3 in %lld us",
+			   ktime_to_us(ktime_get() - start));
+		return err;
+	}
 
-		/* Trigger function level reset if the controller is in error
-		 * state during resume() to bring back the controller to
-		 * operational mode
-		 */
+	/* Trigger function level reset if the controller is in error
+	 * state during resume() to bring back the controller to
+	 * operational mode
+	 */
 
-		data->boot_stage_cache = btintel_pcie_rd_reg32(data,
-				BTINTEL_PCIE_CSR_BOOT_STAGE_REG);
-		if (btintel_pcie_in_error(data) ||
-				btintel_pcie_in_device_halt(data)) {
-			bt_dev_err(data->hdev, "Controller in error state for D0 entry");
-			if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS,
-					      &data->flags)) {
-				data->dmp_hdr.trigger_reason =
-					BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT;
-				queue_work(data->workqueue, &data->rx_work);
-			}
-			set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
-			btintel_pcie_reset(data->hdev);
+	data->boot_stage_cache = btintel_pcie_rd_reg32(data,
+			BTINTEL_PCIE_CSR_BOOT_STAGE_REG);
+	if (btintel_pcie_in_error(data) ||
+			btintel_pcie_in_device_halt(data)) {
+		bt_dev_err(data->hdev, "Controller in error state for D0 entry");
+		if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS,
+				      &data->flags)) {
+			data->dmp_hdr.trigger_reason =
+				BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT;
+			queue_work(data->workqueue, &data->rx_work);
 		}
-		return -EBUSY;
+		set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+		btintel_pcie_reset(data->hdev);
 	}
-
-	bt_dev_dbg(data->hdev,
-		    "device entered into d0 state from d3 in %lld us",
-		     ktime_to_us(ktime_get() - start));
-	return 0;
+	return err;
 }
 
 static const struct dev_pm_ops btintel_pcie_pm_ops = {
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 48e1ae1793e5c..e3d941ffef4aa 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -158,6 +158,8 @@ enum msix_mbox_int_causes {
 /* Default interrupt timeout in msec */
 #define BTINTEL_DEFAULT_INTR_TIMEOUT_MS	3000
 
+#define BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES	3
+
 /* The number of descriptors in TX queues */
 #define BTINTEL_PCIE_TX_DESCS_COUNT	32
 
-- 
2.51.0




More information about the kernel-team mailing list