Merge changes  into win_wlan_host.1.0
diff --git a/dp/inc/dp_rate_stats.h b/dp/inc/dp_rate_stats.h
index b840c57..35271fb 100644
--- a/dp/inc/dp_rate_stats.h
+++ b/dp/inc/dp_rate_stats.h
@@ -41,9 +41,28 @@
 #define RATE_STATS_LOCK_ACQUIRE(lock) qdf_spin_lock_bh(lock)
 #define RATE_STATS_LOCK_RELEASE(lock) qdf_spin_unlock_bh(lock)
 
+#define STATS_CTX_LOCK_CREATE(lock) qdf_spinlock_create(lock)
+#define STATS_CTX_LOCK_DESTROY(lock) qdf_spinlock_destroy(lock)
+#define STATS_CTX_LOCK_ACQUIRE(lock) qdf_spin_lock_bh(lock)
+#define STATS_CTX_LOCK_RELEASE(lock) qdf_spin_unlock_bh(lock)
+
 struct cdp_pdev;
 
 /**
+ * enum dp_ppdu_type - enum for ppdu_type
+ * @DP_PPDU_TYPE_SU: single user PPDU
+ * @DP_PPDU_TYPE_MU_MIMO: multi user mimo ppdu
+ * @DP_PPDU_TYPE_MU_OFDMA: multi user ofdma ppdu
+ * @DP_PPDU_TYPE_MU_OFDMA_MIMO: multi user mimo/ofdma ppdu
+ */
+enum dp_ppdu_type {
+	DP_PPDU_TYPE_SU,
+	DP_PPDU_TYPE_MU_MIMO,
+	DP_PPDU_TYPE_MU_OFDMA,
+	DP_PPDU_TYPE_MU_OFDMA_MIMO,
+};
+
+/**
  * struct wlan_peer_tx_rate_stats - peer tx rate statistics
  * @stats: array containing tx rate stats
  * @cur_rix: rate index updated last in list
@@ -72,22 +91,76 @@
 };
 
 /**
- * struct wlan_peer_rate_stats - Peer rate statistics ctx
+ * struct wlan_peer_rx_link_stats - Peer Rx Link statistics
+ * @stats: array containing rx rate stats
+ * @lock: lock protecting list
+ */
+struct wlan_peer_rx_link_stats {
+	struct wlan_rx_link_stats stats;
+	qdf_spinlock_t lock;
+};
+
+/**
+ * struct wlan_peer_tx_link_stats - Peer Tx Link statistics
+ * @stats: array containing rx rate stats
+ * @lock: lock protecting list
+ */
+struct wlan_peer_tx_link_stats {
+	struct wlan_tx_link_stats stats;
+	qdf_spinlock_t lock;
+};
+
+/**
+ * struct wlan_peer_rate_stats - Rate statistics
  * @tx: tx rate statistics
  * @rx: rx rate statistics
+ */
+struct wlan_peer_rate_stats {
+	struct wlan_peer_tx_rate_stats tx;
+	struct wlan_peer_rx_rate_stats rx;
+};
+
+/**
+ * struct wlan_peer_link_metrics - Peer link metrics
+ * @tx: tx link quality stats
+ * @rx: rx link quality stats
+ */
+struct wlan_peer_link_metrics {
+	struct wlan_peer_tx_link_stats tx;
+	struct wlan_peer_rx_link_stats rx;
+};
+
+/**
+ * struct wlan_peer_rate_stats_ctx - Peer statistics context
+ * @rate_stats: Rate statistics (version 1 stats)
+ * @link_metrics: Link Metrics (version 2 stats)
  * @mac_addr: peer MAC address
  * @peer_cookie: cookie for unique session of peer
  * @pdev_id: id of dp pdev
  */
 struct wlan_peer_rate_stats_ctx {
-	struct wlan_peer_tx_rate_stats tx;
-	struct wlan_peer_rx_rate_stats rx;
+	struct wlan_peer_rate_stats *rate_stats;
+	struct wlan_peer_link_metrics *link_metrics;
 	uint8_t mac_addr[WLAN_MAC_ADDR_LEN];
 	uint64_t peer_cookie;
 	uint8_t pdev_id;
 };
 
 /**
+ * enum rdk_stats_version - Peer statistics versions
+ * @RDK_STATS_DISABLED: peer statistics disabled
+ * @RDK_RATE_STATS: peer rate statistics enabled
+ * @RDK_LINK_STATS: peer link metrics enabled
+ * @RDK_ALL_STATS: peer all statistics enabled
+ */
+enum rdk_stats_version {
+	RDK_STATS_DISABLED = 0,
+	RDK_RATE_STATS = 1,
+	RDK_LINK_STATS = 2,
+	RDK_ALL_STATS = 3,
+};
+
+/**
  * struct wlan_soc_rate_stats_ctx - rate stats context at soc level
  * @soc: Opaque soc handle
  * @txs_cache_flush: tx stats flush count
@@ -98,6 +171,10 @@
  * @rxs_cache_hit: cache hit for rate index received from cache database
  * @txs_cache_miss: rate index recevied is not in cache database
  * @rxs_cache_miss: rate index recevied is not in cache database
+ * @stats_ver: peer statistics version
+ * @is_lithium: is lithium or legacy
+ * @tx_ctx_lock: tx context soc level lock
+ * @rx_ctx_lock: rx context soc level lock
  */
 struct wlan_soc_rate_stats_ctx {
 	struct cdp_soc_t *soc;
@@ -109,7 +186,10 @@
 	uint32_t rxs_cache_hit;
 	uint32_t txs_cache_miss;
 	uint32_t rxs_cache_miss;
+	enum rdk_stats_version stats_ver;
 	bool is_lithium;
+	qdf_spinlock_t tx_ctx_lock;
+	qdf_spinlock_t rx_ctx_lock;
 };
 
 /**
diff --git a/dp/inc/dp_rate_stats_pub.h b/dp/inc/dp_rate_stats_pub.h
index 1d1ea74..aa219b0 100644
--- a/dp/inc/dp_rate_stats_pub.h
+++ b/dp/inc/dp_rate_stats_pub.h
@@ -84,6 +84,8 @@
 	DP_PEER_RX_RATE_STATS,
 	DP_PEER_TX_RATE_STATS,
 	DP_PEER_SOJOURN_STATS,
+	DP_PEER_RX_LINK_STATS,
+	DP_PEER_TX_LINK_STATS,
 };
 
 /** struct wlan_tx_rate_stats - Tx packet rate info
@@ -146,6 +148,86 @@
 	qdf_ewma_tx_lag avg_sojourn_msdu[WLAN_DATA_TID_MAX];
 };
 
+#define BW_USAGE_MAX_SIZE 4
+
+/**
+ * struct wlan_peer_bw_stats - per link bw related stats
+ * @usage_total - sum of total BW (20, 40, 80, 160)
+ * @usage_avg - @usage_total / number of PPDUs (avg BW)
+ * @usage_counter - each BW usage counter
+ * @usage_max - number of pkts in max BW mode
+ */
+struct wlan_peer_bw_stats {
+	uint32_t usage_total;
+	uint32_t usage_counter[BW_USAGE_MAX_SIZE];
+	uint8_t usage_avg;
+	uint8_t usage_max;
+};
+
+/**
+ * struct wlan_rx_link_stats - Peer Rx link statistics
+ * @num_ppdus - number of ppdus per user
+ * @bytes - number of bytes per user
+ * @phy_rate_lpf_avg_su - SU packet LPF averaged rx rate
+ * @phy_rate_actual_su - SU packet rounded average rx rate
+ * @phy_rate_lpf_avg_mu - MU packet LPF averaged rx rate
+ * @phy_rate_actual_mu - MUpacket rounded average rx rate
+ * @ofdma_usage - number of packet in OFDMA
+ * @mu_mimo_usage - number of pakcets in MU MIMO
+ * @bw - average BW and max BW related structure
+ * @su_rssi - single user RSSI
+ * @mpdu_retries - number of retried MPDUs
+ * @pkt_error_rate - average packet error rate
+ * @num_mpdus - total number of mpdus
+ */
+struct wlan_rx_link_stats {
+	uint64_t bytes;
+	uint32_t num_ppdus;
+	uint32_t phy_rate_lpf_avg_su;
+	uint32_t phy_rate_actual_su;
+	uint32_t phy_rate_lpf_avg_mu;
+	uint32_t phy_rate_actual_mu;
+	uint32_t ofdma_usage;
+	uint32_t mu_mimo_usage;
+	struct wlan_peer_bw_stats bw;
+	qdf_ewma_rx_rssi su_rssi;
+	uint32_t mpdu_retries;
+	uint32_t num_mpdus;
+	uint8_t pkt_error_rate;
+};
+
+/**
+ * struct wlan_tx_link_stats - Peer tx link statistics
+ * @num_ppdus - number of ppdus per user
+ * @bytes - number of bytes per user
+ * @phy_rate_lpf_avg_su - SU packet LPF averaged tx rate
+ * @phy_rate_actual_su - SU packet rounded average tx rate
+ * @phy_rate_lpf_avg_mu - MU packet LPF averaged tx rate
+ * @phy_rate_actual_mu - MUpacket rounded average tx rate
+ * @ofdma_usage - number of packet in OFDMA
+ * @mu_mimo_usage - number of pakcets in MU MIMO
+ * @bw - average BW and max BW related structure
+ * @ack_rssi - averaged ACK rssi
+ * @mpdu_failed - number of failed MPDUs
+ * @mpdu_success - number of success MPDUs
+ * @pkt_error_rate - average packet error rate
+ */
+struct wlan_tx_link_stats {
+	uint64_t bytes;
+	uint32_t num_ppdus;
+	uint32_t phy_rate_lpf_avg_su;
+	uint32_t phy_rate_actual_su;
+	uint32_t phy_rate_lpf_avg_mu;
+	uint32_t phy_rate_actual_mu;
+	uint32_t ofdma_usage;
+	uint32_t mu_mimo_usage;
+	struct wlan_peer_bw_stats bw;
+	qdf_ewma_rx_rssi ack_rssi;
+	uint32_t mpdu_failed;
+	uint32_t mpdu_success;
+	uint8_t pkt_error_rate;
+};
+
 /**
  * struct wlan_peer_rate_stats_intf - Interface structure to
  * flush stats to user spave entity
diff --git a/dp/src/dp_rate_stats.c b/dp/src/dp_rate_stats.c
index 1bd4e84..089e026 100644
--- a/dp/src/dp_rate_stats.c
+++ b/dp/src/dp_rate_stats.c
@@ -26,6 +26,14 @@
 
 #ifdef QCA_SUPPORT_RDK_STATS
 
+/* Calculate actual BW from BW ENUM as in
+ * x = 0 for 20MHz
+ * x = 1 for 40MHz
+ * x = 2 for 80MHz
+ * x = 3 for 160MHz
+ */
+#define GET_BW_FROM_BW_ENUM(x) ((20) * (1 << x))
+
 static void
 wlan_peer_read_ewma_avg_rssi(struct wlan_rx_rate_stats *rx_stats)
 {
@@ -56,7 +64,8 @@
 
 	if (!soc_stats_ctx)
 		return;
-	rx_stats = &stats_ctx->rx;
+
+	rx_stats = &stats_ctx->rate_stats->rx;
 
 	buf.cookie = 0;
 	wlan_peer_read_ewma_avg_rssi(rx_stats->stats);
@@ -109,12 +118,24 @@
 	if (!soc_stats_ctx)
 		return;
 
-	tx_stats = &stats_ctx->tx;
+	tx_stats = &stats_ctx->rate_stats->tx;
+
+	buf.cookie = 0;
 	buf.stats = (struct wlan_tx_rate_stats *)tx_stats->stats;
 	buf.buf_len = (WLANSTATS_CACHE_SIZE * sizeof(struct wlan_tx_rate_stats)
 		       + sizeof(struct wlan_tx_sojourn_stats));
 	buf.stats_type = DP_PEER_TX_RATE_STATS;
-	buf.cookie = stats_ctx->peer_cookie;
+	/* Prepare 64 bit cookie */
+	/*-------------------|-------------------|
+	 *  32 bit target    | 32 bit peer cookie|
+	 *-------------------|-------------------|
+	 */
+	buf.cookie = ((((buf.cookie | soc_stats_ctx->is_lithium)
+		      << WLANSTATS_PEER_COOKIE_LSB) &
+		      WLANSTATS_COOKIE_PLATFORM_OFFSET) |
+		      (((buf.cookie | stats_ctx->peer_cookie) &
+		      WLANSTATS_COOKIE_PEER_COOKIE_OFFSET)));
+
 	wlan_peer_read_sojourn_average(tx_stats);
 	qdf_mem_copy(buf.peer_mac, stats_ctx->mac_addr, WLAN_MAC_ADDR_LEN);
 	cdp_peer_flush_rate_stats(soc_stats_ctx->soc,
@@ -135,22 +156,156 @@
 }
 
 static void
+wlan_peer_flush_tx_link_stats(struct wlan_soc_rate_stats_ctx *soc_stats_ctx,
+			      struct wlan_peer_rate_stats_ctx *stats_ctx)
+{
+	struct wlan_peer_rate_stats_intf buf;
+	struct wlan_peer_tx_link_stats *tx_stats;
+	uint8_t bw_max_idx;
+
+	if (!soc_stats_ctx) {
+		qdf_info("soc stats context is NULL\n");
+		return;
+	}
+
+	tx_stats = &stats_ctx->link_metrics->tx;
+
+	buf.cookie = 0;
+	buf.stats = (struct wlan_tx_link_stats *)&tx_stats->stats;
+	buf.buf_len = sizeof(struct wlan_peer_tx_link_stats);
+	buf.stats_type = DP_PEER_TX_LINK_STATS;
+	/* Prepare 64 bit cookie */
+	/*-------------------|-------------------|
+	 *  32 bit target    | 32 bit peer cookie|
+	 *-------------------|-------------------|
+	 */
+	buf.cookie = ((((buf.cookie | soc_stats_ctx->is_lithium)
+		      << WLANSTATS_PEER_COOKIE_LSB) &
+		      WLANSTATS_COOKIE_PLATFORM_OFFSET) |
+		      (((buf.cookie | stats_ctx->peer_cookie) &
+		      WLANSTATS_COOKIE_PEER_COOKIE_OFFSET)));
+
+	tx_stats->stats.ack_rssi.internal =
+			qdf_ewma_rx_rssi_read(&tx_stats->stats.ack_rssi);
+	tx_stats->stats.phy_rate_actual_su =
+			dp_ath_rate_out(tx_stats->stats.phy_rate_lpf_avg_su);
+	tx_stats->stats.phy_rate_actual_mu =
+			dp_ath_rate_out(tx_stats->stats.phy_rate_lpf_avg_mu);
+
+	if (tx_stats->stats.num_ppdus) {
+		tx_stats->stats.bw.usage_avg = tx_stats->stats.bw.usage_total /
+					       tx_stats->stats.num_ppdus;
+		bw_max_idx = tx_stats->stats.bw.usage_max;
+		tx_stats->stats.bw.usage_max =
+			(tx_stats->stats.bw.usage_counter[bw_max_idx] * 100) /
+			 tx_stats->stats.num_ppdus;
+	}
+
+	if (tx_stats->stats.mpdu_success)
+		tx_stats->stats.pkt_error_rate =
+			(tx_stats->stats.mpdu_failed * 100) /
+			 tx_stats->stats.mpdu_success;
+
+	qdf_mem_copy(buf.peer_mac, stats_ctx->mac_addr, WLAN_MAC_ADDR_LEN);
+	cdp_peer_flush_rate_stats(soc_stats_ctx->soc,
+				  stats_ctx->pdev_id, &buf);
+
+	qdf_mem_zero(&tx_stats->stats, sizeof(struct wlan_tx_link_stats));
+}
+
+static void
+wlan_peer_flush_rx_link_stats(struct wlan_soc_rate_stats_ctx *soc_stats_ctx,
+			      struct wlan_peer_rate_stats_ctx *stats_ctx)
+{
+	struct wlan_peer_rate_stats_intf buf;
+	struct wlan_peer_rx_link_stats *rx_stats;
+	uint8_t bw_max_idx;
+
+	if (!soc_stats_ctx) {
+		qdf_info("soc stats context is NULL\n");
+		return;
+	}
+
+	rx_stats = &stats_ctx->link_metrics->rx;
+
+	buf.cookie = 0;
+	buf.stats = (struct wlan_rx_link_stats *)&rx_stats->stats;
+	buf.buf_len = sizeof(struct wlan_peer_rx_link_stats);
+	buf.stats_type = DP_PEER_RX_LINK_STATS;
+	/* Prepare 64 bit cookie */
+	/*-------------------|-------------------|
+	 *  32 bit target    | 32 bit peer cookie|
+	 *-------------------|-------------------|
+	 */
+	buf.cookie = ((((buf.cookie | soc_stats_ctx->is_lithium)
+		      << WLANSTATS_PEER_COOKIE_LSB) &
+		      WLANSTATS_COOKIE_PLATFORM_OFFSET) |
+		      (((buf.cookie | stats_ctx->peer_cookie) &
+		      WLANSTATS_COOKIE_PEER_COOKIE_OFFSET)));
+
+	rx_stats->stats.su_rssi.internal =
+			qdf_ewma_rx_rssi_read(&rx_stats->stats.su_rssi);
+	rx_stats->stats.phy_rate_actual_su =
+			dp_ath_rate_out(rx_stats->stats.phy_rate_lpf_avg_su);
+	rx_stats->stats.phy_rate_actual_mu =
+			dp_ath_rate_out(rx_stats->stats.phy_rate_lpf_avg_mu);
+	if (rx_stats->stats.num_ppdus) {
+		rx_stats->stats.bw.usage_avg = rx_stats->stats.bw.usage_total /
+					       rx_stats->stats.num_ppdus;
+		bw_max_idx = rx_stats->stats.bw.usage_max;
+		rx_stats->stats.bw.usage_max =
+			(rx_stats->stats.bw.usage_counter[bw_max_idx] * 100) /
+			 rx_stats->stats.num_ppdus;
+	}
+
+	if (rx_stats->stats.num_mpdus)
+		rx_stats->stats.pkt_error_rate =
+					(rx_stats->stats.mpdu_retries * 100) /
+					 rx_stats->stats.num_mpdus;
+
+	qdf_mem_copy(buf.peer_mac, stats_ctx->mac_addr, WLAN_MAC_ADDR_LEN);
+	cdp_peer_flush_rate_stats(soc_stats_ctx->soc,
+				  stats_ctx->pdev_id, &buf);
+
+	qdf_mem_zero(&rx_stats->stats, sizeof(struct wlan_rx_link_stats));
+}
+
+static void
 wlan_peer_flush_rate_stats(struct wlan_soc_rate_stats_ctx *soc_stats_ctx,
 			   struct wlan_peer_rate_stats_ctx *stats_ctx)
 {
 	struct wlan_peer_tx_rate_stats *tx_stats;
 	struct wlan_peer_rx_rate_stats *rx_stats;
+	struct wlan_peer_tx_link_stats *tx_link_stats;
+	struct wlan_peer_rx_link_stats *rx_link_stats;
 
-	tx_stats = &stats_ctx->tx;
-	rx_stats = &stats_ctx->rx;
+	if (soc_stats_ctx->stats_ver == RDK_RATE_STATS ||
+	    soc_stats_ctx->stats_ver == RDK_ALL_STATS) {
+		tx_stats = &stats_ctx->rate_stats->tx;
+		rx_stats = &stats_ctx->rate_stats->rx;
 
-	RATE_STATS_LOCK_ACQUIRE(&tx_stats->lock);
-	wlan_peer_flush_tx_rate_stats(soc_stats_ctx, stats_ctx);
-	RATE_STATS_LOCK_RELEASE(&tx_stats->lock);
+		RATE_STATS_LOCK_ACQUIRE(&tx_stats->lock);
+		wlan_peer_flush_tx_rate_stats(soc_stats_ctx, stats_ctx);
+		RATE_STATS_LOCK_RELEASE(&tx_stats->lock);
 
-	RATE_STATS_LOCK_ACQUIRE(&rx_stats->lock);
-	wlan_peer_flush_rx_rate_stats(soc_stats_ctx, stats_ctx);
-	RATE_STATS_LOCK_RELEASE(&rx_stats->lock);
+		RATE_STATS_LOCK_ACQUIRE(&rx_stats->lock);
+		wlan_peer_flush_rx_rate_stats(soc_stats_ctx, stats_ctx);
+		RATE_STATS_LOCK_RELEASE(&rx_stats->lock);
+	}
+
+	if (soc_stats_ctx->stats_ver == RDK_LINK_STATS ||
+	    soc_stats_ctx->stats_ver == RDK_ALL_STATS) {
+		tx_link_stats = &stats_ctx->link_metrics->tx;
+		rx_link_stats = &stats_ctx->link_metrics->rx;
+
+		RATE_STATS_LOCK_ACQUIRE(&tx_link_stats->lock);
+		wlan_peer_flush_tx_link_stats(soc_stats_ctx, stats_ctx);
+		RATE_STATS_LOCK_RELEASE(&tx_link_stats->lock);
+
+		RATE_STATS_LOCK_ACQUIRE(&rx_link_stats->lock);
+		wlan_peer_flush_rx_link_stats(soc_stats_ctx, stats_ctx);
+		RATE_STATS_LOCK_RELEASE(&rx_link_stats->lock);
+	}
 }
 
 void wlan_peer_rate_stats_flush_req(void *ctx, enum WDI_EVENT event,
@@ -197,71 +352,249 @@
 }
 
 static void
+wlan_peer_update_tx_link_stats(struct wlan_soc_rate_stats_ctx *soc_stats_ctx,
+			       struct cdp_tx_completion_ppdu *cdp_tx_ppdu)
+{
+	struct cdp_tx_completion_ppdu_user *ppdu_user;
+	struct wlan_peer_rate_stats_ctx *stats_ctx;
+	struct wlan_tx_link_stats *tx_stats;
+	uint8_t user_idx;
+
+	if (soc_stats_ctx->stats_ver != RDK_LINK_STATS &&
+	    soc_stats_ctx->stats_ver != RDK_ALL_STATS)
+		return;
+
+	for (user_idx = 0; user_idx < cdp_tx_ppdu->num_users; user_idx++) {
+		ppdu_user = &cdp_tx_ppdu->user[user_idx];
+
+		STATS_CTX_LOCK_ACQUIRE(&soc_stats_ctx->tx_ctx_lock);
+		stats_ctx = cdp_peer_get_wlanstats_ctx(soc_stats_ctx->soc,
+						       cdp_tx_ppdu->vdev_id,
+						       ppdu_user->mac_addr);
+
+		if (qdf_unlikely(!stats_ctx)) {
+			qdf_warn("peer rate stats ctx is NULL, return");
+			qdf_warn("peer_mac:  " QDF_MAC_ADDR_STR,
+				 QDF_MAC_ADDR_ARRAY(ppdu_user->mac_addr));
+			STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->tx_ctx_lock);
+			continue;
+		}
+
+		tx_stats = &stats_ctx->link_metrics->tx.stats;
+
+		RATE_STATS_LOCK_ACQUIRE(&stats_ctx->link_metrics->tx.lock);
+
+		tx_stats->num_ppdus += ppdu_user->long_retries + 1;
+		tx_stats->bytes += ppdu_user->success_bytes;
+		tx_stats->mpdu_failed += ppdu_user->mpdu_failed;
+		tx_stats->mpdu_success += ppdu_user->mpdu_success;
+
+		if (ppdu_user->ppdu_type == DP_PPDU_TYPE_SU) {
+			tx_stats->phy_rate_lpf_avg_su =
+				dp_ath_rate_lpf(tx_stats->phy_rate_lpf_avg_su,
+						ppdu_user->tx_ratekbps);
+		} else if (ppdu_user->ppdu_type == DP_PPDU_TYPE_MU_OFDMA ||
+			   ppdu_user->ppdu_type == DP_PPDU_TYPE_MU_MIMO) {
+			tx_stats->phy_rate_lpf_avg_mu =
+				dp_ath_rate_lpf(tx_stats->phy_rate_lpf_avg_mu,
+						ppdu_user->tx_ratekbps);
+
+			if (ppdu_user->ppdu_type == DP_PPDU_TYPE_MU_OFDMA)
+				tx_stats->ofdma_usage++;
+
+			if (ppdu_user->ppdu_type == DP_PPDU_TYPE_MU_MIMO)
+				tx_stats->mu_mimo_usage++;
+		}
+
+		tx_stats->bw.usage_total += GET_BW_FROM_BW_ENUM(ppdu_user->bw);
+
+		if (ppdu_user->bw < BW_USAGE_MAX_SIZE) {
+			if (tx_stats->bw.usage_max < ppdu_user->bw)
+				tx_stats->bw.usage_max = ppdu_user->bw;
+			tx_stats->bw.usage_counter[ppdu_user->bw]++;
+		}
+
+		if (ppdu_user->ack_rssi_valid)
+			qdf_ewma_rx_rssi_add(&tx_stats->ack_rssi,
+					     ppdu_user->usr_ack_rssi);
+
+		RATE_STATS_LOCK_RELEASE(&stats_ctx->link_metrics->tx.lock);
+		STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->tx_ctx_lock);
+	}
+}
+
+static void
+wlan_peer_update_rx_link_stats(struct wlan_soc_rate_stats_ctx *soc_stats_ctx,
+			       struct cdp_rx_indication_ppdu *cdp_rx_ppdu)
+{
+	struct cdp_rx_stats_ppdu_user *ppdu_user;
+	struct wlan_peer_rate_stats_ctx *stats_ctx;
+	struct wlan_rx_link_stats *rx_stats;
+	uint8_t user_idx;
+
+	if (soc_stats_ctx->stats_ver != RDK_LINK_STATS &&
+	    soc_stats_ctx->stats_ver != RDK_ALL_STATS)
+		return;
+
+	for (user_idx = 0;
+	     user_idx < cdp_rx_ppdu->num_users && user_idx < CDP_MU_MAX_USERS;
+	     user_idx++) {
+		ppdu_user = &cdp_rx_ppdu->user[user_idx];
+
+		STATS_CTX_LOCK_ACQUIRE(&soc_stats_ctx->rx_ctx_lock);
+		stats_ctx = cdp_peer_get_wlanstats_ctx(soc_stats_ctx->soc,
+						       ppdu_user->vdev_id,
+						       ppdu_user->mac_addr);
+
+		if (qdf_unlikely(!stats_ctx)) {
+			qdf_warn("peer rate stats ctx is NULL, return");
+			qdf_warn("peer_mac:  " QDF_MAC_ADDR_STR,
+				 QDF_MAC_ADDR_ARRAY(ppdu_user->mac_addr));
+			STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->rx_ctx_lock);
+			continue;
+		}
+
+		rx_stats = &stats_ctx->link_metrics->rx.stats;
+
+		RATE_STATS_LOCK_ACQUIRE(&stats_ctx->link_metrics->rx.lock);
+
+		rx_stats->num_ppdus++;
+		rx_stats->bytes += cdp_rx_ppdu->num_bytes;
+		rx_stats->mpdu_retries += ppdu_user->retries;
+		rx_stats->num_mpdus += ppdu_user->mpdu_cnt_fcs_ok;
+
+		if (cdp_rx_ppdu->u.ppdu_type == DP_PPDU_TYPE_SU) {
+			rx_stats->phy_rate_lpf_avg_su =
+				dp_ath_rate_lpf(rx_stats->phy_rate_lpf_avg_su,
+						cdp_rx_ppdu->rx_ratekbps);
+			qdf_ewma_rx_rssi_add(&rx_stats->su_rssi,
+					     cdp_rx_ppdu->rssi);
+		} else if (cdp_rx_ppdu->u.ppdu_type == DP_PPDU_TYPE_MU_OFDMA ||
+			   cdp_rx_ppdu->u.ppdu_type == DP_PPDU_TYPE_MU_MIMO) {
+			rx_stats->phy_rate_lpf_avg_mu =
+				dp_ath_rate_lpf(rx_stats->phy_rate_lpf_avg_mu,
+						cdp_rx_ppdu->rx_ratekbps);
+
+			if (cdp_rx_ppdu->u.ppdu_type == DP_PPDU_TYPE_MU_OFDMA)
+				rx_stats->ofdma_usage++;
+
+			if (cdp_rx_ppdu->u.ppdu_type == DP_PPDU_TYPE_MU_MIMO)
+				rx_stats->mu_mimo_usage++;
+		}
+
+		rx_stats->bw.usage_total +=
+					GET_BW_FROM_BW_ENUM(cdp_rx_ppdu->u.bw);
+
+		if (cdp_rx_ppdu->u.bw < BW_USAGE_MAX_SIZE) {
+			if (rx_stats->bw.usage_max < cdp_rx_ppdu->u.bw)
+				rx_stats->bw.usage_max = cdp_rx_ppdu->u.bw;
+			rx_stats->bw.usage_counter[cdp_rx_ppdu->u.bw]++;
+		}
+
+		RATE_STATS_LOCK_RELEASE(&stats_ctx->link_metrics->rx.lock);
+		STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->rx_ctx_lock);
+	}
+}
+
+static void
 wlan_peer_update_rx_rate_stats(struct wlan_soc_rate_stats_ctx *soc_stats_ctx,
 			       struct cdp_rx_indication_ppdu *cdp_rx_ppdu)
 {
 	struct wlan_peer_rate_stats_ctx *stats_ctx;
 	struct wlan_peer_rx_rate_stats *rx_stats;
 	struct wlan_rx_rate_stats *__rx_stats;
+	struct cdp_rx_stats_ppdu_user *ppdu_user;
 	uint8_t cache_idx;
+	uint8_t user_idx;
 	bool idx_match = false;
 
-	stats_ctx = (struct wlan_peer_rate_stats_ctx *)cdp_rx_ppdu->cookie;
-
-	if (qdf_unlikely(!stats_ctx)) {
-		qdf_warn("peer rate stats ctx is NULL, return");
-		qdf_warn("peer_mac:  " QDF_MAC_ADDR_STR,
-			 QDF_MAC_ADDR_ARRAY(cdp_rx_ppdu->mac_addr));
+	if (soc_stats_ctx->stats_ver != RDK_RATE_STATS &&
+	    soc_stats_ctx->stats_ver != RDK_ALL_STATS)
 		return;
-	}
 
-	rx_stats = &stats_ctx->rx;
+	user_idx = 0;
+	do {
+		STATS_CTX_LOCK_ACQUIRE(&soc_stats_ctx->rx_ctx_lock);
+		if (cdp_rx_ppdu->u.ppdu_type != DP_PPDU_TYPE_SU) {
+			ppdu_user = &cdp_rx_ppdu->user[user_idx];
 
-	if (qdf_unlikely(!cdp_rx_ppdu->rx_ratekbps ||
-			 cdp_rx_ppdu->rix > DP_RATE_TABLE_SIZE)) {
-		return;
-	}
-
-	RATE_STATS_LOCK_ACQUIRE(&rx_stats->lock);
-	if (qdf_likely(rx_stats->cur_rix == cdp_rx_ppdu->rix)) {
-		__rx_stats = &rx_stats->stats[rx_stats->cur_cache_idx];
-		__wlan_peer_update_rx_rate_stats(__rx_stats,
-						 cdp_rx_ppdu);
-
-		soc_stats_ctx->rxs_last_idx_cache_hit++;
-		goto done;
-	}
-
-	/* check if cache is available */
-	for (cache_idx = 0; cache_idx < WLANSTATS_CACHE_SIZE; cache_idx++) {
-		__rx_stats = &rx_stats->stats[cache_idx];
-		if ((__rx_stats->rix == INVALID_CACHE_IDX) ||
-		    (__rx_stats->rix == cdp_rx_ppdu->rix)) {
-			idx_match = true;
-			break;
+			stats_ctx =
+			cdp_peer_get_wlanstats_ctx(soc_stats_ctx->soc,
+						   ppdu_user->vdev_id,
+						   ppdu_user->mac_addr);
+		} else {
+			stats_ctx =
+			cdp_peer_get_wlanstats_ctx(soc_stats_ctx->soc,
+						   cdp_rx_ppdu->vdev_id,
+						   cdp_rx_ppdu->mac_addr);
 		}
-	}
-	/* if index matches or found empty index, update stats to that
-	 * cache index else flush cache and update stats to cache index zero
-	 */
-	if (idx_match) {
-		__wlan_peer_update_rx_rate_stats(__rx_stats,
-						 cdp_rx_ppdu);
-		rx_stats->cur_rix = cdp_rx_ppdu->rix;
-		rx_stats->cur_cache_idx = cache_idx;
-		soc_stats_ctx->rxs_cache_hit++;
-		goto done;
-	} else {
-		wlan_peer_flush_rx_rate_stats(soc_stats_ctx, stats_ctx);
-		__rx_stats = &rx_stats->stats[0];
-		__wlan_peer_update_rx_rate_stats(__rx_stats,
-						 cdp_rx_ppdu);
-		rx_stats->cur_rix = cdp_rx_ppdu->rix;
-		rx_stats->cur_cache_idx = 0;
-		soc_stats_ctx->rxs_cache_miss++;
-	}
-done:
-	RATE_STATS_LOCK_RELEASE(&rx_stats->lock);
+
+		if (qdf_unlikely(!stats_ctx)) {
+			qdf_warn("peer rate stats ctx is NULL, return");
+			qdf_warn("peer_mac:  " QDF_MAC_ADDR_STR,
+				 QDF_MAC_ADDR_ARRAY(cdp_rx_ppdu->mac_addr));
+			STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->rx_ctx_lock);
+			continue;
+		}
+
+		rx_stats = &stats_ctx->rate_stats->rx;
+
+		if (qdf_unlikely(!cdp_rx_ppdu->rx_ratekbps ||
+				 cdp_rx_ppdu->rix > DP_RATE_TABLE_SIZE)) {
+			STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->rx_ctx_lock);
+			return;
+		}
+
+		RATE_STATS_LOCK_ACQUIRE(&rx_stats->lock);
+		if (qdf_likely(rx_stats->cur_rix == cdp_rx_ppdu->rix)) {
+			__rx_stats = &rx_stats->stats[rx_stats->cur_cache_idx];
+			__wlan_peer_update_rx_rate_stats(__rx_stats,
+							 cdp_rx_ppdu);
+			soc_stats_ctx->rxs_last_idx_cache_hit++;
+			RATE_STATS_LOCK_RELEASE(&rx_stats->lock);
+			STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->rx_ctx_lock);
+			user_idx++;
+			continue;
+		}
+
+		/* check if cache is available */
+		for (cache_idx = 0; cache_idx < WLANSTATS_CACHE_SIZE; cache_idx++) {
+			__rx_stats = &rx_stats->stats[cache_idx];
+			if ((__rx_stats->rix == INVALID_CACHE_IDX) ||
+			    (__rx_stats->rix == cdp_rx_ppdu->rix)) {
+				idx_match = true;
+				break;
+			}
+		}
+		/* if index matches or found empty index, update stats to that
+		 * cache index else flush cache and update stats to cache index
+		 * zero
+		 */
+		if (idx_match) {
+			__wlan_peer_update_rx_rate_stats(__rx_stats,
+							 cdp_rx_ppdu);
+			rx_stats->cur_rix = cdp_rx_ppdu->rix;
+			rx_stats->cur_cache_idx = cache_idx;
+			soc_stats_ctx->rxs_cache_hit++;
+			RATE_STATS_LOCK_RELEASE(&rx_stats->lock);
+			STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->rx_ctx_lock);
+			user_idx++;
+			continue;
+		} else {
+			wlan_peer_flush_rx_rate_stats(soc_stats_ctx, stats_ctx);
+			__rx_stats = &rx_stats->stats[0];
+			__wlan_peer_update_rx_rate_stats(__rx_stats,
+							 cdp_rx_ppdu);
+			rx_stats->cur_rix = cdp_rx_ppdu->rix;
+			rx_stats->cur_cache_idx = 0;
+			soc_stats_ctx->rxs_cache_miss++;
+		}
+		RATE_STATS_LOCK_RELEASE(&rx_stats->lock);
+		STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->rx_ctx_lock);
+
+		user_idx++;
+	} while (user_idx < cdp_rx_ppdu->num_users &&
+		 user_idx < CDP_MU_MAX_USERS);
 }
 
 static inline void
