RTW_DBG_RFK             = 0x00000080,
        RTW_DBG_REGD            = 0x00000100,
        RTW_DBG_DEBUGFS         = 0x00000200,
+       RTW_DBG_PS              = 0x00000400,
 
        RTW_DBG_ALL             = 0xffffffff
 };
 
        int (*setup)(struct rtw_dev *rtwdev);
        int (*start)(struct rtw_dev *rtwdev);
        void (*stop)(struct rtw_dev *rtwdev);
+       void (*deep_ps)(struct rtw_dev *rtwdev, bool enter);
 
        int (*write_data_rsvd_page)(struct rtw_dev *rtwdev, u8 *buf, u32 size);
        int (*write_data_h2c)(struct rtw_dev *rtwdev, u8 *buf, u32 size);
        rtwdev->hci.ops->stop(rtwdev);
 }
 
+static inline void rtw_hci_deep_ps(struct rtw_dev *rtwdev, bool enter)
+{
+       rtwdev->hci.ops->deep_ps(rtwdev, enter);
+}
+
 static inline int
 rtw_hci_write_data_rsvd_page(struct rtw_dev *rtwdev, u8 *buf, u32 size)
 {
 
 
        mutex_lock(&rtwdev->mutex);
 
+       rtw_leave_lps_deep(rtwdev);
+
        if (changed & IEEE80211_CONF_CHANGE_IDLE) {
                if (hw->conf.flags & IEEE80211_CONF_IDLE) {
                        rtw_enter_ips(rtwdev);
 
        mutex_lock(&rtwdev->mutex);
 
+       rtw_leave_lps_deep(rtwdev);
+
        switch (vif->type) {
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_MESH_POINT:
 
        mutex_lock(&rtwdev->mutex);
 
+       rtw_leave_lps_deep(rtwdev);
+
        eth_zero_addr(rtwvif->mac_addr);
        config |= PORT_SET_MAC_ADDR;
        rtwvif->net_type = RTW_NET_NO_LINK;
 
        mutex_lock(&rtwdev->mutex);
 
+       rtw_leave_lps_deep(rtwdev);
+
        if (changed_flags & FIF_ALLMULTI) {
                if (*new_flags & FIF_ALLMULTI)
                        rtwdev->hal.rcr |= BIT_AM | BIT_AB;
 
        mutex_lock(&rtwdev->mutex);
 
+       rtw_leave_lps_deep(rtwdev);
+
        if (changed & BSS_CHANGED_ASSOC) {
                struct rtw_chip_info *chip = rtwdev->chip;
                enum rtw_net_type net_type;
                        rtw_send_rsvd_page_h2c(rtwdev);
                        rtw_coex_media_status_notify(rtwdev, conf->assoc);
                } else {
+                       rtw_leave_lps(rtwdev);
                        net_type = RTW_NET_NO_LINK;
                        rtwvif->aid = 0;
                        rtw_reset_rsvd_page(rtwdev);
 
        mutex_lock(&rtwdev->mutex);
 
+       rtw_leave_lps_deep(rtwdev);
+
        if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) {
                hw_key_idx = rtw_sec_get_free_cam(sec);
        } else {
        struct rtw_dev *rtwdev = hw->priv;
 
        mutex_lock(&rtwdev->mutex);
+       rtw_leave_lps_deep(rtwdev);
        rtw_coex_connect_notify(rtwdev, COEX_ASSOCIATE_START);
        mutex_unlock(&rtwdev->mutex);
 }
 
        switch (rtw_hci_type(rtwdev)) {
        case RTW_HCI_TYPE_PCIE:
                rtwdev->hci.rpwm_addr = 0x03d9;
+               rtwdev->hci.cpwm_addr = 0x03da;
                break;
        default:
                rtw_err(rtwdev, "unsupported hci type\n");
 
        enum rtw_hci_type type;
 
        u32 rpwm_addr;
+       u32 cpwm_addr;
 
        u8 bulkout_num;
 };
        RTW_FLAG_SCANNING,
        RTW_FLAG_INACTIVE_PS,
        RTW_FLAG_LEISURE_PS,
+       RTW_FLAG_LEISURE_PS_DEEP,
        RTW_FLAG_DIG_DISABLE,
        RTW_FLAG_BUSY_TRAFFIC,
 
 
 #include "tx.h"
 #include "rx.h"
 #include "fw.h"
+#include "ps.h"
 #include "debug.h"
 
 static bool rtw_disable_msi;
        spin_unlock_irqrestore(&rtwpci->irq_lock, flags);
 }
 
+static void rtw_pci_deep_ps_enter(struct rtw_dev *rtwdev)
+{
+       struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv;
+       struct rtw_pci_tx_ring *tx_ring;
+       bool tx_empty = true;
+       u8 queue;
+
+       lockdep_assert_held(&rtwpci->irq_lock);
+
+       /* Deep PS state is not allowed to TX-DMA */
+       for (queue = 0; queue < RTK_MAX_TX_QUEUE_NUM; queue++) {
+               /* BCN queue is rsvd page, does not have DMA interrupt
+                * H2C queue is managed by firmware
+                */
+               if (queue == RTW_TX_QUEUE_BCN ||
+                   queue == RTW_TX_QUEUE_H2C)
+                       continue;
+
+               tx_ring = &rtwpci->tx_rings[queue];
+
+               /* check if there is any skb DMAing */
+               if (skb_queue_len(&tx_ring->queue)) {
+                       tx_empty = false;
+                       break;
+               }
+       }
+
+       if (!tx_empty) {
+               rtw_dbg(rtwdev, RTW_DBG_PS,
+                       "TX path not empty, cannot enter deep power save state\n");
+               return;
+       }
+
+       set_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags);
+       rtw_power_mode_change(rtwdev, true);
+}
+
+static void rtw_pci_deep_ps_leave(struct rtw_dev *rtwdev)
+{
+       struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv;
+
+       lockdep_assert_held(&rtwpci->irq_lock);
+
+       if (test_and_clear_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags))
+               rtw_power_mode_change(rtwdev, false);
+}
+
+static void rtw_pci_deep_ps(struct rtw_dev *rtwdev, bool enter)
+{
+       struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv;
+       unsigned long flags;
+
+       spin_lock_irqsave(&rtwpci->irq_lock, flags);
+
+       if (enter && !test_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags))
+               rtw_pci_deep_ps_enter(rtwdev);
+
+       if (!enter && test_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags))
+               rtw_pci_deep_ps_leave(rtwdev);
+
+       spin_unlock_irqrestore(&rtwpci->irq_lock, flags);
+}
+
 static u8 ac_to_hwq[] = {
        [IEEE80211_AC_VO] = RTW_TX_QUEUE_VO,
        [IEEE80211_AC_VI] = RTW_TX_QUEUE_VI,
        u8 *pkt_desc;
        struct rtw_pci_tx_buffer_desc *buf_desc;
        u32 bd_idx;
+       unsigned long flags;
 
        ring = &rtwpci->tx_rings[queue];
 
        tx_data = rtw_pci_get_tx_data(skb);
        tx_data->dma = dma;
        tx_data->sn = pkt_info->sn;
+
+       spin_lock_irqsave(&rtwpci->irq_lock, flags);
+
+       rtw_pci_deep_ps_leave(rtwdev);
        skb_queue_tail(&ring->queue, skb);
 
        /* kick off tx queue */
                reg_bcn_work |= BIT_PCI_BCNQ_FLAG;
                rtw_write8(rtwdev, RTK_PCI_TXBD_BCN_WORK, reg_bcn_work);
        }
