diff --git a/doc/guides/rel_notes/release_23_03.rst b/doc/guides/rel_notes/release_23_03.rst
index b8c5b68d6c..0ce6fbf8ee 100644
--- a/doc/guides/rel_notes/release_23_03.rst
+++ b/doc/guides/rel_notes/release_23_03.rst
@@ -55,6 +55,9 @@ New Features
      Also, make sure to start the actual text at the margin.
      =======================================================
 
+* **Updated Corigine nfp driver.**
+
+  * Added support for meter options.
 
 Removed Items
 -------------
diff --git a/drivers/net/nfp/flower/nfp_flower_cmsg.c b/drivers/net/nfp/flower/nfp_flower_cmsg.c
index eaddd6c52c..430dd9c316 100644
--- a/drivers/net/nfp/flower/nfp_flower_cmsg.c
+++ b/drivers/net/nfp/flower/nfp_flower_cmsg.c
@@ -525,3 +525,32 @@ nfp_flower_cmsg_qos_delete(struct nfp_app_fw_flower *app_fw_flower,
 
 	return 0;
 }
+
+int
+nfp_flower_cmsg_qos_stats(struct nfp_app_fw_flower *app_fw_flower,
+		struct nfp_cfg_head *head)
+{
+	char *msg;
+	uint16_t cnt;
+	uint32_t len;
+	struct rte_mbuf *mbuf;
+
+	mbuf = rte_pktmbuf_alloc(app_fw_flower->ctrl_pktmbuf_pool);
+	if (mbuf == NULL) {
+		PMD_DRV_LOG(DEBUG, "Failed to alloc mbuf for qos stats");
+		return -ENOMEM;
+	}
+
+	len = sizeof(struct nfp_cfg_head);
+	msg = nfp_flower_cmsg_init(mbuf, NFP_FLOWER_CMSG_TYPE_QOS_STATS, len);
+	rte_memcpy(msg, head, len);
+
+	cnt = nfp_flower_ctrl_vnic_xmit(app_fw_flower, mbuf);
+	if (cnt == 0) {
+		PMD_DRV_LOG(ERR, "Send cmsg through ctrl vnic failed.");
+		rte_pktmbuf_free(mbuf);
+		return -EIO;
+	}
+
+	return 0;
+}
diff --git a/drivers/net/nfp/flower/nfp_flower_cmsg.h b/drivers/net/nfp/flower/nfp_flower_cmsg.h
index 1894c454d3..2f4cd68730 100644
--- a/drivers/net/nfp/flower/nfp_flower_cmsg.h
+++ b/drivers/net/nfp/flower/nfp_flower_cmsg.h
@@ -928,5 +928,7 @@ int nfp_flower_cmsg_qos_add(struct nfp_app_fw_flower *app_fw_flower,
 		struct nfp_profile_conf *conf);
 int nfp_flower_cmsg_qos_delete(struct nfp_app_fw_flower *app_fw_flower,
 		struct nfp_profile_conf *conf);
+int nfp_flower_cmsg_qos_stats(struct nfp_app_fw_flower *app_fw_flower,
+		struct nfp_cfg_head *head);
 
 #endif /* _NFP_CMSG_H_ */
diff --git a/drivers/net/nfp/flower/nfp_flower_ctrl.c b/drivers/net/nfp/flower/nfp_flower_ctrl.c
index c545007d0c..03a2e2e622 100644
--- a/drivers/net/nfp/flower/nfp_flower_ctrl.c
+++ b/drivers/net/nfp/flower/nfp_flower_ctrl.c
@@ -249,7 +249,32 @@ nfp_flower_cmsg_rx_stats(struct nfp_flow_priv *flow_priv,
 }
 
 static void
