return ret;
 }
+
+#define H2C_WOW_CAM_UPD_LEN 24
+int rtw89_fw_wow_cam_update(struct rtw89_dev *rtwdev,
+                           struct rtw89_wow_cam_info *cam_info)
+{
+       struct sk_buff *skb;
+       int ret;
+
+       skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, H2C_WOW_CAM_UPD_LEN);
+       if (!skb) {
+               rtw89_err(rtwdev, "failed to alloc skb for keep alive\n");
+               return -ENOMEM;
+       }
+
+       skb_put(skb, H2C_WOW_CAM_UPD_LEN);
+
+       RTW89_SET_WOW_CAM_UPD_R_W(skb->data, cam_info->r_w);
+       RTW89_SET_WOW_CAM_UPD_IDX(skb->data, cam_info->idx);
+       if (cam_info->valid) {
+               RTW89_SET_WOW_CAM_UPD_WKFM1(skb->data, cam_info->mask[0]);
+               RTW89_SET_WOW_CAM_UPD_WKFM2(skb->data, cam_info->mask[1]);
+               RTW89_SET_WOW_CAM_UPD_WKFM3(skb->data, cam_info->mask[2]);
+               RTW89_SET_WOW_CAM_UPD_WKFM4(skb->data, cam_info->mask[3]);
+               RTW89_SET_WOW_CAM_UPD_CRC(skb->data, cam_info->crc);
+               RTW89_SET_WOW_CAM_UPD_NEGATIVE_PATTERN_MATCH(skb->data,
+                                                            cam_info->negative_pattern_match);
+               RTW89_SET_WOW_CAM_UPD_SKIP_MAC_HDR(skb->data,
+                                                  cam_info->skip_mac_hdr);
+               RTW89_SET_WOW_CAM_UPD_UC(skb->data, cam_info->uc);
+               RTW89_SET_WOW_CAM_UPD_MC(skb->data, cam_info->mc);
+               RTW89_SET_WOW_CAM_UPD_BC(skb->data, cam_info->bc);
+       }
+       RTW89_SET_WOW_CAM_UPD_VALID(skb->data, cam_info->valid);
+
+       rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
+                             H2C_CAT_MAC,
+                             H2C_CL_MAC_WOW,
+                             H2C_FUNC_WOW_CAM_UPD, 0, 1,
+                             H2C_WOW_CAM_UPD_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 *)h2c, val, GENMASK(31, 24));
 }
 
+static inline void RTW89_SET_WOW_CAM_UPD_R_W(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, BIT(0));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_IDX(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c, val, GENMASK(7, 1));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_WKFM1(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 1, val, GENMASK(31, 0));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_WKFM2(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 2, val, GENMASK(31, 0));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_WKFM3(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 3, val, GENMASK(31, 0));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_WKFM4(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 4, val, GENMASK(31, 0));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_CRC(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 5, val, GENMASK(15, 0));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_NEGATIVE_PATTERN_MATCH(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 5, val, BIT(22));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_SKIP_MAC_HDR(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 5, val, BIT(23));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_UC(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 5, val, BIT(24));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_MC(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 5, val, BIT(25));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_BC(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 5, val, BIT(26));
+}
+
+static inline void RTW89_SET_WOW_CAM_UPD_VALID(void *h2c, u32 val)
+{
+       le32p_replace_bits((__le32 *)h2c + 5, val, BIT(31));
+}
+
 enum rtw89_btc_btf_h2c_class {
        BTFC_SET = 0x10,
        BTFC_GET = 0x11,
 int rtw89_fw_h2c_wow_wakeup_ctrl(struct rtw89_dev *rtwdev,
                                 struct rtw89_vif *rtwvif, bool enable);
 
+int rtw89_fw_wow_cam_update(struct rtw89_dev *rtwdev,
+                           struct rtw89_wow_cam_info *cam_info);
 static inline void rtw89_fw_h2c_init_ba_cam(struct rtw89_dev *rtwdev)
 {
        const struct rtw89_chip_info *chip = rtwdev->chip;
 
        }
 }
 
