Bluetooth: btintel_pcie: Suspend/Resume: Controller doorbell interrupt handling

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>
This commit is contained in:
Ravindra 2025-10-15 15:09:03 +05:30 committed by Luiz Augusto von Dentz
parent 1fb0d830da
commit 88c6216a52
2 changed files with 75 additions and 44 deletions

View File

@ -2524,6 +2524,48 @@ static void btintel_pcie_coredump(struct device *dev)
} }
#endif #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) static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg)
{ {
struct pci_dev *pdev = to_pci_dev(dev); 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->pm_sx_event = mesg.event;
data->gp0_received = false;
start = ktime_get(); start = ktime_get();
/* Refer: 6.4.11.7 -> Platform power management */ /* Refer: 6.4.11.7 -> Platform power management */
btintel_pcie_wr_sleep_cntrl(data, dxstate); err = btintel_pcie_set_dxstate(data, dxstate);
err = wait_event_timeout(data->gp0_wait_q, data->gp0_received,
msecs_to_jiffies(BTINTEL_DEFAULT_INTR_TIMEOUT_MS)); if (err)
if (err == 0) { return err;
bt_dev_err(data->hdev,
"Timeout (%u ms) on alive interrupt for D3 entry",
BTINTEL_DEFAULT_INTR_TIMEOUT_MS);
return -EBUSY;
}
bt_dev_dbg(data->hdev, bt_dev_dbg(data->hdev,
"device entered into d3 state from d0 in %lld us", "device entered into d3 state from d0 in %lld us",
ktime_to_us(ktime_get() - start)); ktime_to_us(ktime_get() - start));
return err;
return 0;
} }
static int btintel_pcie_suspend(struct device *dev) 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 */ /* Refer: 6.4.11.7 -> Platform power management */
btintel_pcie_wr_sleep_cntrl(data, BTINTEL_PCIE_STATE_D0); err = btintel_pcie_set_dxstate(data, BTINTEL_PCIE_STATE_D0);
err = wait_event_timeout(data->gp0_wait_q, data->gp0_received,
msecs_to_jiffies(BTINTEL_DEFAULT_INTR_TIMEOUT_MS));
if (err == 0) { if (err == 0) {
bt_dev_err(data->hdev, bt_dev_dbg(data->hdev,
"Timeout (%u ms) on alive interrupt for D0 entry", "device entered into d0 state from d3 in %lld us",
BTINTEL_DEFAULT_INTR_TIMEOUT_MS); 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
*/
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;
} }
bt_dev_dbg(data->hdev, /* Trigger function level reset if the controller is in error
"device entered into d0 state from d3 in %lld us", * state during resume() to bring back the controller to
ktime_to_us(ktime_get() - start)); * operational mode
return 0; */
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 err;
} }
static const struct dev_pm_ops btintel_pcie_pm_ops = { static const struct dev_pm_ops btintel_pcie_pm_ops = {

View File

@ -158,6 +158,8 @@ enum msix_mbox_int_causes {
/* Default interrupt timeout in msec */ /* Default interrupt timeout in msec */
#define BTINTEL_DEFAULT_INTR_TIMEOUT_MS 3000 #define BTINTEL_DEFAULT_INTR_TIMEOUT_MS 3000
#define BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES 3
/* The number of descriptors in TX queues */ /* The number of descriptors in TX queues */
#define BTINTEL_PCIE_TX_DESCS_COUNT 32 #define BTINTEL_PCIE_TX_DESCS_COUNT 32