]> Gentwo Git Trees - linux/.git/commitdiff
Bluetooth: btintel_pcie: Suspend/Resume: Controller doorbell interrupt handling
authorRavindra <ravindra@intel.com>
Wed, 15 Oct 2025 09:39:03 +0000 (15:09 +0530)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Mon, 1 Dec 2025 21:00:07 +0000 (16:00 -0500)
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@intel.com>
Signed-off-by: Kiran K <kiran.k@intel.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
drivers/bluetooth/btintel_pcie.c
drivers/bluetooth/btintel_pcie.h

index f280bcc61bbfb763b0abc2ab303996590cd584c2..b0ad3c759ef502c03270b52b284e5d11aa020160 100644 (file)
@@ -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 = {
index 48e1ae1793e5cf25b3e9bf52b9e5c0f802670827..e3d941ffef4aa8072d95b68e8ca86d4bde9140bb 100644 (file)
@@ -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