@@ -283,6 +616,7 @@
 						      ppdu_user->mcs,
 						      ppdu_user->bw);
 	}
+
 	__tx_stats->rate = ppdu_user->tx_ratekbps;
 	__tx_stats->num_ppdus += num_ppdus;
 	__tx_stats->mpdu_attempts += mpdu_attempts;
@@ -304,13 +638,21 @@
 	uint8_t user_idx;
 	bool idx_match = false;
 
+	if (soc_stats_ctx->stats_ver != RDK_RATE_STATS &&
+	    soc_stats_ctx->stats_ver != RDK_ALL_STATS)
+		return;
+
 	for (user_idx = 0; user_idx < cdp_tx_ppdu->num_users; user_idx++) {
 		ppdu_user = &cdp_tx_ppdu->user[user_idx];
-		stats_ctx = (struct wlan_peer_rate_stats_ctx *)
-				ppdu_user->cookie;
+
+		STATS_CTX_LOCK_ACQUIRE(&soc_stats_ctx->tx_ctx_lock);
+		stats_ctx = cdp_peer_get_wlanstats_ctx(soc_stats_ctx->soc,
+						       cdp_tx_ppdu->vdev_id,
+						       ppdu_user->mac_addr);
 
 		if (qdf_unlikely(!ppdu_user->tx_ratekbps || !ppdu_user->rix ||
 				 ppdu_user->rix > DP_RATE_TABLE_SIZE)) {
+			STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->tx_ctx_lock);
 			continue;
 		}
 
