* @BSS_CHANGED_KEEP_ALIVE: keep alive options (idle period or protected
  *     keep alive) changed.
  * @BSS_CHANGED_MCAST_RATE: Multicast Rate setting changed for this interface
+ * @BSS_CHANGED_FTM_RESPONDER: fime timing reasurement request responder
+ *     functionality changed for this BSS (AP mode).
  *
  */
 enum ieee80211_bss_change {
        BSS_CHANGED_MU_GROUPS           = 1<<23,
        BSS_CHANGED_KEEP_ALIVE          = 1<<24,
        BSS_CHANGED_MCAST_RATE          = 1<<25,
+       BSS_CHANGED_FTM_RESPONDER       = 1<<26,
 
        /* when adding here, make sure to change ieee80211_reconfig */
 };
        u8 position[WLAN_USER_POSITION_LEN];
 };
 
+/**
+ * ieee80211_ftm_responder_params - FTM responder parameters
+ *
+ * @lci: LCI subelement content
+ * @civicloc: CIVIC location subelement content
+ * @lci_len: LCI data length
+ * @civicloc_len: Civic data length
+ */
+struct ieee80211_ftm_responder_params {
+       const u8 *lci;
+       const u8 *civicloc;
+       size_t lci_len;
+       size_t civicloc_len;
+};
+
 /**
  * struct ieee80211_bss_conf - holds the BSS's changing parameters
  *
  * @protected_keep_alive: if set, indicates that the station should send an RSN
  *     protected frame to the AP to reset the idle timer at the AP for the
  *     station.
+ * @ftm_responder: whether to enable or disable fine timing measurement FTM
+ *     responder functionality.
+ * @ftmr_params: configurable lci/civic parameter when enabling FTM responder.
  */
 struct ieee80211_bss_conf {
        const u8 *bssid;
        bool allow_p2p_go_ps;
        u16 max_idle_period;
        bool protected_keep_alive;
+       bool ftm_responder;
+       struct ieee80211_ftm_responder_params *ftmr_params;
 };
 
 /**
  *     aggregating two specific frames in the same A-MSDU. The relation
  *     between the skbs should be symmetric and transitive. Note that while
  *     skb is always a real frame, head may or may not be an A-MSDU.
+ * @get_ftm_responder_stats: Retrieve FTM responder statistics, if available.
+ *     Statistics should be cumulative, currently no way to reset is provided.
  */
 struct ieee80211_ops {
        void (*tx)(struct ieee80211_hw *hw,
        bool (*can_aggregate_in_amsdu)(struct ieee80211_hw *hw,
                                       struct sk_buff *head,
                                       struct sk_buff *skb);
+       int (*get_ftm_responder_stats)(struct ieee80211_hw *hw,
+                                      struct ieee80211_vif *vif,
+                                      struct cfg80211_ftm_responder_stats *ftm_stats);
 };
 
 /**
 
        return 0;
 }
 
+static int ieee80211_set_ftm_responder_params(
+                               struct ieee80211_sub_if_data *sdata,
+                               const u8 *lci, size_t lci_len,
+                               const u8 *civicloc, size_t civicloc_len)
+{
+       struct ieee80211_ftm_responder_params *new, *old;
+       struct ieee80211_bss_conf *bss_conf;
+       u8 *pos;
+       int len;
+
+       if ((!lci || !lci_len) && (!civicloc || !civicloc_len))
+               return 1;
+
+       bss_conf = &sdata->vif.bss_conf;
+       old = bss_conf->ftmr_params;
+       len = lci_len + civicloc_len;
+
+       new = kzalloc(sizeof(*new) + len, GFP_KERNEL);
+       if (!new)
+               return -ENOMEM;
+
+       pos = (u8 *)(new + 1);
+       if (lci_len) {
+               new->lci_len = lci_len;
+               new->lci = pos;
+               memcpy(pos, lci, lci_len);
+               pos += lci_len;
+       }
+
+       if (civicloc_len) {
+               new->civicloc_len = civicloc_len;
+               new->civicloc = pos;
+               memcpy(pos, civicloc, civicloc_len);
+               pos += civicloc_len;
+       }
+
+       bss_conf->ftmr_params = new;
+       kfree(old);
+
+       return 0;
+}
+
 static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
                                   struct cfg80211_beacon_data *params,
                                   const struct ieee80211_csa_settings *csa)
        if (err == 0)
                changed |= BSS_CHANGED_AP_PROBE_RESP;
 
+       if (params->ftm_responder != -1) {
+               sdata->vif.bss_conf.ftm_responder = params->ftm_responder;
+               err = ieee80211_set_ftm_responder_params(sdata,
+                                                        params->lci,
+                                                        params->lci_len,
+                                                        params->civicloc,
+                                                        params->civicloc_len);
+
+               if (err < 0)
+                       return err;
+
+               changed |= BSS_CHANGED_FTM_RESPONDER;
+       }
+
        rcu_assign_pointer(sdata->u.ap.beacon, new);
 
        if (old)
                kfree_rcu(old_probe_resp, rcu_head);
        sdata->u.ap.driver_smps_mode = IEEE80211_SMPS_OFF;
 
+       kfree(sdata->vif.bss_conf.ftmr_params);
+       sdata->vif.bss_conf.ftmr_params = NULL;
+
        __sta_info_flush(sdata, true);
        ieee80211_free_keys(sdata, true);
 
                memcpy(pos, beacon->probe_resp, beacon->probe_resp_len);
                pos += beacon->probe_resp_len;
        }
+       if (beacon->ftm_responder)
+               new_beacon->ftm_responder = beacon->ftm_responder;
+       if (beacon->lci) {
+               new_beacon->lci_len = beacon->lci_len;
+               new_beacon->lci = pos;
+               memcpy(pos, beacon->lci, beacon->lci_len);
+               pos += beacon->lci_len;
+       }
+       if (beacon->civicloc) {
+               new_beacon->civicloc_len = beacon->civicloc_len;
+               new_beacon->civicloc = pos;
+               memcpy(pos, beacon->civicloc, beacon->civicloc_len);
+               pos += beacon->civicloc_len;
+       }
 
        return new_beacon;
 }
        return ret;
 }
 
+static int
+ieee80211_get_ftm_responder_stats(struct wiphy *wiphy,
+                                 struct net_device *dev,
+                                 struct cfg80211_ftm_responder_stats *ftm_stats)
+{
+       struct ieee80211_local *local = wiphy_priv(wiphy);
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+       return drv_get_ftm_responder_stats(local, sdata, ftm_stats);
+}
+
 const struct cfg80211_ops mac80211_config_ops = {
        .add_virtual_intf = ieee80211_add_iface,
        .del_virtual_intf = ieee80211_del_iface,
        .set_multicast_to_unicast = ieee80211_set_multicast_to_unicast,
        .tx_control_port = ieee80211_tx_control_port,
        .get_txq_stats = ieee80211_get_txq_stats,
+       .get_ftm_responder_stats = ieee80211_get_ftm_responder_stats,
 };
 
        return local->ops->can_aggregate_in_amsdu(&local->hw, head, skb);
 }
 
+static inline int
+drv_get_ftm_responder_stats(struct ieee80211_local *local,
+                           struct ieee80211_sub_if_data *sdata,
+                           struct cfg80211_ftm_responder_stats *ftm_stats)
+{
+       u32 ret = -EOPNOTSUPP;
+
+       if (local->ops->get_ftm_responder_stats)
+               ret = local->ops->get_ftm_responder_stats(&local->hw,
+                                                        &sdata->vif,
+                                                        ftm_stats);
+       trace_drv_get_ftm_responder_stats(local, sdata, ftm_stats);
+
+       return ret;
+}
+
 static inline int drv_start_nan(struct ieee80211_local *local,
                                struct ieee80211_sub_if_data *sdata,
                                struct cfg80211_nan_conf *conf)
 
        )
 );
 
+TRACE_EVENT(drv_get_ftm_responder_stats,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                struct cfg80211_ftm_responder_stats *ftm_stats),
+
+       TP_ARGS(local, sdata, ftm_stats),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               VIF_ENTRY
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               VIF_ASSIGN;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT VIF_PR_FMT,
+               LOCAL_PR_ARG, VIF_PR_ARG
+       )
+);
+
 #endif /* !__MAC80211_DRIVER_TRACE || TRACE_HEADER_MULTI_READ */
 
 #undef TRACE_INCLUDE_PATH
 
                case NL80211_IFTYPE_AP:
                        changed |= BSS_CHANGED_SSID | BSS_CHANGED_P2P_PS;
 
+                       if (sdata->vif.bss_conf.ftm_responder == 1 &&
+                           wiphy_ext_feature_isset(sdata->local->hw.wiphy,
+                                       NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER))
+                               changed |= BSS_CHANGED_FTM_RESPONDER;
+
                        if (sdata->vif.type == NL80211_IFTYPE_AP) {
                                changed |= BSS_CHANGED_AP_PROBE_RESP;