[][Add Ethernet HW LRO support depending on hw_capability]

[Description]
Add Ethernet HW LRO support and related debug interface
depending on hw_capability.

---------Debug method 1 - Auto learn table---------
- Dump aggregated flows:
	$ cat /proc/mtketh/hw_lro_auto_tlb
- Modify MAX_AGGREGATED_CNT:
	$ echo 0 [setting] > /proc/mtketh/hw_lro_auto_tlb
- Modify MAX_AGG_TIME:
	$ echo 1 [setting] > /proc/mtketh/hw_lro_auto_tlb
- Modify AGE_TIME:
	$ echo 2 [setting] > /proc/mtketh/hw_lro_auto_tlb
- Modify AUTO_LEARN_LRO_ELIGIBLE_THRESHOLD:
	$ echo 3 [setting] > /proc/mtketh/hw_lro_auto_tlb
- Enable/Disable LRO:
	$ echo 4 [1/0] > /proc/mtketh/hw_lro_auto_tlb

---------Debug method 2 - per LRO ring statistics--------
- Dump each LRO rings' statistics, which includes packet count,
  packet length, flush reason, etc.
	$ cat /proc/mtketh/hw_lro_stats

[Release-log]
N/A

Change-Id: I2359ac177b81077bb7c95ee5fae2f32aadda521f
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/4588327
diff --git a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.c b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.c
index 7f4a7b5..979bc9b 100755
--- a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.c
+++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.c
@@ -28,6 +28,19 @@
 #include "mtk_eth_soc.h"
 #include "mtk_eth_dbg.h"
 
+u32 hw_lro_agg_num_cnt[MTK_HW_LRO_RING_NUM][MTK_HW_LRO_MAX_AGG_CNT + 1];
+u32 hw_lro_agg_size_cnt[MTK_HW_LRO_RING_NUM][16];
+u32 hw_lro_tot_agg_cnt[MTK_HW_LRO_RING_NUM];
+u32 hw_lro_tot_flush_cnt[MTK_HW_LRO_RING_NUM];
+u32 hw_lro_agg_flush_cnt[MTK_HW_LRO_RING_NUM];
+u32 hw_lro_age_flush_cnt[MTK_HW_LRO_RING_NUM];
+u32 hw_lro_seq_flush_cnt[MTK_HW_LRO_RING_NUM];
+u32 hw_lro_timestamp_flush_cnt[MTK_HW_LRO_RING_NUM];
+u32 hw_lro_norule_flush_cnt[MTK_HW_LRO_RING_NUM];
+u32 mtk_hwlro_stats_ebl;
+static struct proc_dir_entry *proc_hw_lro_stats, *proc_hw_lro_auto_tlb;
+typedef int (*mtk_lro_dbg_func) (int par);
+
 struct mtk_eth_debug {
         struct dentry *root;
 };