@@ -318,10 +660,11 @@
 			qdf_debug("peer rate stats ctx is NULL, investigate");
 			qdf_debug("peer_mac: " QDF_MAC_ADDR_STR,
 				 QDF_MAC_ADDR_ARRAY(ppdu_user->mac_addr));
+			STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->tx_ctx_lock);
 			continue;
 		}
 
-		tx_stats = &stats_ctx->tx;
+		tx_stats = &stats_ctx->rate_stats->tx;
 		RATE_STATS_LOCK_ACQUIRE(&tx_stats->lock);
 
 		if (qdf_likely(tx_stats->cur_rix == ppdu_user->rix)) {
@@ -329,6 +672,7 @@
 			__wlan_peer_update_tx_rate_stats(__tx_stats, ppdu_user);
 			soc_stats_ctx->txs_last_idx_cache_hit++;
 			RATE_STATS_LOCK_RELEASE(&tx_stats->lock);
+			STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->tx_ctx_lock);
 			continue;
 		}
 
@@ -365,17 +709,22 @@
 			tx_stats->cur_cache_idx = 0;
 		}
 		RATE_STATS_LOCK_RELEASE(&tx_stats->lock);
+		STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->tx_ctx_lock);
 	}
 }
 
 static void
-wlan_peer_update_sojourn_stats(void *ctx,
+wlan_peer_update_sojourn_stats(struct wlan_soc_rate_stats_ctx  *soc_stats_ctx,
 			       struct cdp_tx_sojourn_stats *sojourn_stats)
 {
 	struct wlan_peer_rate_stats_ctx *stats_ctx;
 	struct wlan_peer_tx_rate_stats *tx_stats;
 	uint8_t tid;
 
+	if (soc_stats_ctx->stats_ver != RDK_RATE_STATS &&
+	    soc_stats_ctx->stats_ver != RDK_ALL_STATS)
+		return;
+
 	stats_ctx = (struct wlan_peer_rate_stats_ctx *)sojourn_stats->cookie;
 
 	if (qdf_unlikely(!stats_ctx)) {
@@ -383,7 +732,7 @@
 		return;
 	}
 
-	tx_stats = &stats_ctx->tx;
+	tx_stats = &stats_ctx->rate_stats->tx;
 
 	RATE_STATS_LOCK_ACQUIRE(&tx_stats->lock);
 	for (tid = 0; tid < CDP_DATA_TID_MAX; tid++) {
@@ -424,12 +773,14 @@
 		cdp_tx_ppdu = (struct cdp_tx_completion_ppdu *)
 					qdf_nbuf_data(nbuf);
 		wlan_peer_update_tx_rate_stats(soc_stats_ctx, cdp_tx_ppdu);
+		wlan_peer_update_tx_link_stats(soc_stats_ctx, cdp_tx_ppdu);
 		qdf_nbuf_free(nbuf);
 		break;
 	case WDI_EVENT_RX_PPDU_DESC:
 		cdp_rx_ppdu = (struct cdp_rx_indication_ppdu *)
 					qdf_nbuf_data(nbuf);
 		wlan_peer_update_rx_rate_stats(soc_stats_ctx, cdp_rx_ppdu);
+		wlan_peer_update_rx_link_stats(soc_stats_ctx, cdp_rx_ppdu);
 		qdf_nbuf_free(nbuf);
 		break;
 	case WDI_EVENT_TX_SOJOURN_STAT:
@@ -445,33 +796,71 @@
 	}
 }
 
-void wlan_peer_create_event_handler(void *pdev, enum WDI_EVENT event,
+void wlan_peer_create_event_handler(void *ctx, enum WDI_EVENT event,
 				    void *buf, uint16_t peer_id,
 				    uint32_t type)
 {
 	struct cdp_peer_cookie *peer_info;
 	struct wlan_peer_rate_stats_ctx *stats;
+	struct wlan_soc_rate_stats_ctx *soc_stats_ctx;
 	uint8_t idx;
 
+	soc_stats_ctx = ctx;
+
 	peer_info = (struct cdp_peer_cookie *)buf;
 	stats = qdf_mem_malloc(sizeof(*stats));
 	if (!stats) {
 		qdf_err("malloc failed, returning NULL");
 		return;
 	}
-	qdf_mem_zero(stats, sizeof(*stats));
-	RATE_STATS_LOCK_CREATE(&stats->tx.lock);
-	RATE_STATS_LOCK_CREATE(&stats->rx.lock);
+
+	if (soc_stats_ctx->stats_ver == RDK_RATE_STATS ||
+	    soc_stats_ctx->stats_ver == RDK_ALL_STATS) {
+		stats->rate_stats =
+			qdf_mem_malloc(sizeof(struct wlan_peer_rate_stats));
+		if (!stats->rate_stats) {
+			qdf_err("malloc failed");
+			goto peer_create_fail1;
+		}
+		RATE_STATS_LOCK_CREATE(&stats->rate_stats->tx.lock);
+		RATE_STATS_LOCK_CREATE(&stats->rate_stats->rx.lock);
+		for (idx = 0; idx < WLANSTATS_CACHE_SIZE; idx++) {
+			stats->rate_stats->tx.stats[idx].rix =
+							INVALID_CACHE_IDX;
+			stats->rate_stats->rx.stats[idx].rix =
+							INVALID_CACHE_IDX;
+		}
+	}
+
+	if (soc_stats_ctx->stats_ver == RDK_LINK_STATS ||
+	    soc_stats_ctx->stats_ver == RDK_ALL_STATS) {
+		stats->link_metrics =
+			qdf_mem_malloc(sizeof(struct wlan_peer_link_metrics));
+		if (!stats->link_metrics) {
+			qdf_err("malloc failed");
+			goto peer_create_fail2;
+		}
+		RATE_STATS_LOCK_CREATE(&stats->link_metrics->tx.lock);
+		RATE_STATS_LOCK_CREATE(&stats->link_metrics->rx.lock);
+	}
+
 	qdf_mem_copy(stats->mac_addr, peer_info->mac_addr, QDF_MAC_ADDR_SIZE);
 	stats->peer_cookie = peer_info->cookie;
 	stats->pdev_id = peer_info->pdev_id;
 
-	for (idx = 0; idx < WLANSTATS_CACHE_SIZE; idx++) {
-		stats->tx.stats[idx].rix = INVALID_CACHE_IDX;
-		stats->rx.stats[idx].rix = INVALID_CACHE_IDX;
-	}
-
 	peer_info->ctx = (void *)stats;
+	return;
+
+peer_create_fail2:
+	if (soc_stats_ctx->stats_ver == RDK_RATE_STATS ||
+	    soc_stats_ctx->stats_ver == RDK_ALL_STATS) {
+		RATE_STATS_LOCK_DESTROY(&stats->rate_stats->tx.lock);
+		RATE_STATS_LOCK_DESTROY(&stats->rate_stats->rx.lock);
+		qdf_mem_free(stats->rate_stats);
+		stats->rate_stats = NULL;
+	}
+peer_create_fail1:
+	qdf_mem_free(stats);
 }
 
 void wlan_peer_destroy_event_handler(void *ctx, enum WDI_EVENT event,
@@ -480,16 +869,31 @@
 {
 	struct cdp_peer_cookie *peer_info;
 	struct wlan_peer_rate_stats_ctx *stats;
+	struct wlan_soc_rate_stats_ctx *soc_stats_ctx;
+
+	soc_stats_ctx = ctx;
 
 	peer_info = (struct cdp_peer_cookie *)buf;
 	stats = (struct wlan_peer_rate_stats_ctx *)peer_info->ctx;
 
+	STATS_CTX_LOCK_ACQUIRE(&soc_stats_ctx->tx_ctx_lock);
+	STATS_CTX_LOCK_ACQUIRE(&soc_stats_ctx->rx_ctx_lock);
 	if (stats) {
 		wlan_peer_flush_rate_stats(ctx, stats);
-		RATE_STATS_LOCK_DESTROY(&stats->tx.lock);
-		RATE_STATS_LOCK_DESTROY(&stats->rx.lock);
+		if (stats->rate_stats) {
+			RATE_STATS_LOCK_DESTROY(&stats->rate_stats->tx.lock);
+			RATE_STATS_LOCK_DESTROY(&stats->rate_stats->rx.lock);
+			qdf_mem_free(stats->rate_stats);
+		}
+		if (stats->link_metrics) {
+			RATE_STATS_LOCK_DESTROY(&stats->link_metrics->tx.lock);
+			RATE_STATS_LOCK_DESTROY(&stats->link_metrics->rx.lock);
+			qdf_mem_free(stats->link_metrics);
+		}
 		qdf_mem_free(stats);
 		qdf_info("DEBUG DEiniitialized rate stats");
 	}
+	STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->rx_ctx_lock);
+	STATS_CTX_LOCK_RELEASE(&soc_stats_ctx->tx_ctx_lock);
 }
 #endif
diff --git a/dp/wifi3.0/dp_full_mon.c b/dp/wifi3.0/dp_full_mon.c
index f01ff8c..3a11d92 100644
--- a/dp/wifi3.0/dp_full_mon.c
+++ b/dp/wifi3.0/dp_full_mon.c
@@ -268,12 +268,14 @@
  * to upper layer stack
  *
  * @soc: DP soc handle
+ * @pdev: pdev
  * @mac_id: lmac id
  */
 static inline QDF_STATUS
-dp_rx_monitor_deliver_ppdu(struct dp_soc *soc, uint32_t mac_id)
+dp_rx_monitor_deliver_ppdu(struct dp_soc *soc,
+			   struct dp_pdev *pdev,
+			   uint32_t mac_id)
 {
-	struct dp_pdev *pdev = dp_get_pdev_for_lmac_id(soc, mac_id);
 	struct dp_mon_mpdu *mpdu = NULL;
 	struct dp_mon_mpdu *temp_mpdu = NULL;
 
@@ -311,6 +313,7 @@
  * status ring.
  *
  * @soc: DP soc handle
+ * @pdev: pdev
  * @int_ctx: interrupt context
  * @mac_id: mac id on which interrupt is received
  * @quota: number of status ring entries to be reaped
@@ -318,12 +321,12 @@
  */
 static inline uint32_t
 dp_rx_mon_reap_status_ring(struct dp_soc *soc,
+			   struct dp_pdev *pdev,
 			   struct dp_intr *int_ctx,
 			   uint32_t mac_id,
 			   uint32_t quota,
 			   struct hal_rx_mon_desc_info *desc_info)
 {
-	struct dp_pdev *pdev = dp_get_pdev_for_lmac_id(soc, mac_id);
 	uint8_t status_buf_count;
 	uint32_t work_done = 0;
 	enum dp_mon_reap_status status;
@@ -400,6 +403,7 @@
  * and returns link descriptor to HW (WBM)
  *
  * @soc: DP soc handle
+ * @pdev: pdev
  * @mac_id: lmac id
  * @ring_desc: SW monitor ring desc
  * @head_msdu: nbuf pointing to first msdu in a chain
@@ -410,25 +414,26 @@
  * Return: number of reaped buffers
  */
 static inline uint32_t
-dp_rx_mon_mpdu_reap(struct dp_soc *soc, uint32_t mac_id, void *ring_desc,
-		    qdf_nbuf_t *head_msdu, qdf_nbuf_t *tail_msdu,
+dp_rx_mon_mpdu_reap(struct dp_soc *soc, struct dp_pdev *pdev, uint32_t mac_id,
+		    void *ring_desc, qdf_nbuf_t *head_msdu,
+		    qdf_nbuf_t *tail_msdu,
 		    union dp_rx_desc_list_elem_t **head_desc,
 		    union dp_rx_desc_list_elem_t **tail_desc)
 {
-	struct dp_pdev *pdev = dp_get_pdev_for_lmac_id(soc, mac_id);
 	struct dp_rx_desc *rx_desc = NULL;
 	struct hal_rx_msdu_list msdu_list;
 	uint32_t rx_buf_reaped = 0;
 	uint16_t num_msdus = 0, msdu_index, rx_hdr_tlv_len, l3_hdr_pad;
 	uint32_t total_frag_len = 0, frag_len = 0;
 	bool drop_mpdu = false;
-	bool msdu_frag = false;
+	bool msdu_frag = false, is_first_msdu = true, is_frag_non_raw = false;
 	void *link_desc_va;
 	uint8_t *rx_tlv_hdr;
 	qdf_nbuf_t msdu = NULL, last_msdu = NULL;
 	uint32_t rx_link_buf_info[HAL_RX_BUFFINFO_NUM_DWORDS];
 	struct hal_rx_mon_desc_info *desc_info;
 	uint16_t prev_ppdu_id;
+	struct rx_desc_pool *rx_desc_pool = NULL;
 
 	desc_info = pdev->mon_desc;
 
@@ -478,24 +483,43 @@
 
 			qdf_assert_always(rx_desc);
 
-			msdu = rx_desc->nbuf;
+			msdu = DP_RX_MON_GET_NBUF_FROM_DESC(rx_desc);
 
 			if (rx_desc->unmapped == 0) {
-				qdf_nbuf_unmap_single(soc->osdev,
-						      msdu,
-						      QDF_DMA_FROM_DEVICE);
+				rx_desc_pool = dp_rx_get_mon_desc_pool(
+					soc, mac_id, pdev->pdev_id);
+				dp_rx_mon_buffer_unmap(soc, rx_desc,
+						       rx_desc_pool->buf_size);
+
 				rx_desc->unmapped = 1;
 			}
 
 			if (drop_mpdu) {
-				qdf_nbuf_free(msdu);
+				dp_rx_mon_buffer_free(rx_desc);
 				msdu = NULL;
 				desc_info->msdu_count--;
 				goto next_msdu;
 			}
 
-			rx_tlv_hdr = qdf_nbuf_data(msdu);
+			rx_tlv_hdr = dp_rx_mon_get_buffer_data(rx_desc);
 
+			if (is_first_msdu) {
+				if (dp_rx_mon_alloc_parent_buffer(head_msdu)
+				    != QDF_STATUS_SUCCESS) {
+					DP_STATS_INC(pdev,
+						     replenish.nbuf_alloc_fail,
+						     1);
+					qdf_frag_free(rx_tlv_hdr);
+					QDF_TRACE(QDF_MODULE_ID_DP,
+						  QDF_TRACE_LEVEL_DEBUG,
+						  "[%s] failed to allocate parent buffer to hold all frag",
+						  __func__);
+					drop_mpdu = true;
+					desc_info->msdu_count--;
+					goto next_msdu;
+				}
+				is_first_msdu = false;
+			}
 			if (hal_rx_desc_is_first_msdu(soc->hal_soc,
 						      rx_tlv_hdr))
 				hal_rx_mon_hw_desc_get_mpdu_status(soc->hal_soc,
@@ -508,23 +532,16 @@
 			 *   b. calculate the number of fragmented buffers for
 			 *      a msdu and decrement one msdu_count
 			 */
-			if (msdu_list.msdu_info[msdu_index].msdu_flags
-			    & HAL_MSDU_F_MSDU_CONTINUATION) {
-				if (!msdu_frag) {
-					total_frag_len = msdu_list.msdu_info[msdu_index].msdu_len;
-					msdu_frag = true;
-				}
-				dp_mon_adjust_frag_len(&total_frag_len,
-						       &frag_len);
-			} else {
-				if (msdu_frag)
-					dp_mon_adjust_frag_len(&total_frag_len,
-							       &frag_len);
-				else
-					frag_len = msdu_list.msdu_info[msdu_index].msdu_len;
-				msdu_frag = false;
+			dp_rx_mon_parse_desc_buffer(soc,
+					&(msdu_list.msdu_info[msdu_index]),
+						 &msdu_frag,
+						 &total_frag_len,
+						 &frag_len,
+						 &l3_hdr_pad,
+						 rx_tlv_hdr,
+						 &is_frag_non_raw, rx_tlv_hdr);
+			if (!msdu_frag)
 				desc_info->msdu_count--;
-			}
 
 			rx_hdr_tlv_len = SIZE_OF_MONITOR_TLV;
 
@@ -535,9 +552,6 @@
 			 * header begins.
 			 */
 
-			l3_hdr_pad = hal_rx_msdu_end_l3_hdr_padding_get(
-								soc->hal_soc,
-								rx_tlv_hdr);
 
 			/*******************************************************
 			 *                    RX_PACKET                        *
@@ -546,17 +560,14 @@
 			 * ----------------------------------------------------*
 			 ******************************************************/
 
-			qdf_nbuf_set_pktlen(msdu,
-					    rx_hdr_tlv_len +
-					    l3_hdr_pad +
-					    frag_len);
+			dp_rx_mon_buffer_set_pktlen(msdu,
+						    rx_hdr_tlv_len +
+						    l3_hdr_pad +
+						    frag_len);
 
-			if (head_msdu && !*head_msdu)
-				*head_msdu = msdu;
-			else if (last_msdu)
-				qdf_nbuf_set_next(last_msdu, msdu);
-
-			last_msdu = msdu;
+			dp_rx_mon_add_msdu_to_list(head_msdu, msdu, &last_msdu,
+						   rx_tlv_hdr, frag_len,
+						   l3_hdr_pad);
 
 next_msdu:
 			rx_buf_reaped++;
@@ -592,10 +603,8 @@
 	}
 	pdev->rx_mon_stats.dest_mpdu_done++;
 
-	if (last_msdu)
-		qdf_nbuf_set_next(last_msdu, NULL);
-
-	*tail_msdu = msdu;
+	dp_rx_mon_init_tail_msdu(msdu, last_msdu, tail_msdu);
+	dp_rx_mon_remove_raw_frame_fcs_len(head_msdu);
 
 	return rx_buf_reaped;
 }
@@ -701,7 +710,7 @@
 
 		work_done += dp_rx_mon_status_process(soc, int_ctx, mac_id,
 				desc_info->status_buf_count);
-		dp_rx_monitor_deliver_ppdu(soc, mac_id);
+		dp_rx_monitor_deliver_ppdu(soc, pdev, mac_id);
 	}
 
 	return work_done;
@@ -793,7 +802,7 @@
 			  hal_srng_dst_peek(hal_soc, mon_dest_srng))) {
 		head_msdu = NULL;
 		tail_msdu = NULL;
-		rx_bufs_reaped = dp_rx_mon_mpdu_reap(soc, mac_id,
+		rx_bufs_reaped = dp_rx_mon_mpdu_reap(soc, pdev, mac_id,
 						     ring_desc, &head_msdu,
 						     &tail_msdu, &head_desc,
 						     &tail_desc);
@@ -900,13 +909,13 @@
 		 */
 		rx_mon_stats->dest_ppdu_done++;
 
-		work_done += dp_rx_mon_reap_status_ring(soc, int_ctx, mac_id,
-							quota, desc_info);
+		work_done += dp_rx_mon_reap_status_ring(soc, pdev, int_ctx,
+						mac_id, quota, desc_info);
 		/* Deliver all MPDUs for a PPDU */
 		if (desc_info->drop_ppdu)
 			dp_rx_mon_drop_ppdu(pdev, mac_id);
 		else if (!pdev->hold_mon_dest_ring)
-			dp_rx_monitor_deliver_ppdu(soc, mac_id);
+			dp_rx_monitor_deliver_ppdu(soc, pdev, mac_id);
 
 next_entry:
 		hal_srng_dst_get_next(hal_soc, mon_dest_srng);
diff --git a/dp/wifi3.0/dp_rx_tag.c b/dp/wifi3.0/dp_rx_tag.c
index 3c30400..608eb9f 100644
--- a/dp/wifi3.0/dp_rx_tag.c
+++ b/dp/wifi3.0/dp_rx_tag.c
@@ -96,6 +96,39 @@
  * Return: void
  */
 #ifdef WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG
+
+#ifdef DP_RX_MON_MEM_FRAG
+/**
+ * dp_rx_update_proto_tag() - Update protocol tag to nbuf cb or to headroom
+ *
+ * @nbuf: QDF pkt buffer on which the protocol tag should be set
+ * @protocol_tag: Protocol tag
+ */
+static inline
+void dp_rx_update_proto_tag(qdf_nbuf_t nbuf, uint16_t protocol_tag)
+{
+	uint8_t idx;
+	uint8_t *nbuf_head = NULL;
+
+	if (qdf_nbuf_get_nr_frags(nbuf)) {
+		/* Get frag index, which was saved while restitch */
+		idx = QDF_NBUF_CB_RX_CTX_ID(nbuf);
+		nbuf_head = qdf_nbuf_head(nbuf);
+		nbuf_head += (idx * DP_RX_MON_PF_TAG_SIZE);
+
+		*((uint16_t *)nbuf_head) = protocol_tag;
+	} else {
+		qdf_nbuf_set_rx_protocol_tag(nbuf, protocol_tag);
+	}
+}
+#else
+static inline
+void dp_rx_update_proto_tag(qdf_nbuf_t nbuf, uint16_t protocol_tag)
+{
+	qdf_nbuf_set_rx_protocol_tag(nbuf, protocol_tag);
+}
+#endif
+
 void
 dp_rx_update_protocol_tag(struct dp_soc *soc, struct dp_vdev *vdev,
 			  qdf_nbuf_t nbuf, uint8_t *rx_tlv_hdr,
@@ -156,7 +189,8 @@
 	 * received protocol type.
 	 */
 	protocol_tag = pdev->rx_proto_tag_map[cce_metadata].tag;
-	qdf_nbuf_set_rx_protocol_tag(nbuf, protocol_tag);
+
+	dp_rx_update_proto_tag(nbuf, protocol_tag);
 	QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_INFO_LOW,
 		  "Seq:%u dcap:%u CCE Match:%u ProtoID:%u Tag:%u stats:%u",
 		  hal_rx_get_rx_sequence(soc->hal_soc, rx_tlv_hdr),
@@ -189,6 +223,41 @@
  * Return: void
  */
 #ifdef WLAN_SUPPORT_RX_FLOW_TAG
+
+#ifdef DP_RX_MON_MEM_FRAG
+/**
+ * dp_rx_update_flow_tags() - Update protocol tag to nbuf cb or to headroom
+ *
+ * @nbuf: QDF pkt buffer on which the protocol tag should be set
+ * @flow_tag: Flow tag
+ */
+static inline
+void dp_rx_update_flow_tags(qdf_nbuf_t nbuf, uint32_t flow_tag)
+{
+	uint8_t idx;
+	uint8_t *nbuf_head = NULL;
+	uint16_t updated_flow_tag = (uint16_t)(flow_tag & 0xFFFF);
+
+	if (qdf_nbuf_get_nr_frags(nbuf)) {
+		/* Get frag index, which was saved while restitch */
+		idx = QDF_NBUF_CB_RX_CTX_ID(nbuf);
+		nbuf_head = qdf_nbuf_head(nbuf);
+		nbuf_head += ((idx * DP_RX_MON_PF_TAG_SIZE) +
+				sizeof(uint16_t));
+
+		*((uint16_t *)nbuf_head) = updated_flow_tag;
+	} else {
+		qdf_nbuf_set_rx_flow_tag(nbuf, flow_tag);
+	}
+}
+#else
+static inline
+void dp_rx_update_flow_tags(qdf_nbuf_t nbuf, uint16_t flow_tag)
+{
+	qdf_nbuf_set_rx_flow_tag(nbuf, flow_tag);
+}
+#endif
+
 void
 dp_rx_update_flow_tag(struct dp_soc *soc, struct dp_vdev *vdev,
 		      qdf_nbuf_t nbuf, uint8_t *rx_tlv_hdr, bool update_stats)
@@ -240,7 +309,7 @@
 	fse_metadata = hal_rx_msdu_fse_metadata_get(soc->hal_soc, rx_tlv_hdr) & 0xFFFF;
 
 	/* update the skb->cb with the user-specified tag/metadata */
-	qdf_nbuf_set_rx_flow_tag(nbuf, fse_metadata);
+	dp_rx_update_flow_tags(nbuf, fse_metadata);
 
 	QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_INFO_LOW,
 		  "Seq:%u dcap:%u invalid:%u timeout:%u flow:%u tag:%u stat:%u",
diff --git a/dp/wifi3.0/dp_rx_tag.h b/dp/wifi3.0/dp_rx_tag.h
index a3b79e8..21013ce 100644
--- a/dp/wifi3.0/dp_rx_tag.h
+++ b/dp/wifi3.0/dp_rx_tag.h
@@ -22,6 +22,10 @@
 #include "dp_internal.h"
 #include "dp_types.h"
 
+#ifdef DP_RX_MON_MEM_FRAG
+#define DP_RX_MON_PF_TAG_SIZE (4)
+#endif
+
 #ifdef WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG
 /**
  * dp_update_pdev_rx_protocol_tag - Add/remove a protocol tag that should be
diff --git a/dp/wifi3.0/dp_tx_capture.c b/dp/wifi3.0/dp_tx_capture.c
index 9c4d485..9946795 100644
--- a/dp/wifi3.0/dp_tx_capture.c
+++ b/dp/wifi3.0/dp_tx_capture.c
@@ -6274,11 +6274,11 @@
 		/* initialize ppdu_desc_log to 1 */
 		ptr_log_info->ppdu_desc_log = 1;
 	} else {
-		/* if ppdu_desc log already 1 return success */
-		if (ptr_log_info->ppdu_desc_log)
+		/* if ppdu_desc log already 0 return success */
+		if (!ptr_log_info->ppdu_desc_log)
 			return QDF_STATUS_SUCCESS;
 
-		/* initialize ppdu_desc_log to 1 */
+		/* initialize ppdu_desc_log to 0 */
 		ptr_log_info->ppdu_desc_log = 0;
 	}
 	return QDF_STATUS_SUCCESS;
diff --git a/tools/linux/cfg80211_ven_cmd.h b/tools/linux/cfg80211_ven_cmd.h
index 3ae5d9c..3aa838d 100644
--- a/tools/linux/cfg80211_ven_cmd.h
+++ b/tools/linux/cfg80211_ven_cmd.h
@@ -761,6 +761,7 @@
 	IEEE80211_PARAM_OCE_TX_POWER               = 696, /* Enables tx power to be advertised as OCE attribute in Beacon and  Probe response frame */
 	IEEE80211_PARAM_OCE_IP_SUBNET_ID           = 697, /* IP subnet identifier value to be advertised as OCE attribute in Beacon and  Probe response frame */
 	IEEE80211_PARAM_OCE_ADD_ESS_RPT            = 698, /* Add ESS Report */
+	IEEE80211_PARAM_ENABLE_MSCS                = 699, /* Enable MSCS */
 	IEEE80211_PARAM_RSNX_OVERRIDE              = 700,
 #if QCA_AIRTIME_FAIRNESS
 	IEEE80211_PARAM_ATF_ENABLE_STATS           = 701,
@@ -1203,6 +1204,28 @@
 	OL_ATH_PARAM_RPT_MAX_PHY = 446,
 	/* Enable additional operating triplets in the Country IE */
 	OL_ATH_PARAM_ENABLE_ADDITIONAL_TRIPLETS = 447,
+#ifdef QCA_SUPPORT_DFS_CHAN_POSTNOL
+	/* Set the primary frequency for the AP to go back to, after NOL */
+	OL_ATH_DFS_CHAN_POSTNOL_FREQ = 448,
+	/* Set the mode for the AP to go back to, after NOL */
+	OL_ATH_DFS_CHAN_POSTNOL_MODE = 449,
+	/* Set the secondary center frequency for the post NOL channel. */
+	OL_ATH_DFS_CHAN_POSTNOL_CFREQ2 = 450,
+#endif
+	/* DCS Wideband (5-7GHz) policy */
+	OL_ATH_PARAM_DCS_WIDEBAND_POLICY = 451,
+#ifdef QCA_CBT_INSTRUMENTATION
+	/* Support get_call_map() for CBT */
+	OL_ATH_PARAM_FUNC_CALL_MAP = 452,
+#endif
+	/* Configure punctured band setting */
+	OL_ATH_PARAM_PUNCTURED_BAND = 453,
+	/* Control frame configuration for MBSSID */
+	OL_ATH_PARAM_HE_MBSSID_CTRL_FRAME_CONFIG = 454,
+	/* Max users per-PPDU for OFDMA, 16 LSBs for DL and 16 MSBs for UL */
+	OL_ATH_PARAM_OFDMA_MAX_USERS = 455,
+	/* Max users per-PPDU for MU-MIMO, 16 LSBs for DL and 16 MSBs for UL */
+	OL_ATH_PARAM_MUMIMO_MAX_USERS = 456,
 
 };
 
@@ -2167,6 +2190,8 @@
 	{"rsim_de_frmcnt",      IEEE80211_PARAM_RAWSIM_DEBUG_NUM_DECAP_FRAMES, SET_PARAM, 1},
 	{"g_rsim_de_frmcnt",    IEEE80211_PARAM_RAWSIM_DEBUG_NUM_DECAP_FRAMES, GET_PARAM, 0},
 #endif
+	{"mscs",                IEEE80211_PARAM_ENABLE_MSCS, SET_PARAM, 1},
+	{"g_mscs",              IEEE80211_PARAM_ENABLE_MSCS, GET_PARAM, 0},
 	{"setiebuf",            35828, SET_PARAM, 1},
 	{"getiebuf",            35827, GET_PARAM, 0},
 	{"dbgreq",              35832, SET_PARAM, 1},
@@ -3103,6 +3128,40 @@
 		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_ENABLE_ADDITIONAL_TRIPLETS, SET_PARAM, 1},
 	{"g_enable_additional_triplets",
 		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_ENABLE_ADDITIONAL_TRIPLETS, GET_PARAM, 0},
+#ifdef QCA_SUPPORT_DFS_CHAN_POSTNOL
+	{"setpostNOLfreq",
+		OL_ATH_PARAM_SHIFT | OL_ATH_DFS_CHAN_POSTNOL_FREQ, SET_PARAM, 1},
+	{"getpostNOLfreq",
+		OL_ATH_PARAM_SHIFT | OL_ATH_DFS_CHAN_POSTNOL_FREQ, GET_PARAM, 0},
+	{"setpostNOLmode",
+		OL_ATH_PARAM_SHIFT | OL_ATH_DFS_CHAN_POSTNOL_MODE, SET_PARAM, 1},
+	{"getpostNOLmode",
+		OL_ATH_PARAM_SHIFT | OL_ATH_DFS_CHAN_POSTNOL_MODE, SET_PARAM, 0},
+	{"setpostNOLcfreq2",
+		OL_ATH_PARAM_SHIFT | OL_ATH_DFS_CHAN_POSTNOL_CFREQ2, SET_PARAM, 1},
+	{"getpostNOLcfreq2",
+		OL_ATH_PARAM_SHIFT | OL_ATH_DFS_CHAN_POSTNOL_CFREQ2, GET_PARAM, 0},
+#endif
+	{"dcs_wideband_policy",
+		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_DCS_WIDEBAND_POLICY, SET_PARAM, 1},
+	{"get_dcs_wideband_policy",
+		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_DCS_WIDEBAND_POLICY, GET_PARAM, 0},
+#ifdef QCA_CBT_INSTRUMENTATION
+	{"get_call_map",
+		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_FUNC_CALL_MAP, SET_PARAM, 1},
+#endif
+	{"puncture_band",
+		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_PUNCTURED_BAND, SET_PARAM, 1},
+	{"get_puncture_band",
+		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_PUNCTURED_BAND, GET_PARAM, 0},
+	{"he_mbssid_ctrl_frame_config",
+		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_HE_MBSSID_CTRL_FRAME_CONFIG, SET_PARAM, 1},
+	{"g_he_mbssid_ctrl_frame_config",
+		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_HE_MBSSID_CTRL_FRAME_CONFIG, GET_PARAM, 0},
+	{"get_ofdma_usr",
+		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_OFDMA_MAX_USERS, GET_PARAM, 0},
+	{"get_mumimo_usr",
+		OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_MUMIMO_MAX_USERS, GET_PARAM, 0},
 };
 #endif
 #endif
diff --git a/tools/linux/peerstats.c b/tools/linux/peerstats.c
index 0cd1a65..952784d 100644
--- a/tools/linux/peerstats.c
+++ b/tools/linux/peerstats.c
@@ -340,6 +340,100 @@
 	return;
 }
 