+       spin_unlock_irqrestore(&rtwpci->irq_lock, flags);
 
        return 0;
 }
        .setup = rtw_pci_setup,
        .start = rtw_pci_start,
        .stop = rtw_pci_stop,
+       .deep_ps = rtw_pci_deep_ps,
 
        .read8 = rtw_pci_read8,
        .read16 = rtw_pci_read16,
 
  */
 
 #include "main.h"
+#include "reg.h"
 #include "fw.h"
 #include "ps.h"
 #include "mac.h"
        return 0;
 }
 
+void rtw_power_mode_change(struct rtw_dev *rtwdev, bool enter)
+{
+       u8 request, confirm, polling;
+       u8 polling_cnt;
+       u8 retry_cnt = 0;
+
+retry:
+       request = rtw_read8(rtwdev, rtwdev->hci.rpwm_addr);
+       confirm = rtw_read8(rtwdev, rtwdev->hci.cpwm_addr);
+
+       /* toggle to request power mode, others remain 0 */
+       request ^= request | BIT_RPWM_TOGGLE;
+       if (!enter)
+               request |= POWER_MODE_ACK;
+       else
+               request |= POWER_MODE_LCLK;
+
+       rtw_write8(rtwdev, rtwdev->hci.rpwm_addr, request);
+
+       /* check confirm power mode has left power save state */
+       if (!enter) {
+               for (polling_cnt = 0; polling_cnt < 3; polling_cnt++) {
+                       polling = rtw_read8(rtwdev, rtwdev->hci.cpwm_addr);
+                       if ((polling ^ confirm) & BIT_RPWM_TOGGLE)
+                               return;
+                       mdelay(20);
+               }
+
+               /* in case of fw/hw missed the request, retry 3 times */
+               if (retry_cnt < 3) {
+                       rtw_warn(rtwdev, "failed to leave deep PS, retry=%d\n",
+                                retry_cnt);
+                       retry_cnt++;
+                       goto retry;
+               }
+
+               /* Hit here means that driver failed to change hardware
+                * power mode to active state after retry 3 times.
+                * If the power state is locked at Deep sleep, most of
+                * the hardware circuits is not working, even register
+                * read/write. It should be treated as fatal error and
+                * requires an entire analysis about the firmware/hardware
+                */
+               WARN_ON("Hardware power state locked\n");
+       }
+}
+EXPORT_SYMBOL(rtw_power_mode_change);
+
+static void __rtw_leave_lps_deep(struct rtw_dev *rtwdev)
+{
+       rtw_hci_deep_ps(rtwdev, false);
+}
+
 static void rtw_leave_lps_core(struct rtw_dev *rtwdev)
 {
        struct rtw_lps_conf *conf = &rtwdev->lps_conf;
        rtw_coex_lps_notify(rtwdev, COEX_LPS_DISABLE);
 }
 