@@ -78,8 +91,7 @@
 {
 	struct mtk_eth *eth = m->private;
 	struct mtk_mac *mac = 0;
-	u32 d;
-	int  i, j = 0;
+	int  i = 0;
 
 	for (i = 0 ; i < MTK_MAX_DEVS ; i++) {
 		if (!eth->mac[i] ||
@@ -787,14 +799,610 @@
 	.open = dbg_regs_open,
 	.read = seq_read,
 	.llseek = seq_lseek,
+	.release = single_release
+};
+
+void hw_lro_stats_update(u32 ring_no, struct mtk_rx_dma *rxd)
+{
+	u32 idx, agg_cnt, agg_size;
+
+#if defined(CONFIG_MEDIATEK_NETSYS_V2)
+	idx = ring_no - 4;
+	agg_cnt = RX_DMA_GET_AGG_CNT_V2(rxd->rxd6);
+#else
+	idx = ring_no - 1;
+	agg_cnt = RX_DMA_GET_AGG_CNT(rxd->rxd2);
+#endif
+
+	agg_size = RX_DMA_GET_PLEN0(rxd->rxd2);
+
+	hw_lro_agg_size_cnt[idx][agg_size / 5000]++;
+	hw_lro_agg_num_cnt[idx][agg_cnt]++;
+	hw_lro_tot_flush_cnt[idx]++;
+	hw_lro_tot_agg_cnt[idx] += agg_cnt;
+}
+
+void hw_lro_flush_stats_update(u32 ring_no, struct mtk_rx_dma *rxd)
+{
+	u32 idx, flush_reason;
+
+#if defined(CONFIG_MEDIATEK_NETSYS_V2)
+	idx = ring_no - 4;
+	flush_reason = RX_DMA_GET_FLUSH_RSN_V2(rxd->rxd6);
+#else
+	idx = ring_no - 1;
+	flush_reason = RX_DMA_GET_REV(rxd->rxd2);
+#endif
+
+	if ((flush_reason & 0x7) == MTK_HW_LRO_AGG_FLUSH)
+		hw_lro_agg_flush_cnt[idx]++;
+	else if ((flush_reason & 0x7) == MTK_HW_LRO_AGE_FLUSH)
+		hw_lro_age_flush_cnt[idx]++;
+	else if ((flush_reason & 0x7) == MTK_HW_LRO_NOT_IN_SEQ_FLUSH)
+		hw_lro_seq_flush_cnt[idx]++;
+	else if ((flush_reason & 0x7) == MTK_HW_LRO_TIMESTAMP_FLUSH)
+		hw_lro_timestamp_flush_cnt[idx]++;
+	else if ((flush_reason & 0x7) == MTK_HW_LRO_NON_RULE_FLUSH)
+		hw_lro_norule_flush_cnt[idx]++;
+}
+
+ssize_t hw_lro_stats_write(struct file *file, const char __user *buffer,
+			   size_t count, loff_t *data)
+{
+	memset(hw_lro_agg_num_cnt, 0, sizeof(hw_lro_agg_num_cnt));
+	memset(hw_lro_agg_size_cnt, 0, sizeof(hw_lro_agg_size_cnt));
+	memset(hw_lro_tot_agg_cnt, 0, sizeof(hw_lro_tot_agg_cnt));
+	memset(hw_lro_tot_flush_cnt, 0, sizeof(hw_lro_tot_flush_cnt));
+	memset(hw_lro_agg_flush_cnt, 0, sizeof(hw_lro_agg_flush_cnt));
+	memset(hw_lro_age_flush_cnt, 0, sizeof(hw_lro_age_flush_cnt));
+	memset(hw_lro_seq_flush_cnt, 0, sizeof(hw_lro_seq_flush_cnt));
+	memset(hw_lro_timestamp_flush_cnt, 0,
+	       sizeof(hw_lro_timestamp_flush_cnt));
+	memset(hw_lro_norule_flush_cnt, 0, sizeof(hw_lro_norule_flush_cnt));
+
+	pr_info("clear hw lro cnt table\n");
+
+	return count;
+}
+
+int hw_lro_stats_read_v1(struct seq_file *seq, void *v)
+{
+	int i;
+
+	seq_puts(seq, "HW LRO statistic dump:\n");
+
+	/* Agg number count */
+	seq_puts(seq, "Cnt:   RING1 | RING2 | RING3 | Total\n");
+	for (i = 0; i <= MTK_HW_LRO_MAX_AGG_CNT; i++) {
+		seq_printf(seq, " %d :      %d        %d        %d        %d\n",
+			   i, hw_lro_agg_num_cnt[0][i],
+			   hw_lro_agg_num_cnt[1][i], hw_lro_agg_num_cnt[2][i],
+			   hw_lro_agg_num_cnt[0][i] + hw_lro_agg_num_cnt[1][i] +
+			   hw_lro_agg_num_cnt[2][i]);
+	}
+
+	/* Total agg count */
+	seq_puts(seq, "Total agg:   RING1 | RING2 | RING3 | Total\n");
+	seq_printf(seq, "                %d      %d      %d      %d\n",
+		   hw_lro_tot_agg_cnt[0], hw_lro_tot_agg_cnt[1],
+		   hw_lro_tot_agg_cnt[2],
+		   hw_lro_tot_agg_cnt[0] + hw_lro_tot_agg_cnt[1] +
+		   hw_lro_tot_agg_cnt[2]);
+
+	/* Total flush count */
+	seq_puts(seq, "Total flush:   RING1 | RING2 | RING3 | Total\n");
+	seq_printf(seq, "                %d      %d      %d      %d\n",
+		   hw_lro_tot_flush_cnt[0], hw_lro_tot_flush_cnt[1],
+		   hw_lro_tot_flush_cnt[2],
+		   hw_lro_tot_flush_cnt[0] + hw_lro_tot_flush_cnt[1] +
+		   hw_lro_tot_flush_cnt[2]);
+
+	/* Avg agg count */
+	seq_puts(seq, "Avg agg:   RING1 | RING2 | RING3 | Total\n");
+	seq_printf(seq, "                %d      %d      %d      %d\n",
+		   (hw_lro_tot_flush_cnt[0]) ?
+		    hw_lro_tot_agg_cnt[0] / hw_lro_tot_flush_cnt[0] : 0,
+		   (hw_lro_tot_flush_cnt[1]) ?
+		    hw_lro_tot_agg_cnt[1] / hw_lro_tot_flush_cnt[1] : 0,
+		   (hw_lro_tot_flush_cnt[2]) ?
+		    hw_lro_tot_agg_cnt[2] / hw_lro_tot_flush_cnt[2] : 0,
+		   (hw_lro_tot_flush_cnt[0] + hw_lro_tot_flush_cnt[1] +
+		    hw_lro_tot_flush_cnt[2]) ?
+		    ((hw_lro_tot_agg_cnt[0] + hw_lro_tot_agg_cnt[1] +
+		      hw_lro_tot_agg_cnt[2]) / (hw_lro_tot_flush_cnt[0] +
+		      hw_lro_tot_flush_cnt[1] + hw_lro_tot_flush_cnt[2])) : 0);
+
+	/*  Statistics of aggregation size counts */
+	seq_puts(seq, "HW LRO flush pkt len:\n");
+	seq_puts(seq, " Length  | RING1  | RING2  | RING3  | Total\n");
+	for (i = 0; i < 15; i++) {
+		seq_printf(seq, "%d~%d: %d      %d      %d      %d\n", i * 5000,
+			   (i + 1) * 5000, hw_lro_agg_size_cnt[0][i],
+			   hw_lro_agg_size_cnt[1][i], hw_lro_agg_size_cnt[2][i],
+			   hw_lro_agg_size_cnt[0][i] +
+			   hw_lro_agg_size_cnt[1][i] +
+			   hw_lro_agg_size_cnt[2][i]);
+	}
+
+	seq_puts(seq, "Flush reason:   RING1 | RING2 | RING3 | Total\n");
+	seq_printf(seq, "AGG timeout:      %d      %d      %d      %d\n",
+		   hw_lro_agg_flush_cnt[0], hw_lro_agg_flush_cnt[1],
+		   hw_lro_agg_flush_cnt[2],
+		   (hw_lro_agg_flush_cnt[0] + hw_lro_agg_flush_cnt[1] +
+		    hw_lro_agg_flush_cnt[2]));
+
+	seq_printf(seq, "AGE timeout:      %d      %d      %d      %d\n",
+		   hw_lro_age_flush_cnt[0], hw_lro_age_flush_cnt[1],
+		   hw_lro_age_flush_cnt[2],
+		   (hw_lro_age_flush_cnt[0] + hw_lro_age_flush_cnt[1] +
+		    hw_lro_age_flush_cnt[2]));
+
+	seq_printf(seq, "Not in-sequence:  %d      %d      %d      %d\n",
+		   hw_lro_seq_flush_cnt[0], hw_lro_seq_flush_cnt[1],
+		   hw_lro_seq_flush_cnt[2],
+		   (hw_lro_seq_flush_cnt[0] + hw_lro_seq_flush_cnt[1] +
+		    hw_lro_seq_flush_cnt[2]));
+
+	seq_printf(seq, "Timestamp:        %d      %d      %d      %d\n",
+		   hw_lro_timestamp_flush_cnt[0],
+		   hw_lro_timestamp_flush_cnt[1],
+		   hw_lro_timestamp_flush_cnt[2],
+		   (hw_lro_timestamp_flush_cnt[0] +
+		    hw_lro_timestamp_flush_cnt[1] +
+		    hw_lro_timestamp_flush_cnt[2]));
+
+	seq_printf(seq, "No LRO rule:      %d      %d      %d      %d\n",
+		   hw_lro_norule_flush_cnt[0],
+		   hw_lro_norule_flush_cnt[1],
+		   hw_lro_norule_flush_cnt[2],
+		   (hw_lro_norule_flush_cnt[0] +
+		    hw_lro_norule_flush_cnt[1] +
+		    hw_lro_norule_flush_cnt[2]));
+
+	return 0;
+}
+
+int hw_lro_stats_read_v2(struct seq_file *seq, void *v)
+{
+	int i;
+
+	seq_puts(seq, "HW LRO statistic dump:\n");
+
+	/* Agg number count */
+	seq_puts(seq, "Cnt:   RING4 | RING5 | RING6 | RING7 Total\n");
+	for (i = 0; i <= MTK_HW_LRO_MAX_AGG_CNT; i++) {
+		seq_printf(seq,
+			   " %d :      %d        %d        %d        %d        %d\n",
+			   i, hw_lro_agg_num_cnt[0][i], hw_lro_agg_num_cnt[1][i],
+			   hw_lro_agg_num_cnt[2][i], hw_lro_agg_num_cnt[3][i],
+			   hw_lro_agg_num_cnt[0][i] + hw_lro_agg_num_cnt[1][i] +
+			   hw_lro_agg_num_cnt[2][i] + hw_lro_agg_num_cnt[3][i]);
+	}
+
+	/* Total agg count */
+	seq_puts(seq, "Total agg:   RING4 | RING5 | RING6 | RING7 Total\n");
+	seq_printf(seq, "                %d      %d      %d      %d      %d\n",
+		   hw_lro_tot_agg_cnt[0], hw_lro_tot_agg_cnt[1],
+		   hw_lro_tot_agg_cnt[2], hw_lro_tot_agg_cnt[3],
+		   hw_lro_tot_agg_cnt[0] + hw_lro_tot_agg_cnt[1] +
+		   hw_lro_tot_agg_cnt[2] + hw_lro_tot_agg_cnt[3]);
+
+	/* Total flush count */
+	seq_puts(seq, "Total flush:   RING4 | RING5 | RING6 | RING7 Total\n");
+	seq_printf(seq, "                %d      %d      %d      %d      %d\n",
+		   hw_lro_tot_flush_cnt[0], hw_lro_tot_flush_cnt[1],
+		   hw_lro_tot_flush_cnt[2], hw_lro_tot_flush_cnt[3],
+		   hw_lro_tot_flush_cnt[0] + hw_lro_tot_flush_cnt[1] +
+		   hw_lro_tot_flush_cnt[2] + hw_lro_tot_flush_cnt[3]);
+
+	/* Avg agg count */
+	seq_puts(seq, "Avg agg:   RING4 | RING5 | RING6 | RING7 Total\n");
+	seq_printf(seq, "                %d      %d      %d      %d      %d\n",
+		   (hw_lro_tot_flush_cnt[0]) ?
+		    hw_lro_tot_agg_cnt[0] / hw_lro_tot_flush_cnt[0] : 0,
+		   (hw_lro_tot_flush_cnt[1]) ?
+		    hw_lro_tot_agg_cnt[1] / hw_lro_tot_flush_cnt[1] : 0,
+		   (hw_lro_tot_flush_cnt[2]) ?
+		    hw_lro_tot_agg_cnt[2] / hw_lro_tot_flush_cnt[2] : 0,
+		   (hw_lro_tot_flush_cnt[3]) ?
+                    hw_lro_tot_agg_cnt[3] / hw_lro_tot_flush_cnt[3] : 0,
+		   (hw_lro_tot_flush_cnt[0] + hw_lro_tot_flush_cnt[1] +
+		    hw_lro_tot_flush_cnt[2] + hw_lro_tot_flush_cnt[3]) ?
+		    ((hw_lro_tot_agg_cnt[0] + hw_lro_tot_agg_cnt[1] +
+		      hw_lro_tot_agg_cnt[2] + hw_lro_tot_agg_cnt[3]) /
+		     (hw_lro_tot_flush_cnt[0] + hw_lro_tot_flush_cnt[1] +
+		      hw_lro_tot_flush_cnt[2] + hw_lro_tot_flush_cnt[3])) : 0);
+
+	/*  Statistics of aggregation size counts */
+	seq_puts(seq, "HW LRO flush pkt len:\n");
+	seq_puts(seq, " Length  | RING4  | RING5  | RING6  | RING7 Total\n");
+	for (i = 0; i < 15; i++) {
+		seq_printf(seq, "%d~%d: %d      %d      %d      %d      %d\n",
+			   i * 5000, (i + 1) * 5000,
+			   hw_lro_agg_size_cnt[0][i], hw_lro_agg_size_cnt[1][i],
+			   hw_lro_agg_size_cnt[2][i], hw_lro_agg_size_cnt[3][i],
+			   hw_lro_agg_size_cnt[0][i] +
+			   hw_lro_agg_size_cnt[1][i] +
+			   hw_lro_agg_size_cnt[2][i] +
+			   hw_lro_agg_size_cnt[3][i]);
+	}
+
+	seq_puts(seq, "Flush reason:   RING4 | RING5 | RING6 | RING7 Total\n");
+	seq_printf(seq, "AGG timeout:      %d      %d      %d      %d      %d\n",
+		   hw_lro_agg_flush_cnt[0], hw_lro_agg_flush_cnt[1],
+		   hw_lro_agg_flush_cnt[2], hw_lro_agg_flush_cnt[3],
+		   (hw_lro_agg_flush_cnt[0] + hw_lro_agg_flush_cnt[1] +
+		    hw_lro_agg_flush_cnt[2] + hw_lro_agg_flush_cnt[3]));
+
+	seq_printf(seq, "AGE timeout:      %d      %d      %d      %d      %d\n",
+		   hw_lro_age_flush_cnt[0], hw_lro_age_flush_cnt[1],
+		   hw_lro_age_flush_cnt[2], hw_lro_age_flush_cnt[3],
+		   (hw_lro_age_flush_cnt[0] + hw_lro_age_flush_cnt[1] +
+		    hw_lro_age_flush_cnt[2] + hw_lro_age_flush_cnt[3]));
+
+	seq_printf(seq, "Not in-sequence:  %d      %d      %d      %d      %d\n",
+		   hw_lro_seq_flush_cnt[0], hw_lro_seq_flush_cnt[1],
+		   hw_lro_seq_flush_cnt[2], hw_lro_seq_flush_cnt[3],
+		   (hw_lro_seq_flush_cnt[0] + hw_lro_seq_flush_cnt[1] +
+		    hw_lro_seq_flush_cnt[2] + hw_lro_seq_flush_cnt[3]));
+
+	seq_printf(seq, "Timestamp:        %d      %d      %d      %d      %d\n",
+		   hw_lro_timestamp_flush_cnt[0],
+		   hw_lro_timestamp_flush_cnt[1],
+		   hw_lro_timestamp_flush_cnt[2],
+		   hw_lro_timestamp_flush_cnt[3],
+		   (hw_lro_timestamp_flush_cnt[0] +
+		    hw_lro_timestamp_flush_cnt[1] +
+		    hw_lro_timestamp_flush_cnt[2] +
+		    hw_lro_timestamp_flush_cnt[3]));
+
+	seq_printf(seq, "No LRO rule:      %d      %d      %d      %d      %d\n",
+		   hw_lro_norule_flush_cnt[0],
+		   hw_lro_norule_flush_cnt[1],
+		   hw_lro_norule_flush_cnt[2],
+		   hw_lro_norule_flush_cnt[3],
+		   (hw_lro_norule_flush_cnt[0] +
+		    hw_lro_norule_flush_cnt[1] +
+		    hw_lro_norule_flush_cnt[2] +
+		    hw_lro_norule_flush_cnt[3]));
+
+	return 0;
+}
+
+int hw_lro_stats_read_wrapper(struct seq_file *seq, void *v)
+{
+	struct mtk_eth *eth = g_eth;
+
+	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+		hw_lro_stats_read_v2(seq, v);
+	else
+		hw_lro_stats_read_v1(seq, v);
+
+	return 0;
+}
+
+static int hw_lro_stats_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, hw_lro_stats_read_wrapper, NULL);
+}
+
+static const struct file_operations hw_lro_stats_fops = {
+	.owner = THIS_MODULE,
+	.open = hw_lro_stats_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.write = hw_lro_stats_write,
 	.release = single_release
 };
 
