diff --git a/drivers/net/mvpp2/Makefile b/drivers/net/mvpp2/Makefile
index 211d398..4848d65 100644
--- a/drivers/net/mvpp2/Makefile
+++ b/drivers/net/mvpp2/Makefile
@@ -39,5 +39,6 @@ LDLIBS += -lrte_bus_vdev -lrte_common_mvep
 SRCS-$(CONFIG_RTE_LIBRTE_MVPP2_PMD) += mrvl_ethdev.c
 SRCS-$(CONFIG_RTE_LIBRTE_MVPP2_PMD) += mrvl_qos.c
 SRCS-$(CONFIG_RTE_LIBRTE_MVPP2_PMD) += mrvl_flow.c
+SRCS-$(CONFIG_RTE_LIBRTE_MVPP2_PMD) += mrvl_mtr.c
 
 include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/drivers/net/mvpp2/meson.build b/drivers/net/mvpp2/meson.build
index 3620659..f475511 100644
--- a/drivers/net/mvpp2/meson.build
+++ b/drivers/net/mvpp2/meson.build
@@ -19,7 +19,8 @@ endif
 sources = files(
 	'mrvl_ethdev.c',
 	'mrvl_flow.c',
-	'mrvl_qos.c'
+	'mrvl_qos.c',
+	'mrvl_mtr.c'
 )
 
 deps += ['cfgfile', 'common_mvep']
diff --git a/drivers/net/mvpp2/mrvl_ethdev.c b/drivers/net/mvpp2/mrvl_ethdev.c
index c8c8548..a6cc550 100644
--- a/drivers/net/mvpp2/mrvl_ethdev.c
+++ b/drivers/net/mvpp2/mrvl_ethdev.c
@@ -23,6 +23,7 @@
 #include <rte_mvep_common.h>
 #include "mrvl_ethdev.h"
 #include "mrvl_qos.h"
+#include "mrvl_mtr.h"
 
 /* bitmask with reserved hifs */
 #define MRVL_MUSDK_HIFS_RESERVED 0x0F
@@ -618,6 +619,8 @@ mrvl_dev_start(struct rte_eth_dev *dev)
 			goto out;
 	}
 
+	mrvl_mtr_init(dev);
+
 	return 0;
 out:
 	MRVL_LOG(ERR, "Failed to start device");
@@ -756,6 +759,7 @@ mrvl_dev_close(struct rte_eth_dev *dev)
 
 	mrvl_flush_rx_queues(dev);
 	mrvl_flush_tx_shadow_queues(dev);
+	mrvl_mtr_deinit(dev);
 
 	for (i = 0; i < priv->ppio_params.inqs_params.num_tcs; ++i) {
 		struct pp2_ppio_tc_params *tc_params =
@@ -1858,6 +1862,25 @@ mrvl_eth_filter_ctrl(struct rte_eth_dev *dev __rte_unused,
 	}
 }
 
