diff --git a/doc/guides/nics/features/octeontx2.ini b/doc/guides/nics/features/octeontx2.ini
index 02073be..7c59b43 100644
--- a/doc/guides/nics/features/octeontx2.ini
+++ b/doc/guides/nics/features/octeontx2.ini
@@ -21,6 +21,7 @@ TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
+Multicast MAC filter = Y
 RSS hash             = Y
 RSS key update       = Y
 RSS reta update      = Y
diff --git a/doc/guides/nics/features/octeontx2_vec.ini b/doc/guides/nics/features/octeontx2_vec.ini
index 733bb67..2553105 100644
--- a/doc/guides/nics/features/octeontx2_vec.ini
+++ b/doc/guides/nics/features/octeontx2_vec.ini
@@ -19,6 +19,7 @@ MTU update           = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
+Multicast MAC filter = Y
 RSS hash             = Y
 RSS key update       = Y
 RSS reta update      = Y
diff --git a/doc/guides/nics/octeontx2.rst b/doc/guides/nics/octeontx2.rst
index 9a76567..3b567a5 100644
--- a/doc/guides/nics/octeontx2.rst
+++ b/doc/guides/nics/octeontx2.rst
@@ -24,6 +24,7 @@ Features of the OCTEON TX2 Ethdev PMD are:
 - Multiple queues for TX and RX
 - Receiver Side Scaling (RSS)
 - MAC/VLAN filtering
+- Multicast MAC filtering
 - Generic flow API
 - Inner and Outer Checksum offload
 - VLAN/QinQ stripping and insertion
@@ -193,6 +194,11 @@ CRC striping
 The OCTEON TX2 SoC family NICs strip the CRC for every packet being received by
 the host interface irrespective of the offload configuration.
 
+Multicast MAC filtering
+~~~~~~~~~~~~~~~~~~~~~~~
+
+``net_octeontx2`` pmd supports multicast mac filtering feature only on physical
+function devices.
 
 Debugging Options
 -----------------
diff --git a/drivers/net/octeontx2/Makefile b/drivers/net/octeontx2/Makefile
index 66cb5f2..0bfceca 100644
--- a/drivers/net/octeontx2/Makefile
+++ b/drivers/net/octeontx2/Makefile
@@ -44,6 +44,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_OCTEONTX2_PMD) += \
 	otx2_link.c	\
 	otx2_vlan.c	\
 	otx2_stats.c	\
+	otx2_mcast.c	\
 	otx2_lookup.c	\
 	otx2_ethdev.c	\
 	otx2_flow_ctrl.c \
