[][openwrt][mt7988][pce][TOPS Alpha release]

[Description]
Add alpha version of PCE(packet classification engine) package
The PCE supports packet classification HW features such as packet header,
partial user data and destination IP match. It also provides support to
setup HW crypto offload essential data.

[Release-log]
N/A

Change-Id: Ia1826dcc6e6969ef65bed6ae3a70ce761799a6b9
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/7852682
diff --git a/package-21.02/kernel/pce/src/Makefile b/package-21.02/kernel/pce/src/Makefile
new file mode 100644
index 0000000..7620748
--- /dev/null
+++ b/package-21.02/kernel/pce/src/Makefile
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (c) 2023, Mediatek Inc. All Rights Reserved.
+# Author: Ren-Ting.Wang <ren-ting.wang@mediatek.com>
+
+ccflags-y += -I$(src)/inc
+
+obj-$(CONFIG_MTK_PCE) += pce.o
+
+pce-y += cdrt.o
+pce-y += cls.o
+pce-y += dipfilter.o
+pce-y += init.o
+pce-y += netsys.o
+pce-y += tport_map.o
+pce-y += debugfs.o
diff --git a/package-21.02/kernel/pce/src/cdrt.c b/package-21.02/kernel/pce/src/cdrt.c
new file mode 100644
index 0000000..5c71e34
--- /dev/null
+++ b/package-21.02/kernel/pce/src/cdrt.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/lockdep.h>
+#include <linux/spinlock.h>
+
+#include "pce/cdrt.h"
+#include "pce/internal.h"
+#include "pce/netsys.h"
+
+/* decrypt use cdrt idx 1 ~ 15, encrypt use cdrt idx 16 ~ 31 */
+#define CDRT_ENC_MAX_ENTRY		16
+#define CDRT_ENC_IDX_OFS		16
+#define CDRT_DEC_MAX_ENTRY		15
+#define CDRT_DEC_IDX_OFS		1
+
+struct cdrt_hardware {
+	DECLARE_BITMAP(enc_used, CDRT_ENC_MAX_ENTRY);
+	DECLARE_BITMAP(dec_used, CDRT_DEC_MAX_ENTRY);
+	struct cdrt_entry enc_tbl[CDRT_ENC_MAX_ENTRY];
+	struct cdrt_entry dec_tbl[CDRT_DEC_MAX_ENTRY];
+	spinlock_t lock;
+};
+
+struct cdrt_hardware cdrt_hw;
+
+int mtk_pce_cdrt_update_cls_rule(u32 cdrt_idx)
+{
+	struct cdrt_entry *cdrt;
+
+	if (!cdrt_idx || cdrt_idx >= FE_MEM_CDRT_MAX_INDEX)
+		return -EINVAL;
+
+	if (cdrt_idx >= CDRT_ENC_MAX_ENTRY)
+		cdrt = &cdrt_hw.enc_tbl[cdrt_idx - CDRT_ENC_IDX_OFS];
+	else
+		cdrt = &cdrt_hw.dec_tbl[cdrt_idx - CDRT_DEC_IDX_OFS];
+
+	if (cdrt->update_cls_rule)
+		return cdrt->update_cls_rule(cdrt);
+
+	return 0;
+}
+
+int mtk_pce_cdrt_desc_write(struct cdrt_desc *desc, u32 idx)
+{
+	struct fe_mem_msg msg;
+	int ret;
+
+	if (unlikely(!desc || idx >= FE_MEM_CDRT_MAX_INDEX / 3))
+		return -EINVAL;
+
+	memset(&msg.raw, 0, sizeof(msg.raw));
+
+	/* write CDR 0 ~ 3 */
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_WRITE, FE_MEM_TYPE_CDRT,
+				  3 * idx);
+	memcpy(&msg.raw, &desc->raw1, sizeof(desc->raw1));
+
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+	if (ret)
+		return ret;
+
+	/* write CDR 4 ~ 7 */
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_WRITE, FE_MEM_TYPE_CDRT,
+				  3 * idx + 1);
+	memcpy(&msg.raw, &desc->raw2, sizeof(desc->raw2));
+
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+	if (ret)
+		return ret;
+
+	/* write CDR 8 ~ 11 */
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_WRITE, FE_MEM_TYPE_CDRT,
+				  3 * idx + 2);
+	memcpy(&msg.raw, &desc->raw3, sizeof(desc->raw3));
+
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+
+	return ret;
+}
+
+int mtk_pce_cdrt_desc_read(struct cdrt_desc *desc, u32 idx)
+{
+	struct fe_mem_msg msg;
+	int ret;
+
+	if (unlikely(!desc || idx >= FE_MEM_CDRT_MAX_INDEX / 3))
+		return -EINVAL;
+
+	memset(&msg.raw, 0, sizeof(msg.raw));
+
+	/* read CDR 0 ~ 3 */
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_READ, FE_MEM_TYPE_CDRT,
+				  3 * idx);
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+	if (ret)
+		return ret;
+
+	memcpy(&desc->raw1, &msg.raw, sizeof(desc->raw1));
+
+	/* read CDR 4 ~ 7 */
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_READ, FE_MEM_TYPE_CDRT,
+				  3 * idx + 1);
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+	if (ret)
+		return ret;
+
+	memcpy(&desc->raw2, &msg.raw, sizeof(desc->raw2));
+
+	/* read CDR 8 ~ 11 */
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_READ, FE_MEM_TYPE_CDRT,
+				  3 * idx + 2);
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+	if (ret)
+		return ret;
+
+	memcpy(&desc->raw3, &msg.raw, sizeof(desc->raw3));
+
+	return ret;
+}
+
+int mtk_pce_cdrt_entry_write(struct cdrt_entry *cdrt)
+{
+	if (unlikely(!cdrt))
+		return -EINVAL;
+
+	return mtk_pce_cdrt_desc_write(&cdrt->desc, cdrt->idx);
+}
+EXPORT_SYMBOL(mtk_pce_cdrt_entry_write);
+
+static struct cdrt_entry *mtk_pce_cdrt_entry_encrypt_alloc(void)
+{
+	u32 idx;
+
+	lockdep_assert_held(&cdrt_hw.lock);
+
+	idx = find_first_zero_bit(cdrt_hw.enc_used, CDRT_ENC_MAX_ENTRY);
+	if (idx == CDRT_ENC_MAX_ENTRY)
+		return ERR_PTR(-ENOMEM);
+
+	set_bit(idx, cdrt_hw.enc_used);
+
+	return &cdrt_hw.enc_tbl[idx];
+}
+
+static struct cdrt_entry *mtk_pce_cdrt_entry_decrypt_alloc(void)
+{
+	u32 idx;
+
+	lockdep_assert_held(&cdrt_hw.lock);
+
+	idx = find_first_zero_bit(cdrt_hw.dec_used, CDRT_DEC_MAX_ENTRY);
+	if (idx == CDRT_DEC_MAX_ENTRY)
+		return ERR_PTR(-ENOMEM);
+
+	set_bit(idx, cdrt_hw.dec_used);
+
+	return &cdrt_hw.dec_tbl[idx];
+}
+
+struct cdrt_entry *mtk_pce_cdrt_entry_alloc(enum cdrt_type type)
+{
+	struct cdrt_entry *cdrt;
+	unsigned long flag;
+
+	if (type >= __CDRT_TYPE_MAX)
+		return ERR_PTR(-EINVAL);
+
+	spin_lock_irqsave(&cdrt_hw.lock, flag);
+
+	if (type == CDRT_ENCRYPT)
+		cdrt = mtk_pce_cdrt_entry_encrypt_alloc();
+	else
+		cdrt = mtk_pce_cdrt_entry_decrypt_alloc();
+
+	spin_unlock_irqrestore(&cdrt_hw.lock, flag);
+
+	return cdrt;
+}
+EXPORT_SYMBOL(mtk_pce_cdrt_entry_alloc);
+
+void mtk_pce_cdrt_entry_free(struct cdrt_entry *cdrt)
+{
+	unsigned long flag;
+
+	if (!cdrt)
+		return;
+
+	cdrt->cls = NULL;
+	cdrt->update_cls_rule = NULL;
+
+	memset(&cdrt->desc.raw1, 0, sizeof(cdrt->desc.raw1));
+	memset(&cdrt->desc.raw2, 0, sizeof(cdrt->desc.raw2));
+	memset(&cdrt->desc.raw3, 0, sizeof(cdrt->desc.raw3));
+
+	spin_lock_irqsave(&cdrt_hw.lock, flag);
+
+	if (cdrt->type == CDRT_ENCRYPT)
+		clear_bit(cdrt->idx, cdrt_hw.enc_used);
+	else
+		clear_bit(cdrt->idx, cdrt_hw.dec_used);
+
+	spin_unlock_irqrestore(&cdrt_hw.lock, flag);
+}
+EXPORT_SYMBOL(mtk_pce_cdrt_entry_free);
+
+static void mtk_pce_cdrt_clean_up(void)
+{
+	struct fe_mem_msg msg = {
+		.cmd = FE_MEM_CMD_WRITE,
+		.type = FE_MEM_TYPE_CDRT,
+	};
+	unsigned long flag;
+	int ret = 0;
+	u32 i;
+
+	memset(&msg.raw, 0, sizeof(msg.raw));
+
+	spin_lock_irqsave(&cdrt_hw.lock, flag);
+
+	for (i = 0; i < FE_MEM_CDRT_MAX_INDEX; i++) {
+		msg.index = i;
+
+		ret = mtk_pce_fe_mem_msg_send(&msg);
+		if (ret)
+			return;
+
+		/* TODO: clean up bit map? */
+	}
+
+	spin_unlock_irqrestore(&cdrt_hw.lock, flag);
+}
+
+int mtk_pce_cdrt_enable(void)
+{
+	mtk_pce_netsys_setbits(GLO_MEM_CFG, CDM_CDRT_EN);
+
+	return 0;
+}
+
+void mtk_pce_cdrt_disable(void)
+{
+	mtk_pce_netsys_clrbits(GLO_MEM_CFG, CDM_CDRT_EN);
+}
+
+int mtk_pce_cdrt_init(struct platform_device *pdev)
+{
+	u32 i;
+
+	spin_lock_init(&cdrt_hw.lock);
+
+	mtk_pce_cdrt_clean_up();
+
+	for (i = 0; i < CDRT_DEC_MAX_ENTRY; i++) {
+		cdrt_hw.dec_tbl[i].idx = i + CDRT_DEC_IDX_OFS;
+		cdrt_hw.dec_tbl[i].type = CDRT_DECRYPT;
+	}
+
+	for (i = 0; i < CDRT_ENC_MAX_ENTRY; i++) {
+		cdrt_hw.enc_tbl[i].idx = i + CDRT_ENC_IDX_OFS;
+		cdrt_hw.enc_tbl[i].type = CDRT_ENCRYPT;
+	}
+
+	return 0;
+}
+
+void mtk_pce_cdrt_deinit(struct platform_device *pdev)
+{
+	mtk_pce_cdrt_clean_up();
+}
diff --git a/package-21.02/kernel/pce/src/cls.c b/package-21.02/kernel/pce/src/cls.c
new file mode 100644
index 0000000..847dff8
--- /dev/null
+++ b/package-21.02/kernel/pce/src/cls.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#include <linux/err.h>
+#include <linux/lockdep.h>
+#include <linux/spinlock.h>
+
+#include "pce/cls.h"
+#include "pce/internal.h"
+#include "pce/netsys.h"
+
+struct cls_hw {
+	struct cls_entry *cls_tbl[FE_MEM_CLS_MAX_INDEX];
+	spinlock_t lock;
+};
+
+struct cls_hw cls_hw;
+
+int mtk_pce_cls_enable(void)
+{
+	mtk_pce_netsys_setbits(GLO_MEM_CFG, GDM_CLS_EN);
+
+	return 0;
+}
+
+void mtk_pce_cls_disable(void)
+{
+	mtk_pce_netsys_clrbits(GLO_MEM_CFG, GDM_CLS_EN);
+}
+
+static void mtk_pce_cls_clean_up(void)
+{
+	struct fe_mem_msg msg = {
+		.cmd = FE_MEM_CMD_WRITE,
+		.type = FE_MEM_TYPE_CLS,
+	};
+	unsigned long flag;
+	int ret = 0;
+	u32 i = 0;
+
+	memset(&msg.raw, 0, sizeof(msg.raw));
+
+	spin_lock_irqsave(&cls_hw.lock, flag);
+
+	/* clean up cls table */
+	for (i = 0; i < FE_MEM_CLS_MAX_INDEX; i++) {
+		msg.index = i;
+		ret = mtk_pce_fe_mem_msg_send(&msg);
+		if (ret)
+			goto unlock;
+	}
+
+unlock:
+	spin_unlock_irqrestore(&cls_hw.lock, flag);
+}
+
+int mtk_pce_cls_init(struct platform_device *pdev)
+{
+	spin_lock_init(&cls_hw.lock);
+
+	mtk_pce_cls_clean_up();
+
+	return 0;
+}
+
+void mtk_pce_cls_deinit(struct platform_device *pdev)
+{
+	mtk_pce_cls_clean_up();
+}
+
+/*
+ * Read a cls_desc without checking it is in used or not
+ * cls_hw.lock should be held before calling this function
+ */
+static int __mtk_pce_cls_desc_read(struct cls_desc *cdesc, enum cls_entry_type entry)
+{
+	struct fe_mem_msg msg;
+	int ret;
+
+	lockdep_assert_held(&cls_hw.lock);
+
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_READ, FE_MEM_TYPE_CLS, entry);
+
+	memset(&msg.raw, 0, sizeof(msg.raw));
+
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+	if (ret)
+		return ret;
+
+	memcpy(cdesc, &msg.cdesc, sizeof(struct cls_desc));
+
+	return ret;
+}
+
+/*
+ * Read a cls_desc without checking it is in used or not
+ * This function is only used for debugging purpose
+ */
+int mtk_pce_cls_desc_read(struct cls_desc *cdesc, enum cls_entry_type entry)
+{
+	unsigned long flag;
+	int ret;
+
+	if (unlikely(entry == CLS_ENTRY_NONE || entry >= __CLS_ENTRY_MAX)) {
+		PCE_ERR("invalid cls entry: %u\n", entry);
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&cls_hw.lock, flag);
+	ret = __mtk_pce_cls_desc_read(cdesc, entry);
+	spin_unlock_irqrestore(&cls_hw.lock, flag);
+
+	return ret;
+}
+
+/*
+ * Write a cls_desc to an entry without checking the entry is occupied
+ * cls_hw.lock should be held before calling this function
+ */
+static int __mtk_pce_cls_desc_write(struct cls_desc *cdesc,
+				    enum cls_entry_type entry)
+{
+	struct fe_mem_msg msg;
+
+	lockdep_assert_held(&cls_hw.lock);
+
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_WRITE, FE_MEM_TYPE_CLS, entry);
+
+	memset(&msg.raw, 0, sizeof(msg.raw));
+	memcpy(&msg.cdesc, cdesc, sizeof(struct cls_desc));
+
+	return mtk_pce_fe_mem_msg_send(&msg);
+}
+
+/*
+ * Write a cls_desc to an entry without checking the entry is used by others.
+ * The user should check the entry is occupied by themselves or use the standard API
+ * mtk_pce_cls_entry_register().
+ *
+ * This function is only used for debugging purpose
+ */
+int mtk_pce_cls_desc_write(struct cls_desc *cdesc, enum cls_entry_type entry)
+{
+	unsigned long flag;
+	int ret;
+
+	if (unlikely(!cdesc))
+		return -EINVAL;
+
+	spin_lock_irqsave(&cls_hw.lock, flag);
+	ret = __mtk_pce_cls_desc_write(cdesc, entry);
+	spin_unlock_irqrestore(&cls_hw.lock, flag);
+
+	return ret;
+}
+
+int mtk_pce_cls_entry_register(struct cls_entry *cls)
+{
+	unsigned long flag;
+	int ret = 0;
+
+	if (unlikely(!cls))
+		return -EINVAL;
+
+	if (unlikely(cls->entry == CLS_ENTRY_NONE || cls->entry >= __CLS_ENTRY_MAX)) {
+		PCE_ERR("invalid cls entry: %u\n", cls->entry);
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&cls_hw.lock, flag);
+
+	if (cls_hw.cls_tbl[cls->entry]) {
+		PCE_ERR("cls rules already registered ofr entry: %u\n", cls->entry);
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	ret = __mtk_pce_cls_desc_write(&cls->cdesc, cls->entry);
+	if (ret) {
+		PCE_NOTICE("send cls message failed: %d\n", ret);
+		goto unlock;
+	}
+
+	cls_hw.cls_tbl[cls->entry] = cls;
+
+unlock:
+	spin_unlock_irqrestore(&cls_hw.lock, flag);
+
+	return ret;
+}
+EXPORT_SYMBOL(mtk_pce_cls_entry_register);
+
+void mtk_pce_cls_entry_unregister(struct cls_entry *cls)
+{
+	struct cls_desc cdesc;
+	unsigned long flag;
+	int ret = 0;
+
+	if (unlikely(!cls))
+		return;
+
+	if (unlikely(cls->entry == CLS_ENTRY_NONE || cls->entry >= __CLS_ENTRY_MAX)) {
+		PCE_ERR("invalid cls entry: %u\n", cls->entry);
+		return;
+	}
+
+	spin_lock_irqsave(&cls_hw.lock, flag);
+
+	if (cls_hw.cls_tbl[cls->entry] != cls) {
+		PCE_ERR("cls rules is registered by others\n");
+		goto unlock;
+	}
+
+	memset(&cdesc, 0, sizeof(struct cls_desc));
+
+	ret = __mtk_pce_cls_desc_write(&cdesc, cls->entry);
+	if (ret) {
+		PCE_NOTICE("fe send cls message failed: %d\n", ret);
+		goto unlock;
+	}
+
+	cls_hw.cls_tbl[cls->entry] = NULL;
+
+unlock:
+	spin_unlock_irqrestore(&cls_hw.lock, flag);
+}
+EXPORT_SYMBOL(mtk_pce_cls_entry_unregister);
diff --git a/package-21.02/kernel/pce/src/debugfs.c b/package-21.02/kernel/pce/src/debugfs.c
new file mode 100644
index 0000000..6e925fd
--- /dev/null
+++ b/package-21.02/kernel/pce/src/debugfs.c
@@ -0,0 +1,719 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/inet.h>
+#include <linux/uaccess.h>
+
+#include "pce/cls.h"
+#include "pce/debugfs.h"
+#include "pce/dipfilter.h"
+#include "pce/internal.h"
+#include "pce/netsys.h"
+#include "pce/pce.h"
+#include "pce/tport_map.h"
+
+#define CLS_ENTRY_DBG_CFG(_name, _type, func)				\
+	{								\
+		.name = #_name,						\
+		.type = _type,						\
+		.func = cls_entry_debug_fill_ ## _name,			\
+	}
+
+#define CLS_ENTRY_DBG_CFG_MASK_DATA(_name)				\
+	CLS_ENTRY_DBG_CFG(_name, CLS_ENTRY_MASK_DATA, fill_mask_data)
+
+#define CLS_ENTRY_DBG_CFG_DATA(_name)					\
+	CLS_ENTRY_DBG_CFG(_name, CLS_ENTRY_DATA, fill_data)
+
+enum cls_entry_config_type {
+	CLS_ENTRY_MASK_DATA,
+	CLS_ENTRY_DATA,
+};
+
+struct cls_entry_debug_config {
+	const char *name;
+	enum cls_entry_config_type type;
+
+	union {
+		int (*fill_mask_data)(struct cls_desc *cdesc, u32 mask, u32 data);
+		int (*fill_data)(struct cls_desc *cdesc, u32 data);
+	};
+};
+
+struct dentry *debug_root;
+
+static void cls_entry_debug_show_l4(struct seq_file *s, void *priv,
+				    struct cls_desc *cdesc)
+{
+	bool l4_valid = false;
+	u32 l4_data_m = 0;
+	u32 l4_data = 0;
+
+	seq_printf(s, "\t\tL4 valid      mask: 0x%X,    data: 0x%X\n",
+		   cdesc->l4_valid_m, cdesc->l4_valid);
+
+	if ((cdesc->l4_valid_m & CLS_DESC_VALID_DPORT_BIT)
+	    && (cdesc->l4_valid & CLS_DESC_VALID_DPORT_BIT))
+		seq_printf(s, "\t\t\tL4 Dport              mask: 0x%04X,     data: %05u\n",
+			   cdesc->l4_dport_m, cdesc->l4_dport);
+
+	if ((cdesc->l4_valid_m & CLS_DESC_VALID_LOWER_HALF_WORD_BIT)
+	    && (cdesc->l4_valid & CLS_DESC_VALID_LOWER_HALF_WORD_BIT)) {
+		l4_valid = true;
+		l4_data_m |= cdesc->l4_hdr_usr_data_m & 0xFFFF;
+		l4_data |= cdesc->l4_hdr_usr_data & 0xFFFF;
+	}
+
+	if ((cdesc->l4_valid_m & CLS_DESC_VALID_UPPER_HALF_WORD_BIT)
+	    && (cdesc->l4_valid & CLS_DESC_VALID_UPPER_HALF_WORD_BIT)) {
+		l4_valid = true;
+		l4_data_m |= ((cdesc->l4_hdr_usr_data_m & 0xFFFF0000) >> 16);
+		l4_data |= ((cdesc->l4_hdr_usr_data & 0xFFFF0000) >> 16);
+	}
+
+	if (!l4_valid)
+		return;
+
+	if ((cdesc->tag_m & CLS_DESC_TAG_MATCH_L4_HDR)
+	    && (cdesc->tag & CLS_DESC_TAG_MATCH_L4_HDR)) {
+		seq_printf(s, "\t\t\tL4 header not empty   mask: 0x%X,        data: 0x%X\n",
+			   cdesc->l4_udp_hdr_nez_m, cdesc->l4_udp_hdr_nez);
+		seq_printf(s, "\t\t\tL4 header             mask: 0x%08X, data: 0x%08X\n",
+			   cdesc->l4_hdr_usr_data_m, cdesc->l4_hdr_usr_data);
+	} else {
+		seq_printf(s, "\t\t\tUDP lite valid        mask: 0x%X,        data: 0x%X\n",
+			   cdesc->l4_udp_hdr_nez_m, cdesc->l4_udp_hdr_nez);
+		seq_printf(s, "\t\t\tL4 user data          mask: 0x%08X, data: 0x%08X\n",
+			   cdesc->l4_hdr_usr_data_m, cdesc->l4_hdr_usr_data);
+	}
+}
+
+static int cls_entry_debug_read(struct seq_file *s, void *private)
+{
+	struct cls_desc cdesc;
+	bool cls_enabled;
+	int ret;
+	u32 i;
+
+	for (i = CLS_ENTRY_NONE + 1; i < FE_MEM_CLS_MAX_INDEX; i++) {
+		ret = mtk_pce_cls_desc_read(&cdesc, i);
+
+		if (ret) {
+			PCE_ERR("read cls desc: %u failed: %d\n", i, ret);
+			return 0;
+		}
+
+		cls_enabled = (cdesc.tag && cdesc.tag_m
+			       && cdesc.tag == (cdesc.tag_m & cdesc.tag));
+
+		seq_printf(s, "CLS Entry%02u, Tag mask: 0x%X, data: 0x%X, enabled: %u\n",
+			   i, cdesc.tag_m, cdesc.tag, cls_enabled);
+
+		/* cls entry is not enabled */
+		if (!cls_enabled)
+			continue;
+
+		/* CLS descriptor's tag data should only set 1 bit of [1:0] */
+		if (cdesc.tag ==
+		    (CLS_DESC_TAG_MATCH_L4_HDR | CLS_DESC_TAG_MATCH_L4_USR)) {
+			seq_puts(s, "Invalid CLS descriptor tag\n");
+			continue;
+		}
+
+		seq_puts(s, "\tAction:\n");
+		seq_printf(s, "\t\tFport: %02u, Tport: %02u, TOPS_ENTRY: %02u, CDRT: %02u, QID: %u, DR_IDX: %u\n",
+			   cdesc.fport, cdesc.tport_idx,
+			   cdesc.tops_entry, cdesc.cdrt_idx,
+			   cdesc.qid, cdesc.dr_idx);
+		seq_puts(s, "\tContent:\n");
+		seq_printf(s, "\t\tDIP match     mask: 0x%X,    data: 0x%X\n",
+			   cdesc.dip_match_m, cdesc.dip_match);
+		seq_printf(s, "\t\tL4 protocol   mask: 0x%02X,   data: 0x%02X\n",
+			   cdesc.l4_type_m, cdesc.l4_type);
+
+		cls_entry_debug_show_l4(s, private, &cdesc);
+
+		seq_printf(s, "\t\tSport map     mask: 0x%04X, data: 0x%04X\n",
+			   cdesc.sport_m, cdesc.sport);
+	}
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_tag(struct cls_desc *cdesc, u32 mask, u32 data)
+{
+	if (mask > CLS_DESC_TAG_MASK || data > CLS_DESC_TAG_MASK)
+		return -EINVAL;
+
+	cdesc->tag_m = mask;
+	cdesc->tag = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_udplite_l4_hdr_nez(struct cls_desc *cdesc,
+					       u32 mask, u32 data)
+{
+	if (mask > CLS_DESC_UDPLITE_L4_HDR_NEZ_MASK
+	    || data > CLS_DESC_UDPLITE_L4_HDR_NEZ_MASK)
+		return -EINVAL;
+
+	cdesc->l4_udp_hdr_nez_m = mask;
+	cdesc->l4_udp_hdr_nez = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_dip_match(struct cls_desc *cdesc, u32 mask, u32 data)
+{
+	if (mask > CLS_DESC_DIP_MATCH || data > CLS_DESC_DIP_MATCH)
+		return -EINVAL;
+
+	cdesc->dip_match_m = mask;
+	cdesc->dip_match = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_l4_protocol(struct cls_desc *cdesc,
+					    u32 mask, u32 data)
+{
+	if (mask > CLS_DESC_L4_TYPE_MASK || data > CLS_DESC_L4_TYPE_MASK)
+		return -EINVAL;
+
+	cdesc->l4_type_m = mask;
+	cdesc->l4_type = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_l4_valid(struct cls_desc *cdesc, u32 mask, u32 data)
+{
+	if (mask > CLS_DESC_L4_VALID_MASK || data > CLS_DESC_L4_VALID_MASK)
+		return -EINVAL;
+
+	cdesc->l4_valid_m = mask;
+	cdesc->l4_valid = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_l4_dport(struct cls_desc *cdesc, u32 mask, u32 data)
+{
+	if (mask > CLS_DESC_L4_DPORT_MASK || data > CLS_DESC_L4_DPORT_MASK)
+		return -EINVAL;
+
+	cdesc->l4_dport_m = mask;
+	cdesc->l4_dport = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_l4_data(struct cls_desc *cdesc, u32 mask, u32 data)
+{
+	cdesc->l4_hdr_usr_data_m = mask;
+	cdesc->l4_hdr_usr_data = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_fport(struct cls_desc *cdesc, u32 data)
+{
+	if (data > CLS_DESC_FPORT_MASK)
+		return -EINVAL;
+
+	cdesc->fport = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_dr_idx(struct cls_desc *cdesc, u32 data)
+{
+	if (data > CLS_DESC_DR_IDX_MASK)
+		return -EINVAL;
+
+	cdesc->dr_idx = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_qid(struct cls_desc *cdesc, u32 data)
+{
+	if (data > CLS_DESC_QID_MASK)
+		return -EINVAL;
+
+	cdesc->qid = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_cdrt(struct cls_desc *cdesc, u32 data)
+{
+	if (data > CLS_DESC_CDRT_MASK)
+		return -EINVAL;
+
+	cdesc->cdrt_idx = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_tport(struct cls_desc *cdesc, u32 data)
+{
+	if (data > CLS_DESC_TPORT_MASK)
+		return -EINVAL;
+
+	cdesc->tport_idx = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_tops_entry(struct cls_desc *cdesc, u32 data)
+{
+	if (data > CLS_DESC_TOPS_ENTRY_MASK)
+		return -EINVAL;
+
+	cdesc->tops_entry = data;
+
+	return 0;
+}
+
+static int cls_entry_debug_fill_mask_data(
+			const char *buf, u32 *ofs,
+			struct cls_desc *cdesc,
+			int (*fill_func)(struct cls_desc *cdesc, u32 mask, u32 data))
+{
+	u32 nchar = 0;
+	u32 mask = 0;
+	u32 data = 0;
+	int ret;
+
+	if (!fill_func)
+		return -ENODEV;
+
+	ret = sscanf(buf + *ofs, "%x %x %n", &mask, &data, &nchar);
+	if (ret != 2)
+		return -EINVAL;
+
+	*ofs += nchar;
+
+	ret = fill_func(cdesc, mask, data);
+	if (ret)
+		PCE_ERR("invalid mask: 0x%x, data: 0x%x\n", mask, data);
+
+	return ret;
+}
+
+static int cls_entry_debug_fill_data(const char *buf, u32 *ofs,
+				struct cls_desc *cdesc,
+				int (*fill_func)(struct cls_desc *cdesc, u32 data))
+{
+	u32 nchar = 0;
+	u32 data = 0;
+	int ret;
+
+	if (!fill_func)
+		return -ENODEV;
+
+	ret = sscanf(buf + *ofs, "%x %n", &data, &nchar);
+	if (ret != 1)
+		return -EINVAL;
+
+	*ofs += nchar;
+
+	ret = fill_func(cdesc, data);
+	if (ret)
+		PCE_ERR("invalid data: 0x%x\n", data);
+
+	return ret;
+}
+
+struct cls_entry_debug_config cls_entry_dbg_cfgs[] = {
+	CLS_ENTRY_DBG_CFG_MASK_DATA(tag),
+	CLS_ENTRY_DBG_CFG_MASK_DATA(udplite_l4_hdr_nez),
+	CLS_ENTRY_DBG_CFG_MASK_DATA(dip_match),
+	CLS_ENTRY_DBG_CFG_MASK_DATA(l4_protocol),
+	CLS_ENTRY_DBG_CFG_MASK_DATA(l4_valid),
+	CLS_ENTRY_DBG_CFG_MASK_DATA(l4_dport),
+	CLS_ENTRY_DBG_CFG_MASK_DATA(l4_data),
+	CLS_ENTRY_DBG_CFG_DATA(fport),
+	CLS_ENTRY_DBG_CFG_DATA(dr_idx),
+	CLS_ENTRY_DBG_CFG_DATA(qid),
+	CLS_ENTRY_DBG_CFG_DATA(cdrt),
+	CLS_ENTRY_DBG_CFG_DATA(tport),
+	CLS_ENTRY_DBG_CFG_DATA(tops_entry),
+};
+
+static int cls_entry_debug_fill(const char *buf, u32 *ofs, struct cls_desc *cdesc)
+{
+	struct cls_entry_debug_config *cls_entry_dbg_cfg;
+	char arg[32];
+	u32 nchar = 0;
+	u32 i;
+	int ret;
+
+	ret = sscanf(buf + *ofs, "%31s %n", arg, &nchar);
+	if (ret != 1)
+		return -EINVAL;
+
+	*ofs += nchar;
+
+	for (i = 0; i < ARRAY_SIZE(cls_entry_dbg_cfgs); i++) {
+		cls_entry_dbg_cfg = &cls_entry_dbg_cfgs[i];
+		if (strcmp(cls_entry_dbg_cfg->name, arg))
+			continue;
+
+		switch (cls_entry_dbg_cfg->type) {
+		case CLS_ENTRY_MASK_DATA:
+			ret = cls_entry_debug_fill_mask_data(buf, ofs, cdesc,
+						cls_entry_dbg_cfg->fill_mask_data);
+			break;
+
+		case CLS_ENTRY_DATA:
+			ret = cls_entry_debug_fill_data(buf, ofs, cdesc,
+						cls_entry_dbg_cfg->fill_data);
+			break;
+
+		default:
+			return -EINVAL;
+		}
+
+		if (ret)
+			goto err_out;
+
+		return ret;
+	}
+
+err_out:
+	PCE_ERR("invalid argument: %s\n", arg);
+
+	return ret;
+}
+
+static ssize_t cls_entry_debug_write(struct file *file, const char __user *buffer,
+				     size_t count, loff_t *data)
+{
+	struct cls_desc cdesc;
+	char buf[512];
+	u32 cls_entry = 0;
+	u32 nchar = 0;
+	u32 ofs = 0;
+	int ret;
+
+	if (count > sizeof(buf))
+		return -ENOMEM;
+
+	if (copy_from_user(buf, buffer, count))
+		return -EFAULT;
+
+	buf[count] = '\0';
+
+	memset(&cdesc, 0, sizeof(struct cls_desc));
+
+	ret = sscanf(buf + ofs, "%u %n\n", &cls_entry, &nchar);
+	if (ret != 1)
+		return -EINVAL;
+
+	ofs += nchar;
+
+	if (cls_entry == CLS_ENTRY_NONE || cls_entry > __CLS_ENTRY_MAX) {
+		PCE_ERR("invalid cls entry: %u\n", cls_entry);
+		return -EINVAL;
+	}
+
+	while (1) {
+		if (ofs >= count)
+			break;
+
+		ret = cls_entry_debug_fill(buf, &ofs, &cdesc);
+		if (ret)
+			return ret;
+	}
+
+	ret = mtk_pce_cls_desc_write(&cdesc, cls_entry);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static int cls_entry_debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, cls_entry_debug_read, file->private_data);
+}
+
+static int dipfilter_debug_read(struct seq_file *s, void *private)
+{
+	struct dip_desc ddesc;
+	int ret;
+	u32 i;
+
+	for (i = 0; i < FE_MEM_DIPFILTER_MAX_IDX; i++) {
+		ret = mtk_pce_dipfilter_desc_read(&ddesc, i);
+		if (ret) {
+			PCE_ERR("read dipfilter desc: %u failed: %d\n", i, ret);
+			return 0;
+		}
+
+		seq_printf(s, "DIPFILTER Entry%02u, enabled: %u\n",
+			   i, ddesc.tag != DIPFILTER_DISABLED);
+
+		if (ddesc.tag == DIPFILTER_DISABLED) {
+			continue;
+		} else if (ddesc.tag == DIPFILTER_IPV4) {
+			u32 addr = cpu_to_be32(ddesc.ipv4);
+
+			seq_printf(s, "IPv4 address: %pI4\n", &addr);
+		} else if (ddesc.tag == DIPFILTER_IPV6) {
+			seq_printf(s, "IPv6 address: %pI6\n", ddesc.ipv6);
+		}
+	}
+
+	return 0;
+}
+
+static int dipfilter_debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, dipfilter_debug_read, file->private_data);
+}
+
+static ssize_t dipfilter_debug_write(struct file *file, const char __user *buffer,
+				     size_t count, loff_t *data)
+{
+	struct dip_desc ddesc;
+	const char *end;
+	char buf[512];
+	char arg[5];
+	char s_dip[40];
+	int ret;
+
+	if (count > sizeof(buf))
+		return -ENOMEM;
+
+	if (copy_from_user(buf, buffer, count))
+		return -EFAULT;
+
+	buf[count] = '\0';
+
+	memset(&ddesc, 0, sizeof(struct dip_desc));
+
+	ret = sscanf(buf, "%s %s", arg, s_dip);
+	if (ret != 2)
+		return -EINVAL;
+
+	if (!strcmp(arg, "ipv4")) {
+		ddesc.tag = DIPFILTER_IPV4;
+		if (!in4_pton(s_dip, -1, (u8 *)(&ddesc.ipv4), -1, &end) || (*end)) {
+			PCE_ERR("invalid IPv4 address: %s\n", s_dip);
+			return -EINVAL;
+		}
+	} else if (!strcmp(arg, "ipv6")) {
+		ddesc.tag = DIPFILTER_IPV6;
+		if (!in6_pton(s_dip, -1, (u8 *)ddesc.ipv6, -1, &end) || (*end)) {
+			PCE_ERR("invalid Ipv6 address: %s\n", s_dip);
+			return -EINVAL;
+		}
+	} else {
+		return -EINVAL;
+	}
+
+	ret = mtk_pce_dipfilter_entry_add(&ddesc);
+	if (ret) {
+		PCE_ERR("add dipfilter entry failed: %d\n", ret);
+		return ret;
+	}
+
+	return count;
+}
+
+static int tport_map_debug_read(struct seq_file *s, void *private)
+{
+	struct tsc_desc tdesc;
+	int ret;
+	u64 map;
+	u32 i;
+
+	for (i = 0; i < __PSE_PORT_MAX; i++) {
+		if (TS_CONFIG_MASK & BIT(i)) {
+			ret = mtk_pce_tport_map_ts_config_read(i, &tdesc);
+			if (ret)
+				return ret;
+
+			map = tdesc.tport_map_lower;
+			map |= ((u64)tdesc.tport_map_upper) << 32;
+			seq_printf(s, "PSE_PORT%02u ", i);
+			seq_printf(s, "PORT_MAP: 0x%llX, ", map);
+			seq_printf(s, "default TPORT_IDX: %02u, ", tdesc.tport);
+			seq_printf(s, "default CDRT_IDX: %02u, ", tdesc.cdrt_idx);
+			seq_printf(s, "default TOPS_ENTRY: %02u\n", tdesc.tops_entry);
+		} else if (PSE_PORT_PPE_MASK & BIT(i)) {
+			ret = mtk_pce_tport_map_ppe_read(i, &map);
+			if (ret)
+				return ret;
+
+			seq_printf(s, "PSE PORT%02u ", i);
+			seq_printf(s, "PORT_MAP: 0x%llX\n", map);
+		}
+	}
+
+	return 0;
+}
+
+static int tport_map_debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, tport_map_debug_read, file->private_data);
+}
+
+static int tport_map_debug_update_all(const char *buf, int *ofs, u32 tport_idx)
+{
+	enum pse_port port = PSE_PORT_ADMA;
+	enum pse_port target;
+	u64 port_map = 0;
+	int nchar = 0;
+	int ret = 0;
+
+	*ofs += nchar;
+
+	ret = sscanf(buf + *ofs, "%llx %n", &port_map, &nchar);
+	if (ret != 1)
+		return -EPERM;
+
+	while (port_map && port < __PSE_PORT_MAX) {
+		target = port_map & PSE_PER_PORT_MASK;
+		port_map >>= PSE_PER_PORT_BITS;
+
+		if (TS_CONFIG_MASK & BIT(port) || PSE_PORT_PPE_MASK & BIT(port)) {
+			ret = mtk_pce_tport_map_pse_port_update(port,
+								tport_idx,
+								target);
+			if (ret) {
+				PCE_ERR("invalid port: %u\n", port);
+				return ret;
+			}
+		}
+
+		port++;
+	}
+
+	return 0;
+}
+
+static int tport_map_debug_update_single(const char *buf, int *ofs,
+					 enum pse_port port, u32 tport_idx)
+{
+	u32 target = 0;
+	int nchar = 0;
+	int ret = 0;
+
+	ret = sscanf(buf + *ofs, "%x %n", &target, &nchar);
+	if (ret != 1)
+		return -EPERM;
+
+	return mtk_pce_tport_map_pse_port_update(port, tport_idx, target);
+}
+
+static ssize_t tport_map_debug_write(struct file *file, const char __user *buffer,
+				     size_t count, loff_t *data)
+{
+	enum pse_port port = PSE_PORT_ADMA;
+	char arg1[21] = {0};
+	char cmd[21] = {0};
+	char buf[512];
+	u32 tport_idx = 0;
+	int nchar = 0;
+	int ret;
+
+	if (count > sizeof(buf))
+		return -ENOMEM;
+
+	if (copy_from_user(buf, buffer, count))
+		return -EFAULT;
+
+	buf[count] = '\0';
+
+	ret = sscanf(buf, "%20s %20s %u %n", cmd, arg1, &tport_idx, &nchar);
+	if (ret != 3)
+		return -EINVAL;
+
+	if (tport_idx > TPORT_IDX_MAX) {
+		PCE_ERR("invalid tport idx: %u\n", tport_idx);
+		return -EINVAL;
+	}
+
+	if (!strcmp(cmd, "UPDATE")) {
+		if (!strcmp(arg1, "ALL")) {
+			ret = tport_map_debug_update_all(buf, &nchar, tport_idx);
+			if (ret)
+				return ret;
+		} else {
+			ret = kstrtou32(arg1, 10, &port);
+			if (ret) {
+				PCE_ERR("invalid pse port: %u\n", port);
+				return ret;
+			}
+
+			ret = tport_map_debug_update_single(buf, &nchar,
+							    port, tport_idx);
+			if (ret) {
+				PCE_ERR("update tport map single failed: %d\n",
+					ret);
+				return ret;
+			}
+		}
+	}
+
+	return count;
+}
+
+static const struct file_operations cls_entry_debug_ops = {
+	.open = cls_entry_debug_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.write = cls_entry_debug_write,
+	.release = single_release,
+};
+
+static const struct file_operations dipfilter_debug_ops = {
+	.open = dipfilter_debug_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.write = dipfilter_debug_write,
+	.release = single_release,
+};
+
+static const struct file_operations tport_map_debug_ops = {
+	.open = tport_map_debug_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.write = tport_map_debug_write,
+	.release = single_release,
+};
+
+int mtk_pce_debugfs_init(struct platform_device *pdev)
+{
+	debug_root = debugfs_create_dir("pce", NULL);
+	if (!debug_root) {
+		PCE_ERR("create debugfs root directory failed\n");
+		return -ENOMEM;
+	}
+
+	debugfs_create_file("cls_entry", 0444, debug_root, NULL,
+			    &cls_entry_debug_ops);
+	debugfs_create_file("dipfilter_entry", 0444, debug_root, NULL,
+			    &dipfilter_debug_ops);
+	debugfs_create_file("tport_map", 0444, debug_root, NULL,
+			    &tport_map_debug_ops);
+
+	return 0;
+}
+
+void mtk_pce_debugfs_deinit(struct platform_device *pdev)
+{
+	debugfs_remove_recursive(debug_root);
+
+	debug_root = NULL;
+}
diff --git a/package-21.02/kernel/pce/src/dipfilter.c b/package-21.02/kernel/pce/src/dipfilter.c
new file mode 100644
index 0000000..b064db1
--- /dev/null
+++ b/package-21.02/kernel/pce/src/dipfilter.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/refcount.h>
+#include <linux/spinlock.h>
+
+#include "pce/dipfilter.h"
+#include "pce/internal.h"
+#include "pce/netsys.h"
+
+struct dipfilter_entry {
+	struct dip_desc ddesc;
+	refcount_t refcnt;
+	u32 index;
+};
+
+struct dipfilter_hw {
+	struct dipfilter_entry dip_entries[FE_MEM_DIPFILTER_MAX_IDX];
+	DECLARE_BITMAP(dip_tbl, FE_MEM_DIPFILTER_MAX_IDX);
+	spinlock_t lock;
+};
+
+struct dipfilter_hw dip_hw;
+
+int mtk_pce_dipfilter_enable(void)
+{
+	mtk_pce_netsys_setbits(GLO_MEM_CFG, GDM_DIPFILTER_EN);
+
+	return 0;
+}
+
+void mtk_pce_dipfilter_disable(void)
+{
+	mtk_pce_netsys_clrbits(GLO_MEM_CFG, GDM_DIPFILTER_EN);
+}
+
+/*
+ * find registered dipfilter info
+ * return index on matched dipfilter info
+ * return NULL on not found
+ */
+static struct dipfilter_entry *__mtk_pce_dip_info_find(struct dip_desc *target)
+{
+	struct dip_desc *ddesc;
+	u32 idx = 0;
+
+	lockdep_assert_held(&dip_hw.lock);
+
+	/* can check this before acquiring lock */
+	if (!target
+	    || target->tag == DIPFILTER_DISABLED
+	    || target->tag >= __DIPFILTER_TAG_MAX) {
+		PCE_NOTICE("dip_desc is not enabled or invalid\n");
+		return NULL;
+	}
+
+	for_each_set_bit(idx, dip_hw.dip_tbl, FE_MEM_DIPFILTER_MAX_IDX) {
+		ddesc = &dip_hw.dip_entries[idx].ddesc;
+
+		if (target->tag != ddesc->tag)
+			continue;
+
+		if (ddesc->tag == DIPFILTER_IPV4
+		    && ddesc->ipv4 == target->ipv4) {
+			return &dip_hw.dip_entries[idx];
+		} else if (ddesc->tag == DIPFILTER_IPV6
+			   && !memcmp(ddesc->ipv6, target->ipv6, sizeof(u32) * 4)) {
+			return &dip_hw.dip_entries[idx];
+		}
+	}
+
+	return NULL;
+}
+
+int mtk_pce_dipfilter_desc_read(struct dip_desc *ddesc, u32 idx)
+{
+	struct fe_mem_msg msg;
+	unsigned long flag;
+	int ret;
+
+	if (!ddesc || idx > FE_MEM_DIPFILTER_MAX_IDX)
+		return -EINVAL;
+
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_READ, FE_MEM_TYPE_DIPFILTER, idx);
+	memset(&msg.raw, 0, sizeof(msg.raw));
+
+	spin_lock_irqsave(&dip_hw.lock, flag);
+
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+	if (ret)
+		goto unlock;
+
+	memcpy(ddesc, &msg.ddesc, sizeof(struct dip_desc));
+
+unlock:
+	spin_unlock_irqrestore(&dip_hw.lock, flag);
+
+	return ret;
+}
+
+static int __mtk_pce_dipfilter_entry_add(struct dip_desc *ddesc)
+{
+	struct fe_mem_msg fmsg;
+	struct dipfilter_entry *dip_entry;
+	int ret;
+	u32 idx;
+
+	lockdep_assert_held(&dip_hw.lock);
+
+	/* find available entry */
+	idx = find_first_zero_bit(dip_hw.dip_tbl, FE_MEM_DIPFILTER_MAX_IDX);
+	if (idx == FE_MEM_DIPFILTER_MAX_IDX) {
+		PCE_NOTICE("dipfilter full\n");
+		return -EBUSY;
+	}
+
+	/* prepare fe_mem message */
+	mtk_pce_fe_mem_msg_config(&fmsg, FE_MEM_CMD_WRITE, FE_MEM_TYPE_DIPFILTER, idx);
+
+	memset(&fmsg.raw, 0, sizeof(fmsg.raw));
+	memcpy(&fmsg.raw, ddesc, sizeof(struct dip_desc));
+
+	/* send fe_mem message */
+	ret = mtk_pce_fe_mem_msg_send(&fmsg);
+	if (ret) {
+		PCE_NOTICE("fe_mem send dipfilter desc failed\n");
+		return ret;
+	}
+
+	/* record installed dipfilter data */
+	dip_entry = &dip_hw.dip_entries[idx];
+	memcpy(&dip_entry->ddesc, ddesc, sizeof(struct dip_desc));
+
+	refcount_set(&dip_entry->refcnt, 1);
+
+	set_bit(idx, dip_hw.dip_tbl);
+
+	return 0;
+}
+
+int mtk_pce_dipfilter_entry_add(struct dip_desc *ddesc)
+{
+	struct dipfilter_entry *dip_entry;
+	unsigned long flag;
+	int ret = 0;
+
+	if (unlikely(!ddesc))
+		return 0;
+
+	spin_lock_irqsave(&dip_hw.lock, flag);
+
+	dip_entry = __mtk_pce_dip_info_find(ddesc);
+	if (dip_entry) {
+		refcount_inc(&dip_entry->refcnt);
+		goto unlock;
+	}
+
+	ret = __mtk_pce_dipfilter_entry_add(ddesc);
+
+unlock:
+	spin_unlock_irqrestore(&dip_hw.lock, flag);
+
+	return ret;
+}
+EXPORT_SYMBOL(mtk_pce_dipfilter_entry_add);
+
+int mtk_pce_dipfilter_entry_del(struct dip_desc *ddesc)
+{
+	struct fe_mem_msg fmsg;
+	struct dipfilter_entry *dip_entry;
+	unsigned long flag;
+	int ret;
+	u32 idx;
+
+	if (!ddesc)
+		return 0;
+
+	spin_lock_irqsave(&dip_hw.lock, flag);
+
+	dip_entry = __mtk_pce_dip_info_find(ddesc);
+	if (!dip_entry)
+		goto unlock;
+
+	dip_entry = &dip_hw.dip_entries[idx];
+	if (!refcount_dec_and_test(&dip_entry->refcnt))
+		/* dipfilter descriptor is still in use */
+		return 0;
+
+	mtk_pce_fe_mem_msg_config(&fmsg, FE_MEM_CMD_WRITE, FE_MEM_TYPE_DIPFILTER, idx);
+	memset(&fmsg.raw, 0, sizeof(fmsg.raw));
+
+	ret = mtk_pce_fe_mem_msg_send(&fmsg);
+	if (ret) {
+		PCE_NOTICE("fe_mem send dipfilter desc failed\n");
+		return ret;
+	}
+
+	memset(&dip_entry->ddesc, 0, sizeof(struct dip_desc));
+	clear_bit(idx, dip_hw.dip_tbl);
+
+unlock:
+	spin_unlock_irqrestore(&dip_hw.lock, flag);
+
+	return 0;
+}
+EXPORT_SYMBOL(mtk_pce_dipfilter_entry_del);
+
+static void mtk_pce_dipfilter_clean_up(void)
+{
+	struct fe_mem_msg fmsg = {
+		.cmd = FE_MEM_CMD_WRITE,
+		.type = FE_MEM_TYPE_DIPFILTER,
+	};
+	unsigned long flag;
+	int ret = 0;
+	u32 i = 0;
+
+	memset(&fmsg.raw, 0, sizeof(fmsg.raw));
+
+	spin_lock_irqsave(&dip_hw.lock, flag);
+
+	/* clear all dipfilter desc on init */
+	for (i = 0; i < FE_MEM_DIPFILTER_MAX_IDX; i++) {
+		fmsg.index = i;
+		ret = mtk_pce_fe_mem_msg_send(&fmsg);
+		if (ret)
+			goto unlock;
+
+		dip_hw.dip_entries[i].index = i;
+	}
+
+unlock:
+	spin_unlock_irqrestore(&dip_hw.lock, flag);
+}
+
+int mtk_pce_dipfilter_init(struct platform_device *pdev)
+{
+	spin_lock_init(&dip_hw.lock);
+
+	mtk_pce_dipfilter_clean_up();
+
+	return 0;
+}
+
+void mtk_pce_dipfilter_deinit(struct platform_device *pdev)
+{
+	mtk_pce_dipfilter_clean_up();
+}
diff --git a/package-21.02/kernel/pce/src/inc/pce/cdrt.h b/package-21.02/kernel/pce/src/inc/pce/cdrt.h
new file mode 100644
index 0000000..d2acdf8
--- /dev/null
+++ b/package-21.02/kernel/pce/src/inc/pce/cdrt.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#ifndef _PCE_CDRT_H_
+#define _PCE_CDRT_H_
+
+#include <linux/platform_device.h>
+
+#include "pce/cls.h"
+#include "pce/pce.h"
+
+enum cdrt_type {
+	CDRT_DECRYPT,
+	CDRT_ENCRYPT,
+
+	__CDRT_TYPE_MAX,
+};
+
+struct cdrt_entry {
+	u32 idx;
+	enum cdrt_type type;
+	struct cdrt_desc desc;
+	struct cls_entry *cls;
+	int (*update_cls_rule)(struct cdrt_entry *cdrt);
+};
+
+int mtk_pce_cdrt_update_cls_rule(u32 cdrt_idx);
+
+int mtk_pce_cdrt_desc_write(struct cdrt_desc *desc, u32 idx);
+int mtk_pce_cdrt_desc_read(struct cdrt_desc *desc, u32 idx);
+
+int mtk_pce_cdrt_entry_write(struct cdrt_entry *cdrt);
+
+struct cdrt_entry *mtk_pce_cdrt_entry_alloc(enum cdrt_type type);
+void mtk_pce_cdrt_entry_free(struct cdrt_entry *cdrt);
+
+int mtk_pce_cdrt_enable(void);
+void mtk_pce_cdrt_disable(void);
+
+int mtk_pce_cdrt_init(struct platform_device *pdev);
+void mtk_pce_cdrt_deinit(struct platform_device *pdev);
+#endif /* _PCE_CDRT_H_ */
diff --git a/package-21.02/kernel/pce/src/inc/pce/cls.h b/package-21.02/kernel/pce/src/inc/pce/cls.h
new file mode 100644
index 0000000..59d1708
--- /dev/null
+++ b/package-21.02/kernel/pce/src/inc/pce/cls.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#ifndef _PCE_CLS_H_
+#define _PCE_CLS_H_
+
+#include <linux/platform_device.h>
+
+#include "pce/pce.h"
+
+enum cls_entry_type {
+	CLS_ENTRY_NONE = 0,
+	CLS_ENTRY_GRETAP,
+	CLS_ENTRY_PPTP,
+	CLS_ENTRY_IP_L2TP,
+	CLS_ENTRY_UDP_L2TP_CTRL,
+	CLS_ENTRY_UDP_L2TP_DATA = 5,
+	CLS_ENTRY_VXLAN,
+	CLS_ENTRY_NATT,
+	CLS_ENTRY_CAPWAP_CTRL,
+	CLS_ENTRY_CAPWAP_DATA,
+	CLS_ENTRY_CAPWAP_DTLS = 10,
+	CLS_ENTRY_IPSEC_ESP,
+	CLS_ENTRY_IPSEC_AH,
+
+	__CLS_ENTRY_MAX = FE_MEM_CLS_MAX_INDEX,
+};
+
+#define CLS_DESC_MASK_DATA(cdesc, field, mask, data)			\
+	do {								\
+		(cdesc)->field ## _m = (mask);				\
+		(cdesc)->field = (data);				\
+	} while (0)
+#define CLS_DESC_DATA(cdesc, field, data)				\
+	(cdesc)->field = (data)
+
+struct cls_entry {
+	enum cls_entry_type entry;
+	struct cls_desc cdesc;
+};
+
+int mtk_pce_cls_enable(void);
+void mtk_pce_cls_disable(void);
+
+int mtk_pce_cls_init(struct platform_device *pdev);
+void mtk_pce_cls_deinit(struct platform_device *pdev);
+
+int mtk_pce_cls_desc_read(struct cls_desc *cdesc, enum cls_entry_type entry);
+int mtk_pce_cls_desc_write(struct cls_desc *cdesc, enum cls_entry_type entry);
+
+int mtk_pce_cls_entry_register(struct cls_entry *cls);
+void mtk_pce_cls_entry_unregister(struct cls_entry *cls);
+#endif /* _PCE_CLS_H_ */
diff --git a/package-21.02/kernel/pce/src/inc/pce/debugfs.h b/package-21.02/kernel/pce/src/inc/pce/debugfs.h
new file mode 100644
index 0000000..ac3d29b
--- /dev/null
+++ b/package-21.02/kernel/pce/src/inc/pce/debugfs.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#ifndef _PCE_DEBUGFS_H_
+#define _PCE_DEBUGFS_H_
+
+#include <linux/platform_device.h>
+
+int mtk_pce_debugfs_init(struct platform_device *pdev);
+void mtk_pce_debugfs_deinit(struct platform_device *pdev);
+#endif /* _PCE_DEBUGFS_H_ */
diff --git a/package-21.02/kernel/pce/src/inc/pce/dipfilter.h b/package-21.02/kernel/pce/src/inc/pce/dipfilter.h
new file mode 100644
index 0000000..090a9cd
--- /dev/null
+++ b/package-21.02/kernel/pce/src/inc/pce/dipfilter.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#ifndef _PCE_DIPFILTER_H_
+#define _PCE_DIPFILTER_H_
+
+#include <linux/platform_device.h>
+
+#include "pce/pce.h"
+
+int mtk_pce_dipfilter_enable(void);
+void mtk_pce_dipfilter_disable(void);
+
+int mtk_pce_dipfilter_desc_read(struct dip_desc *ddesc, u32 idx);
+
+int mtk_pce_dipfilter_entry_add(struct dip_desc *ddesc);
+int mtk_pce_dipfilter_entry_del(struct dip_desc *ddesc);
+
+int mtk_pce_dipfilter_init(struct platform_device *pdev);
+void mtk_pce_dipfilter_deinit(struct platform_device *pdev);
+#endif /* _PCE_DIPFILTER_H_ */
diff --git a/package-21.02/kernel/pce/src/inc/pce/internal.h b/package-21.02/kernel/pce/src/inc/pce/internal.h
new file mode 100644
index 0000000..ccfc9a4
--- /dev/null
+++ b/package-21.02/kernel/pce/src/inc/pce/internal.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#ifndef _PCE_INTERNAL_H_
+#define _PCE_INTERNAL_H_
+
+#include <linux/device.h>
+#include <linux/io.h>
+
+extern struct device *pce_dev;
+
+#define PCE_DBG(fmt, ...)		dev_dbg(pce_dev, fmt, ##__VA_ARGS__)
+#define PCE_INFO(fmt, ...)		dev_info(pce_dev, fmt, ##__VA_ARGS__)
+#define PCE_NOTICE(fmt, ...)		dev_notice(pce_dev, fmt, ##__VA_ARGS__)
+#define PCE_WARN(fmt, ...)		dev_warn(pce_dev, fmt, ##__VA_ARGS__)
+#define PCE_ERR(fmt, ...)		dev_err(pce_dev, fmt, ##__VA_ARGS__)
+#endif /* _PCE_INTERNAL_H_ */
diff --git a/package-21.02/kernel/pce/src/inc/pce/netsys.h b/package-21.02/kernel/pce/src/inc/pce/netsys.h
new file mode 100644
index 0000000..f3ecd0e
--- /dev/null
+++ b/package-21.02/kernel/pce/src/inc/pce/netsys.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#ifndef _PCE_NETSYS_H_
+#define _PCE_NETSYS_H_
+
+#include <linux/platform_device.h>
+
+/* FE BASE */
+#define FE_BASE					(0x0000)
+
+/* PPE BASE */
+#define PPE0_BASE				(0x2000)
+#define PPE1_BASE				(0x2400)
+#define PPE2_BASE				(0x2C00)
+
+/* PPE */
+#define PPE_TPORT_TBL_0				(0x0258)
+#define PPE_TPORT_TBL_1				(0x025C)
+
+/* FE_MEM */
+#define GLO_MEM_CFG				(0x0600)
+#define GLO_MEM_CTRL				(0x0604)
+#define GLO_MEM_DATA_IDX(x)			(0x0608 + (0x4 * (x)))
+
+/* GLO_MEM_CFG */
+#define GDM_DIPFILTER_EN			(BIT(0))
+#define GDM_CLS_EN				(BIT(1))
+#define CDM_CDRT_EN				(BIT(2))
+#define EIP197_LOCK_CACHES			(BIT(3))
+
+/* GLO_MEM_CTRL */
+#define GLO_MEM_CTRL_ADDR_SHIFT			(0)
+#define GLO_MEM_CTRL_ADDR_MASK			GENMASK(19, 0)
+#define GLO_MEM_CTRL_INDEX_SHIFT		(20)
+#define GLO_MEM_CTRL_INDEX_MASK			GENMASK(29, 20)
+#define GLO_MEM_CTRL_CMD_SHIFT			(30)
+#define GLO_MEM_CTRL_CMD_MASK			GENMASK(31, 30)
+
+/* TPORT setting etc. */
+#define TPORT_IDX_MAX				(16)
+#define TS_CONFIG_MASK				(0xE746)
+#define PSE_PORT_PPE_MASK			(BIT(PSE_PORT_PPE0) \
+						| BIT(PSE_PORT_PPE1) \
+						| BIT(PSE_PORT_PPE2))
+#define PSE_PER_PORT_MASK			(0xF)
+#define PSE_PER_PORT_BITS			(4)
+
+enum pse_port {
+	PSE_PORT_ADMA = 0,
+	PSE_PORT_GDM1,
+	PSE_PORT_GDM2,
+	PSE_PORT_PPE0,
+	PSE_PORT_PPE1,
+	PSE_PORT_QDMA = 5,
+	PSE_PORT_DISCARD = 7,
+	PSE_PORT_WDMA0,
+	PSE_PORT_WDMA1,
+	PSE_PORT_TDMA = 10,
+	PSE_PORT_EDMA0,
+	PSE_PORT_PPE2,
+	PSE_PORT_WDMA2,
+	PSE_PORT_EIP197,
+	PSE_PORT_GDM3 = 15,
+	__PSE_PORT_MAX,
+};
+
+void mtk_pce_ppe_rmw(enum pse_port ppe, u32 reg, u32 mask, u32 val);
+u32 mtk_pce_ppe_read(enum pse_port ppe, u32 reg);
+
+void mtk_pce_netsys_write(u32 reg, u32 val);
+void mtk_pce_netsys_setbits(u32 reg, u32 mask);
+void mtk_pce_netsys_clrbits(u32 reg, u32 mask);
+void mtk_pce_netsys_rmw(u32 reg, u32 mask, u32 val);
+u32 mtk_pce_netsys_read(u32 reg);
+
+int mtk_pce_netsys_init(struct platform_device *pdev);
+void mtk_pce_netsys_deinit(struct platform_device *pdev);
+#endif /* _PCE_NETSYS_H_ */
diff --git a/package-21.02/kernel/pce/src/inc/pce/pce.h b/package-21.02/kernel/pce/src/inc/pce/pce.h
new file mode 100644
index 0000000..b94f40a
--- /dev/null
+++ b/package-21.02/kernel/pce/src/inc/pce/pce.h
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#ifndef _MTK_PCE_H_
+#define _MTK_PCE_H_
+
+/* GLO_MEM available control max index */
+#define FE_MEM_DATA_WLEN			(10)
+#define FE_MEM_TS_CONFIG_MAX_INDEX		(16)
+#define FE_MEM_DIPFILTER_MAX_IDX		(16)
+#define FE_MEM_CLS_MAX_INDEX			(32)
+#define FE_MEM_CDRT_MAX_INDEX			(96)
+
+enum fe_mem_cmd {
+	FE_MEM_CMD_NO_OP,
+	FE_MEM_CMD_WRITE,
+	FE_MEM_CMD_READ,
+
+	__FE_MEM_CMD_MAX,
+};
+
+enum fe_mem_type {
+	FE_MEM_TYPE_TS_CONFIG,
+	FE_MEM_TYPE_DIPFILTER,
+	FE_MEM_TYPE_CLS,
+	FE_MEM_TYPE_CDRT,
+
+	__FE_MEM_TYPE_MAX,
+};
+
+#define CLS_DESC_TAG_MATCH_L4_HDR		(BIT(0))
+/* match the data follow behind L4 header */
+#define CLS_DESC_TAG_MATCH_L4_USR		(BIT(1))
+/* seems like valid bit0 and bit 1 must both be set? */
+#define CLS_DESC_VALID_UPPER_HALF_WORD_BIT	(BIT(0))
+#define CLS_DESC_VALID_LOWER_HALF_WORD_BIT	(BIT(1))
+#define CLS_DESC_VALID_DPORT_BIT		(BIT(2))
+
+#define CLS_DESC_TAG_MASK			(GENMASK(1, 0))
+#define CLS_DESC_SPORT_MASK			(GENMASK(15, 0))
+#define CLS_DESC_UDPLITE_L4_HDR_NEZ_MASK	(GENMASK(0, 0))
+#define CLS_DESC_DIP_MATCH			(GENMASK(0, 0))
+#define CLS_DESC_L4_TYPE_MASK			(GENMASK(7, 0))
+#define CLS_DESC_L4_VALID_MASK			(GENMASK(2, 0))
+#define CLS_DESC_L4_DPORT_MASK			(GENMASK(15, 0))
+#define CLS_DESC_FPORT_MASK			(GENMASK(3, 0))
+#define CLS_DESC_DR_IDX_MASK			(GENMASK(1, 0))
+#define CLS_DESC_QID_MASK			(GENMASK(7, 0))
+#define CLS_DESC_CDRT_MASK			(GENMASK(7, 0))
+#define CLS_DESC_TPORT_MASK			(GENMASK(3, 0))
+#define CLS_DESC_TOPS_ENTRY_MASK		(GENMASK(5, 0))
+
+struct cls_desc {
+	/* action */
+	u32 fport		: 4;
+	u32 dr_idx		: 2;
+	u32 qid			: 8;
+	u32 cdrt_idx		: 8;
+	u32 tport_idx		: 4;
+	u32 tops_entry		: 6; /* no effect on r/w */
+	/* data */
+	u32 l4_hdr_usr_data	: 32;
+	u32 l4_dport		: 16;
+	u32 l4_valid		: 3;
+	u32 l4_type		: 8;
+	u32 dip_match		: 1;
+	u32 l4_udp_hdr_nez	: 1;
+	u32 sport		: 16;
+	u32 tag			: 2;
+	/* mask */
+	u32 l4_hdr_usr_data_m	: 32;
+	u32 l4_dport_m		: 16;
+	u32 l4_valid_m		: 3;
+	u32 l4_type_m		: 8;
+	u32 dip_match_m		: 1;
+	u32 l4_udp_hdr_nez_m	: 1;
+	u32 sport_m		: 16;
+	u32 tag_m		: 2;
+} __packed;
+
+struct cdrt_non_extend {
+	u32 pkt_len		: 16;
+	u32 ap			: 1;
+	u32 options		: 13;
+	u32 type		: 2;
+};
+
+struct cdrt_ipsec_extend {
+	u32 pkt_len		: 16;
+	u32 rsv1		: 9;
+	u32 enc_last_dest	: 1;
+	u32 rsv2		: 4;
+	u32 type		: 2;
+};
+
+struct cdrt_dtls_extend {
+	u32 pkt_len		: 16;
+	u32 rsv1		: 5;
+	u32 mask_size		: 3;
+	u32 rsv2		: 2;
+	u32 capwap		: 1;
+	u32 dir			: 1;
+	u32 content_type	: 2;
+	u32 type		: 2;
+};
+
+struct cdrt_desc {
+	union {
+		struct {
+			union {
+				struct cdrt_non_extend common;
+				struct cdrt_ipsec_extend ipsec;
+				struct cdrt_dtls_extend dtls;
+			};
+			u32 aad_len		: 8;
+			u32 rsv1		: 1;
+			u32 app_id		: 7;
+			u32 token_len		: 8;
+			u32 rsv2		: 8;
+			u32 p_tr[2];
+		} desc1 __packed;
+		u32 raw1[4];
+	};
+
+	union {
+		struct {
+			u32 usr			: 16;
+			u32 rsv1		: 6;
+			u32 strip_pad		: 1;
+			u32 allow_pad		: 1;
+			u32 hw_srv		: 6;
+			u32 rsv2		: 1;
+			u32 flow_lookup		: 1;
+			u32 rsv3		: 8;
+			u32 ofs			: 8;
+			u32 next_hdr		: 8;
+			u32 fl			: 1;
+			u32 ip4_chksum		: 1;
+			u32 l4_chksum		: 1;
+			u32 parse_eth		: 1;
+			u32 keep_outer		: 1;
+			u32 rsv4		: 3;
+			u32 rsv5[2];
+		} desc2 __packed;
+		u32 raw2[4];
+	};
+
+	union {
+		struct {
+			u32 option_meta[4];
+		} desc3 __packed;
+		u32 raw3[4];
+	};
+} __packed;
+
+enum dipfilter_tag {
+	DIPFILTER_DISABLED,
+	DIPFILTER_IPV4,
+	DIPFILTER_IPV6,
+
+	__DIPFILTER_TAG_MAX,
+};
+
+struct dip_desc {
+	union {
+		u32 ipv4;
+		u32 ipv6[4];
+	};
+	u32 tag			: 2; /* enum dipfilter_tag */
+} __packed;
+
+struct tsc_desc {
+	u32 tport		: 4;
+	u32 cdrt_idx		: 8;
+	u32 tops_entry		: 6;
+	u32 tport_map_lower	: 32;
+	u32 tport_map_upper	: 32;
+} __packed;
+
+struct fe_mem_msg {
+	enum fe_mem_cmd cmd;
+	enum fe_mem_type type;
+	u32 index;
+
+	union {
+		struct cls_desc cdesc;
+		struct dip_desc ddesc;
+		struct tsc_desc tdesc;
+		u32 raw[FE_MEM_DATA_WLEN];
+	};
+};
+
+static inline void mtk_pce_fe_mem_msg_config(struct fe_mem_msg *msg,
+					     enum fe_mem_cmd cmd,
+					     enum fe_mem_type type,
+					     u32 index)
+{
+	if (!msg)
+		return;
+
+	msg->cmd = cmd;
+	msg->type = type;
+	msg->index = index;
+}
+
+int mtk_pce_enable(void);
+void mtk_pce_disable(void);
+
+int mtk_pce_fe_mem_msg_send(struct fe_mem_msg *msg);
+#endif /* _MTK_PCE_H_ */
diff --git a/package-21.02/kernel/pce/src/inc/pce/tport_map.h b/package-21.02/kernel/pce/src/inc/pce/tport_map.h
new file mode 100644
index 0000000..a9608d6
--- /dev/null
+++ b/package-21.02/kernel/pce/src/inc/pce/tport_map.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#ifndef _PCE_TPORT_MAP_H_
+#define _PCE_TPORT_MAP_H_
+
+#include "pce/netsys.h"
+#include "pce/pce.h"
+
+enum ts_config_entry {
+	TS_CONFIG_NONE = 0,
+	TS_CONFIG_GDM1,
+	TS_CONFIG_GDM2,
+	TS_CONFIG_CDM2 = 6,
+	TS_CONFIG_CDM3 = 8,
+	TS_CONFIG_CDM4,
+	TS_CONFIG_CDM6 = 10,
+	TS_CONFIG_CDM5 = 13,
+	TS_CONFIG_CDM7,
+	TS_CONFIG_GDM3 = 15,
+
+	__TS_CONFIG_MAX,
+};
+
+int mtk_pce_tport_map_ts_config_read(enum ts_config_entry entry,
+				    struct tsc_desc *ts_cfg);
+int mtk_pce_tport_map_ts_config_write(enum ts_config_entry entry,
+				    struct tsc_desc *ts_cfg);
+int mtk_pce_tport_map_ppe_read(enum pse_port pse_port, u64 *map);
+int mtk_pce_tport_map_pse_port_update(enum pse_port pse_port,
+				      u32 tport_idx,
+				      enum pse_port target);
+#endif /* _PCE_TPORT_MAP_H_ */
diff --git a/package-21.02/kernel/pce/src/init.c b/package-21.02/kernel/pce/src/init.c
new file mode 100644
index 0000000..aa8fc30
--- /dev/null
+++ b/package-21.02/kernel/pce/src/init.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include "pce/cdrt.h"
+#include "pce/cls.h"
+#include "pce/debugfs.h"
+#include "pce/dipfilter.h"
+#include "pce/internal.h"
+#include "pce/netsys.h"
+#include "pce/pce.h"
+
+struct device *pce_dev;
+
+static int mtk_pce_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	pce_dev = &pdev->dev;
+
+	ret = mtk_pce_netsys_init(pdev);
+	if (ret)
+		return ret;
+
+	ret = mtk_pce_cls_init(pdev);
+	if (ret)
+		goto netsys_deinit;
+
+	ret = mtk_pce_dipfilter_init(pdev);
+	if (ret)
+		goto cls_deinit;
+
+	ret = mtk_pce_cdrt_init(pdev);
+	if (ret)
+		goto dipfilter_deinit;
+
+	ret = mtk_pce_debugfs_init(pdev);
+	if (ret)
+		goto cdrt_deinit;
+
+	ret = mtk_pce_enable();
+	if (ret)
+		goto debugfs_deinit;
+
+	return ret;
+
+debugfs_deinit:
+	mtk_pce_debugfs_deinit(pdev);
+
+cdrt_deinit:
+	mtk_pce_cdrt_deinit(pdev);
+
+dipfilter_deinit:
+	mtk_pce_dipfilter_deinit(pdev);
+
+cls_deinit:
+	mtk_pce_cls_deinit(pdev);
+
+netsys_deinit:
+	mtk_pce_netsys_deinit(pdev);
+
+	return ret;
+}
+
+static int mtk_pce_remove(struct platform_device *pdev)
+{
+	mtk_pce_disable();
+
+	mtk_pce_debugfs_deinit(pdev);
+
+	mtk_pce_cdrt_deinit(pdev);
+
+	mtk_pce_dipfilter_deinit(pdev);
+
+	mtk_pce_cls_deinit(pdev);
+
+	mtk_pce_netsys_deinit(pdev);
+
+	return 0;
+}
+
+static const struct of_device_id mtk_pce_match[] = {
+	{ .compatible = "mediatek,pce", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, mtk_pce_match);
+
+static struct platform_driver mtk_pce_driver = {
+	.probe = mtk_pce_probe,
+	.remove = mtk_pce_remove,
+	.driver = {
+		.name = "mediatek,pce",
+		.owner = THIS_MODULE,
+		.of_match_table = mtk_pce_match,
+	},
+};
+
+static int __init mtk_pce_init(void)
+{
+	return platform_driver_register(&mtk_pce_driver);
+}
+
+static void __exit mtk_pce_exit(void)
+{
+	platform_driver_unregister(&mtk_pce_driver);
+}
+
+module_init(mtk_pce_init);
+module_exit(mtk_pce_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MediaTek PCE Driver");
+MODULE_AUTHOR("Ren-Ting Wang <ren-ting.wang@mediatek.com>");
diff --git a/package-21.02/kernel/pce/src/netsys.c b/package-21.02/kernel/pce/src/netsys.c
new file mode 100644
index 0000000..5a053f5
--- /dev/null
+++ b/package-21.02/kernel/pce/src/netsys.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#include "pce/cdrt.h"
+#include "pce/cls.h"
+#include "pce/dipfilter.h"
+#include "pce/internal.h"
+#include "pce/netsys.h"
+#include "pce/pce.h"
+
+struct netsys_hw {
+	void __iomem *base;
+	spinlock_t lock;
+	u32 fe_mem_limit[__FE_MEM_TYPE_MAX];
+};
+
+static struct netsys_hw netsys = {
+	.fe_mem_limit = {
+		[FE_MEM_TYPE_TS_CONFIG] = FE_MEM_TS_CONFIG_MAX_INDEX,
+		[FE_MEM_TYPE_DIPFILTER] = FE_MEM_DIPFILTER_MAX_IDX,
+		[FE_MEM_TYPE_CLS] = FE_MEM_CLS_MAX_INDEX,
+		[FE_MEM_TYPE_CDRT] = FE_MEM_CDRT_MAX_INDEX,
+	},
+};
+
+void mtk_pce_ppe_rmw(enum pse_port ppe, u32 reg, u32 mask, u32 val)
+{
+	if (ppe == PSE_PORT_PPE0)
+		mtk_pce_netsys_rmw(PPE0_BASE + reg, mask, val);
+	else if (ppe == PSE_PORT_PPE1)
+		mtk_pce_netsys_rmw(PPE1_BASE + reg, mask, val);
+	else if (ppe == PSE_PORT_PPE2)
+		mtk_pce_netsys_rmw(PPE2_BASE + reg, mask, val);
+}
+
+u32 mtk_pce_ppe_read(enum pse_port ppe, u32 reg)
+{
+	if (ppe == PSE_PORT_PPE0)
+		return mtk_pce_netsys_read(PPE0_BASE + reg);
+	else if (ppe == PSE_PORT_PPE1)
+		return mtk_pce_netsys_read(PPE1_BASE + reg);
+	else if (ppe == PSE_PORT_PPE2)
+		return mtk_pce_netsys_read(PPE2_BASE + reg);
+
+	return 0;
+}
+
+void mtk_pce_netsys_write(u32 reg, u32 val)
+{
+	writel(val, netsys.base + reg);
+}
+
+void mtk_pce_netsys_setbits(u32 reg, u32 mask)
+{
+	writel(readl(netsys.base + reg) | mask, netsys.base + reg);
+}
+
+void mtk_pce_netsys_clrbits(u32 reg, u32 mask)
+{
+	writel(readl(netsys.base + reg) & (~mask), netsys.base + reg);
+}
+
+void mtk_pce_netsys_rmw(u32 reg, u32 mask, u32 val)
+{
+	writel((readl(netsys.base + reg) & (~mask)) | val, netsys.base + reg);
+}
+
+u32 mtk_pce_netsys_read(u32 reg)
+{
+	return readl(netsys.base + reg);
+}
+
+static inline void mtk_pce_fe_mem_config(enum fe_mem_type type, u32 idx)
+{
+	/* select memory index to program */
+	mtk_pce_netsys_rmw(GLO_MEM_CTRL,
+		   GLO_MEM_CTRL_ADDR_MASK,
+		   FIELD_PREP(GLO_MEM_CTRL_ADDR_MASK, idx));
+
+	/* select memory type to program */
+	mtk_pce_netsys_rmw(GLO_MEM_CTRL,
+		   GLO_MEM_CTRL_INDEX_MASK,
+		   FIELD_PREP(GLO_MEM_CTRL_INDEX_MASK, type));
+}
+
+static inline void mtk_pce_fe_mem_start(enum fe_mem_cmd cmd)
+{
+	/* trigger start */
+	mtk_pce_netsys_rmw(GLO_MEM_CTRL,
+		   GLO_MEM_CTRL_CMD_MASK,
+		   FIELD_PREP(GLO_MEM_CTRL_CMD_MASK, cmd));
+}
+
+static inline void mtk_pce_fe_mem_wait_transfer_done(void)
+{
+	while (FIELD_GET(GLO_MEM_CTRL_CMD_MASK, mtk_pce_netsys_read(GLO_MEM_CTRL)))
+		udelay(10);
+}
+
+static int mtk_pce_fe_mem_write(enum fe_mem_type type, u32 *raw_data, u32 idx)
+{
+	unsigned long flag;
+	u32 i = 0;
+
+	spin_lock_irqsave(&netsys.lock, flag);
+
+	mtk_pce_fe_mem_config(type, idx);
+
+	/* place data */
+	for (i = 0; i < FE_MEM_DATA_WLEN; i++)
+		mtk_pce_netsys_write(GLO_MEM_DATA_IDX(i), raw_data[i]);
+
+	mtk_pce_fe_mem_start(FE_MEM_CMD_WRITE);
+
+	mtk_pce_fe_mem_wait_transfer_done();
+
+	spin_unlock_irqrestore(&netsys.lock, flag);
+
+	return 0;
+}
+
+static int mtk_pce_fe_mem_read(enum fe_mem_type type, u32 *raw_data, u32 idx)
+{
+	unsigned long flag;
+	u32 i = 0;
+
+	spin_lock_irqsave(&netsys.lock, flag);
+
+	mtk_pce_fe_mem_config(type, idx);
+
+	mtk_pce_fe_mem_start(FE_MEM_CMD_READ);
+
+	mtk_pce_fe_mem_wait_transfer_done();
+
+	/* read data out */
+	for (i = 0; i < FE_MEM_DATA_WLEN; i++)
+		raw_data[i] = mtk_pce_netsys_read(GLO_MEM_DATA_IDX(i));
+
+	spin_unlock_irqrestore(&netsys.lock, flag);
+
+	return 0;
+}
+
+int mtk_pce_fe_mem_msg_send(struct fe_mem_msg *msg)
+{
+	if (unlikely(!msg))
+		return -EINVAL;
+
+	if (msg->cmd >= __FE_MEM_CMD_MAX) {
+		PCE_ERR("invalid fe_mem_cmd: %u\n", msg->cmd);
+		return -EPERM;
+	}
+
+	if (msg->type >= __FE_MEM_TYPE_MAX) {
+		PCE_ERR("invalid fe_mem_type: %u\n", msg->type);
+		return -EPERM;
+	}
+
+	if (msg->index >= netsys.fe_mem_limit[msg->type]) {
+		PCE_ERR("invalid FE_MEM index: %u, type: %u, max: %u\n",
+		       msg->index, msg->type, netsys.fe_mem_limit[msg->type]);
+		return -EPERM;
+	}
+
+	switch (msg->cmd) {
+	case FE_MEM_CMD_WRITE:
+		return mtk_pce_fe_mem_write(msg->type, msg->raw, msg->index);
+	case FE_MEM_CMD_READ:
+		return mtk_pce_fe_mem_read(msg->type, msg->raw, msg->index);
+	default:
+		break;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(mtk_pce_fe_mem_msg_send);
+
+int mtk_pce_netsys_init(struct platform_device *pdev)
+{
+	struct device_node *fe_mem = NULL;
+	struct resource res;
+	int ret = 0;
+
+	fe_mem = of_parse_phandle(pdev->dev.of_node, "fe_mem", 0);
+	if (!fe_mem) {
+		PCE_ERR("can not find fe_mem node\n");
+		return -ENODEV;
+	}
+
+	if (of_address_to_resource(fe_mem, 0, &res))
+		return -ENXIO;
+
+	/* map fe_mem */
+	netsys.base = devm_ioremap(&pdev->dev, res.start, resource_size(&res));
+	if (!netsys.base)
+		return -ENOMEM;
+
+	spin_lock_init(&netsys.lock);
+
+	of_node_put(fe_mem);
+
+	return ret;
+}
+
+void mtk_pce_netsys_deinit(struct platform_device *pdev)
+{
+	/* nothing to deinit right now */
+}
+
+int mtk_pce_enable(void)
+{
+	mtk_pce_cls_enable();
+
+	mtk_pce_dipfilter_enable();
+
+	mtk_pce_cdrt_enable();
+
+	return 0;
+}
+EXPORT_SYMBOL(mtk_pce_enable);
+
+void mtk_pce_disable(void)
+{
+	mtk_pce_cdrt_disable();
+
+	mtk_pce_dipfilter_disable();
+
+	mtk_pce_cls_disable();
+}
+EXPORT_SYMBOL(mtk_pce_disable);
diff --git a/package-21.02/kernel/pce/src/tport_map.c b/package-21.02/kernel/pce/src/tport_map.c
new file mode 100644
index 0000000..0350be6
--- /dev/null
+++ b/package-21.02/kernel/pce/src/tport_map.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+
+#include "pce/internal.h"
+#include "pce/tport_map.h"
+
+int mtk_pce_tport_map_ts_config_read(enum ts_config_entry entry,
+				    struct tsc_desc *ts_cfg)
+{
+	struct fe_mem_msg msg;
+	int ret = 0;
+
+	if (!ts_cfg)
+		return -EINVAL;
+
+	if (!(TS_CONFIG_MASK & BIT(entry))) {
+		PCE_ERR("invalid ts config entry: %u\n", entry);
+		return -EPERM;
+	}
+
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_READ, FE_MEM_TYPE_TS_CONFIG, entry);
+	memset(&msg.raw, 0, sizeof(msg.raw));
+
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+	if (ret) {
+		PCE_NOTICE("fe_mem send msg failed: %d\n", ret);
+		return ret;
+	}
+
+	memcpy(ts_cfg, &msg.tdesc, sizeof(*ts_cfg));
+
+	return ret;
+}
+
+int mtk_pce_tport_map_ts_config_write(enum ts_config_entry entry,
+				    struct tsc_desc *ts_cfg)
+{
+	struct fe_mem_msg msg;
+	int ret = 0;
+
+	if (!ts_cfg)
+		return -EINVAL;
+
+	if (!(TS_CONFIG_MASK & BIT(entry))) {
+		PCE_NOTICE("invalid ts config entry: %u\n", entry);
+		return -EPERM;
+	}
+
+	mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_WRITE, FE_MEM_TYPE_TS_CONFIG, entry);
+	memset(&msg.raw, 0, sizeof(msg.raw));
+	memcpy(&msg.raw, ts_cfg, sizeof(struct tsc_desc));
+
+	ret = mtk_pce_fe_mem_msg_send(&msg);
+	if (ret) {
+		PCE_NOTICE("fe_mem send msg failed: %d\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+int mtk_pce_tport_map_ppe_read(enum pse_port ppe, u64 *map)
+{
+	if (!map)
+		return -EINVAL;
+
+	if (!(PSE_PORT_PPE_MASK & BIT(ppe))) {
+		PCE_NOTICE("invalid ppe index: %u\n", ppe);
+		return -EPERM;
+	}
+
+	*map = mtk_pce_ppe_read(ppe, PPE_TPORT_TBL_0);
+	*map |= ((u64)mtk_pce_ppe_read(ppe, PPE_TPORT_TBL_1)) << 32;
+
+	return 0;
+}
+
+static int mtk_pce_tport_map_update_ts_config(enum ts_config_entry entry,
+					      u32 tport_idx,
+					      enum pse_port target)
+{
+	struct tsc_desc ts_cfg;
+	int ret = 0;
+
+	ret = mtk_pce_tport_map_ts_config_read(entry, &ts_cfg);
+	if (ret)
+		return ret;
+
+	if (tport_idx < TPORT_IDX_MAX / 2) {
+		ts_cfg.tport_map_lower &= (~(0xF << (tport_idx * PSE_PER_PORT_BITS)));
+		ts_cfg.tport_map_lower |= (target << (tport_idx * PSE_PER_PORT_BITS));
+	} else {
+		ts_cfg.tport_map_upper &= (~(0xF << (tport_idx * PSE_PER_PORT_BITS)));
+		ts_cfg.tport_map_upper |= (target << (tport_idx * PSE_PER_PORT_BITS));
+	}
+
+	ret = mtk_pce_tport_map_ts_config_write(entry, &ts_cfg);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+static int mtk_pce_tport_map_update_ppe(enum pse_port ppe,
+					u32 tport_idx,
+					enum pse_port target)
+{
+	u32 mask = (PSE_PER_PORT_MASK
+		    << ((tport_idx % (TPORT_IDX_MAX / 2)) * PSE_PER_PORT_BITS));
+	u32 val = ((target & PSE_PER_PORT_MASK)
+		   << ((tport_idx % (TPORT_IDX_MAX / 2)) * PSE_PER_PORT_BITS));
+
+	if (tport_idx < TPORT_IDX_MAX / 2)
+		mtk_pce_ppe_rmw(ppe, PPE_TPORT_TBL_0, mask, val);
+	else
+		mtk_pce_ppe_rmw(ppe, PPE_TPORT_TBL_1, mask, val);
+
+	return 0;
+}
+
+/*
+ * update tport idx mapping
+ * pse_port: the pse port idx that is going to be modified
+ * tport_idx: the tport idx that is going to be modified
+ * target: the next port for packet when the packet is at pse_port with tport_idx
+ */
+int mtk_pce_tport_map_pse_port_update(enum pse_port pse_port,
+				      u32 tport_idx,
+				      enum pse_port target)
+{
+	int ret = 0;
+
+	if (pse_port >= __PSE_PORT_MAX || target >= __PSE_PORT_MAX) {
+		PCE_NOTICE("invalid pse_port: %u, target: %u\n", pse_port, target);
+		return -EPERM;
+	}
+
+	if (tport_idx >= TPORT_IDX_MAX) {
+		PCE_NOTICE("invalid tport_idx: %u\n", tport_idx);
+		return -EPERM;
+	}
+
+	if (TS_CONFIG_MASK & BIT(pse_port))
+		ret = mtk_pce_tport_map_update_ts_config(pse_port, tport_idx, target);
+	else if (PSE_PORT_PPE_MASK & BIT(pse_port))
+		ret = mtk_pce_tport_map_update_ppe(pse_port, tport_idx, target);
+	else
+		ret = -EINVAL;
+
+	if (ret)
+		PCE_ERR("update tport map failed: %d\n", ret);
+
+	return ret;
+}