+static void dp_peer_tx_link_stats_print(uint8_t *peer_mac,
+					uint64_t peer_cookie,
+					void *buffer,
+					uint32_t buffer_len)
+{
+	struct wlan_tx_link_stats *tx_stats;
+	uint8_t is_lithium;
+
+	is_lithium =  (peer_cookie & WLANSTATS_COOKIE_PLATFORM_OFFSET)
+					>> WLANSTATS_PEER_COOKIE_LSB;
+
+	if (!is_lithium) {
+		PRINT("Not supported in non-lithium platforms\n");
+		return;
+	}
+
+	if (buffer_len < sizeof(struct wlan_tx_link_stats)) {
+		PRINT("invalid buffer len, return");
+		return;
+	}
+
+	tx_stats = (struct wlan_tx_link_stats *)buffer;
+
+	PRINT("\n\n");
+	PRINT("========= PEER TX LINK QUALITY METRICS =========\n");
+	PRINT("PEER %02hhx:%02hhx:%02hhx:%02hhx%02hhx:%02hhx",
+	      peer_mac[0],
+	      peer_mac[1],
+	      peer_mac[2],
+	      peer_mac[3],
+	      peer_mac[4],
+	      peer_mac[5]);
+	PRINT("num_ppdus: %u", tx_stats->num_ppdus);
+	PRINT("bytes: %"PRIu64, tx_stats->bytes);
+	PRINT("phy_rate_actual_su: %u kbps", tx_stats->phy_rate_actual_su);
+	PRINT("phy_rate_actual_mu: %u kbps", tx_stats->phy_rate_actual_mu);
+	PRINT("ofdma_usage: %u", tx_stats->ofdma_usage);
+	PRINT("mu_mimo_usage: %u", tx_stats->mu_mimo_usage);
+	PRINT("bw_usage_avg: %u MHz", tx_stats->bw.usage_avg);
+	PRINT("bw_usage_packets: 20MHz: %u 40MHz: %u 80MHz: %u 160MHz: %u",
+	      tx_stats->bw.usage_counter[0], tx_stats->bw.usage_counter[1],
+	      tx_stats->bw.usage_counter[2], tx_stats->bw.usage_counter[3]);
+	PRINT("bw_usage_max:: %u%%", tx_stats->bw.usage_max);
+	PRINT("ack_rssi: %lu", tx_stats->ack_rssi);
+	PRINT("pkt_error_rate: %u%%", tx_stats->pkt_error_rate);
+}
+
+static void dp_peer_rx_link_stats_print(uint8_t *peer_mac,
+					uint64_t peer_cookie,
+					void *buffer,
+					uint32_t buffer_len)
+{
+	struct wlan_rx_link_stats *rx_stats;
+	uint8_t is_lithium;
+
+	is_lithium =  (peer_cookie & WLANSTATS_COOKIE_PLATFORM_OFFSET)
+					>> WLANSTATS_PEER_COOKIE_LSB;
+
+	if (!is_lithium) {
+		PRINT("Not supported in non-lithium platforms\n");
+		return;
+	}
+
+	if (buffer_len < sizeof(struct wlan_rx_link_stats)) {
+		PRINT("invalid buffer len, return");
+		return;
+	}
+
+	rx_stats = (struct wlan_rx_link_stats *)buffer;
+
+	PRINT("\n\n");
+	PRINT("========= PEER RX LINK QUALITY METRICS =========\n");
+	PRINT("PEER %02hhx:%02hhx:%02hhx:%02hhx%02hhx:%02hhx",
+	      peer_mac[0],
+	      peer_mac[1],
+	      peer_mac[2],
+	      peer_mac[3],
+	      peer_mac[4],
+	      peer_mac[5]);
+	PRINT("num_ppdus: %u", rx_stats->num_ppdus);
+	PRINT("bytes: %"PRIu64, rx_stats->bytes);
+	PRINT("phy_rate_actual_su: %u kbps", rx_stats->phy_rate_actual_su);
+	PRINT("phy_rate_actual_mu: %u kbps", rx_stats->phy_rate_actual_mu);
+	PRINT("ofdma_usage: %u", rx_stats->ofdma_usage);
+	PRINT("mu_mimo_usage: %u", rx_stats->mu_mimo_usage);
+	PRINT("bw_usage_avg: %u MHz", rx_stats->bw.usage_avg);
+	PRINT("bw_usage_packets: 20MHz: %u 40MHz: %u 80MHz: %u 160MHz: %u",
+	      rx_stats->bw.usage_counter[0], rx_stats->bw.usage_counter[1],
+	      rx_stats->bw.usage_counter[2], rx_stats->bw.usage_counter[3]);
+	PRINT("bw_usage_max: %u%%", rx_stats->bw.usage_max);
+	PRINT("su_rssi: %lu", rx_stats->su_rssi);
+	PRINT("pkt_error_rate: %u%%", rx_stats->pkt_error_rate);
+}
+
 static void dp_peer_stats_handler(uint32_t cache_type,
 				 uint8_t *peer_mac,
 				 uint64_t peer_cookie,
@@ -355,6 +449,14 @@
 		dp_peer_tx_rate_stats_print(peer_mac, peer_cookie,
 					    buffer, buffer_len);
 		break;
+	case DP_PEER_TX_LINK_STATS:
+		dp_peer_tx_link_stats_print(peer_mac, peer_cookie,
+					    buffer, buffer_len);
+		break;
+	case DP_PEER_RX_LINK_STATS:
+		dp_peer_rx_link_stats_print(peer_mac, peer_cookie,
+					    buffer, buffer_len);
+		break;
 	}
 }
 
