diff --git a/drivers/net/cxgbe/Makefile b/drivers/net/cxgbe/Makefile
index d75b070f3..68466f13e 100644
--- a/drivers/net/cxgbe/Makefile
+++ b/drivers/net/cxgbe/Makefile
@@ -53,6 +53,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += cxgbe_filter.c
 SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += cxgbe_flow.c
 SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += t4_hw.c
 SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += clip_tbl.c
+SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += mps_tcam.c
 SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += l2t.c
 SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += t4vf_hw.c
 
diff --git a/drivers/net/cxgbe/base/adapter.h b/drivers/net/cxgbe/base/adapter.h
index 9f4a9653c..47cfc5f5f 100644
--- a/drivers/net/cxgbe/base/adapter.h
+++ b/drivers/net/cxgbe/base/adapter.h
@@ -328,6 +328,7 @@ struct adapter {
 	unsigned int l2t_end;     /* Layer 2 table end */
 	struct clip_tbl *clipt;   /* CLIP table */
 	struct l2t_data *l2t;     /* Layer 2 table */
+	struct mpstcam_table *mpstcam;
 
 	struct tid_info tids;     /* Info used to access TID related tables */
 };
diff --git a/drivers/net/cxgbe/base/common.h b/drivers/net/cxgbe/base/common.h
index 157201da2..9f5756850 100644
--- a/drivers/net/cxgbe/base/common.h
+++ b/drivers/net/cxgbe/base/common.h
@@ -388,6 +388,12 @@ int t4_free_vi(struct adapter *adap, unsigned int mbox,
 int t4_set_rxmode(struct adapter *adap, unsigned int mbox, unsigned int viid,
 		  int mtu, int promisc, int all_multi, int bcast, int vlanex,
 		  bool sleep_ok);
+int t4_free_raw_mac_filt(struct adapter *adap, unsigned int viid,
+			 const u8 *addr, const u8 *mask, unsigned int idx,
+			 u8 lookup_type, u8 port_id, bool sleep_ok);
+int t4_alloc_raw_mac_filt(struct adapter *adap, unsigned int viid,
+			  const u8 *addr, const u8 *mask, unsigned int idx,
+			  u8 lookup_type, u8 port_id, bool sleep_ok);
 int t4_change_mac(struct adapter *adap, unsigned int mbox, unsigned int viid,
 		  int idx, const u8 *addr, bool persist, bool add_smt);
 int t4_enable_vi_params(struct adapter *adap, unsigned int mbox,
diff --git a/drivers/net/cxgbe/base/t4_hw.c b/drivers/net/cxgbe/base/t4_hw.c
index 31762c9c5..d60894115 100644
--- a/drivers/net/cxgbe/base/t4_hw.c
+++ b/drivers/net/cxgbe/base/t4_hw.c
@@ -4161,6 +4161,112 @@ int t4_set_rxmode(struct adapter *adap, unsigned int mbox, unsigned int viid,
 		return t4vf_wr_mbox(adap, &c, sizeof(c), NULL);
 }
 
+/**
+ *	t4_alloc_raw_mac_filt - Adds a raw mac entry in mps tcam
+ *	@adap: the adapter
+ *	@viid: the VI id
+ *	@mac: the MAC address
+ *	@mask: the mask
+ *	@idx: index at which to add this entry
+ *	@port_id: the port index
+ *	@lookup_type: MAC address for inner (1) or outer (0) header
+ *	@sleep_ok: call is allowed to sleep
+ *
+ *	Adds the mac entry at the specified index using raw mac interface.
+ *
+ *	Returns a negative error number or the allocated index for this mac.
+ */
+int t4_alloc_raw_mac_filt(struct adapter *adap, unsigned int viid,
+			  const u8 *addr, const u8 *mask, unsigned int idx,
+			  u8 lookup_type, u8 port_id, bool sleep_ok)
+{
+	int ret = 0;
+	struct fw_vi_mac_cmd c;
+	struct fw_vi_mac_raw *p = &c.u.raw;
+	u32 val;
+
+	memset(&c, 0, sizeof(c));
+	c.op_to_viid = cpu_to_be32(V_FW_CMD_OP(FW_VI_MAC_CMD) |
+				   F_FW_CMD_REQUEST | F_FW_CMD_WRITE |
+				   V_FW_VI_MAC_CMD_VIID(viid));
+	val = V_FW_CMD_LEN16(1) |
+	      V_FW_VI_MAC_CMD_ENTRY_TYPE(FW_VI_MAC_TYPE_RAW);
+	c.freemacs_to_len16 = cpu_to_be32(val);
+
+	/* Specify that this is an inner mac address */
+	p->raw_idx_pkd = cpu_to_be32(V_FW_VI_MAC_CMD_RAW_IDX(idx));
+
+	/* Lookup Type. Outer header: 0, Inner header: 1 */
+	p->data0_pkd = cpu_to_be32(V_DATALKPTYPE(lookup_type) |
+				   V_DATAPORTNUM(port_id));
+	/* Lookup mask and port mask */
+	p->data0m_pkd = cpu_to_be64(V_DATALKPTYPE(M_DATALKPTYPE) |
+				    V_DATAPORTNUM(M_DATAPORTNUM));
+
+	/* Copy the address and the mask */
+	memcpy((u8 *)&p->data1[0] + 2, addr, ETHER_ADDR_LEN);
+	memcpy((u8 *)&p->data1m[0] + 2, mask, ETHER_ADDR_LEN);
+
+	ret = t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, sleep_ok);
+	if (ret == 0) {
+		ret = G_FW_VI_MAC_CMD_RAW_IDX(be32_to_cpu(p->raw_idx_pkd));
+		if (ret != (int)idx)
+			ret = -ENOMEM;
+	}
+
+	return ret;
+}
+
+/**
+ *	t4_free_raw_mac_filt - Frees a raw mac entry in mps tcam
+ *	@adap: the adapter
+ *	@viid: the VI id
+ *	@addr: the MAC address
+ *	@mask: the mask
+ *	@idx: index of the entry in mps tcam
+ *	@lookup_type: MAC address for inner (1) or outer (0) header
+ *	@port_id: the port index
+ *	@sleep_ok: call is allowed to sleep
+ *
+ *	Removes the mac entry at the specified index using raw mac interface.
+ *
+ *	Returns a negative error number on failure.
+ */
+int t4_free_raw_mac_filt(struct adapter *adap, unsigned int viid,
+			 const u8 *addr, const u8 *mask, unsigned int idx,
+			 u8 lookup_type, u8 port_id, bool sleep_ok)
+{
+	struct fw_vi_mac_cmd c;
+	struct fw_vi_mac_raw *p = &c.u.raw;
+	u32 raw;
+
+	memset(&c, 0, sizeof(c));
+	c.op_to_viid = cpu_to_be32(V_FW_CMD_OP(FW_VI_MAC_CMD) |
+				   F_FW_CMD_REQUEST | F_FW_CMD_WRITE |
+				   V_FW_CMD_EXEC(0) |
+				   V_FW_VI_MAC_CMD_VIID(viid));
+	raw = V_FW_VI_MAC_CMD_ENTRY_TYPE(FW_VI_MAC_TYPE_RAW);
+	c.freemacs_to_len16 = cpu_to_be32(V_FW_VI_MAC_CMD_FREEMACS(0) |
+					  raw |
+					  V_FW_CMD_LEN16(1));
+
+	p->raw_idx_pkd = cpu_to_be32(V_FW_VI_MAC_CMD_RAW_IDX(idx) |
+				     FW_VI_MAC_ID_BASED_FREE);
+
+	/* Lookup Type. Outer header: 0, Inner header: 1 */
+	p->data0_pkd = cpu_to_be32(V_DATALKPTYPE(lookup_type) |
+				   V_DATAPORTNUM(port_id));
+	/* Lookup mask and port mask */
+	p->data0m_pkd = cpu_to_be64(V_DATALKPTYPE(M_DATALKPTYPE) |
+				    V_DATAPORTNUM(M_DATAPORTNUM));
+
+	/* Copy the address and the mask */
+	memcpy((u8 *)&p->data1[0] + 2, addr, ETHER_ADDR_LEN);
+	memcpy((u8 *)&p->data1m[0] + 2, mask, ETHER_ADDR_LEN);
+
+	return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, sleep_ok);
+}
+
 /**
  * t4_change_mac - modifies the exact-match filter for a MAC address
  * @adap: the adapter
diff --git a/drivers/net/cxgbe/base/t4_regs.h b/drivers/net/cxgbe/base/t4_regs.h
index 6f872edc2..af8c741e2 100644
--- a/drivers/net/cxgbe/base/t4_regs.h
+++ b/drivers/net/cxgbe/base/t4_regs.h
@@ -45,6 +45,14 @@
 #define MPS_T5_CLS_SRAM_H(idx) (A_MPS_T5_CLS_SRAM_H + (idx) * 8)
 #define NUM_MPS_T5_CLS_SRAM_H_INSTANCES 512
 
+#define S_DATAPORTNUM    12
+#define M_DATAPORTNUM    0xfU
+#define V_DATAPORTNUM(x) ((x) << S_DATAPORTNUM)
+
+#define S_DATALKPTYPE    10
+#define M_DATALKPTYPE    0x3U
+#define V_DATALKPTYPE(x) ((x) << S_DATALKPTYPE)
+
 /* registers for module SGE */
 #define SGE_BASE_ADDR 0x1000
 