+static u16 __rtw89_cal_crc16(u8 data, u16 crc)
+{
+       u8 shift_in, data_bit;
+       u8 crc_bit4, crc_bit11, crc_bit15;
+       u16 crc_result;
+       int index;
+
+       for (index = 0; index < 8; index++) {
+               crc_bit15 = crc & BIT(15) ? 1 : 0;
+               data_bit = data & BIT(index) ? 1 : 0;
+               shift_in = crc_bit15 ^ data_bit;
+
+               crc_result = crc << 1;
+
+               if (shift_in == 0)
+                       crc_result &= ~BIT(0);
+               else
+                       crc_result |= BIT(0);
+
+               crc_bit11 = (crc & BIT(11) ? 1 : 0) ^ shift_in;
+
+               if (crc_bit11 == 0)
+                       crc_result &= ~BIT(12);
+               else
+                       crc_result |= BIT(12);
+
+               crc_bit4 = (crc & BIT(4) ? 1 : 0) ^ shift_in;
+
+               if (crc_bit4 == 0)
+                       crc_result &= ~BIT(5);
+               else
+                       crc_result |= BIT(5);
+
+               crc = crc_result;
+       }
+       return crc;
+}
+
+static u16 rtw89_calc_crc(u8 *pdata, int length)
+{
+       u16 crc = 0xffff;
+       int i;
+
+       for (i = 0; i < length; i++)
+               crc = __rtw89_cal_crc16(pdata[i], crc);
+
+       /* get 1' complement */
+       return ~crc;
+}
+
+static int rtw89_wow_pattern_get_type(struct rtw89_vif *rtwvif,
+                                     struct rtw89_wow_cam_info *rtw_pattern,
+                                     const u8 *pattern, u8 da_mask)
+{
+       u8 da[ETH_ALEN];
+
+       ether_addr_copy_mask(da, pattern, da_mask);
+
+       /* Each pattern is divided into different kinds by DA address
+        *  a. DA is broadcast address: set bc = 0;
+        *  b. DA is multicast address: set mc = 0
+        *  c. DA is unicast address same as dev's mac address: set uc = 0
+        *  d. DA is unmasked. Also called wildcard type: set uc = bc = mc = 0
+        *  e. Others is invalid type.
+        */
+
+       if (is_broadcast_ether_addr(da))
+               rtw_pattern->bc = true;
+       else if (is_multicast_ether_addr(da))
+               rtw_pattern->mc = true;
+       else if (ether_addr_equal(da, rtwvif->mac_addr) &&
+                da_mask == GENMASK(5, 0))
+               rtw_pattern->uc = true;
+       else if (!da_mask) /*da_mask == 0 mean wildcard*/
+               return 0;
+       else
+               return -EPERM;
+
+       return 0;
+}
+
+static int rtw89_wow_pattern_generate(struct rtw89_dev *rtwdev,
+                                     struct rtw89_vif *rtwvif,
+                                     const struct cfg80211_pkt_pattern *pkt_pattern,
+                                     struct rtw89_wow_cam_info *rtw_pattern)
+{
+       u8 mask_hw[RTW89_MAX_PATTERN_MASK_SIZE * 4] = {0};
+       u8 content[RTW89_MAX_PATTERN_SIZE] = {0};
+       const u8 *mask;
+       const u8 *pattern;
+       u8 mask_len;
+       u16 count;
+       u32 len;
+       int i, ret;
+
+       pattern = pkt_pattern->pattern;
+       len = pkt_pattern->pattern_len;
+       mask = pkt_pattern->mask;
+       mask_len = DIV_ROUND_UP(len, 8);
+       memset(rtw_pattern, 0, sizeof(*rtw_pattern));
+
+       ret = rtw89_wow_pattern_get_type(rtwvif, rtw_pattern, pattern,
+                                        mask[0] & GENMASK(5, 0));
+       if (ret)
+               return ret;
+
+       /* translate mask from os to mask for hw
+        * pattern from OS uses 'ethenet frame', like this:
+        * |    6   |    6   |   2  |     20    |  Variable  |  4  |
+        * |--------+--------+------+-----------+------------+-----|
+        * |    802.3 Mac Header    | IP Header | TCP Packet | FCS |
+        * |   DA   |   SA   | Type |
+        *
+        * BUT, packet catched by our HW is in '802.11 frame', begin from LLC
+        * |     24 or 30      |    6   |   2  |     20    |  Variable  |  4  |
+        * |-------------------+--------+------+-----------+------------+-----|
+        * | 802.11 MAC Header |       LLC     | IP Header | TCP Packet | FCS |
+        *                     | Others | Tpye |
+        *
+        * Therefore, we need translate mask_from_OS to mask_to_hw.
+        * We should left-shift mask by 6 bits, then set the new bit[0~5] = 0,
+        * because new mask[0~5] means 'SA', but our HW packet begins from LLC,
+        * bit[0~5] corresponds to first 6 Bytes in LLC, they just don't match.
+        */
+
+       /* Shift 6 bits */
+       for (i = 0; i < mask_len - 1; i++) {
+               mask_hw[i] = u8_get_bits(mask[i], GENMASK(7, 6)) |
+                            u8_get_bits(mask[i + 1], GENMASK(5, 0)) << 2;
+       }
+       mask_hw[i] = u8_get_bits(mask[i], GENMASK(7, 6));
+
+       /* Set bit 0-5 to zero */
+       mask_hw[0] &= ~GENMASK(5, 0);
+
+       memcpy(rtw_pattern->mask, mask_hw, sizeof(rtw_pattern->mask));
+
+       /* To get the wake up pattern from the mask.
+        * We do not count first 12 bits which means
+        * DA[6] and SA[6] in the pattern to match HW design.
+        */
+       count = 0;
+       for (i = 12; i < len; i++) {
+               if ((mask[i / 8] >> (i % 8)) & 0x01) {
+                       content[count] = pattern[i];
+                       count++;
+               }
+       }
+
+       rtw_pattern->crc = rtw89_calc_crc(content, count);
+
+       return 0;
+}
+
+static int rtw89_wow_parse_patterns(struct rtw89_dev *rtwdev,
+                                   struct rtw89_vif *rtwvif,
+                                   struct cfg80211_wowlan *wowlan)
+{
+       struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
+       struct rtw89_wow_cam_info *rtw_pattern = rtw_wow->patterns;
+       int i;
+       int ret;
+
+       if (!wowlan->n_patterns || !wowlan->patterns)
+               return 0;
+
+       for (i = 0; i < wowlan->n_patterns; i++) {
+               rtw_pattern = &rtw_wow->patterns[i];
+               ret = rtw89_wow_pattern_generate(rtwdev, rtwvif,
+                                                &wowlan->patterns[i],
+                                                rtw_pattern);
+               if (ret) {
+                       rtw89_err(rtwdev, "failed to generate pattern(%d)\n", i);
+                       rtw_wow->pattern_cnt = 0;
+                       return ret;
+               }
+
+               rtw_pattern->r_w = true;
+               rtw_pattern->idx = i;
+               rtw_pattern->negative_pattern_match = false;
+               rtw_pattern->skip_mac_hdr = true;
+               rtw_pattern->valid = true;
+       }
+       rtw_wow->pattern_cnt = wowlan->n_patterns;
+
+       return 0;
+}
+
+static void rtw89_wow_pattern_clear_cam(struct rtw89_dev *rtwdev)
+{
+       struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
+       struct rtw89_wow_cam_info *rtw_pattern = rtw_wow->patterns;
+       int i = 0;
+
+       for (i = 0; i < rtw_wow->pattern_cnt; i++) {
+               rtw_pattern = &rtw_wow->patterns[i];
+               rtw_pattern->valid = false;
+               rtw89_fw_wow_cam_update(rtwdev, rtw_pattern);
+       }
+}
+
+static void rtw89_wow_pattern_write(struct rtw89_dev *rtwdev)
+{
+       struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
+       struct rtw89_wow_cam_info *rtw_pattern = rtw_wow->patterns;
+       int i;
+
+       for (i = 0; i < rtw_wow->pattern_cnt; i++)
+               rtw89_fw_wow_cam_update(rtwdev, rtw_pattern + i);
+}
+
+static void rtw89_wow_pattern_clear(struct rtw89_dev *rtwdev)
+{
+       struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
+
+       rtw89_wow_pattern_clear_cam(rtwdev);
+
+       rtw_wow->pattern_cnt = 0;
+       memset(rtw_wow->patterns, 0, sizeof(rtw_wow->patterns));
+}
+
 static void rtw89_wow_clear_wakeups(struct rtw89_dev *rtwdev)
 {
        struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
        if (!rtw_wow->wow_vif)
                return -EPERM;
 
-       return 0;
+       rtwvif = (struct rtw89_vif *)rtw_wow->wow_vif->drv_priv;
+       return rtw89_wow_parse_patterns(rtwdev, rtwvif, wowlan);
 }
 
 static int rtw89_wow_cfg_wake(struct rtw89_dev *rtwdev, bool wow)
        struct rtw89_vif *rtwvif = (struct rtw89_vif *)rtw_wow->wow_vif->drv_priv;
        int ret;
 
+       rtw89_wow_pattern_write(rtwdev);
+
        ret = rtw89_fw_h2c_keep_alive(rtwdev, rtwvif, true);
        if (ret) {
                rtw89_err(rtwdev, "wow: failed to enable keep alive\n");
        struct rtw89_vif *rtwvif = (struct rtw89_vif *)rtw_wow->wow_vif->drv_priv;
        int ret;
 
+       rtw89_wow_pattern_clear(rtwdev);
+
        ret = rtw89_fw_h2c_keep_alive(rtwdev, rtwvif, false);
        if (ret) {
                rtw89_err(rtwdev, "wow: failed to disable keep alive\n");