diff --git a/umac/cp_stats/dispatcher/inc/wlan_cp_stats_ic_utils_api.h b/umac/cp_stats/dispatcher/inc/wlan_cp_stats_ic_utils_api.h
index dc1b9f1..0ab1f64 100644
--- a/umac/cp_stats/dispatcher/inc/wlan_cp_stats_ic_utils_api.h
+++ b/umac/cp_stats/dispatcher/inc/wlan_cp_stats_ic_utils_api.h
@@ -40,7 +40,7 @@
 	peer_cp_stats_##field##_dec(struct wlan_objmgr_peer *_peer, \
 				    uint64_t _val) \
 	{ \
-		ucfg_peer_cp_stats_##field##_inc(_peer, _val); \
+		ucfg_peer_cp_stats_##field##_dec(_peer, _val); \
 	} \
 	static inline void \
 	peer_cp_stats_##field##_update(struct wlan_objmgr_peer *_peer, \
diff --git a/umac/dfs/core/src/misc/dfs_zero_cac.c b/umac/dfs/core/src/misc/dfs_zero_cac.c
index fe84861..2134008 100644
--- a/umac/dfs/core/src/misc/dfs_zero_cac.c
+++ b/umac/dfs/core/src/misc/dfs_zero_cac.c
@@ -273,10 +273,12 @@
 	dfs->dfs_precac_secondary_freq_mhz = 0;
 }
 