+int hwlro_agg_cnt_ctrl(int cnt)
+{
+	int i;
+
+	for (i = 1; i <= MTK_HW_LRO_RING_NUM; i++)
+		SET_PDMA_RXRING_MAX_AGG_CNT(g_eth, i, cnt);
+
+	return 0;
+}
+
+int hwlro_agg_time_ctrl(int time)
+{
+	int i;
+
+	for (i = 1; i <= MTK_HW_LRO_RING_NUM; i++)
+		SET_PDMA_RXRING_AGG_TIME(g_eth, i, time);
+
+	return 0;
+}
+
+int hwlro_age_time_ctrl(int time)
+{
+	int i;
+
+	for (i = 1; i <= MTK_HW_LRO_RING_NUM; i++)
+		SET_PDMA_RXRING_AGE_TIME(g_eth, i, time);
+
+	return 0;
+}
+
+int hwlro_threshold_ctrl(int bandwidth)
+{
+	SET_PDMA_LRO_BW_THRESHOLD(g_eth, bandwidth);
+
+	return 0;
+}
+
+int hwlro_ring_enable_ctrl(int enable)
+{
+	int i;
+
+	pr_info("[%s] %s HW LRO rings\n", __func__, (enable) ? "Enable" : "Disable");
+
+	for (i = 1; i <= MTK_HW_LRO_RING_NUM; i++)
+		SET_PDMA_RXRING_VALID(g_eth, i, enable);
+
+	return 0;
+}
+
+int hwlro_stats_enable_ctrl(int enable)
+{
+	pr_info("[%s] %s HW LRO statistics\n", __func__, (enable) ? "Enable" : "Disable");
+	mtk_hwlro_stats_ebl = enable;
+
+	return 0;
+}
+
+static const mtk_lro_dbg_func lro_dbg_func[] = {
+	[0] = hwlro_agg_cnt_ctrl,
+	[1] = hwlro_agg_time_ctrl,
+	[2] = hwlro_age_time_ctrl,
+	[3] = hwlro_threshold_ctrl,
+	[4] = hwlro_ring_enable_ctrl,
+	[5] = hwlro_stats_enable_ctrl,
+};
+
+ssize_t hw_lro_auto_tlb_write(struct file *file, const char __user *buffer,
+			      size_t count, loff_t *data)
+{
+	char buf[32];
+	char *p_buf;
+	char *p_token = NULL;
+	char *p_delimiter = " \t";
+	long x = 0, y = 0;
+	int len = count;
+	int ret;
+
+	if (len >= sizeof(buf)) {
+		pr_info("Input handling fail!\n");
+		len = sizeof(buf) - 1;
+		return -1;
+	}
+
+	if (copy_from_user(buf, buffer, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	p_buf = buf;
+	p_token = strsep(&p_buf, p_delimiter);
+	if (!p_token)
+		x = 0;
+	else
+		ret = kstrtol(p_token, 10, &x);
+
+	p_token = strsep(&p_buf, "\t\n ");
+	if (p_token)
+		ret = kstrtol(p_token, 10, &y);
+
+	if (lro_dbg_func[x] && (ARRAY_SIZE(lro_dbg_func) > x))
+		(*lro_dbg_func[x]) (y);
+
+	return count;
+}
+
+void hw_lro_auto_tlb_dump_v1(struct seq_file *seq, u32 index)
+{
+	int i;
+	struct mtk_lro_alt_v1 alt;
+	__be32 addr;
+	u32 tlb_info[9];
+	u32 dw_len, cnt, priority;
+	u32 entry;
+
+	if (index > 4)
+		index = index - 1;
+	entry = (index * 9) + 1;
+
+	/* read valid entries of the auto-learn table */
+	mtk_w32(g_eth, entry, MTK_FE_ALT_CF8);
+
+	for (i = 0; i < 9; i++)
+		tlb_info[i] = mtk_r32(g_eth, MTK_FE_ALT_SEQ_CFC);
+
+	memcpy(&alt, tlb_info, sizeof(struct mtk_lro_alt_v1));
+
+	dw_len = alt.alt_info7.dw_len;
+	cnt = alt.alt_info6.cnt;
+
+	if (mtk_r32(g_eth, MTK_PDMA_LRO_CTRL_DW0) & MTK_LRO_ALT_PKT_CNT_MODE)
+		priority = cnt;		/* packet count */
+	else
+		priority = dw_len;	/* byte count */
+
+	/* dump valid entries of the auto-learn table */
+	if (index >= 4)
+		seq_printf(seq, "\n===== TABLE Entry: %d (Act) =====\n", index);
+	else
+		seq_printf(seq, "\n===== TABLE Entry: %d (LRU) =====\n", index);
+
+	if (alt.alt_info8.ipv4) {
+		addr = htonl(alt.alt_info1.sip0);
+		seq_printf(seq, "SIP = %pI4 (IPv4)\n", &addr);
+	} else {
+		seq_printf(seq, "SIP = %08X:%08X:%08X:%08X (IPv6)\n",
+			   alt.alt_info4.sip3, alt.alt_info3.sip2,
+			   alt.alt_info2.sip1, alt.alt_info1.sip0);
+	}
+
+	seq_printf(seq, "DIP_ID = %d\n", alt.alt_info8.dip_id);
+	seq_printf(seq, "TCP SPORT = %d | TCP DPORT = %d\n",
+		   alt.alt_info0.stp, alt.alt_info0.dtp);
+	seq_printf(seq, "VLAN_VID_VLD = %d\n", alt.alt_info6.vlan_vid_vld);
+	seq_printf(seq, "VLAN1 = %d | VLAN2 = %d | VLAN3 = %d | VLAN4 =%d\n",
+		   (alt.alt_info5.vlan_vid0 & 0xfff),
+		   ((alt.alt_info5.vlan_vid0 >> 12) & 0xfff),
+		   ((alt.alt_info6.vlan_vid1 << 8) |
+		   ((alt.alt_info5.vlan_vid0 >> 24) & 0xfff)),
+		   ((alt.alt_info6.vlan_vid1 >> 4) & 0xfff));
+	seq_printf(seq, "TPUT = %d | FREQ = %d\n", dw_len, cnt);
+	seq_printf(seq, "PRIORITY = %d\n", priority);
+}
+
+void hw_lro_auto_tlb_dump_v2(struct seq_file *seq, u32 index)
+{
+	int i;
+	struct mtk_lro_alt_v2 alt;
+	u32 score = 0, ipv4 = 0;
+	u32 ipv6[4] = { 0 };
+	u32 tlb_info[12];
+
+	/* read valid entries of the auto-learn table */
+	mtk_w32(g_eth, index << MTK_LRO_ALT_INDEX_OFFSET, MTK_LRO_ALT_DBG);
+
+	for (i = 0; i < 11; i++)
+		tlb_info[i] = mtk_r32(g_eth, MTK_LRO_ALT_DBG_DATA);
+
+	memcpy(&alt, tlb_info, sizeof(struct mtk_lro_alt_v2));
+
-#define PROCREG_ESW_CNT         "esw_cnt"
-#define PROCREG_TXRING          "tx_ring"
-#define PROCREG_RXRING          "rx_ring"
-#define PROCREG_DIR             "mtketh"
-#define PROCREG_DBG_REGS        "dbg_regs"
+	if (mtk_r32(g_eth, MTK_PDMA_LRO_CTRL_DW0) & MTK_LRO_ALT_PKT_CNT_MODE)
+		score = 1;	/* packet count */
+	else
+		score = 0;	/* byte count */
+
+	/* dump valid entries of the auto-learn table */
+	if (alt.alt_info0.valid) {
+		if (index < 5)
+			seq_printf(seq,
+				   "\n===== TABLE Entry: %d (onging) =====\n",
+				   index);
+		else
+			seq_printf(seq,
+				   "\n===== TABLE Entry: %d (candidate) =====\n",
+				   index);
+
+		if (alt.alt_info1.v4_valid) {
+			ipv4 = (alt.alt_info4.sip0_h << 23) |
+				alt.alt_info5.sip0_l;
+			seq_printf(seq, "SIP = 0x%x: (IPv4)\n", ipv4);
+
+			ipv4 = (alt.alt_info8.dip0_h << 23) |
+				alt.alt_info9.dip0_l;
+			seq_printf(seq, "DIP = 0x%x: (IPv4)\n", ipv4);
+		} else if (alt.alt_info1.v6_valid) {
+			ipv6[3] = (alt.alt_info1.sip3_h << 23) |
+				   (alt.alt_info2.sip3_l << 9);
+			ipv6[2] = (alt.alt_info2.sip2_h << 23) |
+				   (alt.alt_info3.sip2_l << 9);
+			ipv6[1] = (alt.alt_info3.sip1_h << 23) |
+				   (alt.alt_info4.sip1_l << 9);
+			ipv6[0] = (alt.alt_info4.sip0_h << 23) |
+				   (alt.alt_info5.sip0_l << 9);
+			seq_printf(seq, "SIP = 0x%x:0x%x:0x%x:0x%x (IPv6)\n",
+				   ipv6[3], ipv6[2], ipv6[1], ipv6[0]);
+
+			ipv6[3] = (alt.alt_info5.dip3_h << 23) |
+				   (alt.alt_info6.dip3_l << 9);
+			ipv6[2] = (alt.alt_info6.dip2_h << 23) |
+				   (alt.alt_info7.dip2_l << 9);
+			ipv6[1] = (alt.alt_info7.dip1_h << 23) |
+				   (alt.alt_info8.dip1_l << 9);
+			ipv6[0] = (alt.alt_info8.dip0_h << 23) |
+				   (alt.alt_info9.dip0_l << 9);
+			seq_printf(seq, "DIP = 0x%x:0x%x:0x%x:0x%x (IPv6)\n",
+				   ipv6[3], ipv6[2], ipv6[1], ipv6[0]);
+		}
+
+		seq_printf(seq, "TCP SPORT = %d | TCP DPORT = %d\n",
+			   (alt.alt_info9.sp_h << 7) | (alt.alt_info10.sp_l),
+			   alt.alt_info10.dp);
+	}
+}
+
+int hw_lro_auto_tlb_read(struct seq_file *seq, void *v)
+{
+	int i;
+	u32 reg_val;
+	u32 reg_op1, reg_op2, reg_op3, reg_op4;
+	u32 agg_cnt, agg_time, age_time;
+
+	seq_puts(seq, "Usage of /proc/mtketh/hw_lro_auto_tlb:\n");
+	seq_puts(seq, "echo [function] [setting] > /proc/mtketh/hw_lro_auto_tlb\n");
+	seq_puts(seq, "Functions:\n");
+	seq_puts(seq, "[0] = hwlro_agg_cnt_ctrl\n");
+	seq_puts(seq, "[1] = hwlro_agg_time_ctrl\n");
+	seq_puts(seq, "[2] = hwlro_age_time_ctrl\n");
+	seq_puts(seq, "[3] = hwlro_threshold_ctrl\n");
+	seq_puts(seq, "[4] = hwlro_ring_enable_ctrl\n");
+	seq_puts(seq, "[5] = hwlro_stats_enable_ctrl\n\n");
+
+	if (MTK_HAS_CAPS(g_eth->soc->caps, MTK_NETSYS_V2)) {
+		for (i = 1; i <= 8; i++)
+			hw_lro_auto_tlb_dump_v2(seq, i);
+	} else {
+		/* Read valid entries of the auto-learn table */
+		mtk_w32(g_eth, 0, MTK_FE_ALT_CF8);
+		reg_val = mtk_r32(g_eth, MTK_FE_ALT_SEQ_CFC);
+
+		seq_printf(seq,
+			   "HW LRO Auto-learn Table: (MTK_FE_ALT_SEQ_CFC=0x%x)\n",
+			   reg_val);
+
+		for (i = 7; i >= 0; i--) {
+			if (reg_val & (1 << i))
+				hw_lro_auto_tlb_dump_v1(seq, i);
+		}
+	}
+
+	/* Read the agg_time/age_time/agg_cnt of LRO rings */
+	seq_puts(seq, "\nHW LRO Ring Settings\n");
+
+	for (i = 1; i <= MTK_HW_LRO_RING_NUM; i++) {
+		reg_op1 = mtk_r32(g_eth, MTK_LRO_CTRL_DW1_CFG(i));
+		reg_op2 = mtk_r32(g_eth, MTK_LRO_CTRL_DW2_CFG(i));
+		reg_op3 = mtk_r32(g_eth, MTK_LRO_CTRL_DW3_CFG(i));
+		reg_op4 = mtk_r32(g_eth, MTK_PDMA_LRO_CTRL_DW2);
+
+		agg_cnt =
+		    ((reg_op3 & 0x3) << 6) |
+		    ((reg_op2 >> MTK_LRO_RING_AGG_CNT_L_OFFSET) & 0x3f);
+		agg_time = (reg_op2 >> MTK_LRO_RING_AGG_TIME_OFFSET) & 0xffff;
+		age_time =
+		    ((reg_op2 & 0x3f) << 10) |
+		    ((reg_op1 >> MTK_LRO_RING_AGE_TIME_L_OFFSET) & 0x3ff);
+		seq_printf(seq,
+			   "Ring[%d]: MAX_AGG_CNT=%d, AGG_TIME=%d, AGE_TIME=%d, Threshold=%d\n",
+			   (MTK_HAS_CAPS(g_eth->soc->caps, MTK_NETSYS_V2))? i+3 : i,
+			   agg_cnt, agg_time, age_time, reg_op4);
+	}
+
+	seq_puts(seq, "\n");
+
+	return 0;
+}
+
+static int hw_lro_auto_tlb_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, hw_lro_auto_tlb_read, NULL);
+}
+
+static const struct file_operations hw_lro_auto_tlb_fops = {
+	.owner = THIS_MODULE,
+	.open = hw_lro_auto_tlb_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.write = hw_lro_auto_tlb_write,
+	.release = single_release
+};
 
 struct proc_dir_entry *proc_reg_dir;
 static struct proc_dir_entry *proc_esw_cnt, *proc_dbg_regs;
