ar->num_stations = 0;
 }
 
+static int ath10k_mac_tdls_peer_update(struct ath10k *ar, u32 vdev_id,
+                                      struct ieee80211_sta *sta,
+                                      enum wmi_tdls_peer_state state)
+{
+       int ret;
+       struct wmi_tdls_peer_update_cmd_arg arg = {};
+       struct wmi_tdls_peer_capab_arg cap = {};
+       struct wmi_channel_arg chan_arg = {};
+
+       lockdep_assert_held(&ar->conf_mutex);
+
+       arg.vdev_id = vdev_id;
+       arg.peer_state = state;
+       ether_addr_copy(arg.addr, sta->addr);
+
+       cap.peer_max_sp = sta->max_sp;
+       cap.peer_uapsd_queues = sta->uapsd_queues;
+
+       if (state == WMI_TDLS_PEER_STATE_CONNECTED &&
+           !sta->tdls_initiator)
+               cap.is_peer_responder = 1;
+
+       ret = ath10k_wmi_tdls_peer_update(ar, &arg, &cap, &chan_arg);
+       if (ret) {
+               ath10k_warn(ar, "failed to update tdls peer %pM on vdev %i: %i\n",
+                           arg.addr, vdev_id, ret);
+               return ret;
+       }
+
+       return 0;
+}
+
 /************************/
 /* Interface management */
 /************************/
 
 static enum ath10k_hw_txrx_mode
 ath10k_tx_h_get_txmode(struct ath10k *ar, struct ieee80211_vif *vif,
-                      struct sk_buff *skb)
+                      struct ieee80211_sta *sta, struct sk_buff *skb)
 {
        const struct ieee80211_hdr *hdr = (void *)skb->data;
        __le16 fc = hdr->frame_control;
            !test_bit(ATH10K_FW_FEATURE_HAS_WMI_MGMT_TX, ar->fw_features))
                return ATH10K_HW_TXRX_MGMT;
 
+       /* Workaround:
+        *
+        * Some wmi-tlv firmwares for qca6174 have broken Tx key selection for
+        * NativeWifi txmode - it selects AP key instead of peer key. It seems
+        * to work with Ethernet txmode so use it.
+        */
+       if (ieee80211_is_data_present(fc) && sta && sta->tdls)
+               return ATH10K_HW_TXRX_ETHERNET;
+
        return ATH10K_HW_TXRX_NATIVE_WIFI;
 }
 
        struct ath10k *ar = hw->priv;
        struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
        struct ieee80211_vif *vif = info->control.vif;
+       struct ieee80211_sta *sta = control->sta;
        struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
        __le16 fc = hdr->frame_control;
 
        ATH10K_SKB_CB(skb)->htt.freq = 0;
        ATH10K_SKB_CB(skb)->htt.tid = ath10k_tx_h_get_tid(hdr);
        ATH10K_SKB_CB(skb)->vdev_id = ath10k_tx_h_get_vdev_id(ar, vif);
-       ATH10K_SKB_CB(skb)->txmode = ath10k_tx_h_get_txmode(ar, vif, skb);
+       ATH10K_SKB_CB(skb)->txmode = ath10k_tx_h_get_txmode(ar, vif, sta, skb);
        ATH10K_SKB_CB(skb)->is_protected = ieee80211_has_protected(fc);
 
        switch (ATH10K_SKB_CB(skb)->txmode) {
        ar->num_stations--;
 }
 