+#if defined(ATH_SUPPORT_ZERO_CAC_DFS) && !defined(QCA_MCL_DFS_SUPPORT)
 void dfs_zero_cac_timer_detach(struct dfs_soc_priv_obj *dfs_soc_obj)
 {
 	qdf_timer_free(&dfs_soc_obj->dfs_precac_timer);
 }
+#endif
 
 int dfs_override_precac_timeout(struct wlan_dfs *dfs, int precac_timeout)
 {
@@ -514,6 +516,13 @@
 	bool ret_val = 0;
 	uint16_t cfreq;
 
+	if (!WLAN_IS_CHAN_5GHZ(chan)) {
+		dfs_debug(dfs, WLAN_DEBUG_DFS,
+			  "Channel %d not a 5GHz channel",
+			  chan->dfs_ch_ieee);
+		return 0;
+	}
+
 	if (WLAN_IS_CHAN_MODE_160(chan))
 		cfreq = chan->dfs_ch_mhz_freq_seg2;
 	else if (WLAN_IS_CHAN_MODE_165(dfs, chan))
@@ -1474,6 +1483,7 @@
 	}
 }
 
+#if defined(ATH_SUPPORT_ZERO_CAC_DFS) && !defined(QCA_MCL_DFS_SUPPORT)
 void dfs_zero_cac_timer_init(struct dfs_soc_priv_obj *dfs_soc_obj)
 {
 	dfs_soc_obj->precac_state_started = false;
@@ -1482,6 +1492,7 @@
 		       (void *)dfs_soc_obj,
 		       QDF_TIMER_TYPE_WAKE_APPS);
 }
