mirror of
https://github.com/torvalds/linux.git
synced 2024-12-04 01:51:34 +00:00
iwlwifi: add support for suspend-resume flow for new device generation
The new device generation has a slightly different suspend resume flow Currently, the way the driver instruct the device to move to D3 is by sending D3_CONFIG_CMD. Instead of using the host command the indication is by writing to the doorbell interrupt. The FW will respond with interrupt to indicate transition completion. Signed-off-by: Haim Dreyfuss <haim.dreyfuss@intel.com> Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
This commit is contained in:
parent
973ef19e9d
commit
e5f3f215d0
@ -451,6 +451,8 @@ enum {
|
||||
|
||||
#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
|
||||
@ -460,4 +462,7 @@ enum {
|
||||
#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__ */
|
||||
|
@ -537,7 +537,7 @@ struct iwl_trans_ops {
|
||||
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);
|
||||
|
||||
@ -883,12 +883,14 @@ static inline void iwl_trans_stop_device(struct iwl_trans *trans)
|
||||
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,
|
||||
|
@ -1068,7 +1068,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,
|
||||
|
||||
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);
|
||||
@ -1930,15 +1930,6 @@ static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)
|
||||
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)) {
|
||||
@ -1950,6 +1941,15 @@ static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)
|
||||
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) {
|
||||
|
@ -558,8 +558,10 @@ struct iwl_trans_pcie {
|
||||
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;
|
||||
|
||||
@ -1117,4 +1119,6 @@ void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans);
|
||||
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__ */
|
||||
|
@ -2196,12 +2196,23 @@ irqreturn_t iwl_pcie_irq_msix_handler(int irq, void *dev_id)
|
||||
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) {
|
||||
|
@ -1478,15 +1478,9 @@ void iwl_trans_pcie_rf_kill(struct iwl_trans *trans, bool state)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
@ -1517,6 +1511,42 @@ static void iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test,
|
||||
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)
|
||||
@ -1528,7 +1558,7 @@ static int iwl_trans_pcie_d3_resume(struct iwl_trans *trans,
|
||||
if (test) {
|
||||
iwl_enable_interrupts(trans);
|
||||
*status = IWL_D3_STATUS_ALIVE;
|
||||
return 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
iwl_set_bit(trans, CSR_GP_CNTRL,
|
||||
@ -1575,6 +1605,25 @@ static int iwl_trans_pcie_d3_resume(struct iwl_trans *trans,
|
||||
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;
|
||||
}
|
||||
|
||||
@ -3514,6 +3563,8 @@ struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev,
|
||||
/* 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)
|
||||
|
Loading…
Reference in New Issue
Block a user