RTW89_REGD_NUM,
 };
 
+enum rtw89_fw_pkt_ofld_type {
+       RTW89_PKT_OFLD_TYPE_PROBE_RSP = 0,
+       RTW89_PKT_OFLD_TYPE_PS_POLL = 1,
+       RTW89_PKT_OFLD_TYPE_NULL_DATA = 2,
+       RTW89_PKT_OFLD_TYPE_QOS_NULL = 3,
+       RTW89_PKT_OFLD_TYPE_CTS2SELF = 4,
+       RTW89_PKT_OFLD_TYPE_ARP_RSP = 5,
+       RTW89_PKT_OFLD_TYPE_NDP = 6,
+       RTW89_PKT_OFLD_TYPE_EAPOL_KEY = 7,
+       RTW89_PKT_OFLD_TYPE_SA_QUERY = 8,
+       RTW89_PKT_OFLD_TYPE_PROBE_REQ = 12,
+       RTW89_PKT_OFLD_TYPE_NUM,
+};
+
 struct rtw89_txpwr_byrate {
        s8 cck[RTW89_RATE_CCK_MAX];
        s8 ofdm[RTW89_RATE_OFDM_MAX];
        RTW89_SC_40_LOWER       = 10,
 };
 
+enum rtw89_wow_flags {
+       RTW89_WOW_FLAG_EN_MAGIC_PKT,
+       RTW89_WOW_FLAG_EN_REKEY_PKT,
+       RTW89_WOW_FLAG_EN_DISCONNECT,
+       RTW89_WOW_FLAG_NUM,
+};
+
 struct rtw89_chan {
        u8 channel;
        u8 primary_channel;
        s8 comp[RF_PATH_MAX][RTW89_SUBBAND_NR]; /* S(8, 0) */
 };
 
+struct rtw89_wow_param {
+       struct ieee80211_vif *wow_vif;
+       DECLARE_BITMAP(flags, RTW89_WOW_FLAG_NUM);
+       u8 pattern_cnt;
+       struct list_head pkt_list;
+};
+
 struct rtw89_dev {
        struct ieee80211_hw *hw;
        struct device *dev;
        enum rtw89_ps_mode ps_mode;
        bool lps_enabled;
 
+       struct rtw89_wow_param wow;
+
        /* napi structure */
        struct net_device netdev;
        struct napi_struct napi;
 
        return ret;
 }
 
+static int rtw89_fw_h2c_add_wow_fw_ofld(struct rtw89_dev *rtwdev,
+                                       struct rtw89_vif *rtwvif,
+                                       enum rtw89_fw_pkt_ofld_type type,
+                                       u8 *id)
+{
+       struct ieee80211_vif *vif = rtwvif_to_vif(rtwvif);
+       struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
+       struct rtw89_pktofld_info *info;
+       struct sk_buff *skb;
+       int ret;
+
+       info = kzalloc(sizeof(*info), GFP_KERNEL);
+       if (!info)
+               return -ENOMEM;
+
+       switch (type) {
+       case RTW89_PKT_OFLD_TYPE_PS_POLL:
+               skb = ieee80211_pspoll_get(rtwdev->hw, vif);
+               break;
+       case RTW89_PKT_OFLD_TYPE_PROBE_RSP:
+               skb = ieee80211_proberesp_get(rtwdev->hw, vif);
+               break;
+       case RTW89_PKT_OFLD_TYPE_NULL_DATA:
+               skb = ieee80211_nullfunc_get(rtwdev->hw, vif, -1, false);
+               break;
+       case RTW89_PKT_OFLD_TYPE_QOS_NULL:
+               skb = ieee80211_nullfunc_get(rtwdev->hw, vif, -1, true);
+               break;
+       default:
+               goto err;
+       }
+
+       if (!skb)
+               goto err;
+
+       list_add_tail(&info->list, &rtw_wow->pkt_list);
+       ret = rtw89_fw_h2c_add_pkt_offload(rtwdev, &info->id, skb);
+       kfree_skb(skb);
+
+       if (ret)
+               return ret;
+
+       *id = info->id;
+       return 0;
+
+err:
+       kfree(info);
+       return -ENOMEM;
+}
+
 #define H2C_GENERAL_PKT_LEN 6
 #define H2C_GENERAL_PKT_ID_UND 0xff
 int rtw89_fw_h2c_general_pkt(struct rtw89_dev *rtwdev, u8 macid)
        dev_kfree_skb_any(skb);
        return ret;
 }
