ath11k_warn(ar->ab, "failed to set beacon tx rate %d\n", ret);
 }
 
+static int ath11k_mac_fils_discovery(struct ath11k_vif *arvif,
+                                    struct ieee80211_bss_conf *info)
+{
+       struct ath11k *ar = arvif->ar;
+       struct sk_buff *tmpl;
+       int ret;
+       u32 interval;
+       bool unsol_bcast_probe_resp_enabled = false;
+
+       if (info->fils_discovery.max_interval) {
+               interval = info->fils_discovery.max_interval;
+
+               tmpl = ieee80211_get_fils_discovery_tmpl(ar->hw, arvif->vif);
+               if (tmpl)
+                       ret = ath11k_wmi_fils_discovery_tmpl(ar, arvif->vdev_id,
+                                                            tmpl);
+       } else if (info->unsol_bcast_probe_resp_interval) {
+               unsol_bcast_probe_resp_enabled = 1;
+               interval = info->unsol_bcast_probe_resp_interval;
+
+               tmpl = ieee80211_get_unsol_bcast_probe_resp_tmpl(ar->hw,
+                                                                arvif->vif);
+               if (tmpl)
+                       ret = ath11k_wmi_probe_resp_tmpl(ar, arvif->vdev_id,
+                                                        tmpl);
+       } else { /* Disable */
+               return ath11k_wmi_fils_discovery(ar, arvif->vdev_id, 0, false);
+       }
+
+       if (!tmpl) {
+               ath11k_warn(ar->ab,
+                           "mac vdev %i failed to retrieve %s template\n",
+                           arvif->vdev_id, (unsol_bcast_probe_resp_enabled ?
+                           "unsolicited broadcast probe response" :
+                           "FILS discovery"));
+               return -EPERM;
+       }
+       kfree_skb(tmpl);
+
+       if (!ret)
+               ret = ath11k_wmi_fils_discovery(ar, arvif->vdev_id, interval,
+                                               unsol_bcast_probe_resp_enabled);
+
+       return ret;
+}
+
 static void ath11k_mac_op_bss_info_changed(struct ieee80211_hw *hw,
                                           struct ieee80211_vif *vif,
                                           struct ieee80211_bss_conf *info,
                }
        }
 
+       if (changed & BSS_CHANGED_FILS_DISCOVERY ||
+           changed & BSS_CHANGED_UNSOL_BCAST_PROBE_RESP)
+               ath11k_mac_fils_discovery(arvif, info);
+
        mutex_unlock(&ar->conf_mutex);
 }
 
        ar->hw->wiphy->num_iftype_ext_capab =
                ARRAY_SIZE(ath11k_iftypes_ext_capa);
 
+       if (ar->supports_6ghz) {
+               wiphy_ext_feature_set(ar->hw->wiphy,
+                                     NL80211_EXT_FEATURE_FILS_DISCOVERY);
+               wiphy_ext_feature_set(ar->hw->wiphy,
+                                     NL80211_EXT_FEATURE_UNSOL_BCAST_PROBE_RESP);
+       }
+
        ath11k_reg_init(ar);
 
        if (!test_bit(ATH11K_FLAG_RAW_MODE, &ab->dev_flags)) {
 
                = { .min_len = sizeof(struct wmi_stats_event) },
        [WMI_TAG_PDEV_CTL_FAILSAFE_CHECK_EVENT]
                = { .min_len = sizeof(struct wmi_pdev_ctl_failsafe_chk_event) },
+       [WMI_TAG_HOST_SWFDA_EVENT] = {
+               .min_len = sizeof(struct wmi_fils_discovery_event) },
+       [WMI_TAG_OFFLOAD_PRB_RSP_TX_STATUS_EVENT] = {
+               .min_len = sizeof(struct wmi_probe_resp_tx_status_event) },
 };
 
 #define PRIMAP(_hw_mode_) \
        return ret;
 }
 