+struct ath10k_mac_tdls_iter_data {
+       u32 num_tdls_stations;
+       struct ieee80211_vif *curr_vif;
+};
+
+static void ath10k_mac_tdls_vif_stations_count_iter(void *data,
+                                                   struct ieee80211_sta *sta)
+{
+       struct ath10k_mac_tdls_iter_data *iter_data = data;
+       struct ath10k_sta *arsta = (struct ath10k_sta *)sta->drv_priv;
+       struct ieee80211_vif *sta_vif = arsta->arvif->vif;
+
+       if (sta->tdls && sta_vif == iter_data->curr_vif)
+               iter_data->num_tdls_stations++;
+}
+
+static int ath10k_mac_tdls_vif_stations_count(struct ieee80211_hw *hw,
+                                             struct ieee80211_vif *vif)
+{
+       struct ath10k_mac_tdls_iter_data data = {};
+
+       data.curr_vif = vif;
+
+       ieee80211_iterate_stations_atomic(hw,
+                                         ath10k_mac_tdls_vif_stations_count_iter,
+                                         &data);
+       return data.num_tdls_stations;
+}
+
+static void ath10k_mac_tdls_vifs_count_iter(void *data, u8 *mac,
+                                           struct ieee80211_vif *vif)
+{
+       struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+       int *num_tdls_vifs = data;
+
+       if (vif->type != NL80211_IFTYPE_STATION)
+               return;
+
+       if (ath10k_mac_tdls_vif_stations_count(arvif->ar->hw, vif) > 0)
+               (*num_tdls_vifs)++;
+}
+
+static int ath10k_mac_tdls_vifs_count(struct ieee80211_hw *hw)
+{
+       int num_tdls_vifs = 0;
+
+       ieee80211_iterate_active_interfaces_atomic(hw,
+                                                  IEEE80211_IFACE_ITER_NORMAL,
+                                                  ath10k_mac_tdls_vifs_count_iter,
+                                                  &num_tdls_vifs);
+       return num_tdls_vifs;
+}
+
 static int ath10k_sta_state(struct ieee80211_hw *hw,
                            struct ieee80211_vif *vif,
                            struct ieee80211_sta *sta,
                /*
                 * New station addition.
                 */
+               enum wmi_peer_type peer_type = WMI_PEER_TYPE_DEFAULT;
+               u32 num_tdls_stations;
+               u32 num_tdls_vifs;
+
                ath10k_dbg(ar, ATH10K_DBG_MAC,
                           "mac vdev %d peer create %pM (new sta) sta %d / %d peer %d / %d\n",
                           arvif->vdev_id, sta->addr,
                        goto exit;
                }
 
+               if (sta->tdls)
+                       peer_type = WMI_PEER_TYPE_TDLS;
+
                ret = ath10k_peer_create(ar, arvif->vdev_id, sta->addr,
-                                        WMI_PEER_TYPE_DEFAULT);
+                                        peer_type);
                if (ret) {
                        ath10k_warn(ar, "failed to add peer %pM for vdev %d when adding a new sta: %i\n",
                                    sta->addr, arvif->vdev_id, ret);
                        goto exit;
                }
 