+
+#define H2C_KEEP_ALIVE_LEN 4
+int rtw89_fw_h2c_keep_alive(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif,
+                           bool enable)
+{
+       struct sk_buff *skb;
+       u8 pkt_id = 0;
+       int ret;
+
+       if (enable) {
+               ret = rtw89_fw_h2c_add_wow_fw_ofld(rtwdev, rtwvif,
+                                                  RTW89_PKT_OFLD_TYPE_NULL_DATA, &pkt_id);
+               if (ret)
+                       return -EPERM;
+       }
+
+       skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, H2C_KEEP_ALIVE_LEN);
+       if (!skb) {
+               rtw89_err(rtwdev, "failed to alloc skb for keep alive\n");
+               return -ENOMEM;
+       }
+
+       skb_put(skb, H2C_KEEP_ALIVE_LEN);
+
+       RTW89_SET_KEEP_ALIVE_ENABLE(skb->data, enable);
+       RTW89_SET_KEEP_ALIVE_PKT_NULL_ID(skb->data, pkt_id);
+       RTW89_SET_KEEP_ALIVE_PERIOD(skb->data, 5);
+       RTW89_SET_KEEP_ALIVE_MACID(skb->data, rtwvif->mac_id);
+
+       rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
+                             H2C_CAT_MAC,
+                             H2C_CL_MAC_WOW,
+                             H2C_FUNC_KEEP_ALIVE, 0, 1,
+                             H2C_KEEP_ALIVE_LEN);
+
+       ret = rtw89_h2c_tx(rtwdev, skb, false);
+       if (ret) {
+               rtw89_err(rtwdev, "failed to send h2c\n");
+               goto fail;
+       }
+
+       return 0;
+
+fail:
+       dev_kfree_skb_any(skb);
+
+       return ret;
+}
+
+#define H2C_DISCONNECT_DETECT_LEN 8
+int rtw89_fw_h2c_disconnect_detect(struct rtw89_dev *rtwdev,
+                                  struct rtw89_vif *rtwvif, bool enable)
+{
+       struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
+       struct sk_buff *skb;
+       u8 macid = rtwvif->mac_id;
+       int ret;
+
+       skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, H2C_DISCONNECT_DETECT_LEN);
+       if (!skb) {
+               rtw89_err(rtwdev, "failed to alloc skb for keep alive\n");
+               return -ENOMEM;
+       }
+
+       skb_put(skb, H2C_DISCONNECT_DETECT_LEN);
+
+       if (test_bit(RTW89_WOW_FLAG_EN_DISCONNECT, rtw_wow->flags)) {
+               RTW89_SET_DISCONNECT_DETECT_ENABLE(skb->data, enable);
+               RTW89_SET_DISCONNECT_DETECT_DISCONNECT(skb->data, !enable);
+               RTW89_SET_DISCONNECT_DETECT_MAC_ID(skb->data, macid);
+               RTW89_SET_DISCONNECT_DETECT_CHECK_PERIOD(skb->data, 100);
+               RTW89_SET_DISCONNECT_DETECT_TRY_PKT_COUNT(skb->data, 5);
+       }
+
+       rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
+                             H2C_CAT_MAC,
+                             H2C_CL_MAC_WOW,
+                             H2C_FUNC_DISCONNECT_DETECT, 0, 1,
+                             H2C_DISCONNECT_DETECT_LEN);
+
+       ret = rtw89_h2c_tx(rtwdev, skb, false);
+       if (ret) {
+               rtw89_err(rtwdev, "failed to send h2c\n");
+               goto fail;
+       }
+
+       return 0;
+
+fail:
+       dev_kfree_skb_any(skb);
+
+       return ret;
+}
+
+#define H2C_WOW_GLOBAL_LEN 8
+int rtw89_fw_h2c_wow_global(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif,
+                           bool enable)
+{
+       struct sk_buff *skb;
+       u8 macid = rtwvif->mac_id;
+       int ret;
+
+       skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, H2C_WOW_GLOBAL_LEN);
+       if (!skb) {
+               rtw89_err(rtwdev, "failed to alloc skb for keep alive\n");
+               return -ENOMEM;
+       }
+
+       skb_put(skb, H2C_WOW_GLOBAL_LEN);
+
+       RTW89_SET_WOW_GLOBAL_ENABLE(skb->data, enable);
+       RTW89_SET_WOW_GLOBAL_MAC_ID(skb->data, macid);
+
+       rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
+                             H2C_CAT_MAC,
+                             H2C_CL_MAC_WOW,
+                             H2C_FUNC_WOW_GLOBAL, 0, 1,
+                             H2C_WOW_GLOBAL_LEN);
+
+       ret = rtw89_h2c_tx(rtwdev, skb, false);
+       if (ret) {
+               rtw89_err(rtwdev, "failed to send h2c\n");
+               goto fail;
+       }
+
+       return 0;
+
+fail:
+       dev_kfree_skb_any(skb);
+
+       return ret;
+}
+
+#define H2C_WAKEUP_CTRL_LEN 4
+int rtw89_fw_h2c_wow_wakeup_ctrl(struct rtw89_dev *rtwdev,
+                                struct rtw89_vif *rtwvif,
+                                bool enable)
+{
+       struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
+       struct sk_buff *skb;
+       u8 macid = rtwvif->mac_id;
+       int ret;
+
+       skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, H2C_WAKEUP_CTRL_LEN);
+       if (!skb) {
+               rtw89_err(rtwdev, "failed to alloc skb for keep alive\n");
+               return -ENOMEM;
+       }
+
+       skb_put(skb, H2C_WAKEUP_CTRL_LEN);
+
+       if (rtw_wow->pattern_cnt)
+               RTW89_SET_WOW_WAKEUP_CTRL_PATTERN_MATCH_ENABLE(skb->data, enable);
+       if (test_bit(RTW89_WOW_FLAG_EN_MAGIC_PKT, rtw_wow->flags))
+               RTW89_SET_WOW_WAKEUP_CTRL_MAGIC_ENABLE(skb->data, enable);
+       if (test_bit(RTW89_WOW_FLAG_EN_DISCONNECT, rtw_wow->flags))
+               RTW89_SET_WOW_WAKEUP_CTRL_DEAUTH_ENABLE(skb->data, enable);
+
+       RTW89_SET_WOW_WAKEUP_CTRL_MAC_ID(skb->data, macid);
+
+       rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
+                             H2C_CAT_MAC,
+                             H2C_CL_MAC_WOW,
+                             H2C_FUNC_WAKEUP_CTRL, 0, 1,
+                             H2C_WAKEUP_CTRL_LEN);
+
+       ret = rtw89_h2c_tx(rtwdev, skb, false);
+       if (ret) {
+               rtw89_err(rtwdev, "failed to send h2c\n");
+               goto fail;
+       }
+
+       return 0;
+
+fail:
+       dev_kfree_skb_any(skb);
+
+       return ret;
+}
 
        le32p_replace_bits((__le32 *)cmd + 5, val, GENMASK(31, 0));
 }
 