+#endif
 
 void dfs_zero_cac_attach(struct wlan_dfs *dfs)
 {
@@ -1883,6 +1894,8 @@
 	precac_entry->center_ch_ieee =
 	utils_dfs_freq_to_chan(precac_center_freq);
 	precac_entry->bw = dfs_max_bw_info[index].dfs_max_bw;
+	/* non_dfs_subch_count will be updated once the channels are marked. */
+	precac_entry->non_dfs_subch_count = 0;
 	precac_entry->dfs = dfs;
 	status =
 	    dfs_create_precac_tree_for_freq(dfs,
@@ -1910,6 +1923,8 @@
 	precac_entry->center_ch_ieee =
 		utils_dfs_freq_to_chan(precac_entry->center_ch_freq);
 	precac_entry->bw = DFS_CHWIDTH_160_VAL;
+	/* non_dfs_subch_count will be updated once the channels are marked. */
+	precac_entry->non_dfs_subch_count = 0;
 	precac_entry->dfs = dfs;
 	dfs_insert_node_into_bstree_for_freq(&precac_entry->tree_root,
 					     RESTRICTED_80P80_CHAN_CENTER_FREQ,
@@ -1934,6 +1949,35 @@
 	return status;
 }
 
+/**
+ * dfs_update_non_dfs_subchannel_count() - API to update the preCAC entry
+ * with the given non DFS subchannel count.
+ * @dfs:       Pointer to DFS object.
+ * @frequency: Frequency whose corresponding preCAC entry needs to be updated.
+ * @count:     Non DFS subchannel count for the preCAC entry.
+ */
+static void
+dfs_update_non_dfs_subchannel_count(struct wlan_dfs *dfs,
+				    qdf_freq_t frequency,
+				    uint8_t count)
+{
+	struct dfs_precac_entry *precac_entry = NULL, *tmp_precac_entry = NULL;
+
+	PRECAC_LIST_LOCK(dfs);
+	TAILQ_FOREACH_SAFE(precac_entry,
+			   &dfs->dfs_precac_list,
+			   pe_list,
+			   tmp_precac_entry) {
+		if (IS_WITHIN_RANGE_STRICT(frequency,
+					   precac_entry->center_ch_freq,
+					   (precac_entry->bw/2))) {
+			precac_entry->non_dfs_subch_count = count;
+			break;
+		}
+	}
+	PRECAC_LIST_UNLOCK(dfs);
+}
+
 static void
 dfs_mark_non_dfs_as_precac_done(struct wlan_dfs *dfs,
 				uint16_t dfs_pri_ch_freq,
@@ -1960,6 +2004,9 @@
 					      ichan->dfs_ch_mhz_freq_seg1,
 					      0,
 					      CH_WIDTH_80MHZ);
+		dfs_update_non_dfs_subchannel_count(dfs,
+						    ichan->dfs_ch_mhz_freq_seg1,
+						    N_SUBCHANS_FOR_80BW);
 		PRECAC_LIST_LOCK(dfs);
 	} else if (!WLAN_IS_CHAN_DFS_CFREQ2(ichan)) {
 		PRECAC_LIST_UNLOCK(dfs);
@@ -1967,6 +2014,9 @@
 					      ichan->dfs_ch_mhz_freq_seg2,
 					      0,
 					      CH_WIDTH_80MHZ);
+		dfs_update_non_dfs_subchannel_count(dfs,
+						    ichan->dfs_ch_mhz_freq_seg2,
+						    N_SUBCHANS_FOR_80BW);
 		PRECAC_LIST_LOCK(dfs);
 	}
 }