+/**
+ * DPDK callback to get rte_mtr callbacks.
+ *
+ * @param dev
+ *   Pointer to the device structure.
+ * @param ops
+ *   Pointer to pass the mtr ops.
+ *
+ * @return
+ *   Always 0.
+ */
+static int
+mrvl_mtr_ops_get(struct rte_eth_dev *dev __rte_unused, void *ops)
+{
+	*(const void **)ops = &mrvl_mtr_ops;
+
+	return 0;
+}
+
 static const struct eth_dev_ops mrvl_ops = {
 	.dev_configure = mrvl_dev_configure,
 	.dev_start = mrvl_dev_start,
@@ -1895,6 +1918,7 @@ static const struct eth_dev_ops mrvl_ops = {
 	.rss_hash_update = mrvl_rss_hash_update,
 	.rss_hash_conf_get = mrvl_rss_hash_conf_get,
 	.filter_ctrl = mrvl_eth_filter_ctrl,
+	.mtr_ops_get = mrvl_mtr_ops_get,
 };
 
 /**
diff --git a/drivers/net/mvpp2/mrvl_ethdev.h b/drivers/net/mvpp2/mrvl_ethdev.h
index 2204be2..ecb8fdc 100644
--- a/drivers/net/mvpp2/mrvl_ethdev.h
+++ b/drivers/net/mvpp2/mrvl_ethdev.h
@@ -9,6 +9,7 @@
 
 #include <rte_spinlock.h>
 #include <rte_flow_driver.h>
+#include <rte_mtr_driver.h>
 
 /*
  * container_of is defined by both DPDK and MUSDK,
@@ -70,6 +71,69 @@
 /** Minimum number of sent buffers to release from shadow queue to BM */
 #define MRVL_PP2_BUF_RELEASE_BURST_SIZE	64
 
+/** Maximum length of a match string */
+#define MRVL_MATCH_LEN 16
+
+/** Parsed fields in processed rte_flow_item. */
+enum mrvl_parsed_fields {
+	/* eth flags */
+	F_DMAC =         BIT(0),
+	F_SMAC =         BIT(1),
+	F_TYPE =         BIT(2),
+	/* vlan flags */
+	F_VLAN_PRI =     BIT(3),
+	F_VLAN_ID =      BIT(4),
+	F_VLAN_TCI =     BIT(5), /* not supported by MUSDK yet */
+	/* ip4 flags */
+	F_IP4_TOS =      BIT(6),
+	F_IP4_SIP =      BIT(7),
+	F_IP4_DIP =      BIT(8),
+	F_IP4_PROTO =    BIT(9),
+	/* ip6 flags */
+	F_IP6_TC =       BIT(10), /* not supported by MUSDK yet */
+	F_IP6_SIP =      BIT(11),
+	F_IP6_DIP =      BIT(12),
+	F_IP6_FLOW =     BIT(13),
+	F_IP6_NEXT_HDR = BIT(14),
+	/* tcp flags */
+	F_TCP_SPORT =    BIT(15),
+	F_TCP_DPORT =    BIT(16),
+	/* udp flags */
+	F_UDP_SPORT =    BIT(17),
+	F_UDP_DPORT =    BIT(18),
+};
+
+/** PMD-specific definition of a flow rule handle. */
+struct mrvl_mtr;
+struct rte_flow {
+	LIST_ENTRY(rte_flow) next;
+	struct mrvl_mtr *mtr;
+
+	enum mrvl_parsed_fields pattern;
+
+	struct pp2_cls_tbl_rule rule;
+	struct pp2_cls_cos_desc cos;
+	struct pp2_cls_tbl_action action;
+};
+
+struct mrvl_mtr_profile {
+	LIST_ENTRY(mrvl_mtr_profile) next;
+	uint32_t profile_id;
+	int refcnt;
+	struct rte_mtr_meter_profile profile;
+};
+
+struct mrvl_mtr {
+	LIST_ENTRY(mrvl_mtr) next;
+	uint32_t mtr_id;
+	int refcnt;
+	int shared;
+	int enabled;
+	int plcr_bit;
+	struct mrvl_mtr_profile *profile;
+	struct pp2_cls_plcr *plcr;
+};
+
 struct mrvl_priv {
 	/* Hot fields, used in fast path. */
 	struct pp2_bpool *bpool;  /**< BPool pointer */
@@ -105,11 +169,18 @@ struct mrvl_priv {
 	LIST_HEAD(mrvl_flows, rte_flow) flows;
 
 	struct pp2_cls_plcr *policer;
+
+	LIST_HEAD(profiles, mrvl_mtr_profile) profiles;
+	LIST_HEAD(mtrs, mrvl_mtr) mtrs;
+	uint32_t used_plcrs;
 };
 
 /** Flow operations forward declaration. */
 extern const struct rte_flow_ops mrvl_flow_ops;
 
+/** Meter operations forward declaration. */
+extern const struct rte_mtr_ops mrvl_mtr_ops;
+
 /** Current log type. */
 extern int mrvl_logtype;
 
diff --git a/drivers/net/mvpp2/mrvl_flow.c b/drivers/net/mvpp2/mrvl_flow.c
index db750f4..e6953e4 100644
--- a/drivers/net/mvpp2/mrvl_flow.c
+++ b/drivers/net/mvpp2/mrvl_flow.c
@@ -20,46 +20,6 @@
 /** Size of the classifier key and mask strings. */
 #define MRVL_CLS_STR_SIZE_MAX 40
 
-/** Parsed fields in processed rte_flow_item. */
-enum mrvl_parsed_fields {
-	/* eth flags */
-	F_DMAC =         BIT(0),
-	F_SMAC =         BIT(1),
-	F_TYPE =         BIT(2),
-	/* vlan flags */
-	F_VLAN_ID =      BIT(3),
-	F_VLAN_PRI =     BIT(4),
-	F_VLAN_TCI =     BIT(5), /* not supported by MUSDK yet */
-	/* ip4 flags */
-	F_IP4_TOS =      BIT(6),
-	F_IP4_SIP =      BIT(7),
-	F_IP4_DIP =      BIT(8),
-	F_IP4_PROTO =    BIT(9),
-	/* ip6 flags */
-	F_IP6_TC =       BIT(10), /* not supported by MUSDK yet */
-	F_IP6_SIP =      BIT(11),
-	F_IP6_DIP =      BIT(12),
-	F_IP6_FLOW =     BIT(13),
-	F_IP6_NEXT_HDR = BIT(14),
-	/* tcp flags */
-	F_TCP_SPORT =    BIT(15),
-	F_TCP_DPORT =    BIT(16),
-	/* udp flags */
-	F_UDP_SPORT =    BIT(17),
-	F_UDP_DPORT =    BIT(18),
-};
-
-/** PMD-specific definition of a flow rule handle. */
-struct rte_flow {
-	LIST_ENTRY(rte_flow) next;
-
-	enum mrvl_parsed_fields pattern;
-
-	struct pp2_cls_tbl_rule rule;
-	struct pp2_cls_cos_desc cos;
-	struct pp2_cls_tbl_action action;
-};
-
 static const enum rte_flow_item_type pattern_eth[] = {
 	RTE_FLOW_ITEM_TYPE_ETH,
 	RTE_FLOW_ITEM_TYPE_END
@@ -2295,19 +2255,59 @@ mrvl_flow_parse_actions(struct mrvl_priv *priv,
 			flow->action.type = PP2_CLS_TBL_ACT_DONE;
 			flow->action.cos = &flow->cos;
 			specified++;
+		} else if (action->type == RTE_FLOW_ACTION_TYPE_METER) {
+			const struct rte_flow_action_meter *meter;
+			struct mrvl_mtr *mtr;
+
+			meter = action->conf;
+			if (!meter)
+				return -rte_flow_error_set(error, EINVAL,
+						RTE_FLOW_ERROR_TYPE_ACTION,
+						NULL, "Invalid meter\n");
+
+			LIST_FOREACH(mtr, &priv->mtrs, next)
+				if (mtr->mtr_id == meter->mtr_id)
+					break;
+
+			if (!mtr)
+				return -rte_flow_error_set(error, EINVAL,
+						RTE_FLOW_ERROR_TYPE_ACTION,
+						NULL,
+						"Meter id does not exist\n");
+
+			if (!mtr->shared && mtr->refcnt)
+				return -rte_flow_error_set(error, EPERM,
+						RTE_FLOW_ERROR_TYPE_ACTION,
+						NULL,
+						"Meter cannot be shared\n");
+
+			/*
+			 * In case cos has already been set
+			 * do not modify it.
+			 */
+			if (!flow->cos.ppio) {
+				flow->cos.ppio = priv->ppio;
+				flow->cos.tc = 0;
+			}
+
+			flow->action.type = PP2_CLS_TBL_ACT_DONE;
+			flow->action.cos = &flow->cos;
+			flow->action.plcr = mtr->enabled ? mtr->plcr : NULL;
+			flow->mtr = mtr;
+			mtr->refcnt++;
+			specified++;
 		} else {
 			rte_flow_error_set(error, ENOTSUP,
 					   RTE_FLOW_ERROR_TYPE_ACTION, NULL,
 					   "Action not supported");
 			return -rte_errno;
 		}
-
 	}
 
 	if (!specified) {
 		rte_flow_error_set(error, EINVAL,
-				   RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
-				   NULL, "Action not specified");
+				   RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+				   "Action not specified");
 		return -rte_errno;
 	}
 
@@ -2657,6 +2657,11 @@ mrvl_flow_remove(struct mrvl_priv *priv, struct rte_flow *flow,
 
 	mrvl_free_all_key_mask(&flow->rule);
 
+	if (flow->mtr) {
+		flow->mtr->refcnt--;
+		flow->mtr = NULL;
+	}
+
 	return 0;
 }
 
diff --git a/drivers/net/mvpp2/mrvl_mtr.c b/drivers/net/mvpp2/mrvl_mtr.c
new file mode 100644
index 0000000..9cd53be
--- /dev/null
+++ b/drivers/net/mvpp2/mrvl_mtr.c
@@ -0,0 +1,512 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2018 Marvell International Ltd.
+ * Copyright(c) 2018 Semihalf.
+ * All rights reserved.
+ */
+
+#include <rte_log.h>
+#include <rte_malloc.h>
+
+#include "mrvl_mtr.h"
+
+/** Maximum meter rate */
+#define MRVL_SRTCM_RFC2697_CIR_MAX 1023000
+
+/** Invalid plcr bit */
+#define MRVL_PLCR_BIT_INVALID -1
+
+/**
+ * Return meter object capabilities.
+ *
+ * @param dev Pointer to the device (unused).
+ * @param cap Pointer to the meter object capabilities.
+ * @param error Pointer to the error (unused).
+ * @returns 0 always.
+ */
+static int
+mrvl_capabilities_get(struct rte_eth_dev *dev __rte_unused,
+			  struct rte_mtr_capabilities *cap,
+			  struct rte_mtr_error *error __rte_unused)
+{
+	struct rte_mtr_capabilities capa = {
+		.n_max = PP2_CLS_PLCR_NUM,
+		.n_shared_max = PP2_CLS_PLCR_NUM,
+		.shared_n_flows_per_mtr_max = -1,
+		.meter_srtcm_rfc2697_n_max = PP2_CLS_PLCR_NUM,
+		.meter_rate_max = MRVL_SRTCM_RFC2697_CIR_MAX,
+	};
+
+	memcpy(cap, &capa, sizeof(capa));
+
+	return 0;
+}
+
+/**
+ * Get profile using it's id.
+ *
+ * @param priv Pointer to the port's private data.
+ * @param meter_profile_id Profile id used by the meter.
+ * @returns Pointer to the profile if exists, NULL otherwise.
+ */
+static struct mrvl_mtr_profile *
+mrvl_mtr_profile_from_id(struct mrvl_priv *priv, uint32_t meter_profile_id)
+{
+	struct mrvl_mtr_profile *profile = NULL;
+
+	LIST_FOREACH(profile, &priv->profiles, next)
+		if (profile->profile_id == meter_profile_id)
+			break;
+
+	return profile;
+}
+
+/**
+ * Add profile to the list of profiles.
+ *
+ * @param dev Pointer to the device.
+ * @param meter_profile_id Id of the new profile.
+ * @param profile Pointer to the profile configuration.
+ * @param error Pointer to the error.
+ * @returns 0 on success, negative value otherwise.
+ */
+static int
+mrvl_meter_profile_add(struct rte_eth_dev *dev, uint32_t meter_profile_id,
+		       struct rte_mtr_meter_profile *profile,
+		       struct rte_mtr_error *error)
+{
+	struct mrvl_priv *priv = dev->data->dev_private;
+	struct mrvl_mtr_profile *prof;
+
+	if (!profile)
+		return -rte_mtr_error_set(error, EINVAL,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL, NULL);
+
+	if (profile->alg != RTE_MTR_SRTCM_RFC2697)
+		return -rte_mtr_error_set(error, EINVAL,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL,
+					  "Only srTCM RFC 2697 is supported\n");
+
+	prof = mrvl_mtr_profile_from_id(priv, meter_profile_id);
+	if (prof)
+		return -rte_mtr_error_set(error, EEXIST,
+					  RTE_MTR_ERROR_TYPE_METER_PROFILE_ID,
+					  NULL, "Profile id already exists\n");
+
+	prof = rte_zmalloc_socket(NULL, sizeof(*prof), 0, rte_socket_id());
+	if (!prof)
+		return -rte_mtr_error_set(error, ENOMEM,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL, NULL);
+
+	prof->profile_id = meter_profile_id;
+	memcpy(&prof->profile, profile, sizeof(*profile));
+
+	LIST_INSERT_HEAD(&priv->profiles, prof, next);
+
+	return 0;
+}
+
+/**
+ * Remove profile from the list of profiles.
+ *
+ * @param dev Pointer to the device.
+ * @param meter_profile_id Id of the profile to remove.
+ * @param error Pointer to the error.
+ * @returns 0 on success, negative value otherwise.
+ */
+static int
+mrvl_meter_profile_delete(struct rte_eth_dev *dev,
+			      uint32_t meter_profile_id,
+			      struct rte_mtr_error *error)
+{
+	struct mrvl_priv *priv = dev->data->dev_private;
+	struct mrvl_mtr_profile *profile;
+
+	profile = mrvl_mtr_profile_from_id(priv, meter_profile_id);
+	if (!profile)
+		return -rte_mtr_error_set(error, ENODEV,
+					  RTE_MTR_ERROR_TYPE_METER_PROFILE_ID,
+					  NULL, "Profile id does not exist\n");
+
+	if (profile->refcnt)
+		return -rte_mtr_error_set(error, EPERM,
+					  RTE_MTR_ERROR_TYPE_METER_PROFILE_ID,
+					  NULL, "Profile is used\n");
+
+	LIST_REMOVE(profile, next);
+	rte_free(profile);
+
+	return 0;
+}
+
+/**
+ * Get meter using it's id.
+ *
+ * @param priv Pointer to port's private data.
+ * @param mtr_id Id of the meter.
+ * @returns Pointer to the meter if exists, NULL otherwise.
+ */
+static struct mrvl_mtr *
+mrvl_mtr_from_id(struct mrvl_priv *priv, uint32_t mtr_id)
+{
+	struct mrvl_mtr *mtr = NULL;
+
+	LIST_FOREACH(mtr, &priv->mtrs, next)
+		if (mtr->mtr_id == mtr_id)
+			break;
+
+	return mtr;
+}
+
+/**
+ * Reserve a policer bit in a bitmap.
+ *
+ * @param plcrs Pointer to the policers bitmap.
+ * @returns Reserved bit number on success, negative value otherwise.
+ */
+static int
+mrvl_reserve_plcr(uint32_t *plcrs)
+{
+	uint32_t i, num;
+
+	num = PP2_CLS_PLCR_NUM;
+	if (num > sizeof(uint32_t) * 8) {
+		num = sizeof(uint32_t) * 8;
+		MRVL_LOG(WARNING, "Plcrs number was limited to 32.");
+	}
+
+	for (i = 0; i < num; i++) {
+		uint32_t bit = BIT(i);
+
+		if (!(*plcrs & bit)) {
+			*plcrs |= bit;
+
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+/**
+ * Enable meter object.
+ *
+ * @param dev Pointer to the device.
+ * @param mtr_id Id of the meter.
+ * @param error Pointer to the error.
+ * @returns 0 in success, negative value otherwise.
+ */
+static int
+mrvl_meter_enable(struct rte_eth_dev *dev, uint32_t mtr_id,
+		  struct rte_mtr_error *error)
+{
+	struct mrvl_priv *priv = dev->data->dev_private;
+	struct mrvl_mtr *mtr = mrvl_mtr_from_id(priv, mtr_id);
+	struct pp2_cls_plcr_params params;
+	char match[MRVL_MATCH_LEN];
+	struct rte_flow *flow;
+	int ret;
+
+	if (!priv->ppio)
+		return -rte_mtr_error_set(error, EPERM,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL, "Port is uninitialized\n");
+
+	if (!mtr)
+		return -rte_mtr_error_set(error, ENODEV,
+					  RTE_MTR_ERROR_TYPE_MTR_ID, NULL,
+					  "Meter id does not exist\n");
+
+	if (mtr->plcr)
+		goto skip;
+
+	mtr->plcr_bit = mrvl_reserve_plcr(&priv->used_plcrs);
+	if (mtr->plcr_bit < 0)
+		return -rte_mtr_error_set(error, ENOSPC,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL,
+					  "Failed to reserve plcr entry\n");
+
+	memset(&params, 0, sizeof(params));
+	snprintf(match, sizeof(match), "policer-%d:%d", priv->pp_id,
+		 mtr->plcr_bit);
+	params.match = match;
+	params.token_unit = PP2_CLS_PLCR_BYTES_TOKEN_UNIT;
+	params.color_mode = PP2_CLS_PLCR_COLOR_BLIND_MODE;
+	params.cir = mtr->profile->profile.srtcm_rfc2697.cir;
+	params.cbs = mtr->profile->profile.srtcm_rfc2697.cbs;
+	params.ebs = mtr->profile->profile.srtcm_rfc2697.ebs;
+
+	ret = pp2_cls_plcr_init(&params, &mtr->plcr);
+	if (ret) {
+		priv->used_plcrs &= ~BIT(mtr->plcr_bit);
+		mtr->plcr_bit = MRVL_PLCR_BIT_INVALID;
+
+		return -rte_mtr_error_set(error, -ret,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL, "Failed to setup policer\n");
+	}
+
+	mtr->enabled = 1;
+skip:
+	/* iterate over flows that have this mtr attached */
+	LIST_FOREACH(flow, &priv->flows, next) {
+		if (flow->mtr != mtr)
+			continue;
+
+		flow->action.plcr = mtr->plcr;
+
+		ret = pp2_cls_tbl_modify_rule(priv->cls_tbl, &flow->rule,
+					      &flow->action);
+		if (ret)
+			return -rte_mtr_error_set(error, -ret,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL, "Failed to update cls rule\n");
+	}
+
+	return 0;
+}
+
+/**
+ * Disable meter object.
+ *
+ * @param dev Pointer to the device.
+ * @param mtr Id of the meter.
+ * @param error Pointer to the error.
+ * @returns 0 on success, negative value otherwise.
+ */
+static int
+mrvl_meter_disable(struct rte_eth_dev *dev, uint32_t mtr_id,
+		       struct rte_mtr_error *error)
+{
+	struct mrvl_priv *priv = dev->data->dev_private;
+	struct mrvl_mtr *mtr = mrvl_mtr_from_id(priv, mtr_id);
+	struct rte_flow *flow;
+	int ret;
+
+	if (!mtr)
+		return -rte_mtr_error_set(error, ENODEV,
+					  RTE_MTR_ERROR_TYPE_MTR_ID, NULL,
+					  "Meter id does not exist\n");
+
+	LIST_FOREACH(flow, &priv->flows, next) {
+		if (flow->mtr != mtr)
+			continue;
+
+		flow->action.plcr = NULL;
+
+		ret = pp2_cls_tbl_modify_rule(priv->cls_tbl, &flow->rule,
+					      &flow->action);
+		if (ret)
+			return -rte_mtr_error_set(error, -ret,
+					RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					NULL, "Failed to disable meter\n");
+	}
+
+	mtr->enabled = 0;
+
+	return 0;
+}
+
+/**
+ * Create new meter.
+ *
+ * @param dev Pointer to the device.
+ * @param mtr_id Id of the meter.
+ * @param params Pointer to the meter parameters.
+ * @param shared Flags indicating whether meter is shared.
+ * @param error Pointer to the error.
+ * @returns 0 on success, negative value otherwise.
+ */
+static int
+mrvl_create(struct rte_eth_dev *dev, uint32_t mtr_id,
+	    struct rte_mtr_params *params, int shared,
+	    struct rte_mtr_error *error)
+{
+	struct mrvl_priv *priv = dev->data->dev_private;
+	struct mrvl_mtr_profile *profile;
+	struct mrvl_mtr *mtr;
+
+	mtr = mrvl_mtr_from_id(priv, mtr_id);
+	if (mtr)
+		return -rte_mtr_error_set(error, EEXIST,
+					  RTE_MTR_ERROR_TYPE_MTR_ID, NULL,
+					  "Meter id already exists\n");
+
+	mtr = rte_zmalloc_socket(NULL, sizeof(*mtr), 0, rte_socket_id());
+	if (!mtr)
+		return -rte_mtr_error_set(error, ENOMEM,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL, NULL);
+
+	profile = mrvl_mtr_profile_from_id(priv, params->meter_profile_id);
+	if (!profile)
+		return -rte_mtr_error_set(error, EINVAL,
+					  RTE_MTR_ERROR_TYPE_METER_PROFILE_ID,
+					  NULL, "Profile id does not exist\n");
+
+	mtr->shared = shared;
+	mtr->mtr_id = mtr_id;
+	mtr->plcr_bit = MRVL_PLCR_BIT_INVALID;
+	mtr->profile = profile;
+	profile->refcnt++;
+	LIST_INSERT_HEAD(&priv->mtrs, mtr, next);
+
+	if (params->meter_enable)
+		return mrvl_meter_enable(dev, mtr_id, error);
+
+	return 0;
+}
+
+/**
+ * Destroy meter object.
+ *
+ * @param dev Pointer to the device.
+ * @param mtr_id Id of the meter object.
+ * @param error Pointer to the error.
+ * @returns 0 on success, negative value otherwise.
+ */
+static int
+mrvl_destroy(struct rte_eth_dev *dev, uint32_t mtr_id,
+		 struct rte_mtr_error *error)
+{
+	struct mrvl_priv *priv = dev->data->dev_private;
+	struct mrvl_mtr *mtr;
+
+	if (!priv->ppio)
+		return -rte_mtr_error_set(error, EPERM,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL, "Port is uninitialized\n");
+
+	mtr = mrvl_mtr_from_id(priv, mtr_id);
+	if (!mtr)
+		return -rte_mtr_error_set(error, EEXIST,
+					  RTE_MTR_ERROR_TYPE_MTR_ID, NULL,
+					  "Meter id does not exist\n");
+
+	if (mtr->refcnt)
+		return -rte_mtr_error_set(error, EPERM,
+					  RTE_MTR_ERROR_TYPE_MTR_ID, NULL,
+					  "Meter is used\n");
+
+	LIST_REMOVE(mtr, next);
+	mtr->profile->refcnt--;
+
+	if (mtr->plcr_bit != MRVL_PLCR_BIT_INVALID)
+		priv->used_plcrs &= ~BIT(mtr->plcr_bit);
+
+	if (mtr->plcr)
+		pp2_cls_plcr_deinit(mtr->plcr);
+
+	rte_free(mtr);
+
+	return 0;
+}
+
+/**
+ * Update profile used by the meter.
+ *
+ * @param dev Pointer to the device.
+ * @param mtr_id Id of the meter object.
+ * @param error Pointer to the error.
+ * @returns 0 on success, negative value otherwise.
+ */
+static int
+mrvl_meter_profile_update(struct rte_eth_dev *dev, uint32_t mtr_id,
+			  uint32_t meter_profile_id,
+			  struct rte_mtr_error *error)
+{
+	struct mrvl_priv *priv = dev->data->dev_private;
+	struct mrvl_mtr_profile *profile;
+	struct mrvl_mtr *mtr;
+	int ret, enabled;
+
+	if (!priv->ppio)
+		return -rte_mtr_error_set(error, EPERM,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+					  NULL, "Port is uninitialized\n");
+
+	mtr = mrvl_mtr_from_id(priv, mtr_id);
+	if (!mtr)
+		return -rte_mtr_error_set(error, EEXIST,
+					  RTE_MTR_ERROR_TYPE_MTR_ID, NULL,
+					  "Meter id does not exist\n");
+
+	profile = mrvl_mtr_profile_from_id(priv, meter_profile_id);
+	if (!profile)
+		return -rte_mtr_error_set(error, EINVAL,
+					  RTE_MTR_ERROR_TYPE_METER_PROFILE_ID,
+					  NULL, "Profile id does not exist\n");
+
+	ret = mrvl_meter_disable(dev, mtr_id, error);
+	if (ret)
+		return -rte_mtr_error_set(error, EPERM,
+					  RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL,
+					  NULL);
+
+	if (mtr->plcr) {
+		enabled = 1;
+		pp2_cls_plcr_deinit(mtr->plcr);
+		mtr->plcr = NULL;
+	}
+
+	mtr->profile->refcnt--;
+	mtr->profile = profile;
+	profile->refcnt++;
+
+	if (enabled)
+		return mrvl_meter_enable(dev, mtr_id, error);
+
+	return 0;
+}
+
+const struct rte_mtr_ops mrvl_mtr_ops = {
+	.capabilities_get = mrvl_capabilities_get,
+	.meter_profile_add = mrvl_meter_profile_add,
+	.meter_profile_delete = mrvl_meter_profile_delete,
+	.create = mrvl_create,
+	.destroy = mrvl_destroy,
+	.meter_enable = mrvl_meter_enable,
+	.meter_disable = mrvl_meter_disable,
+	.meter_profile_update = mrvl_meter_profile_update,
+};
+
+/**
+ * Initialize metering resources.
+ *
+ * @param dev Pointer to the device.
+ */
+void
+mrvl_mtr_init(struct rte_eth_dev *dev)
+{
+	struct mrvl_priv *priv = dev->data->dev_private;
+
+	LIST_INIT(&priv->profiles);
+	LIST_INIT(&priv->mtrs);
+}
+
+/**
+ * Cleanup metering resources.
+ *
+ * @param dev Pointer to the device.
+ */
+void
+mrvl_mtr_deinit(struct rte_eth_dev *dev)
+{
+	struct mrvl_priv *priv = dev->data->dev_private;
+	struct mrvl_mtr_profile *profile, *tmp_profile;
+	struct mrvl_mtr *mtr, *tmp_mtr;
+
+	for (mtr = LIST_FIRST(&priv->mtrs);
+	     mtr && (tmp_mtr = LIST_NEXT(mtr, next), 1);
+	     mtr = tmp_mtr)
+		mrvl_destroy(dev, mtr->mtr_id, NULL);
+
+	for (profile = LIST_FIRST(&priv->profiles);
+	     profile && (tmp_profile = LIST_NEXT(profile, next), 1);
+	     profile = tmp_profile)
+		mrvl_meter_profile_delete(dev, profile->profile_id, NULL);
+}
diff --git a/drivers/net/mvpp2/mrvl_mtr.h b/drivers/net/mvpp2/mrvl_mtr.h
new file mode 100644
index 0000000..302a20f
--- /dev/null
+++ b/drivers/net/mvpp2/mrvl_mtr.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2018 Marvell International Ltd.
+ * Copyright(c) 2018 Semihalf.
+ * All rights reserved.
+ */
+
+#ifndef _MRVL_MTR_H_
+#define _MRVL_MTR_H_
+
+#include "mrvl_ethdev.h"
+
+void mrvl_mtr_init(struct rte_eth_dev *dev);
+void mrvl_mtr_deinit(struct rte_eth_dev *dev);
+
+#endif /* _MRVL_MTR_H_ */