diff --git a/drivers/net/cxgbe/base/t4fw_interface.h b/drivers/net/cxgbe/base/t4fw_interface.h
index 1c08637bb..e2d2ee897 100644
--- a/drivers/net/cxgbe/base/t4fw_interface.h
+++ b/drivers/net/cxgbe/base/t4fw_interface.h
@@ -1282,12 +1282,17 @@ struct fw_vi_cmd {
 /* Special VI_MAC command index ids */
 #define FW_VI_MAC_ADD_MAC		0x3FF
 #define FW_VI_MAC_ADD_PERSIST_MAC	0x3FE
+#define FW_VI_MAC_ID_BASED_FREE         0x3FC
 
 enum fw_vi_mac_smac {
 	FW_VI_MAC_MPS_TCAM_ENTRY,
 	FW_VI_MAC_SMT_AND_MPSTCAM
 };
 
+enum fw_vi_mac_entry_types {
+	FW_VI_MAC_TYPE_RAW = 0x2,
+};
+
 struct fw_vi_mac_cmd {
 	__be32 op_to_viid;
 	__be32 freemacs_to_len16;
@@ -1299,6 +1304,13 @@ struct fw_vi_mac_cmd {
 		struct fw_vi_mac_hash {
 			__be64 hashvec;
 		} hash;
+		struct fw_vi_mac_raw {
+			__be32 raw_idx_pkd;
+			__be32 data0_pkd;
+			__be32 data1[2];
+			__be64 data0m_pkd;
+			__be32 data1m[2];
+		} raw;
 	} u;
 };
 
@@ -1308,6 +1320,12 @@ struct fw_vi_mac_cmd {
 #define G_FW_VI_MAC_CMD_VIID(x)	\
 	(((x) >> S_FW_VI_MAC_CMD_VIID) & M_FW_VI_MAC_CMD_VIID)
 
+#define S_FW_VI_MAC_CMD_FREEMACS	31
+#define V_FW_VI_MAC_CMD_FREEMACS(x)	((x) << S_FW_VI_MAC_CMD_FREEMACS)
+
+#define S_FW_VI_MAC_CMD_ENTRY_TYPE      23
+#define V_FW_VI_MAC_CMD_ENTRY_TYPE(x)   ((x) << S_FW_VI_MAC_CMD_ENTRY_TYPE)
+
 #define S_FW_VI_MAC_CMD_VALID		15
 #define M_FW_VI_MAC_CMD_VALID		0x1
 #define V_FW_VI_MAC_CMD_VALID(x)	((x) << S_FW_VI_MAC_CMD_VALID)
@@ -1327,6 +1345,12 @@ struct fw_vi_mac_cmd {
 #define G_FW_VI_MAC_CMD_IDX(x)	\
 	(((x) >> S_FW_VI_MAC_CMD_IDX) & M_FW_VI_MAC_CMD_IDX)
 
+#define S_FW_VI_MAC_CMD_RAW_IDX         16
+#define M_FW_VI_MAC_CMD_RAW_IDX         0xffff
+#define V_FW_VI_MAC_CMD_RAW_IDX(x)      ((x) << S_FW_VI_MAC_CMD_RAW_IDX)
+#define G_FW_VI_MAC_CMD_RAW_IDX(x)      \
+	(((x) >> S_FW_VI_MAC_CMD_RAW_IDX) & M_FW_VI_MAC_CMD_RAW_IDX)
+
 struct fw_vi_rxmode_cmd {
 	__be32 op_to_viid;
 	__be32 retval_len16;
diff --git a/drivers/net/cxgbe/cxgbe_main.c b/drivers/net/cxgbe/cxgbe_main.c
index be2bc4213..20d2de442 100644
--- a/drivers/net/cxgbe/cxgbe_main.c
+++ b/drivers/net/cxgbe/cxgbe_main.c
@@ -39,6 +39,7 @@
 #include "cxgbe.h"
 #include "clip_tbl.h"
 #include "l2t.h"
+#include "mps_tcam.h"
 
 /**
  * Allocate a chunk of memory. The allocated memory is cleared.
@@ -1689,6 +1690,7 @@ void cxgbe_close(struct adapter *adapter)
 
 	if (adapter->flags & FULL_INIT_DONE) {
 		tid_free(&adapter->tids);
+		t4_cleanup_mpstcam(adapter);
 		t4_cleanup_clip_tbl(adapter);
 		t4_cleanup_l2t(adapter);
 		if (is_pf4(adapter))
@@ -1877,6 +1879,11 @@ int cxgbe_probe(struct adapter *adapter)
 			 "filter support disabled. Continuing\n");
 	}
 
+	adapter->mpstcam = t4_init_mpstcam(adapter);
+	if (!adapter->mpstcam)
+		dev_warn(adapter, "could not allocate mps tcam table."
+			 " Continuing\n");
+
 	if (is_hashfilter(adapter)) {
 		if (t4_read_reg(adapter, A_LE_DB_CONFIG) & F_HASHEN) {
 			u32 hash_base, hash_reg;
diff --git a/drivers/net/cxgbe/meson.build b/drivers/net/cxgbe/meson.build
index f6a442cb8..c51af26e9 100644
--- a/drivers/net/cxgbe/meson.build
+++ b/drivers/net/cxgbe/meson.build
@@ -9,6 +9,7 @@ sources = files('cxgbe_ethdev.c',
 	'cxgbe_filter.c',
 	'cxgbe_flow.c',
 	'clip_tbl.c',
+	'mps_tcam.c',
 	'l2t.c',
 	'base/t4_hw.c',
 	'base/t4vf_hw.c')
diff --git a/drivers/net/cxgbe/mps_tcam.c b/drivers/net/cxgbe/mps_tcam.c
new file mode 100644
index 000000000..02ec69a92
--- /dev/null
+++ b/drivers/net/cxgbe/mps_tcam.c
@@ -0,0 +1,243 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2018 Chelsio Communications.
+ * All rights reserved.
+ */
+
+#include "mps_tcam.h"
+
+static inline bool
+match_entry(struct mps_tcam_entry *entry, const u8 *eth_addr, const u8 *mask)
+{
+	if (!memcmp(eth_addr, entry->eth_addr, ETHER_ADDR_LEN) &&
+	    !memcmp(mask, entry->mask, ETHER_ADDR_LEN))
+		return true;
+	return false;
+}
+
+static int cxgbe_update_free_idx(struct mpstcam_table *t)
+{
+	struct mps_tcam_entry *entry = t->entry;
+	u16 i, next = t->free_idx + 1;
+
+	if (entry[t->free_idx].state == MPS_ENTRY_UNUSED)
+		/* You are already pointing to a free entry !! */
+		return 0;
+
+	/* loop, till we don't rollback to same index where we started */
+	for (i = next; i != t->free_idx; i++) {
+		if (i == t->size)
+			/* rollback and search free entry from start */
+			i = 0;
+
+		if (entry[i].state == MPS_ENTRY_UNUSED) {
+			t->free_idx = i;
+			return 0;
+		}
+	}
+
+	return -1;	/* table is full */
+}
+
+static struct mps_tcam_entry *
+cxgbe_mpstcam_lookup(struct mpstcam_table *t, const u8 *eth_addr,
+		     const u8 *mask)
+{
+	struct mps_tcam_entry *entry = t->entry;
+	int i;
+
+	if (!entry)
+		return NULL;
+
+	for (i = 0; i < t->size; i++) {
+		if (entry[i].state == MPS_ENTRY_UNUSED)
+			continue;	/* entry is not being used */
+		if (match_entry(&entry[i], eth_addr, mask))
+			return &entry[i];
+	}
+
+	return NULL;
+}
+
+int cxgbe_mpstcam_alloc(struct port_info *pi, const u8 *eth_addr,
+			const u8 *mask)
+{
+	struct adapter *adap = pi->adapter;
+	struct mpstcam_table *mpstcam = adap->mpstcam;
+	struct mps_tcam_entry *entry;
+	int ret;
+
+	if (!adap->mpstcam) {
+		dev_err(adap, "mpstcam table is not available\n");
+		return -EOPNOTSUPP;
+	}
+
+	/* If entry already present, return it. */
+	t4_os_write_lock(&mpstcam->lock);
+	entry = cxgbe_mpstcam_lookup(adap->mpstcam, eth_addr, mask);
+	if (entry) {
+		rte_atomic32_add(&entry->refcnt, 1);
+		t4_os_write_unlock(&mpstcam->lock);
+		return entry->idx;
+	}
+
+	if (mpstcam->full) {
+		t4_os_write_unlock(&mpstcam->lock);
+		dev_err(adap, "mps-tcam table is full\n");
+		return -ENOMEM;
+	}
+
+	ret = t4_alloc_raw_mac_filt(adap, pi->viid, eth_addr, mask,
+				    mpstcam->free_idx, 0, pi->port_id, false);
+	if (ret <= 0) {
+		t4_os_write_unlock(&mpstcam->lock);
+		return ret;
+	}
+
+	/* Fill in the new values */
+	entry = &mpstcam->entry[ret];
+	memcpy(entry->eth_addr, eth_addr, ETHER_ADDR_LEN);
+	memcpy(entry->mask, mask, ETHER_ADDR_LEN);
+	rte_atomic32_set(&entry->refcnt, 1);
+	entry->state = MPS_ENTRY_USED;
+
+	if (cxgbe_update_free_idx(mpstcam))
+		mpstcam->full = true;
+
+	t4_os_write_unlock(&mpstcam->lock);
+	return ret;
+}
+
+int cxgbe_mpstcam_modify(struct port_info *pi, int idx, const u8 *addr)
+{
+	struct adapter *adap = pi->adapter;
+	struct mpstcam_table *mpstcam = adap->mpstcam;
+	struct mps_tcam_entry *entry;
+
+	if (!mpstcam)
+		return -EOPNOTSUPP;
+	t4_os_write_lock(&mpstcam->lock);
+	if (idx != -1 && idx >= mpstcam->size) {
+		t4_os_write_unlock(&mpstcam->lock);
+		return -EINVAL;
+	}
+	if (idx >= 0) {
+		entry = &mpstcam->entry[idx];
+		/* user wants to modify an existing entry.
+		 * verify if entry exists
+		 */
+		if (entry->state != MPS_ENTRY_USED) {
+			t4_os_write_unlock(&mpstcam->lock);
+			return -EINVAL;
+		}
+	}
+
+	idx = t4_change_mac(adap, adap->mbox, pi->viid, idx, addr, true, true);
+	if (idx < 0) {
+		t4_os_write_unlock(&mpstcam->lock);
+		return idx;
+	}
+
+	/* idx can now be different from what user provided */
+	entry = &mpstcam->entry[idx];
+	memcpy(entry->eth_addr, addr, ETHER_ADDR_LEN);
+	/* NOTE: we have considered the case that idx returned by t4_change_mac
+	 * will be different from the user provided value only if user
+	 * provided value is -1
+	 */
+	if (entry->state == MPS_ENTRY_UNUSED) {
+		rte_atomic32_set(&entry->refcnt, 1);
+		entry->state = MPS_ENTRY_USED;
+	}
+
+	if (cxgbe_update_free_idx(mpstcam))
+		mpstcam->full = true;
+
+	t4_os_write_unlock(&mpstcam->lock);
+	return idx;
+}
+
+/**
+ * hold appropriate locks while calling this.
+ */
+static inline void reset_mpstcam_entry(struct mps_tcam_entry *entry)
+{
+	memset(entry->eth_addr, 0, ETHER_ADDR_LEN);
+	memset(entry->mask, 0, ETHER_ADDR_LEN);
+	rte_atomic32_clear(&entry->refcnt);
+	entry->state = MPS_ENTRY_UNUSED;
+}
+
+/**
+ * ret < 0: fatal error
+ * ret = 0: entry removed in h/w
+ * ret > 0: updated refcount.
+ */
+int cxgbe_mpstcam_remove(struct port_info *pi, u16 idx)
+{
+	struct adapter *adap = pi->adapter;
+	struct mpstcam_table *t = adap->mpstcam;
+	struct mps_tcam_entry *entry;
+	int ret;
+
+	if (!t)
+		return -EOPNOTSUPP;
+	t4_os_write_lock(&t->lock);
+	entry = &t->entry[idx];
+	if (entry->state == MPS_ENTRY_UNUSED) {
+		t4_os_write_unlock(&t->lock);
+		return -EINVAL;
+	}
+
+	if (rte_atomic32_read(&entry->refcnt) == 1)
+		ret = t4_free_raw_mac_filt(adap, pi->viid, entry->eth_addr,
+					   entry->mask, idx, 1, pi->port_id,
+					   false);
+	else
+		ret = rte_atomic32_sub_return(&entry->refcnt, 1);
+
+	if (ret == 0) {
+		reset_mpstcam_entry(entry);
+		t->full = false;	/* We have atleast 1 free entry */
+		cxgbe_update_free_idx(t);
+	}
+
+	t4_os_write_unlock(&t->lock);
+	return ret;
+}
+
+struct mpstcam_table *t4_init_mpstcam(struct adapter *adap)
+{
+	struct mpstcam_table *t;
+	int i;
+	u16 size = adap->params.arch.mps_tcam_size;
+
+	t =  t4_os_alloc(sizeof(*t) + size * sizeof(struct mps_tcam_entry));
+	if (!t)
+		return NULL;
+
+	t4_os_rwlock_init(&t->lock);
+	t->full = false;
+	t->size = size;
+
+	for (i = 0; i < size; i++) {
+		reset_mpstcam_entry(&t->entry[i]);
+		t->entry[i].mpstcam = t;
+		t->entry[i].idx = i;
+	}
+
+	/* first entry is used by chip. this is overwritten only
+	 * in t4_cleanup_mpstcam()
+	 */
+	t->entry[0].state = MPS_ENTRY_USED;
+	t->free_idx = 1;
+
+	return t;
+}
+
+void t4_cleanup_mpstcam(struct adapter *adap)
+{
+	if (adap->mpstcam) {
+		t4_os_free(adap->mpstcam->entry);
+		t4_os_free(adap->mpstcam);
+	}
+}
diff --git a/drivers/net/cxgbe/mps_tcam.h b/drivers/net/cxgbe/mps_tcam.h
new file mode 100644
index 000000000..c3d6fe0d7
--- /dev/null
+++ b/drivers/net/cxgbe/mps_tcam.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2018 Chelsio Communications.
+ * All rights reserved.
+ */
+
+#ifndef _CXGBE_MPSTCAM_H_
+#define _CXGBE_MPSTCAM_H_
+
+#include "common.h"
+
+enum {
+	MPS_ENTRY_UNUSED,	/* Keep this first so memset 0 renders
+				 * the correct state. Other states can
+				 * be added in future like MPS_ENTRY_BUSY
+				 * to reduce contention while mboxing
+				 * the request to f/w or to denote attributes
+				 * for a specific entry
+				 */
+	MPS_ENTRY_USED,
+};
+
+struct mps_tcam_entry {
+	u8 state;
+	u16 idx;
+
+	/* add data here which uniquely defines an entry */
+	u8 eth_addr[ETHER_ADDR_LEN];
+	u8 mask[ETHER_ADDR_LEN];
+
+	struct mpstcam_table *mpstcam; /* backptr */
+	rte_atomic32_t refcnt;
+};
+
+struct mpstcam_table {
+	u16 size;
+	rte_rwlock_t lock;
+	u16 free_idx;	/* next free index */
+	bool full;	/* since free index can be present
+			 * anywhere in the table, size and
+			 * free_idx cannot alone determine
+			 * if the table is full
+			 */
+	struct mps_tcam_entry entry[0];
+};
+
+struct mpstcam_table *t4_init_mpstcam(struct adapter *adap);
+void t4_cleanup_mpstcam(struct adapter *adap);
+int cxgbe_mpstcam_alloc(struct port_info *pi, const u8 *mac, const u8 *mask);
+int cxgbe_mpstcam_remove(struct port_info *pi, u16 idx);
+int cxgbe_mpstcam_modify(struct port_info *pi, int idx, const u8 *addr);
+
+#endif /* _CXGBE_MPSTCAM_H_ */