@@ -2209,14 +2259,12 @@
 {
 	struct dfs_soc_priv_obj *dfs_soc_obj;
 
-	dfs_soc_obj = wlan_objmgr_psoc_get_comp_private_obj(psoc,
-							    WLAN_UMAC_COMP_DFS);
+	dfs_soc_obj = dfs->dfs_soc_obj;
 	dfs->dfs_psoc_idx = dfs_soc_obj->num_dfs_privs;
 	dfs_info(dfs, WLAN_DEBUG_DFS_ALWAYS,
 		 "dfs->dfs_psoc_idx: %d ", dfs->dfs_psoc_idx);
 	dfs_soc_obj->dfs_priv[dfs_soc_obj->num_dfs_privs].dfs = dfs;
 	dfs_soc_obj->num_dfs_privs++;
-	dfs->dfs_soc_obj = dfs_soc_obj;
 
 	dfs_info(dfs, WLAN_DEBUG_DFS_ALWAYS, "dfs_soc_obj->num_dfs_privs: %d ",
 		 dfs_soc_obj->num_dfs_privs);
@@ -2910,6 +2958,54 @@
 	}
 }
 
+/**
+ * dfs_is_precac_completed_count_non_zero() - API to find if the preCAC
+ * completed channels count is zero/non_zero.
+ * @dfs: Pointer to DFS object.
+ *
+ * Return true, if there exists atleast one node/subchannel in the preCAC list
+ * that is CAC done, else return false.
+ */
+static bool
+dfs_is_precac_completed_count_non_zero(struct wlan_dfs *dfs)
+{
+	struct dfs_precac_entry *precac_entry = NULL;
+
+	PRECAC_LIST_LOCK(dfs);
+	if (!TAILQ_EMPTY(&dfs->dfs_precac_list)) {
+		TAILQ_FOREACH(precac_entry,
+			      &dfs->dfs_precac_list,
+			      pe_list) {
+			/* Find if the tree root has any preCAC channels
+			 * that is CAC done.
+			 */
+			if (!precac_entry->tree_root->n_caced_subchs)
+				continue;
+			if (abs(precac_entry->tree_root->n_caced_subchs -
+			    precac_entry->non_dfs_subch_count)) {
+				PRECAC_LIST_UNLOCK(dfs);
+				return true;
+			}
+		}
+	}
+	PRECAC_LIST_UNLOCK(dfs);
+
+	return false;
+}
+
+enum precac_status_for_chan
+dfs_precac_status_for_channel(struct wlan_dfs *dfs,
+			      struct dfs_channel *deschan)
+{
+	if (!dfs_is_precac_completed_count_non_zero(dfs))
+		return DFS_NO_PRECAC_COMPLETED_CHANS;
+
+	if (dfs_is_precac_done(dfs, deschan))
+		return DFS_PRECAC_COMPLETED_CHAN;
+
+	return DFS_PRECAC_REQUIRED_CHAN;
+}
+
 void dfs_print_precaclists(struct wlan_dfs *dfs)
 {
 	struct dfs_precac_entry *tmp_precac_entry;
@@ -3542,6 +3638,12 @@
 		if (WLAN_IS_CHAN_MODE_165(dfs, dfs->dfs_curchan))
 			flags |= DFS_RANDOM_CH_FLAG_RESTRICTED_80P80_ENABLED;
 
+		if (!WLAN_IS_CHAN_5GHZ(dfs->dfs_curchan)) {
+			dfs_debug(dfs, WLAN_DEBUG_DFS_AGILE,
+				  "Current operating channel not a 5G channel");
+			goto exit;
+		}
+
 		dfs_fill_des_rcac_chan_params(dfs,
 					      &nxt_chan_params,
 					      curchan_chwidth);
@@ -4977,3 +5079,96 @@
 }
 #endif
 #endif
+
+#if defined(QCA_SUPPORT_AGILE_DFS) || defined(ATH_SUPPORT_ZERO_CAC_DFS) || \
+    defined(QCA_SUPPORT_ADFS_RCAC)
+QDF_STATUS
+dfs_process_radar_ind_on_agile_chan(struct wlan_dfs *dfs,
+				    struct radar_found_info *radar_found)
+{
+	uint32_t freq_center;
+	uint32_t radarfound_freq;
+	QDF_STATUS status;
+	uint8_t num_channels;
+	uint16_t freq_list[NUM_CHANNELS_160MHZ];
+	uint16_t nol_freq_list[NUM_CHANNELS_160MHZ];
+	bool is_radar_source_agile =
+		(radar_found->detector_id == dfs_get_agile_detector_id(dfs));
+
+	if ((!dfs_is_agile_precac_enabled(dfs) &&
+	     !dfs_is_agile_rcac_enabled(dfs)) ||
+	     !dfs->dfs_agile_precac_freq_mhz) {
+		dfs_err(dfs, WLAN_DEBUG_DFS,
+			"radar on Agile detector when ADFS is not running");
+		status = QDF_STATUS_E_FAILURE;
+		goto exit;
+	}
+
+	dfs_compute_radar_found_cfreq(dfs, radar_found, &freq_center);
+	radarfound_freq = freq_center + radar_found->freq_offset;
+	if (is_radar_source_agile)
+		dfs_info(dfs, WLAN_DEBUG_DFS_ALWAYS,
+			 "Radar found on Agile detector freq=%d radar freq=%d",
+			 freq_center, radarfound_freq);
+	else if (radar_found->segment_id == SEG_ID_SECONDARY)
+		dfs_info(dfs, WLAN_DEBUG_DFS_ALWAYS,
+			 "Radar found on second segment.Radarfound Freq=%d MHz.Secondary Chan cfreq=%d MHz.",
+			 radarfound_freq, freq_center);
+	utils_dfs_deliver_event(dfs->dfs_pdev_obj, radarfound_freq,
+				WLAN_EV_RADAR_DETECTED);
+	if (!dfs->dfs_use_nol) {
+		dfs_reset_bangradar(dfs);
+		dfs_send_csa_to_current_chan(dfs);
+		status = QDF_STATUS_SUCCESS;
+		goto exit;
+	}
+
+	 num_channels = dfs_find_radar_affected_channels(dfs,
+			 radar_found,
+			 freq_list,
+			 freq_center);
+
+	 dfs_reset_bangradar(dfs);
+
+	 status = dfs_radar_add_channel_list_to_nol_for_freq(dfs,
+			 freq_list,
+			 nol_freq_list,
+			 &num_channels);
+	 if (QDF_IS_STATUS_ERROR(status)) {
+		dfs_err(dfs, WLAN_DEBUG_DFS,
+			"radar event received on invalid channel");
+		goto exit;
+	 }
+	 /*
+	  * If precac is running and the radar found in secondary
+	  * VHT80 mark the channel as radar and add to NOL list.
+	  * Otherwise random channel selection can choose this
+	  * channel.
+	  */
+	 dfs_debug(dfs, WLAN_DEBUG_DFS,
+			 "found_on_second=%d is_pre=%d",
+			 dfs->is_radar_found_on_secondary_seg,
+			 dfs_is_precac_timer_running(dfs));
+	 /*
+	  * Even if radar found on primary, we need to mark the channel as NOL
+	  * in preCAC list. The preCAC list also maintains the current CAC
+	  * channels as part of pre-cleared DFS. Hence call the API
+	  * to mark channels as NOL irrespective of preCAC being enabled or not.
+	  */
+
+	 dfs_debug(dfs, WLAN_DEBUG_DFS,
+			 "%s: %d Radar found on dfs detector:%d",
+			 __func__, __LINE__, radar_found->detector_id);
+	 dfs_mark_precac_nol_for_freq(dfs,
+			 dfs->is_radar_found_on_secondary_seg,
+			 radar_found->detector_id,
+			 nol_freq_list,
+			 num_channels);
+	if (is_radar_source_agile)
+		utils_dfs_agile_sm_deliver_evt(dfs->dfs_pdev_obj,
+					       DFS_AGILE_SM_EV_ADFS_RADAR);
+
+exit:
+	return status;
+}
+#endif
diff --git a/umac/mlme/vdev_mgr/core/src/vdev_mlme_sm_actions.c b/umac/mlme/vdev_mgr/core/src/vdev_mlme_sm_actions.c
index 459979c..5163ce5 100644
--- a/umac/mlme/vdev_mgr/core/src/vdev_mlme_sm_actions.c
+++ b/umac/mlme/vdev_mgr/core/src/vdev_mlme_sm_actions.c
@@ -220,6 +220,8 @@
 		    !wlan_util_map_is_any_index_set(
 				pdev_mlme->restart_send_vdev_bmap,
 				sizeof(pdev_mlme->restart_send_vdev_bmap))) {
+			mlme_err("Clear MVR bit for Pdev %d",
+				 wlan_objmgr_pdev_get_pdev_id(pdev));
 			wlan_pdev_mlme_op_clear
 					(pdev,
 					 WLAN_PDEV_OP_RESTART_INPROGRESS);
@@ -359,6 +361,8 @@
 	if (!wlan_util_map_is_any_index_set(
 			pdev_mlme->restart_pend_vdev_bmap,
 			sizeof(pdev_mlme->restart_pend_vdev_bmap))) {
+		mlme_err("Sending MVR for Pdev %d",
+			 wlan_objmgr_pdev_get_pdev_id(pdev));
 		wlan_pdev_mlme_op_clear(pdev, WLAN_PDEV_OP_MBSSID_RESTART);
 		wlan_pdev_mlme_op_clear(pdev, WLAN_PDEV_OP_RESTART_INPROGRESS);