return mt7615_mcu_sched_scan_enable(mphy->priv, vif, false);
 }
 
+#ifdef CONFIG_PM
+static int mt7615_suspend(struct ieee80211_hw *hw,
+                         struct cfg80211_wowlan *wowlan)
+{
+       struct mt7615_dev *dev = mt7615_hw_dev(hw);
+       struct mt7615_phy *phy = mt7615_hw_phy(hw);
+       bool ext_phy = phy != &dev->phy;
+       int err = 0;
+
+       mutex_lock(&dev->mt76.mutex);
+
+       clear_bit(MT76_STATE_RUNNING, &phy->mt76->state);
+       cancel_delayed_work_sync(&phy->scan_work);
+       cancel_delayed_work_sync(&phy->mac_work);
+
+       mt76_set(dev, MT_WF_RFCR(ext_phy), MT_WF_RFCR_DROP_OTHER_BEACON);
+
+       set_bit(MT76_STATE_SUSPEND, &phy->mt76->state);
+       ieee80211_iterate_active_interfaces(hw,
+                                           IEEE80211_IFACE_ITER_RESUME_ALL,
+                                           mt7615_mcu_set_suspend_iter, phy);
+
+       if (!mt7615_dev_running(dev))
+               err = mt7615_mcu_set_hif_suspend(dev, true);
+
+       mutex_unlock(&dev->mt76.mutex);
+
+       return err;
+}
+
+static int mt7615_resume(struct ieee80211_hw *hw)
+{
+       struct mt7615_dev *dev = mt7615_hw_dev(hw);
+       struct mt7615_phy *phy = mt7615_hw_phy(hw);
+       bool running, ext_phy = phy != &dev->phy;
+
+       mutex_lock(&dev->mt76.mutex);
+
+       running = mt7615_dev_running(dev);
+       set_bit(MT76_STATE_RUNNING, &phy->mt76->state);
+
+       if (!running) {
+               int err;
+
+               err = mt7615_mcu_set_hif_suspend(dev, false);
+               if (err < 0) {
+                       mutex_unlock(&dev->mt76.mutex);
+                       return err;
+               }
+       }
+
+       clear_bit(MT76_STATE_SUSPEND, &phy->mt76->state);
+       ieee80211_iterate_active_interfaces(hw,
+                                           IEEE80211_IFACE_ITER_RESUME_ALL,
+                                           mt7615_mcu_set_suspend_iter, phy);
+
+       ieee80211_queue_delayed_work(hw, &phy->mac_work,
+                                    MT7615_WATCHDOG_TIME);
+       mt76_clear(dev, MT_WF_RFCR(ext_phy), MT_WF_RFCR_DROP_OTHER_BEACON);
+
+       mutex_unlock(&dev->mt76.mutex);
+
+       return 0;
+}
+
+static void mt7615_set_wakeup(struct ieee80211_hw *hw, bool enabled)
+{
+       struct mt7615_dev *dev = mt7615_hw_dev(hw);
+       struct mt76_dev *mdev = &dev->mt76;
+
+       device_set_wakeup_enable(mdev->dev, enabled);
+}
+#endif /* CONFIG_PM */
+
 const struct ieee80211_ops mt7615_ops = {
        .tx = mt7615_tx,
        .start = mt7615_start,
        .cancel_hw_scan = mt7615_cancel_hw_scan,
        .sched_scan_start = mt7615_start_sched_scan,
        .sched_scan_stop = mt7615_stop_sched_scan,
+#ifdef CONFIG_PM
+       .suspend = mt7615_suspend,
+       .resume = mt7615_resume,
+       .set_wakeup = mt7615_set_wakeup,
+#endif /* CONFIG_PM */
 };
 EXPORT_SYMBOL_GPL(mt7615_ops);
 
 
        case MCU_UNI_CMD_DEV_INFO_UPDATE:
        case MCU_UNI_CMD_BSS_INFO_UPDATE:
        case MCU_UNI_CMD_STA_REC_UPDATE:
