[PATCH] net/ftgmac100: Rewrite the driver

Benjamin Herrenschmidt benh at kernel.crashing.org
Mon Mar 27 21:28:36 AEDT 2017


This is an almost-complete rewrite of this driver.

The patch overall multiplies the performance of the driver
on an AST2500 eval board with a gigabit link.

I get above 360Mbit/s with this vs. about 80Mbit/s with the current
driver. I've done some tests on NC-SI machines as well, I get close
to peak (above 90Mbit/s).

It does that by, among other things, rewriting the receive and
transmit code, to both simplify the fast path as much as possible,
avoid flushing of the caches (the aspeed chips have really slow
little ARM cores), implementing support for fragmented sends,
fixing HW checksum generation (AST2500 only), etc...

In addition, I've added netpoll support, tx timeout recovery,
better handling of link speed changes, multicast filter
and promisc support, various ethtool config additions etc...

Finally, the code has been cleaned up and reorganized, and
various corner cases fixed, such as recovery in some error
situations.

Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
--

Please give it a good beating before I submit upstream.

While I tried initially to do an incremental series of patch this
quickly became unrealistic as I rewrote more of the driver. This
is better reviewed as a new replacement driver for the same chip
instead. Since the Aspeed SoC is the only in-tree user and we are
maintaining this for OpenBMC, the risk is limited, I didn't feel
the need of submitting this as a separate driver and deprecate
the old one.
---
 arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts |    1 -
 arch/arm/boot/dts/aspeed-g4.dtsi              |    6 +-
 arch/arm/boot/dts/aspeed-g5.dtsi              |    6 +-
 drivers/net/ethernet/faraday/ftgmac100.c      | 2141 ++++++++++++++-----------
 drivers/net/ethernet/faraday/ftgmac100.h      |  183 ++-
 5 files changed, 1390 insertions(+), 947 deletions(-)

diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
index 6c52f1c..24e2c0f 100644
--- a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
+++ b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
@@ -102,7 +102,6 @@
 	status = "okay";
 
 	use-ncsi;
-	no-hw-checksum;
 };
 
 
diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi
index 28408a2..80c0867 100644
--- a/arch/arm/boot/dts/aspeed-g4.dtsi
+++ b/arch/arm/boot/dts/aspeed-g4.dtsi
@@ -42,18 +42,16 @@
 		};
 
 		mac0: ethernet at 1e660000 {
-			compatible = "faraday,ftgmac100";
+			compatible = "aspeed,ast2400-mac", "faraday,ftgmac100";
 			reg = <0x1e660000 0x180>;
 			interrupts = <2>;
-			no-hw-checksum;
 			status = "disabled";
 		};
 
 		mac1: ethernet at 1e680000 {
-			compatible = "faraday,ftgmac100";
+			compatible = "aspeed,ast2400-mac", "faraday,ftgmac100";
 			reg = <0x1e680000 0x180>;
 			interrupts = <3>;
-			no-hw-checksum;
 			status = "disabled";
 		};
 
diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi
index 7c09a26..925cdf7 100644
--- a/arch/arm/boot/dts/aspeed-g5.dtsi
+++ b/arch/arm/boot/dts/aspeed-g5.dtsi
@@ -141,18 +141,16 @@
 		};
 
 		mac0: ethernet at 1e660000 {
-			compatible = "faraday,ftgmac100";
+			compatible = "aspeed,ast2500-mac", "faraday,ftgmac100";
 			reg = <0x1e660000 0x180>;
 			interrupts = <2>;
-			no-hw-checksum;
 			status = "disabled";
 		};
 
 		mac1: ethernet at 1e680000 {
-			compatible = "faraday,ftgmac100";
+			compatible = "aspeed,ast2500-mac", "faraday,ftgmac100";
 			reg = <0x1e680000 0x180>;
 			interrupts = <3>;
-			no-hw-checksum;
 			status = "disabled";
 		};
 
diff --git a/drivers/net/ethernet/faraday/ftgmac100.c b/drivers/net/ethernet/faraday/ftgmac100.c
index f40fa92..a81568a 100644
--- a/drivers/net/ethernet/faraday/ftgmac100.c
+++ b/drivers/net/ethernet/faraday/ftgmac100.c
@@ -4,6 +4,10 @@
  * (C) Copyright 2009-2011 Faraday Technology
  * Po-Yu Chuang <ratbert at faraday-tech.com>
  *
+ * Largely rewritten by
+ *
+ * Benjamin Herrenschmidt, copyright 2017, IBM Corp.
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -18,7 +22,6 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-
 #define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
 
 #include <linux/dma-mapping.h>
@@ -30,900 +33,1384 @@
 #include <linux/netdevice.h>
 #include <linux/phy.h>
 #include <linux/platform_device.h>
+#include <linux/crc32.h>
+#include <linux/delay.h>
 #include <net/ip.h>
 #include <net/ncsi.h>
 
 #include "ftgmac100.h"
 
 #define DRV_NAME	"ftgmac100"
-#define DRV_VERSION	"0.7"
+#define DRV_VERSION	"1.0"
 
-#define RX_QUEUE_ENTRIES	256	/* must be power of 2 */
-#define TX_QUEUE_ENTRIES	512	/* must be power of 2 */
+/* Arbitrary values, I am not sure the HW has limits */
+#define MAX_RX_QUEUE_ENTRIES	1024
+#define MAX_TX_QUEUE_ENTRIES	1024
+#define MIN_RX_QUEUE_ENTRIES	32
+#define MIN_TX_QUEUE_ENTRIES	32
 
-#define MAX_PKT_SIZE		1518
-#define RX_BUF_SIZE		PAGE_SIZE	/* must be smaller than 0x3fff */
+/* Defaults */
+#define DEF_RX_QUEUE_ENTRIES	128
+#define DEF_TX_QUEUE_ENTRIES	128
 
-/******************************************************************************
- * private data
- *****************************************************************************/
-struct ftgmac100_descs {
-	struct ftgmac100_rxdes rxdes[RX_QUEUE_ENTRIES];
-	struct ftgmac100_txdes txdes[TX_QUEUE_ENTRIES];
-};
+/* We don't do jumbo frames */
+#define MAX_PKT_SIZE		1536
+#define RX_BUF_SIZE		MAX_PKT_SIZE  /* must be smaller than 0x3fff */
 
 struct ftgmac100 {
+	/* Registers */
 	struct resource *res;
 	void __iomem *base;
-	int irq;
-
-	struct ftgmac100_descs *descs;
-	dma_addr_t descs_dma_addr;
-
-	struct page *rx_pages[RX_QUEUE_ENTRIES];
 
+	/* Rx ring */
+	unsigned int rx_q_entries;
+	struct ftgmac100_rxdes *rxdes;
+	dma_addr_t rxdes_dma;
+	struct sk_buff **rx_skbs;
 	unsigned int rx_pointer;
+
+	/* Tx ring */
+	struct ftgmac100_txdes *txdes;
+	dma_addr_t txdes_dma;
+	unsigned int tx_q_entries;
+	struct sk_buff **tx_skbs;
 	unsigned int tx_clean_pointer;
 	unsigned int tx_pointer;
-	unsigned int tx_pending;
 
-	spinlock_t tx_lock;
+	/* Used to signal the reset task of ring change request */
+	unsigned int new_rx_q_entries;
+	unsigned int new_tx_q_entries;
+
+	/* Scratch page to use when rx skb alloc fails */
+	void *rx_scratch;
+	dma_addr_t rx_scratch_dma;
 
-	struct net_device *netdev;
+	/* Component structures */
+	struct net_device *ndev;
 	struct device *dev;
-	struct ncsi_dev *ndev;
+	struct ncsi_dev *ncsidev;
 	struct napi_struct napi;
-
+	struct work_struct reset_task;
 	struct mii_bus *mii_bus;
-	int old_speed;
-	int int_mask_all;
+
+	/* Link management */
+	int cur_speed;
+	int cur_duplex;
 	bool use_ncsi;
-	bool enabled;
+
+	/* Multicast filter settings */
+	u32 maht0;
+	u32 maht1;
+
+	/* Tells the reset task to skip  */
+	bool stopping;
+	bool need_mac_restart;
+
+	/* Flow control settings */
+	bool tx_pause;
+	bool rx_pause;
+	bool aneg_pause;
 
 	uint32_t rxdes0_edorr_mask;
 	uint32_t txdes0_edotr_mask;
 };
 
-static int ftgmac100_alloc_rx_page(struct ftgmac100 *priv,
-				   struct ftgmac100_rxdes *rxdes, gfp_t gfp);
-
-/******************************************************************************
- * internal functions (hardware register access)
- *****************************************************************************/
-static void ftgmac100_set_rx_ring_base(struct ftgmac100 *priv, dma_addr_t addr)
+static int ftgmac100_alloc_rx_buf(struct ftgmac100 *priv,
+				  unsigned int entry, gfp_t gfp)
 {
-	iowrite32(addr, priv->base + FTGMAC100_OFFSET_RXR_BADR);
-}
+	struct net_device *ndev = priv->ndev;
+	struct ftgmac100_rxdes *rxdes = &priv->rxdes[entry];
+	struct sk_buff *skb;
+	dma_addr_t map;
+	int err = 0;
 
-static void ftgmac100_set_rx_buffer_size(struct ftgmac100 *priv,
-		unsigned int size)
-{
-	size = FTGMAC100_RBSR_SIZE(size);
-	iowrite32(size, priv->base + FTGMAC100_OFFSET_RBSR);
-}
+	skb = netdev_alloc_skb_ip_align(ndev, RX_BUF_SIZE);
+	if (unlikely(!skb)) {
+		if (net_ratelimit())
+			netdev_err(ndev, "failed to allocate rx skb\n");
+		err = -ENOMEM;
+		map = priv->rx_scratch_dma;
+	} else {
+		map = dma_map_single(priv->dev, skb->data, RX_BUF_SIZE,
+				     DMA_FROM_DEVICE);
+		if (unlikely(dma_mapping_error(priv->dev, map))) {
+			if (net_ratelimit())
+				netdev_err(ndev, "failed to map rx page\n");
+			dev_kfree_skb_any(skb);
+			map = priv->rx_scratch_dma;
+			skb = NULL;
+			err = -ENOMEM;
+		}
+	}
 
-static void ftgmac100_set_normal_prio_tx_ring_base(struct ftgmac100 *priv,
-						   dma_addr_t addr)
-{
-	iowrite32(addr, priv->base + FTGMAC100_OFFSET_NPTXR_BADR);
-}
+	/* Store skb */
+	priv->rx_skbs[entry] = skb;
 
-static void ftgmac100_txdma_normal_prio_start_polling(struct ftgmac100 *priv)
-{
-	iowrite32(1, priv->base + FTGMAC100_OFFSET_NPTXPD);
+	/* Store DMA address into RX desc */
+	ftgmac100_rxdes_set_dma_addr(rxdes, map);
+
+	/* Ensure the above is ordered vs clearing the OWN bit */
+	dma_wmb();
+
+	/* Clean rxdes0 (which resets own bit) */
+	rxdes->rxdes0 &= cpu_to_le32(priv->rxdes0_edorr_mask);
+
+	return err;
 }
 
-static int ftgmac100_reset_hw(struct ftgmac100 *priv)
+static void ftgmac100_free_tx_packet(struct ftgmac100 *priv,
+				     unsigned int pointer,
+				     struct ftgmac100_txdes *txdes)
 {
-	struct net_device *netdev = priv->netdev;
-	int i;
+	struct sk_buff *skb = priv->tx_skbs[pointer];
+	dma_addr_t map = ftgmac100_txdes_get_dma_addr(txdes);
 
-	/* NOTE: reset clears all registers */
-	iowrite32(FTGMAC100_MACCR_SW_RST, priv->base + FTGMAC100_OFFSET_MACCR);
-	for (i = 0; i < 5; i++) {
-		unsigned int maccr;
+	if (ftgmac100_txdes_get_first_segment(txdes)) {
+		size_t len = skb_headlen(skb);
 
-		maccr = ioread32(priv->base + FTGMAC100_OFFSET_MACCR);
-		if (!(maccr & FTGMAC100_MACCR_SW_RST))
-			return 0;
+		if (skb_shinfo(skb)->nr_frags == 0 && len < ETH_ZLEN)
+			len = ETH_ZLEN;
+		dma_unmap_single(priv->dev, map, len, DMA_TO_DEVICE);
+	} else
+		dma_unmap_page(priv->dev, map,
+			       ftgmac100_txdes_get_buffer_size(txdes),
+			       DMA_TO_DEVICE);
 
-		udelay(1000);
-	}
-
-	netdev_err(netdev, "software reset failed\n");
-	return -EIO;
+	if (ftgmac100_txdes_get_last_segment(txdes))
+		dev_kfree_skb_any(skb);
+	priv->tx_skbs[pointer] = NULL;
 }
 
-static void ftgmac100_set_mac(struct ftgmac100 *priv, const unsigned char *mac)
+static int ftgmac100_next_rx_pointer(struct ftgmac100 *priv, int pointer)
 {
-	unsigned int maddr = mac[0] << 8 | mac[1];
-	unsigned int laddr = mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5];
-
-	iowrite32(maddr, priv->base + FTGMAC100_OFFSET_MAC_MADR);
-	iowrite32(laddr, priv->base + FTGMAC100_OFFSET_MAC_LADR);
+	return (pointer + 1) & (priv->rx_q_entries - 1);
 }
 
-static void ftgmac100_setup_mac(struct ftgmac100 *priv)
+static void ftgmac100_rx_packet_error(struct ftgmac100 *priv,
+				      struct ftgmac100_rxdes *rxdes)
 {
-	u8 mac[ETH_ALEN];
-	unsigned int m;
-	unsigned int l;
-	void *addr;
+	struct net_device *ndev = priv->ndev;
 
-	addr = device_get_mac_address(priv->dev, mac, ETH_ALEN);
-	if (addr) {
-		ether_addr_copy(priv->netdev->dev_addr, mac);
-		dev_info(priv->dev, "Read MAC address %pM from device tree\n",
-			 mac);
-		return;
-	}
+	if (unlikely(ftgmac100_rxdes_rx_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(ndev, "rx err\n");
 
-	m = ioread32(priv->base + FTGMAC100_OFFSET_MAC_MADR);
-	l = ioread32(priv->base + FTGMAC100_OFFSET_MAC_LADR);
+		ndev->stats.rx_errors++;
+	}
 
-	mac[0] = (m >> 8) & 0xff;
-	mac[1] = m & 0xff;
-	mac[2] = (l >> 24) & 0xff;
-	mac[3] = (l >> 16) & 0xff;
-	mac[4] = (l >> 8) & 0xff;
-	mac[5] = l & 0xff;
+	if (unlikely(ftgmac100_rxdes_crc_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(ndev, "rx crc err\n");
 
-	if (is_valid_ether_addr(mac)) {
-		ether_addr_copy(priv->netdev->dev_addr, mac);
-		dev_info(priv->dev, "Read MAC address %pM from chip\n", mac);
-	} else {
-		eth_hw_addr_random(priv->netdev);
-		dev_info(priv->dev, "Generated random MAC address %pM\n",
-			 priv->netdev->dev_addr);
+		ndev->stats.rx_crc_errors++;
 	}
-}
 
-static int ftgmac100_set_mac_addr(struct net_device *dev, void *p)
-{
-	int ret;
+	if (unlikely(ftgmac100_rxdes_frame_too_long(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(ndev, "rx frame too long\n");
 
-	ret = eth_prepare_mac_addr_change(dev, p);
-	if (ret < 0)
-		return ret;
+		ndev->stats.rx_length_errors++;
+	} else if (unlikely(ftgmac100_rxdes_runt(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(ndev, "rx runt\n");
 
-	eth_commit_mac_addr_change(dev, p);
-	ftgmac100_set_mac(netdev_priv(dev), dev->dev_addr);
+		ndev->stats.rx_length_errors++;
+	} else if (unlikely(ftgmac100_rxdes_odd_nibble(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(ndev, "rx odd nibble\n");
 
-	return 0;
+		ndev->stats.rx_length_errors++;
+	}
 }
 
-static void ftgmac100_init_hw(struct ftgmac100 *priv)
+static bool ftgmac100_rx_packet(struct ftgmac100 *priv, int *processed)
 {
-	/* setup ring buffer base registers */
-	ftgmac100_set_rx_ring_base(priv,
-				   priv->descs_dma_addr +
-				   offsetof(struct ftgmac100_descs, rxdes));
-	ftgmac100_set_normal_prio_tx_ring_base(priv,
-					       priv->descs_dma_addr +
-					       offsetof(struct ftgmac100_descs, txdes));
+	struct net_device *ndev = priv->ndev;
+	struct ftgmac100_rxdes *rxdes;
+	struct sk_buff *skb;
+	unsigned int pointer, size;
+	dma_addr_t map;
 
-	ftgmac100_set_rx_buffer_size(priv, RX_BUF_SIZE);
+	/* Grab next RX descriptor */
+	pointer = priv->rx_pointer;
+	rxdes = &priv->rxdes[pointer];
 
-	iowrite32(FTGMAC100_APTC_RXPOLL_CNT(1), priv->base + FTGMAC100_OFFSET_APTC);
+	/* Do we have a packet ? */
+	if (!ftgmac100_rxdes_packet_ready(rxdes))
+		return false;
 
-	ftgmac100_set_mac(priv, priv->netdev->dev_addr);
-}
+	/* We don't cope with fragmented RX packets */
+	if (unlikely(!ftgmac100_rxdes_first_segment(rxdes) ||
+		     !ftgmac100_rxdes_last_segment(rxdes)))
+		goto drop;
 
-#define MACCR_ENABLE_ALL	(FTGMAC100_MACCR_TXDMA_EN	| \
-				 FTGMAC100_MACCR_RXDMA_EN	| \
-				 FTGMAC100_MACCR_TXMAC_EN	| \
-				 FTGMAC100_MACCR_RXMAC_EN	| \
-				 FTGMAC100_MACCR_FULLDUP	| \
-				 FTGMAC100_MACCR_CRC_APD	| \
-				 FTGMAC100_MACCR_PHY_LINK_LEVEL | \
-				 FTGMAC100_MACCR_RX_RUNT	| \
-				 FTGMAC100_MACCR_RX_BROADPKT)
-
-static void ftgmac100_start_hw(struct ftgmac100 *priv, int speed)
-{
-	int maccr = MACCR_ENABLE_ALL;
+	/* Any error (other than csum offload) flagged ? */
+	if (unlikely(ftgmac100_rxdes_any_error(rxdes))) {
+		ftgmac100_rx_packet_error(priv, rxdes);
+		goto drop;
+	}
 
-	switch (speed) {
-	default:
-	case 10:
-		break;
+	/* Grab the corresponding skb */
+	skb = priv->rx_skbs[pointer];
+	if (unlikely(!skb)) {
+		netdev_err(ndev, "Missing skb in rx ring !\n");
+		goto drop;
+	}
 
-	case 100:
-		maccr |= FTGMAC100_MACCR_FAST_MODE;
-		break;
+	/* Grab received size */
+	size = ftgmac100_rxdes_data_length(rxdes);
+	skb_put(skb, size);
 
-	case 1000:
-		maccr |= FTGMAC100_MACCR_GIGA_MODE;
-		break;
-	}
+	/* Tear down DMA mapping, do necessary cache management */
+	map = ftgmac100_rxdes_get_dma_addr(rxdes);
 
-	iowrite32(maccr, priv->base + FTGMAC100_OFFSET_MACCR);
-}
+#if defined(CONFIG_ARM) && !defined(CONFIG_ARM_DMA_USE_IOMMU)
+	/*
+	 * When we don't have an iommu, we can save cycles by not
+	 * invalidating the cache for the part of the packet that
+	 * wasn't received.
+	 */
+	dma_unmap_single(priv->dev, map, size, DMA_FROM_DEVICE);
+#else
+	dma_unmap_single(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
+#endif
+
+	/* Grab protocol and handle rx csum */
+	skb->protocol = eth_type_trans(skb, ndev);
+	if ((ndev->features & NETIF_F_RXCSUM) &&
+	    !ftgmac100_rxdes_csum_err(rxdes))
+		skb->ip_summed = CHECKSUM_UNNECESSARY;
+	else
+		skb->ip_summed = CHECKSUM_NONE;
 
-static void ftgmac100_stop_hw(struct ftgmac100 *priv)
-{
-	iowrite32(0, priv->base + FTGMAC100_OFFSET_MACCR);
-}
+	/* Some stats ... */
+	if (unlikely(ftgmac100_rxdes_multicast(rxdes)))
+		ndev->stats.multicast++;
+	ndev->stats.rx_packets++;
+	ndev->stats.rx_bytes += size;
 
-/******************************************************************************
- * internal functions (receive descriptor)
- *****************************************************************************/
-static bool ftgmac100_rxdes_first_segment(struct ftgmac100_rxdes *rxdes)
-{
-	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_FRS);
-}
+	/* Resplenish rx ring */
+	ftgmac100_alloc_rx_buf(priv, pointer, GFP_ATOMIC);
+	priv->rx_pointer = ftgmac100_next_rx_pointer(priv, pointer);
 
-static bool ftgmac100_rxdes_last_segment(struct ftgmac100_rxdes *rxdes)
-{
-	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_LRS);
-}
+	/* push packet to protocol stack */
+	if (skb->ip_summed == CHECKSUM_NONE)
+		netif_receive_skb(skb);
+	else
+		napi_gro_receive(&priv->napi, skb);
 
-static bool ftgmac100_rxdes_packet_ready(struct ftgmac100_rxdes *rxdes)
-{
-	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_RXPKT_RDY);
-}
+	(*processed)++;
+	return true;
 
-static void ftgmac100_rxdes_set_dma_own(const struct ftgmac100 *priv,
-		struct ftgmac100_rxdes *rxdes)
-{
-	/* clear status bits */
+ drop:
+	/* Clean rxdes0 (which resets own bit) */
 	rxdes->rxdes0 &= cpu_to_le32(priv->rxdes0_edorr_mask);
+	priv->rx_pointer = ftgmac100_next_rx_pointer(priv, priv->rx_pointer);
+	ndev->stats.rx_dropped++;
+	return true;
 }
 
-static bool ftgmac100_rxdes_rx_error(struct ftgmac100_rxdes *rxdes)
+static int ftgmac100_next_tx_pointer(struct ftgmac100 *priv, int pointer)
 {
-	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_RX_ERR);
+	return (pointer + 1) & (priv->tx_q_entries - 1);
 }
 
-static bool ftgmac100_rxdes_crc_error(struct ftgmac100_rxdes *rxdes)
+static u32 ftgmac100_tx_buf_avail(struct ftgmac100 *priv)
 {
-	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_CRC_ERR);
+	if (priv->tx_clean_pointer <= priv->tx_pointer)
+		return priv->tx_clean_pointer + (priv->tx_q_entries - 1)
+			- priv->tx_pointer;
+	else
+		return priv->tx_clean_pointer - priv->tx_pointer - 1;
 }
 
-static bool ftgmac100_rxdes_frame_too_long(struct ftgmac100_rxdes *rxdes)
+static bool ftgmac100_tx_buf_cleanable(struct ftgmac100 *priv)
 {
-	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_FTL);
+	return priv->tx_pointer != priv->tx_clean_pointer;
 }
 
-static bool ftgmac100_rxdes_runt(struct ftgmac100_rxdes *rxdes)
+static bool ftgmac100_tx_complete_packet(struct ftgmac100 *priv)
 {
-	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_RUNT);
-}
+	struct net_device *ndev = priv->ndev;
+	struct ftgmac100_txdes *txdes;
+	struct sk_buff *skb;
+	unsigned int pointer = priv->tx_clean_pointer;
 
-static bool ftgmac100_rxdes_odd_nibble(struct ftgmac100_rxdes *rxdes)
-{
-	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_RX_ODD_NB);
-}
+	txdes = &priv->txdes[pointer];
+	if (ftgmac100_txdes_owned_by_dma(txdes))
+		return false;
 
-static unsigned int ftgmac100_rxdes_data_length(struct ftgmac100_rxdes *rxdes)
-{
-	return le32_to_cpu(rxdes->rxdes0) & FTGMAC100_RXDES0_VDBC;
-}
+	if (ftgmac100_txdes_get_last_segment(txdes)) {
+		skb = priv->tx_skbs[pointer];
+		ndev->stats.tx_packets++;
+		ndev->stats.tx_bytes += skb->len;
+	}
+	ftgmac100_free_tx_packet(priv, priv->tx_clean_pointer, txdes);
 
-static bool ftgmac100_rxdes_multicast(struct ftgmac100_rxdes *rxdes)
-{
-	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_MULTICAST);
-}
+	/* Clear except end of ring bit */
+	txdes->txdes0 &= cpu_to_le32(priv->txdes0_edotr_mask);
+	txdes->txdes1 = 0;
 
-static void ftgmac100_rxdes_set_end_of_ring(const struct ftgmac100 *priv,
-		struct ftgmac100_rxdes *rxdes)
-{
-	rxdes->rxdes0 |= cpu_to_le32(priv->rxdes0_edorr_mask);
-}
+	priv->tx_clean_pointer = ftgmac100_next_tx_pointer(priv, pointer);
 
-static void ftgmac100_rxdes_set_dma_addr(struct ftgmac100_rxdes *rxdes,
-					 dma_addr_t addr)
-{
-	rxdes->rxdes3 = cpu_to_le32(addr);
+	return true;
 }
 
-static dma_addr_t ftgmac100_rxdes_get_dma_addr(struct ftgmac100_rxdes *rxdes)
+static irqreturn_t ftgmac100_interrupt(int irq __always_unused, void *dev_id)
 {
-	return le32_to_cpu(rxdes->rxdes3);
-}
+	struct net_device *ndev = dev_id;
+	struct ftgmac100 *priv = netdev_priv(ndev);
+	unsigned int status;
 
-static bool ftgmac100_rxdes_is_tcp(struct ftgmac100_rxdes *rxdes)
-{
-	return (rxdes->rxdes1 & cpu_to_le32(FTGMAC100_RXDES1_PROT_MASK)) ==
-	       cpu_to_le32(FTGMAC100_RXDES1_PROT_TCPIP);
-}
+	/* Fetch and clear interrupt bits, process abnormal ones */
+	status = ioread32(priv->base + FTGMAC100_OFFSET_ISR);
+	iowrite32(status, priv->base + FTGMAC100_OFFSET_ISR);
+	if (unlikely(status & FTGMAC100_INT_BAD)) {
+		if (net_ratelimit())
+			netdev_warn(ndev, "[ISR] = 0x%x: %s%s%s\n", status,
+				    status & FTGMAC100_INT_NO_RXBUF ? "NO_RXBUF " : "",
+				    status & FTGMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "",
+				    status & FTGMAC100_INT_AHB_ERR ? "AHB_ERR " : "");
 
-static bool ftgmac100_rxdes_is_udp(struct ftgmac100_rxdes *rxdes)
-{
-	return (rxdes->rxdes1 & cpu_to_le32(FTGMAC100_RXDES1_PROT_MASK)) ==
-	       cpu_to_le32(FTGMAC100_RXDES1_PROT_UDPIP);
-}
+		/* RX buffer unavailable */
+		if (status & FTGMAC100_INT_NO_RXBUF)
+			ndev->stats.rx_over_errors++;
 
-static bool ftgmac100_rxdes_tcpcs_err(struct ftgmac100_rxdes *rxdes)
-{
-	return rxdes->rxdes1 & cpu_to_le32(FTGMAC100_RXDES1_TCP_CHKSUM_ERR);
-}
+		/* Received packet lost due to RX FIFO full */
+		if (status & FTGMAC100_INT_RPKT_LOST)
+			ndev->stats.rx_fifo_errors++;
 
-static bool ftgmac100_rxdes_udpcs_err(struct ftgmac100_rxdes *rxdes)
-{
-	return rxdes->rxdes1 & cpu_to_le32(FTGMAC100_RXDES1_UDP_CHKSUM_ERR);
-}
+		/* AHB error -> Reset the chip */
+		if (status & FTGMAC100_INT_AHB_ERR) {
+			iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
+			schedule_work(&priv->reset_task);
+			return IRQ_HANDLED;
+		}
 
-static bool ftgmac100_rxdes_ipcs_err(struct ftgmac100_rxdes *rxdes)
-{
-	return rxdes->rxdes1 & cpu_to_le32(FTGMAC100_RXDES1_IP_CHKSUM_ERR);
-}
+		/*
+		 * We may need to restart the MAC after such errors, delay
+		 * this until after we have freed some Rx buffers though
+		 */
+		priv->need_mac_restart = true;
+	}
 
-static inline struct page **ftgmac100_rxdes_page_slot(struct ftgmac100 *priv,
-		struct ftgmac100_rxdes *rxdes)
-{
-	return &priv->rx_pages[rxdes - priv->descs->rxdes];
-}
 
-/*
- * rxdes2 is not used by hardware. We use it to keep track of page.
- * Since hardware does not touch it, we can skip cpu_to_le32()/le32_to_cpu().
- */
-static void ftgmac100_rxdes_set_page(struct ftgmac100 *priv,
-		struct ftgmac100_rxdes *rxdes, struct page *page)
-{
-	*ftgmac100_rxdes_page_slot(priv, rxdes) = page;
-}
+	/* Only enable "bad" interrupts while NAPI is on */
+	iowrite32(FTGMAC100_INT_BAD, priv->base + FTGMAC100_OFFSET_IER);
 
-static struct page *ftgmac100_rxdes_get_page(struct ftgmac100 *priv,
-		struct ftgmac100_rxdes *rxdes)
-{
-	return *ftgmac100_rxdes_page_slot(priv, rxdes);
-}
+	/* Schedule NAPI bh */
+	napi_schedule(&priv->napi);
 
-/******************************************************************************
- * internal functions (receive)
- *****************************************************************************/
-static int ftgmac100_next_rx_pointer(int pointer)
-{
-	return (pointer + 1) & (RX_QUEUE_ENTRIES - 1);
+	return IRQ_HANDLED;
 }
 
-static void ftgmac100_rx_pointer_advance(struct ftgmac100 *priv)
+static int ftgmac100_hard_start_xmit(struct sk_buff *skb,
+				     struct net_device *ndev)
 {
-	priv->rx_pointer = ftgmac100_next_rx_pointer(priv->rx_pointer);
-}
+	struct ftgmac100 *priv = netdev_priv(ndev);
+	struct ftgmac100_txdes *txdes, *first;
+	int nfrags;
+	int pointer, len, i, j;
+	dma_addr_t map;
 
-static struct ftgmac100_rxdes *ftgmac100_current_rxdes(struct ftgmac100 *priv)
-{
-	return &priv->descs->rxdes[priv->rx_pointer];
-}
+	if (unlikely(skb->len > MAX_PKT_SIZE)) {
+		if (net_ratelimit())
+			netdev_dbg(ndev, "tx packet too big\n");
+		goto drop;
+	}
 
-static struct ftgmac100_rxdes *
-ftgmac100_rx_locate_first_segment(struct ftgmac100 *priv)
-{
-	struct ftgmac100_rxdes *rxdes = ftgmac100_current_rxdes(priv);
+	/* The HW doesn't pad small frames */
+	if (skb_padto(skb, ETH_ZLEN) < 0) {
+		ndev->stats.tx_dropped ++;
+		return NETDEV_TX_OK;
+	}
 
-	while (ftgmac100_rxdes_packet_ready(rxdes)) {
-		if (ftgmac100_rxdes_first_segment(rxdes))
-			return rxdes;
+	/* XXX Do we have a limit on #fragments ? */
+	nfrags = skb_shinfo(skb)->nr_frags;
 
-		ftgmac100_rxdes_set_dma_own(priv, rxdes);
-		ftgmac100_rx_pointer_advance(priv);
-		rxdes = ftgmac100_current_rxdes(priv);
+	/* Get header len and pad for non-fragmented packets */
+	len = skb_headlen(skb);
+	if (nfrags == 0 && len < ETH_ZLEN)
+		len = ETH_ZLEN;
+
+	/* Map the packet head */
+	map = dma_map_single(priv->dev, skb->data, len, DMA_TO_DEVICE);
+	if (dma_mapping_error(priv->dev, map)) {
+		if (net_ratelimit())
+			netdev_err(ndev, "map tx packet head failed\n");
+		goto drop;
 	}
 
-	return NULL;
-}
+	/* Grab the next free tx descriptor */
+	pointer = priv->tx_pointer;
+	txdes = first = &priv->txdes[pointer];
 
-static bool ftgmac100_rx_packet_error(struct ftgmac100 *priv,
-				      struct ftgmac100_rxdes *rxdes)
-{
-	struct net_device *netdev = priv->netdev;
-	bool error = false;
+	/* Setup it up. We don't set the OWN bit yet. */
+	priv->tx_skbs[pointer] = skb;
+	ftgmac100_txdes_set_dma_addr(txdes, map);
+	ftgmac100_txdes_set_buffer_size(txdes, len);
+	ftgmac100_txdes_set_first_segment(txdes);
 
-	if (unlikely(ftgmac100_rxdes_rx_error(rxdes))) {
-		if (net_ratelimit())
-			netdev_info(netdev, "rx err\n");
+	/* Setup HW checksumming */
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		__be16 protocol = skb->protocol;
 
-		netdev->stats.rx_errors++;
-		error = true;
+		if (protocol == cpu_to_be16(ETH_P_IP)) {
+			u8 ip_proto = ip_hdr(skb)->protocol;
+
+			ftgmac100_txdes_set_ipcs(txdes);
+			if (ip_proto == IPPROTO_TCP)
+				ftgmac100_txdes_set_tcpcs(txdes);
+			else if (ip_proto == IPPROTO_UDP)
+				ftgmac100_txdes_set_udpcs(txdes);
+		} else if (skb_checksum_help(skb))
+			goto drop;
 	}
 
-	if (unlikely(ftgmac100_rxdes_crc_error(rxdes))) {
-		if (net_ratelimit())
-			netdev_info(netdev, "rx crc err\n");
+	/* Next descriptor */
+	pointer = ftgmac100_next_tx_pointer(priv, pointer);
 
-		netdev->stats.rx_crc_errors++;
-		error = true;
-	} else if (unlikely(ftgmac100_rxdes_ipcs_err(rxdes))) {
-		if (net_ratelimit())
-			netdev_info(netdev, "rx IP checksum err\n");
+	/* Add the fragments */
+	for (i = 0; i < nfrags; i++) {
+		skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+		len = frag->size;
 
-		error = true;
+		/* Map it */
+		map = skb_frag_dma_map(priv->dev, frag, 0, len, DMA_TO_DEVICE);
+		if (dma_mapping_error(priv->dev, map))
+			goto dma_err;
+
+		/* Setup descriptor */
+		priv->tx_skbs[pointer] = skb;
+		txdes = &priv->txdes[pointer];
+		ftgmac100_txdes_set_dma_addr(txdes, map);
+		ftgmac100_txdes_set_buffer_size(txdes, len);
+		ftgmac100_txdes_set_dma_own(txdes);
+
+		/*
+		 * The spec is unclear, whether these need to be in the
+		 * first descriptor only or all of them. Gor now, do all
+		 * of them.
+		 */
+#define CSUM_MASK cpu_to_le32(FTGMAC100_TXDES1_TCP_CHKSUM | \
+			      FTGMAC100_TXDES1_UDP_CHKSUM | \
+			      FTGMAC100_TXDES1_IP_CHKSUM)
+		txdes->txdes1 |= first->txdes1 & CSUM_MASK;
+		pointer = ftgmac100_next_tx_pointer(priv, pointer);
 	}
 
-	if (unlikely(ftgmac100_rxdes_frame_too_long(rxdes))) {
-		if (net_ratelimit())
-			netdev_info(netdev, "rx frame too long\n");
+	/* Tag last fragment */
+	ftgmac100_txdes_set_last_segment(txdes);
 
-		netdev->stats.rx_length_errors++;
-		error = true;
-	} else if (unlikely(ftgmac100_rxdes_runt(rxdes))) {
-		if (net_ratelimit())
-			netdev_info(netdev, "rx runt\n");
+	/*
+	 * Set the own bit on the first descriptor, this can cause the
+	 * HW to transmit, it needs to be ordered after all previous
+	 * stores.
+	 */
+	dma_wmb();
+	ftgmac100_txdes_set_dma_own(first);
 
-		netdev->stats.rx_length_errors++;
-		error = true;
-	} else if (unlikely(ftgmac100_rxdes_odd_nibble(rxdes))) {
-		if (net_ratelimit())
-			netdev_info(netdev, "rx odd nibble\n");
+	/* Update next TX pointer */
+	priv->tx_pointer = pointer;
 
-		netdev->stats.rx_length_errors++;
-		error = true;
+	/*
+	 * If there isn't enough room for all the fragments of a new packet
+	 * in the TX ring, stop the queue. The sequence below is race free
+	 * vs. a concurrent restart in ftgmac100_poll()
+	 */
+	if (unlikely(ftgmac100_tx_buf_avail(priv) <= (MAX_SKB_FRAGS + 1))) {
+		netif_stop_queue(ndev);
+		smp_mb();
+		if (ftgmac100_tx_buf_avail(priv) > (MAX_SKB_FRAGS + 1))
+			netif_wake_queue(ndev);
 	}
 
-	return error;
-}
+	/* Poke transmitter to read the updated TX descriptors */
+	iowrite32(1, priv->base + FTGMAC100_OFFSET_NPTXPD);
 
-static void ftgmac100_rx_drop_packet(struct ftgmac100 *priv)
-{
-	struct net_device *netdev = priv->netdev;
-	struct ftgmac100_rxdes *rxdes = ftgmac100_current_rxdes(priv);
-	bool done = false;
+	return NETDEV_TX_OK;
 
+ dma_err:
 	if (net_ratelimit())
-		netdev_dbg(netdev, "drop packet %p\n", rxdes);
+		netdev_dbg(ndev, "map tx fragment failed\n");
 
-	do {
-		if (ftgmac100_rxdes_last_segment(rxdes))
-			done = true;
+	/* Free head */
+	pointer = priv->tx_pointer;
+	ftgmac100_free_tx_packet(priv, pointer, first);
 
-		ftgmac100_rxdes_set_dma_own(priv, rxdes);
-		ftgmac100_rx_pointer_advance(priv);
-		rxdes = ftgmac100_current_rxdes(priv);
-	} while (!done && ftgmac100_rxdes_packet_ready(rxdes));
+	/* Then all fragments */
+	for (j = 0; j < i; j++) {
+		pointer = ftgmac100_next_tx_pointer(priv, pointer);
+		txdes = &priv->txdes[pointer];
+		ftgmac100_free_tx_packet(priv, pointer, txdes);
+	}
 
-	netdev->stats.rx_dropped++;
+	/*
+	 * This cannot be reached if we successfully mapped the
+	 * last fragment, so we know ftgmac100_free_tx_packet()
+	 * hasn't freed the skb yet.
+	 */
+ drop:
+	/* Drop the packet */
+	dev_kfree_skb_any(skb);
+	ndev->stats.tx_dropped++;
+
+	return NETDEV_TX_OK;
 }
 
-static bool ftgmac100_rx_packet(struct ftgmac100 *priv, int *processed)
+static void ftgmac100_start_mac(struct ftgmac100 *priv)
 {
-	struct net_device *netdev = priv->netdev;
-	struct ftgmac100_rxdes *rxdes;
-	struct sk_buff *skb;
-	bool done = false;
+	u32 maccr = ioread32(priv->base + FTGMAC100_OFFSET_MACCR);
 
-	rxdes = ftgmac100_rx_locate_first_segment(priv);
-	if (!rxdes)
-		return false;
+	priv->need_mac_restart = false;
 
-	if (unlikely(ftgmac100_rx_packet_error(priv, rxdes))) {
-		ftgmac100_rx_drop_packet(priv);
-		return true;
-	}
+	/* Keep the original GMAC and FAST bits */
+	maccr &= (FTGMAC100_MACCR_FAST_MODE | FTGMAC100_MACCR_GIGA_MODE);
 
-	/* start processing */
-	skb = netdev_alloc_skb_ip_align(netdev, 128);
-	if (unlikely(!skb)) {
-		if (net_ratelimit())
-			netdev_err(netdev, "rx skb alloc failed\n");
+	/* Add all the main enable bits */
+	maccr |= FTGMAC100_MACCR_TXDMA_EN	|
+		 FTGMAC100_MACCR_RXDMA_EN	|
+		 FTGMAC100_MACCR_TXMAC_EN	|
+		 FTGMAC100_MACCR_RXMAC_EN	|
+		 FTGMAC100_MACCR_CRC_APD	|
+		 FTGMAC100_MACCR_PHY_LINK_LEVEL	|
+		 FTGMAC100_MACCR_RX_RUNT	|
+		 FTGMAC100_MACCR_RX_BROADPKT;
+
+	/* Add other bits as needed */
+	if (priv->cur_duplex == DUPLEX_FULL)
+		maccr |= FTGMAC100_MACCR_FULLDUP;
+	if (priv->ndev->flags & IFF_PROMISC)
+		maccr |= FTGMAC100_MACCR_RX_ALL;
+	if (priv->ndev->flags & IFF_ALLMULTI)
+		maccr |= FTGMAC100_MACCR_RX_MULTIPKT;
+	else if (netdev_mc_count(priv->ndev))
+		maccr |= FTGMAC100_MACCR_HT_MULTI_EN;
+
+	/* Hit the HW */
+	iowrite32(maccr, priv->base + FTGMAC100_OFFSET_MACCR);
+}
+
+static void ftgmac100_stop_mac(struct ftgmac100 *priv)
+{
+	iowrite32(0, priv->base + FTGMAC100_OFFSET_MACCR);
+}
 
-		ftgmac100_rx_drop_packet(priv);
-		return true;
+static int ftgmac100_poll(struct napi_struct *napi, int budget)
+{
+	struct ftgmac100 *priv = container_of(napi, struct ftgmac100, napi);
+	struct net_device *ndev = priv->ndev;
+	int work_done = 0;
+	bool more;
+
+	/* Handle Tx packet reclaim */
+	if (ftgmac100_tx_buf_cleanable(priv)) {
+		while(ftgmac100_tx_buf_cleanable(priv) &&
+		      ftgmac100_tx_complete_packet(priv))
+			;
+		/* Restart queue if needed */
+		smp_mb();
+		if (unlikely(netif_queue_stopped(ndev) &&
+			     ftgmac100_tx_buf_avail(priv) > (MAX_SKB_FRAGS + 1))) {
+			struct netdev_queue *txq = netdev_get_tx_queue(ndev, 0);
+			__netif_tx_lock(txq, smp_processor_id());
+			if (netif_queue_stopped(ndev) &&
+			    ftgmac100_tx_buf_avail(priv) > (MAX_SKB_FRAGS + 1))
+				netif_wake_queue(ndev);
+			__netif_tx_unlock(txq);
+		}
 	}
 
-	if (unlikely(ftgmac100_rxdes_multicast(rxdes)))
-		netdev->stats.multicast++;
+	/* Handle RX packets */
+	do
+		more = ftgmac100_rx_packet(priv, &work_done);
+	while (more && work_done < budget);
 
 	/*
-	 * It seems that HW does checksum incorrectly with fragmented packets,
-	 * so we are conservative here - if HW checksum error, let software do
-	 * the checksum again.
+	 * The interrupt is telling us to kick the MAC back to life
+	 * after an RX overflow
 	 */
-	if ((ftgmac100_rxdes_is_tcp(rxdes) && !ftgmac100_rxdes_tcpcs_err(rxdes)) ||
-	    (ftgmac100_rxdes_is_udp(rxdes) && !ftgmac100_rxdes_udpcs_err(rxdes)))
-		skb->ip_summed = CHECKSUM_UNNECESSARY;
-
-	do {
-		dma_addr_t map = ftgmac100_rxdes_get_dma_addr(rxdes);
-		struct page *page = ftgmac100_rxdes_get_page(priv, rxdes);
-		unsigned int size;
+	if (unlikely(priv->need_mac_restart))
+		ftgmac100_start_mac(priv);
 
-		dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
+	/*
+	 * As long as we are waiting for transmit packets to be
+	 * completed we keep NAPI going
+	 */
+	if (ftgmac100_tx_buf_cleanable(priv))
+		work_done = budget;
 
-		size = ftgmac100_rxdes_data_length(rxdes);
-		skb_fill_page_desc(skb, skb_shinfo(skb)->nr_frags, page, 0, size);
+	/* Are we done ? */
+	if (work_done < budget) {
+		/* NAPI's over for now */
+		napi_complete(napi);
 
-		skb->len += size;
-		skb->data_len += size;
-		skb->truesize += PAGE_SIZE;
+		/* Enable all interrupts */
+		iowrite32(FTGMAC100_INT_ALL, priv->base + FTGMAC100_OFFSET_IER);
+	}
 
-		if (ftgmac100_rxdes_last_segment(rxdes))
-			done = true;
+	return work_done;
+}
 
-		ftgmac100_alloc_rx_page(priv, rxdes, GFP_ATOMIC);
+static void ftgmac100_write_mac_addr(struct ftgmac100 *priv, const u8 *mac)
+{
+	unsigned int maddr = mac[0] << 8 | mac[1];
+	unsigned int laddr = mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5];
 
-		ftgmac100_rx_pointer_advance(priv);
-		rxdes = ftgmac100_current_rxdes(priv);
-	} while (!done);
+	iowrite32(maddr, priv->base + FTGMAC100_OFFSET_MAC_MADR);
+	iowrite32(laddr, priv->base + FTGMAC100_OFFSET_MAC_LADR);
+}
 
-	/* Small frames are copied into linear part of skb to free one page */
-	if (skb->len <= 128) {
-		skb->truesize -= PAGE_SIZE;
-		__pskb_pull_tail(skb, skb->len);
-	} else {
-		/* We pull the minimum amount into linear part */
-		__pskb_pull_tail(skb, ETH_HLEN);
-	}
-	skb->protocol = eth_type_trans(skb, netdev);
+static void ftgmac100_config_pause(struct ftgmac100 *priv)
+{
+	u32 fcr = FTGMAC100_FCR_PAUSE_TIME(16);
 
-	netdev->stats.rx_packets++;
-	netdev->stats.rx_bytes += skb->len;
+	/*
+	 * Throttle tx queue when receiving pause frames.
+	 * XXX Double check with HW vendor the HW bits.
+	 */
+	if (priv->rx_pause)
+		fcr |= FTGMAC100_FCR_FC_EN;
 
-	/* push packet to protocol stack */
-	napi_gro_receive(&priv->napi, skb);
+	/*
+	 * Enables sending pause frames when the RX queue is past a
+	 * certain threshold.
+	 * XXX Double check the HW thresholds config...
+	 */
+	if (priv->tx_pause)
+		fcr |= FTGMAC100_FCR_FCTHR_EN;
 
-	(*processed)++;
-	return true;
+	iowrite32(fcr, priv->base + FTGMAC100_OFFSET_FCR);
 }
 
-/******************************************************************************
- * internal functions (transmit descriptor)
- *****************************************************************************/
-static void ftgmac100_txdes_reset(const struct ftgmac100 *priv, struct ftgmac100_txdes *txdes)
+static void ftgmac100_init_hw(struct ftgmac100 *priv)
 {
-	/* clear all except end of ring bit */
-	txdes->txdes0 &= cpu_to_le32(priv->txdes0_edotr_mask);
-	txdes->txdes1 = 0;
-	txdes->txdes2 = 0;
-	txdes->txdes3 = 0;
-}
+	u32 reg, rfifo_sz, tfifo_sz;
 
-static bool ftgmac100_txdes_owned_by_dma(struct ftgmac100_txdes *txdes)
-{
-	return txdes->txdes0 & cpu_to_le32(FTGMAC100_TXDES0_TXDMA_OWN);
-}
+	/* Clear stale interrupts */
+	reg = ioread32(priv->base + FTGMAC100_OFFSET_ISR);
+	iowrite32(reg, priv->base + FTGMAC100_OFFSET_ISR);
+
+	/* Setup RX ring buffer base */
+	iowrite32(priv->rxdes_dma, priv->base + FTGMAC100_OFFSET_RXR_BADR);
+
+	/* Setup TX ring buffer base */
+	iowrite32(priv->txdes_dma, priv->base + FTGMAC100_OFFSET_NPTXR_BADR);
+
+	/* Configure RX buffer size */
+	iowrite32(FTGMAC100_RBSR_SIZE(RX_BUF_SIZE),
+		  priv->base + FTGMAC100_OFFSET_RBSR);
+
+	/* Set RX descriptor autopoll */
+	iowrite32(FTGMAC100_APTC_RXPOLL_CNT(1),
+		  priv->base + FTGMAC100_OFFSET_APTC);
 
-static void ftgmac100_txdes_set_dma_own(struct ftgmac100_txdes *txdes)
-{
 	/*
-	 * Make sure dma own bit will not be set before any other
-	 * descriptor fields.
+	 * Configure descriptor sizes and increase burst sizes according
+	 * to values in Aspeed SDK. The FIFO arbitration is enabled and
+	 * the thresholds set based on the recommended values in the
+	 * AST2400 specification.
 	 */
-	wmb();
-	txdes->txdes0 |= cpu_to_le32(FTGMAC100_TXDES0_TXDMA_OWN);
-}
+	iowrite32(FTGMAC100_DBLAC_RXDES_SIZE(2) |   /* 2*8 bytes RX descs */
+		  FTGMAC100_DBLAC_TXDES_SIZE(2) |   /* 2*8 bytes TX descs */
+		  FTGMAC100_DBLAC_RXBURST_SIZE(3) | /* 512 bytes max RX bursts */
+		  FTGMAC100_DBLAC_TXBURST_SIZE(3) | /* 512 bytes max TX bursts */
+		  FTGMAC100_DBLAC_RX_THR_EN |       /* Enable fifo threshold arb */
+		  FTGMAC100_DBLAC_RXFIFO_HTHR(6) |  /* 6/8 of FIFO high threshold */
+		  FTGMAC100_DBLAC_RXFIFO_LTHR(2),   /* 2/8 of FIFO low threshold */
+		  priv->base + FTGMAC100_OFFSET_DBLAC);
 
-static void ftgmac100_txdes_set_end_of_ring(const struct ftgmac100 *priv,
-		struct ftgmac100_txdes *txdes)
-{
-	txdes->txdes0 |= cpu_to_le32(priv->txdes0_edotr_mask);
+	/*
+	 * Interrupt mitigation configured for 1 interrupt/packet. HW interrupt
+	 * mitigation doesn't seem to provide any benefit with NAPI so leave
+	 * it at that.
+	 */
+	iowrite32(FTGMAC100_ITC_RXINT_THR(1) |
+		  FTGMAC100_ITC_TXINT_THR(1),
+		  priv->base + FTGMAC100_OFFSET_ITC);
+
+	/* Configure FIFO sizes in the TPAFCR register */
+	reg = ioread32(priv->base + FTGMAC100_OFFSET_FEAR);
+	rfifo_sz = reg & 0x00000007;
+	tfifo_sz = (reg >> 3) & 0x00000007;
+	reg = ioread32(priv->base + FTGMAC100_OFFSET_TPAFCR);
+	reg &= ~0x3f000000;
+	reg |= (tfifo_sz << 27);
+	reg |= (rfifo_sz << 24);
+	iowrite32(reg, priv->base + FTGMAC100_OFFSET_TPAFCR);
+
+	/* Write MAC address */
+	ftgmac100_write_mac_addr(priv, priv->ndev->dev_addr);
+
+	/* Write multicast filters */
+	iowrite32(priv->maht0, priv->base + FTGMAC100_OFFSET_MAHT0);
+	iowrite32(priv->maht1, priv->base + FTGMAC100_OFFSET_MAHT1);
 }
 
-static void ftgmac100_txdes_set_first_segment(struct ftgmac100_txdes *txdes)
+static int ftgmac100_reset_mac(struct ftgmac100 *priv, u32 maccr)
 {
-	txdes->txdes0 |= cpu_to_le32(FTGMAC100_TXDES0_FTS);
+	struct net_device *ndev = priv->ndev;
+	int i;
+
+	/* NOTE: reset clears all registers */
+	iowrite32(maccr, priv->base + FTGMAC100_OFFSET_MACCR);
+	iowrite32(maccr | FTGMAC100_MACCR_SW_RST,
+		  priv->base + FTGMAC100_OFFSET_MACCR);
+	for (i = 0; i < 50; i++) {
+		maccr = ioread32(priv->base + FTGMAC100_OFFSET_MACCR);
+		if (!(maccr & FTGMAC100_MACCR_SW_RST))
+			return 0;
+		udelay(100);
+	}
+
+	netdev_err(ndev, "Hardware reset failed\n");
+	return -EIO;
 }
 
-static void ftgmac100_txdes_set_last_segment(struct ftgmac100_txdes *txdes)
+static int ftgmac100_reset_and_config_mac(struct ftgmac100 *priv)
 {
-	txdes->txdes0 |= cpu_to_le32(FTGMAC100_TXDES0_LTS);
+	u32 maccr = 0;
+
+	switch (priv->cur_speed) {
+	case SPEED_10:
+	case 0: /* no link */
+		break;
+
+	case SPEED_100:
+		maccr |= FTGMAC100_MACCR_FAST_MODE;
+		break;
+
+	case SPEED_1000:
+		maccr |= FTGMAC100_MACCR_GIGA_MODE;
+		break;
+	default:
+		netdev_err(priv->ndev, "Unknown speed %d !\n", priv->cur_speed);
+		break;
+	}
+
+	/* (Re)initialize the queue pointers */
+	priv->rx_pointer = 0;
+	priv->tx_clean_pointer = 0;
+	priv->tx_pointer = 0;
+
+	/* The doc says reset twice with 10us interval */
+	if (ftgmac100_reset_mac(priv, maccr))
+		return -EIO;
+	udelay(10);
+	return ftgmac100_reset_mac(priv, maccr);
 }
 
-static void ftgmac100_txdes_set_buffer_size(struct ftgmac100_txdes *txdes,
-					    unsigned int len)
+static void ftgmac100_calc_mc_hash(struct ftgmac100 *priv)
 {
-	txdes->txdes0 |= cpu_to_le32(FTGMAC100_TXDES0_TXBUF_SIZE(len));
+	struct netdev_hw_addr *ha;
+
+	priv->maht1 = 0;
+	priv->maht0 = 0;
+	netdev_for_each_mc_addr(ha, priv->ndev) {
+		u32 crc_val = ether_crc_le(ETH_ALEN, ha->addr);
+		crc_val = (~(crc_val >> 2)) & 0x3f;
+		if (crc_val >= 32)
+			priv->maht1 |= 1ul << (crc_val - 32);
+		else
+			priv->maht0 |= 1ul << (crc_val);
+	}
 }
 
-static void ftgmac100_txdes_set_txint(struct ftgmac100_txdes *txdes)
+static void ftgmac100_set_rx_mode(struct net_device *ndev)
 {
-	txdes->txdes1 |= cpu_to_le32(FTGMAC100_TXDES1_TXIC);
+	struct ftgmac100 *priv = netdev_priv(ndev);
+
+	/* If we get passed some MC addresses, setup the hash filter */
+	if (netdev_mc_count(ndev)) {
+		ftgmac100_calc_mc_hash(priv);
+		iowrite32(priv->maht0, priv->base + FTGMAC100_OFFSET_MAHT0);
+		iowrite32(priv->maht1, priv->base + FTGMAC100_OFFSET_MAHT1);
+	}
+
+	/* Reconfigure MACCR */
+	ftgmac100_start_mac(priv);
 }
 
-static void ftgmac100_txdes_set_tcpcs(struct ftgmac100_txdes *txdes)
+static int ftgmac100_set_mac_addr(struct net_device *ndev, void *p)
 {
-	txdes->txdes1 |= cpu_to_le32(FTGMAC100_TXDES1_TCP_CHKSUM);
+	struct ftgmac100 *priv = netdev_priv(ndev);
+	int ret;
+
+	ret = eth_prepare_mac_addr_change(ndev, p);
+	if (ret < 0)
+		return ret;
+	ftgmac100_write_mac_addr(priv, p);
+	eth_commit_mac_addr_change(ndev, p);
+	return 0;
 }
 
-static void ftgmac100_txdes_set_udpcs(struct ftgmac100_txdes *txdes)
+static int ftgmac100_do_ioctl(struct net_device *ndev, struct ifreq *ifr,
+			      int cmd)
 {
-	txdes->txdes1 |= cpu_to_le32(FTGMAC100_TXDES1_UDP_CHKSUM);
+	if (!ndev->phydev)
+		return -ENXIO;
+
+	return phy_mii_ioctl(ndev->phydev, ifr, cmd);
 }
 
-static void ftgmac100_txdes_set_ipcs(struct ftgmac100_txdes *txdes)
+static void ftgmac100_get_drvinfo(struct net_device *ndev,
+				  struct ethtool_drvinfo *info)
 {
-	txdes->txdes1 |= cpu_to_le32(FTGMAC100_TXDES1_IP_CHKSUM);
+	strlcpy(info->driver, DRV_NAME, sizeof(info->driver));
+	strlcpy(info->version, DRV_VERSION, sizeof(info->version));
+	strlcpy(info->bus_info, dev_name(&ndev->dev), sizeof(info->bus_info));
 }
 
-static void ftgmac100_txdes_set_dma_addr(struct ftgmac100_txdes *txdes,
-					 dma_addr_t addr)
+static int ftgmac100_nway_reset(struct net_device *ndev)
 {
-	txdes->txdes3 = cpu_to_le32(addr);
+	if (!ndev->phydev)
+		return -ENXIO;
+	return phy_start_aneg(ndev->phydev);
 }
 
-static dma_addr_t ftgmac100_txdes_get_dma_addr(struct ftgmac100_txdes *txdes)
+static void ftgmac100_get_ringparam(struct net_device *ndev,
+				    struct ethtool_ringparam *ering)
 {
-	return le32_to_cpu(txdes->txdes3);
+	struct ftgmac100 *priv = netdev_priv(ndev);
+
+	memset(ering, 0, sizeof(*ering));
+	ering->rx_max_pending = MAX_RX_QUEUE_ENTRIES;
+	ering->tx_max_pending = MAX_TX_QUEUE_ENTRIES;
+	ering->rx_pending = priv->rx_q_entries;
+	ering->tx_pending = priv->tx_q_entries;
 }
 
-/*
- * txdes2 is not used by hardware. We use it to keep track of socket buffer.
- * Since hardware does not touch it, we can skip cpu_to_le32()/le32_to_cpu().
- */
-static void ftgmac100_txdes_set_skb(struct ftgmac100_txdes *txdes,
-				    struct sk_buff *skb)
+static int ftgmac100_set_ringparam(struct net_device *ndev,
+				   struct ethtool_ringparam *ering)
 {
-	txdes->txdes2 = (unsigned int)skb;
+	struct ftgmac100 *priv = netdev_priv(ndev);
+
+	if (ering->rx_pending > MAX_RX_QUEUE_ENTRIES ||
+	    ering->tx_pending > MAX_TX_QUEUE_ENTRIES ||
+	    ering->rx_pending < MIN_RX_QUEUE_ENTRIES ||
+	    ering->tx_pending < MIN_TX_QUEUE_ENTRIES ||
+	    !is_power_of_2(ering->rx_pending) ||
+	    !is_power_of_2(ering->tx_pending))
+		return -EINVAL;
+
+	priv->new_rx_q_entries = ering->rx_pending;
+	priv->new_tx_q_entries = ering->tx_pending;
+	if (netif_running(ndev))
+		schedule_work(&priv->reset_task);
+
+	return 0;
 }
 
-static struct sk_buff *ftgmac100_txdes_get_skb(struct ftgmac100_txdes *txdes)
+static void ftgmac100_get_pauseparam(struct net_device *ndev,
+				     struct ethtool_pauseparam *pause)
 {
-	return (struct sk_buff *)txdes->txdes2;
+	struct ftgmac100 *priv = netdev_priv(ndev);
+
+	pause->autoneg = priv->aneg_pause;
+	pause->tx_pause = priv->tx_pause;
+	pause->rx_pause = priv->rx_pause;
 }
 
-/******************************************************************************
- * internal functions (transmit)
- *****************************************************************************/
-static int ftgmac100_next_tx_pointer(int pointer)
+static int ftgmac100_set_pauseparam(struct net_device *ndev,
+				    struct ethtool_pauseparam *pause)
 {
-	return (pointer + 1) & (TX_QUEUE_ENTRIES - 1);
+	struct ftgmac100 *priv = netdev_priv(ndev);
+	struct phy_device *phydev = ndev->phydev;
+
+	priv->aneg_pause = pause->autoneg;
+	priv->tx_pause = pause->tx_pause;
+	priv->rx_pause = pause->rx_pause;
+
+	if (phydev) {
+		phydev->advertising &= ~ADVERTISED_Pause;
+		phydev->advertising &= ~ADVERTISED_Asym_Pause;
+
+		if (pause->rx_pause) {
+			phydev->advertising |= ADVERTISED_Pause;
+			phydev->advertising |= ADVERTISED_Asym_Pause;
+		}
+
+		if (pause->tx_pause)
+			phydev->advertising ^= ADVERTISED_Asym_Pause;
+	}
+	if (netif_running(ndev)) {
+		if (phydev && priv->aneg_pause)
+			phy_start_aneg(phydev);
+		else
+			ftgmac100_config_pause(priv);
+	}
+
+	return 0;
 }
 
-static void ftgmac100_tx_pointer_advance(struct ftgmac100 *priv)
+static const struct ethtool_ops ftgmac100_ethtool_ops = {
+	.get_drvinfo		= ftgmac100_get_drvinfo,
+	.get_link		= ethtool_op_get_link,
+	.get_link_ksettings	= phy_ethtool_get_link_ksettings,
+	.set_link_ksettings	= phy_ethtool_set_link_ksettings,
+	.nway_reset		= ftgmac100_nway_reset,
+	.get_ringparam		= ftgmac100_get_ringparam,
+	.set_ringparam		= ftgmac100_set_ringparam,
+	.get_pauseparam		= ftgmac100_get_pauseparam,
+	.set_pauseparam		= ftgmac100_set_pauseparam,
+};
+
+static void ftgmac100_free_tx_buffers(struct ftgmac100 *priv)
 {
-	priv->tx_pointer = ftgmac100_next_tx_pointer(priv->tx_pointer);
+	int i;
+
+	/* Free all tx buffers */
+	for (i = 0; i < priv->tx_q_entries; i++) {
+		struct ftgmac100_txdes *txdes = &priv->txdes[i];
+
+		if (!priv->tx_skbs[i])
+			continue;
+		ftgmac100_free_tx_packet(priv, i, txdes);
+	}
 }
 
-static void ftgmac100_tx_clean_pointer_advance(struct ftgmac100 *priv)
+static void ftgmac100_free_rx_buffers(struct ftgmac100 *priv)
 {
-	priv->tx_clean_pointer = ftgmac100_next_tx_pointer(priv->tx_clean_pointer);
+	int i;
+
+	/* Free all RX buffers */
+	for (i = 0; i < priv->rx_q_entries; i++) {
+		struct ftgmac100_rxdes *rxdes = &priv->rxdes[i];
+		struct sk_buff *skb = priv->rx_skbs[i];
+		dma_addr_t map = ftgmac100_rxdes_get_dma_addr(rxdes);
+
+		if (!skb)
+			continue;
+
+		priv->rx_skbs[i] = NULL;
+		dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
+		dev_kfree_skb_any(skb);
+	}
 }
 
-static struct ftgmac100_txdes *ftgmac100_current_txdes(struct ftgmac100 *priv)
+static void ftgmac100_free_descriptors(struct ftgmac100 *priv)
 {
-	return &priv->descs->txdes[priv->tx_pointer];
+	/* Free skb arrays */
+	if (priv->rx_skbs)
+		kfree(priv->rx_skbs);
+	if (priv->tx_skbs)
+		kfree(priv->tx_skbs);
+
+	/* Free descriptor arrays */
+	if (priv->rxdes)
+		dma_free_coherent(priv->dev, MAX_RX_QUEUE_ENTRIES *
+				  sizeof(struct ftgmac100_rxdes),
+				  priv->rxdes, priv->rxdes_dma);
+	priv->rxdes = NULL;
+	if (priv->txdes)
+		dma_free_coherent(priv->dev, MAX_TX_QUEUE_ENTRIES *
+				  sizeof(struct ftgmac100_txdes),
+				  priv->txdes, priv->txdes_dma);
+	priv->txdes = NULL;
+
+	/* Free scratch packet buffer */
+	if (priv->rx_scratch)
+		dma_free_coherent(priv->dev, RX_BUF_SIZE,
+				  priv->rx_scratch, priv->rx_scratch_dma);
 }
 
-static struct ftgmac100_txdes *
-ftgmac100_current_clean_txdes(struct ftgmac100 *priv)
+static void ftgmac100_init_descriptors(struct ftgmac100 *priv)
 {
-	return &priv->descs->txdes[priv->tx_clean_pointer];
+	int i;
+
+	/* Update entries counts */
+	priv->rx_q_entries = priv->new_rx_q_entries;
+	priv->tx_q_entries = priv->new_tx_q_entries;
+
+	/*
+	 * Clean all rx and tx descriptors and set the end-of-ring
+	 * marker on the last entry. For the RX descriptor, populate
+	 * all entries with a DMA address pointing to the scratch
+	 * page.
+	 */
+	for (i = 0; i < priv->rx_q_entries; i++) {
+		priv->rxdes[i].rxdes0 = 0;
+		ftgmac100_rxdes_set_dma_addr(&priv->rxdes[i], priv->rx_scratch_dma);
+	}
+	priv->rxdes[i - 1].rxdes0 = cpu_to_le32(priv->rxdes0_edorr_mask);
+	for (i = 0; i < priv->tx_q_entries; i++)
+		priv->txdes[i].txdes0 = 0;
+	priv->txdes[i - 1].txdes0 = cpu_to_le32(priv->txdes0_edotr_mask);
 }
 
-static bool ftgmac100_tx_complete_packet(struct ftgmac100 *priv)
+static int ftgmac100_alloc_descriptors(struct ftgmac100 *priv)
 {
-	struct net_device *netdev = priv->netdev;
-	struct ftgmac100_txdes *txdes;
-	struct sk_buff *skb;
-	dma_addr_t map;
+	/* Allocate skb arrays */
+	priv->rx_skbs = kzalloc(MAX_RX_QUEUE_ENTRIES * sizeof(void *), GFP_KERNEL);
+	if (!priv->rx_skbs)
+		return -ENOMEM;
+	priv->tx_skbs = kzalloc(MAX_TX_QUEUE_ENTRIES * sizeof(void *), GFP_KERNEL);
+	if (!priv->tx_skbs)
+		return -ENOMEM;
 
-	if (priv->tx_pending == 0)
-		return false;
+	/* Allocate descriptor arrays */
+	priv->rxdes = dma_zalloc_coherent(priv->dev,
+					  MAX_RX_QUEUE_ENTRIES *
+					  sizeof(struct ftgmac100_rxdes),
+					  &priv->rxdes_dma, GFP_KERNEL);
+	if (!priv->rxdes)
+		return -ENOMEM
+;	priv->txdes = dma_zalloc_coherent(priv->dev,
+					  MAX_TX_QUEUE_ENTRIES *
+					  sizeof(struct ftgmac100_txdes),
+					  &priv->txdes_dma, GFP_KERNEL);
+	if (!priv->txdes)
+		return -ENOMEM;
 
-	txdes = ftgmac100_current_clean_txdes(priv);
+	/* Allocate scratch packet buffer */
+	priv->rx_scratch = dma_alloc_coherent(priv->dev,
+					      RX_BUF_SIZE,
+					      &priv->rx_scratch_dma,
+					      GFP_KERNEL);
+	if (!priv->rx_scratch)
+		return -ENOMEM;
 
-	if (ftgmac100_txdes_owned_by_dma(txdes))
-		return false;
+	return 0;
+}
 
-	skb = ftgmac100_txdes_get_skb(txdes);
-	map = ftgmac100_txdes_get_dma_addr(txdes);
+static int ftgmac100_alloc_rx_buffers(struct ftgmac100 *priv)
+{
+	int i;
 
-	netdev->stats.tx_packets++;
-	netdev->stats.tx_bytes += skb->len;
+	/* Populate RX ring */
+	for (i = 0; i < priv->rx_q_entries; i++) {
+		/*
+		 * Give up on error, the entries have been pre-populated
+		 * with the address of the scratch page
+		 */
+		if (ftgmac100_alloc_rx_buf(priv, i, GFP_KERNEL))
+			return ENOMEM;;
+	}
+	return 0;
+}
 
-	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+static void ftgmac100_init_all(struct ftgmac100 *priv)
+{
+	if (!netif_running(priv->ndev))
+		return;
 
-	dev_kfree_skb(skb);
+	/* Re-init descriptors (adjust queue sizes) */
+	ftgmac100_init_descriptors(priv);
 
-	ftgmac100_txdes_reset(priv, txdes);
+	/* Realloc rx descriptors */
+	ftgmac100_alloc_rx_buffers(priv);
 
-	ftgmac100_tx_clean_pointer_advance(priv);
+	/* Reinit and restart HW */
+	ftgmac100_init_hw(priv);
+	ftgmac100_config_pause(priv);
+	ftgmac100_start_mac(priv);
 
-	spin_lock(&priv->tx_lock);
-	priv->tx_pending--;
-	spin_unlock(&priv->tx_lock);
-	netif_wake_queue(netdev);
+	/* Re-enable the device */
+	napi_enable(&priv->napi);
+	netif_start_queue(priv->ndev);
 
-	return true;
+	/* Enable all interrupts */
+	iowrite32(FTGMAC100_INT_ALL, priv->base + FTGMAC100_OFFSET_IER);
 }
 
-static void ftgmac100_tx_complete(struct ftgmac100 *priv)
+static int ftgmac100_open(struct net_device *ndev)
 {
-	while (ftgmac100_tx_complete_packet(priv))
-		;
-}
+	struct ftgmac100 *priv = netdev_priv(ndev);
+	int err;
 
-static int ftgmac100_xmit(struct ftgmac100 *priv, struct sk_buff *skb,
-			  dma_addr_t map)
-{
-	struct net_device *netdev = priv->netdev;
-	struct ftgmac100_txdes *txdes;
-	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
+	/* Clear stale stopping flag */
+	priv->stopping = false;
 
-	txdes = ftgmac100_current_txdes(priv);
-	ftgmac100_tx_pointer_advance(priv);
+	/* Allocate ring buffers and populate Rx ring */
+	err = ftgmac100_alloc_descriptors(priv);
+	if (err) {
+		netdev_err(ndev, "failed to allocate descriptors\n");
+		goto err_alloc;
+	}
 
-	/* setup TX descriptor */
-	ftgmac100_txdes_set_skb(txdes, skb);
-	ftgmac100_txdes_set_dma_addr(txdes, map);
-	ftgmac100_txdes_set_buffer_size(txdes, len);
+	/*
+	 * When using NC-SI we force the speed to 100Mbit/s full duplex,
+	 *
+	 * Otherwise we leave it set to 0 (no link), the link
+	 * message from the PHY layer will handle setting it up to
+	 * something else if needed.
+	 */
+	if (priv->use_ncsi) {
+		priv->cur_duplex = DUPLEX_FULL;
+		priv->cur_speed = SPEED_100;
+	} else {
+		priv->cur_duplex = 0;
+		priv->cur_speed = 0;
+	}
 
-	ftgmac100_txdes_set_first_segment(txdes);
-	ftgmac100_txdes_set_last_segment(txdes);
-	ftgmac100_txdes_set_txint(txdes);
-	if (skb->ip_summed == CHECKSUM_PARTIAL) {
-		__be16 protocol = skb->protocol;
+	/* Reset the hardware */
+	err = ftgmac100_reset_and_config_mac(priv);
+	if (err)
+		goto err_hw;
 
-		if (protocol == cpu_to_be16(ETH_P_IP)) {
-			u8 ip_proto = ip_hdr(skb)->protocol;
+	/* Initialize NAPI */
+	netif_napi_add(ndev, &priv->napi, ftgmac100_poll, NAPI_POLL_WEIGHT);
+
+	/* Disable all interrupts */
+	iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
+
+	/* Grab our interrupt */
+	err = request_irq(ndev->irq, ftgmac100_interrupt, 0,
+			  ndev->name, ndev);
+	if (err) {
+		netdev_err(ndev, "failed to request irq %d\n", ndev->irq);
+		goto err_irq;
+	}
+
+	/* Start thing up */
+	ftgmac100_init_all(priv);
+	if (ndev->phydev) {
+		/* If we have a PHY, start polling */
+		phy_start(ndev->phydev);
+	} else if (priv->use_ncsi) {
+		/* If using NC-SI, set our carrier on and start the stack */
+		netif_carrier_on(ndev);
+
+		/* Start the NCSI device */
+		err = ncsi_start_dev(priv->ncsidev);
+		if (err)
+			goto err_ncsi;
+	}
+
+	return 0;
+
+ err_ncsi:
+	napi_disable(&priv->napi);
+	netif_stop_queue(ndev);
+	free_irq(ndev->irq, ndev);
+ err_irq:
+	netif_napi_del(&priv->napi);
+ err_hw:
+ err_alloc:
+	iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
+	ftgmac100_free_tx_buffers(priv);
+	ftgmac100_free_rx_buffers(priv);
+	ftgmac100_free_descriptors(priv);
+	return err;
+}
 
-			ftgmac100_txdes_set_ipcs(txdes);
-			if (ip_proto == IPPROTO_TCP)
-				ftgmac100_txdes_set_tcpcs(txdes);
-			else if (ip_proto == IPPROTO_UDP)
-				ftgmac100_txdes_set_udpcs(txdes);
-		}
-	}
+static int ftgmac100_stop(struct net_device *ndev)
+{
+	struct ftgmac100 *priv = netdev_priv(ndev);
 
-	spin_lock(&priv->tx_lock);
-	priv->tx_pending++;
-	if (priv->tx_pending == TX_QUEUE_ENTRIES)
-		netif_stop_queue(netdev);
+	/* Block reset task */
+	priv->stopping = true;
 
-	/* start transmit */
-	ftgmac100_txdes_set_dma_own(txdes);
-	spin_unlock(&priv->tx_lock);
+	/* Kill any pending one */
+	cancel_work_sync(&priv->reset_task);
 
-	ftgmac100_txdma_normal_prio_start_polling(priv);
+	/* Disable all interrupts */
+	iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
 
-	return NETDEV_TX_OK;
-}
+	/* Stop the PHY or NCSI */
+	if (ndev->phydev)
+		phy_stop(ndev->phydev);
+	else if (priv->use_ncsi)
+		ncsi_stop_dev(priv->ncsidev);
 
-/******************************************************************************
- * internal functions (buffer)
- *****************************************************************************/
-static int ftgmac100_alloc_rx_page(struct ftgmac100 *priv,
-				   struct ftgmac100_rxdes *rxdes, gfp_t gfp)
-{
-	struct net_device *netdev = priv->netdev;
-	struct page *page;
-	dma_addr_t map;
+	/* Stop the network stack */
+	netif_stop_queue(ndev);
+	napi_disable(&priv->napi);
+	netif_napi_del(&priv->napi);
 
-	page = alloc_page(gfp);
-	if (!page) {
-		if (net_ratelimit())
-			netdev_err(netdev, "failed to allocate rx page\n");
-		return -ENOMEM;
-	}
+	/* Stop the HW */
+	ftgmac100_stop_mac(priv);
 
-	map = dma_map_page(priv->dev, page, 0, RX_BUF_SIZE, DMA_FROM_DEVICE);
-	if (unlikely(dma_mapping_error(priv->dev, map))) {
-		if (net_ratelimit())
-			netdev_err(netdev, "failed to map rx page\n");
-		__free_page(page);
-		return -ENOMEM;
-	}
+	/* No more IRQ for us */
+	free_irq(ndev->irq, ndev);
+
+	/* Free everything */
+	ftgmac100_free_tx_buffers(priv);
+	ftgmac100_free_rx_buffers(priv);
+	ftgmac100_free_descriptors(priv);
 
-	ftgmac100_rxdes_set_page(priv, rxdes, page);
-	ftgmac100_rxdes_set_dma_addr(rxdes, map);
-	ftgmac100_rxdes_set_dma_own(priv, rxdes);
 	return 0;
 }
 
-static void ftgmac100_free_buffers(struct ftgmac100 *priv)
+static void ftgmac100_reset_task(struct work_struct *work)
 {
-	int i;
+	struct ftgmac100 *priv = container_of(work, struct ftgmac100, reset_task);
+	struct net_device *ndev = priv->ndev;
+	int err;
 
-	for (i = 0; i < RX_QUEUE_ENTRIES; i++) {
-		struct ftgmac100_rxdes *rxdes = &priv->descs->rxdes[i];
-		struct page *page = ftgmac100_rxdes_get_page(priv, rxdes);
-		dma_addr_t map = ftgmac100_rxdes_get_dma_addr(rxdes);
+	/* Adapter is going down */
+	if (priv->stopping)
+		return;
 
-		if (!page)
-			continue;
+	netdev_dbg(ndev, "Resetting NIC...\n");
 
-		dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
-		__free_page(page);
-	}
+	/* Block PHY polling */
+	if (ndev->phydev)
+		mutex_lock(&ndev->phydev->lock);
 
-	for (i = 0; i < TX_QUEUE_ENTRIES; i++) {
-		struct ftgmac100_txdes *txdes = &priv->descs->txdes[i];
-		struct sk_buff *skb = ftgmac100_txdes_get_skb(txdes);
-		dma_addr_t map = ftgmac100_txdes_get_dma_addr(txdes);
+	rtnl_lock();
 
-		if (!skb)
-			continue;
+	/* Check if link state changed again */
+	if (priv->cur_speed == 0)
+		goto bail;
+
+	/* Stop the network stack */
+	netif_trans_update(ndev);
+	napi_disable(&priv->napi);
+	netif_tx_disable(ndev);
 
-		dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
-		kfree_skb(skb);
+	/* Stop and reset the MAC */
+	ftgmac100_stop_mac(priv);
+	err = ftgmac100_reset_and_config_mac(priv);
+	if (err) {
+		/* Not much we can do ... it might come back... */
+		netdev_err(ndev, "attempting to continue...\n");
 	}
 
-	dma_free_coherent(priv->dev, sizeof(struct ftgmac100_descs),
-			  priv->descs, priv->descs_dma_addr);
+	/* Free all rx and tx buffers */
+	ftgmac100_free_tx_buffers(priv);
+	ftgmac100_free_rx_buffers(priv);
+
+	/* Setup everything and restart chip */
+	ftgmac100_init_all(priv);
+
+	netdev_dbg(ndev, "Reset done !\n");
+ bail:
+	rtnl_unlock();
+
+	/* Unblock PHY polling */
+	if (ndev->phydev)
+		mutex_unlock(&ndev->phydev->lock);
 }
 
-static int ftgmac100_alloc_buffers(struct ftgmac100 *priv)
+static void ftgmac100_tx_timeout(struct net_device *ndev)
 {
-	int i;
+	struct ftgmac100 *priv = netdev_priv(ndev);
 
-	priv->descs = dma_zalloc_coherent(priv->dev,
-					  sizeof(struct ftgmac100_descs),
-					  &priv->descs_dma_addr, GFP_KERNEL);
-	if (!priv->descs)
-		return -ENOMEM;
-
-	/* initialize RX ring */
-	ftgmac100_rxdes_set_end_of_ring(priv,
-			&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
+	/* Disable all interrupts */
+	iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
 
-	for (i = 0; i < RX_QUEUE_ENTRIES; i++) {
-		struct ftgmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+	/* Do the reset outside of interrupt context */
+	schedule_work(&priv->reset_task);
+}
 
-		if (ftgmac100_alloc_rx_page(priv, rxdes, GFP_KERNEL))
-			goto err;
-	}
+#ifdef CONFIG_NET_POLL_CONTROLLER
+static void ftgmac100_netpoll(struct net_device *ndev)
+{
+	disable_irq(dev->irq);
+	ftgmac100_interrupt(dev->irq, ndev);
+	enable_irq(dev->irq);
+}
+#endif
 
-	/* initialize TX ring */
-	ftgmac100_txdes_set_end_of_ring(priv,
-			&priv->descs->txdes[TX_QUEUE_ENTRIES - 1]);
-	return 0;
+static const struct net_device_ops ftgmac100_netdev_ops = {
+	.ndo_open		= ftgmac100_open,
+	.ndo_stop		= ftgmac100_stop,
+	.ndo_start_xmit		= ftgmac100_hard_start_xmit,
+	.ndo_set_mac_address	= ftgmac100_set_mac_addr,
+        .ndo_set_rx_mode	= ftgmac100_set_rx_mode,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= ftgmac100_do_ioctl,
+	.ndo_tx_timeout		= ftgmac100_tx_timeout,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	.ndo_poll_controller	= ftgmac100_poll_controller,
+#endif
+};
 
-err:
-	ftgmac100_free_buffers(priv);
-	return -ENOMEM;
+static const char *ftgmac100_fctrl_string(struct ftgmac100 *priv)
+{
+	if (priv->tx_pause && priv->rx_pause)
+		return "rx/tx";
+	else if (priv->rx_pause)
+		return "rx";
+	else if (priv->tx_pause)
+		return "tx";
+	else
+		return "no";
 }
 
-/******************************************************************************
- * internal functions (mdio)
- *****************************************************************************/
-static void ftgmac100_adjust_link(struct net_device *netdev)
+static void ftgmac100_adjust_link(struct net_device *ndev)
 {
-	struct ftgmac100 *priv = netdev_priv(netdev);
-	struct phy_device *phydev = netdev->phydev;
-	int ier;
+	struct ftgmac100 *priv = netdev_priv(ndev);
+	struct phy_device *phydev = ndev->phydev;
+	bool tx_pause, rx_pause;
+
+	/* Link is down */
+	if (!phydev->link) {
+		if (priv->cur_speed)
+			netdev_info(ndev, "Link down\n");
+		priv->cur_speed = 0;
 
-	if (phydev->speed == priv->old_speed)
+		/*
+		 * We just stop the MAC, we'll reset the adapter
+		 * if/when the link comes back up
+		 */
+		ftgmac100_stop_mac(priv);
 		return;
+	}
+
+	/* Grab pause settings from PHY if configured to do so */
+	if (priv->aneg_pause) {
+		rx_pause = tx_pause = phydev->pause;
+		if (phydev->asym_pause)
+			tx_pause = !rx_pause;
+	} else {
+		rx_pause = priv->rx_pause;
+		tx_pause = priv->tx_pause;
+	}
 
-	priv->old_speed = phydev->speed;
+	/* Link hasn't changed, do nothing */
+	if (phydev->speed == priv->cur_speed &&
+	    phydev->duplex == priv->cur_duplex &&
+	    rx_pause == priv->rx_pause &&
+	    tx_pause == priv->tx_pause)
+		return;
 
-	ier = ioread32(priv->base + FTGMAC100_OFFSET_IER);
+	priv->cur_speed = phydev->speed;
+	priv->cur_duplex = phydev->duplex;
+	priv->rx_pause = rx_pause;
+	priv->tx_pause = tx_pause;
 
-	/* disable all interrupts */
-	iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
+	netdev_info(ndev, "Link up at %d Mbit/s %s duplex %s flow ctrl\n",
+		    priv->cur_speed,
+		    phydev->duplex == DUPLEX_FULL ? "full" : "half",
+		    ftgmac100_fctrl_string(priv));
 
-	netif_stop_queue(netdev);
-	ftgmac100_stop_hw(priv);
 
-	netif_start_queue(netdev);
-	ftgmac100_init_hw(priv);
-	ftgmac100_start_hw(priv, phydev->speed);
+	/* Disable all interrupts */
+	iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
 
-	/* re-enable interrupts */
-	iowrite32(ier, priv->base + FTGMAC100_OFFSET_IER);
+	/* Reset the adapter asynchronously */
+	schedule_work(&priv->reset_task);
 }
 
 static int ftgmac100_mii_probe(struct ftgmac100 *priv)
 {
-	struct net_device *netdev = priv->netdev;
+	struct net_device *ndev = priv->ndev;
 	struct phy_device *phydev;
 
 	phydev = phy_find_first(priv->mii_bus);
 	if (!phydev) {
-		netdev_info(netdev, "%s: no PHY found\n", netdev->name);
+		netdev_info(ndev, "%s: no PHY found\n", ndev->name);
 		return -ENODEV;
 	}
 
-	phydev = phy_connect(netdev, phydev_name(phydev),
+	phydev = phy_connect(ndev, phydev_name(phydev),
 			     &ftgmac100_adjust_link, PHY_INTERFACE_MODE_GMII);
 
 	if (IS_ERR(phydev)) {
-		netdev_err(netdev, "%s: Could not attach to PHY\n", netdev->name);
+		netdev_err(ndev, "%s: Could not attach to PHY\n", ndev->name);
 		return PTR_ERR(phydev);
 	}
 
 	return 0;
 }
 
-/******************************************************************************
- * struct mii_bus functions
- *****************************************************************************/
-static int ftgmac100_mdiobus_read(struct mii_bus *bus, int phy_addr, int regnum)
+static int ftgmac100_mii_read(struct mii_bus *bus, int phy_addr, int regnum)
 {
-	struct net_device *netdev = bus->priv;
-	struct ftgmac100 *priv = netdev_priv(netdev);
+	struct net_device *ndev = bus->priv;
+	struct ftgmac100 *priv = netdev_priv(ndev);
 	unsigned int phycr;
 	int i;
 
@@ -951,15 +1438,15 @@ static int ftgmac100_mdiobus_read(struct mii_bus *bus, int phy_addr, int regnum)
 		udelay(100);
 	}
 
-	netdev_err(netdev, "mdio read timed out\n");
+	netdev_err(ndev, "mdio read timed out\n");
 	return -EIO;
 }
 
-static int ftgmac100_mdiobus_write(struct mii_bus *bus, int phy_addr,
-				   int regnum, u16 value)
+static int ftgmac100_mii_write(struct mii_bus *bus, int phy_addr,
+			       int regnum, u16 value)
 {
-	struct net_device *netdev = bus->priv;
-	struct ftgmac100 *priv = netdev_priv(netdev);
+	struct net_device *ndev = bus->priv;
+	struct ftgmac100 *priv = netdev_priv(ndev);
 	unsigned int phycr;
 	int data;
 	int i;
@@ -987,268 +1474,13 @@ static int ftgmac100_mdiobus_write(struct mii_bus *bus, int phy_addr,
 		udelay(100);
 	}
 
-	netdev_err(netdev, "mdio write timed out\n");
+	netdev_err(ndev, "mdio write timed out\n");
 	return -EIO;
 }
 
-/******************************************************************************
- * struct ethtool_ops functions
- *****************************************************************************/
-static void ftgmac100_get_drvinfo(struct net_device *netdev,
-				  struct ethtool_drvinfo *info)
-{
-	strlcpy(info->driver, DRV_NAME, sizeof(info->driver));
-	strlcpy(info->version, DRV_VERSION, sizeof(info->version));
-	strlcpy(info->bus_info, dev_name(&netdev->dev), sizeof(info->bus_info));
-}
-
-static const struct ethtool_ops ftgmac100_ethtool_ops = {
-	.get_drvinfo		= ftgmac100_get_drvinfo,
-	.get_link		= ethtool_op_get_link,
-	.get_link_ksettings	= phy_ethtool_get_link_ksettings,
-	.set_link_ksettings	= phy_ethtool_set_link_ksettings,
-};
-
-/******************************************************************************
- * interrupt handler
- *****************************************************************************/
-static irqreturn_t ftgmac100_interrupt(int irq, void *dev_id)
-{
-	struct net_device *netdev = dev_id;
-	struct ftgmac100 *priv = netdev_priv(netdev);
-
-	/* When running in NCSI mode, the interface should be ready for
-	 * receiving or transmitting NCSI packets before it's opened.
-	 */
-	if (likely(priv->use_ncsi || netif_running(netdev))) {
-		/* Disable interrupts for polling */
-		iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
-		napi_schedule(&priv->napi);
-	}
-
-	return IRQ_HANDLED;
-}
-
-/******************************************************************************
- * struct napi_struct functions
- *****************************************************************************/
-static int ftgmac100_poll(struct napi_struct *napi, int budget)
-{
-	struct ftgmac100 *priv = container_of(napi, struct ftgmac100, napi);
-	struct net_device *netdev = priv->netdev;
-	unsigned int status;
-	bool completed = true;
-	int rx = 0;
-
-	status = ioread32(priv->base + FTGMAC100_OFFSET_ISR);
-	iowrite32(status, priv->base + FTGMAC100_OFFSET_ISR);
-
-	if (status & (FTGMAC100_INT_RPKT_BUF | FTGMAC100_INT_NO_RXBUF)) {
-		/*
-		 * FTGMAC100_INT_RPKT_BUF:
-		 *	RX DMA has received packets into RX buffer successfully
-		 *
-		 * FTGMAC100_INT_NO_RXBUF:
-		 *	RX buffer unavailable
-		 */
-		bool retry;
-
-		do {
-			retry = ftgmac100_rx_packet(priv, &rx);
-		} while (retry && rx < budget);
-
-		if (retry && rx == budget)
-			completed = false;
-	}
-
-	if (status & (FTGMAC100_INT_XPKT_ETH | FTGMAC100_INT_XPKT_LOST)) {
-		/*
-		 * FTGMAC100_INT_XPKT_ETH:
-		 *	packet transmitted to ethernet successfully
-		 *
-		 * FTGMAC100_INT_XPKT_LOST:
-		 *	packet transmitted to ethernet lost due to late
-		 *	collision or excessive collision
-		 */
-		ftgmac100_tx_complete(priv);
-	}
-
-	if (status & priv->int_mask_all & (FTGMAC100_INT_NO_RXBUF |
-			FTGMAC100_INT_RPKT_LOST | FTGMAC100_INT_AHB_ERR)) {
-		if (net_ratelimit())
-			netdev_info(netdev, "[ISR] = 0x%x: %s%s%s\n", status,
-				    status & FTGMAC100_INT_NO_RXBUF ? "NO_RXBUF " : "",
-				    status & FTGMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "",
-				    status & FTGMAC100_INT_AHB_ERR ? "AHB_ERR " : "");
-
-		if (status & FTGMAC100_INT_NO_RXBUF) {
-			/* RX buffer unavailable */
-			netdev->stats.rx_over_errors++;
-		}
-
-		if (status & FTGMAC100_INT_RPKT_LOST) {
-			/* received packet lost due to RX FIFO full */
-			netdev->stats.rx_fifo_errors++;
-		}
-	}
-
-	if (completed) {
-		napi_complete(napi);
-
-		/* enable all interrupts */
-		iowrite32(priv->int_mask_all,
-			  priv->base + FTGMAC100_OFFSET_IER);
-	}
-
-	return rx;
-}
-
-/******************************************************************************
- * struct net_device_ops functions
- *****************************************************************************/
-static int ftgmac100_open(struct net_device *netdev)
-{
-	struct ftgmac100 *priv = netdev_priv(netdev);
-	unsigned int status;
-	int err;
-
-	err = ftgmac100_alloc_buffers(priv);
-	if (err) {
-		netdev_err(netdev, "failed to allocate buffers\n");
-		goto err_alloc;
-	}
-
-	err = request_irq(priv->irq, ftgmac100_interrupt, 0, netdev->name, netdev);
-	if (err) {
-		netdev_err(netdev, "failed to request irq %d\n", priv->irq);
-		goto err_irq;
-	}
-
-	priv->rx_pointer = 0;
-	priv->tx_clean_pointer = 0;
-	priv->tx_pointer = 0;
-	priv->tx_pending = 0;
-
-	err = ftgmac100_reset_hw(priv);
-	if (err)
-		goto err_hw;
-
-	ftgmac100_init_hw(priv);
-	ftgmac100_start_hw(priv, priv->use_ncsi ? 100 : 10);
-
-	/* Clear stale interrupts */
-	status = ioread32(priv->base + FTGMAC100_OFFSET_ISR);
-	iowrite32(status, priv->base + FTGMAC100_OFFSET_ISR);
-
-	if (netdev->phydev)
-		phy_start(netdev->phydev);
-	else if (priv->use_ncsi)
-		netif_carrier_on(netdev);
-
-	napi_enable(&priv->napi);
-	netif_start_queue(netdev);
-
-	/* enable all interrupts */
-	iowrite32(priv->int_mask_all, priv->base + FTGMAC100_OFFSET_IER);
-
-	/* Start the NCSI device */
-	if (priv->use_ncsi) {
-		err = ncsi_start_dev(priv->ndev);
-		if (err)
-			goto err_ncsi;
-	}
-
-	priv->enabled = true;
-
-	return 0;
-
-err_ncsi:
-	napi_disable(&priv->napi);
-	netif_stop_queue(netdev);
-	iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
-err_hw:
-	free_irq(priv->irq, netdev);
-err_irq:
-	ftgmac100_free_buffers(priv);
-err_alloc:
-	return err;
-}
-
-static int ftgmac100_stop(struct net_device *netdev)
-{
-	struct ftgmac100 *priv = netdev_priv(netdev);
-
-	if (!priv->enabled)
-		return 0;
-
-	/* disable all interrupts */
-	priv->enabled = false;
-	iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
-
-	netif_stop_queue(netdev);
-	napi_disable(&priv->napi);
-	if (netdev->phydev)
-		phy_stop(netdev->phydev);
-	else if (priv->use_ncsi)
-		ncsi_stop_dev(priv->ndev);
-
-	ftgmac100_stop_hw(priv);
-	free_irq(priv->irq, netdev);
-	ftgmac100_free_buffers(priv);
-
-	return 0;
-}
-
-static int ftgmac100_hard_start_xmit(struct sk_buff *skb,
-				     struct net_device *netdev)
-{
-	struct ftgmac100 *priv = netdev_priv(netdev);
-	dma_addr_t map;
-
-	if (unlikely(skb->len > MAX_PKT_SIZE)) {
-		if (net_ratelimit())
-			netdev_dbg(netdev, "tx packet too big\n");
-
-		netdev->stats.tx_dropped++;
-		kfree_skb(skb);
-		return NETDEV_TX_OK;
-	}
-
-	map = dma_map_single(priv->dev, skb->data, skb_headlen(skb), DMA_TO_DEVICE);
-	if (unlikely(dma_mapping_error(priv->dev, map))) {
-		/* drop packet */
-		if (net_ratelimit())
-			netdev_err(netdev, "map socket buffer failed\n");
-
-		netdev->stats.tx_dropped++;
-		kfree_skb(skb);
-		return NETDEV_TX_OK;
-	}
-
-	return ftgmac100_xmit(priv, skb, map);
-}
-
-/* optional */
-static int ftgmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
-{
-	if (!netdev->phydev)
-		return -ENXIO;
-
-	return phy_mii_ioctl(netdev->phydev, ifr, cmd);
-}
-
-static const struct net_device_ops ftgmac100_netdev_ops = {
-	.ndo_open		= ftgmac100_open,
-	.ndo_stop		= ftgmac100_stop,
-	.ndo_start_xmit		= ftgmac100_hard_start_xmit,
-	.ndo_set_mac_address	= ftgmac100_set_mac_addr,
-	.ndo_validate_addr	= eth_validate_addr,
-	.ndo_do_ioctl		= ftgmac100_do_ioctl,
-};
-
-static int ftgmac100_setup_mdio(struct net_device *netdev)
+static int ftgmac100_setup_mdio(struct net_device *ndev)
 {
-	struct ftgmac100 *priv = netdev_priv(netdev);
+	struct ftgmac100 *priv = netdev_priv(ndev);
 	struct platform_device *pdev = to_platform_device(priv->dev);
 	int i, err = 0;
 	u32 reg;
@@ -1258,8 +1490,9 @@ static int ftgmac100_setup_mdio(struct net_device *netdev)
 	if (!priv->mii_bus)
 		return -EIO;
 
-	if (of_machine_is_compatible("aspeed,ast2400") ||
-	    of_machine_is_compatible("aspeed,ast2500")) {
+	if (pdev->dev.of_node &&
+	    (of_device_is_compatible(pdev->dev.of_node, "aspeed,ast2400-mac") ||
+	     of_device_is_compatible(pdev->dev.of_node, "aspeed,ast2500-mac"))) {
 		/* This driver supports the old MDIO interface */
 		reg = ioread32(priv->base + FTGMAC100_OFFSET_REVR);
 		reg &= ~FTGMAC100_REVR_NEW_MDIO_INTERFACE;
@@ -1269,9 +1502,9 @@ static int ftgmac100_setup_mdio(struct net_device *netdev)
 	priv->mii_bus->name = "ftgmac100_mdio";
 	snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "%s-%d",
 		 pdev->name, pdev->id);
-	priv->mii_bus->priv = priv->netdev;
-	priv->mii_bus->read = ftgmac100_mdiobus_read;
-	priv->mii_bus->write = ftgmac100_mdiobus_write;
+	priv->mii_bus->priv = priv->ndev;
+	priv->mii_bus->read = ftgmac100_mii_read;
+	priv->mii_bus->write = ftgmac100_mii_write;
 
 	for (i = 0; i < PHY_MAX_ADDR; i++)
 		priv->mii_bus->irq[i] = PHY_POLL;
@@ -1297,14 +1530,14 @@ err_register_mdiobus:
 	return err;
 }
 
-static void ftgmac100_destroy_mdio(struct net_device *netdev)
+static void ftgmac100_destroy_mdio(struct net_device *ndev)
 {
-	struct ftgmac100 *priv = netdev_priv(netdev);
+	struct ftgmac100 *priv = netdev_priv(ndev);
 
-	if (!netdev->phydev)
+	if (!ndev->phydev)
 		return;
 
-	phy_disconnect(netdev->phydev);
+	phy_disconnect(ndev->phydev);
 	mdiobus_unregister(priv->mii_bus);
 	mdiobus_free(priv->mii_bus);
 }
@@ -1318,16 +1551,48 @@ static void ftgmac100_ncsi_handler(struct ncsi_dev *nd)
 		    nd->link_up ? "up" : "down");
 }
 
-/******************************************************************************
- * struct platform_driver functions
- *****************************************************************************/
+static void ftgmac100_initial_mac(struct ftgmac100 *priv)
+{
+	u8 mac[ETH_ALEN];
+	unsigned int m;
+	unsigned int l;
+	void *addr;
+
+	addr = device_get_mac_address(priv->dev, mac, ETH_ALEN);
+	if (addr) {
+		ether_addr_copy(priv->ndev->dev_addr, mac);
+		dev_info(priv->dev, "Read MAC address %pM from device tree\n",
+			 mac);
+		return;
+	}
+
+	m = ioread32(priv->base + FTGMAC100_OFFSET_MAC_MADR);
+	l = ioread32(priv->base + FTGMAC100_OFFSET_MAC_LADR);
+
+	mac[0] = (m >> 8) & 0xff;
+	mac[1] = m & 0xff;
+	mac[2] = (l >> 24) & 0xff;
+	mac[3] = (l >> 16) & 0xff;
+	mac[4] = (l >> 8) & 0xff;
+	mac[5] = l & 0xff;
+
+	if (is_valid_ether_addr(mac)) {
+		ether_addr_copy(priv->ndev->dev_addr, mac);
+		dev_info(priv->dev, "Read MAC address %pM from chip\n", mac);
+	} else {
+		eth_hw_addr_random(priv->ndev);
+		dev_info(priv->dev, "Generated random MAC address %pM\n",
+			 priv->ndev->dev_addr);
+	}
+}
+
 static int ftgmac100_probe(struct platform_device *pdev)
 {
 	struct resource *res;
-	int irq;
-	struct net_device *netdev;
+	struct net_device *ndev;
 	struct ftgmac100 *priv;
-	int err = 0;
+	struct device_node *np;
+	int irq, err = 0;
 
 	if (!pdev)
 		return -ENODEV;
@@ -1341,26 +1606,31 @@ static int ftgmac100_probe(struct platform_device *pdev)
 		return irq;
 
 	/* setup net_device */
-	netdev = alloc_etherdev(sizeof(*priv));
-	if (!netdev) {
+	ndev = alloc_etherdev(sizeof(*priv));
+	if (!ndev) {
 		err = -ENOMEM;
 		goto err_alloc_etherdev;
 	}
 
-	SET_NETDEV_DEV(netdev, &pdev->dev);
+	SET_NETDEV_DEV(ndev, &pdev->dev);
 
-	netdev->ethtool_ops = &ftgmac100_ethtool_ops;
-	netdev->netdev_ops = &ftgmac100_netdev_ops;
+	ndev->ethtool_ops = &ftgmac100_ethtool_ops;
+	ndev->netdev_ops = &ftgmac100_netdev_ops;
+	ndev->irq = irq;
 
-	platform_set_drvdata(pdev, netdev);
+	platform_set_drvdata(pdev, ndev);
 
 	/* setup private data */
-	priv = netdev_priv(netdev);
-	priv->netdev = netdev;
+	priv = netdev_priv(ndev);
+	priv->ndev = ndev;
 	priv->dev = &pdev->dev;
+	priv->maht1 = 0;
+	priv->maht0 = 0;
+	INIT_WORK(&priv->reset_task, ftgmac100_reset_task);
 
-	if (of_machine_is_compatible("aspeed,ast2400") ||
-	    of_machine_is_compatible("aspeed,ast2500")) {
+	np = pdev->dev.of_node;
+	if (np && (of_device_is_compatible(np, "aspeed,ast2400-mac") ||
+		   of_device_is_compatible(np, "aspeed,ast2500-mac"))) {
 		priv->rxdes0_edorr_mask = BIT(30);
 		priv->txdes0_edotr_mask = BIT(30);
 	} else {
@@ -1368,11 +1638,6 @@ static int ftgmac100_probe(struct platform_device *pdev)
 		priv->txdes0_edotr_mask = BIT(15);
 	}
 
-	spin_lock_init(&priv->tx_lock);
-
-	/* initialize NAPI */
-	netif_napi_add(netdev, &priv->napi, ftgmac100_poll, 64);
-
 	/* map io memory */
 	priv->res = request_mem_region(res->start, resource_size(res),
 				       dev_name(&pdev->dev));
@@ -1389,19 +1654,15 @@ static int ftgmac100_probe(struct platform_device *pdev)
 		goto err_ioremap;
 	}
 
-	priv->irq = irq;
+	/* Enable pause */
+	priv->tx_pause = true;
+	priv->rx_pause = true;
+	priv->aneg_pause = true;
 
 	/* MAC address from chip or random one */
-	ftgmac100_setup_mac(priv);
-
-	priv->int_mask_all = (FTGMAC100_INT_RPKT_LOST |
-			      FTGMAC100_INT_XPKT_ETH |
-			      FTGMAC100_INT_XPKT_LOST |
-			      FTGMAC100_INT_AHB_ERR |
-			      FTGMAC100_INT_RPKT_BUF |
-			      FTGMAC100_INT_NO_RXBUF);
-	if (pdev->dev.of_node &&
-	    of_get_property(pdev->dev.of_node, "use-ncsi", NULL)) {
+	ftgmac100_initial_mac(priv);
+
+	if (np && of_get_property(np, "use-ncsi", NULL)) {
 		if (!IS_ENABLED(CONFIG_NET_NCSI)) {
 			dev_err(&pdev->dev, "NCSI stack not enabled\n");
 			goto err_ncsi_dev;
@@ -1409,67 +1670,81 @@ static int ftgmac100_probe(struct platform_device *pdev)
 
 		dev_info(&pdev->dev, "Using NCSI interface\n");
 		priv->use_ncsi = true;
-		priv->ndev = ncsi_register_dev(netdev, ftgmac100_ncsi_handler);
+		priv->ncsidev = ncsi_register_dev(ndev, ftgmac100_ncsi_handler);
 		if (!priv->ndev)
 			goto err_ncsi_dev;
 	} else {
 		priv->use_ncsi = false;
-		err = ftgmac100_setup_mdio(netdev);
+		err = ftgmac100_setup_mdio(ndev);
 		if (err)
 			goto err_setup_mdio;
 	}
 
-	/* We have to disable on-chip IP checksum functionality
-	 * when NCSI is enabled on the interface. It doesn't work
-	 * in that case.
-	 */
-	netdev->features = NETIF_F_IP_CSUM | NETIF_F_GRO;
-	if (priv->use_ncsi &&
-	    of_get_property(pdev->dev.of_node, "no-hw-checksum", NULL))
-		netdev->features &= ~NETIF_F_IP_CSUM;
+	/* Default ring sizes */
+	priv->rx_q_entries = priv->new_rx_q_entries = DEF_RX_QUEUE_ENTRIES;
+	priv->tx_q_entries = priv->new_tx_q_entries = DEF_TX_QUEUE_ENTRIES;
 
+	/* Setup feature set */
+	ndev->hw_features = NETIF_F_RXCSUM | NETIF_F_GRO | NETIF_F_SG;
+	if (np && of_device_is_compatible(np, "aspeed,ast2500-mac"))
+		ndev->hw_features |= NETIF_F_IP_CSUM;
+	if (np && of_get_property(np, "no-hw-checksum", NULL))
+		ndev->hw_features &= ~(NETIF_F_IP_CSUM | NETIF_F_RXCSUM);
+	ndev->features |= ndev->hw_features;
 
 	/* register network device */
-	err = register_netdev(netdev);
+	err = register_netdev(ndev);
 	if (err) {
 		dev_err(&pdev->dev, "Failed to register netdev\n");
 		goto err_register_netdev;
 	}
 
-	netdev_info(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base);
+	netdev_info(ndev, "irq %d, mapped at %p\n", ndev->irq, priv->base);
 
 	return 0;
 
 err_ncsi_dev:
 err_register_netdev:
-	ftgmac100_destroy_mdio(netdev);
+	ftgmac100_destroy_mdio(ndev);
 err_setup_mdio:
 	iounmap(priv->base);
 err_ioremap:
 	release_resource(priv->res);
 err_req_mem:
-	netif_napi_del(&priv->napi);
-	free_netdev(netdev);
+	free_netdev(ndev);
 err_alloc_etherdev:
 	return err;
 }
 
 static int __exit ftgmac100_remove(struct platform_device *pdev)
 {
-	struct net_device *netdev;
+	struct net_device *ndev;
 	struct ftgmac100 *priv;
 
-	netdev = platform_get_drvdata(pdev);
-	priv = netdev_priv(netdev);
+	ndev = platform_get_drvdata(pdev);
+	priv = netdev_priv(ndev);
+
+	/* Preent the reset task from kicking early on */
+	priv->stopping = true;
 
-	unregister_netdev(netdev);
-	ftgmac100_destroy_mdio(netdev);
+	/*
+	 * Close & unregister the netdevice, at this points
+	 * the interrupt will be disabled
+	 */
+	unregister_netdev(ndev);
+
+	/*
+	 * There's a small chance the reset task will have been re-queued,
+	 * make sure it's gone before we free the structure
+	 */
+	/* Kill any pending one */
+	cancel_work_sync(&priv->reset_task);
+
+	ftgmac100_destroy_mdio(ndev);
 
 	iounmap(priv->base);
 	release_resource(priv->res);
-
-	netif_napi_del(&priv->napi);
-	free_netdev(netdev);
+	free_netdev(ndev);
 	return 0;
 }
 
diff --git a/drivers/net/ethernet/faraday/ftgmac100.h b/drivers/net/ethernet/faraday/ftgmac100.h
index a7ce0ac..dd41763 100644
--- a/drivers/net/ethernet/faraday/ftgmac100.h
+++ b/drivers/net/ethernet/faraday/ftgmac100.h
@@ -86,6 +86,19 @@
 #define FTGMAC100_INT_PHYSTS_CHG	(1 << 9)
 #define FTGMAC100_INT_NO_HPTXBUF	(1 << 10)
 
+/* These are all the interrupts we care about */
+#define FTGMAC100_INT_ALL (FTGMAC100_INT_RPKT_LOST |	\
+			   FTGMAC100_INT_XPKT_ETH |	\
+			   FTGMAC100_INT_XPKT_LOST |	\
+			   FTGMAC100_INT_AHB_ERR |	\
+			   FTGMAC100_INT_RPKT_BUF |	\
+			   FTGMAC100_INT_NO_RXBUF)
+
+/* These are the interrupts we care about in NAPI mode */
+#define FTGMAC100_INT_BAD (FTGMAC100_INT_RPKT_LOST |	\
+			   FTGMAC100_INT_AHB_ERR |	\
+			   FTGMAC100_INT_NO_RXBUF)
+
 /*
  * Interrupt timer control register
  */
@@ -185,6 +198,13 @@
 #define FTGMAC100_PHYDATA_MIIRDATA(phydata)	(((phydata) >> 16) & 0xffff)
 
 /*
+ * Flow control register
+ */
+#define FTGMAC100_FCR_FC_EN		(1 << 0)
+#define FTGMAC100_FCR_FCTHR_EN		(1 << 2)
+#define FTGMAC100_FCR_PAUSE_TIME(x)	(((x) & 0xffff) << 16)
+
+/*
  * Transmit descriptor, aligned to 16 bytes
  */
 struct ftgmac100_txdes {
@@ -209,6 +229,79 @@ struct ftgmac100_txdes {
 #define FTGMAC100_TXDES1_TX2FIC		(1 << 30)
 #define FTGMAC100_TXDES1_TXIC		(1 << 31)
 
+static inline bool ftgmac100_txdes_owned_by_dma(struct ftgmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTGMAC100_TXDES0_TXDMA_OWN);
+}
+
+static inline void ftgmac100_txdes_set_dma_own(struct ftgmac100_txdes *txdes)
+{
+	txdes->txdes0 |= cpu_to_le32(FTGMAC100_TXDES0_TXDMA_OWN);
+}
+
+static inline void ftgmac100_txdes_set_first_segment(struct ftgmac100_txdes *txdes)
+{
+	txdes->txdes0 |= cpu_to_le32(FTGMAC100_TXDES0_FTS);
+}
+
+static inline bool ftgmac100_txdes_get_first_segment(struct ftgmac100_txdes *txdes)
+{
+	return (txdes->txdes0 & cpu_to_le32(FTGMAC100_TXDES0_FTS)) != 0;
+}
+
+static inline void ftgmac100_txdes_set_last_segment(struct ftgmac100_txdes *txdes)
+{
+	txdes->txdes0 |= cpu_to_le32(FTGMAC100_TXDES0_LTS);
+}
+
+static inline bool ftgmac100_txdes_get_last_segment(struct ftgmac100_txdes *txdes)
+{
+	return (txdes->txdes0 & cpu_to_le32(FTGMAC100_TXDES0_LTS)) != 0;
+}
+
+static inline void ftgmac100_txdes_set_buffer_size(struct ftgmac100_txdes *txdes,
+					    unsigned int len)
+{
+	txdes->txdes0 |= cpu_to_le32(FTGMAC100_TXDES0_TXBUF_SIZE(len));
+}
+
+static inline unsigned int ftgmac100_txdes_get_buffer_size(struct ftgmac100_txdes *txdes)
+{
+	return FTGMAC100_TXDES0_TXBUF_SIZE(cpu_to_le32(txdes->txdes0));
+}
+
+
+static inline void ftgmac100_txdes_set_txint(struct ftgmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTGMAC100_TXDES1_TXIC);
+}
+
+static inline void ftgmac100_txdes_set_tcpcs(struct ftgmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTGMAC100_TXDES1_TCP_CHKSUM);
+}
+
+static inline void ftgmac100_txdes_set_udpcs(struct ftgmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTGMAC100_TXDES1_UDP_CHKSUM);
+}
+
+static inline void ftgmac100_txdes_set_ipcs(struct ftgmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTGMAC100_TXDES1_IP_CHKSUM);
+}
+
+static inline void ftgmac100_txdes_set_dma_addr(struct ftgmac100_txdes *txdes,
+						dma_addr_t addr)
+{
+	txdes->txdes3 = cpu_to_le32(addr);
+}
+
+static inline dma_addr_t ftgmac100_txdes_get_dma_addr(struct ftgmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes3);
+}
+
 /*
  * Receive descriptor, aligned to 16 bytes
  */