-               if (vif->type == NL80211_IFTYPE_STATION) {
+               if (vif->type == NL80211_IFTYPE_STATION &&
+                   !sta->tdls) {
                        WARN_ON(arvif->is_started);
 
                        ret = ath10k_vdev_start(arvif);
 
                        arvif->is_started = true;
                }
+
+               if (!sta->tdls)
+                       goto exit;
+
+               num_tdls_stations = ath10k_mac_tdls_vif_stations_count(hw, vif);
+               num_tdls_vifs = ath10k_mac_tdls_vifs_count(hw);
+
+               if (num_tdls_vifs >= ar->max_num_tdls_vdevs &&
+                   num_tdls_stations == 0) {
+                       ath10k_warn(ar, "vdev %i exceeded maximum number of tdls vdevs %i\n",
+                                   arvif->vdev_id, ar->max_num_tdls_vdevs);
+                       ath10k_peer_delete(ar, arvif->vdev_id, sta->addr);
+                       ath10k_mac_dec_num_stations(arvif, sta);
+                       ret = -ENOBUFS;
+                       goto exit;
+               }
+
+               if (num_tdls_stations == 0) {
+                       /* This is the first tdls peer in current vif */
+                       enum wmi_tdls_state state = WMI_TDLS_ENABLE_ACTIVE;
+
+                       ret = ath10k_wmi_update_fw_tdls_state(ar, arvif->vdev_id,
+                                                             state);
+                       if (ret) {
+                               ath10k_warn(ar, "failed to update fw tdls state on vdev %i: %i\n",
+                                           arvif->vdev_id, ret);
+                               ath10k_peer_delete(ar, arvif->vdev_id,
+                                                  sta->addr);
+                               ath10k_mac_dec_num_stations(arvif, sta);
+                               goto exit;
+                       }
+               }
+
+               ret = ath10k_mac_tdls_peer_update(ar, arvif->vdev_id, sta,
+                                                 WMI_TDLS_PEER_STATE_PEERING);
+               if (ret) {
+                       ath10k_warn(ar,
+                                   "failed to update tdls peer %pM for vdev %d when adding a new sta: %i\n",
+                                   sta->addr, arvif->vdev_id, ret);
+                       ath10k_peer_delete(ar, arvif->vdev_id, sta->addr);
+                       ath10k_mac_dec_num_stations(arvif, sta);
+
+                       if (num_tdls_stations != 0)
+                               goto exit;
+                       ath10k_wmi_update_fw_tdls_state(ar, arvif->vdev_id,
+                                                       WMI_TDLS_DISABLE);
+               }
        } else if ((old_state == IEEE80211_STA_NONE &&
                    new_state == IEEE80211_STA_NOTEXIST)) {
                /*
                           "mac vdev %d peer delete %pM (sta gone)\n",
                           arvif->vdev_id, sta->addr);
 
-               if (vif->type == NL80211_IFTYPE_STATION) {
+               if (vif->type == NL80211_IFTYPE_STATION &&
+                   !sta->tdls) {
                        WARN_ON(!arvif->is_started);
 
                        ret = ath10k_vdev_stop(arvif);
                                    sta->addr, arvif->vdev_id, ret);
 
                ath10k_mac_dec_num_stations(arvif, sta);
+
+               if (!sta->tdls)
+                       goto exit;
+
+               if (ath10k_mac_tdls_vif_stations_count(hw, vif))
+                       goto exit;
+
+               /* This was the last tdls peer in current vif */
+               ret = ath10k_wmi_update_fw_tdls_state(ar, arvif->vdev_id,
+                                                     WMI_TDLS_DISABLE);
+               if (ret) {
+                       ath10k_warn(ar, "failed to update fw tdls state on vdev %i: %i\n",
+                                   arvif->vdev_id, ret);
+               }
        } else if (old_state == IEEE80211_STA_AUTH &&
                   new_state == IEEE80211_STA_ASSOC &&
                   (vif->type == NL80211_IFTYPE_AP ||
                        ath10k_warn(ar, "failed to associate station %pM for vdev %i: %i\n",
                                    sta->addr, arvif->vdev_id, ret);
        } else if (old_state == IEEE80211_STA_ASSOC &&
-                  new_state == IEEE80211_STA_AUTH &&
-                  (vif->type == NL80211_IFTYPE_AP ||
-                   vif->type == NL80211_IFTYPE_ADHOC)) {
+                  new_state == IEEE80211_STA_AUTHORIZED &&
+                  sta->tdls) {
+               /*
+                * Tdls station authorized.
+                */
+               ath10k_dbg(ar, ATH10K_DBG_MAC, "mac tdls sta %pM authorized\n",
+                          sta->addr);
+
+               ret = ath10k_station_assoc(ar, vif, sta, false);
+               if (ret) {
+                       ath10k_warn(ar, "failed to associate tdls station %pM for vdev %i: %i\n",
+                                   sta->addr, arvif->vdev_id, ret);
+                       goto exit;
+               }
+
+               ret = ath10k_mac_tdls_peer_update(ar, arvif->vdev_id, sta,
+                                                 WMI_TDLS_PEER_STATE_CONNECTED);
+               if (ret)
+                       ath10k_warn(ar, "failed to update tdls peer %pM for vdev %i: %i\n",
+                                   sta->addr, arvif->vdev_id, ret);
+       } else if (old_state == IEEE80211_STA_ASSOC &&
+                   new_state == IEEE80211_STA_AUTH &&
+                   (vif->type == NL80211_IFTYPE_AP ||
+                    vif->type == NL80211_IFTYPE_ADHOC)) {
                /*
                 * Disassociation.
                 */
                        NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P;
        }
 
+       if (test_bit(WMI_SERVICE_TDLS, ar->wmi.svc_map))
+               ar->hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
+
        ar->hw->wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
        ar->hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH;
        ar->hw->wiphy->max_remain_on_channel_duration = 5000;