-       case MCU_UNI_CMD_HIF_CTRL: {
+       case MCU_UNI_CMD_HIF_CTRL:
+       case MCU_UNI_CMD_SUSPEND: {
                struct mt7615_mcu_uni_event *event;
 
                skb_pull(skb, sizeof(*rxd));
        return 0;
 }
 
+static const struct wiphy_wowlan_support mt7615_wowlan_support = {
+       .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT,
+       .n_patterns = 1,
+       .pattern_min_len = 1,
+       .pattern_max_len = MT7615_WOW_PATTEN_MAX_LEN,
+};
+
 static int mt7615_load_n9(struct mt7615_dev *dev, const char *name)
 {
        const struct mt7615_fw_trailer *hdr;
                return -EIO;
        }
 
+#ifdef CONFIG_PM
+       if (mt7615_firmware_offload(dev))
+               dev->mt76.hw->wiphy->wowlan = &mt7615_wowlan_support;
+#endif /* CONFIG_PM */
+
        dev_dbg(dev->mt76.dev, "Firmware init done\n");
 
        return 0;
                                    &req, sizeof(req), false);
 }
 
-int mt7615_mcu_set_bss_pm(struct mt7615_dev *dev, struct ieee80211_vif *vif,
-                         bool enable)
-{
-       struct mt7615_vif *mvif = (struct mt7615_vif *)vif->drv_priv;
-       struct {
-               u8 bss_idx;
-               u8 dtim_period;
-               __le16 aid;
-               __le16 bcn_interval;
-               __le16 atim_window;
-               u8 uapsd;
-               u8 bmc_delivered_ac;
-               u8 bmc_triggered_ac;
-               u8 pad;
-       } req = {
-               .bss_idx = mvif->idx,
-               .aid = cpu_to_le16(vif->bss_conf.aid),
-               .dtim_period = vif->bss_conf.dtim_period,
-               .bcn_interval = cpu_to_le16(vif->bss_conf.beacon_int),
-       };
-       struct {
-               u8 bss_idx;
-               u8 pad[3];
-       } req_hdr = {
-               .bss_idx = mvif->idx,
-       };
-       int err;
-
-       if (vif->type != NL80211_IFTYPE_STATION ||
-           !mt7615_firmware_offload(dev))
-               return -ENOTSUPP;
-
-       err = __mt76_mcu_send_msg(&dev->mt76, MCU_CMD_SET_BSS_ABORT,
-                                 &req_hdr, sizeof(req_hdr), false);
-       if (err < 0 || !enable)
-               return err;
-
-       return __mt76_mcu_send_msg(&dev->mt76, MCU_CMD_SET_BSS_CONNECTED,
-                                  &req, sizeof(req), false);
-}
-
 int mt7615_mcu_set_channel_domain(struct mt7615_phy *phy)
 {
        struct mt76_phy *mphy = phy->mt76;
        return ret;
 }
 
+#ifdef CONFIG_PM
 int mt7615_mcu_set_hif_suspend(struct mt7615_dev *dev, bool suspend)
 {
        struct {
                                   &req, sizeof(req), true);
 }
 EXPORT_SYMBOL_GPL(mt7615_mcu_set_hif_suspend);