@@ -826,6 +1434,21 @@
 	if (!proc_dbg_regs)
 		pr_notice("!! FAIL to create %s PROC !!\n", PROCREG_DBG_REGS);
 
+	if (g_eth->hwlro) {
+		proc_hw_lro_stats =
+			proc_create(PROCREG_HW_LRO_STATS, 0, proc_reg_dir,
+				    &hw_lro_stats_fops);
+		if (!proc_hw_lro_stats)
+			pr_info("!! FAIL to create %s PROC !!\n", PROCREG_HW_LRO_STATS);
+
+		proc_hw_lro_auto_tlb =
+			proc_create(PROCREG_HW_LRO_AUTO_TLB, 0, proc_reg_dir,
+				    &hw_lro_auto_tlb_fops);
+		if (!proc_hw_lro_auto_tlb)
+			pr_info("!! FAIL to create %s PROC !!\n",
+				PROCREG_HW_LRO_AUTO_TLB);
+	}
+
 	return 0;
 }
 
@@ -844,5 +1467,13 @@
 
 	if (proc_dbg_regs)
 		remove_proc_entry(PROCREG_DBG_REGS, proc_reg_dir);
+
+	if (g_eth->hwlro) {
+		if (proc_hw_lro_stats)
+			remove_proc_entry(PROCREG_HW_LRO_STATS, proc_reg_dir);
+
+		if (proc_hw_lro_auto_tlb)
+			remove_proc_entry(PROCREG_HW_LRO_AUTO_TLB, proc_reg_dir);
+	}
 }
 
