Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[cascardo/linux.git] / drivers / net / ethernet / mediatek / mtk_eth_soc.c
index 3743af8..4cc50c0 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/mfd/syscon.h>
 #include <linux/regmap.h>
 #include <linux/clk.h>
+#include <linux/pm_runtime.h>
 #include <linux/if_vlan.h>
 #include <linux/reset.h>
 #include <linux/tcp.h>
@@ -51,7 +52,7 @@ static const struct mtk_ethtool_stats {
 };
 
 static const char * const mtk_clks_source_name[] = {
-       "ethif", "esw", "gp1", "gp2"
+       "ethif", "esw", "gp1", "gp2", "trgpll"
 };
 
 void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg)
@@ -134,6 +135,33 @@ static int mtk_mdio_read(struct mii_bus *bus, int phy_addr, int phy_reg)
        return _mtk_mdio_read(eth, phy_addr, phy_reg);
 }
 
+static void mtk_gmac0_rgmii_adjust(struct mtk_eth *eth, int speed)
+{
+       u32 val;
+       int ret;
+
+       val = (speed == SPEED_1000) ?
+               INTF_MODE_RGMII_1000 : INTF_MODE_RGMII_10_100;
+       mtk_w32(eth, val, INTF_MODE);
+
+       regmap_update_bits(eth->ethsys, ETHSYS_CLKCFG0,
+                          ETHSYS_TRGMII_CLK_SEL362_5,
+                          ETHSYS_TRGMII_CLK_SEL362_5);
+
+       val = (speed == SPEED_1000) ? 250000000 : 500000000;
+       ret = clk_set_rate(eth->clks[MTK_CLK_TRGPLL], val);
+       if (ret)
+               dev_err(eth->dev, "Failed to set trgmii pll: %d\n", ret);
+
+       val = (speed == SPEED_1000) ?
+               RCK_CTRL_RGMII_1000 : RCK_CTRL_RGMII_10_100;
+       mtk_w32(eth, val, TRGMII_RCK_CTRL);
+
+       val = (speed == SPEED_1000) ?
+               TCK_CTRL_RGMII_1000 : TCK_CTRL_RGMII_10_100;
+       mtk_w32(eth, val, TRGMII_TCK_CTRL);
+}
+
 static void mtk_phy_link_adjust(struct net_device *dev)
 {
        struct mtk_mac *mac = netdev_priv(dev);
@@ -144,7 +172,10 @@ static void mtk_phy_link_adjust(struct net_device *dev)
                  MAC_MCR_RX_EN | MAC_MCR_BACKOFF_EN |
                  MAC_MCR_BACKPR_EN;
 
-       switch (mac->phy_dev->speed) {
+       if (unlikely(test_bit(MTK_RESETTING, &mac->hw->state)))
+               return;
+
+       switch (dev->phydev->speed) {
        case SPEED_1000:
                mcr |= MAC_MCR_SPEED_1000;
                break;
@@ -153,20 +184,23 @@ static void mtk_phy_link_adjust(struct net_device *dev)
                break;
        };
 
-       if (mac->phy_dev->link)
+       if (mac->id == 0 && !mac->trgmii)
+               mtk_gmac0_rgmii_adjust(mac->hw, dev->phydev->speed);
+
+       if (dev->phydev->link)
                mcr |= MAC_MCR_FORCE_LINK;
 
-       if (mac->phy_dev->duplex) {
+       if (dev->phydev->duplex) {
                mcr |= MAC_MCR_FORCE_DPX;
 
-               if (mac->phy_dev->pause)
+               if (dev->phydev->pause)
                        rmt_adv = LPA_PAUSE_CAP;
-               if (mac->phy_dev->asym_pause)
+               if (dev->phydev->asym_pause)
                        rmt_adv |= LPA_PAUSE_ASYM;
 
-               if (mac->phy_dev->advertising & ADVERTISED_Pause)
+               if (dev->phydev->advertising & ADVERTISED_Pause)
                        lcl_adv |= ADVERTISE_PAUSE_CAP;
-               if (mac->phy_dev->advertising & ADVERTISED_Asym_Pause)
+               if (dev->phydev->advertising & ADVERTISED_Asym_Pause)
                        lcl_adv |= ADVERTISE_PAUSE_ASYM;
 
                flowctrl = mii_resolve_flowctrl_fdx(lcl_adv, rmt_adv);
@@ -183,7 +217,7 @@ static void mtk_phy_link_adjust(struct net_device *dev)
 
        mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
 
-       if (mac->phy_dev->link)
+       if (dev->phydev->link)
                netif_carrier_on(dev);
        else
                netif_carrier_off(dev);
@@ -192,17 +226,9 @@ static void mtk_phy_link_adjust(struct net_device *dev)
 static int mtk_phy_connect_node(struct mtk_eth *eth, struct mtk_mac *mac,
                                struct device_node *phy_node)
 {
-       const __be32 *_addr = NULL;
        struct phy_device *phydev;
-       int phy_mode, addr;
-
-       _addr = of_get_property(phy_node, "reg", NULL);
+       int phy_mode;
 
-       if (!_addr || (be32_to_cpu(*_addr) >= 0x20)) {
-               pr_err("%s: invalid phy address\n", phy_node->name);
-               return -EINVAL;
-       }
-       addr = be32_to_cpu(*_addr);
        phy_mode = of_get_phy_mode(phy_node);
        if (phy_mode < 0) {
                dev_err(eth->dev, "incorrect phy-mode %d\n", phy_mode);
@@ -221,17 +247,17 @@ static int mtk_phy_connect_node(struct mtk_eth *eth, struct mtk_mac *mac,
                 mac->id, phydev_name(phydev), phydev->phy_id,
                 phydev->drv->name);
 
-       mac->phy_dev = phydev;
-
        return 0;
 }
 
-static int mtk_phy_connect(struct mtk_mac *mac)
+static int mtk_phy_connect(struct net_device *dev)
 {
-       struct mtk_eth *eth = mac->hw;
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth;
        struct device_node *np;
-       u32 val, ge_mode;
+       u32 val;
 
+       eth = mac->hw;
        np = of_parse_phandle(mac->of_node, "phy-handle", 0);
        if (!np && of_phy_is_fixed_link(mac->of_node))
                if (!of_phy_register_fixed_link(mac->of_node))
@@ -240,22 +266,24 @@ static int mtk_phy_connect(struct mtk_mac *mac)
                return -ENODEV;
 
        switch (of_get_phy_mode(np)) {
+       case PHY_INTERFACE_MODE_TRGMII:
+               mac->trgmii = true;
        case PHY_INTERFACE_MODE_RGMII_TXID:
        case PHY_INTERFACE_MODE_RGMII_RXID:
        case PHY_INTERFACE_MODE_RGMII_ID:
        case PHY_INTERFACE_MODE_RGMII:
-               ge_mode = 0;
+               mac->ge_mode = 0;
                break;
        case PHY_INTERFACE_MODE_MII:
-               ge_mode = 1;
+               mac->ge_mode = 1;
                break;
        case PHY_INTERFACE_MODE_REVMII:
-               ge_mode = 2;
+               mac->ge_mode = 2;
                break;
        case PHY_INTERFACE_MODE_RMII:
                if (!mac->id)
                        goto err_phy;
-               ge_mode = 3;
+               mac->ge_mode = 3;
                break;
        default:
                goto err_phy;
@@ -264,23 +292,26 @@ static int mtk_phy_connect(struct mtk_mac *mac)
        /* put the gmac into the right mode */
        regmap_read(eth->ethsys, ETHSYS_SYSCFG0, &val);
        val &= ~SYSCFG0_GE_MODE(SYSCFG0_GE_MASK, mac->id);
-       val |= SYSCFG0_GE_MODE(ge_mode, mac->id);
+       val |= SYSCFG0_GE_MODE(mac->ge_mode, mac->id);
        regmap_write(eth->ethsys, ETHSYS_SYSCFG0, val);
 
-       mtk_phy_connect_node(eth, mac, np);
-       mac->phy_dev->autoneg = AUTONEG_ENABLE;
-       mac->phy_dev->speed = 0;
-       mac->phy_dev->duplex = 0;
+       /* couple phydev to net_device */
+       if (mtk_phy_connect_node(eth, mac, np))
+               goto err_phy;
+
+       dev->phydev->autoneg = AUTONEG_ENABLE;
+       dev->phydev->speed = 0;
+       dev->phydev->duplex = 0;
 
        if (of_phy_is_fixed_link(mac->of_node))
-               mac->phy_dev->supported |=
+               dev->phydev->supported |=
                SUPPORTED_Pause | SUPPORTED_Asym_Pause;
 
-       mac->phy_dev->supported &= PHY_GBIT_FEATURES | SUPPORTED_Pause |
+       dev->phydev->supported &= PHY_GBIT_FEATURES | SUPPORTED_Pause |
                                   SUPPORTED_Asym_Pause;
-       mac->phy_dev->advertising = mac->phy_dev->supported |
+       dev->phydev->advertising = dev->phydev->supported |
                                    ADVERTISED_Autoneg;
-       phy_start_aneg(mac->phy_dev);
+       phy_start_aneg(dev->phydev);
 
        of_node_put(np);
 
@@ -288,7 +319,7 @@ static int mtk_phy_connect(struct mtk_mac *mac)
 
 err_phy:
        of_node_put(np);
-       dev_err(eth->dev, "invalid phy_mode\n");
+       dev_err(eth->dev, "%s: invalid phy\n", __func__);
        return -EINVAL;
 }
 
@@ -336,25 +367,27 @@ static void mtk_mdio_cleanup(struct mtk_eth *eth)
        mdiobus_unregister(eth->mii_bus);
 }
 
-static inline void mtk_irq_disable(struct mtk_eth *eth, u32 mask)
+static inline void mtk_irq_disable(struct mtk_eth *eth,
+                                  unsigned reg, u32 mask)
 {
        unsigned long flags;
        u32 val;
 
        spin_lock_irqsave(&eth->irq_lock, flags);
-       val = mtk_r32(eth, MTK_QDMA_INT_MASK);
-       mtk_w32(eth, val & ~mask, MTK_QDMA_INT_MASK);
+       val = mtk_r32(eth, reg);
+       mtk_w32(eth, val & ~mask, reg);
        spin_unlock_irqrestore(&eth->irq_lock, flags);
 }
 
-static inline void mtk_irq_enable(struct mtk_eth *eth, u32 mask)
+static inline void mtk_irq_enable(struct mtk_eth *eth,
+                                 unsigned reg, u32 mask)
 {
        unsigned long flags;
        u32 val;
 
        spin_lock_irqsave(&eth->irq_lock, flags);
-       val = mtk_r32(eth, MTK_QDMA_INT_MASK);
-       mtk_w32(eth, val | mask, MTK_QDMA_INT_MASK);
+       val = mtk_r32(eth, reg);
+       mtk_w32(eth, val | mask, reg);
        spin_unlock_irqrestore(&eth->irq_lock, flags);
 }
 
@@ -363,18 +396,20 @@ static int mtk_set_mac_address(struct net_device *dev, void *p)
        int ret = eth_mac_addr(dev, p);
        struct mtk_mac *mac = netdev_priv(dev);
        const char *macaddr = dev->dev_addr;
-       unsigned long flags;
 
        if (ret)
                return ret;
 
-       spin_lock_irqsave(&mac->hw->page_lock, flags);
+       if (unlikely(test_bit(MTK_RESETTING, &mac->hw->state)))
+               return -EBUSY;
+
+       spin_lock_bh(&mac->hw->page_lock);
        mtk_w32(mac->hw, (macaddr[0] << 8) | macaddr[1],
                MTK_GDMA_MAC_ADRH(mac->id));
        mtk_w32(mac->hw, (macaddr[2] << 24) | (macaddr[3] << 16) |
                (macaddr[4] << 8) | macaddr[5],
                MTK_GDMA_MAC_ADRL(mac->id));
-       spin_unlock_irqrestore(&mac->hw->page_lock, flags);
+       spin_unlock_bh(&mac->hw->page_lock);
 
        return 0;
 }
@@ -759,7 +794,6 @@ static int mtk_start_xmit(struct sk_buff *skb, struct net_device *dev)
        struct mtk_eth *eth = mac->hw;
        struct mtk_tx_ring *ring = &eth->tx_ring;
        struct net_device_stats *stats = &dev->stats;
-       unsigned long flags;
        bool gso = false;
        int tx_num;
 
@@ -767,14 +801,17 @@ static int mtk_start_xmit(struct sk_buff *skb, struct net_device *dev)
         * however we have 2 queues running on the same ring so we need to lock
         * the ring access
         */
-       spin_lock_irqsave(&eth->page_lock, flags);
+       spin_lock(&eth->page_lock);
+
+       if (unlikely(test_bit(MTK_RESETTING, &eth->state)))
+               goto drop;
 
        tx_num = mtk_cal_txd_req(skb);
        if (unlikely(atomic_read(&ring->free_count) <= tx_num)) {
                mtk_stop_queue(eth);
                netif_err(eth, tx_queued, dev,
                          "Tx Ring full when queue awake!\n");
-               spin_unlock_irqrestore(&eth->page_lock, flags);
+               spin_unlock(&eth->page_lock);
                return NETDEV_TX_BUSY;
        }
 
@@ -799,22 +836,62 @@ static int mtk_start_xmit(struct sk_buff *skb, struct net_device *dev)
        if (unlikely(atomic_read(&ring->free_count) <= ring->thresh))
                mtk_stop_queue(eth);
 
-       spin_unlock_irqrestore(&eth->page_lock, flags);
+       spin_unlock(&eth->page_lock);
 
        return NETDEV_TX_OK;
 
 drop:
-       spin_unlock_irqrestore(&eth->page_lock, flags);
+       spin_unlock(&eth->page_lock);
        stats->tx_dropped++;
        dev_kfree_skb(skb);
        return NETDEV_TX_OK;
 }
 
+static struct mtk_rx_ring *mtk_get_rx_ring(struct mtk_eth *eth)
+{
+       int i;
+       struct mtk_rx_ring *ring;
+       int idx;
+
+       if (!eth->hwlro)
+               return &eth->rx_ring[0];
+
+       for (i = 0; i < MTK_MAX_RX_RING_NUM; i++) {
+               ring = &eth->rx_ring[i];
+               idx = NEXT_RX_DESP_IDX(ring->calc_idx, ring->dma_size);
+               if (ring->dma[idx].rxd2 & RX_DMA_DONE) {
+                       ring->calc_idx_update = true;
+                       return ring;
+               }
+       }
+
+       return NULL;
+}
+
+static void mtk_update_rx_cpu_idx(struct mtk_eth *eth)
+{
+       struct mtk_rx_ring *ring;
+       int i;
+
+       if (!eth->hwlro) {
+               ring = &eth->rx_ring[0];
+               mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg);
+       } else {
+               for (i = 0; i < MTK_MAX_RX_RING_NUM; i++) {
+                       ring = &eth->rx_ring[i];
+                       if (ring->calc_idx_update) {
+                               ring->calc_idx_update = false;
+                               mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg);
+                       }
+               }
+       }
+}
+
 static int mtk_poll_rx(struct napi_struct *napi, int budget,
                       struct mtk_eth *eth)
 {
-       struct mtk_rx_ring *ring = &eth->rx_ring;
-       int idx = ring->calc_idx;
+       struct mtk_rx_ring *ring;
+       int idx;
        struct sk_buff *skb;
        u8 *data, *new_data;
        struct mtk_rx_dma *rxd, trxd;
@@ -826,7 +903,11 @@ static int mtk_poll_rx(struct napi_struct *napi, int budget,
                dma_addr_t dma_addr;
                int mac = 0;
 
-               idx = NEXT_RX_DESP_IDX(idx);
+               ring = mtk_get_rx_ring(eth);
+               if (unlikely(!ring))
+                       goto rx_done;
+
+               idx = NEXT_RX_DESP_IDX(ring->calc_idx, ring->dma_size);
                rxd = &ring->dma[idx];
                data = ring->data[idx];
 
@@ -841,6 +922,9 @@ static int mtk_poll_rx(struct napi_struct *napi, int budget,
 
                netdev = eth->netdev[mac];
 
+               if (unlikely(test_bit(MTK_RESETTING, &eth->state)))
+                       goto release_desc;
+
                /* alloc new buffer */
                new_data = napi_alloc_frag(ring->frag_size);
                if (unlikely(!new_data)) {
@@ -890,17 +974,19 @@ release_desc:
                rxd->rxd2 = RX_DMA_PLEN0(ring->buf_size);
 
                ring->calc_idx = idx;
+
+               done++;
+       }
+
+rx_done:
+       if (done) {
                /* make sure that all changes to the dma ring are flushed before
                 * we continue
                 */
                wmb();
-               mtk_w32(eth, ring->calc_idx, MTK_QRX_CRX_IDX0);
-               done++;
+               mtk_update_rx_cpu_idx(eth);
        }
 
-       if (done < budget)
-               mtk_w32(eth, MTK_RX_DONE_INT, MTK_QMTK_INT_STATUS);
-
        return done;
 }
 
@@ -1009,7 +1095,7 @@ static int mtk_napi_tx(struct napi_struct *napi, int budget)
                return budget;
 
        napi_complete(napi);
-       mtk_irq_enable(eth, MTK_TX_DONE_INT);
+       mtk_irq_enable(eth, MTK_QDMA_INT_MASK, MTK_TX_DONE_INT);
 
        return tx_done;
 }
@@ -1019,30 +1105,33 @@ static int mtk_napi_rx(struct napi_struct *napi, int budget)
        struct mtk_eth *eth = container_of(napi, struct mtk_eth, rx_napi);
        u32 status, mask;
        int rx_done = 0;
+       int remain_budget = budget;
 
        mtk_handle_status_irq(eth);
-       mtk_w32(eth, MTK_RX_DONE_INT, MTK_QMTK_INT_STATUS);
-       rx_done = mtk_poll_rx(napi, budget, eth);
+
+poll_again:
+       mtk_w32(eth, MTK_RX_DONE_INT, MTK_PDMA_INT_STATUS);
+       rx_done = mtk_poll_rx(napi, remain_budget, eth);
 
        if (unlikely(netif_msg_intr(eth))) {
-               status = mtk_r32(eth, MTK_QMTK_INT_STATUS);
-               mask = mtk_r32(eth, MTK_QDMA_INT_MASK);
+               status = mtk_r32(eth, MTK_PDMA_INT_STATUS);
+               mask = mtk_r32(eth, MTK_PDMA_INT_MASK);
                dev_info(eth->dev,
                         "done rx %d, intr 0x%08x/0x%x\n",
                         rx_done, status, mask);
        }
-
-       if (rx_done == budget)
-               return budget;
-
-       status = mtk_r32(eth, MTK_QMTK_INT_STATUS);
-       if (status & MTK_RX_DONE_INT)
+       if (rx_done == remain_budget)
                return budget;
 
+       status = mtk_r32(eth, MTK_PDMA_INT_STATUS);
+       if (status & MTK_RX_DONE_INT) {
+               remain_budget -= rx_done;
+               goto poll_again;
+       }
        napi_complete(napi);
-       mtk_irq_enable(eth, MTK_RX_DONE_INT);
+       mtk_irq_enable(eth, MTK_PDMA_INT_MASK, MTK_RX_DONE_INT);
 
-       return rx_done;
+       return rx_done + budget - remain_budget;
 }
 
 static int mtk_tx_alloc(struct mtk_eth *eth)
@@ -1089,6 +1178,7 @@ static int mtk_tx_alloc(struct mtk_eth *eth)
        mtk_w32(eth,
                ring->phys + ((MTK_DMA_SIZE - 1) * sz),
                MTK_QTX_DRX_PTR);
+       mtk_w32(eth, (QDMA_RES_THRES << 8) | QDMA_RES_THRES, MTK_QTX_CFG(0));
 
        return 0;
 
@@ -1117,32 +1207,41 @@ static void mtk_tx_clean(struct mtk_eth *eth)
        }
 }
 
-static int mtk_rx_alloc(struct mtk_eth *eth)
+static int mtk_rx_alloc(struct mtk_eth *eth, int ring_no, int rx_flag)
 {
-       struct mtk_rx_ring *ring = &eth->rx_ring;
+       struct mtk_rx_ring *ring = &eth->rx_ring[ring_no];
+       int rx_data_len, rx_dma_size;
        int i;
 
-       ring->frag_size = mtk_max_frag_size(ETH_DATA_LEN);
+       if (rx_flag == MTK_RX_FLAGS_HWLRO) {
+               rx_data_len = MTK_MAX_LRO_RX_LENGTH;
+               rx_dma_size = MTK_HW_LRO_DMA_SIZE;
+       } else {
+               rx_data_len = ETH_DATA_LEN;
+               rx_dma_size = MTK_DMA_SIZE;
+       }
+
+       ring->frag_size = mtk_max_frag_size(rx_data_len);
        ring->buf_size = mtk_max_buf_size(ring->frag_size);
-       ring->data = kcalloc(MTK_DMA_SIZE, sizeof(*ring->data),
+       ring->data = kcalloc(rx_dma_size, sizeof(*ring->data),
                             GFP_KERNEL);
        if (!ring->data)
                return -ENOMEM;
 
-       for (i = 0; i < MTK_DMA_SIZE; i++) {
+       for (i = 0; i < rx_dma_size; i++) {
                ring->data[i] = netdev_alloc_frag(ring->frag_size);
                if (!ring->data[i])
                        return -ENOMEM;
        }
 
        ring->dma = dma_alloc_coherent(eth->dev,
-                                      MTK_DMA_SIZE * sizeof(*ring->dma),
+                                      rx_dma_size * sizeof(*ring->dma),
                                       &ring->phys,
                                       GFP_ATOMIC | __GFP_ZERO);
        if (!ring->dma)
                return -ENOMEM;
 
-       for (i = 0; i < MTK_DMA_SIZE; i++) {
+       for (i = 0; i < rx_dma_size; i++) {
                dma_addr_t dma_addr = dma_map_single(eth->dev,
                                ring->data[i] + NET_SKB_PAD,
                                ring->buf_size,
@@ -1153,28 +1252,30 @@ static int mtk_rx_alloc(struct mtk_eth *eth)
 
                ring->dma[i].rxd2 = RX_DMA_PLEN0(ring->buf_size);
        }
-       ring->calc_idx = MTK_DMA_SIZE - 1;
+       ring->dma_size = rx_dma_size;
+       ring->calc_idx_update = false;
+       ring->calc_idx = rx_dma_size - 1;
+       ring->crx_idx_reg = MTK_PRX_CRX_IDX_CFG(ring_no);
        /* make sure that all changes to the dma ring are flushed before we
         * continue
         */
        wmb();
 
-       mtk_w32(eth, eth->rx_ring.phys, MTK_QRX_BASE_PTR0);
-       mtk_w32(eth, MTK_DMA_SIZE, MTK_QRX_MAX_CNT0);
-       mtk_w32(eth, eth->rx_ring.calc_idx, MTK_QRX_CRX_IDX0);
-       mtk_w32(eth, MTK_PST_DRX_IDX0, MTK_QDMA_RST_IDX);
-       mtk_w32(eth, (QDMA_RES_THRES << 8) | QDMA_RES_THRES, MTK_QTX_CFG(0));
+       mtk_w32(eth, ring->phys, MTK_PRX_BASE_PTR_CFG(ring_no));
+       mtk_w32(eth, rx_dma_size, MTK_PRX_MAX_CNT_CFG(ring_no));
+       mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg);
+       mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no), MTK_PDMA_RST_IDX);
 
        return 0;
 }
 
-static void mtk_rx_clean(struct mtk_eth *eth)
+static void mtk_rx_clean(struct mtk_eth *eth, int ring_no)
 {
-       struct mtk_rx_ring *ring = &eth->rx_ring;
+       struct mtk_rx_ring *ring = &eth->rx_ring[ring_no];
        int i;
 
        if (ring->data && ring->dma) {
-               for (i = 0; i < MTK_DMA_SIZE; i++) {
+               for (i = 0; i < ring->dma_size; i++) {
                        if (!ring->data[i])
                                continue;
                        if (!ring->dma[i].rxd1)
@@ -1191,13 +1292,274 @@ static void mtk_rx_clean(struct mtk_eth *eth)
 
        if (ring->dma) {
                dma_free_coherent(eth->dev,
-                                 MTK_DMA_SIZE * sizeof(*ring->dma),
+                                 ring->dma_size * sizeof(*ring->dma),
                                  ring->dma,
                                  ring->phys);
                ring->dma = NULL;
        }
 }
 
+static int mtk_hwlro_rx_init(struct mtk_eth *eth)
+{
+       int i;
+       u32 ring_ctrl_dw1 = 0, ring_ctrl_dw2 = 0, ring_ctrl_dw3 = 0;
+       u32 lro_ctrl_dw0 = 0, lro_ctrl_dw3 = 0;
+
+       /* set LRO rings to auto-learn modes */
+       ring_ctrl_dw2 |= MTK_RING_AUTO_LERAN_MODE;
+
+       /* validate LRO ring */
+       ring_ctrl_dw2 |= MTK_RING_VLD;
+
+       /* set AGE timer (unit: 20us) */
+       ring_ctrl_dw2 |= MTK_RING_AGE_TIME_H;
+       ring_ctrl_dw1 |= MTK_RING_AGE_TIME_L;
+
+       /* set max AGG timer (unit: 20us) */
+       ring_ctrl_dw2 |= MTK_RING_MAX_AGG_TIME;
+
+       /* set max LRO AGG count */
+       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++) {
+               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));
+       }
+
+       /* IPv4 checksum update enable */
+       lro_ctrl_dw0 |= MTK_L3_CKS_UPD_EN;
+
+       /* switch priority comparison to packet count mode */
+       lro_ctrl_dw0 |= MTK_LRO_ALT_PKT_CNT_MODE;
+
+       /* bandwidth threshold setting */
+       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);
+
+       /* 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;
+
+       /* enable HW LRO */
+       lro_ctrl_dw0 |= MTK_LRO_EN;
+
+       mtk_w32(eth, lro_ctrl_dw3, MTK_PDMA_LRO_CTRL_DW3);
+       mtk_w32(eth, lro_ctrl_dw0, MTK_PDMA_LRO_CTRL_DW0);
+
+       return 0;
+}
+
+static void mtk_hwlro_rx_uninit(struct mtk_eth *eth)
+{
+       int i;
+       u32 val;
+
+       /* relinquish lro rings, flush aggregated packets */
+       mtk_w32(eth, MTK_LRO_RING_RELINQUISH_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) {
+                       msleep(20);
+                       continue;
+               }
+       }
+
+       /* invalidate lro rings */
+       for (i = 1; i < MTK_MAX_RX_RING_NUM; i++)
+               mtk_w32(eth, 0, MTK_LRO_CTRL_DW2_CFG(i));
+
+       /* disable HW LRO */
+       mtk_w32(eth, 0, MTK_PDMA_LRO_CTRL_DW0);
+}
+
+static void mtk_hwlro_val_ipaddr(struct mtk_eth *eth, int idx, __be32 ip)
+{
+       u32 reg_val;
+
+       reg_val = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(idx));
+
+       /* invalidate the IP setting */
+       mtk_w32(eth, (reg_val & ~MTK_RING_MYIP_VLD), MTK_LRO_CTRL_DW2_CFG(idx));
+
+       mtk_w32(eth, ip, MTK_LRO_DIP_DW0_CFG(idx));
+
+       /* validate the IP setting */
+       mtk_w32(eth, (reg_val | MTK_RING_MYIP_VLD), MTK_LRO_CTRL_DW2_CFG(idx));
+}
+
+static void mtk_hwlro_inval_ipaddr(struct mtk_eth *eth, int idx)
+{
+       u32 reg_val;
+
+       reg_val = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(idx));
+
+       /* invalidate the IP setting */
+       mtk_w32(eth, (reg_val & ~MTK_RING_MYIP_VLD), MTK_LRO_CTRL_DW2_CFG(idx));
+
+       mtk_w32(eth, 0, MTK_LRO_DIP_DW0_CFG(idx));
+}
+
+static int mtk_hwlro_get_ip_cnt(struct mtk_mac *mac)
+{
+       int cnt = 0;
+       int i;
+
+       for (i = 0; i < MTK_MAX_LRO_IP_CNT; i++) {
+               if (mac->hwlro_ip[i])
+                       cnt++;
+       }
+
+       return cnt;
+}
+
+static int mtk_hwlro_add_ipaddr(struct net_device *dev,
+                               struct ethtool_rxnfc *cmd)
+{
+       struct ethtool_rx_flow_spec *fsp =
+               (struct ethtool_rx_flow_spec *)&cmd->fs;
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth = mac->hw;
+       int hwlro_idx;
+
+       if ((fsp->flow_type != TCP_V4_FLOW) ||
+           (!fsp->h_u.tcp_ip4_spec.ip4dst) ||
+           (fsp->location > 1))
+               return -EINVAL;
+
+       mac->hwlro_ip[fsp->location] = htonl(fsp->h_u.tcp_ip4_spec.ip4dst);
+       hwlro_idx = (mac->id * MTK_MAX_LRO_IP_CNT) + fsp->location;
+
+       mac->hwlro_ip_cnt = mtk_hwlro_get_ip_cnt(mac);
+
+       mtk_hwlro_val_ipaddr(eth, hwlro_idx, mac->hwlro_ip[fsp->location]);
+
+       return 0;
+}
+
+static int mtk_hwlro_del_ipaddr(struct net_device *dev,
+                               struct ethtool_rxnfc *cmd)
+{
+       struct ethtool_rx_flow_spec *fsp =
+               (struct ethtool_rx_flow_spec *)&cmd->fs;
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth = mac->hw;
+       int hwlro_idx;
+
+       if (fsp->location > 1)
+               return -EINVAL;
+
+       mac->hwlro_ip[fsp->location] = 0;
+       hwlro_idx = (mac->id * MTK_MAX_LRO_IP_CNT) + fsp->location;
+
+       mac->hwlro_ip_cnt = mtk_hwlro_get_ip_cnt(mac);
+
+       mtk_hwlro_inval_ipaddr(eth, hwlro_idx);
+
+       return 0;
+}
+
+static void mtk_hwlro_netdev_disable(struct net_device *dev)
+{
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth = mac->hw;
+       int i, hwlro_idx;
+
+       for (i = 0; i < MTK_MAX_LRO_IP_CNT; i++) {
+               mac->hwlro_ip[i] = 0;
+               hwlro_idx = (mac->id * MTK_MAX_LRO_IP_CNT) + i;
+
+               mtk_hwlro_inval_ipaddr(eth, hwlro_idx);
+       }
+
+       mac->hwlro_ip_cnt = 0;
+}
+
+static int mtk_hwlro_get_fdir_entry(struct net_device *dev,
+                                   struct ethtool_rxnfc *cmd)
+{
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct ethtool_rx_flow_spec *fsp =
+               (struct ethtool_rx_flow_spec *)&cmd->fs;
+
+       /* only tcp dst ipv4 is meaningful, others are meaningless */
+       fsp->flow_type = TCP_V4_FLOW;
+       fsp->h_u.tcp_ip4_spec.ip4dst = ntohl(mac->hwlro_ip[fsp->location]);
+       fsp->m_u.tcp_ip4_spec.ip4dst = 0;
+
+       fsp->h_u.tcp_ip4_spec.ip4src = 0;
+       fsp->m_u.tcp_ip4_spec.ip4src = 0xffffffff;
+       fsp->h_u.tcp_ip4_spec.psrc = 0;
+       fsp->m_u.tcp_ip4_spec.psrc = 0xffff;
+       fsp->h_u.tcp_ip4_spec.pdst = 0;
+       fsp->m_u.tcp_ip4_spec.pdst = 0xffff;
+       fsp->h_u.tcp_ip4_spec.tos = 0;
+       fsp->m_u.tcp_ip4_spec.tos = 0xff;
+
+       return 0;
+}
+
+static int mtk_hwlro_get_fdir_all(struct net_device *dev,
+                                 struct ethtool_rxnfc *cmd,
+                                 u32 *rule_locs)
+{
+       struct mtk_mac *mac = netdev_priv(dev);
+       int cnt = 0;
+       int i;
+
+       for (i = 0; i < MTK_MAX_LRO_IP_CNT; i++) {
+               if (mac->hwlro_ip[i]) {
+                       rule_locs[cnt] = i;
+                       cnt++;
+               }
+       }
+
+       cmd->rule_cnt = cnt;
+
+       return 0;
+}
+
+static netdev_features_t mtk_fix_features(struct net_device *dev,
+                                         netdev_features_t features)
+{
+       if (!(features & NETIF_F_LRO)) {
+               struct mtk_mac *mac = netdev_priv(dev);
+               int ip_cnt = mtk_hwlro_get_ip_cnt(mac);
+
+               if (ip_cnt) {
+                       netdev_info(dev, "RX flow is programmed, LRO should keep on\n");
+
+                       features |= NETIF_F_LRO;
+               }
+       }
+
+       return features;
+}
+
+static int mtk_set_features(struct net_device *dev, netdev_features_t features)
+{
+       int err = 0;
+
+       if (!((dev->features ^ features) & NETIF_F_LRO))
+               return 0;
+
+       if (!(features & NETIF_F_LRO))
+               mtk_hwlro_netdev_disable(dev);
+
+       return err;
+}
+
 /* wait for DMA to finish whatever it is doing before we start using it again */
 static int mtk_dma_busy_wait(struct mtk_eth *eth)
 {
@@ -1218,6 +1580,7 @@ static int mtk_dma_busy_wait(struct mtk_eth *eth)
 static int mtk_dma_init(struct mtk_eth *eth)
 {
        int err;
+       u32 i;
 
        if (mtk_dma_busy_wait(eth))
                return -EBUSY;
@@ -1233,10 +1596,21 @@ static int mtk_dma_init(struct mtk_eth *eth)
        if (err)
                return err;
 
-       err = mtk_rx_alloc(eth);
+       err = mtk_rx_alloc(eth, 0, MTK_RX_FLAGS_NORMAL);
        if (err)
                return err;
 
+       if (eth->hwlro) {
+               for (i = 1; i < MTK_MAX_RX_RING_NUM; i++) {
+                       err = mtk_rx_alloc(eth, i, MTK_RX_FLAGS_HWLRO);
+                       if (err)
+                               return err;
+               }
+               err = mtk_hwlro_rx_init(eth);
+               if (err)
+                       return err;
+       }
+
        /* Enable random early drop and set drop threshold automatically */
        mtk_w32(eth, FC_THRES_DROP_MODE | FC_THRES_DROP_EN | FC_THRES_MIN,
                MTK_QDMA_FC_THRES);
@@ -1261,7 +1635,14 @@ static void mtk_dma_free(struct mtk_eth *eth)
                eth->phy_scratch_ring = 0;
        }
        mtk_tx_clean(eth);
-       mtk_rx_clean(eth);
+       mtk_rx_clean(eth, 0);
+
+       if (eth->hwlro) {
+               mtk_hwlro_rx_uninit(eth);
+               for (i = 1; i < MTK_MAX_RX_RING_NUM; i++)
+                       mtk_rx_clean(eth, i);
+       }
+
        kfree(eth->scratch_head);
 }
 
@@ -1282,7 +1663,7 @@ static irqreturn_t mtk_handle_irq_rx(int irq, void *_eth)
 
        if (likely(napi_schedule_prep(&eth->rx_napi))) {
                __napi_schedule(&eth->rx_napi);
-               mtk_irq_disable(eth, MTK_RX_DONE_INT);
+               mtk_irq_disable(eth, MTK_PDMA_INT_MASK, MTK_RX_DONE_INT);
        }
 
        return IRQ_HANDLED;
@@ -1294,7 +1675,7 @@ static irqreturn_t mtk_handle_irq_tx(int irq, void *_eth)
 
        if (likely(napi_schedule_prep(&eth->tx_napi))) {
                __napi_schedule(&eth->tx_napi);
-               mtk_irq_disable(eth, MTK_TX_DONE_INT);
+               mtk_irq_disable(eth, MTK_QDMA_INT_MASK, MTK_TX_DONE_INT);
        }
 
        return IRQ_HANDLED;
@@ -1305,11 +1686,12 @@ static void mtk_poll_controller(struct net_device *dev)
 {
        struct mtk_mac *mac = netdev_priv(dev);
        struct mtk_eth *eth = mac->hw;
-       u32 int_mask = MTK_TX_DONE_INT | MTK_RX_DONE_INT;
 
-       mtk_irq_disable(eth, int_mask);
+       mtk_irq_disable(eth, MTK_QDMA_INT_MASK, MTK_TX_DONE_INT);
+       mtk_irq_disable(eth, MTK_PDMA_INT_MASK, MTK_RX_DONE_INT);
        mtk_handle_irq_rx(eth->irq[2], dev);
-       mtk_irq_enable(eth, int_mask);
+       mtk_irq_enable(eth, MTK_QDMA_INT_MASK, MTK_TX_DONE_INT);
+       mtk_irq_enable(eth, MTK_PDMA_INT_MASK, MTK_RX_DONE_INT);
 }
 #endif
 
@@ -1324,11 +1706,15 @@ static int mtk_start_dma(struct mtk_eth *eth)
        }
 
        mtk_w32(eth,
-               MTK_TX_WB_DDONE | MTK_RX_DMA_EN | MTK_TX_DMA_EN |
-               MTK_RX_2B_OFFSET | MTK_DMA_SIZE_16DWORDS |
-               MTK_RX_BT_32DWORDS | MTK_NDP_CO_PRO,
+               MTK_TX_WB_DDONE | MTK_TX_DMA_EN |
+               MTK_DMA_SIZE_16DWORDS | MTK_NDP_CO_PRO,
                MTK_QDMA_GLO_CFG);
 
+       mtk_w32(eth,
+               MTK_RX_DMA_EN | MTK_RX_2B_OFFSET |
+               MTK_RX_BT_32DWORDS | MTK_MULTI_EN,
+               MTK_PDMA_GLO_CFG);
+
        return 0;
 }
 
@@ -1346,11 +1732,12 @@ static int mtk_open(struct net_device *dev)
 
                napi_enable(&eth->tx_napi);
                napi_enable(&eth->rx_napi);
-               mtk_irq_enable(eth, MTK_TX_DONE_INT | MTK_RX_DONE_INT);
+               mtk_irq_enable(eth, MTK_QDMA_INT_MASK, MTK_TX_DONE_INT);
+               mtk_irq_enable(eth, MTK_PDMA_INT_MASK, MTK_RX_DONE_INT);
        }
        atomic_inc(&eth->dma_refcnt);
 
-       phy_start(mac->phy_dev);
+       phy_start(dev->phydev);
        netif_start_queue(dev);
 
        return 0;
@@ -1358,16 +1745,15 @@ static int mtk_open(struct net_device *dev)
 
 static void mtk_stop_dma(struct mtk_eth *eth, u32 glo_cfg)
 {
-       unsigned long flags;
        u32 val;
        int i;
 
        /* stop the dma engine */
-       spin_lock_irqsave(&eth->page_lock, flags);
+       spin_lock_bh(&eth->page_lock);
        val = mtk_r32(eth, glo_cfg);
        mtk_w32(eth, val & ~(MTK_TX_WB_DDONE | MTK_RX_DMA_EN | MTK_TX_DMA_EN),
                glo_cfg);
-       spin_unlock_irqrestore(&eth->page_lock, flags);
+       spin_unlock_bh(&eth->page_lock);
 
        /* wait for dma stop */
        for (i = 0; i < 10; i++) {
@@ -1386,13 +1772,14 @@ static int mtk_stop(struct net_device *dev)
        struct mtk_eth *eth = mac->hw;
 
        netif_tx_disable(dev);
-       phy_stop(mac->phy_dev);
+       phy_stop(dev->phydev);
 
        /* only shutdown DMA if this is the last user */
        if (!atomic_dec_and_test(&eth->dma_refcnt))
                return 0;
 
-       mtk_irq_disable(eth, MTK_TX_DONE_INT | MTK_RX_DONE_INT);
+       mtk_irq_disable(eth, MTK_QDMA_INT_MASK, MTK_TX_DONE_INT);
+       mtk_irq_disable(eth, MTK_PDMA_INT_MASK, MTK_RX_DONE_INT);
        napi_disable(&eth->tx_napi);
        napi_disable(&eth->rx_napi);
 
@@ -1403,15 +1790,44 @@ static int mtk_stop(struct net_device *dev)
        return 0;
 }
 
-static int __init mtk_hw_init(struct mtk_eth *eth)
+static void ethsys_reset(struct mtk_eth *eth, u32 reset_bits)
 {
-       int err, i;
+       regmap_update_bits(eth->ethsys, ETHSYS_RSTCTRL,
+                          reset_bits,
+                          reset_bits);
+
+       usleep_range(1000, 1100);
+       regmap_update_bits(eth->ethsys, ETHSYS_RSTCTRL,
+                          reset_bits,
+                          ~reset_bits);
+       mdelay(10);
+}
+
+static int mtk_hw_init(struct mtk_eth *eth)
+{
+       int i, val;
+
+       if (test_and_set_bit(MTK_HW_INIT, &eth->state))
+               return 0;
+
+       pm_runtime_enable(eth->dev);
+       pm_runtime_get_sync(eth->dev);
+
+       clk_prepare_enable(eth->clks[MTK_CLK_ETHIF]);
+       clk_prepare_enable(eth->clks[MTK_CLK_ESW]);
+       clk_prepare_enable(eth->clks[MTK_CLK_GP1]);
+       clk_prepare_enable(eth->clks[MTK_CLK_GP2]);
+       ethsys_reset(eth, RSTCTRL_FE);
+       ethsys_reset(eth, RSTCTRL_PPE);
 
-       /* reset the frame engine */
-       reset_control_assert(eth->rstc);
-       usleep_range(10, 20);
-       reset_control_deassert(eth->rstc);
-       usleep_range(10, 20);
+       regmap_read(eth->ethsys, ETHSYS_SYSCFG0, &val);
+       for (i = 0; i < MTK_MAC_COUNT; i++) {
+               if (!eth->mac[i])
+                       continue;
+               val &= ~SYSCFG0_GE_MODE(SYSCFG0_GE_MASK, eth->mac[i]->id);
+               val |= SYSCFG0_GE_MODE(eth->mac[i]->ge_mode, eth->mac[i]->id);
+       }
+       regmap_write(eth->ethsys, ETHSYS_SYSCFG0, val);
 
        /* Set GE2 driving and slew rate */
        regmap_write(eth->pctl, GPIO_DRV_SEL10, 0xa00);
@@ -1431,22 +1847,11 @@ static int __init mtk_hw_init(struct mtk_eth *eth)
        /* Enable RX VLan Offloading */
        mtk_w32(eth, 1, MTK_CDMP_EG_CTRL);
 
-       err = devm_request_irq(eth->dev, eth->irq[1], mtk_handle_irq_tx, 0,
-                              dev_name(eth->dev), eth);
-       if (err)
-               return err;
-       err = devm_request_irq(eth->dev, eth->irq[2], mtk_handle_irq_rx, 0,
-                              dev_name(eth->dev), eth);
-       if (err)
-               return err;
-
-       err = mtk_mdio_init(eth);
-       if (err)
-               return err;
-
        /* disable delay and normal interrupt */
        mtk_w32(eth, 0, MTK_QDMA_DELAY_INT);
-       mtk_irq_disable(eth, ~0);
+       mtk_w32(eth, 0, MTK_PDMA_DELAY_INT);
+       mtk_irq_disable(eth, MTK_QDMA_INT_MASK, ~0);
+       mtk_irq_disable(eth, MTK_PDMA_INT_MASK, ~0);
        mtk_w32(eth, RST_GL_PSE, MTK_RST_GL);
        mtk_w32(eth, 0, MTK_RST_GL);
 
@@ -1460,9 +1865,8 @@ static int __init mtk_hw_init(struct mtk_eth *eth)
        for (i = 0; i < 2; i++) {
                u32 val = mtk_r32(eth, MTK_GDMA_FWD_CFG(i));
 
-               /* setup the forward port to send frame to QDMA */
+               /* setup the forward port to send frame to PDMA */
                val &= ~0xffff;
-               val |= 0x5555;
 
                /* Enable RX checksum */
                val |= MTK_GDMA_ICS_EN | MTK_GDMA_TCS_EN | MTK_GDMA_UCS_EN;
@@ -1474,6 +1878,22 @@ static int __init mtk_hw_init(struct mtk_eth *eth)
        return 0;
 }
 
+static int mtk_hw_deinit(struct mtk_eth *eth)
+{
+       if (!test_and_clear_bit(MTK_HW_INIT, &eth->state))
+               return 0;
+
+       clk_disable_unprepare(eth->clks[MTK_CLK_GP2]);
+       clk_disable_unprepare(eth->clks[MTK_CLK_GP1]);
+       clk_disable_unprepare(eth->clks[MTK_CLK_ESW]);
+       clk_disable_unprepare(eth->clks[MTK_CLK_ETHIF]);
+
+       pm_runtime_put_sync(eth->dev);
+       pm_runtime_disable(eth->dev);
+
+       return 0;
+}
+
 static int __init mtk_init(struct net_device *dev)
 {
        struct mtk_mac *mac = netdev_priv(dev);
@@ -1492,7 +1912,7 @@ static int __init mtk_init(struct net_device *dev)
                dev->addr_assign_type = NET_ADDR_RANDOM;
        }
 
-       return mtk_phy_connect(mac);
+       return mtk_phy_connect(dev);
 }
 
 static void mtk_uninit(struct net_device *dev)
@@ -1500,19 +1920,18 @@ static void mtk_uninit(struct net_device *dev)
        struct mtk_mac *mac = netdev_priv(dev);
        struct mtk_eth *eth = mac->hw;
 
-       phy_disconnect(mac->phy_dev);
-       mtk_irq_disable(eth, ~0);
+       phy_disconnect(dev->phydev);
+       mtk_irq_disable(eth, MTK_QDMA_INT_MASK, ~0);
+       mtk_irq_disable(eth, MTK_PDMA_INT_MASK, ~0);
 }
 
 static int mtk_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 {
-       struct mtk_mac *mac = netdev_priv(dev);
-
        switch (cmd) {
        case SIOCGMIIPHY:
        case SIOCGMIIREG:
        case SIOCSMIIREG:
-               return phy_mii_ioctl(mac->phy_dev, ifr, cmd);
+               return phy_mii_ioctl(dev->phydev, ifr, cmd);
        default:
                break;
        }
@@ -1528,6 +1947,12 @@ static void mtk_pending_work(struct work_struct *work)
 
        rtnl_lock();
 
+       dev_dbg(eth->dev, "[%s][%d] reset\n", __func__, __LINE__);
+
+       while (test_and_set_bit_lock(MTK_RESETTING, &eth->state))
+               cpu_relax();
+
+       dev_dbg(eth->dev, "[%s][%d] mtk_stop starts\n", __func__, __LINE__);
        /* stop all devices to make sure that dma is properly shut down */
        for (i = 0; i < MTK_MAC_COUNT; i++) {
                if (!eth->netdev[i])
@@ -1535,6 +1960,27 @@ static void mtk_pending_work(struct work_struct *work)
                mtk_stop(eth->netdev[i]);
                __set_bit(i, &restart);
        }
+       dev_dbg(eth->dev, "[%s][%d] mtk_stop ends\n", __func__, __LINE__);
+
+       /* restart underlying hardware such as power, clock, pin mux
+        * and the connected phy
+        */
+       mtk_hw_deinit(eth);
+
+       if (eth->dev->pins)
+               pinctrl_select_state(eth->dev->pins->p,
+                                    eth->dev->pins->default_state);
+       mtk_hw_init(eth);
+
+       for (i = 0; i < MTK_MAC_COUNT; i++) {
+               if (!eth->mac[i] ||
+                   of_phy_is_fixed_link(eth->mac[i]->of_node))
+                       continue;
+               err = phy_init_hw(eth->netdev[i]->phydev);
+               if (err)
+                       dev_err(eth->dev, "%s: PHY init failed.\n",
+                               eth->netdev[i]->name);
+       }
 
        /* restart DMA and enable IRQs */
        for (i = 0; i < MTK_MAC_COUNT; i++) {
@@ -1547,51 +1993,69 @@ static void mtk_pending_work(struct work_struct *work)
                        dev_close(eth->netdev[i]);
                }
        }
+
+       dev_dbg(eth->dev, "[%s][%d] reset done\n", __func__, __LINE__);
+
+       clear_bit_unlock(MTK_RESETTING, &eth->state);
+
        rtnl_unlock();
 }
 
-static int mtk_cleanup(struct mtk_eth *eth)
+static int mtk_free_dev(struct mtk_eth *eth)
 {
        int i;
 
        for (i = 0; i < MTK_MAC_COUNT; i++) {
                if (!eth->netdev[i])
                        continue;
+               free_netdev(eth->netdev[i]);
+       }
+
+       return 0;
+}
+
+static int mtk_unreg_dev(struct mtk_eth *eth)
+{
+       int i;
 
+       for (i = 0; i < MTK_MAC_COUNT; i++) {
+               if (!eth->netdev[i])
+                       continue;
                unregister_netdev(eth->netdev[i]);
-               free_netdev(eth->netdev[i]);
        }
+
+       return 0;
+}
+
+static int mtk_cleanup(struct mtk_eth *eth)
+{
+       mtk_unreg_dev(eth);
+       mtk_free_dev(eth);
        cancel_work_sync(&eth->pending_work);
 
        return 0;
 }
 
-static int mtk_get_settings(struct net_device *dev,
-                           struct ethtool_cmd *cmd)
+int mtk_get_link_ksettings(struct net_device *ndev,
+                          struct ethtool_link_ksettings *cmd)
 {
-       struct mtk_mac *mac = netdev_priv(dev);
-       int err;
+       struct mtk_mac *mac = netdev_priv(ndev);
 
-       err = phy_read_status(mac->phy_dev);
-       if (err)
-               return -ENODEV;
+       if (unlikely(test_bit(MTK_RESETTING, &mac->hw->state)))
+               return -EBUSY;
 
-       return phy_ethtool_gset(mac->phy_dev, cmd);
+       return phy_ethtool_ksettings_get(ndev->phydev, cmd);
 }
 
-static int mtk_set_settings(struct net_device *dev,
-                           struct ethtool_cmd *cmd)
+int mtk_set_link_ksettings(struct net_device *ndev,
+                          const struct ethtool_link_ksettings *cmd)
 {
-       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_mac *mac = netdev_priv(ndev);
 
-       if (cmd->phy_address != mac->phy_dev->mdio.addr) {
-               mac->phy_dev = mdiobus_get_phy(mac->hw->mii_bus,
-                                              cmd->phy_address);
-               if (!mac->phy_dev)
-                       return -ENODEV;
-       }
+       if (unlikely(test_bit(MTK_RESETTING, &mac->hw->state)))
+               return -EBUSY;
 
-       return phy_ethtool_sset(mac->phy_dev, cmd);
+       return phy_ethtool_ksettings_set(ndev->phydev, cmd);
 }
 
 static void mtk_get_drvinfo(struct net_device *dev,
@@ -1622,7 +2086,10 @@ static int mtk_nway_reset(struct net_device *dev)
 {
        struct mtk_mac *mac = netdev_priv(dev);
 
-       return genphy_restart_aneg(mac->phy_dev);
+       if (unlikely(test_bit(MTK_RESETTING, &mac->hw->state)))
+               return -EBUSY;
+
+       return genphy_restart_aneg(dev->phydev);
 }
 
 static u32 mtk_get_link(struct net_device *dev)
@@ -1630,11 +2097,14 @@ static u32 mtk_get_link(struct net_device *dev)
        struct mtk_mac *mac = netdev_priv(dev);
        int err;
 
-       err = genphy_update_link(mac->phy_dev);
+       if (unlikely(test_bit(MTK_RESETTING, &mac->hw->state)))
+               return -EBUSY;
+
+       err = genphy_update_link(dev->phydev);
        if (err)
                return ethtool_op_get_link(dev);
 
-       return mac->phy_dev->link;
+       return dev->phydev->link;
 }
 
 static void mtk_get_strings(struct net_device *dev, u32 stringset, u8 *data)