+static inline void RTW89_SET_KEEP_ALIVE_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(1, 0));
+}
+
+static inline void RTW89_SET_KEEP_ALIVE_PKT_NULL_ID(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(15, 8));
+}
+
+static inline void RTW89_SET_KEEP_ALIVE_PERIOD(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(24, 16));
+}
+
+static inline void RTW89_SET_KEEP_ALIVE_MACID(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(31, 24));
+}
+
+static inline void RTW89_SET_DISCONNECT_DETECT_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(0));
+}
+
+static inline void RTW89_SET_DISCONNECT_DETECT_TRYOK_BCNFAIL_COUNT_EN(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(1));
+}
+
+static inline void RTW89_SET_DISCONNECT_DETECT_DISCONNECT(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(2));
+}
+
+static inline void RTW89_SET_DISCONNECT_DETECT_MAC_ID(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(15, 8));
+}
+
+static inline void RTW89_SET_DISCONNECT_DETECT_CHECK_PERIOD(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(23, 16));
+}
+
+static inline void RTW89_SET_DISCONNECT_DETECT_TRY_PKT_COUNT(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(31, 24));
+}
+
+static inline void RTW89_SET_DISCONNECT_DETECT_TRYOK_BCNFAIL_COUNT_LIMIT(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)(h2c) + 1, val, GENMASK(7, 0));
+}
+
+static inline void RTW89_SET_WOW_GLOBAL_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(0));
+}
+
+static inline void RTW89_SET_WOW_GLOBAL_DROP_ALL_PKT(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(1));
+}
+
+static inline void RTW89_SET_WOW_GLOBAL_RX_PARSE_AFTER_WAKE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(2));
+}
+
+static inline void RTW89_SET_WOW_GLOBAL_WAKE_BAR_PULLED(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(3));
+}
+
+static inline void RTW89_SET_WOW_GLOBAL_MAC_ID(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(15, 8));
+}
+
+static inline void RTW89_SET_WOW_GLOBAL_PAIRWISE_SEC_ALGO(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(23, 16));
+}
+
+static inline void RTW89_SET_WOW_GLOBAL_GROUP_SEC_ALGO(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(31, 24));
+}
+
+static inline void RTW89_SET_WOW_GLOBAL_REMOTECTRL_INFO_CONTENT(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)(h2c) + 1, val, GENMASK(31, 0));
+}
+
+static inline void RTW89_SET_WOW_WAKEUP_CTRL_PATTERN_MATCH_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(0));
+}
+
+static inline void RTW89_SET_WOW_WAKEUP_CTRL_MAGIC_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(1));
+}
+
+static inline void RTW89_SET_WOW_WAKEUP_CTRL_HW_UNICAST_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(2));
+}
+
+static inline void RTW89_SET_WOW_WAKEUP_CTRL_FW_UNICAST_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(3));
+}
+
+static inline void RTW89_SET_WOW_WAKEUP_CTRL_DEAUTH_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(4));
+}
+
+static inline void RTW89_SET_WOW_WAKEUP_CTRL_REKEYP_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(5));
+}
+
+static inline void RTW89_SET_WOW_WAKEUP_CTRL_EAP_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(6));
+}
+
+static inline void RTW89_SET_WOW_WAKEUP_CTRL_ALL_DATA_ENABLE(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(7));
+}
+
+static inline void RTW89_SET_WOW_WAKEUP_CTRL_MAC_ID(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(31, 24));
+}
+
 enum rtw89_btc_btf_h2c_class {
        BTFC_SET = 0x10,
        BTFC_GET = 0x11,
 #define H2C_FUNC_LOG_CFG               0x0
 #define H2C_FUNC_MAC_GENERAL_PKT       0x1
 
+/* CLASS 1 - WOW */
+#define H2C_CL_MAC_WOW                 0x1
+#define H2C_FUNC_KEEP_ALIVE            0x0
+#define H2C_FUNC_DISCONNECT_DETECT     0x1
+#define H2C_FUNC_WOW_GLOBAL            0x2
+#define H2C_FUNC_WAKEUP_CTRL           0x8
+#define H2C_FUNC_WOW_CAM_UPD           0xC
+
 /* CLASS 2 - PS */
 #define H2C_CL_MAC_PS                  0x2
 #define H2C_FUNC_MAC_LPS_PARM          0x0
                         u8 act, u8 noa_id);
 int rtw89_fw_h2c_tsf32_toggle(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif,
                              bool en);
+int rtw89_fw_h2c_wow_global(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif,
+                           bool enable);
+int rtw89_fw_h2c_wow_wakeup_ctrl(struct rtw89_dev *rtwdev,
+                                struct rtw89_vif *rtwvif, bool enable);
+int rtw89_fw_h2c_keep_alive(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif,
+                           bool enable);
+int rtw89_fw_h2c_disconnect_detect(struct rtw89_dev *rtwdev,
+                                  struct rtw89_vif *rtwvif, bool enable);
+int rtw89_fw_h2c_wow_global(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif,
+                           bool enable);
+int rtw89_fw_h2c_wow_wakeup_ctrl(struct rtw89_dev *rtwdev,
+                                struct rtw89_vif *rtwvif, bool enable);
 
 static inline void rtw89_fw_h2c_init_ba_cam(struct rtw89_dev *rtwdev)
 {