diff --git a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.h b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.h
index 0e96a60..b44f93e 100755
--- a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.h
+++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.h
@@ -45,6 +45,196 @@
 #define MTKETH_MII_WRITE_CL45            0x89FD
 #define REG_ESW_MAX                     0xFC
 
+#define PROCREG_ESW_CNT			"esw_cnt"
+#define PROCREG_TXRING			"tx_ring"
+#define PROCREG_RXRING			"rx_ring"
+#define PROCREG_DIR			"mtketh"
+#define PROCREG_DBG_REGS		"dbg_regs"
+#define PROCREG_HW_LRO_STATS		"hw_lro_stats"
+#define PROCREG_HW_LRO_AUTO_TLB		"hw_lro_auto_tlb"
+
+/* HW LRO flush reason */
+#define MTK_HW_LRO_AGG_FLUSH		(1)
+#define MTK_HW_LRO_AGE_FLUSH		(2)
+#define MTK_HW_LRO_NOT_IN_SEQ_FLUSH	(3)
+#define MTK_HW_LRO_TIMESTAMP_FLUSH	(4)
+#define MTK_HW_LRO_NON_RULE_FLUSH	(5)
+
+#define SET_PDMA_RXRING_MAX_AGG_CNT(eth, x, y)				\
+{									\
+	u32 reg_val1 = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(x));		\
+	u32 reg_val2 = mtk_r32(eth, MTK_LRO_CTRL_DW3_CFG(x));		\
+	reg_val1 &= ~MTK_LRO_RING_AGG_CNT_L_MASK;			\
+	reg_val2 &= ~MTK_LRO_RING_AGG_CNT_H_MASK;			\
+	reg_val1 |= ((y) & 0x3f) << MTK_LRO_RING_AGG_CNT_L_OFFSET;	\
+	reg_val2 |= (((y) >> 6) & 0x03) <<				\
+		     MTK_LRO_RING_AGG_CNT_H_OFFSET;			\
+	mtk_w32(eth, reg_val1, MTK_LRO_CTRL_DW2_CFG(x));		\
+	mtk_w32(eth, reg_val2, MTK_LRO_CTRL_DW3_CFG(x));		\
+}
+
+#define SET_PDMA_RXRING_AGG_TIME(eth, x, y)				\
+{									\
+	u32 reg_val = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(x));		\
+	reg_val &= ~MTK_LRO_RING_AGG_TIME_MASK;				\
+	reg_val |= ((y) & 0xffff) << MTK_LRO_RING_AGG_TIME_OFFSET;	\
+	mtk_w32(eth, reg_val, MTK_LRO_CTRL_DW2_CFG(x));			\
+}
+
+#define SET_PDMA_RXRING_AGE_TIME(eth, x, y)				\
+{									\
+	u32 reg_val1 = mtk_r32(eth, MTK_LRO_CTRL_DW1_CFG(x));		\
+	u32 reg_val2 = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(x));		\
+	reg_val1 &= ~MTK_LRO_RING_AGE_TIME_L_MASK;			\
+	reg_val2 &= ~MTK_LRO_RING_AGE_TIME_H_MASK;			\
+	reg_val1 |= ((y) & 0x3ff) << MTK_LRO_RING_AGE_TIME_L_OFFSET;	\
+	reg_val2 |= (((y) >> 10) & 0x03f) <<				\
+		     MTK_LRO_RING_AGE_TIME_H_OFFSET;			\
+	mtk_w32(eth, reg_val1, MTK_LRO_CTRL_DW1_CFG(x));		\
+	mtk_w32(eth, reg_val2, MTK_LRO_CTRL_DW2_CFG(x));		\
+}
+
+#define SET_PDMA_LRO_BW_THRESHOLD(eth, x)				\
+{									\
+	u32 reg_val = mtk_r32(eth, MTK_PDMA_LRO_CTRL_DW2);		\
+	reg_val = (x);							\
+	mtk_w32(eth, reg_val, MTK_PDMA_LRO_CTRL_DW2);			\
+}
+
+#define SET_PDMA_RXRING_VALID(eth, x, y)				\
+{									\
+	u32 reg_val = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(x));		\
+	reg_val &= ~(0x1 << MTK_RX_PORT_VALID_OFFSET);			\
+	reg_val |= ((y) & 0x1) << MTK_RX_PORT_VALID_OFFSET;		\
+	mtk_w32(eth, reg_val, MTK_LRO_CTRL_DW2_CFG(x));			\
+}
+
+struct mtk_lro_alt_v1_info0 {
+	u32 dtp : 16;
+	u32 stp : 16;
+};
+
+struct mtk_lro_alt_v1_info1 {
+	u32 sip0 : 32;
+};
+
+struct mtk_lro_alt_v1_info2 {
+	u32 sip1 : 32;
+};
+
+struct mtk_lro_alt_v1_info3 {
+	u32 sip2 : 32;
+};
+
+struct mtk_lro_alt_v1_info4 {
+	u32 sip3 : 32;
+};
+
+struct mtk_lro_alt_v1_info5 {
+	u32 vlan_vid0 : 32;
+};
+
+struct mtk_lro_alt_v1_info6 {
+	u32 vlan_vid1 : 16;
+	u32 vlan_vid_vld : 4;
+	u32 cnt : 12;
+};
+
+struct mtk_lro_alt_v1_info7 {
+	u32 dw_len : 32;
+};
+
+struct mtk_lro_alt_v1_info8 {
+	u32 dip_id : 2;
+	u32 ipv6 : 1;
+	u32 ipv4 : 1;
+	u32 resv : 27;
+	u32 valid : 1;
+};
+
+struct mtk_lro_alt_v1 {
+	struct mtk_lro_alt_v1_info0 alt_info0;
+	struct mtk_lro_alt_v1_info1 alt_info1;
+	struct mtk_lro_alt_v1_info2 alt_info2;
+	struct mtk_lro_alt_v1_info3 alt_info3;
+	struct mtk_lro_alt_v1_info4 alt_info4;
+	struct mtk_lro_alt_v1_info5 alt_info5;
+	struct mtk_lro_alt_v1_info6 alt_info6;
+	struct mtk_lro_alt_v1_info7 alt_info7;
+	struct mtk_lro_alt_v1_info8 alt_info8;
+};
+
+struct mtk_lro_alt_v2_info0 {
+	u32 v2_id_h:3;
+	u32 v1_id:12;
+	u32 v0_id:12;
+	u32 v3_valid:1;
+	u32 v2_valid:1;
+	u32 v1_valid:1;
+	u32 v0_valid:1;
+	u32 valid:1;
+};
+
+struct mtk_lro_alt_v2_info1 {
+	u32 sip3_h:9;
+	u32 v6_valid:1;
+	u32 v4_valid:1;
+	u32 v3_id:12;
+	u32 v2_id_l:9;
+};
+
+struct mtk_lro_alt_v2_info2 {
+	u32 sip2_h:9;
+	u32 sip3_l:23;
+};
+struct mtk_lro_alt_v2_info3 {
+	u32 sip1_h:9;
+	u32 sip2_l:23;
+};
+struct mtk_lro_alt_v2_info4 {
+	u32 sip0_h:9;
+	u32 sip1_l:23;
+};
+struct mtk_lro_alt_v2_info5 {
+	u32 dip3_h:9;
+	u32 sip0_l:23;
+};
+struct mtk_lro_alt_v2_info6 {
+	u32 dip2_h:9;
+	u32 dip3_l:23;
+};
+struct mtk_lro_alt_v2_info7 {
+	u32 dip1_h:9;
+	u32 dip2_l:23;
+};
+struct mtk_lro_alt_v2_info8 {
+	u32 dip0_h:9;
+	u32 dip1_l:23;
+};
+struct mtk_lro_alt_v2_info9 {
+	u32 sp_h:9;
+	u32 dip0_l:23;
+};
+struct mtk_lro_alt_v2_info10 {
+	u32 resv:9;
+	u32 dp:16;
+	u32 sp_l:7;
+};
+
+struct mtk_lro_alt_v2 {
+	struct mtk_lro_alt_v2_info0 alt_info0;
+	struct mtk_lro_alt_v2_info1 alt_info1;
+	struct mtk_lro_alt_v2_info2 alt_info2;
+	struct mtk_lro_alt_v2_info3 alt_info3;
+	struct mtk_lro_alt_v2_info4 alt_info4;
+	struct mtk_lro_alt_v2_info5 alt_info5;
+	struct mtk_lro_alt_v2_info6 alt_info6;
+	struct mtk_lro_alt_v2_info7 alt_info7;
+	struct mtk_lro_alt_v2_info8 alt_info8;
+	struct mtk_lro_alt_v2_info9 alt_info9;
+	struct mtk_lro_alt_v2_info10 alt_info10;
+};
+
 struct mtk_esw_reg {
 	unsigned int off;
 	unsigned int val;
@@ -82,5 +272,7 @@
 int mtketh_debugfs_init(struct mtk_eth *eth);
 void mtketh_debugfs_exit(struct mtk_eth *eth);
 int mtk_do_priv_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
+void hw_lro_stats_update(u32 ring_no, struct mtk_rx_dma *rxd);
+void hw_lro_flush_stats_update(u32 ring_no, struct mtk_rx_dma *rxd);
 
 #endif /* MTK_ETH_DBG_H */
diff --git a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 236f18d..2dbf968 100755
--- a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -1316,6 +1316,9 @@
 		return &eth->rx_ring[0];
 
 	for (i = 0; i < MTK_MAX_RX_RING_NUM; i++) {
+		if (!IS_NORMAL_RING(i) && !IS_HW_LRO_RING(i))
+			continue;
+
 		ring = &eth->rx_ring[i];
 		idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
 		if (ring->dma[idx].rxd2 & RX_DMA_DONE) {
@@ -1482,6 +1485,11 @@
 			     __func__, skb_hnat_entry(skb), skb_hnat_sport(skb),
 			     skb_hnat_reason(skb), skb_hnat_alg(skb));
 #endif
+		if (mtk_hwlro_stats_ebl &&
+		    IS_HW_LRO_RING(ring->ring_no) && eth->hwlro) {
+			hw_lro_stats_update(ring->ring_no, &trxd);
+			hw_lro_flush_stats_update(ring->ring_no, &trxd);
+		}
 
 		skb_record_rx_queue(skb, 0);
 		napi_gro_receive(napi, skb);
@@ -1906,6 +1914,7 @@
 	ring->crx_idx_reg = (rx_flag == MTK_RX_FLAGS_QDMA) ?
 			     MTK_QRX_CRX_IDX_CFG(ring_no) :
 			     MTK_PRX_CRX_IDX_CFG(ring_no);
+	ring->ring_no = ring_no;
 	/* make sure that all changes to the dma ring are flushed before we
 	 * continue
 	 */
@@ -1961,6 +1970,7 @@
 static int mtk_hwlro_rx_init(struct mtk_eth *eth)
 {
 	int i;
+	u32 val;
 	u32 ring_ctrl_dw1 = 0, ring_ctrl_dw2 = 0, ring_ctrl_dw3 = 0;
 	u32 lro_ctrl_dw0 = 0, lro_ctrl_dw3 = 0;
 
@@ -1981,7 +1991,7 @@
 	ring_ctrl_dw2 |= MTK_RING_MAX_AGG_CNT_L;
 	ring_ctrl_dw3 |= MTK_RING_MAX_AGG_CNT_H;
 
-	for (i = 1; i < MTK_MAX_RX_RING_NUM; i++) {
+	for (i = 1; i <= MTK_HW_LRO_RING_NUM; i++) {
 		mtk_w32(eth, ring_ctrl_dw1, MTK_LRO_CTRL_DW1_CFG(i));
 		mtk_w32(eth, ring_ctrl_dw2, MTK_LRO_CTRL_DW2_CFG(i));
 		mtk_w32(eth, ring_ctrl_dw3, MTK_LRO_CTRL_DW3_CFG(i));
@@ -1997,24 +2007,38 @@
 	mtk_w32(eth, MTK_HW_LRO_BW_THRE, MTK_PDMA_LRO_CTRL_DW2);
 
 	/* auto-learn score delta setting */
-	mtk_w32(eth, MTK_HW_LRO_REPLACE_DELTA, MTK_PDMA_LRO_ALT_SCORE_DELTA);
+	mtk_w32(eth, MTK_HW_LRO_REPLACE_DELTA, MTK_LRO_ALT_SCORE_DELTA);
 
 	/* set refresh timer for altering flows to 1 sec. (unit: 20us) */
 	mtk_w32(eth, (MTK_HW_LRO_TIMER_UNIT << 16) | MTK_HW_LRO_REFRESH_TIME,
 		MTK_PDMA_LRO_ALT_REFRESH_TIMER);
 
-	/* set HW LRO mode & the max aggregation count for rx packets */
-	lro_ctrl_dw3 |= MTK_ADMA_MODE | (MTK_HW_LRO_MAX_AGG_CNT & 0xff);
-
 	/* the minimal remaining room of SDL0 in RXD for lro aggregation */
 	lro_ctrl_dw3 |= MTK_LRO_MIN_RXD_SDL;
 
+	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+		val = mtk_r32(eth, MTK_PDMA_RX_CFG);
+		mtk_w32(eth, val | (MTK_PDMA_LRO_SDL << MTK_RX_CFG_SDL_OFFSET),
+			MTK_PDMA_RX_CFG);
+
+		lro_ctrl_dw0 |= MTK_PDMA_LRO_SDL << MTK_CTRL_DW0_SDL_OFFSET;
+	} else {
+		/* set HW LRO mode & the max aggregation count for rx packets */
+		lro_ctrl_dw3 |= MTK_ADMA_MODE | (MTK_HW_LRO_MAX_AGG_CNT & 0xff);
+	}
+
 	/* enable HW LRO */
 	lro_ctrl_dw0 |= MTK_LRO_EN;
 
+	/* enable cpu reason black list */
+	lro_ctrl_dw0 |= MTK_LRO_CRSN_BNW;
+
 	mtk_w32(eth, lro_ctrl_dw3, MTK_PDMA_LRO_CTRL_DW3);
 	mtk_w32(eth, lro_ctrl_dw0, MTK_PDMA_LRO_CTRL_DW0);
 
+	/* no use PPE cpu reason */
+	mtk_w32(eth, 0xffffffff, MTK_PDMA_LRO_CTRL_DW1);
+
 	return 0;
 }
 
@@ -2024,12 +2048,12 @@
 	u32 val;
 
 	/* relinquish lro rings, flush aggregated packets */
-	mtk_w32(eth, MTK_LRO_RING_RELINQUISH_REQ, MTK_PDMA_LRO_CTRL_DW0);
+	mtk_w32(eth, MTK_LRO_RING_RELINGUISH_REQ, MTK_PDMA_LRO_CTRL_DW0);
 
 	/* wait for relinquishments done */
 	for (i = 0; i < 10; i++) {
 		val = mtk_r32(eth, MTK_PDMA_LRO_CTRL_DW0);
-		if (val & MTK_LRO_RING_RELINQUISH_DONE) {
+		if (val & MTK_LRO_RING_RELINGUISH_DONE) {
 			msleep(20);
 			continue;
 		}
@@ -2037,7 +2061,7 @@
 	}
 
 	/* invalidate lro rings */
-	for (i = 1; i < MTK_MAX_RX_RING_NUM; i++)
+	for (i = 1; i <= MTK_HW_LRO_RING_NUM; i++)
 		mtk_w32(eth, 0, MTK_LRO_CTRL_DW2_CFG(i));
 
 	/* disable HW LRO */
@@ -2048,6 +2072,9 @@
 {
 	u32 reg_val;
 
+	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+		idx += 1;
+
 	reg_val = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(idx));
 
 	/* invalidate the IP setting */
@@ -2063,6 +2090,9 @@
 {
 	u32 reg_val;
 
+	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+		idx += 1;
+
 	reg_val = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(idx));
 
 	/* invalidate the IP setting */
@@ -2289,7 +2319,8 @@
 		return err;
 
 	if (eth->hwlro) {
-		for (i = 1; i < MTK_MAX_RX_RING_NUM; i++) {
+		i = (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) ? 4 : 1;
+		for (; i < MTK_MAX_RX_RING_NUM; i++) {
 			err = mtk_rx_alloc(eth, i, MTK_RX_FLAGS_HWLRO);
 			if (err)
 				return err;
@@ -2332,8 +2363,10 @@
 
 	if (eth->hwlro) {
 		mtk_hwlro_rx_uninit(eth);
-		for (i = 1; i < MTK_MAX_RX_RING_NUM; i++)
-			mtk_rx_clean(eth, &eth->rx_ring[i],0);
+
+		i = (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) ? 4 : 1;
+		for (; i < MTK_MAX_RX_RING_NUM; i++)
+			mtk_rx_clean(eth, &eth->rx_ring[i], 0);
 	}
 
 	kfree(eth->scratch_head);
@@ -2407,7 +2440,7 @@
 static int mtk_start_dma(struct mtk_eth *eth)
 {
 	u32 rx_2b_offset = (NET_IP_ALIGN == 2) ? MTK_RX_2B_OFFSET : 0;
-	int err;
+	int val, err;
 
 	err = mtk_dma_init(eth);
 	if (err) {
@@ -2442,6 +2475,11 @@
 			MTK_PDMA_GLO_CFG);
 	}
 
+	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2) && eth->hwlro) {
+		val = mtk_r32(eth, MTK_PDMA_GLO_CFG);
+		mtk_w32(eth, val | MTK_RX_DMA_LRO_EN, MTK_PDMA_GLO_CFG);
+	}
+
 	return 0;
 }
 
@@ -2614,7 +2652,7 @@
 
 static int mtk_hw_init(struct mtk_eth *eth)
 {
-	int i, val, ret;
+	int i, ret;
 
 	if (test_and_set_bit(MTK_HW_INIT, &eth->state))
 		return 0;
diff --git a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
index 2bac3e6..d02b248 100755
--- a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
@@ -43,7 +43,6 @@
 #define MTK_HW_FEATURES_MT7628	(NETIF_F_SG | NETIF_F_RXCSUM)
 #define NEXT_DESP_IDX(X, Y)	(((X) + 1) & ((Y) - 1))
 
-#define MTK_MAX_RX_RING_NUM	4
 #define MTK_HW_LRO_DMA_SIZE	8
 
 #define	MTK_MAX_LRO_RX_LENGTH		(4096 * 3)
@@ -72,6 +71,11 @@
 /* Frame Engine Interrupt Grouping Register */
 #define MTK_FE_INT_GRP		0x20
 
+/* Frame Engine LRO auto-learn table info */
+#define MTK_FE_ALT_CF8		0x300
+#define MTK_FE_ALT_SGL_CFC	0x304
+#define MTK_FE_ALT_SEQ_CFC	0x308
+
 /* CDMP Ingress Control Register */
 #define MTK_CDMQ_IG_CTRL	0x1400
 #define MTK_CDMQ_STAG_EN	BIT(0)
@@ -132,12 +136,41 @@
 #define MTK_PRX_CRX_IDX_CFG(x)	(MTK_PRX_CRX_IDX0 + (x * 0x10))
 
 /* PDMA HW LRO Control Registers */
-#define MTK_PDMA_LRO_CTRL_DW0	(PDMA_BASE + 0x180)
-#define MTK_LRO_EN			BIT(0)
+#define BITS(m, n)			(~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n)))
+#if defined(CONFIG_MEDIATEK_NETSYS_V2)
+#define MTK_MAX_RX_RING_NUM		(8)
+#define MTK_HW_LRO_RING_NUM		(4)
+#define IS_HW_LRO_RING(ring_no)		(((ring_no) > 3) && ((ring_no) < 8))
+#define MTK_PDMA_LRO_CTRL_DW0		(PDMA_BASE + 0x408)
+#define MTK_LRO_ALT_SCORE_DELTA		(PDMA_BASE + 0x41c)
+#define MTK_LRO_RX_RING0_CTRL_DW1	(PDMA_BASE + 0x438)
+#define MTK_LRO_RX_RING0_CTRL_DW2	(PDMA_BASE + 0x43c)
+#define MTK_LRO_RX_RING0_CTRL_DW3	(PDMA_BASE + 0x440)
+#define MTK_L3_CKS_UPD_EN		BIT(19)
+#define MTK_LRO_CRSN_BNW		BIT(22)
+#define MTK_LRO_RING_RELINGUISH_REQ	(0xf << 24)
+#define MTK_LRO_RING_RELINGUISH_DONE	(0xf << 28)
+#else
+#define MTK_MAX_RX_RING_NUM		(4)
+#define MTK_HW_LRO_RING_NUM		(3)
+#define IS_HW_LRO_RING(ring_no)		(((ring_no) > 0) && ((ring_no) < 4))
+#define MTK_PDMA_LRO_CTRL_DW0		(PDMA_BASE + 0x180)
+#define MTK_LRO_ALT_SCORE_DELTA		(PDMA_BASE + 0x24c)
+#define MTK_LRO_RX_RING0_CTRL_DW1	(PDMA_BASE + 0x328)
+#define MTK_LRO_RX_RING0_CTRL_DW2	(PDMA_BASE + 0x32c)
+#define MTK_LRO_RX_RING0_CTRL_DW3	(PDMA_BASE + 0x330)
+#define MTK_LRO_CRSN_BNW		BIT(6)
 #define MTK_L3_CKS_UPD_EN		BIT(7)
+#define MTK_LRO_RING_RELINGUISH_REQ	(0x7 << 26)
+#define MTK_LRO_RING_RELINGUISH_DONE	(0x7 << 29)
+#endif
+
+#define IS_NORMAL_RING(ring_no)		((ring_no) == 0)
+#define MTK_LRO_EN			BIT(0)
 #define MTK_LRO_ALT_PKT_CNT_MODE	BIT(21)
-#define MTK_LRO_RING_RELINQUISH_REQ	(0x7 << 26)
-#define MTK_LRO_RING_RELINQUISH_DONE	(0x7 << 29)
+#define MTK_LRO_L4_CTRL_PSH_EN		BIT(23)
+#define MTK_CTRL_DW0_SDL_OFFSET		(3)
+#define MTK_CTRL_DW0_SDL_MASK		BITS(3, 18)
 
 #define MTK_PDMA_LRO_CTRL_DW1	(MTK_PDMA_LRO_CTRL_DW0 + 0x04)
 #define MTK_PDMA_LRO_CTRL_DW2	(MTK_PDMA_LRO_CTRL_DW0 + 0x08)
@@ -147,9 +180,15 @@
 
 /* PDMA Global Configuration Register */
 #define MTK_PDMA_GLO_CFG	(PDMA_BASE + 0x204)
+#define MTK_RX_DMA_LRO_EN	BIT(8)
 #define MTK_MULTI_EN		BIT(10)
 #define MTK_PDMA_SIZE_8DWORDS	(1 << 4)
 
+/* PDMA Global Configuration Register */
+#define MTK_PDMA_RX_CFG		(PDMA_BASE + 0x210)
+#define MTK_PDMA_LRO_SDL	(0x3000)
+#define MTK_RX_CFG_SDL_OFFSET	(16)
+
 /* PDMA Reset Index Register */
 #define MTK_PDMA_RST_IDX	(PDMA_BASE + 0x208)
 #define MTK_PST_DRX_IDX0	BIT(16)
@@ -171,22 +210,27 @@
 /* PDMA Interrupt Mask Register */
 #define MTK_PDMA_INT_MASK	(PDMA_BASE + 0x228)
 
-/* PDMA HW LRO Alter Flow Delta Register */
-#define MTK_PDMA_LRO_ALT_SCORE_DELTA	(PDMA_BASE + 0x24c)
-
 /* PDMA Interrupt grouping registers */
 #define MTK_PDMA_INT_GRP1	(PDMA_BASE + 0x250)
 #define MTK_PDMA_INT_GRP2	(PDMA_BASE + 0x254)
 
 /* PDMA HW LRO IP Setting Registers */
+#if defined(CONFIG_MEDIATEK_NETSYS_V2)
+#define MTK_LRO_RX_RING0_DIP_DW0	(PDMA_BASE + 0x414)
+#else
 #define MTK_LRO_RX_RING0_DIP_DW0	(PDMA_BASE + 0x304)
+#endif
 #define MTK_LRO_DIP_DW0_CFG(x)		(MTK_LRO_RX_RING0_DIP_DW0 + (x * 0x40))
 #define MTK_RING_MYIP_VLD		BIT(9)
 
+/* PDMA HW LRO ALT Debug Registers */
+#define MTK_LRO_ALT_DBG			(PDMA_BASE + 0x440)
+#define MTK_LRO_ALT_INDEX_OFFSET	(8)
+
+/* PDMA HW LRO ALT Data Registers */
+#define MTK_LRO_ALT_DBG_DATA		(PDMA_BASE + 0x444)
+
 /* PDMA HW LRO Ring Control Registers */
-#define MTK_LRO_RX_RING0_CTRL_DW1	(PDMA_BASE + 0x328)
-#define MTK_LRO_RX_RING0_CTRL_DW2	(PDMA_BASE + 0x32c)
-#define MTK_LRO_RX_RING0_CTRL_DW3	(PDMA_BASE + 0x330)
 #define MTK_LRO_CTRL_DW1_CFG(x)		(MTK_LRO_RX_RING0_CTRL_DW1 + (x * 0x40))
 #define MTK_LRO_CTRL_DW2_CFG(x)		(MTK_LRO_RX_RING0_CTRL_DW2 + (x * 0x40))
 #define MTK_LRO_CTRL_DW3_CFG(x)		(MTK_LRO_RX_RING0_CTRL_DW3 + (x * 0x40))
@@ -198,6 +242,35 @@
 #define MTK_RING_MAX_AGG_CNT_L		((MTK_HW_LRO_MAX_AGG_CNT & 0x3f) << 26)
 #define MTK_RING_MAX_AGG_CNT_H		((MTK_HW_LRO_MAX_AGG_CNT >> 6) & 0x3)
 
+/* LRO_RX_RING_CTRL_DW masks */
+#define MTK_LRO_RING_AGG_TIME_MASK	BITS(10, 25)
+#define MTK_LRO_RING_AGG_CNT_L_MASK	BITS(26, 31)
+#define MTK_LRO_RING_AGG_CNT_H_MASK	BITS(0, 1)
+#define MTK_LRO_RING_AGE_TIME_L_MASK	BITS(22, 31)
+#define MTK_LRO_RING_AGE_TIME_H_MASK	BITS(0, 5)
+
+/* LRO_RX_RING_CTRL_DW0 offsets */
+#define MTK_RX_IPV6_FORCE_OFFSET	(0)
+#define MTK_RX_IPV4_FORCE_OFFSET	(1)
+
+/* LRO_RX_RING_CTRL_DW1 offsets  */
+#define MTK_LRO_RING_AGE_TIME_L_OFFSET	(22)
+
+/* LRO_RX_RING_CTRL_DW2 offsets  */
+#define MTK_LRO_RING_AGE_TIME_H_OFFSET	(0)
+#define MTK_RX_MODE_OFFSET		(6)
+#define MTK_RX_PORT_VALID_OFFSET	(8)
+#define MTK_RX_MYIP_VALID_OFFSET	(9)
+#define MTK_LRO_RING_AGG_TIME_OFFSET	(10)
+#define MTK_LRO_RING_AGG_CNT_L_OFFSET	(26)
+
+/* LRO_RX_RING_CTRL_DW3 offsets  */
+#define MTK_LRO_RING_AGG_CNT_H_OFFSET	(0)
+
+/* LRO_RX_RING_STP_DTP_DW offsets */
+#define MTK_RX_TCP_DEST_PORT_OFFSET	(0)
+#define MTK_RX_TCP_SRC_PORT_OFFSET	(16)
+
 /* QDMA TX Queue Configuration Registers */
 #define MTK_QTX_CFG(x)		(QDMA_BASE + (x * 0x10))
 #define QDMA_RES_THRES		4
@@ -379,6 +452,8 @@
 #define RX_DMA_LSO		BIT(30)
 #define RX_DMA_PLEN0(_x)	(((_x) & MTK_RX_DMA_BUF_LEN) << MTK_RX_DMA_BUF_SHIFT)
 #define RX_DMA_GET_PLEN0(_x)	(((_x) >> MTK_RX_DMA_BUF_SHIFT) & MTK_RX_DMA_BUF_LEN)
+#define RX_DMA_GET_AGG_CNT(_x)	(((_x) >> 2) & 0xff)
+#define RX_DMA_GET_REV(_x)	(((_x) >> 10) & 0x1f)
 #define RX_DMA_VTAG		BIT(15)
 
 /* QDMA descriptor rxd3 */
@@ -402,6 +477,10 @@
 #define RX_DMA_TCI_V2(_x)	(((_x) >> 1) & (VLAN_PRIO_MASK | VLAN_VID_MASK))
 #define RX_DMA_VPID_V2(x3, x4)	((((x3) & 1) << 15) | (((x4) >> 17) & 0x7fff))
 
+/* PDMA V2 descriptor rxd6 */
+#define RX_DMA_GET_FLUSH_RSN_V2(_x)	((_x) & 0x7)
+#define RX_DMA_GET_AGG_CNT_V2(_x)	(((_x) >> 16) & 0xff)
+
 /* PHY Indirect Access Control registers */
 #define MTK_PHY_IAC		0x10004
 #define PHY_IAC_ACCESS		BIT(31)
@@ -778,6 +857,7 @@
  * @frag_size:		How big can each fragment be
  * @buf_size:		The size of each packet buffer
  * @calc_idx:		The current head of ring
+ * @ring_no:		The index of ring
  */
 struct mtk_rx_ring {
 	struct mtk_rx_dma *dma;
@@ -789,6 +869,7 @@
 	bool calc_idx_update;
 	u16 calc_idx;
 	u32 crx_idx_reg;
+	u32 ring_no;
 };
 
 enum mkt_eth_capabilities {
@@ -1057,6 +1138,7 @@
 
 /* the struct describing the SoC. these are declared in the soc_xyz.c files */
 extern const struct of_device_id of_mtk_match[];
+extern u32 mtk_hwlro_stats_ebl;
 
 /* read the hardware status register */
 void mtk_stats_update_mac(struct mtk_mac *mac);