diff --git a/drivers/net/octeontx2/meson.build b/drivers/net/octeontx2/meson.build
index 94bf09a..fad3076 100644
--- a/drivers/net/octeontx2/meson.build
+++ b/drivers/net/octeontx2/meson.build
@@ -12,6 +12,7 @@ sources = files('otx2_rx.c',
 		'otx2_link.c',
 		'otx2_vlan.c',
 		'otx2_stats.c',
+		'otx2_mcast.c',
 		'otx2_lookup.c',
 		'otx2_ethdev.c',
 		'otx2_flow_ctrl.c',
diff --git a/drivers/net/octeontx2/otx2_ethdev.c b/drivers/net/octeontx2/otx2_ethdev.c
index 4a60f9f..62291c6 100644
--- a/drivers/net/octeontx2/otx2_ethdev.c
+++ b/drivers/net/octeontx2/otx2_ethdev.c
@@ -1583,6 +1583,7 @@ otx2_nix_configure(struct rte_eth_dev *eth_dev)
 	if (dev->configured == 1) {
 		otx2_nix_rxchan_bpid_cfg(eth_dev, false);
 		otx2_nix_vlan_fini(eth_dev);
+		otx2_nix_mc_addr_list_uninstall(eth_dev);
 		otx2_flow_free_all_resources(dev);
 		oxt2_nix_unregister_queue_irqs(eth_dev);
 		if (eth_dev->data->dev_conf.intr_conf.rxq)
@@ -1678,6 +1679,12 @@ otx2_nix_configure(struct rte_eth_dev *eth_dev)
 		goto q_irq_fini;
 	}
 
+	rc = otx2_nix_mc_addr_list_install(eth_dev);
+	if (rc < 0) {
+		otx2_err("Failed to install mc address list rc=%d", rc);
+		goto cq_fini;
+	}
+
 	/*
 	 * Restore queue config when reconfigure followed by
 	 * reconfigure and no queue configure invoked from application case.
@@ -1685,7 +1692,7 @@ otx2_nix_configure(struct rte_eth_dev *eth_dev)
 	if (dev->configured == 1) {
 		rc = nix_restore_queue_cfg(eth_dev);
 		if (rc)
-			goto cq_fini;
+			goto uninstall_mc_list;
 	}
 
 	/* Update the mac address */
@@ -1709,6 +1716,8 @@ otx2_nix_configure(struct rte_eth_dev *eth_dev)
 	dev->configured_nb_tx_qs = data->nb_tx_queues;
 	return 0;
 
+uninstall_mc_list:
+	otx2_nix_mc_addr_list_uninstall(eth_dev);
 cq_fini:
 	oxt2_nix_unregister_cq_irqs(eth_dev);
 q_irq_fini:
@@ -1952,6 +1961,7 @@ static const struct eth_dev_ops otx2_eth_dev_ops = {
 	.mac_addr_add             = otx2_nix_mac_addr_add,
 	.mac_addr_remove          = otx2_nix_mac_addr_del,
 	.mac_addr_set             = otx2_nix_mac_addr_set,
+	.set_mc_addr_list         = otx2_nix_set_mc_addr_list,
 	.promiscuous_enable       = otx2_nix_promisc_enable,
 	.promiscuous_disable      = otx2_nix_promisc_disable,
 	.allmulticast_enable      = otx2_nix_allmulticast_enable,
@@ -2169,6 +2179,8 @@ otx2_eth_dev_init(struct rte_eth_dev *eth_dev)
 	if (rc)
 		goto free_mac_addrs;
 
+	otx2_nix_mc_filter_init(dev);
+
 	otx2_nix_dbg("Port=%d pf=%d vf=%d ver=%s msix_off=%d hwcap=0x%" PRIx64
 		     " rxoffload_capa=0x%" PRIx64 " txoffload_capa=0x%" PRIx64,
 		     eth_dev->data->port_id, dev->pf, dev->vf,
@@ -2216,6 +2228,9 @@ otx2_eth_dev_uninit(struct rte_eth_dev *eth_dev, bool mbox_close)
 	/* Disable other rte_flow entries */
 	otx2_flow_fini(dev);
 
+	/* Free multicast filter list */
+	otx2_nix_mc_filter_fini(dev);
+
 	/* Disable PTP if already enabled */
 	if (otx2_ethdev_is_ptp_en(dev))
 		otx2_nix_timesync_disable(eth_dev);
diff --git a/drivers/net/octeontx2/otx2_ethdev.h b/drivers/net/octeontx2/otx2_ethdev.h
index 33fa0c6..4d9ed48 100644
--- a/drivers/net/octeontx2/otx2_ethdev.h
+++ b/drivers/net/octeontx2/otx2_ethdev.h
@@ -207,6 +207,14 @@ struct vlan_mkex_info {
 	uint64_t lb_lt_offset;
 };
 
+struct mcast_entry {
+	struct rte_ether_addr mcast_mac;
+	uint16_t mcam_index;
+	TAILQ_ENTRY(mcast_entry) next;
+};
+
+TAILQ_HEAD(otx2_nix_mc_filter_tbl, mcast_entry);
+
 struct vlan_entry {
 	uint32_t mcam_idx;
 	uint16_t vlan_id;
@@ -311,6 +319,8 @@ struct otx2_eth_dev {
 	struct rte_timecounter  tx_tstamp_tc;
 	double clk_freq_mult;
 	uint64_t clk_delta;
+	bool mc_tbl_set;
+	struct otx2_nix_mc_filter_tbl mc_fltr_tbl;
 } __rte_cache_aligned;
 
 struct otx2_eth_txq {
@@ -393,6 +403,15 @@ int otx2_nix_tx_queue_start(struct rte_eth_dev *eth_dev, uint16_t qidx);
 int otx2_nix_tx_queue_stop(struct rte_eth_dev *eth_dev, uint16_t qidx);
 uint64_t otx2_nix_rxq_mbuf_setup(struct otx2_eth_dev *dev, uint16_t port_id);
 
+/* Multicast filter APIs */
+void otx2_nix_mc_filter_init(struct otx2_eth_dev *dev);
+void otx2_nix_mc_filter_fini(struct otx2_eth_dev *dev);
+int otx2_nix_mc_addr_list_install(struct rte_eth_dev *eth_dev);
+int otx2_nix_mc_addr_list_uninstall(struct rte_eth_dev *eth_dev);
+int otx2_nix_set_mc_addr_list(struct rte_eth_dev *eth_dev,
+			      struct rte_ether_addr *mc_addr_set,
+			      uint32_t nb_mc_addr);
+
 /* MTU */
 int otx2_nix_mtu_set(struct rte_eth_dev *eth_dev, uint16_t mtu);
 int otx2_nix_recalc_mtu(struct rte_eth_dev *eth_dev);
diff --git a/drivers/net/octeontx2/otx2_mcast.c b/drivers/net/octeontx2/otx2_mcast.c
new file mode 100644
index 0000000..f84aa1b
--- /dev/null
+++ b/drivers/net/octeontx2/otx2_mcast.c
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2019 Marvell International Ltd.
+ */
+
+#include "otx2_ethdev.h"
+
+static int
+nix_mc_addr_list_free(struct otx2_eth_dev *dev, uint32_t entry_count)
+{
+	struct npc_mcam_free_entry_req *req;
+	struct otx2_mbox *mbox = dev->mbox;
+	struct mcast_entry *entry;
+	int rc = 0;
+
+	if (entry_count == 0)
+		goto exit;
+
+	TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next) {
+		req = otx2_mbox_alloc_msg_npc_mcam_free_entry(mbox);
+		req->entry = entry->mcam_index;
+
+		rc = otx2_mbox_process_msg(mbox, NULL);
+		if (rc < 0)
+			goto exit;
+
+		TAILQ_REMOVE(&dev->mc_fltr_tbl, entry, next);
+		rte_free(entry);
+		entry_count--;
+
+		if (entry_count == 0)
+			break;
+	}
+
+	if (entry == NULL)
+		dev->mc_tbl_set = false;
+
+exit:
+	return rc;
+}
+
+static int
+nix_hw_update_mc_addr_list(struct rte_eth_dev *eth_dev)
+{
+	struct otx2_eth_dev *dev = otx2_eth_pmd_priv(eth_dev);
+	struct otx2_npc_flow_info *npc = &dev->npc_flow;
+	volatile uint8_t *key_data, *key_mask;
+	struct npc_mcam_write_entry_req *req;
+	struct otx2_mbox *mbox = dev->mbox;
+	struct npc_xtract_info *x_info;
+	uint64_t mcam_data, mcam_mask;
+	struct mcast_entry *entry;
+	otx2_dxcfg_t *ld_cfg;
+	uint8_t *mac_addr;
+	uint64_t action;
+	int idx, rc = 0;
+
+	ld_cfg = &npc->prx_dxcfg;
+	/* Get ETH layer profile info for populating mcam entries */
+	x_info = &(*ld_cfg)[NPC_MCAM_RX][NPC_LID_LA][NPC_LT_LA_ETHER].xtract[0];
+
+	TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next) {
+		req = otx2_mbox_alloc_msg_npc_mcam_write_entry(mbox);
+		if (req == NULL) {
+			/* The mbox memory buffer can be full.
+			 * Flush it and retry
+			 */
+			otx2_mbox_msg_send(mbox, 0);
+			rc = otx2_mbox_wait_for_rsp(mbox, 0);
+			if (rc < 0)
+				goto exit;
+
+			req = otx2_mbox_alloc_msg_npc_mcam_write_entry(mbox);
+			if (req == NULL) {
+				rc = -ENOMEM;
+				goto exit;
+			}
+		}
+		req->entry = entry->mcam_index;
+		req->intf = NPC_MCAM_RX;
+		req->enable_entry = 1;
+
+		/* Channel base extracted to KW0[11:0] */
+		req->entry_data.kw[0] = dev->rx_chan_base;
+		req->entry_data.kw_mask[0] = RTE_LEN2MASK(12, uint64_t);
+
+		/* Update mcam address */
+		key_data = (volatile uint8_t *)req->entry_data.kw;
+		key_mask = (volatile uint8_t *)req->entry_data.kw_mask;
+
+		mcam_data = 0ull;
+		mcam_mask = RTE_LEN2MASK(48, uint64_t);
+		mac_addr = &entry->mcast_mac.addr_bytes[0];
+		for (idx = RTE_ETHER_ADDR_LEN - 1; idx >= 0; idx--)
+			mcam_data |= ((uint64_t)*mac_addr++) << (8 * idx);
+
+		otx2_mbox_memcpy(key_data + x_info->key_off,
+				 &mcam_data, x_info->len);
+		otx2_mbox_memcpy(key_mask + x_info->key_off,
+				 &mcam_mask, x_info->len);
+
+		action = NIX_RX_ACTIONOP_UCAST;
+
+		if (eth_dev->data->dev_conf.rxmode.mq_mode == ETH_MQ_RX_RSS) {
+			action = NIX_RX_ACTIONOP_RSS;
+			action |= (uint64_t)(dev->rss_info.alg_idx) << 56;
+		}
+
+		action |= ((uint64_t)otx2_pfvf_func(dev->pf, dev->vf)) << 4;
+		req->entry_data.action = action;
+	}
+
+	otx2_mbox_msg_send(mbox, 0);
+	rc = otx2_mbox_wait_for_rsp(mbox, 0);
+
+exit:
+	return rc;
+}
+
+int
+otx2_nix_mc_addr_list_install(struct rte_eth_dev *eth_dev)
+{
+	struct otx2_eth_dev *dev = otx2_eth_pmd_priv(eth_dev);
+	struct npc_mcam_alloc_entry_req *req;
+	struct npc_mcam_alloc_entry_rsp *rsp;
+	struct otx2_mbox *mbox = dev->mbox;
+	uint32_t entry_count = 0, idx  = 0;
+	struct mcast_entry *entry;
+	int rc = 0;
+
+	if (!dev->mc_tbl_set)
+		return 0;
+
+	TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next)
+		entry_count++;
+
+	req = otx2_mbox_alloc_msg_npc_mcam_alloc_entry(mbox);
+	req->priority = NPC_MCAM_ANY_PRIO;
+	req->count = entry_count;
+
+	rc = otx2_mbox_process_msg(mbox, (void *)&rsp);
+	if (rc || rsp->count  < entry_count) {
+		otx2_err("Failed to allocate required mcam entries");
+		goto exit;
+	}
+
+	TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next)
+		entry->mcam_index = rsp->entry_list[idx];
+
+	rc = nix_hw_update_mc_addr_list(eth_dev);
+
+exit:
+	return rc;
+}
+
+int
+otx2_nix_mc_addr_list_uninstall(struct rte_eth_dev *eth_dev)
+{
+	struct otx2_eth_dev *dev = otx2_eth_pmd_priv(eth_dev);
+	struct npc_mcam_free_entry_req *req;
+	struct otx2_mbox *mbox = dev->mbox;
+	struct mcast_entry *entry;
+	int rc = 0;
+
+	if (!dev->mc_tbl_set)
+		return 0;
+
+	TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next) {
+		req = otx2_mbox_alloc_msg_npc_mcam_free_entry(mbox);
+		if (req == NULL) {
+			otx2_mbox_msg_send(mbox, 0);
+			rc = otx2_mbox_wait_for_rsp(mbox, 0);
+			if (rc < 0)
+				goto exit;
+
+			req = otx2_mbox_alloc_msg_npc_mcam_free_entry(mbox);
+			if (req == NULL) {
+				rc = -ENOMEM;
+				goto exit;
+			}
+		}
+		req->entry = entry->mcam_index;
+	}
+
+	otx2_mbox_msg_send(mbox, 0);
+	rc = otx2_mbox_wait_for_rsp(mbox, 0);
+
+exit:
+	return rc;
+}
+
+static int
+nix_setup_mc_addr_list(struct otx2_eth_dev *dev,
+		       struct rte_ether_addr *mc_addr_set)
+{
+	struct npc_mcam_ena_dis_entry_req *req;
+	struct otx2_mbox *mbox = dev->mbox;
+	struct mcast_entry *entry;
+	uint32_t idx = 0;
+	int rc = 0;
+
+	/* Populate PMD's mcast list with given mcast mac addresses and
+	 * disable all mcam entries pertaining to the mcast list.
+	 */
+	TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next) {
+		rte_memcpy(&entry->mcast_mac, &mc_addr_set[idx++],
+			   RTE_ETHER_ADDR_LEN);
+
+		req = otx2_mbox_alloc_msg_npc_mcam_dis_entry(mbox);
+		if (req == NULL) {
+			otx2_mbox_msg_send(mbox, 0);
+			rc = otx2_mbox_wait_for_rsp(mbox, 0);
+			if (rc < 0)
+				goto exit;
+
+			req = otx2_mbox_alloc_msg_npc_mcam_dis_entry(mbox);
+			if (req == NULL) {
+				rc = -ENOMEM;
+				goto exit;
+			}
+		}
+		req->entry = entry->mcam_index;
+	}
+
+	otx2_mbox_msg_send(mbox, 0);
+	rc = otx2_mbox_wait_for_rsp(mbox, 0);
+
+exit:
+	return rc;
+}
+
+int
+otx2_nix_set_mc_addr_list(struct rte_eth_dev *eth_dev,
+			  struct rte_ether_addr *mc_addr_set,
+			  uint32_t nb_mc_addr)
+{
+	struct otx2_eth_dev *dev = otx2_eth_pmd_priv(eth_dev);
+	struct npc_mcam_alloc_entry_req *req;
+	struct npc_mcam_alloc_entry_rsp *rsp;
+	struct otx2_mbox *mbox = dev->mbox;
+	uint32_t idx, priv_count = 0;
+	struct mcast_entry *entry;
+	int rc = 0;
+
+	if (otx2_dev_is_vf(dev))
+		return -ENOTSUP;
+
+	TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next)
+		priv_count++;
+
+	if (nb_mc_addr == 0 || mc_addr_set == NULL) {
+		/* Free existing list if new list is null */
+		nb_mc_addr = priv_count;
+		goto exit;
+	}
+
+	for (idx = 0; idx < nb_mc_addr; idx++) {
+		if (!rte_is_multicast_ether_addr(&mc_addr_set[idx]))
+			return -EINVAL;
+	}
+
+	/* New list is bigger than the existing list,
+	 * allocate mcam entries for the extra entries.
+	 */
+	if (nb_mc_addr > priv_count) {
+		req = otx2_mbox_alloc_msg_npc_mcam_alloc_entry(mbox);
+		req->priority = NPC_MCAM_ANY_PRIO;
+		req->count = nb_mc_addr - priv_count;
+
+		rc = otx2_mbox_process_msg(mbox, (void *)&rsp);
+		if (rc || (rsp->count + priv_count < nb_mc_addr)) {
+			otx2_err("Failed to allocate required entries");
+			nb_mc_addr = priv_count;
+			goto exit;
+		}
+
+		/* Append new mcam entries to the existing mc list */
+		for (idx = 0; idx < rsp->count; idx++) {
+			entry = rte_zmalloc("otx2_nix_mc_entry",
+					    sizeof(struct mcast_entry), 0);
+			if (!entry) {
+				otx2_err("Failed to allocate memory");
+				nb_mc_addr = priv_count;
+				rc = -ENOMEM;
+				goto exit;
+			}
+			entry->mcam_index = rsp->entry_list[idx];
+			TAILQ_INSERT_HEAD(&dev->mc_fltr_tbl, entry, next);
+		}
+	} else {
+		/* Free the extra mcam entries if the new list is smaller
+		 * than exiting list.
+		 */
+		nix_mc_addr_list_free(dev, priv_count - nb_mc_addr);
+	}
+
+
+	/* Now mc_fltr_tbl has the required number of mcam entries,
+	 * Traverse through it and add new multicast filter table entries.
+	 */
+	rc = nix_setup_mc_addr_list(dev, mc_addr_set);
+	if (rc < 0)
+		goto exit;
+
+	rc = nix_hw_update_mc_addr_list(eth_dev);
+	if (rc < 0)
+		goto exit;
+
+	dev->mc_tbl_set = true;
+
+	return 0;
+
+exit:
+	nix_mc_addr_list_free(dev, nb_mc_addr);
+	return rc;
+}
+
+void
+otx2_nix_mc_filter_init(struct otx2_eth_dev *dev)
+{
+	if (otx2_dev_is_vf(dev))
+		return;
+
+	TAILQ_INIT(&dev->mc_fltr_tbl);
+}
+
+void
+otx2_nix_mc_filter_fini(struct otx2_eth_dev *dev)
+{
+	struct mcast_entry *entry;
+	uint32_t count = 0;
+
+	if (otx2_dev_is_vf(dev))
+		return;
+
+	TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next)
+		count++;
+
+	nix_mc_addr_list_free(dev, count);
+}
