diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h
index fe6b30356d3b..c624b96f8b84 100644
--- a/drivers/net/wireless/ath/ath10k/core.h
+++ b/drivers/net/wireless/ath/ath10k/core.h
@@ -1,6 +1,7 @@
 /*
  * Copyright (c) 2005-2011 Atheros Communications Inc.
  * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -354,6 +355,45 @@ struct ath10k_txq {
 	unsigned long num_push_allowed;
 };
 
+enum ath10k_pkt_rx_err {
+	ATH10K_PKT_RX_ERR_FCS,
+	ATH10K_PKT_RX_ERR_TKIP,
+	ATH10K_PKT_RX_ERR_CRYPT,
+	ATH10K_PKT_RX_ERR_PEER_IDX_INVAL,
+	ATH10K_PKT_RX_ERR_MAX,
+};
+
+enum ath10k_ampdu_subfrm_num {
+	ATH10K_AMPDU_SUBFRM_NUM_10,
+	ATH10K_AMPDU_SUBFRM_NUM_20,
+	ATH10K_AMPDU_SUBFRM_NUM_30,
+	ATH10K_AMPDU_SUBFRM_NUM_40,
+	ATH10K_AMPDU_SUBFRM_NUM_50,
+	ATH10K_AMPDU_SUBFRM_NUM_60,
+	ATH10K_AMPDU_SUBFRM_NUM_MORE,
+	ATH10K_AMPDU_SUBFRM_NUM_MAX,
+};
+
+enum ath10k_amsdu_subfrm_num {
+	ATH10K_AMSDU_SUBFRM_NUM_1,
+	ATH10K_AMSDU_SUBFRM_NUM_2,
+	ATH10K_AMSDU_SUBFRM_NUM_3,
+	ATH10K_AMSDU_SUBFRM_NUM_4,
+	ATH10K_AMSDU_SUBFRM_NUM_MORE,
+	ATH10K_AMSDU_SUBFRM_NUM_MAX,
+};
+
+struct ath10k_sta_tid_stats {
+	unsigned long int rx_pkt_from_fw;
+	unsigned long int rx_pkt_unchained;
+	unsigned long int rx_pkt_drop_chained;
+	unsigned long int rx_pkt_drop_filter;
+	unsigned long int rx_pkt_err[ATH10K_PKT_RX_ERR_MAX];
+	unsigned long int rx_pkt_queued_for_mac;
+	unsigned long int rx_pkt_ampdu[ATH10K_AMPDU_SUBFRM_NUM_MAX];
+	unsigned long int rx_pkt_amsdu[ATH10K_AMSDU_SUBFRM_NUM_MAX];
+};
+
 struct ath10k_sta {
 	struct ath10k_vif *arvif;
 
@@ -371,6 +411,9 @@ struct ath10k_sta {
 #ifdef CONFIG_MAC80211_DEBUGFS
 	/* protected by conf_mutex */
 	bool aggr_mode;
+
+	/* Protected with ar->data_lock */
+	struct ath10k_sta_tid_stats tid_stats[IEEE80211_NUM_TIDS + 1];
 #endif
 };
 
@@ -1019,6 +1062,8 @@ struct ath10k {
 
 	void *ce_priv;
 
+	u32 sta_tid_stats_mask;
+
 	/* must be last */
 	u8 drv_priv[0] __aligned(sizeof(void *));
 };
diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
index 6d836a26272f..df253f8a7779 100644
--- a/drivers/net/wireless/ath/ath10k/debug.c
+++ b/drivers/net/wireless/ath/ath10k/debug.c
@@ -1,6 +1,7 @@
 /*
  * Copyright (c) 2005-2011 Atheros Communications Inc.
  * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -2135,6 +2136,48 @@ static ssize_t ath10k_debug_fw_checksums_read(struct file *file,
 	.llseek = default_llseek,
 };
 
+static ssize_t ath10k_sta_tid_stats_mask_read(struct file *file,
+					      char __user *user_buf,
+					      size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char buf[32];
+	size_t len;
+
+	len = scnprintf(buf, sizeof(buf), "0x%08x\n", ar->sta_tid_stats_mask);
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t ath10k_sta_tid_stats_mask_write(struct file *file,
+					       const char __user *user_buf,
+					       size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char buf[32];
+	ssize_t len;
+	u32 mask;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	if (kstrtoint(buf, 0, &mask))
+		return -EINVAL;
+
+	ar->sta_tid_stats_mask = mask;
+
+	return len;
+}
+
+static const struct file_operations fops_sta_tid_stats_mask = {
+	.read = ath10k_sta_tid_stats_mask_read,
+	.write = ath10k_sta_tid_stats_mask_write,
+	.open = simple_open,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
 int ath10k_debug_create(struct ath10k *ar)
 {
 	ar->debug.cal_data = vzalloc(ATH10K_DEBUG_CAL_DATA_LEN);
@@ -2250,6 +2293,11 @@ int ath10k_debug_register(struct ath10k *ar)
 	debugfs_create_file("fw_checksums", 0400, ar->debug.debugfs_phy, ar,
 			    &fops_fw_checksums);
 
+	if (IS_ENABLED(CONFIG_MAC80211_DEBUGFS))
+		debugfs_create_file("sta_tid_stats_mask", 0600,
+				    ar->debug.debugfs_phy,
+				    ar, &fops_sta_tid_stats_mask);
+
 	return 0;
 }
 
diff --git a/drivers/net/wireless/ath/ath10k/debug.h b/drivers/net/wireless/ath/ath10k/debug.h
index e54308889e59..306796dcedae 100644
--- a/drivers/net/wireless/ath/ath10k/debug.h
+++ b/drivers/net/wireless/ath/ath10k/debug.h
@@ -1,6 +1,7 @@
 /*
  * Copyright (c) 2005-2011 Atheros Communications Inc.
  * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -191,12 +192,42 @@ void ath10k_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 			    struct ieee80211_sta *sta, struct dentry *dir);
 void ath10k_sta_update_rx_duration(struct ath10k *ar,
 				   struct ath10k_fw_stats *stats);
+void ath10k_sta_update_rx_tid_stats(struct ath10k *ar, u8 *first_hdr,
+				    unsigned long int num_msdus,
+				    enum ath10k_pkt_rx_err err,
+				    unsigned long int unchain_cnt,
+				    unsigned long int drop_cnt,
+				    unsigned long int drop_cnt_filter,
+				    unsigned long int queued_msdus);
+void ath10k_sta_update_rx_tid_stats_ampdu(struct ath10k *ar,
+			u16 peer_id, u8 tid,
+			struct htt_rx_indication_mpdu_range *mpdu_ranges,
+			int num_mpdu_rages);
 #else
 static inline
 void ath10k_sta_update_rx_duration(struct ath10k *ar,
 				   struct ath10k_fw_stats *stats)
 {
 }
+
+static inline
+void ath10k_sta_update_rx_tid_stats(struct ath10k *ar, u8 *first_hdr,
+				    unsigned long int num_msdus,
+				    enum ath10k_pkt_rx_err err,
+				    unsigned long int unchain_cnt,
+				    unsigned long int drop_cnt,
+				    unsigned long int drop_cnt_filter,
+				    unsigned long int queued_msdus)
+{
+}
+
+static inline
+void ath10k_sta_update_rx_tid_stats_ampdu(struct ath10k *ar,
+			u16 peer_id, u8 tid,
+			struct htt_rx_indication_mpdu_range *mpdu_ranges,
+			int num_mpdu_rages)
+{
+}
 #endif /* CONFIG_MAC80211_DEBUGFS */
 
 #ifdef CONFIG_ATH10K_DEBUG
diff --git a/drivers/net/wireless/ath/ath10k/debugfs_sta.c b/drivers/net/wireless/ath/ath10k/debugfs_sta.c
index b260b09dd4d3..c6301324af82 100644
--- a/drivers/net/wireless/ath/ath10k/debugfs_sta.c
+++ b/drivers/net/wireless/ath/ath10k/debugfs_sta.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2014-2017 Qualcomm Atheros, Inc.
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -16,8 +17,125 @@
 
 #include "core.h"
 #include "wmi-ops.h"
+#include "txrx.h"
 #include "debug.h"
 