+int ath11k_wmi_fils_discovery_tmpl(struct ath11k *ar, u32 vdev_id,
+                                  struct sk_buff *tmpl)
+{
+       struct wmi_tlv *tlv;
+       struct sk_buff *skb;
+       void *ptr;
+       int ret, len;
+       size_t aligned_len;
+       struct wmi_fils_discovery_tmpl_cmd *cmd;
+
+       aligned_len = roundup(tmpl->len, 4);
+       len = sizeof(*cmd) + TLV_HDR_SIZE + aligned_len;
+
+       ath11k_dbg(ar->ab, ATH11K_DBG_WMI,
+                  "WMI vdev %i set FILS discovery template\n", vdev_id);
+
+       skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
+       if (!skb)
+               return -ENOMEM;
+
+       cmd = (struct wmi_fils_discovery_tmpl_cmd *)skb->data;
+       cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG,
+                                    WMI_TAG_FILS_DISCOVERY_TMPL_CMD) |
+                         FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);
+       cmd->vdev_id = vdev_id;
+       cmd->buf_len = tmpl->len;
+       ptr = skb->data + sizeof(*cmd);
+
+       tlv = ptr;
+       tlv->header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ARRAY_BYTE) |
+                     FIELD_PREP(WMI_TLV_LEN, aligned_len);
+       memcpy(tlv->value, tmpl->data, tmpl->len);
+
+       ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_FILS_DISCOVERY_TMPL_CMDID);
+       if (ret) {
+               ath11k_warn(ar->ab,
+                           "WMI vdev %i failed to send FILS discovery template command\n",
+                           vdev_id);
+               dev_kfree_skb(skb);
+       }
+       return ret;
+}
+
+int ath11k_wmi_probe_resp_tmpl(struct ath11k *ar, u32 vdev_id,
+                              struct sk_buff *tmpl)
+{
+       struct wmi_probe_tmpl_cmd *cmd;
+       struct wmi_bcn_prb_info *probe_info;
+       struct wmi_tlv *tlv;
+       struct sk_buff *skb;
+       void *ptr;
+       int ret, len;
+       size_t aligned_len = roundup(tmpl->len, 4);
+
+       ath11k_dbg(ar->ab, ATH11K_DBG_WMI,
+                  "WMI vdev %i set probe response template\n", vdev_id);
+
+       len = sizeof(*cmd) + sizeof(*probe_info) + TLV_HDR_SIZE + aligned_len;
+
+       skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
+       if (!skb)
+               return -ENOMEM;
+
+       cmd = (struct wmi_probe_tmpl_cmd *)skb->data;
+       cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_PRB_TMPL_CMD) |
+                         FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);
+       cmd->vdev_id = vdev_id;
+       cmd->buf_len = tmpl->len;
+
+       ptr = skb->data + sizeof(*cmd);
+
+       probe_info = ptr;
+       len = sizeof(*probe_info);
+       probe_info->tlv_header = FIELD_PREP(WMI_TLV_TAG,
+                                           WMI_TAG_BCN_PRB_INFO) |
+                                FIELD_PREP(WMI_TLV_LEN, len - TLV_HDR_SIZE);
+       probe_info->caps = 0;
+       probe_info->erp = 0;
+
+       ptr += sizeof(*probe_info);
+
+       tlv = ptr;
+       tlv->header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ARRAY_BYTE) |
+                     FIELD_PREP(WMI_TLV_LEN, aligned_len);
+       memcpy(tlv->value, tmpl->data, tmpl->len);
+
+       ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_PRB_TMPL_CMDID);
+       if (ret) {
+               ath11k_warn(ar->ab,
+                           "WMI vdev %i failed to send probe response template command\n",
+                           vdev_id);
+               dev_kfree_skb(skb);
+       }
+       return ret;
+}
+
+int ath11k_wmi_fils_discovery(struct ath11k *ar, u32 vdev_id, u32 interval,
+                             bool unsol_bcast_probe_resp_enabled)
+{
+       struct sk_buff *skb;
+       int ret, len;
+       struct wmi_fils_discovery_cmd *cmd;
+
+       ath11k_dbg(ar->ab, ATH11K_DBG_WMI,
+                  "WMI vdev %i set %s interval to %u TU\n",
+                  vdev_id, unsol_bcast_probe_resp_enabled ?
+                  "unsolicited broadcast probe response" : "FILS discovery",
+                  interval);
+
+       len = sizeof(*cmd);
+       skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
+       if (!skb)
+               return -ENOMEM;
+
+       cmd = (struct wmi_fils_discovery_cmd *)skb->data;
+       cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ENABLE_FILS_CMD) |
+                         FIELD_PREP(WMI_TLV_LEN, len - TLV_HDR_SIZE);
+       cmd->vdev_id = vdev_id;
+       cmd->interval = interval;
+       cmd->config = unsol_bcast_probe_resp_enabled;
+
+       ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_ENABLE_FILS_CMDID);
+       if (ret) {
+               ath11k_warn(ar->ab,
+                           "WMI vdev %i failed to send FILS discovery enable/disable command\n",
+                           vdev_id);
+               dev_kfree_skb(skb);
+       }
+       return ret;
+}
+
 static void
 ath11k_fill_band_to_mac_param(struct ath11k_base  *soc,
                              struct wmi_host_pdev_band_to_mac *band_to_mac)
        ath11k_thermal_event_temperature(ar, ev.temp);
 }
 