+static void __rtw_enter_lps_deep(struct rtw_dev *rtwdev)
+{
+       if (!test_bit(RTW_FLAG_LEISURE_PS, rtwdev->flags)) {
+               rtw_dbg(rtwdev, RTW_DBG_PS,
+                       "Should enter LPS before entering deep PS\n");
+               return;
+       }
+
+       rtw_hci_deep_ps(rtwdev, true);
+}
+
 static void rtw_enter_lps_core(struct rtw_dev *rtwdev)
 {
        struct rtw_lps_conf *conf = &rtwdev->lps_conf;
        set_bit(RTW_FLAG_LEISURE_PS, rtwdev->flags);
 }
 
-void rtw_enter_lps(struct rtw_dev *rtwdev, u8 port_id)
+static void __rtw_enter_lps(struct rtw_dev *rtwdev, u8 port_id)
 {
        struct rtw_lps_conf *conf = &rtwdev->lps_conf;
 
-       lockdep_assert_held(&rtwdev->mutex);
-
        if (test_bit(RTW_FLAG_LEISURE_PS, rtwdev->flags))
                return;
 
        rtw_enter_lps_core(rtwdev);
 }
 
-void rtw_leave_lps(struct rtw_dev *rtwdev)
+static void __rtw_leave_lps(struct rtw_dev *rtwdev)
 {
        struct rtw_lps_conf *conf = &rtwdev->lps_conf;
 
-       lockdep_assert_held(&rtwdev->mutex);
+       if (test_and_clear_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags)) {
+               rtw_dbg(rtwdev, RTW_DBG_PS,
+                       "Should leave deep PS before leaving LPS\n");
+               __rtw_leave_lps_deep(rtwdev);
+       }
 
        if (!test_bit(RTW_FLAG_LEISURE_PS, rtwdev->flags))
                return;
 
        rtw_leave_lps_core(rtwdev);
 }
+
+void rtw_enter_lps(struct rtw_dev *rtwdev, u8 port_id)
+{
+       lockdep_assert_held(&rtwdev->mutex);
+
+       __rtw_enter_lps(rtwdev, port_id);
+       __rtw_enter_lps_deep(rtwdev);
+}
+
+void rtw_leave_lps(struct rtw_dev *rtwdev)
+{
+       lockdep_assert_held(&rtwdev->mutex);
+
+       __rtw_leave_lps_deep(rtwdev);
+       __rtw_leave_lps(rtwdev);
+}
+
+void rtw_leave_lps_deep(struct rtw_dev *rtwdev)
+{
+       lockdep_assert_held(&rtwdev->mutex);
+
+       __rtw_leave_lps_deep(rtwdev);
+}
 
 
 #define RTW_LPS_THRESHOLD      2
 
+#define POWER_MODE_ACK         BIT(6)
+#define POWER_MODE_LCLK                BIT(0)
+
 int rtw_enter_ips(struct rtw_dev *rtwdev);
 int rtw_leave_ips(struct rtw_dev *rtwdev);
 
+void rtw_power_mode_change(struct rtw_dev *rtwdev, bool enter);
 void rtw_enter_lps(struct rtw_dev *rtwdev, u8 port_id);
 void rtw_leave_lps(struct rtw_dev *rtwdev);
+void rtw_leave_lps_deep(struct rtw_dev *rtwdev);
 
 #endif