-nfp_flower_cmsg_rx(struct nfp_flow_priv *flow_priv,
+nfp_flower_cmsg_rx_qos_stats(struct nfp_mtr_priv *mtr_priv,
+		struct rte_mbuf *mbuf)
+{
+	char *msg;
+	uint32_t profile_id;
+	struct nfp_mtr *mtr;
+	struct nfp_mtr_stats_reply *mtr_stats;
+
+	msg = rte_pktmbuf_mtod(mbuf, char *) + NFP_FLOWER_CMSG_HLEN;
+
+	mtr_stats = (struct nfp_mtr_stats_reply *)msg;
+	profile_id = rte_be_to_cpu_32(mtr_stats->head.profile_id);
+	mtr = nfp_mtr_find_by_profile_id(mtr_priv, profile_id);
+	if (mtr == NULL)
+		return;
+
+	rte_spinlock_lock(&mtr_priv->mtr_stats_lock);
+	mtr->mtr_stats.curr.pass_bytes = rte_be_to_cpu_64(mtr_stats->pass_bytes);
+	mtr->mtr_stats.curr.pass_pkts = rte_be_to_cpu_64(mtr_stats->pass_pkts);
+	mtr->mtr_stats.curr.drop_bytes = rte_be_to_cpu_64(mtr_stats->drop_bytes);
+	mtr->mtr_stats.curr.drop_pkts = rte_be_to_cpu_64(mtr_stats->drop_pkts);
+	rte_spinlock_unlock(&mtr_priv->mtr_stats_lock);
+}
+
+static void
+nfp_flower_cmsg_rx(struct nfp_app_fw_flower *app_fw_flower,
 		struct rte_mbuf **pkts_burst,
 		uint16_t count)
 {
@@ -257,8 +282,13 @@ nfp_flower_cmsg_rx(struct nfp_flow_priv *flow_priv,
 	char *meta;
 	uint32_t meta_type;
 	uint32_t meta_info;
+	struct nfp_mtr_priv *mtr_priv;
+	struct nfp_flow_priv *flow_priv;
 	struct nfp_flower_cmsg_hdr *cmsg_hdr;
 
+	mtr_priv = app_fw_flower->mtr_priv;
+	flow_priv = app_fw_flower->flow_priv;
+
 	for (i = 0; i < count; i++) {
 		meta = rte_pktmbuf_mtod(pkts_burst[i], char *);
 
@@ -282,21 +312,38 @@ nfp_flower_cmsg_rx(struct nfp_flow_priv *flow_priv,
 		if (cmsg_hdr->type == NFP_FLOWER_CMSG_TYPE_FLOW_STATS) {
 			/* We need to deal with stats updates from HW asap */
 			nfp_flower_cmsg_rx_stats(flow_priv, pkts_burst[i]);
+		} else if (cmsg_hdr->type == NFP_FLOWER_CMSG_TYPE_QOS_STATS) {
+			/* Handle meter stats */
+			nfp_flower_cmsg_rx_qos_stats(mtr_priv, pkts_burst[i]);
 		}
 
 		rte_pktmbuf_free(pkts_burst[i]);
 	}
 }
 
+static void
+nfp_mtr_stats_request(struct nfp_app_fw_flower *app_fw_flower)
+{
+	struct nfp_mtr *mtr;
+
+	LIST_FOREACH(mtr, &app_fw_flower->mtr_priv->mtrs, next)
+		(void)nfp_flower_cmsg_qos_stats(app_fw_flower, &mtr->mtr_profile->conf.head);
+}
+
 void
 nfp_flower_ctrl_vnic_poll(struct nfp_app_fw_flower *app_fw_flower)
 {
 	uint16_t count;
+	uint64_t cur_tsc;
+	uint64_t drain_tsc;
+	uint64_t pre_tsc = 0;
 	struct nfp_net_rxq *rxq;
 	struct nfp_net_hw *ctrl_hw;
 	struct rte_eth_dev *ctrl_eth_dev;
 	struct rte_mbuf *pkts_burst[MAX_PKT_BURST];
 
+	drain_tsc = app_fw_flower->mtr_priv->drain_tsc;
+
 	ctrl_hw = app_fw_flower->ctrl_hw;
 	ctrl_eth_dev = ctrl_hw->eth_dev;
 
@@ -308,7 +355,13 @@ nfp_flower_ctrl_vnic_poll(struct nfp_app_fw_flower *app_fw_flower)
 		if (count != 0) {
 			app_fw_flower->ctrl_vnic_rx_count += count;
 			/* Process cmsgs here */
-			nfp_flower_cmsg_rx(app_fw_flower->flow_priv, pkts_burst, count);
+			nfp_flower_cmsg_rx(app_fw_flower, pkts_burst, count);
+		}
+
+		cur_tsc = rte_rdtsc();
+		if (unlikely(cur_tsc - pre_tsc > drain_tsc)) {
+			nfp_mtr_stats_request(app_fw_flower);
+			pre_tsc = cur_tsc;
 		}
 	}
 }
diff --git a/drivers/net/nfp/nfp_mtr.c b/drivers/net/nfp/nfp_mtr.c
index 1df7103417..f77381dcb3 100644
--- a/drivers/net/nfp/nfp_mtr.c
+++ b/drivers/net/nfp/nfp_mtr.c
@@ -553,6 +553,48 @@ nfp_mtr_find_by_mtr_id(struct nfp_mtr_priv *priv, uint32_t mtr_id)
 	return mtr;
 }
 
+struct nfp_mtr *
+nfp_mtr_find_by_profile_id(struct nfp_mtr_priv *priv, uint32_t profile_id)
+{
+	struct nfp_mtr *mtr;
+
+	LIST_FOREACH(mtr, &priv->mtrs, next)
+		if (mtr->mtr_profile->profile_id == profile_id)
+			break;
+
+	return mtr;
+}
+
+static int
+nfp_mtr_stats_mask_validate(uint64_t stats_mask, struct rte_mtr_error *error)
+{
+	if ((stats_mask & RTE_MTR_STATS_N_PKTS_YELLOW) != 0) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_MTR_PARAMS,
+				NULL, "RTE_MTR_STATS_N_PKTS_YELLOW not support");
+	}
+
+	if ((stats_mask & RTE_MTR_STATS_N_PKTS_RED) != 0) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_MTR_PARAMS,
+				NULL, "RTE_MTR_STATS_N_PKTS_RED not support");
+	}
+
+	if ((stats_mask & RTE_MTR_STATS_N_BYTES_YELLOW) != 0) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_MTR_PARAMS,
+				NULL, "RTE_MTR_STATS_N_BYTES_YELLOW not support");
+	}
+
+	if ((stats_mask & RTE_MTR_STATS_N_BYTES_RED) != 0) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_MTR_PARAMS,
+				NULL, "RTE_MTR_STATS_N_BYTES_RED not support");
+	}
+
+	return 0;
+}
+
 static int
 nfp_mtr_validate(uint32_t meter_id,
 		struct rte_mtr_params *params,
@@ -578,7 +620,7 @@ nfp_mtr_validate(uint32_t meter_id,
 				NULL, "Feature use_prev_mtr_color not support");
 	}
 