+static void ath10k_rx_stats_update_amsdu_subfrm(struct ath10k *ar,
+					     struct ath10k_sta_tid_stats *stats,
+					     u32 msdu_count)
+{
+	if (msdu_count == 1)
+		stats->rx_pkt_amsdu[ATH10K_AMSDU_SUBFRM_NUM_1]++;
+	else if (msdu_count == 2)
+		stats->rx_pkt_amsdu[ATH10K_AMSDU_SUBFRM_NUM_2]++;
+	else if (msdu_count == 3)
+		stats->rx_pkt_amsdu[ATH10K_AMSDU_SUBFRM_NUM_3]++;
+	else if (msdu_count == 4)
+		stats->rx_pkt_amsdu[ATH10K_AMSDU_SUBFRM_NUM_4]++;
+	else if (msdu_count > 4)
+		stats->rx_pkt_amsdu[ATH10K_AMSDU_SUBFRM_NUM_MORE]++;
+}
+
+static void ath10k_rx_stats_update_ampdu_subfrm(struct ath10k *ar,
+					     struct ath10k_sta_tid_stats *stats,
+					     u32 mpdu_count)
+{
+	if (mpdu_count <= 10)
+		stats->rx_pkt_ampdu[ATH10K_AMPDU_SUBFRM_NUM_10]++;
+	else if (mpdu_count <= 20)
+		stats->rx_pkt_ampdu[ATH10K_AMPDU_SUBFRM_NUM_20]++;
+	else if (mpdu_count <= 30)
+		stats->rx_pkt_ampdu[ATH10K_AMPDU_SUBFRM_NUM_30]++;
+	else if (mpdu_count <= 40)
+		stats->rx_pkt_ampdu[ATH10K_AMPDU_SUBFRM_NUM_40]++;
+	else if (mpdu_count <= 50)
+		stats->rx_pkt_ampdu[ATH10K_AMPDU_SUBFRM_NUM_50]++;
+	else if (mpdu_count <= 60)
+		stats->rx_pkt_ampdu[ATH10K_AMPDU_SUBFRM_NUM_60]++;
+	else if (mpdu_count > 60)
+		stats->rx_pkt_ampdu[ATH10K_AMPDU_SUBFRM_NUM_MORE]++;
+}
+
+void ath10k_sta_update_rx_tid_stats_ampdu(struct ath10k *ar, u16 peer_id, u8 tid,
+			struct htt_rx_indication_mpdu_range *mpdu_ranges,
+			int num_mpdu_ranges)
+{
+	struct ath10k_sta *arsta;
+	struct ath10k_peer *peer;
+	int i;
+
+	if (tid > IEEE80211_NUM_TIDS || !(ar->sta_tid_stats_mask & BIT(tid)))
+		return;
+
+	rcu_read_lock();
+	spin_lock_bh(&ar->data_lock);
+
+	peer = ath10k_peer_find_by_id(ar, peer_id);
+	if (!peer)
+		goto out;
+
+	arsta = (struct ath10k_sta *)peer->sta->drv_priv;
+
+	for (i = 0; i < num_mpdu_ranges; i++)
+		ath10k_rx_stats_update_ampdu_subfrm(ar,
+						    &arsta->tid_stats[tid],
+						    mpdu_ranges[i].mpdu_count);
+
+out:
+	spin_unlock_bh(&ar->data_lock);
+	rcu_read_unlock();
+}
+
+void ath10k_sta_update_rx_tid_stats(struct ath10k *ar, u8 *first_hdr,
+				    unsigned long int num_msdus,
+				    enum ath10k_pkt_rx_err err,
+				    unsigned long int unchain_cnt,
+				    unsigned long int drop_cnt,
+				    unsigned long int drop_cnt_filter,
+				    unsigned long int queued_msdus)
+{
+	struct ieee80211_sta *sta;
+	struct ath10k_sta *arsta;
+	struct ieee80211_hdr *hdr;
+	struct ath10k_sta_tid_stats *stats;
+	u8 tid = IEEE80211_NUM_TIDS;
+	bool non_data_frm = false;
+
+	hdr = (struct ieee80211_hdr *)first_hdr;
+	if (!ieee80211_is_data(hdr->frame_control))
+		non_data_frm = true;
+
+	if (ieee80211_is_data_qos(hdr->frame_control))
+		tid = *ieee80211_get_qos_ctl(hdr) & IEEE80211_QOS_CTL_TID_MASK;
+
+	if (!(ar->sta_tid_stats_mask & BIT(tid)) || non_data_frm)
+		return;
+
+	rcu_read_lock();
+
+	sta = ieee80211_find_sta_by_ifaddr(ar->hw, hdr->addr2, NULL);
+	if (!sta)
+		goto exit;
+
+	arsta = (struct ath10k_sta *)sta->drv_priv;
+
+	spin_lock_bh(&ar->data_lock);
+	stats = &arsta->tid_stats[tid];
+	stats->rx_pkt_from_fw += num_msdus;
+	stats->rx_pkt_unchained += unchain_cnt;
+	stats->rx_pkt_drop_chained += drop_cnt;
+	stats->rx_pkt_drop_filter += drop_cnt_filter;
+	if (err != ATH10K_PKT_RX_ERR_MAX)
+		stats->rx_pkt_err[err] += queued_msdus;
+	stats->rx_pkt_queued_for_mac += queued_msdus;
+	ath10k_rx_stats_update_amsdu_subfrm(ar, &arsta->tid_stats[tid],
+					    num_msdus);
+	spin_unlock_bh(&ar->data_lock);
+
+exit:
+	rcu_read_unlock();
+}
+
 static void ath10k_sta_update_extd_stats_rx_duration(struct ath10k *ar,
 						     struct ath10k_fw_stats *stats)
 {
@@ -342,6 +460,172 @@ static ssize_t ath10k_dbg_sta_read_peer_debug_trigger(struct file *file,
 	.llseek = default_llseek,
 };
 
+static char *get_err_str(enum ath10k_pkt_rx_err i)
+{
+	switch (i) {
+	case ATH10K_PKT_RX_ERR_FCS:
+		return "fcs_err";
+	case ATH10K_PKT_RX_ERR_TKIP:
+		return "tkip_err";
+	case ATH10K_PKT_RX_ERR_CRYPT:
+		return "crypt_err";
+	case ATH10K_PKT_RX_ERR_PEER_IDX_INVAL:
+		return "peer_idx_inval";
+	case ATH10K_PKT_RX_ERR_MAX:
+		return "unknown";
+	}
+
+	return "unknown";
+}
+
+static char *get_num_ampdu_subfrm_str(enum ath10k_ampdu_subfrm_num i)
+{
+	switch (i) {
+	case ATH10K_AMPDU_SUBFRM_NUM_10:
+		return "upto 10";
+	case ATH10K_AMPDU_SUBFRM_NUM_20:
+		return "11-20";
+	case ATH10K_AMPDU_SUBFRM_NUM_30:
+		return "21-30";
+	case ATH10K_AMPDU_SUBFRM_NUM_40:
+		return "31-40";
+	case ATH10K_AMPDU_SUBFRM_NUM_50:
+		return "41-50";
+	case ATH10K_AMPDU_SUBFRM_NUM_60:
+		return "51-60";
+	case ATH10K_AMPDU_SUBFRM_NUM_MORE:
+		return ">60";
+	case ATH10K_AMPDU_SUBFRM_NUM_MAX:
+		return "0";
+	}
+
+	return "0";
+}
+
+static char *get_num_amsdu_subfrm_str(enum ath10k_amsdu_subfrm_num i)
+{
+	switch (i) {
+	case ATH10K_AMSDU_SUBFRM_NUM_1:
+		return "1";
+	case ATH10K_AMSDU_SUBFRM_NUM_2:
+		return "2";
+	case ATH10K_AMSDU_SUBFRM_NUM_3:
+		return "3";
+	case ATH10K_AMSDU_SUBFRM_NUM_4:
+		return "4";
+	case ATH10K_AMSDU_SUBFRM_NUM_MORE:
+		return ">4";
+	case ATH10K_AMSDU_SUBFRM_NUM_MAX:
+		return "0";
+	}
+
+	return "0";
+}
+
+#define PRINT_TID_STATS(_field, _tabs) \
+	do { \
+		int k = 0; \
+		for (j = 0; j <= IEEE80211_NUM_TIDS; j++) { \
+			if (ar->sta_tid_stats_mask & BIT(j))  { \
+				len += scnprintf(buf + len, buf_len - len, \
+						 "[%02d] %-10lu  ", \
+						 j, stats[j]._field); \
+				k++; \
+				if (k % 8 == 0)  { \
+					len += scnprintf(buf + len, \
+							 buf_len - len, "\n"); \
+					len += scnprintf(buf + len, \
+							 buf_len - len, \
+							 _tabs); \
+				} \
+			} \
+		} \
+		len += scnprintf(buf + len, buf_len - len, "\n"); \
+	} while (0)
+
+static ssize_t ath10k_dbg_sta_read_tid_stats(struct file *file,
+					     char __user *user_buf,
+					     size_t count, loff_t *ppos)
+{
+	struct ieee80211_sta *sta = file->private_data;
+	struct ath10k_sta *arsta = (struct ath10k_sta *)sta->drv_priv;
+	struct ath10k *ar = arsta->arvif->ar;
+	struct ath10k_sta_tid_stats *stats = arsta->tid_stats;
+	size_t len = 0, buf_len = 1048 * IEEE80211_NUM_TIDS;
+	char *buf;
+	int i, j;
+	ssize_t ret;
+
+	buf = kzalloc(buf_len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	mutex_lock(&ar->conf_mutex);
+
+	spin_lock_bh(&ar->data_lock);
+
+	len += scnprintf(buf + len, buf_len - len,
+			 "\n\t\tDriver Rx pkt stats per tid, ([tid] count)\n");
+	len += scnprintf(buf + len, buf_len - len,
+			 "\t\t------------------------------------------\n");
+	len += scnprintf(buf + len, buf_len - len, "MSDUs from FW\t\t\t");
+	PRINT_TID_STATS(rx_pkt_from_fw, "\t\t\t\t");
+
+	len += scnprintf(buf + len, buf_len - len, "MSDUs unchained\t\t\t");
+	PRINT_TID_STATS(rx_pkt_unchained, "\t\t\t\t");
+
+	len += scnprintf(buf + len, buf_len - len,
+			 "MSDUs locally dropped:chained\t");
+	PRINT_TID_STATS(rx_pkt_drop_chained, "\t\t\t\t");
+
+	len += scnprintf(buf + len, buf_len - len,
+			 "MSDUs locally dropped:filtered\t");
+	PRINT_TID_STATS(rx_pkt_drop_filter, "\t\t\t\t");
+
+	len += scnprintf(buf + len, buf_len - len,
+			 "MSDUs queued for mac80211\t");
+	PRINT_TID_STATS(rx_pkt_queued_for_mac, "\t\t\t\t");
+
+	for (i = 0; i < ATH10K_PKT_RX_ERR_MAX; i++) {
+		len += scnprintf(buf + len, buf_len - len,
+				 "MSDUs with error:%s\t", get_err_str(i));
+		PRINT_TID_STATS(rx_pkt_err[i], "\t\t\t\t");
+	}
+
+	len += scnprintf(buf + len, buf_len - len, "\n");
+	for (i = 0; i < ATH10K_AMPDU_SUBFRM_NUM_MAX; i++) {
+		len += scnprintf(buf + len, buf_len - len,
+				 "A-MPDU num subframes %s\t",
+				 get_num_ampdu_subfrm_str(i));
+		PRINT_TID_STATS(rx_pkt_ampdu[i], "\t\t\t\t");
+	}
+
+	len += scnprintf(buf + len, buf_len - len, "\n");
+	for (i = 0; i < ATH10K_AMSDU_SUBFRM_NUM_MAX; i++) {
+		len += scnprintf(buf + len, buf_len - len,
+				 "A-MSDU num subframes %s\t\t",
+				 get_num_amsdu_subfrm_str(i));
+		PRINT_TID_STATS(rx_pkt_amsdu[i], "\t\t\t\t");
+	}
+
+	spin_unlock_bh(&ar->data_lock);
+
+	ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+	kfree(buf);
+
+	mutex_unlock(&ar->conf_mutex);
+
+	return ret;
+}
+
+static const struct file_operations fops_tid_stats_dump = {
+	.open = simple_open,
+	.read = ath10k_dbg_sta_read_tid_stats,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
 void ath10k_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 			    struct ieee80211_sta *sta, struct dentry *dir)
 {
@@ -351,4 +635,6 @@ void ath10k_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 	debugfs_create_file("delba", 0200, dir, sta, &fops_delba);
 	debugfs_create_file("peer_debug_trigger", 0600, dir, sta,
 			    &fops_peer_debug_trigger);
+	debugfs_create_file("dump_tid_stats", 0400, dir, sta,
+			    &fops_tid_stats_dump);
 }
diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c
index 6d96f9560950..64996aba1485 100644
--- a/drivers/net/wireless/ath/ath10k/htt_rx.c
+++ b/drivers/net/wireless/ath/ath10k/htt_rx.c
@@ -1,6 +1,7 @@
 /*
  * Copyright (c) 2005-2011 Atheros Communications Inc.
  * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -1502,7 +1503,9 @@ static void ath10k_htt_rx_h_csum_offload(struct sk_buff *msdu)
 static void ath10k_htt_rx_h_mpdu(struct ath10k *ar,
 				 struct sk_buff_head *amsdu,
 				 struct ieee80211_rx_status *status,
-				 bool fill_crypt_header)
+				 bool fill_crypt_header,
+				 u8 *rx_hdr,
+				 enum ath10k_pkt_rx_err *err)
 {
 	struct sk_buff *first;
 	struct sk_buff *last;
@@ -1538,6 +1541,9 @@ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar,
 	hdr = (void *)rxd->rx_hdr_status;
 	memcpy(first_hdr, hdr, RX_HTT_HDR_STATUS_LEN);
 
+	if (rx_hdr)
+		memcpy(rx_hdr, hdr, RX_HTT_HDR_STATUS_LEN);
+
 	/* Each A-MSDU subframe will use the original header as the base and be
 	 * reported as a separate MSDU so strip the A-MSDU bit from QoS Ctl.
 	 */
@@ -1581,6 +1587,17 @@ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar,
 	if (has_tkip_err)
 		status->flag |= RX_FLAG_MMIC_ERROR;
 
+	if (err) {
+		if (has_fcs_err)
+			*err = ATH10K_PKT_RX_ERR_FCS;
+		else if (has_tkip_err)
+			*err = ATH10K_PKT_RX_ERR_TKIP;
+		else if (has_crypto_err)
+			*err = ATH10K_PKT_RX_ERR_CRYPT;
+		else if (has_peer_idx_invalid)
+			*err = ATH10K_PKT_RX_ERR_PEER_IDX_INVAL;
+	}
+
 	/* Firmware reports all necessary management frames via WMI already.
 	 * They are not reported to monitor interfaces at all so pass the ones
 	 * coming via HTT to monitor interfaces instead. This simplifies
@@ -1651,11 +1668,13 @@ static void ath10k_htt_rx_h_enqueue(struct ath10k *ar,
 	}
 }
 
-static int ath10k_unchain_msdu(struct sk_buff_head *amsdu)
+static int ath10k_unchain_msdu(struct sk_buff_head *amsdu,
+			       unsigned long int *unchain_cnt)
 {
 	struct sk_buff *skb, *first;
 	int space;
 	int total_len = 0;
+	int amsdu_len = skb_queue_len(amsdu);
 
 	/* TODO:  Might could optimize this by using
 	 * skb_try_coalesce or similar method to
@@ -1691,11 +1710,16 @@ static int ath10k_unchain_msdu(struct sk_buff_head *amsdu)
 	}
 
 	__skb_queue_head(amsdu, first);
+
+	*unchain_cnt += amsdu_len - 1;
+
 	return 0;
 }
 
 static void ath10k_htt_rx_h_unchain(struct ath10k *ar,
-				    struct sk_buff_head *amsdu)
+				    struct sk_buff_head *amsdu,
+				    unsigned long int *drop_cnt,
+				    unsigned long int *unchain_cnt)
 {
 	struct sk_buff *first;
 	struct htt_rx_desc *rxd;
@@ -1713,11 +1737,12 @@ static void ath10k_htt_rx_h_unchain(struct ath10k *ar,
 	 */
 	if (decap != RX_MSDU_DECAP_RAW ||
 	    skb_queue_len(amsdu) != 1 + rxd->frag_info.ring2_more_count) {
+		*drop_cnt += skb_queue_len(amsdu);
 		__skb_queue_purge(amsdu);
 		return;
 	}
 
-	ath10k_unchain_msdu(amsdu);
+	ath10k_unchain_msdu(amsdu, unchain_cnt);
 }
 
 static bool ath10k_htt_rx_amsdu_allowed(struct ath10k *ar,
@@ -1743,7 +1768,8 @@ static bool ath10k_htt_rx_amsdu_allowed(struct ath10k *ar,
 
 static void ath10k_htt_rx_h_filter(struct ath10k *ar,
 				   struct sk_buff_head *amsdu,
-				   struct ieee80211_rx_status *rx_status)
+				   struct ieee80211_rx_status *rx_status,
+				   unsigned long int *drop_cnt)
 {
 	if (skb_queue_empty(amsdu))
 		return;
@@ -1751,6 +1777,9 @@ static void ath10k_htt_rx_h_filter(struct ath10k *ar,
 	if (ath10k_htt_rx_amsdu_allowed(ar, amsdu, rx_status))
 		return;
 
+	if (drop_cnt)
+		*drop_cnt += skb_queue_len(amsdu);
+
 	__skb_queue_purge(amsdu);
 }
 
@@ -1760,6 +1789,12 @@ static int ath10k_htt_rx_handle_amsdu(struct ath10k_htt *htt)
 	struct ieee80211_rx_status *rx_status = &htt->rx_status;
 	struct sk_buff_head amsdu;
 	int ret;
+	unsigned long int drop_cnt = 0;
+	unsigned long int unchain_cnt = 0;
+	unsigned long int drop_cnt_filter = 0;
+	unsigned long int msdus_to_queue, num_msdus;
+	enum ath10k_pkt_rx_err err = ATH10K_PKT_RX_ERR_MAX;
+	u8 first_hdr[RX_HTT_HDR_STATUS_LEN];
 
 	__skb_queue_head_init(&amsdu);
 
@@ -1781,16 +1816,23 @@ static int ath10k_htt_rx_handle_amsdu(struct ath10k_htt *htt)
 		return ret;
 	}
 
+	num_msdus = skb_queue_len(&amsdu);
+
 	ath10k_htt_rx_h_ppdu(ar, &amsdu, rx_status, 0xffff);
 
 	/* only for ret = 1 indicates chained msdus */
 	if (ret > 0)
-		ath10k_htt_rx_h_unchain(ar, &amsdu);
+		ath10k_htt_rx_h_unchain(ar, &amsdu, &drop_cnt, &unchain_cnt);
 
-	ath10k_htt_rx_h_filter(ar, &amsdu, rx_status);
-	ath10k_htt_rx_h_mpdu(ar, &amsdu, rx_status, true);
+	ath10k_htt_rx_h_filter(ar, &amsdu, rx_status, &drop_cnt_filter);
+	ath10k_htt_rx_h_mpdu(ar, &amsdu, rx_status, true, first_hdr, &err);
+	msdus_to_queue = skb_queue_len(&amsdu);
 	ath10k_htt_rx_h_enqueue(ar, &amsdu, rx_status);
 
+	ath10k_sta_update_rx_tid_stats(ar, first_hdr, num_msdus, err,
+				       unchain_cnt, drop_cnt, drop_cnt_filter,
+				       msdus_to_queue);
+
 	return 0;
 }
 
@@ -1801,9 +1843,14 @@ static void ath10k_htt_rx_proc_rx_ind(struct ath10k_htt *htt,
 	struct htt_rx_indication_mpdu_range *mpdu_ranges;
 	int num_mpdu_ranges;
 	int i, mpdu_count = 0;
+	u16 peer_id;
+	u8 tid;
 
 	num_mpdu_ranges = MS(__le32_to_cpu(rx->hdr.info1),
 			     HTT_RX_INDICATION_INFO1_NUM_MPDU_RANGES);
+	peer_id = __le16_to_cpu(rx->hdr.peer_id);
+	tid =  MS(rx->hdr.info0, HTT_RX_INDICATION_INFO0_EXT_TID);
+
 	mpdu_ranges = htt_rx_ind_get_mpdu_ranges(rx);
 
 	ath10k_dbg_dump(ar, ATH10K_DBG_HTT_DUMP, NULL, "htt rx ind: ",
@@ -1815,6 +1862,9 @@ static void ath10k_htt_rx_proc_rx_ind(struct ath10k_htt *htt,
 		mpdu_count += mpdu_ranges[i].mpdu_count;
 
 	atomic_add(mpdu_count, &htt->num_mpdus_ready);
+
+	ath10k_sta_update_rx_tid_stats_ampdu(ar, peer_id, tid, mpdu_ranges,
+					     num_mpdu_ranges);
 }
 
 static void ath10k_htt_rx_tx_compl_ind(struct ath10k *ar,
@@ -2124,8 +2174,9 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb)
 			 * should still give an idea about rx rate to the user.
 			 */
 			ath10k_htt_rx_h_ppdu(ar, &amsdu, status, vdev_id);
-			ath10k_htt_rx_h_filter(ar, &amsdu, status);
-			ath10k_htt_rx_h_mpdu(ar, &amsdu, status, false);
+			ath10k_htt_rx_h_filter(ar, &amsdu, status, NULL);
+			ath10k_htt_rx_h_mpdu(ar, &amsdu, status, false, NULL,
+					     NULL);
 			ath10k_htt_rx_h_enqueue(ar, &amsdu, status);
 			break;
 		case -EAGAIN:
