mirror of https://github.com/torvalds/linux.git
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:
parent
1fb0d830da
commit
88c6216a52
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue