[][Add initial mtk feed for OpenWRT v21.02]

[Description]
Add initial mtk feed for OpenWRT v21.02

[Release-log]
N/A

Change-Id: I8051c6ba87f1ccf26c02fdd88a17d66f63c0b101
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/4495320
diff --git a/target/linux/mediatek/files-5.4/drivers/net/ethernet/raeth/raether_pdma.c b/target/linux/mediatek/files-5.4/drivers/net/ethernet/raeth/raether_pdma.c
new file mode 100644
index 0000000..344f3d5
--- /dev/null
+++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/raeth/raether_pdma.c
@@ -0,0 +1,770 @@
+/* Copyright  2016 MediaTek Inc.
+ * Author: Nelson Chang <nelson.chang@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include "raether.h"
+
+int fe_pdma_wait_dma_idle(void)
+{
+	unsigned int reg_val;
+	unsigned int loop_cnt = 0;
+
+	while (1) {
+		if (loop_cnt++ > 1000)
+			break;
+		reg_val = sys_reg_read(PDMA_GLO_CFG);
+		if ((reg_val & RX_DMA_BUSY)) {
+			pr_warn("\n  RX_DMA_BUSY !!! ");
+			continue;
+		}
+		if ((reg_val & TX_DMA_BUSY)) {
+			pr_warn("\n  TX_DMA_BUSY !!! ");
+			continue;
+		}
+		return 0;
+	}
+
+	return -1;
+}
+
+int fe_pdma_rx_dma_init(struct net_device *dev)
+{
+	int i;
+	unsigned int skb_size;
+	struct END_DEVICE *ei_local = netdev_priv(dev);
+	dma_addr_t dma_addr;
+
+	skb_size = SKB_DATA_ALIGN(MAX_RX_LENGTH + NET_IP_ALIGN + NET_SKB_PAD) +
+		   SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+
+	/* Initial RX Ring 0 */
+	ei_local->rx_ring[0] = dma_alloc_coherent(dev->dev.parent,
+						num_rx_desc *
+						sizeof(struct PDMA_rxdesc),
+						&ei_local->phy_rx_ring[0],
+						GFP_ATOMIC | __GFP_ZERO);
+	pr_debug("\nphy_rx_ring[0] = 0x%08x, rx_ring[0] = 0x%p\n",
+		 (unsigned int)ei_local->phy_rx_ring[0],
+		 (void *)ei_local->rx_ring[0]);
+
+	for (i = 0; i < num_rx_desc; i++) {
+		ei_local->netrx_skb_data[0][i] =
+			raeth_alloc_skb_data(skb_size, GFP_KERNEL);
+		if (!ei_local->netrx_skb_data[0][i]) {
+			pr_err("rx skbuff buffer allocation failed!");
+			goto no_rx_mem;
+		}
+
+		memset(&ei_local->rx_ring[0][i], 0, sizeof(struct PDMA_rxdesc));
+		ei_local->rx_ring[0][i].rxd_info2.DDONE_bit = 0;
+		ei_local->rx_ring[0][i].rxd_info2.LS0 = 0;
+		ei_local->rx_ring[0][i].rxd_info2.PLEN0 = MAX_RX_LENGTH;
+		dma_addr = dma_map_single(dev->dev.parent,
+					  ei_local->netrx_skb_data[0][i] +
+					  NET_SKB_PAD,
+					  MAX_RX_LENGTH,
+					  DMA_FROM_DEVICE);
+		ei_local->rx_ring[0][i].rxd_info1.PDP0 = dma_addr;
+		if (unlikely
+		    (dma_mapping_error
+		     (dev->dev.parent,
+		      ei_local->rx_ring[0][i].rxd_info1.PDP0))) {
+			pr_err("[%s]dma_map_single() failed...\n", __func__);
+			goto no_rx_mem;
+		}
+	}
+
+	/* Tell the adapter where the RX rings are located. */
+	sys_reg_write(RX_BASE_PTR0, phys_to_bus((u32)ei_local->phy_rx_ring[0]));
+	sys_reg_write(RX_MAX_CNT0, cpu_to_le32((u32)num_rx_desc));
+	sys_reg_write(RX_CALC_IDX0, cpu_to_le32((u32)(num_rx_desc - 1)));
+
+	sys_reg_write(PDMA_RST_CFG, PST_DRX_IDX0);
+
+	return 0;
+
+no_rx_mem:
+	return -ENOMEM;
+}
+
+int fe_pdma_tx_dma_init(struct net_device *dev)
+{
+	int i;
+	struct END_DEVICE *ei_local = netdev_priv(dev);
+
+	for (i = 0; i < num_tx_desc; i++)
+		ei_local->skb_free[i] = 0;
+
+	ei_local->tx_ring_full = 0;
+	ei_local->free_idx = 0;
+	ei_local->tx_ring0 =
+	    dma_alloc_coherent(dev->dev.parent,
+			       num_tx_desc * sizeof(struct PDMA_txdesc),
+			       &ei_local->phy_tx_ring0,
+			       GFP_ATOMIC | __GFP_ZERO);
+	pr_debug("\nphy_tx_ring = 0x%08x, tx_ring = 0x%p\n",
+		 (unsigned int)ei_local->phy_tx_ring0,
+		 (void *)ei_local->tx_ring0);
+
+	for (i = 0; i < num_tx_desc; i++) {
+		memset(&ei_local->tx_ring0[i], 0, sizeof(struct PDMA_txdesc));
+		ei_local->tx_ring0[i].txd_info2.LS0_bit = 1;
+		ei_local->tx_ring0[i].txd_info2.DDONE_bit = 1;
+	}
+
+	/* Tell the adapter where the TX rings are located. */
+	sys_reg_write(TX_BASE_PTR0, phys_to_bus((u32)ei_local->phy_tx_ring0));
+	sys_reg_write(TX_MAX_CNT0, cpu_to_le32((u32)num_tx_desc));
+	sys_reg_write(TX_CTX_IDX0, 0);
+#ifdef CONFIG_RAETH_RW_PDMAPTR_FROM_VAR
+	ei_local->tx_cpu_owner_idx0 = 0;
+#endif
+	sys_reg_write(PDMA_RST_CFG, PST_DTX_IDX0);
+
+	return 0;
+}
+
+void fe_pdma_rx_dma_deinit(struct net_device *dev)
+{
+	struct END_DEVICE *ei_local = netdev_priv(dev);
+	int i;
+
+	/* free RX Ring */
+	dma_free_coherent(dev->dev.parent,
+			  num_rx_desc * sizeof(struct PDMA_rxdesc),
+			  ei_local->rx_ring[0], ei_local->phy_rx_ring[0]);
+
+	/* free RX data */
+	for (i = 0; i < num_rx_desc; i++) {
+		raeth_free_skb_data(ei_local->netrx_skb_data[0][i]);
+		ei_local->netrx_skb_data[0][i] = NULL;
+	}
+}
+
+void fe_pdma_tx_dma_deinit(struct net_device *dev)
+{
+	struct END_DEVICE *ei_local = netdev_priv(dev);
+	int i;
+
+	/* free TX Ring */
+	if (ei_local->tx_ring0)
+		dma_free_coherent(dev->dev.parent,
+				  num_tx_desc *
+				  sizeof(struct PDMA_txdesc),
+				  ei_local->tx_ring0,
+				  ei_local->phy_tx_ring0);
+
+	/* free TX data */
+	for (i = 0; i < num_tx_desc; i++) {
+		if ((ei_local->skb_free[i] != 0) &&
+		    (ei_local->skb_free[i] != (struct sk_buff *)0xFFFFFFFF))
+			dev_kfree_skb_any(ei_local->skb_free[i]);
+	}
+}
+
+void set_fe_pdma_glo_cfg(void)
+{
+	unsigned int dma_glo_cfg = 0;
+
+	dma_glo_cfg =
+	    (TX_WB_DDONE | RX_DMA_EN | TX_DMA_EN | PDMA_BT_SIZE_16DWORDS |
+	     MULTI_EN | ADMA_RX_BT_SIZE_32DWORDS);
+//	dma_glo_cfg |= (RX_2B_OFFSET);
+
+	sys_reg_write(PDMA_GLO_CFG, dma_glo_cfg);
+}
+
+/* @brief cal txd number for a page
+ *
+ *  @parm size
+ *
+ *  @return frag_txd_num
+ */
+static inline unsigned int pdma_cal_frag_txd_num(unsigned int size)
+{
+	unsigned int frag_txd_num = 0;
+
+	if (size == 0)
+		return 0;
+	while (size > 0) {
+		if (size > MAX_PTXD_LEN) {
+			frag_txd_num++;
+			size -= MAX_PTXD_LEN;
+		} else {
+			frag_txd_num++;
+			size = 0;
+		}
+	}
+	return frag_txd_num;
+}
+
+int fe_fill_tx_desc(struct net_device *dev,
+		    unsigned long *tx_cpu_owner_idx,
+		    struct sk_buff *skb,
+		    int gmac_no)
+{
+	struct END_DEVICE *ei_local = netdev_priv(dev);
+	struct PDMA_txdesc *tx_ring = &ei_local->tx_ring0[*tx_cpu_owner_idx];
+	struct PDMA_TXD_INFO2_T txd_info2_tmp;
+	struct PDMA_TXD_INFO4_T txd_info4_tmp;
+
+	tx_ring->txd_info1.SDP0 = virt_to_phys(skb->data);
+	txd_info2_tmp.SDL0 = skb->len;
+	txd_info4_tmp.FPORT = gmac_no;
+	txd_info4_tmp.TSO = 0;
+
+	if (ei_local->features & FE_CSUM_OFFLOAD) {
+		if (skb->ip_summed == CHECKSUM_PARTIAL)
+			txd_info4_tmp.TUI_CO = 7;
+		else
+			txd_info4_tmp.TUI_CO = 0;
+	}
+
+	if (ei_local->features & FE_HW_VLAN_TX) {
+		if (skb_vlan_tag_present(skb))
+			txd_info4_tmp.VLAN_TAG =
+				0x10000 | skb_vlan_tag_get(skb);
+		else
+			txd_info4_tmp.VLAN_TAG = 0;
+	}
+#if defined(CONFIG_RA_HW_NAT) || defined(CONFIG_RA_HW_NAT_MODULE)
+	if (IS_MAGIC_TAG_PROTECT_VALID_HEAD(skb)) {
+		if (FOE_MAGIC_TAG_HEAD(skb) == FOE_MAGIC_PPE) {
+			if (ppe_hook_rx_eth) {
+				/* PPE */
+				txd_info4_tmp.FPORT = 4;
+				FOE_MAGIC_TAG(skb) = 0;
+			}
+		}
+	} else if (IS_MAGIC_TAG_PROTECT_VALID_TAIL(skb)) {
+		if (FOE_MAGIC_TAG_TAIL(skb) == FOE_MAGIC_PPE) {
+			if (ppe_hook_rx_eth) {
+				/* PPE */
+				txd_info4_tmp.FPORT = 4;
+				FOE_MAGIC_TAG(skb) = 0;
+			}
+		}
+	}
+#endif
+
+	txd_info2_tmp.LS0_bit = 1;
+	txd_info2_tmp.DDONE_bit = 0;
+
+	tx_ring->txd_info4 = txd_info4_tmp;
+	tx_ring->txd_info2 = txd_info2_tmp;
+
+	return 0;
+}
+
+static int fe_fill_tx_tso_data(struct END_DEVICE *ei_local,
+			       unsigned int frag_offset,
+			       unsigned int frag_size,
+			       unsigned long *tx_cpu_owner_idx,
+			       unsigned int nr_frags,
+			       int gmac_no)
+{
+	struct PSEUDO_ADAPTER *p_ad;
+	unsigned int size;
+	unsigned int frag_txd_num;
+	struct PDMA_txdesc *tx_ring;
+
+	frag_txd_num = pdma_cal_frag_txd_num(frag_size);
+	tx_ring = &ei_local->tx_ring0[*tx_cpu_owner_idx];
+
+	while (frag_txd_num > 0) {
+		if (frag_size < MAX_PTXD_LEN)
+			size = frag_size;
+		else
+			size = MAX_PTXD_LEN;
+
+		if (ei_local->skb_txd_num % 2 == 0) {
+			*tx_cpu_owner_idx =
+			    (*tx_cpu_owner_idx + 1) % num_tx_desc;
+			tx_ring = &ei_local->tx_ring0[*tx_cpu_owner_idx];
+
+			while (tx_ring->txd_info2.DDONE_bit == 0) {
+				if (gmac_no == 2) {
+					p_ad =
+					    netdev_priv(ei_local->pseudo_dev);
+					p_ad->stat.tx_errors++;
+				} else {
+					ei_local->stat.tx_errors++;
+				}
+			}
+			tx_ring->txd_info1.SDP0 = frag_offset;
+			tx_ring->txd_info2.SDL0 = size;
+			if (((nr_frags == 0)) && (frag_txd_num == 1))
+				tx_ring->txd_info2.LS0_bit = 1;
+			else
+				tx_ring->txd_info2.LS0_bit = 0;
+			tx_ring->txd_info2.DDONE_bit = 0;
+			tx_ring->txd_info4.FPORT = gmac_no;
+		} else {
+			tx_ring->txd_info3.SDP1 = frag_offset;
+			tx_ring->txd_info2.SDL1 = size;
+			if (((nr_frags == 0)) && (frag_txd_num == 1))
+				tx_ring->txd_info2.LS1_bit = 1;
+			else
+				tx_ring->txd_info2.LS1_bit = 0;
+		}
+		frag_offset += size;
+		frag_size -= size;
+		frag_txd_num--;
+		ei_local->skb_txd_num++;
+	}
+
+	return 0;
+}
+
+static int fe_fill_tx_tso_frag(struct net_device *netdev,
+			       struct sk_buff *skb,
+			       unsigned long *tx_cpu_owner_idx,
+			       int gmac_no)
+{
+	struct END_DEVICE *ei_local = netdev_priv(netdev);
+	struct PSEUDO_ADAPTER *p_ad;
+	unsigned int size;
+	unsigned int frag_txd_num;
+	skb_frag_t * frag;
+	unsigned int nr_frags;
+	unsigned int frag_offset, frag_size;
+	struct PDMA_txdesc *tx_ring;
+	int i = 0, j = 0, unmap_idx = 0;
+
+	nr_frags = skb_shinfo(skb)->nr_frags;
+	tx_ring = &ei_local->tx_ring0[*tx_cpu_owner_idx];
+
+	for (i = 0; i < nr_frags; i++) {
+		frag = &skb_shinfo(skb)->frags[i];
+		frag_offset = 0;
+		frag_size = skb_frag_size(frag);
+		frag_txd_num = pdma_cal_frag_txd_num(frag_size);
+
+		while (frag_txd_num > 0) {
+			if (frag_size < MAX_PTXD_LEN)
+				size = frag_size;
+			else
+				size = MAX_PTXD_LEN;
+
+			if (ei_local->skb_txd_num % 2 == 0) {
+				*tx_cpu_owner_idx =
+					(*tx_cpu_owner_idx + 1) % num_tx_desc;
+				tx_ring =
+					&ei_local->tx_ring0[*tx_cpu_owner_idx];
+
+				while (tx_ring->txd_info2.DDONE_bit == 0) {
+					if (gmac_no == 2) {
+						p_ad =
+						    netdev_priv
+						    (ei_local->pseudo_dev);
+						p_ad->stat.tx_errors++;
+					} else {
+						ei_local->stat.tx_errors++;
+					}
+				}
+
+				tx_ring->txd_info1.SDP0 = skb_frag_dma_map(netdev->dev.parent, frag, frag_offset, size, DMA_TO_DEVICE);
+
+				if (unlikely
+				    (dma_mapping_error
+				     (netdev->dev.parent,
+				      tx_ring->txd_info1.SDP0))) {
+					pr_err
+					    ("[%s]dma_map_page() failed\n",
+					     __func__);
+					goto err_dma;
+				}
+
+				tx_ring->txd_info2.SDL0 = size;
+
+				if ((frag_txd_num == 1) &&
+				    (i == (nr_frags - 1)))
+					tx_ring->txd_info2.LS0_bit = 1;
+				else
+					tx_ring->txd_info2.LS0_bit = 0;
+				tx_ring->txd_info2.DDONE_bit = 0;
+				tx_ring->txd_info4.FPORT = gmac_no;
+			} else {
+				tx_ring->txd_info3.SDP1 = skb_frag_dma_map(netdev->dev.parent, frag, frag_offset, size, DMA_TO_DEVICE);
+
+				if (unlikely
+				    (dma_mapping_error
+				     (netdev->dev.parent,
+				      tx_ring->txd_info3.SDP1))) {
+					pr_err
+					    ("[%s]dma_map_page() failed\n",
+					     __func__);
+					goto err_dma;
+				}
+				tx_ring->txd_info2.SDL1 = size;
+				if ((frag_txd_num == 1) &&
+				    (i == (nr_frags - 1)))
+					tx_ring->txd_info2.LS1_bit = 1;
+				else
+					tx_ring->txd_info2.LS1_bit = 0;
+			}
+			frag_offset += size;
+			frag_size -= size;
+			frag_txd_num--;
+			ei_local->skb_txd_num++;
+		}
+	}
+
+	return 0;
+
+err_dma:
+	/* unmap dma */
+	j = *tx_cpu_owner_idx;
+	unmap_idx = i;
+	for (i = 0; i < unmap_idx; i++) {
+		frag = &skb_shinfo(skb)->frags[i];
+		frag_size = skb_frag_size(frag);
+		frag_txd_num = pdma_cal_frag_txd_num(frag_size);
+
+		while (frag_txd_num > 0) {
+			if (frag_size < MAX_PTXD_LEN)
+				size = frag_size;
+			else
+				size = MAX_PTXD_LEN;
+			if (ei_local->skb_txd_num % 2 == 0) {
+				j = (j + 1) % num_tx_desc;
+				dma_unmap_page(netdev->dev.parent,
+					       ei_local->tx_ring0[j].
+					       txd_info1.SDP0,
+					       ei_local->tx_ring0[j].
+					       txd_info2.SDL0, DMA_TO_DEVICE);
+				/* reinit txd */
+				ei_local->tx_ring0[j].txd_info2.LS0_bit = 1;
+				ei_local->tx_ring0[j].txd_info2.DDONE_bit = 1;
+			} else {
+				dma_unmap_page(netdev->dev.parent,
+					       ei_local->tx_ring0[j].
+					       txd_info3.SDP1,
+					       ei_local->tx_ring0[j].
+					       txd_info2.SDL1, DMA_TO_DEVICE);
+				/* reinit txd */
+				ei_local->tx_ring0[j].txd_info2.LS1_bit = 1;
+			}
+			frag_size -= size;
+			frag_txd_num--;
+			ei_local->skb_txd_num++;
+		}
+	}
+
+	return -1;
+}
+
+int fe_fill_tx_desc_tso(struct net_device *dev,
+			unsigned long *tx_cpu_owner_idx,
+			struct sk_buff *skb,
+			int gmac_no)
+{
+	struct END_DEVICE *ei_local = netdev_priv(dev);
+	struct iphdr *iph = NULL;
+	struct ipv6hdr *ip6h = NULL;
+	struct tcphdr *th = NULL;
+	unsigned int nr_frags = skb_shinfo(skb)->nr_frags;
+	unsigned int len, offset;
+	int err;
+	struct PDMA_txdesc *tx_ring = &ei_local->tx_ring0[*tx_cpu_owner_idx];
+
+	tx_ring->txd_info4.FPORT = gmac_no;
+	tx_ring->txd_info4.TSO = 0;
+
+	if (skb->ip_summed == CHECKSUM_PARTIAL)
+		tx_ring->txd_info4.TUI_CO = 7;
+	else
+		tx_ring->txd_info4.TUI_CO = 0;
+
+	if (ei_local->features & FE_HW_VLAN_TX) {
+		if (skb_vlan_tag_present(skb))
+			tx_ring->txd_info4.VLAN_TAG =
+				0x10000 | skb_vlan_tag_get(skb);
+		else
+			tx_ring->txd_info4.VLAN_TAG = 0;
+	}
+#if defined(CONFIG_RA_HW_NAT) || defined(CONFIG_RA_HW_NAT_MODULE)
+	if (IS_MAGIC_TAG_PROTECT_VALID_HEAD(skb)) {
+		if (FOE_MAGIC_TAG_HEAD(skb) == FOE_MAGIC_PPE) {
+			if (ppe_hook_rx_eth) {
+				/* PPE */
+				tx_ring->txd_info4.FPORT = 4;
+				FOE_MAGIC_TAG(skb) = 0;
+			}
+		}
+	} else if (IS_MAGIC_TAG_PROTECT_VALID_TAIL(skb)) {
+		if (FOE_MAGIC_TAG_TAIL(skb) == FOE_MAGIC_PPE) {
+			if (ppe_hook_rx_eth) {
+				/* PPE */
+				tx_ring->txd_info4.FPORT = 4;
+				FOE_MAGIC_TAG(skb) = 0;
+			}
+		}
+	}
+#endif
+	ei_local->skb_txd_num = 1;
+
+	/* skb data handle */
+	len = skb->len - skb->data_len;
+	offset = virt_to_phys(skb->data);
+	tx_ring->txd_info1.SDP0 = offset;
+	if (len < MAX_PTXD_LEN) {
+		tx_ring->txd_info2.SDL0 = len;
+		tx_ring->txd_info2.LS0_bit = nr_frags ? 0 : 1;
+		len = 0;
+	} else {
+		tx_ring->txd_info2.SDL0 = MAX_PTXD_LEN;
+		tx_ring->txd_info2.LS0_bit = 0;
+		len -= MAX_PTXD_LEN;
+		offset += MAX_PTXD_LEN;
+	}
+
+	if (len > 0)
+		fe_fill_tx_tso_data(ei_local, offset, len,
+				    tx_cpu_owner_idx, nr_frags, gmac_no);
+
+	/* skb fragments handle */
+	if (nr_frags > 0) {
+		err = fe_fill_tx_tso_frag(dev, skb, tx_cpu_owner_idx, gmac_no);
+		if (unlikely(err))
+			return err;
+	}
+
+	/* fill in MSS info in tcp checksum field */
+	if (skb_shinfo(skb)->gso_segs > 1) {
+		/* TCP over IPv4 */
+		iph = (struct iphdr *)skb_network_header(skb);
+		if ((iph->version == 4) && (iph->protocol == IPPROTO_TCP)) {
+			th = (struct tcphdr *)skb_transport_header(skb);
+			tx_ring->txd_info4.TSO = 1;
+			th->check = htons(skb_shinfo(skb)->gso_size);
+			dma_sync_single_for_device(dev->dev.parent,
+						   virt_to_phys(th),
+						   sizeof(struct tcphdr),
+						   DMA_TO_DEVICE);
+		}
+
+		/* TCP over IPv6 */
+		if (ei_local->features & FE_TSO_V6) {
+			ip6h = (struct ipv6hdr *)skb_network_header(skb);
+			if ((ip6h->nexthdr == NEXTHDR_TCP) &&
+			    (ip6h->version == 6)) {
+				th = (struct tcphdr *)skb_transport_header(skb);
+				tx_ring->txd_info4.TSO = 1;
+				th->check = htons(skb_shinfo(skb)->gso_size);
+				dma_sync_single_for_device(dev->dev.parent,
+							   virt_to_phys(th),
+							   sizeof(struct
+								  tcphdr),
+							   DMA_TO_DEVICE);
+			}
+		}
+	}
+	tx_ring->txd_info2.DDONE_bit = 0;
+
+	return 0;
+}
+
+static inline int rt2880_pdma_eth_send(struct net_device *dev,
+				       struct sk_buff *skb, int gmac_no,
+				       unsigned int num_of_frag)
+{
+	unsigned int length = skb->len;
+	struct END_DEVICE *ei_local = netdev_priv(dev);
+#ifdef CONFIG_RAETH_RW_PDMAPTR_FROM_VAR
+	unsigned long tx_cpu_owner_idx0 = ei_local->tx_cpu_owner_idx0;
+#else
+	unsigned long tx_cpu_owner_idx0 = sys_reg_read(TX_CTX_IDX0);
+#endif
+	struct PSEUDO_ADAPTER *p_ad;
+	int err;
+
+	while (ei_local->tx_ring0[tx_cpu_owner_idx0].txd_info2.DDONE_bit == 0) {
+		if (gmac_no == 2) {
+			if (ei_local->pseudo_dev) {
+				p_ad = netdev_priv(ei_local->pseudo_dev);
+				p_ad->stat.tx_errors++;
+			} else {
+				pr_err
+				    ("pseudo_dev is still not initialize ");
+				pr_err
+				    ("but receive packet from GMAC2\n");
+			}
+		} else {
+			ei_local->stat.tx_errors++;
+		}
+	}
+
+	if (num_of_frag > 1)
+		err = fe_fill_tx_desc_tso(dev, &tx_cpu_owner_idx0,
+					  skb, gmac_no);
+	else
+		err = fe_fill_tx_desc(dev, &tx_cpu_owner_idx0, skb, gmac_no);
+	if (err)
+		return err;
+
+	tx_cpu_owner_idx0 = (tx_cpu_owner_idx0 + 1) % num_tx_desc;
+	while (ei_local->tx_ring0[tx_cpu_owner_idx0].txd_info2.DDONE_bit == 0) {
+		if (gmac_no == 2) {
+			p_ad = netdev_priv(ei_local->pseudo_dev);
+			p_ad->stat.tx_errors++;
+		} else {
+			ei_local->stat.tx_errors++;
+		}
+	}
+#ifdef CONFIG_RAETH_RW_PDMAPTR_FROM_VAR
+	ei_local->tx_cpu_owner_idx0 = tx_cpu_owner_idx0;
+#endif
+	/* make sure that all changes to the dma ring are flushed before we
+	 * continue
+	 */
+	wmb();
+
+	sys_reg_write(TX_CTX_IDX0, cpu_to_le32((u32)tx_cpu_owner_idx0));
+
+	if (gmac_no == 2) {
+		p_ad = netdev_priv(ei_local->pseudo_dev);
+		p_ad->stat.tx_packets++;
+		p_ad->stat.tx_bytes += length;
+	} else {
+		ei_local->stat.tx_packets++;
+		ei_local->stat.tx_bytes += length;
+	}
+
+	return length;
+}
+
+int ei_pdma_start_xmit(struct sk_buff *skb, struct net_device *dev, int gmac_no)
+{
+	struct END_DEVICE *ei_local = netdev_priv(dev);
+	unsigned long tx_cpu_owner_idx;
+	unsigned int tx_cpu_owner_idx_next, tx_cpu_owner_idx_next2;
+	unsigned int num_of_txd, num_of_frag;
+	unsigned int nr_frags = skb_shinfo(skb)->nr_frags, i;
+	skb_frag_t * frag;
+	struct PSEUDO_ADAPTER *p_ad;
+	unsigned int tx_cpu_cal_idx;
+
+#if defined(CONFIG_RA_HW_NAT)  || defined(CONFIG_RA_HW_NAT_MODULE)
+	if (ppe_hook_tx_eth) {
+#if defined(CONFIG_RA_HW_NAT) || defined(CONFIG_RA_HW_NAT_MODULE)
+		if (FOE_MAGIC_TAG(skb) != FOE_MAGIC_PPE)
+#endif
+			if (ppe_hook_tx_eth(skb, gmac_no) != 1) {
+				dev_kfree_skb_any(skb);
+				return 0;
+			}
+	}
+#endif
+
+//	dev->trans_start = jiffies;	/* save the timestamp */
+	netif_trans_update(dev);
+	spin_lock(&ei_local->page_lock);
+	dma_sync_single_for_device(dev->dev.parent, virt_to_phys(skb->data),
+				   skb->len, DMA_TO_DEVICE);
+
+#ifdef CONFIG_RAETH_RW_PDMAPTR_FROM_VAR
+	tx_cpu_owner_idx = ei_local->tx_cpu_owner_idx0;
+#else
+	tx_cpu_owner_idx = sys_reg_read(TX_CTX_IDX0);
+#endif
+
+	if (ei_local->features & FE_TSO) {
+		num_of_txd = pdma_cal_frag_txd_num(skb->len - skb->data_len);
+		if (nr_frags != 0) {
+			for (i = 0; i < nr_frags; i++) {
+				frag = &skb_shinfo(skb)->frags[i];
+				num_of_txd += pdma_cal_frag_txd_num(skb_frag_size(frag));
+
+			}
+		}
+		num_of_frag = num_of_txd;
+		num_of_txd = (num_of_txd + 1) >> 1;
+	} else {
+		num_of_frag = 1;
+		num_of_txd = 1;
+	}
+
+	tx_cpu_owner_idx_next = (tx_cpu_owner_idx + num_of_txd) % num_tx_desc;
+
+	if ((ei_local->skb_free[tx_cpu_owner_idx_next] == 0) &&
+	    (ei_local->skb_free[tx_cpu_owner_idx] == 0)) {
+		if (rt2880_pdma_eth_send(dev, skb, gmac_no, num_of_frag) < 0) {
+			dev_kfree_skb_any(skb);
+			if (gmac_no == 2) {
+				p_ad = netdev_priv(ei_local->pseudo_dev);
+				p_ad->stat.tx_dropped++;
+			} else {
+				ei_local->stat.tx_dropped++;
+			}
+			goto tx_err;
+		}
+
+		tx_cpu_owner_idx_next2 =
+		    (tx_cpu_owner_idx_next + 1) % num_tx_desc;
+
+		if (ei_local->skb_free[tx_cpu_owner_idx_next2] != 0)
+			ei_local->tx_ring_full = 1;
+	} else {
+		if (gmac_no == 2) {
+			p_ad = netdev_priv(ei_local->pseudo_dev);
+			p_ad->stat.tx_dropped++;
+		} else {
+			ei_local->stat.tx_dropped++;
+		}
+
+		dev_kfree_skb_any(skb);
+		spin_unlock(&ei_local->page_lock);
+		return NETDEV_TX_OK;
+	}
+
+	/* SG: use multiple TXD to send the packet (only have one skb) */
+	tx_cpu_cal_idx = (tx_cpu_owner_idx + num_of_txd - 1) % num_tx_desc;
+	ei_local->skb_free[tx_cpu_cal_idx] = skb;
+	while (--num_of_txd)
+		/* MAGIC ID */
+		ei_local->skb_free[(--tx_cpu_cal_idx) % num_tx_desc] =
+			(struct sk_buff *)0xFFFFFFFF;
+
+tx_err:
+	spin_unlock(&ei_local->page_lock);
+	return NETDEV_TX_OK;
+}
+
+int ei_pdma_xmit_housekeeping(struct net_device *netdev, int budget)
+{
+	struct END_DEVICE *ei_local = netdev_priv(netdev);
+	struct PDMA_txdesc *tx_desc;
+	unsigned long skb_free_idx;
+	int tx_processed = 0;
+
+	tx_desc = ei_local->tx_ring0;
+	skb_free_idx = ei_local->free_idx;
+
+	while (budget &&
+	       (ei_local->skb_free[skb_free_idx] != 0) &&
+	       (tx_desc[skb_free_idx].txd_info2.DDONE_bit == 1)) {
+		if (ei_local->skb_free[skb_free_idx] !=
+		    (struct sk_buff *)0xFFFFFFFF)
+			dev_kfree_skb_any(ei_local->skb_free[skb_free_idx]);
+
+		ei_local->skb_free[skb_free_idx] = 0;
+		skb_free_idx = (skb_free_idx + 1) % num_tx_desc;
+		budget--;
+		tx_processed++;
+	}
+
+	ei_local->tx_ring_full = 0;
+	ei_local->free_idx = skb_free_idx;
+
+	return tx_processed;
+}
+