+static void ath11k_fils_discovery_event(struct ath11k_base *ab,
+                                       struct sk_buff *skb)
+{
+       const void **tb;
+       const struct wmi_fils_discovery_event *ev;
+       int ret;
+
+       tb = ath11k_wmi_tlv_parse_alloc(ab, skb->data, skb->len, GFP_ATOMIC);
+       if (IS_ERR(tb)) {
+               ret = PTR_ERR(tb);
+               ath11k_warn(ab,
+                           "failed to parse FILS discovery event tlv %d\n",
+                           ret);
+               return;
+       }
+
+       ev = tb[WMI_TAG_HOST_SWFDA_EVENT];
+       if (!ev) {
+               ath11k_warn(ab, "failed to fetch FILS discovery event\n");
+               kfree(tb);
+               return;
+       }
+
+       ath11k_warn(ab,
+                   "FILS discovery frame expected from host for vdev_id: %u, transmission scheduled at %u, next TBTT: %u\n",
+                   ev->vdev_id, ev->fils_tt, ev->tbtt);
+
+       kfree(tb);
+}
+
+static void ath11k_probe_resp_tx_status_event(struct ath11k_base *ab,
+                                             struct sk_buff *skb)
+{
+       const void **tb;
+       const struct wmi_probe_resp_tx_status_event *ev;
+       int ret;
+
+       tb = ath11k_wmi_tlv_parse_alloc(ab, skb->data, skb->len, GFP_ATOMIC);
+       if (IS_ERR(tb)) {
+               ret = PTR_ERR(tb);
+               ath11k_warn(ab,
+                           "failed to parse probe response transmission status event tlv: %d\n",
+                           ret);
+               return;
+       }
+
+       ev = tb[WMI_TAG_OFFLOAD_PRB_RSP_TX_STATUS_EVENT];
+       if (!ev) {
+               ath11k_warn(ab,
+                           "failed to fetch probe response transmission status event");
+               kfree(tb);
+               return;
+       }
+
+       if (ev->tx_status)
+               ath11k_warn(ab,
+                           "Probe response transmission failed for vdev_id %u, status %u\n",
+                           ev->vdev_id, ev->tx_status);
+
+       kfree(tb);
+}
+
 static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb)
 {
        struct wmi_cmd_hdr *cmd_hdr;
        case WMI_PDEV_DMA_RING_BUF_RELEASE_EVENTID:
                ath11k_wmi_pdev_dma_ring_buf_release_event(ab, skb);
                break;
+       case WMI_HOST_FILS_DISCOVERY_EVENTID:
+               ath11k_fils_discovery_event(ab, skb);
+               break;
+       case WMI_OFFLOAD_PROB_RESP_TX_STATUS_EVENTID:
+               ath11k_probe_resp_tx_status_event(ab, skb);
+               break;
        /* add Unsupported events here */
        case WMI_TBTTOFFSET_EXT_UPDATE_EVENTID:
        case WMI_VDEV_DELETE_RESP_EVENTID:
 
        WMI_BCN_OFFLOAD_CTRL_CMDID,
        WMI_BSS_COLOR_CHANGE_ENABLE_CMDID,
        WMI_VDEV_BCN_OFFLOAD_QUIET_CONFIG_CMDID,
+       WMI_FILS_DISCOVERY_TMPL_CMDID,
        WMI_ADDBA_CLEAR_RESP_CMDID = WMI_TLV_CMD(WMI_GRP_BA_NEG),
        WMI_ADDBA_SEND_CMDID,
        WMI_ADDBA_STATUS_CMDID,
        WMI_ROAM_CONFIGURE_MAWC_CMDID,
        WMI_ROAM_SET_MBO_PARAM_CMDID,
        WMI_ROAM_PER_CONFIG_CMDID,
+       WMI_ROAM_BTM_CONFIG_CMDID,
+       WMI_ENABLE_FILS_CMDID,
        WMI_OFL_SCAN_ADD_AP_PROFILE = WMI_TLV_CMD(WMI_GRP_OFL_SCAN),
        WMI_OFL_SCAN_REMOVE_AP_PROFILE,
        WMI_OFL_SCAN_PERIOD,
        WMI_MGMT_TX_COMPLETION_EVENTID,
        WMI_MGMT_TX_BUNDLE_COMPLETION_EVENTID,
        WMI_TBTTOFFSET_EXT_UPDATE_EVENTID,
+       WMI_OFFCHAN_DATA_TX_COMPLETION_EVENTID,
+       WMI_HOST_FILS_DISCOVERY_EVENTID,
        WMI_TX_DELBA_COMPLETE_EVENTID = WMI_TLV_CMD(WMI_GRP_BA_NEG),
        WMI_TX_ADDBA_COMPLETE_EVENTID,
        WMI_BA_RSP_SSN_EVENTID,
        /* TODO add all the missing cmds */
        WMI_TAG_PDEV_PEER_PKTLOG_FILTER_CMD = 0x301,
        WMI_TAG_PDEV_PEER_PKTLOG_FILTER_INFO,
+       WMI_TAG_FILS_DISCOVERY_TMPL_CMD = 0x344,
        WMI_TAG_MAX
 };
 
        const u8 *macaddr;
 };
 
