]> Gentwo Git Trees - linux/.git/commitdiff
Bluetooth: btintel_pcie: Support for S4 (Hibernate)
authorRavindra <ravindra@intel.com>
Wed, 15 Oct 2025 09:39:02 +0000 (15:09 +0530)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Mon, 1 Dec 2025 21:00:07 +0000 (16:00 -0500)
During S4 (hibernate), the Bluetooth device loses power. Upon resume,
the driver performs the following actions:

1. Unregisters hdev
2. Calls function level reset
3. Registers hdev

Test case:
- run command sudo rtcwake -m disk -s 60

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 a075d8ec467738acc741e46c53acd4b4cdb61c0d..f280bcc61bbfb763b0abc2ab303996590cd584c2 100644 (file)
@@ -825,6 +825,11 @@ static inline bool btintel_pcie_in_d0(struct btintel_pcie_data *data)
        return !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY);
 }
 
+static inline bool btintel_pcie_in_device_halt(struct btintel_pcie_data *data)
+{
+       return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_DEVICE_HALTED;
+}
+
 static void btintel_pcie_wr_sleep_cntrl(struct btintel_pcie_data *data,
                                        u32 dxstate)
 {
@@ -2532,6 +2537,8 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg)
        dxstate = (mesg.event == PM_EVENT_SUSPEND ?
                   BTINTEL_PCIE_STATE_D3_HOT : BTINTEL_PCIE_STATE_D3_COLD);
 
+       data->pm_sx_event = mesg.event;
+
        data->gp0_received = false;
 
        start = ktime_get();
@@ -2581,6 +2588,20 @@ static int btintel_pcie_resume(struct device *dev)
 
        start = ktime_get();
 
+       /* When the system enters S4 (hibernate) mode, bluetooth device loses
+        * power, which results in the erasure of its loaded firmware.
+        * Consequently, function level reset (flr) is required on system
+        * resume to bring the controller back into an operational state by
+        * initiating a new firmware download.
+        */
+
+       if (data->pm_sx_event == PM_EVENT_FREEZE ||
+           data->pm_sx_event == PM_EVENT_HIBERNATE) {
+               set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+               btintel_pcie_reset(data->hdev);
+               return 0;
+       }
+
        /* 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,
@@ -2589,6 +2610,26 @@ static int btintel_pcie_resume(struct device *dev)
                bt_dev_err(data->hdev,
                           "Timeout (%u ms) on alive interrupt for D0 entry",
                           BTINTEL_DEFAULT_INTR_TIMEOUT_MS);
+
+               /* 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);
+               }
                return -EBUSY;
        }
 
index 04b21f968ad30ff75d256dedc3b1a7ddb9009801..48e1ae1793e5cf25b3e9bf52b9e5c0f802670827 100644 (file)
@@ -464,6 +464,7 @@ struct btintel_pcie_dump_header {
  * @txq: TX Queue struct
  * @rxq: RX Queue struct
  * @alive_intr_ctxt: Alive interrupt context
+ * @pm_sx_event: PM event on which system got suspended
  */
 struct btintel_pcie_data {
        struct pci_dev  *pdev;
@@ -513,6 +514,7 @@ struct btintel_pcie_data {
        u32     alive_intr_ctxt;
        struct btintel_pcie_dbgc        dbgc;
        struct btintel_pcie_dump_header dmp_hdr;
+       u8      pm_sx_event;
 };
 
 static inline u32 btintel_pcie_rd_reg32(struct btintel_pcie_data *data,