@@ -235,11 +328,11 @@ struct ftgmac100_rxdes {
 #define FTGMAC100_RXDES0_RXPKT_RDY	(1 << 31)
 
 #define FTGMAC100_RXDES1_VLANTAG_CI	0xffff
-#define FTGMAC100_RXDES1_PROT_MASK	(0x3 << 20)
-#define FTGMAC100_RXDES1_PROT_NONIP	(0x0 << 20)
-#define FTGMAC100_RXDES1_PROT_IP	(0x1 << 20)
-#define FTGMAC100_RXDES1_PROT_TCPIP	(0x2 << 20)
-#define FTGMAC100_RXDES1_PROT_UDPIP	(0x3 << 20)
+#define FTGMAC100_RXDES1_PROT(x)	(((x) >> 20) & 3)
+#define   FTGMAC100_PROT_NONIP	0
+#define   FTGMAC100_PROT_IP	1
+#define   FTGMAC100_PROT_TCPIP	2
+#define   FTGMAC100_PROT_UDPIP	3
 #define FTGMAC100_RXDES1_LLC		(1 << 22)
 #define FTGMAC100_RXDES1_DF		(1 << 23)
 #define FTGMAC100_RXDES1_VLANTAG_AVAIL	(1 << 24)
@@ -247,4 +340,84 @@ struct ftgmac100_rxdes {
 #define FTGMAC100_RXDES1_UDP_CHKSUM_ERR	(1 << 26)
 #define FTGMAC100_RXDES1_IP_CHKSUM_ERR	(1 << 27)
 
+static bool ftgmac100_rxdes_first_segment(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_FRS);
+}
+
+static bool ftgmac100_rxdes_last_segment(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_LRS);
+}
+
+static bool ftgmac100_rxdes_packet_ready(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_RXPKT_RDY);
+}
+
+#define RXDES0_ANY_ERROR		  \
+	FTGMAC100_RXDES0_RX_ERR		| \
+	FTGMAC100_RXDES0_CRC_ERR	| \
+	FTGMAC100_RXDES0_FTL		| \
+	FTGMAC100_RXDES0_RUNT		| \
+	FTGMAC100_RXDES0_RX_ODD_NB
+
+static inline bool ftgmac100_rxdes_any_error(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(RXDES0_ANY_ERROR);
+}
+
+static inline bool ftgmac100_rxdes_rx_error(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_RX_ERR);
+}
+
+static inline bool ftgmac100_rxdes_crc_error(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_CRC_ERR);
+}
+
+static inline bool ftgmac100_rxdes_frame_too_long(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_FTL);
+}
+
+static inline bool ftgmac100_rxdes_runt(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_RUNT);
+}
+
+static inline bool ftgmac100_rxdes_odd_nibble(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_RX_ODD_NB);
+}
+
+static inline unsigned int ftgmac100_rxdes_data_length(struct ftgmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTGMAC100_RXDES0_VDBC;
+}
+
+static inline bool ftgmac100_rxdes_multicast(struct ftgmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTGMAC100_RXDES0_MULTICAST);
+}
+
+static inline void ftgmac100_rxdes_set_dma_addr(struct ftgmac100_rxdes *rxdes,
+					 dma_addr_t addr)
+{
+	rxdes->rxdes3 = cpu_to_le32(addr);
+}
+
+static inline dma_addr_t ftgmac100_rxdes_get_dma_addr(struct ftgmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes3);
+}
+
+static inline bool ftgmac100_rxdes_csum_err(struct ftgmac100_rxdes *rxdes)
+{
+	return !!(rxdes->rxdes1 & cpu_to_le32(FTGMAC100_RXDES1_TCP_CHKSUM_ERR |
+					      FTGMAC100_RXDES1_UDP_CHKSUM_ERR |
+					      FTGMAC100_RXDES1_IP_CHKSUM_ERR));
+}
+
 #endif /* __FTGMAC100_H */



More information about the openbmc mailing list