+struct wmi_fils_discovery_event {
+       u32 vdev_id;
+       u32 fils_tt;
+       u32 tbtt;
+} __packed;
+
+struct wmi_probe_resp_tx_status_event {
+       u32 vdev_id;
+       u32 tx_status;
+} __packed;
+
 /*
  * PDEV statistics
  */
        u32 ch_width;
 } __packed;
 
+enum wmi_fils_discovery_cmd_type {
+       WMI_FILS_DISCOVERY_CMD,
+       WMI_UNSOL_BCAST_PROBE_RESP,
+};
+
+struct wmi_fils_discovery_cmd {
+       u32 tlv_header;
+       u32 vdev_id;
+       u32 interval;
+       u32 config; /* enum wmi_fils_discovery_cmd_type */
+} __packed;
+
+struct wmi_fils_discovery_tmpl_cmd {
+       u32 tlv_header;
+       u32 vdev_id;
+       u32 buf_len;
+} __packed;
+
+struct wmi_probe_tmpl_cmd {
+       u32 tlv_header;
+       u32 vdev_id;
+       u32 buf_len;
+} __packed;
+
 struct target_resource_config {
        u32 num_vdevs;
        u32 num_peers;
                                    u32 trigger, u32 enable);
 int ath11k_wmi_vdev_spectral_conf(struct ath11k *ar,
                                  struct ath11k_wmi_vdev_spectral_conf_param *param);
+int ath11k_wmi_fils_discovery_tmpl(struct ath11k *ar, u32 vdev_id,
+                                  struct sk_buff *tmpl);
+int ath11k_wmi_fils_discovery(struct ath11k *ar, u32 vdev_id, u32 interval,
+                             bool unsol_bcast_probe_resp_enabled);
+int ath11k_wmi_probe_resp_tmpl(struct ath11k *ar, u32 vdev_id,
+                              struct sk_buff *tmpl);
 #endif