+
+static int
+mt7615_mcu_set_bss_pm(struct mt7615_dev *dev, struct ieee80211_vif *vif,
+                     bool enable)
+{
+       struct mt7615_vif *mvif = (struct mt7615_vif *)vif->drv_priv;
+       struct {
+               u8 bss_idx;
+               u8 dtim_period;
+               __le16 aid;
+               __le16 bcn_interval;
+               __le16 atim_window;
+               u8 uapsd;
+               u8 bmc_delivered_ac;
+               u8 bmc_triggered_ac;
+               u8 pad;
+       } req = {
+               .bss_idx = mvif->idx,
+               .aid = cpu_to_le16(vif->bss_conf.aid),
+               .dtim_period = vif->bss_conf.dtim_period,
+               .bcn_interval = cpu_to_le16(vif->bss_conf.beacon_int),
+       };
+       struct {
+               u8 bss_idx;
+               u8 pad[3];
+       } req_hdr = {
+               .bss_idx = mvif->idx,
+       };
+       int err;
+
+       if (vif->type != NL80211_IFTYPE_STATION ||
+           !mt7615_firmware_offload(dev))
+               return -ENOTSUPP;
+
+       err = __mt76_mcu_send_msg(&dev->mt76, MCU_CMD_SET_BSS_ABORT,
+                                 &req_hdr, sizeof(req_hdr), false);
+       if (err < 0 || !enable)
+               return err;
+
+       return __mt76_mcu_send_msg(&dev->mt76, MCU_CMD_SET_BSS_CONNECTED,
+                                  &req, sizeof(req), false);
+}
+
+static int
+mt7615_mcu_set_wow_ctrl(struct mt7615_dev *dev, struct ieee80211_vif *vif,
+                       bool suspend, struct cfg80211_wowlan *wowlan)
+{
+       struct mt7615_vif *mvif = (struct mt7615_vif *)vif->drv_priv;
+       struct {
+               struct {
+                       u8 bss_idx;
+                       u8 pad[3];
+               } __packed hdr;
+               struct mt7615_wow_ctrl_tlv wow_ctrl_tlv;
+       } req = {
+               .hdr = {
+                       .bss_idx = mvif->idx,
+               },
+               .wow_ctrl_tlv = {
+                       .tag = cpu_to_le16(UNI_SUSPEND_WOW_CTRL),
+                       .len = cpu_to_le16(sizeof(struct mt7615_wow_ctrl_tlv)),
+                       .cmd = suspend ? 1 : 2,
+               },
+       };
+
+       if (wowlan->magic_pkt)
+               req.wow_ctrl_tlv.trigger |= BIT(0);
+       if (wowlan->disconnect)
+               req.wow_ctrl_tlv.trigger |= BIT(2);
+
+       if (mt76_is_mmio(&dev->mt76))
+               req.wow_ctrl_tlv.wakeup_hif = 2;
+       else if (mt76_is_usb(&dev->mt76))
+               req.wow_ctrl_tlv.wakeup_hif = 1;
+
+       return __mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD_SUSPEND,
+                                  &req, sizeof(req), true);
+}
+
+static int
+mt7615_mcu_set_wow_pattern(struct mt7615_dev *dev,
+                          struct ieee80211_vif *vif,
+                          u8 index, bool enable,
+                          struct cfg80211_pkt_pattern *pattern)
+{
+       struct mt7615_vif *mvif = (struct mt7615_vif *)vif->drv_priv;
+       struct mt7615_wow_pattern_tlv *ptlv;
+       struct sk_buff *skb;
+       struct req_hdr {
+               u8 bss_idx;
+               u8 pad[3];
+       } __packed hdr = {
+               .bss_idx = mvif->idx,
+       };
+
+       skb = mt76_mcu_msg_alloc(&dev->mt76, NULL,
+                                sizeof(hdr) + sizeof(*ptlv));
+       if (!skb)
+               return -ENOMEM;
+
+       skb_put_data(skb, &hdr, sizeof(hdr));
+       ptlv = (struct mt7615_wow_pattern_tlv *)skb_put(skb, sizeof(*ptlv));
+       ptlv->tag = cpu_to_le16(UNI_SUSPEND_WOW_PATTERN);
+       ptlv->len = cpu_to_le16(sizeof(*ptlv));
+       ptlv->data_len = pattern->pattern_len;
+       ptlv->enable = enable;
+       ptlv->index = index;
+
+       memcpy(ptlv->pattern, pattern->pattern, pattern->pattern_len);
+       memcpy(ptlv->mask, pattern->mask, pattern->pattern_len / 8);
+
+       return __mt76_mcu_skb_send_msg(&dev->mt76, skb,
+                                      MCU_UNI_CMD_SUSPEND, true);
+}
+
+static int
+mt7615_mcu_set_suspend_mode(struct mt7615_dev *dev,
+                           struct ieee80211_vif *vif,
+                           bool enable, u8 mdtim, bool wow_suspend)
+{
+       struct mt7615_vif *mvif = (struct mt7615_vif *)vif->drv_priv;
+       struct {
+               struct {
+                       u8 bss_idx;
+                       u8 pad[3];
+               } __packed hdr;
+               struct mt7615_suspend_tlv suspend_tlv;
+       } req = {
+               .hdr = {
+                       .bss_idx = mvif->idx,
+               },
+               .suspend_tlv = {
+                       .tag = cpu_to_le16(UNI_SUSPEND_MODE_SETTING),
+                       .len = cpu_to_le16(sizeof(struct mt7615_suspend_tlv)),
+                       .enable = enable,
+                       .mdtim = mdtim,
+                       .wow_suspend = wow_suspend,
+               },
+       };
+
+       return __mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD_SUSPEND,
+                                  &req, sizeof(req), true);
+}
+
+void mt7615_mcu_set_suspend_iter(void *priv, u8 *mac,
+                                struct ieee80211_vif *vif)
+{
+       struct mt7615_phy *phy = priv;
+       bool suspend = test_bit(MT76_STATE_SUSPEND, &phy->mt76->state);
+       struct ieee80211_hw *hw = phy->mt76->hw;
+       struct cfg80211_wowlan *wowlan = hw->wiphy->wowlan_config;
+       int i;
+
+       mt7615_mcu_set_bss_pm(phy->dev, vif, suspend);
+
+       mt7615_mcu_set_suspend_mode(phy->dev, vif, suspend, 1, true);
+
+       for (i = 0; i < wowlan->n_patterns; i++)
+               mt7615_mcu_set_wow_pattern(phy->dev, vif, i, suspend,
+                                          &wowlan->patterns[i]);
+       mt7615_mcu_set_wow_ctrl(phy->dev, vif, suspend, wowlan);
+}
+#endif /* CONFIG_PM */
 
        MCU_UNI_CMD_DEV_INFO_UPDATE = MCU_UNI_PREFIX | 0x01,
        MCU_UNI_CMD_BSS_INFO_UPDATE = MCU_UNI_PREFIX | 0x02,
        MCU_UNI_CMD_STA_REC_UPDATE = MCU_UNI_PREFIX | 0x03,