-	return 0;
+	return nfp_mtr_stats_mask_validate(params->stats_mask, error);
 }
 
 static void
@@ -599,6 +641,7 @@ nfp_mtr_config(uint32_t mtr_id,
 
 	mtr->mtr_profile = mtr_profile;
 	mtr->mtr_policy = mtr_policy;
+	mtr->stats_mask = params->stats_mask;
 }
 
 /**
@@ -886,6 +929,119 @@ nfp_mtr_profile_update(struct rte_eth_dev *dev,
 	return 0;
 }
 
+/**
+ * Callback to update meter stats mask.
+ *
+ * @param[in] dev
+ *   Pointer to Ethernet device.
+ * @param[in] mtr_id
+ *   Meter id.
+ * @param[in] stats_mask
+ *   To be updated stats_mask.
+ * @param[out] error
+ *   Pointer to rte meter error structure.
+ *
+ * @return
+ *   0 on success, a negative value otherwise and rte_errno is set.
+ */
+static int
+nfp_mtr_stats_update(struct rte_eth_dev *dev,
+		uint32_t mtr_id,
+		uint64_t stats_mask,
+		struct rte_mtr_error *error)
+{
+	int ret;
+	struct nfp_mtr *mtr;
+	struct nfp_mtr_priv *priv;
+	struct nfp_flower_representor *representor;
+
+	representor = dev->data->dev_private;
+	priv = representor->app_fw_flower->mtr_priv;
+
+	/* Check if meter id exist */
+	mtr = nfp_mtr_find_by_mtr_id(priv, mtr_id);
+	if (mtr == NULL) {
+		return -rte_mtr_error_set(error, EEXIST,
+				RTE_MTR_ERROR_TYPE_MTR_ID,
+				NULL, "Request meter id not exist");
+	}
+
+	ret = nfp_mtr_stats_mask_validate(stats_mask, error);
+	if (ret != 0)
+		return ret;
+
+	mtr->stats_mask = stats_mask;
+
+	return 0;
+}
+
+/**
+ * Callback to read meter statistics.
+ *
+ * @param[in] dev
+ *   Pointer to Ethernet device.
+ * @param[in] mtr_id
+ *   Meter id.
+ * @param[out] stats
+ *   Pointer to store the statistics.
+ * @param[out] stats_mask
+ *   Pointer to store the stats_mask.
+ * @param[in] clear
+ *   Statistic to be cleared after read or not.
+ * @param[out] error
+ *   Pointer to rte meter error structure.
+ *
+ * @return
+ *   0 on success, a negative value otherwise and rte_errno is set.
+ */
+static int
+nfp_mtr_stats_read(struct rte_eth_dev *dev,
+		uint32_t mtr_id,
+		struct rte_mtr_stats *stats,
+		uint64_t *stats_mask,
+		int clear,
+		struct rte_mtr_error *error)
+{
+	struct nfp_mtr *mtr;
+	struct nfp_mtr_priv *priv;
+	struct nfp_mtr_stats curr;
+	struct nfp_mtr_stats *prev;
+	struct nfp_flower_representor *representor;
+
+	representor = dev->data->dev_private;
+	priv = representor->app_fw_flower->mtr_priv;
+
+	/* Check if meter id exist */
+	mtr = nfp_mtr_find_by_mtr_id(priv, mtr_id);
+	if (mtr == NULL) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_MTR_ID,
+				NULL, "Request meter not exist");
+	}
+
+	*stats_mask = mtr->stats_mask;
+
+	rte_spinlock_lock(&priv->mtr_stats_lock);
+	rte_memcpy(&curr, &mtr->mtr_stats.curr, sizeof(curr));
+	rte_spinlock_unlock(&priv->mtr_stats_lock);
+
+	prev = &mtr->mtr_stats.prev;
+
+	stats->n_pkts[RTE_COLOR_GREEN] = curr.pass_pkts - prev->pass_pkts;
+	stats->n_bytes[RTE_COLOR_GREEN] = curr.pass_bytes - prev->pass_bytes;
+	stats->n_pkts_dropped = curr.drop_pkts - prev->drop_pkts;
+	stats->n_bytes_dropped = curr.drop_bytes - prev->drop_bytes;
+
+	if (clear != 0) {
+		prev->pass_pkts = curr.pass_pkts;
+		prev->pass_bytes = curr.pass_bytes;
+		prev->drop_pkts = curr.drop_pkts;
+		prev->drop_bytes = curr.drop_bytes;
+	}
+
+	return 0;
+}
+
 static const struct rte_mtr_ops nfp_mtr_ops = {
 	.capabilities_get      = nfp_mtr_cap_get,
 	.meter_profile_add     = nfp_mtr_profile_add,
@@ -897,6 +1053,8 @@ static const struct rte_mtr_ops nfp_mtr_ops = {
 	.meter_enable          = nfp_mtr_enable,
 	.meter_disable         = nfp_mtr_disable,
 	.meter_profile_update  = nfp_mtr_profile_update,
+	.stats_update          = nfp_mtr_stats_update,
+	.stats_read            = nfp_mtr_stats_read,
 };
 
 int
@@ -927,10 +1085,14 @@ nfp_mtr_priv_init(struct nfp_pf_dev *pf_dev)
 	app_fw_flower = NFP_PRIV_TO_APP_FW_FLOWER(pf_dev->app_fw_priv);
 	app_fw_flower->mtr_priv = priv;
 
+	priv->drain_tsc = rte_get_tsc_hz();
+
 	LIST_INIT(&priv->mtrs);
 	LIST_INIT(&priv->profiles);
 	LIST_INIT(&priv->policies);
 
+	rte_spinlock_init(&priv->mtr_stats_lock);
+
 	return 0;
 }
 
diff --git a/drivers/net/nfp/nfp_mtr.h b/drivers/net/nfp/nfp_mtr.h
index 1f74e39e17..7bfc935e7d 100644
--- a/drivers/net/nfp/nfp_mtr.h
+++ b/drivers/net/nfp/nfp_mtr.h
@@ -66,6 +66,22 @@ struct nfp_profile_conf {
 	rte_be32_t cir;
 };
 
+/**
+ * Struct nfp_mtr_stats_reply - meter stats, read from firmware
+ * @head:          config head information
+ * @pass_bytes:    count of passed bytes
+ * @pass_pkts:     count of passed packets
+ * @drop_bytes:    count of dropped bytes
+ * @drop_pkts:     count of dropped packets
+ */
+struct nfp_mtr_stats_reply {
+	struct nfp_cfg_head head;
+	rte_be64_t pass_bytes;
+	rte_be64_t pass_pkts;
+	rte_be64_t drop_bytes;
+	rte_be64_t drop_pkts;
+};
+
 /**
  * Struct nfp_mtr_profile - meter profile, stored in driver
  * Can only be used by one meter
@@ -95,6 +111,20 @@ struct nfp_mtr_policy {
 	struct rte_mtr_meter_policy_params policy;
 };
 
+/**
+ * Struct nfp_mtr_stats - meter stats information
+ * @pass_bytes:        count of passed bytes for meter
+ * @pass_pkts:         count of passed packets for meter
+ * @drop_bytes:        count of dropped bytes for meter
+ * @drop_pkts:         count of dropped packets for meter
+ */
+struct nfp_mtr_stats {
+	uint64_t pass_bytes;
+	uint64_t pass_pkts;
+	uint64_t drop_bytes;
+	uint64_t drop_pkts;
+};
+
 /**
  * Struct nfp_mtr - meter object information
  * @next:        next meter object
@@ -104,6 +134,9 @@ struct nfp_mtr_policy {
  * @enable:      if meter is enable to use
  * @mtr_profile: the pointer of profile
  * @mtr_policy:  the pointer of policy
+ * @stats_mask:  supported meter stats mask
+ * @curr:        current meter stats
+ * @prev:        previous meter stats
  */
 struct nfp_mtr {
 	LIST_ENTRY(nfp_mtr) next;
@@ -113,6 +146,11 @@ struct nfp_mtr {
 	bool enable;
 	struct nfp_mtr_profile *mtr_profile;
 	struct nfp_mtr_policy *mtr_policy;
+	uint64_t stats_mask;
+	struct {
+		struct nfp_mtr_stats curr;
+		struct nfp_mtr_stats prev;
+	} mtr_stats;
 };
 
 /**
@@ -120,11 +158,15 @@ struct nfp_mtr {
  * @profiles:        the head node of profile list
  * @policies:        the head node of policy list
  * @mtrs:            the head node of mtrs list
+ * @mtr_stats_lock:  spinlock for meter stats
+ * @drain_tsc:       clock period
  */
 struct nfp_mtr_priv {
 	LIST_HEAD(, nfp_mtr_profile) profiles;
 	LIST_HEAD(, nfp_mtr_policy) policies;
 	LIST_HEAD(, nfp_mtr) mtrs;
+	rte_spinlock_t mtr_stats_lock;
+	uint64_t drain_tsc;
 };
 
 int nfp_net_mtr_ops_get(struct rte_eth_dev *dev, void *arg);
@@ -132,5 +174,7 @@ int nfp_mtr_priv_init(struct nfp_pf_dev *pf_dev);
 void nfp_mtr_priv_uninit(struct nfp_pf_dev *pf_dev);
 struct nfp_mtr *nfp_mtr_find_by_mtr_id(struct nfp_mtr_priv *priv,
 		uint32_t mtr_id);
+struct nfp_mtr *nfp_mtr_find_by_profile_id(struct nfp_mtr_priv *priv,
+		uint32_t profile_id);
 
 #endif /* __NFP_MTR_H__ */
