#define UREG_DOORBELL_TO_ISR6 0xA05C04
#define UREG_DOORBELL_TO_ISR6_NMI_BIT BIT(0)
+#define UREG_DOORBELL_TO_ISR6_SUSPEND BIT(18)
+#define UREG_DOORBELL_TO_ISR6_RESUME BIT(19)
#define FSEQ_ERROR_CODE 0xA340C8
#define FSEQ_TOP_INIT_VERSION 0xA34038
#define FSEQ_ALIVE_TOKEN 0xA340F0
#define FSEQ_CNVI_ID 0xA3408C
#define FSEQ_CNVR_ID 0xA34090
+
+#define IWL_D3_SLEEP_STATUS_SUSPEND 0xD3
+#define IWL_D3_SLEEP_STATUS_RESUME 0xD0
#endif /* __iwl_prph_h__ */
void (*fw_alive)(struct iwl_trans *trans, u32 scd_addr);
void (*stop_device)(struct iwl_trans *trans);
- void (*d3_suspend)(struct iwl_trans *trans, bool test, bool reset);
+ int (*d3_suspend)(struct iwl_trans *trans, bool test, bool reset);
int (*d3_resume)(struct iwl_trans *trans, enum iwl_d3_status *status,
bool test, bool reset);
trans->state = IWL_TRANS_NO_FW;
}
-static inline void iwl_trans_d3_suspend(struct iwl_trans *trans, bool test,
- bool reset)
+static inline int iwl_trans_d3_suspend(struct iwl_trans *trans, bool test,
+ bool reset)
{
might_sleep();
- if (trans->ops->d3_suspend)
- trans->ops->d3_suspend(trans, test, reset);
+ if (!trans->ops->d3_suspend)
+ return 0;
+
+ return trans->ops->d3_suspend(trans, test, reset);
}
static inline int iwl_trans_d3_resume(struct iwl_trans *trans,
clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status);
- iwl_trans_d3_suspend(mvm->trans, test, !unified_image);
+ ret = iwl_trans_d3_suspend(mvm->trans, test, !unified_image);
out:
if (ret < 0) {
iwl_mvm_free_nd(mvm);
if (IS_ERR_OR_NULL(vif))
goto err;
- ret = iwl_trans_d3_resume(mvm->trans, &d3_status, test, !unified_image);
- if (ret)
- goto err;
-
- if (d3_status != IWL_D3_STATUS_ALIVE) {
- IWL_INFO(mvm, "Device was reset during suspend\n");
- goto err;
- }
-
iwl_fw_dbg_read_d3_debug_data(&mvm->fwrt);
if (iwl_mvm_check_rt_status(mvm, vif)) {
goto err;
}
+ ret = iwl_trans_d3_resume(mvm->trans, &d3_status, test, !unified_image);
+ if (ret)
+ goto err;
+
+ if (d3_status != IWL_D3_STATUS_ALIVE) {
+ IWL_INFO(mvm, "Device was reset during suspend\n");
+ goto err;
+ }
+
if (d0i3_first) {
ret = iwl_mvm_send_cmd_pdu(mvm, D0I3_END_CMD, 0, 0, NULL);
if (ret < 0) {
void __iomem *hw_base;
bool ucode_write_complete;
+ bool sx_complete;
wait_queue_head_t ucode_write_waitq;
wait_queue_head_t wait_command_queue;
+ wait_queue_head_t sx_waitq;
u8 page_offs, dev_cmd_offs;
void iwl_pcie_gen2_txq_unmap(struct iwl_trans *trans, int txq_id);
void iwl_pcie_gen2_tx_free(struct iwl_trans *trans);
void iwl_pcie_gen2_tx_stop(struct iwl_trans *trans);
+void iwl_pcie_d3_complete_suspend(struct iwl_trans *trans,
+ bool test, bool reset);
#endif /* __iwl_trans_int_pcie_h__ */
iwl_pcie_irq_handle_error(trans);
}
} else if (inta_hw & MSIX_HW_INT_CAUSES_REG_WAKEUP) {
- /* uCode wakes up after power-down sleep */
- IWL_DEBUG_ISR(trans, "Wakeup interrupt\n");
- iwl_pcie_rxq_check_wrptr(trans);
- iwl_pcie_txq_check_wrptrs(trans);
+ u32 sleep_notif =
+ le32_to_cpu(trans_pcie->prph_info->sleep_notif);
+ if (sleep_notif == IWL_D3_SLEEP_STATUS_SUSPEND ||
+ sleep_notif == IWL_D3_SLEEP_STATUS_RESUME) {
+ IWL_DEBUG_ISR(trans,
+ "Sx interrupt: sleep notification = 0x%x\n",
+ sleep_notif);
+ trans_pcie->sx_complete = true;
+ wake_up(&trans_pcie->sx_waitq);
+ } else {
+ /* uCode wakes up after power-down sleep */
+ IWL_DEBUG_ISR(trans, "Wakeup interrupt\n");
+ iwl_pcie_rxq_check_wrptr(trans);
+ iwl_pcie_txq_check_wrptrs(trans);
- isr_stats->wakeup++;
+ isr_stats->wakeup++;
+ }
}
if (inta_hw & MSIX_HW_INT_CAUSES_REG_IML) {
}
}
-static void iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test,
- bool reset)
+void iwl_pcie_d3_complete_suspend(struct iwl_trans *trans,
+ bool test, bool reset)
{
- if (!reset) {
- /* Enable persistence mode to avoid reset */
- iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG,
- CSR_HW_IF_CONFIG_REG_PERSIST_MODE);
- }
-
iwl_disable_interrupts(trans);
/*
iwl_pcie_set_pwr(trans, true);
}
+static int iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test,
+ bool reset)
+{
+ int ret;
+ struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
+
+ /*
+ * Family IWL_DEVICE_FAMILY_AX210 and above persist mode is set by FW.
+ */
+ if (!reset && trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) {
+ /* Enable persistence mode to avoid reset */
+ iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG,
+ CSR_HW_IF_CONFIG_REG_PERSIST_MODE);
+ }
+
+ if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) {
+ iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6,
+ UREG_DOORBELL_TO_ISR6_SUSPEND);
+
+ ret = wait_event_timeout(trans_pcie->sx_waitq,
+ trans_pcie->sx_complete, 2 * HZ);
+ /*
+ * Invalidate it toward resume.
+ */
+ trans_pcie->sx_complete = false;
+
+ if (!ret) {
+ IWL_ERR(trans, "Timeout entering D3\n");
+ return -ETIMEDOUT;
+ }
+ }
+ iwl_pcie_d3_complete_suspend(trans, test, reset);
+
+ return 0;
+}
+
static int iwl_trans_pcie_d3_resume(struct iwl_trans *trans,
enum iwl_d3_status *status,
bool test, bool reset)
if (test) {
iwl_enable_interrupts(trans);
*status = IWL_D3_STATUS_ALIVE;
- return 0;
+ goto out;
}
iwl_set_bit(trans, CSR_GP_CNTRL,
else
*status = IWL_D3_STATUS_ALIVE;
+out:
+ if (*status == IWL_D3_STATUS_ALIVE &&
+ trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) {
+ trans_pcie->sx_complete = false;
+ iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6,
+ UREG_DOORBELL_TO_ISR6_RESUME);
+
+ ret = wait_event_timeout(trans_pcie->sx_waitq,
+ trans_pcie->sx_complete, 2 * HZ);
+ /*
+ * Invalidate it toward next suspend.
+ */
+ trans_pcie->sx_complete = false;
+
+ if (!ret) {
+ IWL_ERR(trans, "Timeout exiting D3\n");
+ return -ETIMEDOUT;
+ }
+ }
return 0;
}
/* Initialize the wait queue for commands */
init_waitqueue_head(&trans_pcie->wait_command_queue);
+ init_waitqueue_head(&trans_pcie->sx_waitq);
+
if (trans_pcie->msix_enabled) {
ret = iwl_pcie_init_msix_handler(pdev, trans_pcie);
if (ret)