iwlwifi: add support for suspend-resume flow for new device generation
authorHaim Dreyfuss <haim.dreyfuss@intel.com>
Wed, 3 Apr 2019 11:37:54 +0000 (14:37 +0300)
committerLuca Coelho <luciano.coelho@intel.com>
Fri, 6 Sep 2019 12:52:05 +0000 (15:52 +0300)
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>
drivers/net/wireless/intel/iwlwifi/iwl-prph.h
drivers/net/wireless/intel/iwlwifi/iwl-trans.h
drivers/net/wireless/intel/iwlwifi/mvm/d3.c
drivers/net/wireless/intel/iwlwifi/pcie/internal.h
drivers/net/wireless/intel/iwlwifi/pcie/rx.c
drivers/net/wireless/intel/iwlwifi/pcie/trans.c

index 8d930bfe0727579788985d8622e7a9ab3a6a5daf..f47e0f97acf89d3c5d9bc483b7e2b75eefc4f8ec 100644 (file)
@@ -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__ */
index 4152ae972aa7e763c71e226f13e8d9a9672ab4f2..b6c79f45d1ec882539476e65cda5dac3637dfc22 100644 (file)
@@ -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,
index cd7172d7f72ee299cf9a3d6a9ebb4f2ed4815141..66d610a2f3d657c0716af43257274053d64d25de 100644 (file)
@@ -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) {
index f07559b3633abc5a0145f848d653e50c37a10610..1047d48beaa5d60cec40a282e4364845f91f2fb4 100644 (file)
@@ -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__ */
index fce8c500ec028170cdd937b07e9e71e68cc8f355..19dd075f2f63640fba7c2754685574d328f3d31b 100644 (file)
@@ -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) {
index ae4f84016c3c45dcede1e6fea7656288a401ff8e..5ab87a8dc9070d4d137f4833f097f9d64d9d1f61 100644 (file)
@@ -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)