@@ -1670,6 +2140,9 @@ static void mtk_get_ethtool_stats(struct net_device *dev,
        unsigned int start;
        int i;
 
+       if (unlikely(test_bit(MTK_RESETTING, &mac->hw->state)))
+               return;
+
        if (netif_running(dev) && netif_device_present(dev)) {
                if (spin_trylock(&hwstats->stats_lock)) {
                        mtk_stats_update_mac(mac);
@@ -1677,8 +2150,9 @@ static void mtk_get_ethtool_stats(struct net_device *dev,
                }
        }
 
+       data_src = (u64 *)hwstats;
+
        do {
-               data_src = (u64*)hwstats;
                data_dst = data;
                start = u64_stats_fetch_begin_irq(&hwstats->syncp);
 
@@ -1687,9 +2161,65 @@ static void mtk_get_ethtool_stats(struct net_device *dev,
        } while (u64_stats_fetch_retry_irq(&hwstats->syncp, start));
 }
 
-static struct ethtool_ops mtk_ethtool_ops = {
-       .get_settings           = mtk_get_settings,
-       .set_settings           = mtk_set_settings,
+static int mtk_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
+                        u32 *rule_locs)
+{
+       int ret = -EOPNOTSUPP;
+
+       switch (cmd->cmd) {
+       case ETHTOOL_GRXRINGS:
+               if (dev->features & NETIF_F_LRO) {
+                       cmd->data = MTK_MAX_RX_RING_NUM;
+                       ret = 0;
+               }
+               break;
+       case ETHTOOL_GRXCLSRLCNT:
+               if (dev->features & NETIF_F_LRO) {
+                       struct mtk_mac *mac = netdev_priv(dev);
+
+                       cmd->rule_cnt = mac->hwlro_ip_cnt;
+                       ret = 0;
+               }
+               break;
+       case ETHTOOL_GRXCLSRULE:
+               if (dev->features & NETIF_F_LRO)
+                       ret = mtk_hwlro_get_fdir_entry(dev, cmd);
+               break;
+       case ETHTOOL_GRXCLSRLALL:
+               if (dev->features & NETIF_F_LRO)
+                       ret = mtk_hwlro_get_fdir_all(dev, cmd,
+                                                    rule_locs);
+               break;
+       default:
+               break;
+       }
+
+       return ret;
+}
+
+static int mtk_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
+{
+       int ret = -EOPNOTSUPP;
+
+       switch (cmd->cmd) {
+       case ETHTOOL_SRXCLSRLINS:
+               if (dev->features & NETIF_F_LRO)
+                       ret = mtk_hwlro_add_ipaddr(dev, cmd);
+               break;
+       case ETHTOOL_SRXCLSRLDEL:
+               if (dev->features & NETIF_F_LRO)
+                       ret = mtk_hwlro_del_ipaddr(dev, cmd);
+               break;
+       default:
+               break;
+       }
+
+       return ret;
+}
+
+static const struct ethtool_ops mtk_ethtool_ops = {
+       .get_link_ksettings     = mtk_get_link_ksettings,
+       .set_link_ksettings     = mtk_set_link_ksettings,
        .get_drvinfo            = mtk_get_drvinfo,
        .get_msglevel           = mtk_get_msglevel,
        .set_msglevel           = mtk_set_msglevel,
@@ -1698,6 +2228,8 @@ static struct ethtool_ops mtk_ethtool_ops = {
        .get_strings            = mtk_get_strings,
        .get_sset_count         = mtk_get_sset_count,
        .get_ethtool_stats      = mtk_get_ethtool_stats,
+       .get_rxnfc              = mtk_get_rxnfc,
+       .set_rxnfc              = mtk_set_rxnfc,
 };
 
 static const struct net_device_ops mtk_netdev_ops = {
@@ -1712,6 +2244,8 @@ static const struct net_device_ops mtk_netdev_ops = {
        .ndo_change_mtu         = eth_change_mtu,
        .ndo_tx_timeout         = mtk_tx_timeout,
        .ndo_get_stats64        = mtk_get_stats64,
+       .ndo_fix_features       = mtk_fix_features,
+       .ndo_set_features       = mtk_set_features,
 #ifdef CONFIG_NET_POLL_CONTROLLER
        .ndo_poll_controller    = mtk_poll_controller,
 #endif
@@ -1750,6 +2284,9 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
        mac->hw = eth;
        mac->of_node = np;
 
+       memset(mac->hwlro_ip, 0, sizeof(mac->hwlro_ip));
+       mac->hwlro_ip_cnt = 0;
+
        mac->hw_stats = devm_kzalloc(eth->dev,
                                     sizeof(*mac->hw_stats),
                                     GFP_KERNEL);
@@ -1766,21 +2303,17 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
        eth->netdev[id]->watchdog_timeo = 5 * HZ;
        eth->netdev[id]->netdev_ops = &mtk_netdev_ops;
        eth->netdev[id]->base_addr = (unsigned long)eth->base;
+
+       eth->netdev[id]->hw_features = MTK_HW_FEATURES;
+       if (eth->hwlro)
+               eth->netdev[id]->hw_features |= NETIF_F_LRO;
+
        eth->netdev[id]->vlan_features = MTK_HW_FEATURES &
                ~(NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX);
        eth->netdev[id]->features |= MTK_HW_FEATURES;
        eth->netdev[id]->ethtool_ops = &mtk_ethtool_ops;
 
-       err = register_netdev(eth->netdev[id]);
-       if (err) {
-               dev_err(eth->dev, "error bringing up device\n");
-               goto free_netdev;
-       }
        eth->netdev[id]->irq = eth->irq[0];
-       netif_info(eth, probe, eth->netdev[id],
-                  "mediatek frame engine at 0x%08lx, irq %d\n",
-                  eth->netdev[id]->base_addr, eth->irq[0]);
-
        return 0;
 
 free_netdev:
@@ -1827,11 +2360,7 @@ static int mtk_probe(struct platform_device *pdev)
                return PTR_ERR(eth->pctl);
        }
 
-       eth->rstc = devm_reset_control_get(&pdev->dev, "eth");
-       if (IS_ERR(eth->rstc)) {
-               dev_err(&pdev->dev, "no eth reset found\n");
-               return PTR_ERR(eth->rstc);
-       }
+       eth->hwlro = of_property_read_bool(pdev->dev.of_node, "mediatek,hwlro");
 
        for (i = 0; i < 3; i++) {
                eth->irq[i] = platform_get_irq(pdev, i);
@@ -1850,11 +2379,6 @@ static int mtk_probe(struct platform_device *pdev)
                }
        }
 
-       clk_prepare_enable(eth->clks[MTK_CLK_ETHIF]);
-       clk_prepare_enable(eth->clks[MTK_CLK_ESW]);
-       clk_prepare_enable(eth->clks[MTK_CLK_GP1]);
-       clk_prepare_enable(eth->clks[MTK_CLK_GP2]);
-
        eth->msg_enable = netif_msg_init(mtk_msg_level, MTK_DEFAULT_MSG_ENABLE);
        INIT_WORK(&eth->pending_work, mtk_pending_work);
 
@@ -1872,7 +2396,35 @@ static int mtk_probe(struct platform_device *pdev)
 
                err = mtk_add_mac(eth, mac_np);
                if (err)
-                       goto err_free_dev;
+                       goto err_deinit_hw;
+       }
+
+       err = devm_request_irq(eth->dev, eth->irq[1], mtk_handle_irq_tx, 0,
+                              dev_name(eth->dev), eth);
+       if (err)
+               goto err_free_dev;
+
+       err = devm_request_irq(eth->dev, eth->irq[2], mtk_handle_irq_rx, 0,
+                              dev_name(eth->dev), eth);
+       if (err)
+               goto err_free_dev;
+
+       err = mtk_mdio_init(eth);
+       if (err)
+               goto err_free_dev;
+
+       for (i = 0; i < MTK_MAX_DEVS; i++) {
+               if (!eth->netdev[i])
+                       continue;
+
+               err = register_netdev(eth->netdev[i]);
+               if (err) {
+                       dev_err(eth->dev, "error bringing up device\n");
+                       goto err_deinit_mdio;
+               } else
+                       netif_info(eth, probe, eth->netdev[i],
+                                  "mediatek frame engine at 0x%08lx, irq %d\n",
+                                  eth->netdev[i]->base_addr, eth->irq[0]);
        }
 
        /* we run 2 devices on the same DMA ring so we need a dummy device
@@ -1888,8 +2440,13 @@ static int mtk_probe(struct platform_device *pdev)
 
        return 0;
 
+err_deinit_mdio:
+       mtk_mdio_cleanup(eth);
 err_free_dev:
-       mtk_cleanup(eth);
+       mtk_free_dev(eth);
+err_deinit_hw:
+       mtk_hw_deinit(eth);
+
        return err;
 }
 
@@ -1905,16 +2462,12 @@ static int mtk_remove(struct platform_device *pdev)
                mtk_stop(eth->netdev[i]);
        }
 
-       clk_disable_unprepare(eth->clks[MTK_CLK_ETHIF]);
-       clk_disable_unprepare(eth->clks[MTK_CLK_ESW]);
-       clk_disable_unprepare(eth->clks[MTK_CLK_GP1]);
-       clk_disable_unprepare(eth->clks[MTK_CLK_GP2]);
+       mtk_hw_deinit(eth);
 
        netif_napi_del(&eth->tx_napi);
        netif_napi_del(&eth->rx_napi);
        mtk_cleanup(eth);
        mtk_mdio_cleanup(eth);
-       platform_set_drvdata(pdev, NULL);
 
        return 0;
 }