+       MCU_UNI_CMD_SUSPEND = MCU_UNI_PREFIX | 0x05,
        MCU_UNI_CMD_HIF_CTRL = MCU_UNI_PREFIX | 0x07,
 };
 
        u8 pad;
 } __packed;
 
+struct mt7615_wow_ctrl_tlv {
+       __le16 tag;
+       __le16 len;
+       u8 cmd; /* 0x1: PM_WOWLAN_REQ_START
+                * 0x2: PM_WOWLAN_REQ_STOP
+                * 0x3: PM_WOWLAN_PARAM_CLEAR
+                */
+       u8 trigger; /* 0: NONE
+                    * BIT(0): NL80211_WOWLAN_TRIG_MAGIC_PKT
+                    * BIT(1): NL80211_WOWLAN_TRIG_ANY
+                    * BIT(2): NL80211_WOWLAN_TRIG_DISCONNECT
+                    * BIT(3): NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE
+                    * BIT(4): BEACON_LOST
+                    * BIT(5): NL80211_WOWLAN_TRIG_NET_DETECT
+                    */
+       u8 wakeup_hif; /* 0x0: HIF_SDIO
+                       * 0x1: HIF_USB
+                       * 0x2: HIF_PCIE
+                       * 0x3: HIF_GPIO
+                       */
+       u8 pad;
+       u8 rsv[4];
+} __packed;
+
+#define MT7615_WOW_MASK_MAX_LEN                16
+#define MT7615_WOW_PATTEN_MAX_LEN      128
+struct mt7615_wow_pattern_tlv {
+       __le16 tag;
+       __le16 len;
+       u8 index; /* pattern index */
+       u8 enable; /* 0: disable
+                   * 1: enable
+                   */
+       u8 data_len; /* pattern length */
+       u8 pad;
+       u8 mask[MT7615_WOW_MASK_MAX_LEN];
+       u8 pattern[MT7615_WOW_PATTEN_MAX_LEN];
+       u8 rsv[4];
+} __packed;
+
+struct mt7615_suspend_tlv {
+       __le16 tag;
+       __le16 len;
+       u8 enable; /* 0: suspend mode disabled
+                   * 1: suspend mode enabled
+                   */
+       u8 mdtim; /* LP parameter */
+       u8 wow_suspend; /* 0: update by origin policy
+                        * 1: update by wow dtim
+                        */
+       u8 pad[5];
+} __packed;
+
 /* offload mcu commands */
 enum {
        MCU_CMD_START_HW_SCAN = MCU_CE_PREFIX | 0x03,
        UNI_BSS_INFO_BCN_CONTENT = 7,
 };
 
+enum {
+       UNI_SUSPEND_MODE_SETTING,
+       UNI_SUSPEND_WOW_CTRL,
+       UNI_SUSPEND_WOW_GPIO_PARAM,
+       UNI_SUSPEND_WOW_WAKEUP_PORT,
+       UNI_SUSPEND_WOW_PATTERN,
+};
+
 enum {
        PATCH_SEM_RELEASE = 0x0,
        PATCH_SEM_GET     = 0x1