All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
@ 2011-01-13 11:49 Po-Yu Chuang
  2011-01-13 14:03 ` Ben Hutchings
                   ` (4 more replies)
  0 siblings, 5 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-13 11:49 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, Po-Yu Chuang

From: Po-Yu Chuang <ratbert@faraday-tech.com>

FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII.  This driver has been working on some ARM/NDS32 SoC including
Faraday A320 and Andes AG101.

Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
---
 drivers/net/Kconfig    |    9 +
 drivers/net/Makefile   |    1 +
 drivers/net/ftmac100.c | 1341 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/ftmac100.h |  180 +++++++
 4 files changed, 1531 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/ftmac100.c
 create mode 100644 drivers/net/ftmac100.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 3fda24a..0720acc 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2014,6 +2014,15 @@ config BCM63XX_ENET
 	  This driver supports the ethernet MACs in the Broadcom 63xx
 	  MIPS chipset family (BCM63XX).
 
+config FTMAC100
+	tristate "Faraday FTMAC100 10/100 Ethernet support"
+	depends on ARM
+	select MII
+	help
+	  This driver supports the FTMAC100 Ethernet controller from
+	  Faraday. It is used on Faraday A320, Andes AG101, AG101P
+	  and some other ARM/NDS32 SoC's.
+
 source "drivers/net/fs_enet/Kconfig"
 
 source "drivers/net/octeon/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..7c21711 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_FORCEDETH) += forcedeth.o
 obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
 obj-$(CONFIG_AX88796) += ax88796.o
 obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
+obj-$(CONFIG_FTMAC100) += ftmac100.o
 
 obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
new file mode 100644
index 0000000..97d1f8d
--- /dev/null
+++ b/drivers/net/ftmac100.c
@@ -0,0 +1,1341 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#include "ftmac100.h"
+
+#define USE_NAPI
+
+#define DRV_NAME	"ftmac100"
+#define DRV_VERSION	"0.1"
+
+#define RX_QUEUE_ENTRIES	128	/* must be power of 2 */
+#define TX_QUEUE_ENTRIES	16	/* must be power of 2 */
+
+#define MAX_PKT_SIZE		1518
+#define RX_BUF_SIZE		2044	/* must be smaller than 0x7ff */
+
+/******************************************************************************
+ * priveate data
+ *****************************************************************************/
+struct ftmac100_descs {
+	struct ftmac100_rxdes	rxdes[RX_QUEUE_ENTRIES];
+	struct ftmac100_txdes	txdes[TX_QUEUE_ENTRIES];
+};
+
+struct ftmac100 {
+	struct resource		*res;
+	void			*base;
+	int			irq;
+
+	struct ftmac100_descs	*descs;
+	dma_addr_t		descs_dma_addr;
+
+	unsigned int		rx_pointer;
+	unsigned int		tx_clean_pointer;
+	unsigned int		tx_pointer;
+	unsigned int		tx_pending;
+
+	spinlock_t		hw_lock;
+	spinlock_t		rx_lock;
+	spinlock_t		tx_lock;
+
+	struct net_device	*netdev;
+	struct device		*dev;
+#ifdef USE_NAPI
+	struct napi_struct	napi;
+#endif
+
+	struct net_device_stats	stats;
+
+	struct mii_if_info	mii;
+};
+
+/******************************************************************************
+ * internal functions (hardware register access)
+ *****************************************************************************/
+#define INT_MASK_RX_DISABLED	(FTMAC100_INT_XPKT_OK		|	\
+				 FTMAC100_INT_XPKT_LOST		|	\
+				 FTMAC100_INT_RPKT_LOST		|	\
+				 FTMAC100_INT_AHB_ERR		|	\
+				 FTMAC100_INT_PHYSTS_CHG)
+
+#define INT_MASK_ALL_ENABLED	(INT_MASK_RX_DISABLED		|	\
+				 FTMAC100_INT_RPKT_FINISH	|	\
+				 FTMAC100_INT_NORXBUF)
+
+#define INT_MASK_ALL_DISABLED	0
+
+#ifdef USE_NAPI
+static inline void ftmac100_disable_rxint(struct ftmac100 *priv)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	iowrite32(INT_MASK_RX_DISABLED, priv->base + FTMAC100_OFFSET_IMR);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+}
+#endif
+
+static inline void ftmac100_enable_all_int(struct ftmac100 *priv)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTMAC100_OFFSET_IMR);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+}
+
+static inline void ftmac100_disable_all_int(struct ftmac100 *priv)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	iowrite32(INT_MASK_ALL_DISABLED, priv->base + FTMAC100_OFFSET_IMR);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+}
+
+static inline void ftmac100_set_receive_ring_base(struct ftmac100 *priv,
+		dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
+}
+
+static inline void ftmac100_set_transmit_ring_base(struct ftmac100 *priv,
+		dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_TXR_BADR);
+}
+
+static inline void ftmac100_txdma_start_polling(struct ftmac100 *priv)
+{
+	iowrite32(1, priv->base + FTMAC100_OFFSET_TXPD);
+}
+
+static int ftmac100_reset(struct ftmac100 *priv)
+{
+	struct device *dev = &priv->netdev->dev;
+	unsigned long flags;
+	int i;
+
+	/* NOTE: reset clears all registers */
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+
+	for (i = 0; i < 5; i++) {
+		int maccr;
+
+		spin_lock_irqsave(&priv->hw_lock, flags);
+		maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
+		spin_unlock_irqrestore(&priv->hw_lock, flags);
+		if (!(maccr & FTMAC100_MACCR_SW_RST)) {
+			/*
+			 * FTMAC100_MACCR_SW_RST cleared does not indicate
+			 * that hardware reset completed (what the f*ck).
+			 * We still need to wait for a while.
+			 */
+			usleep_range(500, 1000);
+			return 0;
+		}
+
+		usleep_range(1000, 10000);
+	}
+
+	dev_err(dev, "software reset failed\n");
+	return -EIO;
+}
+
+static void ftmac100_set_mac(struct ftmac100 *priv, const unsigned char *mac)
+{
+	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 + FTMAC100_OFFSET_MAC_MADR);
+	iowrite32(laddr, priv->base + FTMAC100_OFFSET_MAC_LADR);
+}
+
+static int ftmac100_start_hw(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	unsigned long flags;
+	int maccr;
+
+	if (ftmac100_reset(priv))
+		return -EIO;
+
+	/* setup ring buffer base registers */
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	ftmac100_set_receive_ring_base(priv,
+		priv->descs_dma_addr + offsetof(struct ftmac100_descs, rxdes));
+	ftmac100_set_transmit_ring_base(priv,
+		priv->descs_dma_addr + offsetof(struct ftmac100_descs, txdes));
+
+	iowrite32(FTMAC100_APTC_RXPOLL_CNT(1),
+		priv->base + FTMAC100_OFFSET_APTC);
+
+	ftmac100_set_mac(priv, netdev->dev_addr);
+
+	maccr = FTMAC100_MACCR_XMT_EN |
+		FTMAC100_MACCR_RCV_EN |
+		FTMAC100_MACCR_XDMA_EN |
+		FTMAC100_MACCR_RDMA_EN |
+		FTMAC100_MACCR_CRC_APD |
+		FTMAC100_MACCR_FULLDUP |
+		FTMAC100_MACCR_RX_RUNT |
+		FTMAC100_MACCR_RX_BROADPKT;
+
+	iowrite32(maccr, priv->base + FTMAC100_OFFSET_MACCR);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+
+	return 0;
+}
+
+static void ftmac100_stop_hw(struct ftmac100 *priv)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	iowrite32(0, priv->base + FTMAC100_OFFSET_MACCR);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+}
+
+/******************************************************************************
+ * internal functions (receive descriptor)
+ *****************************************************************************/
+static inline int ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_FRS;
+}
+
+static inline int ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_LRS;
+}
+
+static inline int ftmac100_rxdes_owned_by_dma(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RXDMA_OWN;
+}
+
+static inline void ftmac100_rxdes_set_dma_own(struct ftmac100_rxdes *rxdes)
+{
+	/* clear status bits */
+	rxdes->rxdes0 = FTMAC100_RXDES0_RXDMA_OWN;
+}
+
+static inline int ftmac100_rxdes_rx_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RX_ERR;
+}
+
+static inline int ftmac100_rxdes_crc_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_CRC_ERR;
+}
+
+static inline int ftmac100_rxdes_frame_too_long(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_FTL;
+}
+
+static inline int ftmac100_rxdes_runt(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RUNT;
+}
+
+static inline int ftmac100_rxdes_odd_nibble(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RX_ODD_NB;
+}
+
+static inline unsigned int ftmac100_rxdes_frame_length(
+		struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RFL;
+}
+
+static inline int ftmac100_rxdes_multicast(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_MULTICAST;
+}
+
+static inline void ftmac100_rxdes_set_buffer_size(struct ftmac100_rxdes *rxdes,
+		unsigned int size)
+{
+	rxdes->rxdes1 = (rxdes->rxdes1 & FTMAC100_RXDES1_EDORR) |
+			 FTMAC100_RXDES1_RXBUF_SIZE(size);
+}
+
+static inline void ftmac100_rxdes_set_end_of_ring(struct ftmac100_rxdes *rxdes)
+{
+	rxdes->rxdes1 |= FTMAC100_RXDES1_EDORR;
+}
+
+static inline void ftmac100_rxdes_set_dma_addr(struct ftmac100_rxdes *rxdes,
+		dma_addr_t addr)
+{
+	rxdes->rxdes2 = addr;
+}
+
+static inline dma_addr_t ftmac100_rxdes_get_dma_addr(
+		struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes2;
+}
+
+/* rxdes3 is not used by hardware, we use it to keep track of buffer */
+static inline void ftmac100_rxdes_set_va(struct ftmac100_rxdes *rxdes,
+		void *addr)
+{
+	rxdes->rxdes3 = (unsigned int)addr;
+}
+
+static inline void *ftmac100_rxdes_get_va(struct ftmac100_rxdes *rxdes)
+{
+	return (void *)rxdes->rxdes3;
+}
+
+/******************************************************************************
+ * internal functions (receive)
+ *****************************************************************************/
+static inline int ftmac100_next_rx_pointer(int pointer)
+{
+	return (pointer + 1) & (RX_QUEUE_ENTRIES - 1);
+}
+
+static inline void ftmac100_rx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->rx_pointer = ftmac100_next_rx_pointer(priv->rx_pointer);
+}
+
+static inline struct ftmac100_rxdes *ftmac100_current_rxdes(
+		struct ftmac100 *priv)
+{
+	return &priv->descs->rxdes[priv->rx_pointer];
+}
+
+static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
+		struct ftmac100 *priv)
+{
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+
+	while (!ftmac100_rxdes_owned_by_dma(rxdes)) {
+		if (ftmac100_rxdes_first_segment(rxdes))
+			return rxdes;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	}
+
+	return NULL;
+}
+
+static int ftmac100_rx_packet_error(struct ftmac100 *priv,
+		struct ftmac100_rxdes *rxdes)
+{
+	struct device *dev = &priv->netdev->dev;
+	int error = 0;
+
+	if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
+		if (printk_ratelimit())
+			dev_info(dev, "rx err\n");
+
+		priv->stats.rx_errors++;
+		error = 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_crc_error(rxdes))) {
+		if (printk_ratelimit())
+			dev_info(dev, "rx crc err\n");
+
+		priv->stats.rx_crc_errors++;
+		error = 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
+		if (printk_ratelimit())
+			dev_info(dev, "rx frame too long\n");
+
+		priv->stats.rx_length_errors++;
+		error = 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_runt(rxdes))) {
+		if (printk_ratelimit())
+			dev_info(dev, "rx runt\n");
+
+		priv->stats.rx_length_errors++;
+		error = 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_odd_nibble(rxdes))) {
+		if (printk_ratelimit())
+			dev_info(dev, "rx odd nibble\n");
+
+		priv->stats.rx_length_errors++;
+		error = 1;
+	}
+
+	return error;
+}
+
+static void ftmac100_rx_drop_packet(struct ftmac100 *priv)
+{
+	struct device *dev = &priv->netdev->dev;
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+	int done = 0;
+
+	if (printk_ratelimit())
+		dev_dbg(dev, "drop packet %p\n", rxdes);
+
+	do {
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = 1;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && !ftmac100_rxdes_owned_by_dma(rxdes));
+
+	priv->stats.rx_dropped++;
+}
+
+static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+	struct net_device *netdev = priv->netdev;
+	struct device *dev = &netdev->dev;
+	unsigned long flags;
+	struct ftmac100_rxdes *rxdes;
+	struct sk_buff *skb;
+	int length;
+	int copied = 0;
+	int done = 0;
+
+	spin_lock_irqsave(&priv->rx_lock, flags);
+	rxdes = ftmac100_rx_locate_first_segment(priv);
+	spin_unlock_irqrestore(&priv->rx_lock, flags);
+	if (!rxdes)
+		return 0;
+
+	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+		spin_lock_irqsave(&priv->rx_lock, flags);
+		ftmac100_rx_drop_packet(priv);
+		spin_unlock_irqrestore(&priv->rx_lock, flags);
+		return 1;
+	}
+
+	/* start processing */
+
+	length = ftmac100_rxdes_frame_length(rxdes);
+
+	skb = dev_alloc_skb(length + NET_IP_ALIGN);
+	if (unlikely(!skb)) {
+		if (printk_ratelimit())
+			dev_err(dev, "rx skb alloc failed\n");
+
+		spin_lock_irqsave(&priv->rx_lock, flags);
+		ftmac100_rx_drop_packet(priv);
+		spin_unlock_irqrestore(&priv->rx_lock, flags);
+		return 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_multicast(rxdes)))
+		priv->stats.multicast++;
+
+	skb_reserve(skb, NET_IP_ALIGN);
+
+	do {
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *buf = ftmac100_rxdes_get_va(rxdes);
+		int size;
+
+		size = min(length - copied, RX_BUF_SIZE);
+
+		dma_sync_single_for_cpu(priv->dev, d, RX_BUF_SIZE,
+			DMA_FROM_DEVICE);
+		memcpy(skb_put(skb, size), buf, size);
+
+		copied += size;
+
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = 1;
+
+		dma_sync_single_for_device(priv->dev, d, RX_BUF_SIZE,
+			DMA_FROM_DEVICE);
+
+		spin_lock_irqsave(&priv->rx_lock, flags);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+
+		spin_unlock_irqrestore(&priv->rx_lock, flags);
+	} while (!done && copied < length);
+
+	skb->protocol = eth_type_trans(skb, netdev);
+
+	/* push packet to protocol stack */
+
+#ifdef USE_NAPI
+	netif_receive_skb(skb);
+#else
+	netif_rx(skb);
+#endif
+
+	netdev->last_rx = jiffies;
+
+	priv->stats.rx_packets++;
+	priv->stats.rx_bytes += skb->len;
+
+	(*processed)++;
+
+	return 1;
+}
+
+/******************************************************************************
+ * internal functions (transmit descriptor)
+ *****************************************************************************/
+static inline void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
+{
+	/* clear all except end of ring bit */
+	txdes->txdes0 = 0;
+	txdes->txdes1 &= FTMAC100_TXDES1_EDOTR;
+	txdes->txdes2 = 0;
+	txdes->txdes3 = 0;
+}
+
+static inline int ftmac100_txdes_owned_by_dma(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & FTMAC100_TXDES0_TXDMA_OWN;
+}
+
+static inline void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes0 |= FTMAC100_TXDES0_TXDMA_OWN;
+}
+
+static inline int ftmac100_txdes_excessive_collision(
+		struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & FTMAC100_TXDES0_TXPKT_EXSCOL;
+}
+
+static inline int ftmac100_txdes_late_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & FTMAC100_TXDES0_TXPKT_LATECOL;
+}
+
+static inline void ftmac100_txdes_set_end_of_ring(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_EDOTR;
+}
+
+static inline void ftmac100_txdes_set_first_segment(
+		struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_FTS;
+}
+
+static inline void ftmac100_txdes_set_last_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_LTS;
+}
+
+static inline void ftmac100_txdes_set_txint(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_TXIC;
+}
+
+static inline void ftmac100_txdes_set_buffer_size(struct ftmac100_txdes *txdes,
+		unsigned int len)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_TXBUF_SIZE(len);
+}
+
+static inline void ftmac100_txdes_set_dma_addr(struct ftmac100_txdes *txdes,
+		dma_addr_t addr)
+{
+	txdes->txdes2 = addr;
+}
+
+static inline dma_addr_t ftmac100_txdes_get_dma_addr(
+		struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes2;
+}
+
+/* txdes3 is not used by hardware, we use it to keep track of socket buffer */
+static inline void ftmac100_txdes_set_skb(struct ftmac100_txdes *txdes,
+		struct sk_buff *skb)
+{
+	txdes->txdes3 = (unsigned int)skb;
+}
+
+static inline struct sk_buff *ftmac100_txdes_get_skb(
+		struct ftmac100_txdes *txdes)
+{
+	return (struct sk_buff *)txdes->txdes3;
+}
+
+/******************************************************************************
+ * internal functions (transmit)
+ *****************************************************************************/
+static inline int ftmac100_next_tx_pointer(int pointer)
+{
+	return (pointer + 1) & (TX_QUEUE_ENTRIES - 1);
+}
+
+static inline void ftmac100_tx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_pointer = ftmac100_next_tx_pointer(priv->tx_pointer);
+}
+
+static inline void ftmac100_tx_clean_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_clean_pointer =
+		ftmac100_next_tx_pointer(priv->tx_clean_pointer);
+}
+
+static inline struct ftmac100_txdes *ftmac100_current_txdes(
+		struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_pointer];
+}
+
+static inline struct ftmac100_txdes *ftmac100_current_clean_txdes(
+		struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_clean_pointer];
+}
+
+static int ftmac100_tx_complete_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	struct sk_buff *skb;
+	dma_addr_t map;
+
+	if (priv->tx_pending == 0)
+		return 0;
+
+	txdes = ftmac100_current_clean_txdes(priv);
+
+	if (ftmac100_txdes_owned_by_dma(txdes))
+		return 0;
+
+	skb = ftmac100_txdes_get_skb(txdes);
+	map = ftmac100_txdes_get_dma_addr(txdes);
+
+	if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
+			ftmac100_txdes_late_collision(txdes))) {
+		/*
+		 * packet transmitted to ethernet lost due to late collision
+		 * or excessive collision
+		 */
+		priv->stats.tx_aborted_errors++;
+	} else {
+		priv->stats.tx_packets++;
+		priv->stats.tx_bytes += skb->len;
+	}
+
+	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+
+	dev_kfree_skb_irq(skb);
+
+	ftmac100_txdes_reset(txdes);
+
+	ftmac100_tx_clean_pointer_advance(priv);
+
+	priv->tx_pending--;
+	netif_wake_queue(netdev);
+
+	return 1;
+}
+
+static void ftmac100_tx_complete(struct ftmac100 *priv)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->tx_lock, flags);
+	while (ftmac100_tx_complete_packet(priv))
+		;
+	spin_unlock_irqrestore(&priv->tx_lock, flags);
+}
+
+static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
+		dma_addr_t map)
+{
+	struct net_device *netdev = priv->netdev;
+	struct device *dev = &netdev->dev;
+	struct ftmac100_txdes *txdes;
+	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
+	unsigned long flags;
+
+	txdes = ftmac100_current_txdes(priv);
+	ftmac100_tx_pointer_advance(priv);
+
+	/* setup TX descriptor */
+
+	spin_lock_irqsave(&priv->tx_lock, flags);
+	ftmac100_txdes_set_skb(txdes, skb);
+	ftmac100_txdes_set_dma_addr(txdes, map);
+
+	ftmac100_txdes_set_first_segment(txdes);
+	ftmac100_txdes_set_last_segment(txdes);
+	ftmac100_txdes_set_txint(txdes);
+	ftmac100_txdes_set_buffer_size(txdes, len);
+
+	priv->tx_pending++;
+	if (priv->tx_pending == TX_QUEUE_ENTRIES) {
+		if (printk_ratelimit())
+			dev_info(dev, "tx queue full\n");
+
+		netif_stop_queue(netdev);
+	}
+
+	/* start transmit */
+
+	wmb();
+	ftmac100_txdes_set_dma_own(txdes);
+	spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	ftmac100_txdma_start_polling(priv);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+	netdev->trans_start = jiffies;
+
+	return NETDEV_TX_OK;
+}
+
+/******************************************************************************
+ * internal functions (buffer)
+ *****************************************************************************/
+static void ftmac100_free_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *page = ftmac100_rxdes_get_va(rxdes);
+
+		if (d)
+			dma_unmap_single(priv->dev, d, PAGE_SIZE,
+				DMA_FROM_DEVICE);
+
+		if (page != NULL)
+			free_page((unsigned long)page);
+	}
+
+	for (i = 0; i < TX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_txdes *txdes = &priv->descs->txdes[i];
+		struct sk_buff *skb = ftmac100_txdes_get_skb(txdes);
+
+		if (skb) {
+			dma_addr_t map;
+
+			map = ftmac100_txdes_get_dma_addr(txdes);
+			dma_unmap_single(priv->dev, map, skb_headlen(skb),
+				DMA_TO_DEVICE);
+			dev_kfree_skb(skb);
+		}
+	}
+
+	dma_free_coherent(priv->dev, sizeof(struct ftmac100_descs),
+		priv->descs, priv->descs_dma_addr);
+}
+
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	priv->descs = dma_alloc_coherent(priv->dev,
+		sizeof(struct ftmac100_descs), &priv->descs_dma_addr,
+		GFP_KERNEL | GFP_DMA);
+	if (priv->descs == NULL)
+		return -ENOMEM;
+
+	memset(priv->descs, 0, sizeof(struct ftmac100_descs));
+
+	/* initialize RX ring */
+
+	ftmac100_rxdes_set_end_of_ring(
+		&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		void *page;
+		dma_addr_t d;
+
+		page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
+		if (page == NULL)
+			goto err;
+
+		d = dma_map_single(priv->dev, page, PAGE_SIZE,
+			DMA_FROM_DEVICE);
+		if (unlikely(dma_mapping_error(priv->dev, d))) {
+			free_page((unsigned long)page);
+			goto err;
+		}
+
+		/*
+		 * The hardware enforces a sub-2K maximum packet size, so we
+		 * put two buffers on every hardware page.
+		 */
+		ftmac100_rxdes_set_va(rxdes, page);
+		ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_dma_addr(rxdes, d);
+		ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
+		ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rxdes_set_dma_own(rxdes + 1);
+	}
+
+	/* initialize TX ring */
+
+	ftmac100_txdes_set_end_of_ring(
+		&priv->descs->txdes[TX_QUEUE_ENTRIES - 1]);
+	return 0;
+
+err:
+	ftmac100_free_buffers(priv);
+	return -ENOMEM;
+}
+
+/******************************************************************************
+ * struct mii_if_info functions
+ *****************************************************************************/
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct device *dev = &netdev->dev;
+	unsigned long flags;
+	int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIRD;
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
+			return phycr & FTMAC100_PHYCR_MIIRDATA;
+
+		usleep_range(100, 1000);
+	}
+
+	dev_err(dev, "mdio read timed out\n");
+	return 0xffff;
+}
+
+static void ftmac100_mdio_write(struct net_device *netdev, int phy_id, int reg,
+		int data)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct device *dev = &netdev->dev;
+	unsigned long flags;
+	int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIWR;
+
+	data = FTMAC100_PHYWDATA_MIIWDATA(data);
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	iowrite32(data, priv->base + FTMAC100_OFFSET_PHYWDATA);
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIWR) == 0)
+			return;
+
+		usleep_range(100, 1000);
+	}
+
+	dev_err(dev, "mdio write timed out\n");
+}
+
+/******************************************************************************
+ * struct ethtool_ops functions
+ *****************************************************************************/
+static void ftmac100_get_drvinfo(struct net_device *netdev,
+		struct ethtool_drvinfo *info)
+{
+	strcpy(info->driver, DRV_NAME);
+	strcpy(info->version, DRV_VERSION);
+	strcpy(info->bus_info, dev_name(&netdev->dev));
+}
+
+static int ftmac100_get_settings(struct net_device *netdev,
+		struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_gset(&priv->mii, cmd);
+}
+
+static int ftmac100_set_settings(struct net_device *netdev,
+		struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_sset(&priv->mii, cmd);
+}
+
+static int ftmac100_nway_reset(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_nway_restart(&priv->mii);
+}
+
+static u32 ftmac100_get_link(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_link_ok(&priv->mii);
+}
+
+static struct ethtool_ops ftmac100_ethtool_ops = {
+	.set_settings		= ftmac100_set_settings,
+	.get_settings		= ftmac100_get_settings,
+	.get_drvinfo		= ftmac100_get_drvinfo,
+	.nway_reset		= ftmac100_nway_reset,
+	.get_link		= ftmac100_get_link,
+};
+
+/******************************************************************************
+ * interrupt handler
+ *****************************************************************************/
+static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct device *dev = &netdev->dev;
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned long flags;
+	unsigned int status;
+	unsigned int imr;
+
+	spin_lock_irqsave(&priv->hw_lock, flags);
+	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
+	imr = ioread32(priv->base + FTMAC100_OFFSET_IMR);
+	spin_unlock_irqrestore(&priv->hw_lock, flags);
+
+	status &= imr;
+	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
+		/*
+		 * FTMAC100_INT_RPKT_FINISH:
+		 *	RX DMA has received packets into RX buffer successfully
+		 *
+		 * FTMAC100_INT_NORXBUF:
+		 *	RX buffer unavailable
+		 */
+#ifdef USE_NAPI
+		/* Disable interrupts for polling */
+		ftmac100_disable_rxint(priv);
+
+		napi_schedule(&priv->napi);
+#else
+		int rx = 0;
+
+		while (ftmac100_rx_packet(priv, &rx))
+			;
+#endif
+	}
+
+	if (status & FTMAC100_INT_NORXBUF) {
+		/* RX buffer unavailable */
+		if (printk_ratelimit())
+			dev_info(dev, "INT_NORXBUF\n");
+
+		priv->stats.rx_over_errors++;
+	}
+
+	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
+		/*
+		 * FTMAC100_INT_XPKT_OK:
+		 *	 packet transmitted to ethernet successfully
+		 *
+		 * FTMAC100_INT_XPKT_LOST:
+		 *	packet transmitted to ethernet lost due to late
+		 *	collision or excessive collision
+		 */
+		ftmac100_tx_complete(priv);
+	}
+
+	if (status & FTMAC100_INT_RPKT_LOST) {
+		/* received packet lost due to RX FIFO full */
+		if (printk_ratelimit())
+			dev_info(dev, "INT_RPKT_LOST\n");
+
+		priv->stats.rx_fifo_errors++;
+	}
+
+	if (status & FTMAC100_INT_AHB_ERR) {
+		/* AHB error */
+		if (printk_ratelimit())
+			dev_info(dev, "INT_AHB_ERR\n");
+
+		/* do nothing */
+	}
+
+	if (status & FTMAC100_INT_PHYSTS_CHG) {
+		/* PHY link status change */
+		if (printk_ratelimit())
+			dev_info(dev, "INT_PHYSTS_CHG\n");
+
+		mii_check_link(&priv->mii);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/******************************************************************************
+ * struct napi_struct functions
+ *****************************************************************************/
+#ifdef USE_NAPI
+static int ftmac100_poll(struct napi_struct *napi, int budget)
+{
+	struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
+	int retry;
+	int rx = 0;
+
+	do {
+		retry = ftmac100_rx_packet(priv, &rx);
+	} while (retry && rx < budget);
+
+	if (!retry || rx < budget) {
+		/* stop polling */
+		napi_complete(napi);
+		ftmac100_enable_all_int(priv);
+	}
+
+	return rx;
+}
+#endif
+
+/******************************************************************************
+ * struct net_device_ops functions
+ *****************************************************************************/
+static int ftmac100_open(struct net_device *netdev)
+{
+	struct device *dev = &netdev->dev;
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int err;
+
+	err = ftmac100_alloc_buffers(priv);
+	if (err) {
+		dev_err(dev, "failed to allocate buffers\n");
+		goto err_alloc;
+	}
+
+	err = request_irq(priv->irq, ftmac100_interrupt, 0, netdev->name,
+		netdev);
+	if (err) {
+		dev_err(dev, "failed to request irq %d\n", priv->irq);
+		goto err_irq;
+	}
+
+	priv->rx_pointer = 0;
+	priv->tx_clean_pointer = 0;
+	priv->tx_pointer = 0;
+	spin_lock_init(&priv->hw_lock);
+	spin_lock_init(&priv->rx_lock);
+	spin_lock_init(&priv->tx_lock);
+	priv->tx_pending = 0;
+
+	err = ftmac100_start_hw(priv);
+	if (err)
+		goto err_hw;
+
+#ifdef USE_NAPI
+	napi_enable(&priv->napi);
+#endif
+	netif_start_queue(netdev);
+
+	ftmac100_enable_all_int(priv);
+
+	return 0;
+
+err_hw:
+	free_irq(priv->irq, netdev);
+err_irq:
+	ftmac100_free_buffers(priv);
+err_alloc:
+	return err;
+}
+
+static int ftmac100_stop(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	ftmac100_disable_all_int(priv);
+	netif_stop_queue(netdev);
+#ifdef USE_NAPI
+	napi_disable(&priv->napi);
+#endif
+	ftmac100_stop_hw(priv);
+	free_irq(priv->irq, netdev);
+	ftmac100_free_buffers(priv);
+
+	return 0;
+}
+
+static int ftmac100_hard_start_xmit(struct sk_buff *skb,
+		struct net_device *netdev)
+{
+	struct device *dev = &netdev->dev;
+	struct ftmac100 *priv = netdev_priv(netdev);
+	dma_addr_t map;
+
+	if (unlikely(skb->len > MAX_PKT_SIZE)) {
+		if (printk_ratelimit())
+			dev_dbg(dev, "tx packet too big\n");
+
+		priv->stats.tx_dropped++;
+		dev_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 (printk_ratelimit())
+			dev_err(dev, "map socket buffer failed\n");
+
+		priv->stats.tx_dropped++;
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	return ftmac100_xmit(priv, skb, map);
+}
+
+static struct net_device_stats *ftmac100_get_stats(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return &priv->stats;
+}
+
+/* optional */
+static int ftmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr,
+		int cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+
+	return generic_mii_ioctl(&priv->mii, data, cmd, NULL);
+}
+
+static struct net_device_ops ftmac100_netdev_ops = {
+	.ndo_open		= ftmac100_open,
+	.ndo_stop		= ftmac100_stop,
+	.ndo_start_xmit		= ftmac100_hard_start_xmit,
+	.ndo_set_mac_address	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_get_stats		= ftmac100_get_stats,
+	.ndo_do_ioctl		= ftmac100_do_ioctl,
+};
+
+/******************************************************************************
+ * struct platform_driver functions
+ *****************************************************************************/
+static int ftmac100_remove(struct platform_device *pdev)
+{
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+
+	netdev = platform_get_drvdata(pdev);
+	if (netdev == NULL)
+		return 0;
+
+	platform_set_drvdata(pdev, NULL);
+
+	priv = netdev_priv(netdev);
+
+	unregister_netdev(netdev);
+
+	if (priv->base != NULL)
+		iounmap(priv->base);
+
+	if (priv->res != NULL)
+		release_resource(priv->res);
+
+	free_netdev(netdev);
+
+	return 0;
+}
+
+static int ftmac100_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int irq;
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+	int err;
+
+	if (!pdev)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENXIO;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/* setup net_device */
+
+	netdev = alloc_etherdev(sizeof(struct ftmac100));
+	if (netdev == NULL) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	SET_NETDEV_DEV(netdev, &pdev->dev);
+	SET_ETHTOOL_OPS(netdev, &ftmac100_ethtool_ops);
+	netdev->netdev_ops = &ftmac100_netdev_ops;
+
+	platform_set_drvdata(pdev, netdev);
+
+	/* setup private data */
+
+	priv = netdev_priv(netdev);
+	priv->netdev = netdev;
+	priv->dev = &pdev->dev;
+
+#ifdef USE_NAPI
+	/* initialize NAPI */
+	netif_napi_add(netdev, &priv->napi, ftmac100_poll, 64);
+#endif
+
+	/* map io memory */
+
+	priv->res = request_mem_region(res->start, res->end - res->start,
+			dev_name(&pdev->dev));
+	if (priv->res == NULL) {
+		dev_err(&pdev->dev, "Could not reserve memory region\n");
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	priv->base = ioremap(res->start, res->end - res->start);
+	if (priv->base == NULL) {
+		dev_err(&pdev->dev, "Failed to ioremap ethernet registers\n");
+		err = -EIO;
+		goto err_out;
+	}
+
+	priv->irq = irq;
+
+	/* initialize struct mii_if_info */
+
+	priv->mii.phy_id	= 0;
+	priv->mii.phy_id_mask	= 0x1f;
+	priv->mii.reg_num_mask	= 0x1f;
+	priv->mii.dev		= netdev;
+	priv->mii.mdio_read	= ftmac100_mdio_read;
+	priv->mii.mdio_write	= ftmac100_mdio_write;
+
+	/* register network device */
+
+	err = register_netdev(netdev);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to register netdev\n");
+		goto err_out;
+	}
+
+	dev_info(&netdev->dev, "irq %d, mapped at %p\n", priv->irq, priv->base);
+
+	if (is_zero_ether_addr(netdev->dev_addr)) {
+		random_ether_addr(netdev->dev_addr);
+		dev_info(&netdev->dev, "generated random MAC address "
+			"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x.\n",
+			netdev->dev_addr[0], netdev->dev_addr[1],
+			netdev->dev_addr[2], netdev->dev_addr[3],
+			netdev->dev_addr[4], netdev->dev_addr[5]);
+	}
+
+	return 0;
+
+err_out:
+	ftmac100_remove(pdev);
+	return err;
+}
+
+static struct platform_driver ftmac100_driver = {
+	.probe		= ftmac100_probe,
+	.remove		= ftmac100_remove,
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+/******************************************************************************
+ * initialization / finalization
+ *****************************************************************************/
+static int __init ftmac100_init(void)
+{
+	printk(KERN_INFO "Loading " DRV_NAME ": version " DRV_VERSION " ...\n");
+	return platform_driver_register(&ftmac100_driver);
+}
+
+static void __exit ftmac100_exit(void)
+{
+	platform_driver_unregister(&ftmac100_driver);
+}
+
+module_init(ftmac100_init);
+module_exit(ftmac100_exit);
+
+MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");
+MODULE_DESCRIPTION("FTMAC100 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ftmac100.h b/drivers/net/ftmac100.h
new file mode 100644
index 0000000..46a0c47
--- /dev/null
+++ b/drivers/net/ftmac100.h
@@ -0,0 +1,180 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __FTMAC100_H
+#define __FTMAC100_H
+
+#define	FTMAC100_OFFSET_ISR		0x00
+#define	FTMAC100_OFFSET_IMR		0x04
+#define	FTMAC100_OFFSET_MAC_MADR	0x08
+#define	FTMAC100_OFFSET_MAC_LADR	0x0c
+#define	FTMAC100_OFFSET_MAHT0		0x10
+#define	FTMAC100_OFFSET_MAHT1		0x14
+#define	FTMAC100_OFFSET_TXPD		0x18
+#define	FTMAC100_OFFSET_RXPD		0x1c
+#define	FTMAC100_OFFSET_TXR_BADR	0x20
+#define	FTMAC100_OFFSET_RXR_BADR	0x24
+#define	FTMAC100_OFFSET_ITC		0x28
+#define	FTMAC100_OFFSET_APTC		0x2c
+#define	FTMAC100_OFFSET_DBLAC		0x30
+#define	FTMAC100_OFFSET_MACCR		0x88
+#define	FTMAC100_OFFSET_MACSR		0x8c
+#define	FTMAC100_OFFSET_PHYCR		0x90
+#define	FTMAC100_OFFSET_PHYWDATA	0x94
+#define	FTMAC100_OFFSET_FCR		0x98
+#define	FTMAC100_OFFSET_BPR		0x9c
+#define	FTMAC100_OFFSET_TS		0xc4
+#define	FTMAC100_OFFSET_DMAFIFOS	0xc8
+#define	FTMAC100_OFFSET_TM		0xcc
+#define	FTMAC100_OFFSET_TX_MCOL_SCOL	0xd4
+#define	FTMAC100_OFFSET_RPF_AEP		0xd8
+#define	FTMAC100_OFFSET_XM_PG		0xdc
+#define	FTMAC100_OFFSET_RUNT_TLCC	0xe0
+#define	FTMAC100_OFFSET_CRCER_FTL	0xe4
+#define	FTMAC100_OFFSET_RLC_RCC		0xe8
+#define	FTMAC100_OFFSET_BROC		0xec
+#define	FTMAC100_OFFSET_MULCA		0xf0
+#define	FTMAC100_OFFSET_RP		0xf4
+#define	FTMAC100_OFFSET_XP		0xf8
+
+/*
+ * Interrupt status register & interrupt mask register
+ */
+#define	FTMAC100_INT_RPKT_FINISH	(1 << 0)
+#define	FTMAC100_INT_NORXBUF		(1 << 1)
+#define	FTMAC100_INT_XPKT_FINISH	(1 << 2)
+#define	FTMAC100_INT_NOTXBUF		(1 << 3)
+#define	FTMAC100_INT_XPKT_OK		(1 << 4)
+#define	FTMAC100_INT_XPKT_LOST		(1 << 5)
+#define	FTMAC100_INT_RPKT_SAV		(1 << 6)
+#define	FTMAC100_INT_RPKT_LOST		(1 << 7)
+#define	FTMAC100_INT_AHB_ERR		(1 << 8)
+#define	FTMAC100_INT_PHYSTS_CHG		(1 << 9)
+
+/*
+ * Interrupt timer control register
+ */
+#define FTMAC100_ITC_RXINT_CNT(x)	(((x) & 0xf) << 0)
+#define FTMAC100_ITC_RXINT_THR(x)	(((x) & 0x7) << 4)
+#define FTMAC100_ITC_RXINT_TIME_SEL	(1 << 7)
+#define FTMAC100_ITC_TXINT_CNT(x)	(((x) & 0xf) << 8)
+#define FTMAC100_ITC_TXINT_THR(x)	(((x) & 0x7) << 12)
+#define FTMAC100_ITC_TXINT_TIME_SEL	(1 << 15)
+
+/*
+ * Automatic polling timer control register
+ */
+#define	FTMAC100_APTC_RXPOLL_CNT(x)	(((x) & 0xf) << 0)
+#define	FTMAC100_APTC_RXPOLL_TIME_SEL	(1 << 4)
+#define	FTMAC100_APTC_TXPOLL_CNT(x)	(((x) & 0xf) << 8)
+#define	FTMAC100_APTC_TXPOLL_TIME_SEL	(1 << 12)
+
+/*
+ * DMA burst length and arbitration control register
+ */
+#define FTMAC100_DBLAC_INCR4_EN		(1 << 0)
+#define FTMAC100_DBLAC_INCR8_EN		(1 << 1)
+#define FTMAC100_DBLAC_INCR16_EN	(1 << 2)
+#define FTMAC100_DBLAC_RXFIFO_LTHR(x)	(((x) & 0x7) << 3)
+#define FTMAC100_DBLAC_RXFIFO_HTHR(x)	(((x) & 0x7) << 6)
+#define FTMAC100_DBLAC_RX_THR_EN	(1 << 9)
+
+/*
+ * MAC control register
+ */
+#define	FTMAC100_MACCR_XDMA_EN		(1 << 0)
+#define	FTMAC100_MACCR_RDMA_EN		(1 << 1)
+#define	FTMAC100_MACCR_SW_RST		(1 << 2)
+#define	FTMAC100_MACCR_LOOP_EN		(1 << 3)
+#define	FTMAC100_MACCR_CRC_DIS		(1 << 4)
+#define	FTMAC100_MACCR_XMT_EN		(1 << 5)
+#define	FTMAC100_MACCR_ENRX_IN_HALFTX	(1 << 6)
+#define	FTMAC100_MACCR_RCV_EN		(1 << 8)
+#define	FTMAC100_MACCR_HT_MULTI_EN	(1 << 9)
+#define	FTMAC100_MACCR_RX_RUNT		(1 << 10)
+#define	FTMAC100_MACCR_RX_FTL		(1 << 11)
+#define	FTMAC100_MACCR_RCV_ALL		(1 << 12)
+#define	FTMAC100_MACCR_CRC_APD		(1 << 14)
+#define	FTMAC100_MACCR_FULLDUP		(1 << 15)
+#define	FTMAC100_MACCR_RX_MULTIPKT	(1 << 16)
+#define	FTMAC100_MACCR_RX_BROADPKT	(1 << 17)
+
+/*
+ * PHY control register
+ */
+#define FTMAC100_PHYCR_MIIRDATA		0xffff
+#define FTMAC100_PHYCR_PHYAD(x)		(((x) & 0x1f) << 16)
+#define FTMAC100_PHYCR_REGAD(x)		(((x) & 0x1f) << 21)
+#define FTMAC100_PHYCR_MIIRD		(1 << 26)
+#define FTMAC100_PHYCR_MIIWR		(1 << 27)
+
+/*
+ * PHY write data register
+ */
+#define FTMAC100_PHYWDATA_MIIWDATA(x)	((x) & 0xffff)
+
+/*
+ * Transmit descriptor, aligned to 16 bytes
+ */
+struct ftmac100_txdes {
+	unsigned int	txdes0;
+	unsigned int	txdes1;
+	unsigned int	txdes2;	/* TXBUF_BADR */
+	unsigned int	txdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_TXDES0_TXPKT_LATECOL	(1 << 0)
+#define	FTMAC100_TXDES0_TXPKT_EXSCOL	(1 << 1)
+#define	FTMAC100_TXDES0_TXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_TXDES1_TXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_TXDES1_LTS		(1 << 27)
+#define	FTMAC100_TXDES1_FTS		(1 << 28)
+#define	FTMAC100_TXDES1_TX2FIC		(1 << 29)
+#define	FTMAC100_TXDES1_TXIC		(1 << 30)
+#define	FTMAC100_TXDES1_EDOTR		(1 << 31)
+
+/*
+ * Receive descriptor, aligned to 16 bytes
+ */
+struct ftmac100_rxdes {
+	unsigned int	rxdes0;
+	unsigned int	rxdes1;
+	unsigned int	rxdes2;	/* RXBUF_BADR */
+	unsigned int	rxdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_RXDES0_RFL		0x7ff
+#define	FTMAC100_RXDES0_MULTICAST	(1 << 16)
+#define	FTMAC100_RXDES0_BROADCAST	(1 << 17)
+#define	FTMAC100_RXDES0_RX_ERR		(1 << 18)
+#define	FTMAC100_RXDES0_CRC_ERR		(1 << 19)
+#define	FTMAC100_RXDES0_FTL		(1 << 20)
+#define	FTMAC100_RXDES0_RUNT		(1 << 21)
+#define	FTMAC100_RXDES0_RX_ODD_NB	(1 << 22)
+#define	FTMAC100_RXDES0_LRS		(1 << 28)
+#define	FTMAC100_RXDES0_FRS		(1 << 29)
+#define	FTMAC100_RXDES0_RXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_RXDES1_RXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_RXDES1_EDORR		(1 << 31)
+
+#endif /* __FTMAC100_H */
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-13 11:49 [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver Po-Yu Chuang
@ 2011-01-13 14:03 ` Ben Hutchings
  2011-01-14  5:37   ` Po-Yu Chuang
  2011-01-13 14:22 ` Eric Dumazet
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 74+ messages in thread
From: Ben Hutchings @ 2011-01-13 14:03 UTC (permalink / raw)
  To: Po-Yu Chuang; +Cc: netdev, linux-kernel, Po-Yu Chuang

On Thu, 2011-01-13 at 19:49 +0800, Po-Yu Chuang wrote:
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> 
> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
> MII.  This driver has been working on some ARM/NDS32 SoC including
> Faraday A320 and Andes AG101.
[...]
> diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
> new file mode 100644
> index 0000000..97d1f8d
> --- /dev/null
> +++ b/drivers/net/ftmac100.c
[...]
> +#include "ftmac100.h"
> +
> +#define USE_NAPI

All new network drivers should use NAPI only, so please remove the
definition of USE_NAPI and change the conditional code to use NAPI
always.

[...]
> +	struct napi_struct	napi;
> +#endif
> +
> +	struct net_device_stats	stats;

There is a net_device_stats structure in the net_device structure; you
should normally use that instead of adding another one.

[...]
> +static int ftmac100_reset(struct ftmac100 *priv)
> +{
> +	struct device *dev = &priv->netdev->dev;
> +	unsigned long flags;
> +	int i;
> +
> +	/* NOTE: reset clears all registers */
> +
> +	spin_lock_irqsave(&priv->hw_lock, flags);
> +	iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
> +	spin_unlock_irqrestore(&priv->hw_lock, flags);
> +
> +	for (i = 0; i < 5; i++) {
> +		int maccr;
> +
> +		spin_lock_irqsave(&priv->hw_lock, flags);
> +		maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
> +		spin_unlock_irqrestore(&priv->hw_lock, flags);
> +		if (!(maccr & FTMAC100_MACCR_SW_RST)) {
> +			/*
> +			 * FTMAC100_MACCR_SW_RST cleared does not indicate
> +			 * that hardware reset completed (what the f*ck).
> +			 * We still need to wait for a while.
> +			 */
> +			usleep_range(500, 1000);

Sleeping here means this must be running in process context.  But you
used spin_lock_irqsave() above which implies you're not sure what the
context is.  One of these must be wrong.

I wonder whether hw_lock is even needed; you seem to acquire and release
it around each PIO (read or write).  This should be unnecessary since
each PIO is atomic.

[...]
> +static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
> +{
> +	struct net_device *netdev = priv->netdev;
> +	struct device *dev = &netdev->dev;
> +	unsigned long flags;
> +	struct ftmac100_rxdes *rxdes;
> +	struct sk_buff *skb;
> +	int length;
> +	int copied = 0;
> +	int done = 0;
> +
> +	spin_lock_irqsave(&priv->rx_lock, flags);
> +	rxdes = ftmac100_rx_locate_first_segment(priv);
> +	spin_unlock_irqrestore(&priv->rx_lock, flags);
> +	if (!rxdes)
> +		return 0;
> +
> +	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
> +		spin_lock_irqsave(&priv->rx_lock, flags);
> +		ftmac100_rx_drop_packet(priv);
> +		spin_unlock_irqrestore(&priv->rx_lock, flags);

I think you can also get rid of rx_lock; it's only used in the RX data
path which is already serialised by NAPI.

[...]
> +	netif_rx(skb);
> +#endif
> +
> +	netdev->last_rx = jiffies;

Don't set last_rx; the networking core takes care of it now.

> +	priv->stats.rx_packets++;
> +	priv->stats.rx_bytes += skb->len;

This should be done earlier, so that these stats include packets that
are dropped for any reason.

[...]
> +static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
> +		dma_addr_t map)
> +{
[...]
> +	ftmac100_txdma_start_polling(priv);
> +	spin_unlock_irqrestore(&priv->hw_lock, flags);
> +	netdev->trans_start = jiffies;

Don't set trans_start; the networking core takes care of it now.

[...]
> +static int ftmac100_alloc_buffers(struct ftmac100 *priv)
> +{
> +	int i;
> +
> +	priv->descs = dma_alloc_coherent(priv->dev,
> +		sizeof(struct ftmac100_descs), &priv->descs_dma_addr,
> +		GFP_KERNEL | GFP_DMA);

On x86, GFP_DMA means the memory must be within the ISA DMA range
(< 16 MB).  I don't know quite what it means on ARM but it may not be
what you want.

[...]
> +/******************************************************************************
> + * interrupt handler
> + *****************************************************************************/
> +static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)
> +{
> +	struct net_device *netdev = dev_id;
> +	struct device *dev = &netdev->dev;
> +	struct ftmac100 *priv = netdev_priv(netdev);
> +	unsigned long flags;
> +	unsigned int status;
> +	unsigned int imr;
> +
> +	spin_lock_irqsave(&priv->hw_lock, flags);
> +	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
> +	imr = ioread32(priv->base + FTMAC100_OFFSET_IMR);
> +	spin_unlock_irqrestore(&priv->hw_lock, flags);
> +
> +	status &= imr;
> +	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
> +		/*
> +		 * FTMAC100_INT_RPKT_FINISH:
> +		 *	RX DMA has received packets into RX buffer successfully
> +		 *
> +		 * FTMAC100_INT_NORXBUF:
> +		 *	RX buffer unavailable
> +		 */
> +#ifdef USE_NAPI
> +		/* Disable interrupts for polling */
> +		ftmac100_disable_rxint(priv);
> +
> +		napi_schedule(&priv->napi);
> +#else
> +		int rx = 0;
> +
> +		while (ftmac100_rx_packet(priv, &rx))
> +			;
> +#endif
> +	}
> +
> +	if (status & FTMAC100_INT_NORXBUF) {
> +		/* RX buffer unavailable */
> +		if (printk_ratelimit())
> +			dev_info(dev, "INT_NORXBUF\n");
> +
> +		priv->stats.rx_over_errors++;
> +	}
> +
> +	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
> +		/*
> +		 * FTMAC100_INT_XPKT_OK:
> +		 *	 packet transmitted to ethernet successfully
> +		 *
> +		 * FTMAC100_INT_XPKT_LOST:
> +		 *	packet transmitted to ethernet lost due to late
> +		 *	collision or excessive collision
> +		 */
> +		ftmac100_tx_complete(priv);
> +	}

TX completions should also be handled through NAPI if possible.

[...]
> +static int ftmac100_open(struct net_device *netdev)
> +{
> +	struct device *dev = &netdev->dev;
> +	struct ftmac100 *priv = netdev_priv(netdev);
> +	int err;
> +
> +	err = ftmac100_alloc_buffers(priv);
> +	if (err) {
> +		dev_err(dev, "failed to allocate buffers\n");
> +		goto err_alloc;
> +	}
> +
> +	err = request_irq(priv->irq, ftmac100_interrupt, 0, netdev->name,
> +		netdev);
> +	if (err) {
> +		dev_err(dev, "failed to request irq %d\n", priv->irq);
> +		goto err_irq;
> +	}
> +
> +	priv->rx_pointer = 0;
> +	priv->tx_clean_pointer = 0;
> +	priv->tx_pointer = 0;
> +	spin_lock_init(&priv->hw_lock);
> +	spin_lock_init(&priv->rx_lock);
> +	spin_lock_init(&priv->tx_lock);

These locks should be initialised in your probe function.

[...]
> +/******************************************************************************
> + * struct platform_driver functions
> + *****************************************************************************/
> +static int ftmac100_remove(struct platform_device *pdev)
> +{
> +	struct net_device *netdev;
> +	struct ftmac100 *priv;
> +
> +	netdev = platform_get_drvdata(pdev);
> +	if (netdev == NULL)
> +		return 0;
> +
> +	platform_set_drvdata(pdev, NULL);
> +
> +	priv = netdev_priv(netdev);
> +
> +	unregister_netdev(netdev);
[...]

There should be a netif_napi_del() before this.

A general comment: please use netdev_err(), netdev_info() etc. for
logging.  This ensures that both the platform device address and the
network device name appears in the log messages.

Ben.

-- 
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.


^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-13 11:49 [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver Po-Yu Chuang
  2011-01-13 14:03 ` Ben Hutchings
@ 2011-01-13 14:22 ` Eric Dumazet
  2011-01-13 16:29   ` Andres Salomon
  2011-01-14  6:56   ` Po-Yu Chuang
  2011-01-13 15:39 ` Joe Perches
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 74+ messages in thread
From: Eric Dumazet @ 2011-01-13 14:22 UTC (permalink / raw)
  To: Po-Yu Chuang; +Cc: netdev, linux-kernel, Po-Yu Chuang

Le jeudi 13 janvier 2011 à 19:49 +0800, Po-Yu Chuang a écrit :
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> 
> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
> MII.  This driver has been working on some ARM/NDS32 SoC including
> Faraday A320 and Andes AG101.
> 
> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
> ---
>  drivers/net/Kconfig    |    9 +
>  drivers/net/Makefile   |    1 +
>  drivers/net/ftmac100.c | 1341 ++++++++++++++++++++++++++++++++++++++++++++++++
>  drivers/net/ftmac100.h |  180 +++++++
>  4 files changed, 1531 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/net/ftmac100.c
>  create mode 100644 drivers/net/ftmac100.h

Hi

1) please use netdev_alloc_skb_ip_align() instead of dev_alloc_skb() +
skb_reserve(skb, NET_IP_ALIGN);

2) Dont include a "struct net_device_stats stats" in struct ftmac100,
you can use the one included in struct net_device
  (You can then remove ftmac100_get_stats())

3) Dont "netdev->last_rx = jiffies;" : This is not driver job.
 Ditto for trans_start

4) You must comment why wmb() is needed in ftmac100_xmit()

5) Why isnt NAPI used too for TX completions ?
   BTW, I am not sure we want a define. All new drivers must use NAPI.

6) Use is_valid_ether_addr() instead of is_zero_ether_addr() in
ftmac100_probe()

7) "static struct ethtool_ops ftmac100_ethtool_ops" should be const :
	"static const struct ethtool_ops ftmac100_ethtool_ops"
  Ditto for :
	"static struct net_device_ops ftmac100_netdev_ops"


8) Why an interrupt handler (ftmac100_interrupt()) needs to block
interrupts with spin_lock_irqsave(&priv->hw_lock, flags); ?

9) Instead of dev_info(&netdev->dev ...) , please consider netdev_info()



^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-13 11:49 [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver Po-Yu Chuang
  2011-01-13 14:03 ` Ben Hutchings
  2011-01-13 14:22 ` Eric Dumazet
@ 2011-01-13 15:39 ` Joe Perches
  2011-01-14  6:35   ` Po-Yu Chuang
       [not found] ` <1294959948.4114.189.camel@Joe-Laptop>
  2011-01-17  9:21 ` [PATCH v2] " Po-Yu Chuang
  4 siblings, 1 reply; 74+ messages in thread
From: Joe Perches @ 2011-01-13 15:39 UTC (permalink / raw)
  To: Po-Yu Chuang; +Cc: netdev, linux-kernel, Po-Yu Chuang

On Thu, 2011-01-13 at 19:49 +0800, Po-Yu Chuang wrote:
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> 
> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
> MII.  This driver has been working on some ARM/NDS32 SoC including
> Faraday A320 and Andes AG101.

A couple of trivial comments not already mentioned by others.

> diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
[]
> @@ -2014,6 +2014,15 @@ config BCM63XX_ENET
>  	  This driver supports the ethernet MACs in the Broadcom 63xx
>  	  MIPS chipset family (BCM63XX).
>  
> +config FTMAC100
> +	tristate "Faraday FTMAC100 10/100 Ethernet support"
> +	depends on ARM
> +	select MII
> +	help
> +	  This driver supports the FTMAC100 Ethernet controller from
> +	  Faraday. It is used on Faraday A320, Andes AG101, AG101P
> +	  and some other ARM/NDS32 SoC's.
> +

ARM specific net drivers are for now in drivers/net/arm/
Perhaps it's better to locate these files there?

> diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
[]
> +static int ftmac100_rx_packet_error(struct ftmac100 *priv,
> +		struct ftmac100_rxdes *rxdes)
> +{
> +	struct device *dev = &priv->netdev->dev;
> +	int error = 0;
> +
> +	if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
> +		if (printk_ratelimit())
> +			dev_info(dev, "rx err\n");

There are many printk_ratelimit() tests.

It's better to use net_ratelimit() or a local ratelimit_state
so there's less possible suppression of other subsystem
messages.



^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-13 14:22 ` Eric Dumazet
@ 2011-01-13 16:29   ` Andres Salomon
  2011-01-14  6:44     ` Po-Yu Chuang
  2011-01-14  6:56   ` Po-Yu Chuang
  1 sibling, 1 reply; 74+ messages in thread
From: Andres Salomon @ 2011-01-13 16:29 UTC (permalink / raw)
  To: linux-kernel; +Cc: netdev

On Thu, 13 Jan 2011 15:22:39 +0100
Eric Dumazet <eric.dumazet@gmail.com> wrote:

> Le jeudi 13 janvier 2011 à 19:49 +0800, Po-Yu Chuang a écrit :
> > From: Po-Yu Chuang <ratbert@faraday-tech.com>
> > 
> > FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
> > MII.  This driver has been working on some ARM/NDS32 SoC including
> > Faraday A320 and Andes AG101.
> > 
> > Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
> > ---
> >  drivers/net/Kconfig    |    9 +
> >  drivers/net/Makefile   |    1 +
> >  drivers/net/ftmac100.c | 1341
> > ++++++++++++++++++++++++++++++++++++++++++++++++
> > drivers/net/ftmac100.h |  180 +++++++ 4 files changed, 1531
> > insertions(+), 0 deletions(-) create mode 100644
> > drivers/net/ftmac100.c create mode 100644 drivers/net/ftmac100.h
> 
> Hi
> 
[...]
> 
> 9) Instead of dev_info(&netdev->dev ...) , please consider
> netdev_info()
> 
> 

No one else mentioned it, so I'll add:

Don't explicitly inline functions unless they're in a header, or you
have a really good reason (and that reason should probably be described
in a comment).  Otherwise, just leave off the 'inline' keyword; the
compiler is smart enough to decide whether a function should be inlined
or not.



^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-13 14:03 ` Ben Hutchings
@ 2011-01-14  5:37   ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-14  5:37 UTC (permalink / raw)
  To: Ben Hutchings; +Cc: netdev, linux-kernel, Po-Yu Chuang

Dear Ben,

On Thu, Jan 13, 2011 at 10:03 PM, Ben Hutchings
<bhutchings@solarflare.com> wrote:
> On Thu, 2011-01-13 at 19:49 +0800, Po-Yu Chuang wrote:
>> From: Po-Yu Chuang <ratbert@faraday-tech.com>
>>
>> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
>> MII.  This driver has been working on some ARM/NDS32 SoC including
>> Faraday A320 and Andes AG101.
> [...]
>> +#define USE_NAPI
>
> All new network drivers should use NAPI only, so please remove the
> definition of USE_NAPI and change the conditional code to use NAPI
> always.

OK, fixed.

> [...]
>> +     struct net_device_stats stats;
>
> There is a net_device_stats structure in the net_device structure; you
> should normally use that instead of adding another one.

OK, fixed.

> [...]
>> +static int ftmac100_reset(struct ftmac100 *priv)
>> +{
>> +     struct device *dev = &priv->netdev->dev;
>> +     unsigned long flags;
>> +     int i;
>> +
>> +     /* NOTE: reset clears all registers */
>> +
>> +     spin_lock_irqsave(&priv->hw_lock, flags);
>> +     iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
>> +     spin_unlock_irqrestore(&priv->hw_lock, flags);
>> +
>> +     for (i = 0; i < 5; i++) {
>> +             int maccr;
>> +
>> +             spin_lock_irqsave(&priv->hw_lock, flags);
>> +             maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
>> +             spin_unlock_irqrestore(&priv->hw_lock, flags);
>> +             if (!(maccr & FTMAC100_MACCR_SW_RST)) {
>> +                     /*
>> +                      * FTMAC100_MACCR_SW_RST cleared does not indicate
>> +                      * that hardware reset completed (what the f*ck).
>> +                      * We still need to wait for a while.
>> +                      */
>> +                     usleep_range(500, 1000);
>
> Sleeping here means this must be running in process context.  But you
> used spin_lock_irqsave() above which implies you're not sure what the
> context is.  One of these must be wrong.
>
> I wonder whether hw_lock is even needed; you seem to acquire and release
> it around each PIO (read or write).  This should be unnecessary since
> each PIO is atomic.

OK, fixed.

> I think you can also get rid of rx_lock; it's only used in the RX data
> path which is already serialised by NAPI.

OK, fixed.

> [...]
>> +     netdev->last_rx = jiffies;
>
> Don't set last_rx; the networking core takes care of it now.

OK, fixed.

>> +     priv->stats.rx_packets++;
>> +     priv->stats.rx_bytes += skb->len;
>
> This should be done earlier, so that these stats include packets that
> are dropped for any reason.

OK, fixed.

> [...]
>> +     netdev->trans_start = jiffies;
>
> Don't set trans_start; the networking core takes care of it now.

OK, fixed.

> [...]
>> +     priv->descs = dma_alloc_coherent(priv->dev,
>> +             sizeof(struct ftmac100_descs), &priv->descs_dma_addr,
>> +             GFP_KERNEL | GFP_DMA);
>
> On x86, GFP_DMA means the memory must be within the ISA DMA range
> (< 16 MB).  I don't know quite what it means on ARM but it may not be
> what you want.

On ARM, all 4G address space can be used by DMA (at least for all the
hardwares I have ever used). All memory pages are in DMA zone AFAIK.
I put GFP_DMA here just to be safe if there were any constraints.

By checking drivers in drivers/net/arm/, ep93xx_eth.c also uses this flag,
so I guess this is acceptable?

> [...]
>> +     if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
>> +             /*
>> +              * FTMAC100_INT_XPKT_OK:
>> +              *       packet transmitted to ethernet successfully
>> +              *
>> +              * FTMAC100_INT_XPKT_LOST:
>> +              *      packet transmitted to ethernet lost due to late
>> +              *      collision or excessive collision
>> +              */
>> +             ftmac100_tx_complete(priv);
>> +     }
>
> TX completions should also be handled through NAPI if possible.

OK, I'll study how to do that.

> [...]
>> +     priv->rx_pointer = 0;
>> +     priv->tx_clean_pointer = 0;
>> +     priv->tx_pointer = 0;
>> +     spin_lock_init(&priv->hw_lock);
>> +     spin_lock_init(&priv->rx_lock);
>> +     spin_lock_init(&priv->tx_lock);
>
> These locks should be initialised in your probe function.

OK, fixed.

> [...]
>> +     unregister_netdev(netdev);
>
> There should be a netif_napi_del() before this.

OK, fixed.

> A general comment: please use netdev_err(), netdev_info() etc. for
> logging.  This ensures that both the platform device address and the
> network device name appears in the log messages.

OK, fixed.

Thanks a lot for your detailed review. I'll submit a new version ASAP.

Thanks,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-13 15:39 ` Joe Perches
@ 2011-01-14  6:35   ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-14  6:35 UTC (permalink / raw)
  To: Joe Perches; +Cc: netdev, linux-kernel, Po-Yu Chuang

Dear Joe,

On Thu, Jan 13, 2011 at 11:39 PM, Joe Perches <joe@perches.com> wrote:
> On Thu, 2011-01-13 at 19:49 +0800, Po-Yu Chuang wrote:
>> From: Po-Yu Chuang <ratbert@faraday-tech.com>
>>
>> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
>> MII.  This driver has been working on some ARM/NDS32 SoC including
>> Faraday A320 and Andes AG101.
>
> A couple of trivial comments not already mentioned by others.
>
>> diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
> []
>> @@ -2014,6 +2014,15 @@ config BCM63XX_ENET
>>         This driver supports the ethernet MACs in the Broadcom 63xx
>>         MIPS chipset family (BCM63XX).
>>
>> +config FTMAC100
>> +     tristate "Faraday FTMAC100 10/100 Ethernet support"
>> +     depends on ARM
>> +     select MII
>> +     help
>> +       This driver supports the FTMAC100 Ethernet controller from
>> +       Faraday. It is used on Faraday A320, Andes AG101, AG101P
>> +       and some other ARM/NDS32 SoC's.
>> +
>
> ARM specific net drivers are for now in drivers/net/arm/
> Perhaps it's better to locate these files there?

This controller is used by not only ARM-base platforms, but also
NDS32-base ones.
NDS32 is an ISA designed by Andes tech. Although it is not yet in the mainline,
they plan to push their stuff to mainline and are working on that.

So... I don't know if it is better to put my driver to driver/net/arm/.

Should I leave my driver at driver/net/ or put it in drvier/net/arm/
and let Andes tech guys
move it out of driver/net/arm/ when they use it later?

>> +     if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
>> +             if (printk_ratelimit())
>> +                     dev_info(dev, "rx err\n");
>
> There are many printk_ratelimit() tests.
>
> It's better to use net_ratelimit() or a local ratelimit_state
> so there's less possible suppression of other subsystem
> messages.

OK, fixed.


Thanks a lot for your absolutely non-trivial review. :-)
I'll submit a new version ASAP.

Thanks,
Po-Yu  Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-13 16:29   ` Andres Salomon
@ 2011-01-14  6:44     ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-14  6:44 UTC (permalink / raw)
  To: Andres Salomon; +Cc: Eric Dumazet, netdev, linux-kernel, Po-Yu Chuang

Dear Andres,

On Fri, Jan 14, 2011 at 12:29 AM, Andres Salomon <dilinger@queued.net> wrote:
> On Thu, 13 Jan 2011 15:22:39 +0100
> Eric Dumazet <eric.dumazet@gmail.com> wrote:
>> Le jeudi 13 janvier 2011 à 19:49 +0800, Po-Yu Chuang a écrit :

> No one else mentioned it, so I'll add:
>
> Don't explicitly inline functions unless they're in a header, or you
> have a really good reason (and that reason should probably be described
> in a comment).  Otherwise, just leave off the 'inline' keyword; the
> compiler is smart enough to decide whether a function should be inlined
> or not.

OK, fixed.

Thanks,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
       [not found] ` <1294959948.4114.189.camel@Joe-Laptop>
@ 2011-01-14  6:49   ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-14  6:49 UTC (permalink / raw)
  To: Joe Perches; +Cc: netdev, linux-kernel, Po-Yu Chuang

Dear Joe,

On Fri, Jan 14, 2011 at 7:05 AM, Joe Perches <joe@perches.com> wrote:
> On Thu, 2011-01-13 at 19:49 +0800, Po-Yu Chuang wrote:
>> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> []
>> +     if (is_zero_ether_addr(netdev->dev_addr)) {
>> +             random_ether_addr(netdev->dev_addr);
>> +             dev_info(&netdev->dev, "generated random MAC address "
>> +                     "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x.\n",
>> +                     netdev->dev_addr[0], netdev->dev_addr[1],
>> +                     netdev->dev_addr[2], netdev->dev_addr[3],
>> +                     netdev->dev_addr[4], netdev->dev_addr[5]);
>
> Sorry, one other thing.
> There are kernel specific printf extensions for pointer types.
> (look at lib/vsprintf.c)  There's one for mac addresses: "%pM".
> This could be done as:
>
>                netdev_info(netdev, "Generated random MAC address: %pM\n",
>                            netdev->dev_addr);

Wow, this is pretty beautiful. Thanks.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-13 14:22 ` Eric Dumazet
  2011-01-13 16:29   ` Andres Salomon
@ 2011-01-14  6:56   ` Po-Yu Chuang
  1 sibling, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-14  6:56 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: netdev, linux-kernel, Po-Yu Chuang

Dear Eric,

On Thu, Jan 13, 2011 at 10:22 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le jeudi 13 janvier 2011 à 19:49 +0800, Po-Yu Chuang a écrit :
>> From: Po-Yu Chuang <ratbert@faraday-tech.com>
>>
>> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
>> MII.  This driver has been working on some ARM/NDS32 SoC including
>> Faraday A320 and Andes AG101.
>>
>> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
> Hi
>
> 1) please use netdev_alloc_skb_ip_align() instead of dev_alloc_skb() +
> skb_reserve(skb, NET_IP_ALIGN);

OK, fixed.

> 2) Dont include a "struct net_device_stats stats" in struct ftmac100,
> you can use the one included in struct net_device
>  (You can then remove ftmac100_get_stats())

OK, fixed.

> 3) Dont "netdev->last_rx = jiffies;" : This is not driver job.
>  Ditto for trans_start

OK, fixed.

> 4) You must comment why wmb() is needed in ftmac100_xmit()

OK, comment added.

> 5) Why isnt NAPI used too for TX completions ?
>   BTW, I am not sure we want a define. All new drivers must use NAPI.

I'll study how to do that

> 6) Use is_valid_ether_addr() instead of is_zero_ether_addr() in
> ftmac100_probe()

OK, fixed.

> 7) "static struct ethtool_ops ftmac100_ethtool_ops" should be const :
>        "static const struct ethtool_ops ftmac100_ethtool_ops"
>  Ditto for :
>        "static struct net_device_ops ftmac100_netdev_ops"

OK, both fixed.

> 8) Why an interrupt handler (ftmac100_interrupt()) needs to block
> interrupts with spin_lock_irqsave(&priv->hw_lock, flags); ?

Fixed. Not a problem anymore since hw_lock is removed now.

> 9) Instead of dev_info(&netdev->dev ...) , please consider netdev_info()

OK, fixed.


Thanks a lot for your detailed review. I'll submit a new version ASAP.

Thanks,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-13 11:49 [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver Po-Yu Chuang
                   ` (3 preceding siblings ...)
       [not found] ` <1294959948.4114.189.camel@Joe-Laptop>
@ 2011-01-17  9:21 ` Po-Yu Chuang
  2011-01-17 17:19   ` Joe Perches
                     ` (3 more replies)
  4 siblings, 4 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-17  9:21 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, ratbert, bhutchings, eric.dumazet, joe, dilinger

From: Po-Yu Chuang <ratbert@faraday-tech.com>

FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII.  This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.

Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts

As I said in the previous mail, this controller is used not only on ARM,
so I still leave it in drivers/net/.  If there is any suggestion, please
let me know. Thanks


 drivers/net/Kconfig    |    9 +
 drivers/net/Makefile   |    1 +
 drivers/net/ftmac100.c | 1223 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/ftmac100.h |  180 +++++++
 4 files changed, 1413 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/ftmac100.c
 create mode 100644 drivers/net/ftmac100.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 3fda24a..0720acc 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2014,6 +2014,15 @@ config BCM63XX_ENET
 	  This driver supports the ethernet MACs in the Broadcom 63xx
 	  MIPS chipset family (BCM63XX).
 
+config FTMAC100
+	tristate "Faraday FTMAC100 10/100 Ethernet support"
+	depends on ARM
+	select MII
+	help
+	  This driver supports the FTMAC100 Ethernet controller from
+	  Faraday. It is used on Faraday A320, Andes AG101, AG101P
+	  and some other ARM/NDS32 SoC's.
+
 source "drivers/net/fs_enet/Kconfig"
 
 source "drivers/net/octeon/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..7c21711 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_FORCEDETH) += forcedeth.o
 obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
 obj-$(CONFIG_AX88796) += ax88796.o
 obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
+obj-$(CONFIG_FTMAC100) += ftmac100.o
 
 obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
new file mode 100644
index 0000000..33b0c77
--- /dev/null
+++ b/drivers/net/ftmac100.c
@@ -0,0 +1,1223 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#include "ftmac100.h"
+
+#define DRV_NAME	"ftmac100"
+#define DRV_VERSION	"0.1"
+
+#define RX_QUEUE_ENTRIES	128	/* must be power of 2 */
+#define TX_QUEUE_ENTRIES	16	/* must be power of 2 */
+
+#define MAX_PKT_SIZE		1518
+#define RX_BUF_SIZE		2044	/* must be smaller than 0x7ff */
+
+/******************************************************************************
+ * priveate data
+ *****************************************************************************/
+struct ftmac100_descs {
+	struct ftmac100_rxdes	rxdes[RX_QUEUE_ENTRIES];
+	struct ftmac100_txdes	txdes[TX_QUEUE_ENTRIES];
+};
+
+struct ftmac100 {
+	struct resource		*res;
+	void			*base;
+	int			irq;
+
+	struct ftmac100_descs	*descs;
+	dma_addr_t		descs_dma_addr;
+
+	unsigned int		rx_pointer;
+	unsigned int		tx_clean_pointer;
+	unsigned int		tx_pointer;
+	unsigned int		tx_pending;
+
+	spinlock_t		tx_lock;
+
+	struct net_device	*netdev;
+	struct device		*dev;
+	struct napi_struct	napi;
+
+	struct mii_if_info	mii;
+};
+
+/******************************************************************************
+ * internal functions (hardware register access)
+ *****************************************************************************/
+static void ftmac100_enable_all_int(struct ftmac100 *priv)
+{
+	unsigned int imr;
+
+	imr = FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF
+	     | FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST
+	     | FTMAC100_INT_RPKT_LOST | FTMAC100_INT_AHB_ERR
+	     | FTMAC100_INT_PHYSTS_CHG;
+	iowrite32(imr, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_disable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_set_receive_ring_base(struct ftmac100 *priv,
+		dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
+}
+
+static void ftmac100_set_transmit_ring_base(struct ftmac100 *priv,
+		dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_TXR_BADR);
+}
+
+static void ftmac100_txdma_start_polling(struct ftmac100 *priv)
+{
+	iowrite32(1, priv->base + FTMAC100_OFFSET_TXPD);
+}
+
+static int ftmac100_reset(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	int i;
+
+	/* NOTE: reset clears all registers */
+	iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
+
+	for (i = 0; i < 5; i++) {
+		int maccr;
+
+		maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
+		if (!(maccr & FTMAC100_MACCR_SW_RST)) {
+			/*
+			 * FTMAC100_MACCR_SW_RST cleared does not indicate
+			 * that hardware reset completed (what the f*ck).
+			 * We still need to wait for a while.
+			 */
+			usleep_range(500, 1000);
+			return 0;
+		}
+
+		usleep_range(1000, 10000);
+	}
+
+	netdev_err(netdev, "software reset failed\n");
+	return -EIO;
+}
+
+static void ftmac100_set_mac(struct ftmac100 *priv, const unsigned char *mac)
+{
+	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 + FTMAC100_OFFSET_MAC_MADR);
+	iowrite32(laddr, priv->base + FTMAC100_OFFSET_MAC_LADR);
+}
+
+static int ftmac100_start_hw(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	int maccr;
+
+	if (ftmac100_reset(priv))
+		return -EIO;
+
+	/* setup ring buffer base registers */
+
+	ftmac100_set_receive_ring_base(priv,
+		priv->descs_dma_addr + offsetof(struct ftmac100_descs, rxdes));
+	ftmac100_set_transmit_ring_base(priv,
+		priv->descs_dma_addr + offsetof(struct ftmac100_descs, txdes));
+
+	iowrite32(FTMAC100_APTC_RXPOLL_CNT(1),
+		priv->base + FTMAC100_OFFSET_APTC);
+
+	ftmac100_set_mac(priv, netdev->dev_addr);
+
+	maccr = FTMAC100_MACCR_XMT_EN |
+		FTMAC100_MACCR_RCV_EN |
+		FTMAC100_MACCR_XDMA_EN |
+		FTMAC100_MACCR_RDMA_EN |
+		FTMAC100_MACCR_CRC_APD |
+		FTMAC100_MACCR_FULLDUP |
+		FTMAC100_MACCR_RX_RUNT |
+		FTMAC100_MACCR_RX_BROADPKT;
+
+	iowrite32(maccr, priv->base + FTMAC100_OFFSET_MACCR);
+	return 0;
+}
+
+static void ftmac100_stop_hw(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_MACCR);
+}
+
+/******************************************************************************
+ * internal functions (receive descriptor)
+ *****************************************************************************/
+static int ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_FRS;
+}
+
+static int ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_LRS;
+}
+
+static int ftmac100_rxdes_owned_by_dma(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RXDMA_OWN;
+}
+
+static void ftmac100_rxdes_set_dma_own(struct ftmac100_rxdes *rxdes)
+{
+	/* clear status bits */
+	rxdes->rxdes0 = FTMAC100_RXDES0_RXDMA_OWN;
+}
+
+static int ftmac100_rxdes_rx_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RX_ERR;
+}
+
+static int ftmac100_rxdes_crc_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_CRC_ERR;
+}
+
+static int ftmac100_rxdes_frame_too_long(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_FTL;
+}
+
+static int ftmac100_rxdes_runt(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RUNT;
+}
+
+static int ftmac100_rxdes_odd_nibble(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RX_ODD_NB;
+}
+
+static unsigned int ftmac100_rxdes_frame_length(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_RFL;
+}
+
+static int ftmac100_rxdes_multicast(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & FTMAC100_RXDES0_MULTICAST;
+}
+
+static void ftmac100_rxdes_set_buffer_size(struct ftmac100_rxdes *rxdes,
+		unsigned int size)
+{
+	rxdes->rxdes1 = (rxdes->rxdes1 & FTMAC100_RXDES1_EDORR) |
+			 FTMAC100_RXDES1_RXBUF_SIZE(size);
+}
+
+static void ftmac100_rxdes_set_end_of_ring(struct ftmac100_rxdes *rxdes)
+{
+	rxdes->rxdes1 |= FTMAC100_RXDES1_EDORR;
+}
+
+static void ftmac100_rxdes_set_dma_addr(struct ftmac100_rxdes *rxdes,
+		dma_addr_t addr)
+{
+	rxdes->rxdes2 = addr;
+}
+
+static dma_addr_t ftmac100_rxdes_get_dma_addr(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes2;
+}
+
+/* rxdes3 is not used by hardware, we use it to keep track of buffer */
+static void ftmac100_rxdes_set_va(struct ftmac100_rxdes *rxdes, void *addr)
+{
+	rxdes->rxdes3 = (unsigned int)addr;
+}
+
+static void *ftmac100_rxdes_get_va(struct ftmac100_rxdes *rxdes)
+{
+	return (void *)rxdes->rxdes3;
+}
+
+/******************************************************************************
+ * internal functions (receive)
+ *****************************************************************************/
+static int ftmac100_next_rx_pointer(int pointer)
+{
+	return (pointer + 1) & (RX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_rx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->rx_pointer = ftmac100_next_rx_pointer(priv->rx_pointer);
+}
+
+static struct ftmac100_rxdes *ftmac100_current_rxdes(struct ftmac100 *priv)
+{
+	return &priv->descs->rxdes[priv->rx_pointer];
+}
+
+static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
+		struct ftmac100 *priv)
+{
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+
+	while (!ftmac100_rxdes_owned_by_dma(rxdes)) {
+		if (ftmac100_rxdes_first_segment(rxdes))
+			return rxdes;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	}
+
+	return NULL;
+}
+
+static int ftmac100_rx_packet_error(struct ftmac100 *priv,
+		struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	int error = 0;
+
+	if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx err\n");
+
+		netdev->stats.rx_errors++;
+		error = 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_crc_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx crc err\n");
+
+		netdev->stats.rx_crc_errors++;
+		error = 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx frame too long\n");
+
+		netdev->stats.rx_length_errors++;
+		error = 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_runt(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx runt\n");
+
+		netdev->stats.rx_length_errors++;
+		error = 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_odd_nibble(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx odd nibble\n");
+
+		netdev->stats.rx_length_errors++;
+		error = 1;
+	}
+
+	return error;
+}
+
+static void ftmac100_rx_drop_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+	int done = 0;
+
+	if (net_ratelimit())
+		netdev_dbg(netdev, "drop packet %p\n", rxdes);
+
+	do {
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = 1;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && !ftmac100_rxdes_owned_by_dma(rxdes));
+
+	netdev->stats.rx_dropped++;
+}
+
+static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes;
+	struct sk_buff *skb;
+	int length;
+	int copied = 0;
+	int done = 0;
+
+	rxdes = ftmac100_rx_locate_first_segment(priv);
+	if (!rxdes)
+		return 0;
+
+	length = ftmac100_rxdes_frame_length(rxdes);
+
+	netdev->stats.rx_packets++;
+	netdev->stats.rx_bytes += length;
+
+	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+		ftmac100_rx_drop_packet(priv);
+		return 1;
+	}
+
+	/* start processing */
+	skb = netdev_alloc_skb_ip_align(netdev, length);
+	if (unlikely(!skb)) {
+		if (net_ratelimit())
+			netdev_err(netdev, "rx skb alloc failed\n");
+
+		ftmac100_rx_drop_packet(priv);
+		return 1;
+	}
+
+	if (unlikely(ftmac100_rxdes_multicast(rxdes)))
+		netdev->stats.multicast++;
+
+	do {
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *buf = ftmac100_rxdes_get_va(rxdes);
+		int size;
+
+		size = min(length - copied, RX_BUF_SIZE);
+
+		dma_sync_single_for_cpu(priv->dev, d, RX_BUF_SIZE,
+			DMA_FROM_DEVICE);
+		memcpy(skb_put(skb, size), buf, size);
+
+		copied += size;
+
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = 1;
+
+		dma_sync_single_for_device(priv->dev, d, RX_BUF_SIZE,
+			DMA_FROM_DEVICE);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && copied < length);
+
+	skb->protocol = eth_type_trans(skb, netdev);
+
+	/* push packet to protocol stack */
+	netif_receive_skb(skb);
+
+	(*processed)++;
+	return 1;
+}
+
+/******************************************************************************
+ * internal functions (transmit descriptor)
+ *****************************************************************************/
+static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
+{
+	/* clear all except end of ring bit */
+	txdes->txdes0 = 0;
+	txdes->txdes1 &= FTMAC100_TXDES1_EDOTR;
+	txdes->txdes2 = 0;
+	txdes->txdes3 = 0;
+}
+
+static int ftmac100_txdes_owned_by_dma(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & FTMAC100_TXDES0_TXDMA_OWN;
+}
+
+static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
+{
+	/*
+	 * Make sure dma own bit will not be set before any other
+	 * descriptor fiels.
+	 */
+	wmb();
+
+	txdes->txdes0 |= FTMAC100_TXDES0_TXDMA_OWN;
+}
+
+static int ftmac100_txdes_excessive_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & FTMAC100_TXDES0_TXPKT_EXSCOL;
+}
+
+static int ftmac100_txdes_late_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & FTMAC100_TXDES0_TXPKT_LATECOL;
+}
+
+static void ftmac100_txdes_set_end_of_ring(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_EDOTR;
+}
+
+static void ftmac100_txdes_set_first_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_FTS;
+}
+
+static void ftmac100_txdes_set_last_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_LTS;
+}
+
+static void ftmac100_txdes_set_txint(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_TXIC;
+}
+
+static void ftmac100_txdes_set_buffer_size(struct ftmac100_txdes *txdes,
+		unsigned int len)
+{
+	txdes->txdes1 |= FTMAC100_TXDES1_TXBUF_SIZE(len);
+}
+
+static void ftmac100_txdes_set_dma_addr(struct ftmac100_txdes *txdes,
+		dma_addr_t addr)
+{
+	txdes->txdes2 = addr;
+}
+
+static dma_addr_t ftmac100_txdes_get_dma_addr(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes2;
+}
+
+/* txdes3 is not used by hardware, we use it to keep track of socket buffer */
+static void ftmac100_txdes_set_skb(struct ftmac100_txdes *txdes,
+		struct sk_buff *skb)
+{
+	txdes->txdes3 = (unsigned int)skb;
+}
+
+static struct sk_buff *ftmac100_txdes_get_skb(struct ftmac100_txdes *txdes)
+{
+	return (struct sk_buff *)txdes->txdes3;
+}
+
+/******************************************************************************
+ * internal functions (transmit)
+ *****************************************************************************/
+static int ftmac100_next_tx_pointer(int pointer)
+{
+	return (pointer + 1) & (TX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_tx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_pointer = ftmac100_next_tx_pointer(priv->tx_pointer);
+}
+
+static void ftmac100_tx_clean_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_clean_pointer =
+		ftmac100_next_tx_pointer(priv->tx_clean_pointer);
+}
+
+static struct ftmac100_txdes *ftmac100_current_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_pointer];
+}
+
+static struct ftmac100_txdes *ftmac100_current_clean_txdes(
+		struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_clean_pointer];
+}
+
+static int ftmac100_tx_complete_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	struct sk_buff *skb;
+	dma_addr_t map;
+
+	if (priv->tx_pending == 0)
+		return 0;
+
+	txdes = ftmac100_current_clean_txdes(priv);
+
+	if (ftmac100_txdes_owned_by_dma(txdes))
+		return 0;
+
+	skb = ftmac100_txdes_get_skb(txdes);
+	map = ftmac100_txdes_get_dma_addr(txdes);
+
+	if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
+			ftmac100_txdes_late_collision(txdes))) {
+		/*
+		 * packet transmitted to ethernet lost due to late collision
+		 * or excessive collision
+		 */
+		netdev->stats.tx_aborted_errors++;
+	} else {
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += skb->len;
+	}
+
+	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+
+	dev_kfree_skb_irq(skb);
+
+	ftmac100_txdes_reset(txdes);
+
+	ftmac100_tx_clean_pointer_advance(priv);
+
+	priv->tx_pending--;
+	netif_wake_queue(netdev);
+
+	return 1;
+}
+
+static void ftmac100_tx_complete(struct ftmac100 *priv)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->tx_lock, flags);
+	while (ftmac100_tx_complete_packet(priv))
+		;
+	spin_unlock_irqrestore(&priv->tx_lock, flags);
+}
+
+static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
+		dma_addr_t map)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
+	unsigned long flags;
+
+	txdes = ftmac100_current_txdes(priv);
+	ftmac100_tx_pointer_advance(priv);
+
+	/* setup TX descriptor */
+
+	spin_lock_irqsave(&priv->tx_lock, flags);
+	ftmac100_txdes_set_skb(txdes, skb);
+	ftmac100_txdes_set_dma_addr(txdes, map);
+
+	ftmac100_txdes_set_first_segment(txdes);
+	ftmac100_txdes_set_last_segment(txdes);
+	ftmac100_txdes_set_txint(txdes);
+	ftmac100_txdes_set_buffer_size(txdes, len);
+
+	priv->tx_pending++;
+	if (priv->tx_pending == TX_QUEUE_ENTRIES) {
+		if (net_ratelimit())
+			netdev_info(netdev, "tx queue full\n");
+
+		netif_stop_queue(netdev);
+	}
+
+	/* start transmit */
+	ftmac100_txdes_set_dma_own(txdes);
+	spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+	ftmac100_txdma_start_polling(priv);
+
+	return NETDEV_TX_OK;
+}
+
+/******************************************************************************
+ * internal functions (buffer)
+ *****************************************************************************/
+static void ftmac100_free_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *page = ftmac100_rxdes_get_va(rxdes);
+
+		if (d)
+			dma_unmap_single(priv->dev, d, PAGE_SIZE,
+				DMA_FROM_DEVICE);
+
+		if (page != NULL)
+			free_page((unsigned long)page);
+	}
+
+	for (i = 0; i < TX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_txdes *txdes = &priv->descs->txdes[i];
+		struct sk_buff *skb = ftmac100_txdes_get_skb(txdes);
+
+		if (skb) {
+			dma_addr_t map;
+
+			map = ftmac100_txdes_get_dma_addr(txdes);
+			dma_unmap_single(priv->dev, map, skb_headlen(skb),
+				DMA_TO_DEVICE);
+			dev_kfree_skb(skb);
+		}
+	}
+
+	dma_free_coherent(priv->dev, sizeof(struct ftmac100_descs),
+		priv->descs, priv->descs_dma_addr);
+}
+
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	priv->descs = dma_alloc_coherent(priv->dev,
+		sizeof(struct ftmac100_descs), &priv->descs_dma_addr,
+		GFP_KERNEL | GFP_DMA);
+	if (priv->descs == NULL)
+		return -ENOMEM;
+
+	memset(priv->descs, 0, sizeof(struct ftmac100_descs));
+
+	/* initialize RX ring */
+
+	ftmac100_rxdes_set_end_of_ring(
+		&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		void *page;
+		dma_addr_t d;
+
+		page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
+		if (page == NULL)
+			goto err;
+
+		d = dma_map_single(priv->dev, page, PAGE_SIZE,
+			DMA_FROM_DEVICE);
+		if (unlikely(dma_mapping_error(priv->dev, d))) {
+			free_page((unsigned long)page);
+			goto err;
+		}
+
+		/*
+		 * The hardware enforces a sub-2K maximum packet size, so we
+		 * put two buffers on every hardware page.
+		 */
+		ftmac100_rxdes_set_va(rxdes, page);
+		ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_dma_addr(rxdes, d);
+		ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
+		ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rxdes_set_dma_own(rxdes + 1);
+	}
+
+	/* initialize TX ring */
+
+	ftmac100_txdes_set_end_of_ring(
+		&priv->descs->txdes[TX_QUEUE_ENTRIES - 1]);
+	return 0;
+
+err:
+	ftmac100_free_buffers(priv);
+	return -ENOMEM;
+}
+
+/******************************************************************************
+ * struct mii_if_info functions
+ *****************************************************************************/
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIRD;
+
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
+			return phycr & FTMAC100_PHYCR_MIIRDATA;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio read timed out\n");
+	return 0xffff;
+}
+
+static void ftmac100_mdio_write(struct net_device *netdev, int phy_id, int reg,
+		int data)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIWR;
+
+	data = FTMAC100_PHYWDATA_MIIWDATA(data);
+
+	iowrite32(data, priv->base + FTMAC100_OFFSET_PHYWDATA);
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIWR) == 0)
+			return;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio write timed out\n");
+}
+
+/******************************************************************************
+ * struct ethtool_ops functions
+ *****************************************************************************/
+static void ftmac100_get_drvinfo(struct net_device *netdev,
+		struct ethtool_drvinfo *info)
+{
+	strcpy(info->driver, DRV_NAME);
+	strcpy(info->version, DRV_VERSION);
+	strcpy(info->bus_info, dev_name(&netdev->dev));
+}
+
+static int ftmac100_get_settings(struct net_device *netdev,
+		struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_gset(&priv->mii, cmd);
+}
+
+static int ftmac100_set_settings(struct net_device *netdev,
+		struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_sset(&priv->mii, cmd);
+}
+
+static int ftmac100_nway_reset(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_nway_restart(&priv->mii);
+}
+
+static u32 ftmac100_get_link(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_link_ok(&priv->mii);
+}
+
+static const struct ethtool_ops ftmac100_ethtool_ops = {
+	.set_settings		= ftmac100_set_settings,
+	.get_settings		= ftmac100_get_settings,
+	.get_drvinfo		= ftmac100_get_drvinfo,
+	.nway_reset		= ftmac100_nway_reset,
+	.get_link		= ftmac100_get_link,
+};
+
+/******************************************************************************
+ * interrupt handler
+ *****************************************************************************/
+static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	if (likely(netif_running(netdev))) {
+		/* Disable interrupts for polling */
+		ftmac100_disable_all_int(priv);
+		napi_schedule(&priv->napi);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/******************************************************************************
+ * struct napi_struct functions
+ *****************************************************************************/
+static int ftmac100_poll(struct napi_struct *napi, int budget)
+{
+	struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
+	struct net_device *netdev = priv->netdev;
+	unsigned int status;
+	int completed = 1;
+	int rx = 0;
+
+	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
+
+	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
+		/*
+		 * FTMAC100_INT_RPKT_FINISH:
+		 *	RX DMA has received packets into RX buffer successfully
+		 *
+		 * FTMAC100_INT_NORXBUF:
+		 *	RX buffer unavailable
+		 */
+		int retry;
+
+		do {
+			retry = ftmac100_rx_packet(priv, &rx);
+		} while (retry && rx < budget);
+
+		if (retry && rx == budget)
+			completed = 0;
+	}
+
+	if (status & FTMAC100_INT_NORXBUF) {
+		/* RX buffer unavailable */
+		if (net_ratelimit())
+			netdev_info(netdev, "INT_NORXBUF\n");
+
+		netdev->stats.rx_over_errors++;
+	}
+
+	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
+		/*
+		 * FTMAC100_INT_XPKT_OK:
+		 *	 packet transmitted to ethernet successfully
+		 *
+		 * FTMAC100_INT_XPKT_LOST:
+		 *	packet transmitted to ethernet lost due to late
+		 *	collision or excessive collision
+		 */
+		ftmac100_tx_complete(priv);
+	}
+
+	if (status & FTMAC100_INT_RPKT_LOST) {
+		/* received packet lost due to RX FIFO full */
+		if (net_ratelimit())
+			netdev_info(netdev, "INT_RPKT_LOST\n");
+
+		netdev->stats.rx_fifo_errors++;
+	}
+
+	if (status & FTMAC100_INT_AHB_ERR) {
+		/* AHB error */
+		if (net_ratelimit())
+			netdev_info(netdev, "INT_AHB_ERR\n");
+
+		/* do nothing */
+	}
+
+	if (status & FTMAC100_INT_PHYSTS_CHG) {
+		/* PHY link status change */
+		if (net_ratelimit())
+			netdev_info(netdev, "INT_PHYSTS_CHG\n");
+
+		mii_check_link(&priv->mii);
+	}
+
+	if (completed) {
+		/* stop polling */
+		napi_complete(napi);
+		ftmac100_enable_all_int(priv);
+	}
+
+	return rx;
+}
+
+/******************************************************************************
+ * struct net_device_ops functions
+ *****************************************************************************/
+static int ftmac100_open(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int err;
+
+	err = ftmac100_alloc_buffers(priv);
+	if (err) {
+		netdev_err(netdev, "failed to allocate buffers\n");
+		goto err_alloc;
+	}
+
+	err = request_irq(priv->irq, ftmac100_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 = ftmac100_start_hw(priv);
+	if (err)
+		goto err_hw;
+
+	napi_enable(&priv->napi);
+	netif_start_queue(netdev);
+
+	ftmac100_enable_all_int(priv);
+	return 0;
+
+err_hw:
+	free_irq(priv->irq, netdev);
+err_irq:
+	ftmac100_free_buffers(priv);
+err_alloc:
+	return err;
+}
+
+static int ftmac100_stop(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	ftmac100_disable_all_int(priv);
+	netif_stop_queue(netdev);
+	napi_disable(&priv->napi);
+	ftmac100_stop_hw(priv);
+	free_irq(priv->irq, netdev);
+	ftmac100_free_buffers(priv);
+
+	return 0;
+}
+
+static int ftmac100_hard_start_xmit(struct sk_buff *skb,
+		struct net_device *netdev)
+{
+	struct ftmac100 *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++;
+		dev_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++;
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	return ftmac100_xmit(priv, skb, map);
+}
+
+/* optional */
+static int ftmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr,
+		int cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+
+	return generic_mii_ioctl(&priv->mii, data, cmd, NULL);
+}
+
+static const struct net_device_ops ftmac100_netdev_ops = {
+	.ndo_open		= ftmac100_open,
+	.ndo_stop		= ftmac100_stop,
+	.ndo_start_xmit		= ftmac100_hard_start_xmit,
+	.ndo_set_mac_address	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= ftmac100_do_ioctl,
+};
+
+/******************************************************************************
+ * struct platform_driver functions
+ *****************************************************************************/
+static int ftmac100_remove(struct platform_device *pdev)
+{
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+
+	netdev = platform_get_drvdata(pdev);
+	if (netdev == NULL)
+		return 0;
+
+	platform_set_drvdata(pdev, NULL);
+
+	priv = netdev_priv(netdev);
+
+	netif_napi_del(&priv->napi);
+	unregister_netdev(netdev);
+
+	if (priv->base != NULL)
+		iounmap(priv->base);
+
+	if (priv->res != NULL)
+		release_resource(priv->res);
+
+	free_netdev(netdev);
+	return 0;
+}
+
+static int ftmac100_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int irq;
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+	int err;
+
+	if (!pdev)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENXIO;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/* setup net_device */
+
+	netdev = alloc_etherdev(sizeof(struct ftmac100));
+	if (netdev == NULL) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	SET_NETDEV_DEV(netdev, &pdev->dev);
+	SET_ETHTOOL_OPS(netdev, &ftmac100_ethtool_ops);
+	netdev->netdev_ops = &ftmac100_netdev_ops;
+
+	platform_set_drvdata(pdev, netdev);
+
+	/* setup private data */
+
+	priv = netdev_priv(netdev);
+	priv->netdev = netdev;
+	priv->dev = &pdev->dev;
+
+	spin_lock_init(&priv->tx_lock);
+
+	/* initialize NAPI */
+	netif_napi_add(netdev, &priv->napi, ftmac100_poll, 64);
+
+	/* map io memory */
+	priv->res = request_mem_region(res->start, res->end - res->start,
+			dev_name(&pdev->dev));
+	if (priv->res == NULL) {
+		dev_err(&pdev->dev, "Could not reserve memory region\n");
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	priv->base = ioremap(res->start, res->end - res->start);
+	if (priv->base == NULL) {
+		dev_err(&pdev->dev, "Failed to ioremap ethernet registers\n");
+		err = -EIO;
+		goto err_out;
+	}
+
+	priv->irq = irq;
+
+	/* initialize struct mii_if_info */
+
+	priv->mii.phy_id	= 0;
+	priv->mii.phy_id_mask	= 0x1f;
+	priv->mii.reg_num_mask	= 0x1f;
+	priv->mii.dev		= netdev;
+	priv->mii.mdio_read	= ftmac100_mdio_read;
+	priv->mii.mdio_write	= ftmac100_mdio_write;
+
+	/* register network device */
+
+	err = register_netdev(netdev);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to register netdev\n");
+		goto err_out;
+	}
+
+	netdev_info(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base);
+
+	if (!is_valid_ether_addr(netdev->dev_addr)) {
+		random_ether_addr(netdev->dev_addr);
+		netdev_info(netdev, "generated random MAC address %pM\n",
+			netdev->dev_addr);
+	}
+
+	return 0;
+
+err_out:
+	ftmac100_remove(pdev);
+	return err;
+}
+
+static struct platform_driver ftmac100_driver = {
+	.probe		= ftmac100_probe,
+	.remove		= ftmac100_remove,
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+/******************************************************************************
+ * initialization / finalization
+ *****************************************************************************/
+static int __init ftmac100_init(void)
+{
+	printk(KERN_INFO "Loading " DRV_NAME ": version " DRV_VERSION " ...\n");
+	return platform_driver_register(&ftmac100_driver);
+}
+
+static void __exit ftmac100_exit(void)
+{
+	platform_driver_unregister(&ftmac100_driver);
+}
+
+module_init(ftmac100_init);
+module_exit(ftmac100_exit);
+
+MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");
+MODULE_DESCRIPTION("FTMAC100 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ftmac100.h b/drivers/net/ftmac100.h
new file mode 100644
index 0000000..46a0c47
--- /dev/null
+++ b/drivers/net/ftmac100.h
@@ -0,0 +1,180 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __FTMAC100_H
+#define __FTMAC100_H
+
+#define	FTMAC100_OFFSET_ISR		0x00
+#define	FTMAC100_OFFSET_IMR		0x04
+#define	FTMAC100_OFFSET_MAC_MADR	0x08
+#define	FTMAC100_OFFSET_MAC_LADR	0x0c
+#define	FTMAC100_OFFSET_MAHT0		0x10
+#define	FTMAC100_OFFSET_MAHT1		0x14
+#define	FTMAC100_OFFSET_TXPD		0x18
+#define	FTMAC100_OFFSET_RXPD		0x1c
+#define	FTMAC100_OFFSET_TXR_BADR	0x20
+#define	FTMAC100_OFFSET_RXR_BADR	0x24
+#define	FTMAC100_OFFSET_ITC		0x28
+#define	FTMAC100_OFFSET_APTC		0x2c
+#define	FTMAC100_OFFSET_DBLAC		0x30
+#define	FTMAC100_OFFSET_MACCR		0x88
+#define	FTMAC100_OFFSET_MACSR		0x8c
+#define	FTMAC100_OFFSET_PHYCR		0x90
+#define	FTMAC100_OFFSET_PHYWDATA	0x94
+#define	FTMAC100_OFFSET_FCR		0x98
+#define	FTMAC100_OFFSET_BPR		0x9c
+#define	FTMAC100_OFFSET_TS		0xc4
+#define	FTMAC100_OFFSET_DMAFIFOS	0xc8
+#define	FTMAC100_OFFSET_TM		0xcc
+#define	FTMAC100_OFFSET_TX_MCOL_SCOL	0xd4
+#define	FTMAC100_OFFSET_RPF_AEP		0xd8
+#define	FTMAC100_OFFSET_XM_PG		0xdc
+#define	FTMAC100_OFFSET_RUNT_TLCC	0xe0
+#define	FTMAC100_OFFSET_CRCER_FTL	0xe4
+#define	FTMAC100_OFFSET_RLC_RCC		0xe8
+#define	FTMAC100_OFFSET_BROC		0xec
+#define	FTMAC100_OFFSET_MULCA		0xf0
+#define	FTMAC100_OFFSET_RP		0xf4
+#define	FTMAC100_OFFSET_XP		0xf8
+
+/*
+ * Interrupt status register & interrupt mask register
+ */
+#define	FTMAC100_INT_RPKT_FINISH	(1 << 0)
+#define	FTMAC100_INT_NORXBUF		(1 << 1)
+#define	FTMAC100_INT_XPKT_FINISH	(1 << 2)
+#define	FTMAC100_INT_NOTXBUF		(1 << 3)
+#define	FTMAC100_INT_XPKT_OK		(1 << 4)
+#define	FTMAC100_INT_XPKT_LOST		(1 << 5)
+#define	FTMAC100_INT_RPKT_SAV		(1 << 6)
+#define	FTMAC100_INT_RPKT_LOST		(1 << 7)
+#define	FTMAC100_INT_AHB_ERR		(1 << 8)
+#define	FTMAC100_INT_PHYSTS_CHG		(1 << 9)
+
+/*
+ * Interrupt timer control register
+ */
+#define FTMAC100_ITC_RXINT_CNT(x)	(((x) & 0xf) << 0)
+#define FTMAC100_ITC_RXINT_THR(x)	(((x) & 0x7) << 4)
+#define FTMAC100_ITC_RXINT_TIME_SEL	(1 << 7)
+#define FTMAC100_ITC_TXINT_CNT(x)	(((x) & 0xf) << 8)
+#define FTMAC100_ITC_TXINT_THR(x)	(((x) & 0x7) << 12)
+#define FTMAC100_ITC_TXINT_TIME_SEL	(1 << 15)
+
+/*
+ * Automatic polling timer control register
+ */
+#define	FTMAC100_APTC_RXPOLL_CNT(x)	(((x) & 0xf) << 0)
+#define	FTMAC100_APTC_RXPOLL_TIME_SEL	(1 << 4)
+#define	FTMAC100_APTC_TXPOLL_CNT(x)	(((x) & 0xf) << 8)
+#define	FTMAC100_APTC_TXPOLL_TIME_SEL	(1 << 12)
+
+/*
+ * DMA burst length and arbitration control register
+ */
+#define FTMAC100_DBLAC_INCR4_EN		(1 << 0)
+#define FTMAC100_DBLAC_INCR8_EN		(1 << 1)
+#define FTMAC100_DBLAC_INCR16_EN	(1 << 2)
+#define FTMAC100_DBLAC_RXFIFO_LTHR(x)	(((x) & 0x7) << 3)
+#define FTMAC100_DBLAC_RXFIFO_HTHR(x)	(((x) & 0x7) << 6)
+#define FTMAC100_DBLAC_RX_THR_EN	(1 << 9)
+
+/*
+ * MAC control register
+ */
+#define	FTMAC100_MACCR_XDMA_EN		(1 << 0)
+#define	FTMAC100_MACCR_RDMA_EN		(1 << 1)
+#define	FTMAC100_MACCR_SW_RST		(1 << 2)
+#define	FTMAC100_MACCR_LOOP_EN		(1 << 3)
+#define	FTMAC100_MACCR_CRC_DIS		(1 << 4)
+#define	FTMAC100_MACCR_XMT_EN		(1 << 5)
+#define	FTMAC100_MACCR_ENRX_IN_HALFTX	(1 << 6)
+#define	FTMAC100_MACCR_RCV_EN		(1 << 8)
+#define	FTMAC100_MACCR_HT_MULTI_EN	(1 << 9)
+#define	FTMAC100_MACCR_RX_RUNT		(1 << 10)
+#define	FTMAC100_MACCR_RX_FTL		(1 << 11)
+#define	FTMAC100_MACCR_RCV_ALL		(1 << 12)
+#define	FTMAC100_MACCR_CRC_APD		(1 << 14)
+#define	FTMAC100_MACCR_FULLDUP		(1 << 15)
+#define	FTMAC100_MACCR_RX_MULTIPKT	(1 << 16)
+#define	FTMAC100_MACCR_RX_BROADPKT	(1 << 17)
+
+/*
+ * PHY control register
+ */
+#define FTMAC100_PHYCR_MIIRDATA		0xffff
+#define FTMAC100_PHYCR_PHYAD(x)		(((x) & 0x1f) << 16)
+#define FTMAC100_PHYCR_REGAD(x)		(((x) & 0x1f) << 21)
+#define FTMAC100_PHYCR_MIIRD		(1 << 26)
+#define FTMAC100_PHYCR_MIIWR		(1 << 27)
+
+/*
+ * PHY write data register
+ */
+#define FTMAC100_PHYWDATA_MIIWDATA(x)	((x) & 0xffff)
+
+/*
+ * Transmit descriptor, aligned to 16 bytes
+ */
+struct ftmac100_txdes {
+	unsigned int	txdes0;
+	unsigned int	txdes1;
+	unsigned int	txdes2;	/* TXBUF_BADR */
+	unsigned int	txdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_TXDES0_TXPKT_LATECOL	(1 << 0)
+#define	FTMAC100_TXDES0_TXPKT_EXSCOL	(1 << 1)
+#define	FTMAC100_TXDES0_TXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_TXDES1_TXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_TXDES1_LTS		(1 << 27)
+#define	FTMAC100_TXDES1_FTS		(1 << 28)
+#define	FTMAC100_TXDES1_TX2FIC		(1 << 29)
+#define	FTMAC100_TXDES1_TXIC		(1 << 30)
+#define	FTMAC100_TXDES1_EDOTR		(1 << 31)
+
+/*
+ * Receive descriptor, aligned to 16 bytes
+ */
+struct ftmac100_rxdes {
+	unsigned int	rxdes0;
+	unsigned int	rxdes1;
+	unsigned int	rxdes2;	/* RXBUF_BADR */
+	unsigned int	rxdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_RXDES0_RFL		0x7ff
+#define	FTMAC100_RXDES0_MULTICAST	(1 << 16)
+#define	FTMAC100_RXDES0_BROADCAST	(1 << 17)
+#define	FTMAC100_RXDES0_RX_ERR		(1 << 18)
+#define	FTMAC100_RXDES0_CRC_ERR		(1 << 19)
+#define	FTMAC100_RXDES0_FTL		(1 << 20)
+#define	FTMAC100_RXDES0_RUNT		(1 << 21)
+#define	FTMAC100_RXDES0_RX_ODD_NB	(1 << 22)
+#define	FTMAC100_RXDES0_LRS		(1 << 28)
+#define	FTMAC100_RXDES0_FRS		(1 << 29)
+#define	FTMAC100_RXDES0_RXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_RXDES1_RXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_RXDES1_EDORR		(1 << 31)
+
+#endif /* __FTMAC100_H */
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-17  9:21 ` [PATCH v2] " Po-Yu Chuang
@ 2011-01-17 17:19   ` Joe Perches
  2011-01-19  9:40     ` Po-Yu Chuang
  2011-01-17 17:29   ` Eric Dumazet
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 74+ messages in thread
From: Joe Perches @ 2011-01-17 17:19 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, ratbert, bhutchings, eric.dumazet, dilinger

On Mon, 2011-01-17 at 17:21 +0800, Po-Yu Chuang wrote:
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
> MII.  This driver has been working on some ARM/NDS32 SoC's including
> Faraday A320 and Andes AG101.

Hi again.

> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
> ---
> v2:
> always use NAPI
> do not use our own net_device_stats structure
> don't set trans_start and last_rx
> stats.rx_packets and stats.rx_bytes include dropped packets
> add missed netif_napi_del()
> initialize spinlocks in probe function
> remove rx_lock and hw_lock
> use netdev_[err/info/dbg] instead of dev_* ones
> use netdev_alloc_skb_ip_align()
> remove ftmac100_get_stats()
> use is_valid_ether_addr() instead of is_zero_ether_addr()
> add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
> use net_ratelimit() instead of printk_ratelimit()
> no explicit inline
> use %pM to print MAC address
> add comment before wmb
> use napi poll() to handle all interrupts

This looks very clean, thanks for doing the rework.

Now the the really trivial...

> + * priveate data

private

> +static void ftmac100_enable_all_int(struct ftmac100 *priv)
> +{
> +	unsigned int imr;
> +
> +	imr = FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF
> +	     | FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST
> +	     | FTMAC100_INT_RPKT_LOST | FTMAC100_INT_AHB_ERR
> +	     | FTMAC100_INT_PHYSTS_CHG;

This could be a #define.

> +	maccr = FTMAC100_MACCR_XMT_EN |
> +		FTMAC100_MACCR_RCV_EN |
> +		FTMAC100_MACCR_XDMA_EN |
> +		FTMAC100_MACCR_RDMA_EN |
> +		FTMAC100_MACCR_CRC_APD |
> +		FTMAC100_MACCR_FULLDUP |
> +		FTMAC100_MACCR_RX_RUNT |
> +		FTMAC100_MACCR_RX_BROADPKT;

Here too.

> +static int ftmac100_rx_packet_error(struct ftmac100 *priv,
> +		struct ftmac100_rxdes *rxdes)
[]
> +	if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
> +		if (net_ratelimit())
> +			netdev_info(netdev, "rx frame too long\n");
> +
> +		netdev->stats.rx_length_errors++;
> +		error = 1;
> +	}
> +
> +	if (unlikely(ftmac100_rxdes_runt(rxdes))) {

else if ?

> +static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
> +{
> +	struct net_device *netdev = priv->netdev;
> +	struct ftmac100_rxdes *rxdes;
> +	struct sk_buff *skb;
> +	int length;
> +	int copied = 0;
> +	int done = 0;

You could use bool/true/false here for copied and done
and all the other uses of an int for a logical bool.

> +static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
> +{
> +	/*
> +	 * Make sure dma own bit will not be set before any other
> +	 * descriptor fiels.

field/fields

> +static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
> +{
> +	struct ftmac100 *priv = netdev_priv(netdev);
> +	int phycr;
> +	int i;
> +
> +	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
> +		FTMAC100_PHYCR_REGAD(reg) |
> +		FTMAC100_PHYCR_MIIRD;
> +
> +	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
> +	for (i = 0; i < 10; i++) {
> +		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
> +
> +		if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
> +			return phycr & FTMAC100_PHYCR_MIIRDATA;
> +
> +		usleep_range(100, 1000);
> +	}
> +
> +	netdev_err(netdev, "mdio read timed out\n");
> +	return 0xffff;

0xffff is a rather odd return, perhaps a #define?

> +/******************************************************************************
> + * initialization / finalization
> + *****************************************************************************/
> +static int __init ftmac100_init(void)
> +{
> +	printk(KERN_INFO "Loading " DRV_NAME ": version " DRV_VERSION " ...\n");

You could use
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
before any #include and
	pr_info("Loading version " DRV_VERSION " ...\n");

One last comment on split long line indentation style
and long function declarations.

There's no required style so you can use what you are
most comfortable doing.

Most of drivers/net uses an alignment to open parenthesis
using maximal tabs and minimal necessary spaces instead of
an extra tabstop.

Like:

static int some_long_function(type var1, type var2...
			      type varN)
and
	some_long_function(var1, var2, ...
			   varN);

not
static int some_long_function(type var1, type var2...
				type varN)
and
	some_long_function(var1, var2, ...
				varN);


^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-17  9:21 ` [PATCH v2] " Po-Yu Chuang
  2011-01-17 17:19   ` Joe Perches
@ 2011-01-17 17:29   ` Eric Dumazet
  2011-01-17 18:58     ` Ben Hutchings
  2011-01-17 18:21   ` Eric Dumazet
  2011-01-20 15:30   ` [PATCH v3] " Po-Yu Chuang
  3 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-01-17 17:29 UTC (permalink / raw)
  To: Po-Yu Chuang; +Cc: netdev, linux-kernel, ratbert, bhutchings, joe, dilinger

Le lundi 17 janvier 2011 à 17:21 +0800, Po-Yu Chuang a écrit :


> +static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
> +{
> +	struct net_device *netdev = priv->netdev;
> +	struct ftmac100_rxdes *rxdes;
> +	struct sk_buff *skb;
> +	int length;
> +	int copied = 0;
> +	int done = 0;
> +
> +	rxdes = ftmac100_rx_locate_first_segment(priv);
> +	if (!rxdes)
> +		return 0;
> +
> +	length = ftmac100_rxdes_frame_length(rxdes);
> +
> +	netdev->stats.rx_packets++;
> +	netdev->stats.rx_bytes += length;
> +
> +	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
> +		ftmac100_rx_drop_packet(priv);
> +		return 1;
> +	}
> +
> +	/* start processing */
> +	skb = netdev_alloc_skb_ip_align(netdev, length);
> +	if (unlikely(!skb)) {
> +		if (net_ratelimit())
> +			netdev_err(netdev, "rx skb alloc failed\n");
> +
> +		ftmac100_rx_drop_packet(priv);
> +		return 1;
> +	}
> +

Please dont increase rx_packets/rx_bytes before the
netdev_alloc_skb_ip_align().

In case of mem allocation failure, it would be better not pretending we
handled a packet.

drivers/net/r8169.c for example does the rx_packets/rx_bytes only if
packet is delivered to upper stack.




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-17  9:21 ` [PATCH v2] " Po-Yu Chuang
  2011-01-17 17:19   ` Joe Perches
  2011-01-17 17:29   ` Eric Dumazet
@ 2011-01-17 18:21   ` Eric Dumazet
  2011-01-18  3:08     ` Po-Yu Chuang
  2011-01-19  9:20     ` Po-Yu Chuang
  2011-01-20 15:30   ` [PATCH v3] " Po-Yu Chuang
  3 siblings, 2 replies; 74+ messages in thread
From: Eric Dumazet @ 2011-01-17 18:21 UTC (permalink / raw)
  To: Po-Yu Chuang; +Cc: netdev, linux-kernel, ratbert, bhutchings, joe, dilinger

Le lundi 17 janvier 2011 à 17:21 +0800, Po-Yu Chuang a écrit :


> +
> +	spin_lock_irqsave(&priv->tx_lock, flags);
> +	ftmac100_txdes_set_skb(txdes, skb);
> +	ftmac100_txdes_set_dma_addr(txdes, map);
> +
> +	ftmac100_txdes_set_first_segment(txdes);
> +	ftmac100_txdes_set_last_segment(txdes);
> +	ftmac100_txdes_set_txint(txdes);
> +	ftmac100_txdes_set_buffer_size(txdes, len);
> +

I wonder if its not too expensive to read/modify/write txdes->txdes1

Maybe you should use a temporary u32 var and perform one final write on
txdes->txdes1 (with the set_dma_own)

> +	priv->tx_pending++;
> +	if (priv->tx_pending == TX_QUEUE_ENTRIES) {
> +		if (net_ratelimit())
> +			netdev_info(netdev, "tx queue full\n");
> +
> +		netif_stop_queue(netdev);
> +	}
> +
> +	/* start transmit */
> +	ftmac100_txdes_set_dma_own(txdes);

	txdes->txdes1 = txdes1;

BTW, shouldnt you use cpu_to_be32() or cpu_to_le32(), if this driver is
multi platform ?




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-17 17:29   ` Eric Dumazet
@ 2011-01-17 18:58     ` Ben Hutchings
  2011-01-17 20:39       ` Eric Dumazet
  0 siblings, 1 reply; 74+ messages in thread
From: Ben Hutchings @ 2011-01-17 18:58 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: Po-Yu Chuang, netdev, linux-kernel, ratbert, joe, dilinger

On Mon, 2011-01-17 at 18:29 +0100, Eric Dumazet wrote:
> Le lundi 17 janvier 2011 à 17:21 +0800, Po-Yu Chuang a écrit :
> 
> 
> > +static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
> > +{
> > +	struct net_device *netdev = priv->netdev;
> > +	struct ftmac100_rxdes *rxdes;
> > +	struct sk_buff *skb;
> > +	int length;
> > +	int copied = 0;
> > +	int done = 0;
> > +
> > +	rxdes = ftmac100_rx_locate_first_segment(priv);
> > +	if (!rxdes)
> > +		return 0;
> > +
> > +	length = ftmac100_rxdes_frame_length(rxdes);
> > +
> > +	netdev->stats.rx_packets++;
> > +	netdev->stats.rx_bytes += length;
> > +
> > +	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
> > +		ftmac100_rx_drop_packet(priv);
> > +		return 1;
> > +	}
> > +
> > +	/* start processing */
> > +	skb = netdev_alloc_skb_ip_align(netdev, length);
> > +	if (unlikely(!skb)) {
> > +		if (net_ratelimit())
> > +			netdev_err(netdev, "rx skb alloc failed\n");
> > +
> > +		ftmac100_rx_drop_packet(priv);
> > +		return 1;
> > +	}
> > +
> 
> Please dont increase rx_packets/rx_bytes before the
> netdev_alloc_skb_ip_align().
> 
> In case of mem allocation failure, it would be better not pretending we
> handled a packet.
>
> drivers/net/r8169.c for example does the rx_packets/rx_bytes only if
> packet is delivered to upper stack.

That's news to me.  I specifically advised Po-Yu Chuang to increment
these earlier because my understanding is that all packets/bytes should
be counted.  And drivers which use hardware MAC stats will generally do
that, so I really don't think it makes sense to make other drivers
different deliberately.

Ben.

-- 
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.


^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-17 18:58     ` Ben Hutchings
@ 2011-01-17 20:39       ` Eric Dumazet
  0 siblings, 0 replies; 74+ messages in thread
From: Eric Dumazet @ 2011-01-17 20:39 UTC (permalink / raw)
  To: Ben Hutchings; +Cc: Po-Yu Chuang, netdev, linux-kernel, ratbert, joe, dilinger

Le lundi 17 janvier 2011 à 18:58 +0000, Ben Hutchings a écrit :
> On Mon, 2011-01-17 at 18:29 +0100, Eric Dumazet wrote:
> > Le lundi 17 janvier 2011 à 17:21 +0800, Po-Yu Chuang a écrit :
> > 
> > 
> > > +static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
> > > +{
> > > +	struct net_device *netdev = priv->netdev;
> > > +	struct ftmac100_rxdes *rxdes;
> > > +	struct sk_buff *skb;
> > > +	int length;
> > > +	int copied = 0;
> > > +	int done = 0;
> > > +
> > > +	rxdes = ftmac100_rx_locate_first_segment(priv);
> > > +	if (!rxdes)
> > > +		return 0;
> > > +
> > > +	length = ftmac100_rxdes_frame_length(rxdes);
> > > +
> > > +	netdev->stats.rx_packets++;
> > > +	netdev->stats.rx_bytes += length;
> > > +
> > > +	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
> > > +		ftmac100_rx_drop_packet(priv);
> > > +		return 1;
> > > +	}
> > > +
> > > +	/* start processing */
> > > +	skb = netdev_alloc_skb_ip_align(netdev, length);
> > > +	if (unlikely(!skb)) {
> > > +		if (net_ratelimit())
> > > +			netdev_err(netdev, "rx skb alloc failed\n");
> > > +
> > > +		ftmac100_rx_drop_packet(priv);
> > > +		return 1;
> > > +	}
> > > +
> > 
> > Please dont increase rx_packets/rx_bytes before the
> > netdev_alloc_skb_ip_align().
> > 
> > In case of mem allocation failure, it would be better not pretending we
> > handled a packet.
> >
> > drivers/net/r8169.c for example does the rx_packets/rx_bytes only if
> > packet is delivered to upper stack.
> 
> That's news to me.  I specifically advised Po-Yu Chuang to increment
> these earlier because my understanding is that all packets/bytes should
> be counted.  And drivers which use hardware MAC stats will generally do
> that, so I really don't think it makes sense to make other drivers
> different deliberately.
> 

I see, but when one frame is dropped because of RX ring buffer
under/overflow we dont account for the lost packet/bytes.

Thats probably not very important, but would be good if all drivers
behave the same.




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-17 18:21   ` Eric Dumazet
@ 2011-01-18  3:08     ` Po-Yu Chuang
  2011-01-19  9:20     ` Po-Yu Chuang
  1 sibling, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-18  3:08 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: netdev, linux-kernel, ratbert, bhutchings, joe, dilinger

Dear Eric,

On Tue, Jan 18, 2011 at 2:21 AM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> BTW, shouldnt you use cpu_to_be32() or cpu_to_le32(), if this driver is
> multi platform ?

This reminds me another thing. Should I use u32 instead of unsigned int for all
hardware related variables (registers, descriptors) ?
Not quite sure about these cross-platform issues.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-17 18:21   ` Eric Dumazet
  2011-01-18  3:08     ` Po-Yu Chuang
@ 2011-01-19  9:20     ` Po-Yu Chuang
  1 sibling, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-19  9:20 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: netdev, linux-kernel, ratbert, bhutchings, joe, dilinger

Dear Eric,

On Tue, Jan 18, 2011 at 2:21 AM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le lundi 17 janvier 2011 à 17:21 +0800, Po-Yu Chuang a écrit :
>> +     spin_lock_irqsave(&priv->tx_lock, flags);
>> +     ftmac100_txdes_set_skb(txdes, skb);
>> +     ftmac100_txdes_set_dma_addr(txdes, map);
>> +
>> +     ftmac100_txdes_set_first_segment(txdes);
>> +     ftmac100_txdes_set_last_segment(txdes);
>> +     ftmac100_txdes_set_txint(txdes);
>> +     ftmac100_txdes_set_buffer_size(txdes, len);
>
> I wonder if its not too expensive to read/modify/write txdes->txdes1
>
> Maybe you should use a temporary u32 var and perform one final write on
> txdes->txdes1 (with the set_dma_own)

That's OK, the compiler combines these bits updates together and write once
since txdes is not volatile. (I checked the object file)
It's not expensive.

BTW, DMA_OWN bit is at txdes0

>> +     priv->tx_pending++;
>> +     if (priv->tx_pending == TX_QUEUE_ENTRIES) {
>> +             if (net_ratelimit())
>> +                     netdev_info(netdev, "tx queue full\n");
>> +
>> +             netif_stop_queue(netdev);
>> +     }
>> +
>> +     /* start transmit */
>> +     ftmac100_txdes_set_dma_own(txdes);
>
>        txdes->txdes1 = txdes1;
>
> BTW, shouldnt you use cpu_to_be32() or cpu_to_le32(), if this driver is
> multi platform ?

OK, but I am not sure if I use them the right way.
Please check my v3 patch later.

Thanks,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-17 17:19   ` Joe Perches
@ 2011-01-19  9:40     ` Po-Yu Chuang
  2011-01-19 12:46       ` Ben Hutchings
  2011-01-19 16:41       ` Joe Perches
  0 siblings, 2 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-19  9:40 UTC (permalink / raw)
  To: Joe Perches
  Cc: netdev, linux-kernel, ratbert, bhutchings, eric.dumazet, dilinger

Dear Joe,

On Tue, Jan 18, 2011 at 1:19 AM, Joe Perches <joe@perches.com> wrote:
> On Mon, 2011-01-17 at 17:21 +0800, Po-Yu Chuang wrote:
>
>> + * priveate data
>
> private

Fixed.

>> +static void ftmac100_enable_all_int(struct ftmac100 *priv)
>> +{
>> +     unsigned int imr;
>> +
>> +     imr = FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF
>> +          | FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST
>> +          | FTMAC100_INT_RPKT_LOST | FTMAC100_INT_AHB_ERR
>> +          | FTMAC100_INT_PHYSTS_CHG;
>
> This could be a #define.

OK, done.

>> +     maccr = FTMAC100_MACCR_XMT_EN |
>> +             FTMAC100_MACCR_RCV_EN |
>> +             FTMAC100_MACCR_XDMA_EN |
>> +             FTMAC100_MACCR_RDMA_EN |
>> +             FTMAC100_MACCR_CRC_APD |
>> +             FTMAC100_MACCR_FULLDUP |
>> +             FTMAC100_MACCR_RX_RUNT |
>> +             FTMAC100_MACCR_RX_BROADPKT;
>
> Here too.

OK, done.

>> +static int ftmac100_rx_packet_error(struct ftmac100 *priv,
>> +             struct ftmac100_rxdes *rxdes)
> []
>> +     if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
>> +             if (net_ratelimit())
>> +                     netdev_info(netdev, "rx frame too long\n");
>> +
>> +             netdev->stats.rx_length_errors++;
>> +             error = 1;
>> +     }
>> +
>> +     if (unlikely(ftmac100_rxdes_runt(rxdes))) {
>
> else if ?

OK, fixed.

>> +static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
>> +{
>> +     struct net_device *netdev = priv->netdev;
>> +     struct ftmac100_rxdes *rxdes;
>> +     struct sk_buff *skb;
>> +     int length;
>> +     int copied = 0;
>> +     int done = 0;
>
> You could use bool/true/false here for copied and done
> and all the other uses of an int for a logical bool.

OK, fixed.

>> +static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
>> +{
>> +     /*
>> +      * Make sure dma own bit will not be set before any other
>> +      * descriptor fiels.
>
> field/fields

Fixed.

>> +static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
>> +{
>> +     struct ftmac100 *priv = netdev_priv(netdev);
>> +     int phycr;
>> +     int i;
>> +
>> +     phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
>> +             FTMAC100_PHYCR_REGAD(reg) |
>> +             FTMAC100_PHYCR_MIIRD;
>> +
>> +     iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
>> +     for (i = 0; i < 10; i++) {
>> +             phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
>> +
>> +             if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
>> +                     return phycr & FTMAC100_PHYCR_MIIRDATA;
>> +
>> +             usleep_range(100, 1000);
>> +     }
>> +
>> +     netdev_err(netdev, "mdio read timed out\n");
>> +     return 0xffff;
>
> 0xffff is a rather odd return, perhaps a #define?

After a little digging in drivers/net/mii.c, it seems that mii lib does not
check return value if it is error. So I guess I should return 0 if error.

>> +/******************************************************************************
>> + * initialization / finalization
>> + *****************************************************************************/
>> +static int __init ftmac100_init(void)
>> +{
>> +     printk(KERN_INFO "Loading " DRV_NAME ": version " DRV_VERSION " ...\n");
>
> You could use
> #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> before any #include and
>        pr_info("Loading version " DRV_VERSION " ...\n");

OK

> One last comment on split long line indentation style
> and long function declarations.
>
> There's no required style so you can use what you are
> most comfortable doing.
>
> Most of drivers/net uses an alignment to open parenthesis
> using maximal tabs and minimal necessary spaces instead of
> an extra tabstop.
>
> Like:
>
> static int some_long_function(type var1, type var2...
>                              type varN)
> and
>        some_long_function(var1, var2, ...
>                           varN);
>
> not
> static int some_long_function(type var1, type var2...
>                                type varN)
> and
>        some_long_function(var1, var2, ...
>                                varN);

Well, TBH, I don't like this style because if I changed the
function name, the indentation might need to be adjusted.

Even worse, I got an infeasible case :-(

static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
							       struct ftmac100 *priv)

I know my function names are quite long, but I like them to be descriptive.
Do you really insist on it?

Thanks,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-19  9:40     ` Po-Yu Chuang
@ 2011-01-19 12:46       ` Ben Hutchings
  2011-01-19 16:41       ` Joe Perches
  1 sibling, 0 replies; 74+ messages in thread
From: Ben Hutchings @ 2011-01-19 12:46 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: Joe Perches, netdev, linux-kernel, ratbert, eric.dumazet, dilinger

On Wed, 2011-01-19 at 17:40 +0800, Po-Yu Chuang wrote:
[...]
> Well, TBH, I don't like this style because if I changed the
> function name, the indentation might need to be adjusted.
> 
> Even worse, I got an infeasible case :-(
> 
> static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
> 							       struct ftmac100 *priv)
> 
> I know my function names are quite long, but I like them to be descriptive.
> Do you really insist on it?

You can break after the return type:

static struct ftmac100_rxdes *
ftmac100_rx_locate_first_segment(struct ftmac100 *priv)

Ben.

-- 
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.


^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-19  9:40     ` Po-Yu Chuang
  2011-01-19 12:46       ` Ben Hutchings
@ 2011-01-19 16:41       ` Joe Perches
  2011-01-20  5:30         ` Po-Yu Chuang
  1 sibling, 1 reply; 74+ messages in thread
From: Joe Perches @ 2011-01-19 16:41 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, ratbert, bhutchings, eric.dumazet, dilinger

On Wed, 2011-01-19 at 17:40 +0800, Po-Yu Chuang wrote:
> On Tue, Jan 18, 2011 at 1:19 AM, Joe Perches <joe@perches.com> wrote:
> > on split long line indentation style
> > and long function declarations.
[]
> > Most of drivers/net uses an alignment to open parenthesis
> > using maximal tabs and minimal necessary spaces instead of
> > an extra tabstop.
[]
> Well, TBH, I don't like this style because if I changed the
> function name, the indentation might need to be adjusted.

No worries.  That could happen using either style.

There's no required style so you can use what you are
most comfortable doing.  It's not a big deal at all.

> Even worse, I got an infeasible case :-(
> 
> static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
> 							       struct ftmac100 *priv)
> 
> I know my function names are quite long, but I like them to be descriptive.
> Do you really insist on it?

Here's a common alternative style for this case:

static struct ftmac100_rxdes *
ftmac100_rx_locate_first_segment(struct ftmac100 *priv)



^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-19 16:41       ` Joe Perches
@ 2011-01-20  5:30         ` Po-Yu Chuang
  2011-01-20  8:46           ` Joe Perches
  0 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-20  5:30 UTC (permalink / raw)
  To: Joe Perches
  Cc: netdev, linux-kernel, ratbert, bhutchings, eric.dumazet, dilinger

Dear Joe,

On Thu, Jan 20, 2011 at 12:41 AM, Joe Perches <joe@perches.com> wrote:
> On Wed, 2011-01-19 at 17:40 +0800, Po-Yu Chuang wrote:
>> Well, TBH, I don't like this style because if I changed the
>> function name, the indentation might need to be adjusted.
>
> No worries.  That could happen using either style.
>
> There's no required style so you can use what you are
> most comfortable doing.  It's not a big deal at all.
>
>> Even worse, I got an infeasible case :-(
>>
>> static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
>>                                                              struct ftmac100 *priv)
>>
>> I know my function names are quite long, but I like them to be descriptive.
>> Do you really insist on it?
>
> Here's a common alternative style for this case:
>
> static struct ftmac100_rxdes *
> ftmac100_rx_locate_first_segment(struct ftmac100 *priv)

OK, I see.

One more question: how to deal with this? Add a local variable for the
2nd argument?

	ftmac100_set_receive_ring_base(priv,
				       priv->descs_dma_addr + offsetof(struct ftmac100_descs, rxdes));

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v2] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20  5:30         ` Po-Yu Chuang
@ 2011-01-20  8:46           ` Joe Perches
  0 siblings, 0 replies; 74+ messages in thread
From: Joe Perches @ 2011-01-20  8:46 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, ratbert, bhutchings, eric.dumazet, dilinger

On Thu, 2011-01-20 at 13:30 +0800, Po-Yu Chuang wrote:
> One more question: how to deal with this? Add a local variable for the
> 2nd argument?
> 
> 	ftmac100_set_receive_ring_base(priv,
> 				       priv->descs_dma_addr + offsetof(struct ftmac100_descs, rxdes));

That's one way, another is:

	ftmac100_set_receive_ring_base(priv,
 				       priv->descs_dma_addr +
				       offsetof(struct ftmac100_descs, rxdes));

Another would be to change set_receive_ring_base to
just pass priv and determine the dma address there:

static void ftmac100_set_receive_ring_base(struct ftmac100 *priv)
{
	dma_addr_t addr;

	addr = priv->descs_dma_addr + offsetof(struct ftmac100_descs, rxdes);
	iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
}
...
	ftmac100_set_receive_ring_base(priv);

Another is not to be overly adherent to 80 columns.

Pick one that suits you.

You chose to use a lot of single use, single line
functions with descriptive names that use iowrite32 or
return some flag.  I probably would have just used
iowrite32 or tested the flag directly, but that's your
choice and it's perfectly fine.

There are a lot of coding choices that are readable and
good.  There isn't and shouldn't be some mandate for some
specific code appearance before inclusion or acceptance.

checkpatch is just a style guide.  Ignore it and ignore
me when you feel it's appropriate.  I won't mind.

cheers, Joe


^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-17  9:21 ` [PATCH v2] " Po-Yu Chuang
                     ` (2 preceding siblings ...)
  2011-01-17 18:21   ` Eric Dumazet
@ 2011-01-20 15:30   ` Po-Yu Chuang
  2011-01-20 15:35     ` Eric Dumazet
                       ` (5 more replies)
  3 siblings, 6 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-20 15:30 UTC (permalink / raw)
  To: netdev
  Cc: linux-kernel, bhutchings, eric.dumazet, joe, dilinger, Po-Yu Chuang

From: Po-Yu Chuang <ratbert@faraday-tech.com>

FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII.  This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.

Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts

v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix

 drivers/net/Kconfig    |    9 +
 drivers/net/Makefile   |    1 +
 drivers/net/ftmac100.c | 1243 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/ftmac100.h |  180 +++++++
 4 files changed, 1433 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/ftmac100.c
 create mode 100644 drivers/net/ftmac100.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 4f1755b..26da0ee 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2014,6 +2014,15 @@ config BCM63XX_ENET
 	  This driver supports the ethernet MACs in the Broadcom 63xx
 	  MIPS chipset family (BCM63XX).
 
+config FTMAC100
+	tristate "Faraday FTMAC100 10/100 Ethernet support"
+	depends on ARM
+	select MII
+	help
+	  This driver supports the FTMAC100 Ethernet controller from
+	  Faraday. It is used on Faraday A320, Andes AG101, AG101P
+	  and some other ARM/NDS32 SoC's.
+
 source "drivers/net/fs_enet/Kconfig"
 
 source "drivers/net/octeon/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..7c21711 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_FORCEDETH) += forcedeth.o
 obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
 obj-$(CONFIG_AX88796) += ax88796.o
 obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
+obj-$(CONFIG_FTMAC100) += ftmac100.o
 
 obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
new file mode 100644
index 0000000..3d39fe1
--- /dev/null
+++ b/drivers/net/ftmac100.c
@@ -0,0 +1,1243 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#include "ftmac100.h"
+
+#define DRV_NAME	"ftmac100"
+#define DRV_VERSION	"0.2"
+
+#define RX_QUEUE_ENTRIES	128	/* must be power of 2 */
+#define TX_QUEUE_ENTRIES	16	/* must be power of 2 */
+
+#define MAX_PKT_SIZE		1518
+#define RX_BUF_SIZE		2044	/* must be smaller than 0x7ff */
+
+/******************************************************************************
+ * private data
+ *****************************************************************************/
+struct ftmac100_descs {
+	struct ftmac100_rxdes	rxdes[RX_QUEUE_ENTRIES];
+	struct ftmac100_txdes	txdes[TX_QUEUE_ENTRIES];
+};
+
+struct ftmac100 {
+	struct resource		*res;
+	void			*base;
+	int			irq;
+
+	struct ftmac100_descs	*descs;
+	dma_addr_t		descs_dma_addr;
+
+	unsigned int		rx_pointer;
+	unsigned int		tx_clean_pointer;
+	unsigned int		tx_pointer;
+	unsigned int		tx_pending;
+
+	spinlock_t		tx_lock;
+
+	struct net_device	*netdev;
+	struct device		*dev;
+	struct napi_struct	napi;
+
+	struct mii_if_info	mii;
+};
+
+/******************************************************************************
+ * internal functions (hardware register access)
+ *****************************************************************************/
+#define INT_MASK_ALL_ENABLED	(FTMAC100_INT_RPKT_FINISH	| \
+				 FTMAC100_INT_NORXBUF		| \
+				 FTMAC100_INT_XPKT_OK		| \
+				 FTMAC100_INT_XPKT_LOST		| \
+				 FTMAC100_INT_RPKT_LOST		| \
+				 FTMAC100_INT_AHB_ERR		| \
+				 FTMAC100_INT_PHYSTS_CHG)
+
+static void ftmac100_enable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_disable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_set_rx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
+}
+
+static void ftmac100_set_tx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_TXR_BADR);
+}
+
+static void ftmac100_txdma_start_polling(struct ftmac100 *priv)
+{
+	iowrite32(1, priv->base + FTMAC100_OFFSET_TXPD);
+}
+
+static int ftmac100_reset(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	int i;
+
+	/* NOTE: reset clears all registers */
+	iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
+
+	for (i = 0; i < 5; i++) {
+		unsigned int maccr;
+
+		maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
+		if (!(maccr & FTMAC100_MACCR_SW_RST)) {
+			/*
+			 * FTMAC100_MACCR_SW_RST cleared does not indicate
+			 * that hardware reset completed (what the f*ck).
+			 * We still need to wait for a while.
+			 */
+			usleep_range(500, 1000);
+			return 0;
+		}
+
+		usleep_range(1000, 10000);
+	}
+
+	netdev_err(netdev, "software reset failed\n");
+	return -EIO;
+}
+
+static void ftmac100_set_mac(struct ftmac100 *priv, const unsigned char *mac)
+{
+	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 + FTMAC100_OFFSET_MAC_MADR);
+	iowrite32(laddr, priv->base + FTMAC100_OFFSET_MAC_LADR);
+}
+
+#define MACCR_ENABLE_ALL	(FTMAC100_MACCR_XMT_EN	| \
+				 FTMAC100_MACCR_RCV_EN	| \
+				 FTMAC100_MACCR_XDMA_EN	| \
+				 FTMAC100_MACCR_RDMA_EN	| \
+				 FTMAC100_MACCR_CRC_APD	| \
+				 FTMAC100_MACCR_FULLDUP	| \
+				 FTMAC100_MACCR_RX_RUNT	| \
+				 FTMAC100_MACCR_RX_BROADPKT)
+
+static int ftmac100_start_hw(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+
+	if (ftmac100_reset(priv))
+		return -EIO;
+
+	/* setup ring buffer base registers */
+
+	ftmac100_set_rx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, rxdes));
+	ftmac100_set_tx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, txdes));
+
+	iowrite32(FTMAC100_APTC_RXPOLL_CNT(1), priv->base + FTMAC100_OFFSET_APTC);
+
+	ftmac100_set_mac(priv, netdev->dev_addr);
+
+	iowrite32(MACCR_ENABLE_ALL, priv->base + FTMAC100_OFFSET_MACCR);
+	return 0;
+}
+
+static void ftmac100_stop_hw(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_MACCR);
+}
+
+/******************************************************************************
+ * internal functions (receive descriptor)
+ *****************************************************************************/
+static bool ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_FRS;
+}
+
+static bool ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_LRS;
+}
+
+static bool ftmac100_rxdes_owned_by_dma(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_RXDMA_OWN;
+}
+
+static void ftmac100_rxdes_set_dma_own(struct ftmac100_rxdes *rxdes)
+{
+	/* clear status bits */
+	rxdes->rxdes0 = cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static bool ftmac100_rxdes_rx_error(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_RX_ERR;
+}
+
+static bool ftmac100_rxdes_crc_error(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_CRC_ERR;
+}
+
+static bool ftmac100_rxdes_frame_too_long(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_FTL;
+}
+
+static bool ftmac100_rxdes_runt(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_RUNT;
+}
+
+static bool ftmac100_rxdes_odd_nibble(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_RX_ODD_NB;
+}
+
+static unsigned int ftmac100_rxdes_frame_length(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_RFL;
+}
+
+static bool ftmac100_rxdes_multicast(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_MULTICAST;
+}
+
+static void ftmac100_rxdes_set_buffer_size(struct ftmac100_rxdes *rxdes,
+					   unsigned int size)
+{
+	unsigned int tmp = le32_to_cpu(rxdes->rxdes1);
+
+	tmp &= FTMAC100_RXDES1_EDORR;
+	tmp |= FTMAC100_RXDES1_RXBUF_SIZE(size);
+	rxdes->rxdes1 = cpu_to_le32(tmp);
+}
+
+static void ftmac100_rxdes_set_end_of_ring(struct ftmac100_rxdes *rxdes)
+{
+	unsigned int tmp = le32_to_cpu(rxdes->rxdes1);
+
+	tmp |= FTMAC100_RXDES1_EDORR;
+	rxdes->rxdes1 = cpu_to_le32(tmp);
+}
+
+static void ftmac100_rxdes_set_dma_addr(struct ftmac100_rxdes *rxdes,
+					dma_addr_t addr)
+{
+	rxdes->rxdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_rxdes_get_dma_addr(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes2);
+}
+
+/* rxdes3 is not used by hardware, we use it to keep track of buffer */
+static void ftmac100_rxdes_set_va(struct ftmac100_rxdes *rxdes, void *addr)
+{
+	rxdes->rxdes3 = cpu_to_le32(addr);
+}
+
+static void *ftmac100_rxdes_get_va(struct ftmac100_rxdes *rxdes)
+{
+	return (void *)le32_to_cpu(rxdes->rxdes3);
+}
+
+/******************************************************************************
+ * internal functions (receive)
+ *****************************************************************************/
+static int ftmac100_next_rx_pointer(int pointer)
+{
+	return (pointer + 1) & (RX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_rx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->rx_pointer = ftmac100_next_rx_pointer(priv->rx_pointer);
+}
+
+static struct ftmac100_rxdes *ftmac100_current_rxdes(struct ftmac100 *priv)
+{
+	return &priv->descs->rxdes[priv->rx_pointer];
+}
+
+static struct ftmac100_rxdes *
+ftmac100_rx_locate_first_segment(struct ftmac100 *priv)
+{
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+
+	while (!ftmac100_rxdes_owned_by_dma(rxdes)) {
+		if (ftmac100_rxdes_first_segment(rxdes))
+			return rxdes;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	}
+
+	return NULL;
+}
+
+static bool ftmac100_rx_packet_error(struct ftmac100 *priv,
+				     struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	bool error = false;
+
+	if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx err\n");
+
+		netdev->stats.rx_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_crc_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx crc err\n");
+
+		netdev->stats.rx_crc_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx frame too long\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_runt(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx runt\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_odd_nibble(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx odd nibble\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	}
+
+	return error;
+}
+
+static void ftmac100_rx_drop_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+	bool done = false;
+
+	if (net_ratelimit())
+		netdev_dbg(netdev, "drop packet %p\n", rxdes);
+
+	do {
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = true;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && !ftmac100_rxdes_owned_by_dma(rxdes));
+
+	netdev->stats.rx_dropped++;
+}
+
+static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes;
+	struct sk_buff *skb;
+	int length;
+	bool copied = false;
+	bool done = false;
+
+	rxdes = ftmac100_rx_locate_first_segment(priv);
+	if (!rxdes)
+		return false;
+
+	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	/* start processing */
+
+	length = ftmac100_rxdes_frame_length(rxdes);
+
+	skb = netdev_alloc_skb_ip_align(netdev, length);
+	if (unlikely(!skb)) {
+		if (net_ratelimit())
+			netdev_err(netdev, "rx skb alloc failed\n");
+
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	if (unlikely(ftmac100_rxdes_multicast(rxdes)))
+		netdev->stats.multicast++;
+
+	do {
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *buf = ftmac100_rxdes_get_va(rxdes);
+		int size;
+
+		size = min(length - copied, RX_BUF_SIZE);
+
+		dma_sync_single_for_cpu(priv->dev, d, RX_BUF_SIZE,
+					DMA_FROM_DEVICE);
+		memcpy(skb_put(skb, size), buf, size);
+
+		copied += size;
+
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = true;
+
+		dma_sync_single_for_device(priv->dev, d, RX_BUF_SIZE,
+					   DMA_FROM_DEVICE);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && copied < length);
+
+	skb->protocol = eth_type_trans(skb, netdev);
+
+	/* push packet to protocol stack */
+	netif_receive_skb(skb);
+
+	netdev->stats.rx_packets++;
+	netdev->stats.rx_bytes += skb->len;
+
+	(*processed)++;
+
+	return true;
+}
+
+/******************************************************************************
+ * internal functions (transmit descriptor)
+ *****************************************************************************/
+static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
+{
+	/* clear all except end of ring bit */
+	txdes->txdes0 = 0;
+	txdes->txdes1 &= FTMAC100_TXDES1_EDOTR;
+	txdes->txdes2 = 0;
+	txdes->txdes3 = 0;
+}
+
+static bool ftmac100_txdes_owned_by_dma(struct ftmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes0) & FTMAC100_TXDES0_TXDMA_OWN;
+}
+
+static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
+{
+	unsigned int tmp = le32_to_cpu(txdes->txdes0);
+
+	tmp |= FTMAC100_TXDES0_TXDMA_OWN;
+
+	/*
+	 * Make sure dma own bit will not be set before any other
+	 * descriptor fields.
+	 */
+	wmb();
+	txdes->txdes0 = cpu_to_le32(tmp);
+}
+
+static bool ftmac100_txdes_excessive_collision(struct ftmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes0) & FTMAC100_TXDES0_TXPKT_EXSCOL;
+}
+
+static bool ftmac100_txdes_late_collision(struct ftmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes0) & FTMAC100_TXDES0_TXPKT_LATECOL;
+}
+
+static void ftmac100_txdes_set_end_of_ring(struct ftmac100_txdes *txdes)
+{
+	unsigned int tmp = le32_to_cpu(txdes->txdes1);
+
+	tmp |= FTMAC100_TXDES1_EDOTR;
+	txdes->txdes1 = cpu_to_le32(tmp);
+}
+
+static void ftmac100_txdes_set_first_segment(struct ftmac100_txdes *txdes)
+{
+	unsigned int tmp = le32_to_cpu(txdes->txdes1);
+
+	tmp |= FTMAC100_TXDES1_FTS;
+	txdes->txdes1 = cpu_to_le32(tmp);
+}
+
+static void ftmac100_txdes_set_last_segment(struct ftmac100_txdes *txdes)
+{
+	unsigned int tmp = le32_to_cpu(txdes->txdes1);
+
+	tmp |= FTMAC100_TXDES1_LTS;
+	txdes->txdes1 = cpu_to_le32(tmp);
+}
+
+static void ftmac100_txdes_set_txint(struct ftmac100_txdes *txdes)
+{
+	unsigned int tmp = le32_to_cpu(txdes->txdes1);
+
+	tmp |= FTMAC100_TXDES1_TXIC;
+	txdes->txdes1 = cpu_to_le32(tmp);
+}
+
+static void ftmac100_txdes_set_buffer_size(struct ftmac100_txdes *txdes,
+					   unsigned int len)
+{
+	unsigned int tmp = le32_to_cpu(txdes->txdes1);
+
+	tmp |= FTMAC100_TXDES1_TXBUF_SIZE(len);
+	txdes->txdes1 = cpu_to_le32(tmp);
+}
+
+static void ftmac100_txdes_set_dma_addr(struct ftmac100_txdes *txdes,
+					dma_addr_t addr)
+{
+	txdes->txdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_txdes_get_dma_addr(struct ftmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes2);
+}
+
+/* txdes3 is not used by hardware, we use it to keep track of socket buffer */
+static void ftmac100_txdes_set_skb(struct ftmac100_txdes *txdes,
+				   struct sk_buff *skb)
+{
+	txdes->txdes3 = cpu_to_le32(skb);
+}
+
+static struct sk_buff *ftmac100_txdes_get_skb(struct ftmac100_txdes *txdes)
+{
+	return (struct sk_buff *)le32_to_cpu(txdes->txdes3);
+}
+
+/******************************************************************************
+ * internal functions (transmit)
+ *****************************************************************************/
+static int ftmac100_next_tx_pointer(int pointer)
+{
+	return (pointer + 1) & (TX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_tx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_pointer = ftmac100_next_tx_pointer(priv->tx_pointer);
+}
+
+static void ftmac100_tx_clean_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_clean_pointer = ftmac100_next_tx_pointer(priv->tx_clean_pointer);
+}
+
+static struct ftmac100_txdes *ftmac100_current_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_pointer];
+}
+
+static struct ftmac100_txdes *
+ftmac100_current_clean_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_clean_pointer];
+}
+
+static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	struct sk_buff *skb;
+	dma_addr_t map;
+
+	if (priv->tx_pending == 0)
+		return false;
+
+	txdes = ftmac100_current_clean_txdes(priv);
+
+	if (ftmac100_txdes_owned_by_dma(txdes))
+		return false;
+
+	skb = ftmac100_txdes_get_skb(txdes);
+	map = ftmac100_txdes_get_dma_addr(txdes);
+
+	if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
+		     ftmac100_txdes_late_collision(txdes))) {
+		/*
+		 * packet transmitted to ethernet lost due to late collision
+		 * or excessive collision
+		 */
+		netdev->stats.tx_aborted_errors++;
+	} else {
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += skb->len;
+	}
+
+	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+
+	dev_kfree_skb_irq(skb);
+
+	ftmac100_txdes_reset(txdes);
+
+	ftmac100_tx_clean_pointer_advance(priv);
+
+	priv->tx_pending--;
+	netif_wake_queue(netdev);
+
+	return true;
+}
+
+static void ftmac100_tx_complete(struct ftmac100 *priv)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->tx_lock, flags);
+	while (ftmac100_tx_complete_packet(priv))
+		;
+	spin_unlock_irqrestore(&priv->tx_lock, flags);
+}
+
+static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
+			 dma_addr_t map)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
+	unsigned long flags;
+
+	txdes = ftmac100_current_txdes(priv);
+	ftmac100_tx_pointer_advance(priv);
+
+	/* setup TX descriptor */
+
+	spin_lock_irqsave(&priv->tx_lock, flags);
+	ftmac100_txdes_set_skb(txdes, skb);
+	ftmac100_txdes_set_dma_addr(txdes, map);
+
+	ftmac100_txdes_set_first_segment(txdes);
+	ftmac100_txdes_set_last_segment(txdes);
+	ftmac100_txdes_set_txint(txdes);
+	ftmac100_txdes_set_buffer_size(txdes, len);
+
+	priv->tx_pending++;
+	if (priv->tx_pending == TX_QUEUE_ENTRIES) {
+		if (net_ratelimit())
+			netdev_info(netdev, "tx queue full\n");
+
+		netif_stop_queue(netdev);
+	}
+
+	/* start transmit */
+	ftmac100_txdes_set_dma_own(txdes);
+	spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+	ftmac100_txdma_start_polling(priv);
+
+	return NETDEV_TX_OK;
+}
+
+/******************************************************************************
+ * internal functions (buffer)
+ *****************************************************************************/
+static void ftmac100_free_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *page = ftmac100_rxdes_get_va(rxdes);
+
+		if (d)
+			dma_unmap_single(priv->dev, d, PAGE_SIZE,
+					 DMA_FROM_DEVICE);
+
+		if (page != NULL)
+			free_page((unsigned long)page);
+	}
+
+	for (i = 0; i < TX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_txdes *txdes = &priv->descs->txdes[i];
+		struct sk_buff *skb = ftmac100_txdes_get_skb(txdes);
+
+		if (skb) {
+			dma_addr_t map;
+
+			map = ftmac100_txdes_get_dma_addr(txdes);
+			dma_unmap_single(priv->dev, map, skb_headlen(skb),
+					 DMA_TO_DEVICE);
+			dev_kfree_skb(skb);
+		}
+	}
+
+	dma_free_coherent(priv->dev, sizeof(struct ftmac100_descs),
+			  priv->descs, priv->descs_dma_addr);
+}
+
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	priv->descs = dma_alloc_coherent(priv->dev,
+					 sizeof(struct ftmac100_descs),
+					 &priv->descs_dma_addr,
+					 GFP_KERNEL | GFP_DMA);
+	if (priv->descs == NULL)
+		return -ENOMEM;
+
+	memset(priv->descs, 0, sizeof(struct ftmac100_descs));
+
+	/* initialize RX ring */
+
+	ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		void *page;
+		dma_addr_t d;
+
+		page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
+		if (page == NULL)
+			goto err;
+
+		d = dma_map_single(priv->dev, page, PAGE_SIZE, DMA_FROM_DEVICE);
+		if (unlikely(dma_mapping_error(priv->dev, d))) {
+			free_page((unsigned long)page);
+			goto err;
+		}
+
+		/*
+		 * The hardware enforces a sub-2K maximum packet size, so we
+		 * put two buffers on every hardware page.
+		 */
+		ftmac100_rxdes_set_va(rxdes, page);
+		ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_dma_addr(rxdes, d);
+		ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
+		ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rxdes_set_dma_own(rxdes + 1);
+	}
+
+	/* initialize TX ring */
+
+	ftmac100_txdes_set_end_of_ring(&priv->descs->txdes[TX_QUEUE_ENTRIES - 1]);
+	return 0;
+
+err:
+	ftmac100_free_buffers(priv);
+	return -ENOMEM;
+}
+
+/******************************************************************************
+ * struct mii_if_info functions
+ *****************************************************************************/
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIRD;
+
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
+			return phycr & FTMAC100_PHYCR_MIIRDATA;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio read timed out\n");
+	return 0;
+}
+
+static void ftmac100_mdio_write(struct net_device *netdev, int phy_id, int reg,
+				int data)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIWR;
+
+	data = FTMAC100_PHYWDATA_MIIWDATA(data);
+
+	iowrite32(data, priv->base + FTMAC100_OFFSET_PHYWDATA);
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIWR) == 0)
+			return;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio write timed out\n");
+}
+
+/******************************************************************************
+ * struct ethtool_ops functions
+ *****************************************************************************/
+static void ftmac100_get_drvinfo(struct net_device *netdev,
+				 struct ethtool_drvinfo *info)
+{
+	strcpy(info->driver, DRV_NAME);
+	strcpy(info->version, DRV_VERSION);
+	strcpy(info->bus_info, dev_name(&netdev->dev));
+}
+
+static int ftmac100_get_settings(struct net_device *netdev,
+				 struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_gset(&priv->mii, cmd);
+}
+
+static int ftmac100_set_settings(struct net_device *netdev,
+				 struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_sset(&priv->mii, cmd);
+}
+
+static int ftmac100_nway_reset(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_nway_restart(&priv->mii);
+}
+
+static u32 ftmac100_get_link(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_link_ok(&priv->mii);
+}
+
+static const struct ethtool_ops ftmac100_ethtool_ops = {
+	.set_settings		= ftmac100_set_settings,
+	.get_settings		= ftmac100_get_settings,
+	.get_drvinfo		= ftmac100_get_drvinfo,
+	.nway_reset		= ftmac100_nway_reset,
+	.get_link		= ftmac100_get_link,
+};
+
+/******************************************************************************
+ * interrupt handler
+ *****************************************************************************/
+static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	if (likely(netif_running(netdev))) {
+		/* Disable interrupts for polling */
+		ftmac100_disable_all_int(priv);
+		napi_schedule(&priv->napi);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/******************************************************************************
+ * struct napi_struct functions
+ *****************************************************************************/
+static int ftmac100_poll(struct napi_struct *napi, int budget)
+{
+	struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
+	struct net_device *netdev = priv->netdev;
+	unsigned int status;
+	bool completed = true;
+	int rx = 0;
+
+	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
+
+	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
+		/*
+		 * FTMAC100_INT_RPKT_FINISH:
+		 *	RX DMA has received packets into RX buffer successfully
+		 *
+		 * FTMAC100_INT_NORXBUF:
+		 *	RX buffer unavailable
+		 */
+		bool retry;
+
+		do {
+			retry = ftmac100_rx_packet(priv, &rx);
+		} while (retry && rx < budget);
+
+		if (retry && rx == budget)
+			completed = false;
+	}
+
+	if (status & FTMAC100_INT_NORXBUF) {
+		/* RX buffer unavailable */
+		if (net_ratelimit())
+			netdev_info(netdev, "INT_NORXBUF\n");
+
+		netdev->stats.rx_over_errors++;
+	}
+
+	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
+		/*
+		 * FTMAC100_INT_XPKT_OK:
+		 *	 packet transmitted to ethernet successfully
+		 *
+		 * FTMAC100_INT_XPKT_LOST:
+		 *	packet transmitted to ethernet lost due to late
+		 *	collision or excessive collision
+		 */
+		ftmac100_tx_complete(priv);
+	}
+
+	if (status & FTMAC100_INT_RPKT_LOST) {
+		/* received packet lost due to RX FIFO full */
+		if (net_ratelimit())
+			netdev_info(netdev, "INT_RPKT_LOST\n");
+
+		netdev->stats.rx_fifo_errors++;
+	}
+
+	if (status & FTMAC100_INT_AHB_ERR) {
+		/* AHB error */
+		if (net_ratelimit())
+			netdev_info(netdev, "INT_AHB_ERR\n");
+
+		/* do nothing */
+	}
+
+	if (status & FTMAC100_INT_PHYSTS_CHG) {
+		/* PHY link status change */
+		if (net_ratelimit())
+			netdev_info(netdev, "INT_PHYSTS_CHG\n");
+
+		mii_check_link(&priv->mii);
+	}
+
+	if (completed) {
+		/* stop polling */
+		napi_complete(napi);
+		ftmac100_enable_all_int(priv);
+	}
+
+	return rx;
+}
+
+/******************************************************************************
+ * struct net_device_ops functions
+ *****************************************************************************/
+static int ftmac100_open(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int err;
+
+	err = ftmac100_alloc_buffers(priv);
+	if (err) {
+		netdev_err(netdev, "failed to allocate buffers\n");
+		goto err_alloc;
+	}
+
+	err = request_irq(priv->irq, ftmac100_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 = ftmac100_start_hw(priv);
+	if (err)
+		goto err_hw;
+
+	napi_enable(&priv->napi);
+	netif_start_queue(netdev);
+
+	ftmac100_enable_all_int(priv);
+	return 0;
+
+err_hw:
+	free_irq(priv->irq, netdev);
+err_irq:
+	ftmac100_free_buffers(priv);
+err_alloc:
+	return err;
+}
+
+static int ftmac100_stop(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	ftmac100_disable_all_int(priv);
+	netif_stop_queue(netdev);
+	napi_disable(&priv->napi);
+	ftmac100_stop_hw(priv);
+	free_irq(priv->irq, netdev);
+	ftmac100_free_buffers(priv);
+
+	return 0;
+}
+
+static int ftmac100_hard_start_xmit(struct sk_buff *skb,
+				    struct net_device *netdev)
+{
+	struct ftmac100 *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++;
+		dev_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++;
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	return ftmac100_xmit(priv, skb, map);
+}
+
+/* optional */
+static int ftmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr,
+			     int cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+
+	return generic_mii_ioctl(&priv->mii, data, cmd, NULL);
+}
+
+static const struct net_device_ops ftmac100_netdev_ops = {
+	.ndo_open		= ftmac100_open,
+	.ndo_stop		= ftmac100_stop,
+	.ndo_start_xmit		= ftmac100_hard_start_xmit,
+	.ndo_set_mac_address	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= ftmac100_do_ioctl,
+};
+
+/******************************************************************************
+ * struct platform_driver functions
+ *****************************************************************************/
+static int ftmac100_remove(struct platform_device *pdev)
+{
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+
+	netdev = platform_get_drvdata(pdev);
+	if (netdev == NULL)
+		return 0;
+
+	platform_set_drvdata(pdev, NULL);
+
+	priv = netdev_priv(netdev);
+
+	netif_napi_del(&priv->napi);
+	unregister_netdev(netdev);
+
+	if (priv->base != NULL)
+		iounmap(priv->base);
+
+	if (priv->res != NULL)
+		release_resource(priv->res);
+
+	free_netdev(netdev);
+	return 0;
+}
+
+static int ftmac100_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int irq;
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+	int err;
+
+	if (!pdev)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENXIO;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/* setup net_device */
+
+	netdev = alloc_etherdev(sizeof(struct ftmac100));
+	if (netdev == NULL) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	SET_NETDEV_DEV(netdev, &pdev->dev);
+	SET_ETHTOOL_OPS(netdev, &ftmac100_ethtool_ops);
+	netdev->netdev_ops = &ftmac100_netdev_ops;
+
+	platform_set_drvdata(pdev, netdev);
+
+	/* setup private data */
+
+	priv = netdev_priv(netdev);
+	priv->netdev = netdev;
+	priv->dev = &pdev->dev;
+
+	spin_lock_init(&priv->tx_lock);
+
+	/* initialize NAPI */
+	netif_napi_add(netdev, &priv->napi, ftmac100_poll, 64);
+
+	/* map io memory */
+	priv->res = request_mem_region(res->start, res->end - res->start,
+				       dev_name(&pdev->dev));
+	if (priv->res == NULL) {
+		dev_err(&pdev->dev, "Could not reserve memory region\n");
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	priv->base = ioremap(res->start, res->end - res->start);
+	if (priv->base == NULL) {
+		dev_err(&pdev->dev, "Failed to ioremap ethernet registers\n");
+		err = -EIO;
+		goto err_out;
+	}
+
+	priv->irq = irq;
+
+	/* initialize struct mii_if_info */
+
+	priv->mii.phy_id	= 0;
+	priv->mii.phy_id_mask	= 0x1f;
+	priv->mii.reg_num_mask	= 0x1f;
+	priv->mii.dev		= netdev;
+	priv->mii.mdio_read	= ftmac100_mdio_read;
+	priv->mii.mdio_write	= ftmac100_mdio_write;
+
+	/* register network device */
+
+	err = register_netdev(netdev);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to register netdev\n");
+		goto err_out;
+	}
+
+	netdev_info(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base);
+
+	if (!is_valid_ether_addr(netdev->dev_addr)) {
+		random_ether_addr(netdev->dev_addr);
+		netdev_info(netdev, "generated random MAC address %pM\n",
+			    netdev->dev_addr);
+	}
+
+	return 0;
+
+err_out:
+	ftmac100_remove(pdev);
+	return err;
+}
+
+static struct platform_driver ftmac100_driver = {
+	.probe		= ftmac100_probe,
+	.remove		= ftmac100_remove,
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+/******************************************************************************
+ * initialization / finalization
+ *****************************************************************************/
+static int __init ftmac100_init(void)
+{
+	pr_info("Loading version " DRV_VERSION " ...\n");
+	return platform_driver_register(&ftmac100_driver);
+}
+
+static void __exit ftmac100_exit(void)
+{
+	platform_driver_unregister(&ftmac100_driver);
+}
+
+module_init(ftmac100_init);
+module_exit(ftmac100_exit);
+
+MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");
+MODULE_DESCRIPTION("FTMAC100 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ftmac100.h b/drivers/net/ftmac100.h
new file mode 100644
index 0000000..46a0c47
--- /dev/null
+++ b/drivers/net/ftmac100.h
@@ -0,0 +1,180 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __FTMAC100_H
+#define __FTMAC100_H
+
+#define	FTMAC100_OFFSET_ISR		0x00
+#define	FTMAC100_OFFSET_IMR		0x04
+#define	FTMAC100_OFFSET_MAC_MADR	0x08
+#define	FTMAC100_OFFSET_MAC_LADR	0x0c
+#define	FTMAC100_OFFSET_MAHT0		0x10
+#define	FTMAC100_OFFSET_MAHT1		0x14
+#define	FTMAC100_OFFSET_TXPD		0x18
+#define	FTMAC100_OFFSET_RXPD		0x1c
+#define	FTMAC100_OFFSET_TXR_BADR	0x20
+#define	FTMAC100_OFFSET_RXR_BADR	0x24
+#define	FTMAC100_OFFSET_ITC		0x28
+#define	FTMAC100_OFFSET_APTC		0x2c
+#define	FTMAC100_OFFSET_DBLAC		0x30
+#define	FTMAC100_OFFSET_MACCR		0x88
+#define	FTMAC100_OFFSET_MACSR		0x8c
+#define	FTMAC100_OFFSET_PHYCR		0x90
+#define	FTMAC100_OFFSET_PHYWDATA	0x94
+#define	FTMAC100_OFFSET_FCR		0x98
+#define	FTMAC100_OFFSET_BPR		0x9c
+#define	FTMAC100_OFFSET_TS		0xc4
+#define	FTMAC100_OFFSET_DMAFIFOS	0xc8
+#define	FTMAC100_OFFSET_TM		0xcc
+#define	FTMAC100_OFFSET_TX_MCOL_SCOL	0xd4
+#define	FTMAC100_OFFSET_RPF_AEP		0xd8
+#define	FTMAC100_OFFSET_XM_PG		0xdc
+#define	FTMAC100_OFFSET_RUNT_TLCC	0xe0
+#define	FTMAC100_OFFSET_CRCER_FTL	0xe4
+#define	FTMAC100_OFFSET_RLC_RCC		0xe8
+#define	FTMAC100_OFFSET_BROC		0xec
+#define	FTMAC100_OFFSET_MULCA		0xf0
+#define	FTMAC100_OFFSET_RP		0xf4
+#define	FTMAC100_OFFSET_XP		0xf8
+
+/*
+ * Interrupt status register & interrupt mask register
+ */
+#define	FTMAC100_INT_RPKT_FINISH	(1 << 0)
+#define	FTMAC100_INT_NORXBUF		(1 << 1)
+#define	FTMAC100_INT_XPKT_FINISH	(1 << 2)
+#define	FTMAC100_INT_NOTXBUF		(1 << 3)
+#define	FTMAC100_INT_XPKT_OK		(1 << 4)
+#define	FTMAC100_INT_XPKT_LOST		(1 << 5)
+#define	FTMAC100_INT_RPKT_SAV		(1 << 6)
+#define	FTMAC100_INT_RPKT_LOST		(1 << 7)
+#define	FTMAC100_INT_AHB_ERR		(1 << 8)
+#define	FTMAC100_INT_PHYSTS_CHG		(1 << 9)
+
+/*
+ * Interrupt timer control register
+ */
+#define FTMAC100_ITC_RXINT_CNT(x)	(((x) & 0xf) << 0)
+#define FTMAC100_ITC_RXINT_THR(x)	(((x) & 0x7) << 4)
+#define FTMAC100_ITC_RXINT_TIME_SEL	(1 << 7)
+#define FTMAC100_ITC_TXINT_CNT(x)	(((x) & 0xf) << 8)
+#define FTMAC100_ITC_TXINT_THR(x)	(((x) & 0x7) << 12)
+#define FTMAC100_ITC_TXINT_TIME_SEL	(1 << 15)
+
+/*
+ * Automatic polling timer control register
+ */
+#define	FTMAC100_APTC_RXPOLL_CNT(x)	(((x) & 0xf) << 0)
+#define	FTMAC100_APTC_RXPOLL_TIME_SEL	(1 << 4)
+#define	FTMAC100_APTC_TXPOLL_CNT(x)	(((x) & 0xf) << 8)
+#define	FTMAC100_APTC_TXPOLL_TIME_SEL	(1 << 12)
+
+/*
+ * DMA burst length and arbitration control register
+ */
+#define FTMAC100_DBLAC_INCR4_EN		(1 << 0)
+#define FTMAC100_DBLAC_INCR8_EN		(1 << 1)
+#define FTMAC100_DBLAC_INCR16_EN	(1 << 2)
+#define FTMAC100_DBLAC_RXFIFO_LTHR(x)	(((x) & 0x7) << 3)
+#define FTMAC100_DBLAC_RXFIFO_HTHR(x)	(((x) & 0x7) << 6)
+#define FTMAC100_DBLAC_RX_THR_EN	(1 << 9)
+
+/*
+ * MAC control register
+ */
+#define	FTMAC100_MACCR_XDMA_EN		(1 << 0)
+#define	FTMAC100_MACCR_RDMA_EN		(1 << 1)
+#define	FTMAC100_MACCR_SW_RST		(1 << 2)
+#define	FTMAC100_MACCR_LOOP_EN		(1 << 3)
+#define	FTMAC100_MACCR_CRC_DIS		(1 << 4)
+#define	FTMAC100_MACCR_XMT_EN		(1 << 5)
+#define	FTMAC100_MACCR_ENRX_IN_HALFTX	(1 << 6)
+#define	FTMAC100_MACCR_RCV_EN		(1 << 8)
+#define	FTMAC100_MACCR_HT_MULTI_EN	(1 << 9)
+#define	FTMAC100_MACCR_RX_RUNT		(1 << 10)
+#define	FTMAC100_MACCR_RX_FTL		(1 << 11)
+#define	FTMAC100_MACCR_RCV_ALL		(1 << 12)
+#define	FTMAC100_MACCR_CRC_APD		(1 << 14)
+#define	FTMAC100_MACCR_FULLDUP		(1 << 15)
+#define	FTMAC100_MACCR_RX_MULTIPKT	(1 << 16)
+#define	FTMAC100_MACCR_RX_BROADPKT	(1 << 17)
+
+/*
+ * PHY control register
+ */
+#define FTMAC100_PHYCR_MIIRDATA		0xffff
+#define FTMAC100_PHYCR_PHYAD(x)		(((x) & 0x1f) << 16)
+#define FTMAC100_PHYCR_REGAD(x)		(((x) & 0x1f) << 21)
+#define FTMAC100_PHYCR_MIIRD		(1 << 26)
+#define FTMAC100_PHYCR_MIIWR		(1 << 27)
+
+/*
+ * PHY write data register
+ */
+#define FTMAC100_PHYWDATA_MIIWDATA(x)	((x) & 0xffff)
+
+/*
+ * Transmit descriptor, aligned to 16 bytes
+ */
+struct ftmac100_txdes {
+	unsigned int	txdes0;
+	unsigned int	txdes1;
+	unsigned int	txdes2;	/* TXBUF_BADR */
+	unsigned int	txdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_TXDES0_TXPKT_LATECOL	(1 << 0)
+#define	FTMAC100_TXDES0_TXPKT_EXSCOL	(1 << 1)
+#define	FTMAC100_TXDES0_TXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_TXDES1_TXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_TXDES1_LTS		(1 << 27)
+#define	FTMAC100_TXDES1_FTS		(1 << 28)
+#define	FTMAC100_TXDES1_TX2FIC		(1 << 29)
+#define	FTMAC100_TXDES1_TXIC		(1 << 30)
+#define	FTMAC100_TXDES1_EDOTR		(1 << 31)
+
+/*
+ * Receive descriptor, aligned to 16 bytes
+ */
+struct ftmac100_rxdes {
+	unsigned int	rxdes0;
+	unsigned int	rxdes1;
+	unsigned int	rxdes2;	/* RXBUF_BADR */
+	unsigned int	rxdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_RXDES0_RFL		0x7ff
+#define	FTMAC100_RXDES0_MULTICAST	(1 << 16)
+#define	FTMAC100_RXDES0_BROADCAST	(1 << 17)
+#define	FTMAC100_RXDES0_RX_ERR		(1 << 18)
+#define	FTMAC100_RXDES0_CRC_ERR		(1 << 19)
+#define	FTMAC100_RXDES0_FTL		(1 << 20)
+#define	FTMAC100_RXDES0_RUNT		(1 << 21)
+#define	FTMAC100_RXDES0_RX_ODD_NB	(1 << 22)
+#define	FTMAC100_RXDES0_LRS		(1 << 28)
+#define	FTMAC100_RXDES0_FRS		(1 << 29)
+#define	FTMAC100_RXDES0_RXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_RXDES1_RXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_RXDES1_EDORR		(1 << 31)
+
+#endif /* __FTMAC100_H */
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 15:30   ` [PATCH v3] " Po-Yu Chuang
@ 2011-01-20 15:35     ` Eric Dumazet
  2011-01-20 15:43       ` Po-Yu Chuang
  2011-01-20 15:41     ` Eric Dumazet
                       ` (4 subsequent siblings)
  5 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-01-20 15:35 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, Po-Yu Chuang

Le jeudi 20 janvier 2011 à 23:30 +0800, Po-Yu Chuang a écrit :

> +	/* push packet to protocol stack */
> +	netif_receive_skb(skb);
> +
> +	netdev->stats.rx_packets++;
> +	netdev->stats.rx_bytes += skb->len;
> +
> +	(*processed)++;
> +
> +	return true;
> +}
> +

Hmm, after call to netif_receive_skb(skb), you are not allowed to access
skb anymore (maybe it was freed)

netdev->stats.rx_packets++;
netdev->stats.rx_bytes += skb->len;
netif_receive_skb(skb);




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 15:30   ` [PATCH v3] " Po-Yu Chuang
  2011-01-20 15:35     ` Eric Dumazet
@ 2011-01-20 15:41     ` Eric Dumazet
  2011-01-20 15:54       ` Po-Yu Chuang
  2011-01-20 17:56     ` Joe Perches
                       ` (3 subsequent siblings)
  5 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-01-20 15:41 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, Po-Yu Chuang

Le jeudi 20 janvier 2011 à 23:30 +0800, Po-Yu Chuang a écrit :

> +static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)
> +{
> +	struct net_device *netdev = priv->netdev;
> +	struct ftmac100_txdes *txdes;
> +	struct sk_buff *skb;
> +	dma_addr_t map;
> +
> +	if (priv->tx_pending == 0)
> +		return false;
> +
> +	txdes = ftmac100_current_clean_txdes(priv);
> +
> +	if (ftmac100_txdes_owned_by_dma(txdes))
> +		return false;
> +
> +	skb = ftmac100_txdes_get_skb(txdes);
> +	map = ftmac100_txdes_get_dma_addr(txdes);
> +
> +	if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
> +		     ftmac100_txdes_late_collision(txdes))) {
> +		/*
> +		 * packet transmitted to ethernet lost due to late collision
> +		 * or excessive collision
> +		 */
> +		netdev->stats.tx_aborted_errors++;
> +	} else {
> +		netdev->stats.tx_packets++;
> +		netdev->stats.tx_bytes += skb->len;
> +	}
> +
> +	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
> +
> +	dev_kfree_skb_irq(skb);
> +
> +	ftmac100_txdes_reset(txdes);
> +
> +	ftmac100_tx_clean_pointer_advance(priv);
> +
> +	priv->tx_pending--;
> +	netif_wake_queue(netdev);
> +
> +	return true;
> +}
> +
> +static void ftmac100_tx_complete(struct ftmac100 *priv)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&priv->tx_lock, flags);
> +	while (ftmac100_tx_complete_packet(priv))
> +		;
> +	spin_unlock_irqrestore(&priv->tx_lock, flags);
> +}
> +

I dont understand why you still block hard IRQS, after full NAPI
conversion.

Now you run from NAPI, and softirq handler, are you sure you still need
to block hard IRQ and tx_lock ?

It seems to me ftmac100_xmit() could only block softirqs (but they
already are blocked by caller), so you could use spin_lock() from
ftmac100_xmit()




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 15:35     ` Eric Dumazet
@ 2011-01-20 15:43       ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-20 15:43 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, Po-Yu Chuang

Dear Eric,

On Thu, Jan 20, 2011 at 11:35 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le jeudi 20 janvier 2011 à 23:30 +0800, Po-Yu Chuang a écrit :
>
>> +     /* push packet to protocol stack */
>> +     netif_receive_skb(skb);
>> +
>> +     netdev->stats.rx_packets++;
>> +     netdev->stats.rx_bytes += skb->len;
>> +
>> +     (*processed)++;
>> +
>> +     return true;
>> +}
>> +
>
> Hmm, after call to netif_receive_skb(skb), you are not allowed to access
> skb anymore (maybe it was freed)
>
> netdev->stats.rx_packets++;
> netdev->stats.rx_bytes += skb->len;
> netif_receive_skb(skb);

Wow, you are totally right.

Thanks,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 15:41     ` Eric Dumazet
@ 2011-01-20 15:54       ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-20 15:54 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, Po-Yu Chuang

Dear Eric,

On Thu, Jan 20, 2011 at 11:41 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le jeudi 20 janvier 2011 à 23:30 +0800, Po-Yu Chuang a écrit :
>
>> +static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)
>> +{
>> +     struct net_device *netdev = priv->netdev;
>> +     struct ftmac100_txdes *txdes;
>> +     struct sk_buff *skb;
>> +     dma_addr_t map;
>> +
>> +     if (priv->tx_pending == 0)
>> +             return false;
>> +
>> +     txdes = ftmac100_current_clean_txdes(priv);
>> +
>> +     if (ftmac100_txdes_owned_by_dma(txdes))
>> +             return false;
>> +
>> +     skb = ftmac100_txdes_get_skb(txdes);
>> +     map = ftmac100_txdes_get_dma_addr(txdes);
>> +
>> +     if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
>> +                  ftmac100_txdes_late_collision(txdes))) {
>> +             /*
>> +              * packet transmitted to ethernet lost due to late collision
>> +              * or excessive collision
>> +              */
>> +             netdev->stats.tx_aborted_errors++;
>> +     } else {
>> +             netdev->stats.tx_packets++;
>> +             netdev->stats.tx_bytes += skb->len;
>> +     }
>> +
>> +     dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
>> +
>> +     dev_kfree_skb_irq(skb);
>> +
>> +     ftmac100_txdes_reset(txdes);
>> +
>> +     ftmac100_tx_clean_pointer_advance(priv);
>> +
>> +     priv->tx_pending--;
>> +     netif_wake_queue(netdev);
>> +
>> +     return true;
>> +}
>> +
>> +static void ftmac100_tx_complete(struct ftmac100 *priv)
>> +{
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&priv->tx_lock, flags);
>> +     while (ftmac100_tx_complete_packet(priv))
>> +             ;
>> +     spin_unlock_irqrestore(&priv->tx_lock, flags);
>> +}
>> +
>
> I dont understand why you still block hard IRQS, after full NAPI
> conversion.
>
> Now you run from NAPI, and softirq handler, are you sure you still need
> to block hard IRQ and tx_lock ?
>
> It seems to me ftmac100_xmit() could only block softirqs (but they
> already are blocked by caller), so you could use spin_lock() from
> ftmac100_xmit()

I was not quite clear about when to use what kinds of locking.
After your explanation, now I understand.
I will submit v4 tomorrow.

really appreciate,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 15:30   ` [PATCH v3] " Po-Yu Chuang
  2011-01-20 15:35     ` Eric Dumazet
  2011-01-20 15:41     ` Eric Dumazet
@ 2011-01-20 17:56     ` Joe Perches
  2011-01-21  3:35       ` Po-Yu Chuang
  2011-01-20 19:00     ` Joe Perches
                       ` (2 subsequent siblings)
  5 siblings, 1 reply; 74+ messages in thread
From: Joe Perches @ 2011-01-20 17:56 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, dilinger, Po-Yu Chuang

On Thu, 2011-01-20 at 23:30 +0800, Po-Yu Chuang wrote:
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
[]
> +	/* map io memory */
> +	priv->res = request_mem_region(res->start, res->end - res->start,
> +				       dev_name(&pdev->dev));

Off by one?

	priv->res = request_mem_region(res->start, resource_size(res),
				       dev_name(&pdev->dev));




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 15:30   ` [PATCH v3] " Po-Yu Chuang
                       ` (2 preceding siblings ...)
  2011-01-20 17:56     ` Joe Perches
@ 2011-01-20 19:00     ` Joe Perches
  2011-01-21  5:03       ` Po-Yu Chuang
  2011-01-20 19:01     ` Michał Mirosław
  2011-01-21  7:55     ` [PATCH v4] " Po-Yu Chuang
  5 siblings, 1 reply; 74+ messages in thread
From: Joe Perches @ 2011-01-20 19:00 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, dilinger, Po-Yu Chuang

On Thu, 2011-01-20 at 23:30 +0800, Po-Yu Chuang wrote:
>  drivers/net/ftmac100.c | 1243 ++++++++++++++++++++++++++++++++++++++++++++++++

[]

> +/******************************************************************************
> + * struct napi_struct functions
> + *****************************************************************************/
> +static int ftmac100_poll(struct napi_struct *napi, int budget)
> +{
> +	struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
> +	struct net_device *netdev = priv->netdev;
> +	unsigned int status;
> +	bool completed = true;
> +	int rx = 0;
> +
> +	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
> +
> +	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
> +		/*
> +		 * FTMAC100_INT_RPKT_FINISH:
> +		 *	RX DMA has received packets into RX buffer successfully
> +		 *
> +		 * FTMAC100_INT_NORXBUF:
> +		 *	RX buffer unavailable
> +		 */
> +		bool retry;
> +
> +		do {
> +			retry = ftmac100_rx_packet(priv, &rx);
> +		} while (retry && rx < budget);
> +
> +		if (retry && rx == budget)
> +			completed = false;

Is it useful to retry the NORXBUF case?

> +	}
> +
> +	if (status & FTMAC100_INT_NORXBUF) {
> +		/* RX buffer unavailable */
> +		if (net_ratelimit())
> +			netdev_info(netdev, "INT_NORXBUF\n");
> +
> +		netdev->stats.rx_over_errors++;
> +	}

Perhaps this "if (status & FTMAC100_INT_NORXBUF)" block should be
moved into the test block above it before the retry?

> +
> +	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
> +		/*
> +		 * FTMAC100_INT_XPKT_OK:
> +		 *	 packet transmitted to ethernet successfully
> +		 *
> +		 * FTMAC100_INT_XPKT_LOST:
> +		 *	packet transmitted to ethernet lost due to late
> +		 *	collision or excessive collision
> +		 */
> +		ftmac100_tx_complete(priv);
> +	}
> +
> +	if (status & FTMAC100_INT_RPKT_LOST) {
> +		/* received packet lost due to RX FIFO full */
> +		if (net_ratelimit())
> +			netdev_info(netdev, "INT_RPKT_LOST\n");
> +
> +		netdev->stats.rx_fifo_errors++;
> +	}
> +
> +	if (status & FTMAC100_INT_AHB_ERR) {
> +		/* AHB error */
> +		if (net_ratelimit())
> +			netdev_info(netdev, "INT_AHB_ERR\n");
> +
> +		/* do nothing */
> +	}
> +
> +	if (status & FTMAC100_INT_PHYSTS_CHG) {
> +		/* PHY link status change */
> +		if (net_ratelimit())
> +			netdev_info(netdev, "INT_PHYSTS_CHG\n");
> +
> +		mii_check_link(&priv->mii);
> +	}
> +
> +	if (completed) {
> +		/* stop polling */
> +		napi_complete(napi);
> +		ftmac100_enable_all_int(priv);
> +	}
> +
> +	return rx;
> +}

It's possible to miss multiple states because of the ratelimit.

If multiple ISR status bits are possible, it might be better to
combine all netdev_info uses into a single call.

Something like:

	if ((status & (FTMAC100_INT_NORXBUF | FTMAC100_INT_RPKT_LOST |
		       FTMAC100_INT_AHB_ERR | FTMAC100_INT_PHYSTS_CHG)) &&
	    net_ratelimit())
		netdev_info(netdev, "ISR status: %x%s%s%s%s\n",
			    status & FTMAC100_INT_NORXBUF ? ": INT_NORXBUF" : "",
			    status & FTMAC100_INT_RPKT_LOST ? ": INT_RPKT_LOST" : "",
			    status & FTMAC100_INT_AHB_ERR ? ": INT_AHB_ERR" : "",
			    status & FTMAC100_INT_PHYSTS_CHG ? " : INT_PHYSTS_CHG" : "");



^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 15:30   ` [PATCH v3] " Po-Yu Chuang
                       ` (3 preceding siblings ...)
  2011-01-20 19:00     ` Joe Perches
@ 2011-01-20 19:01     ` Michał Mirosław
  2011-01-21  3:37       ` Po-Yu Chuang
  2011-01-21  7:55     ` [PATCH v4] " Po-Yu Chuang
  5 siblings, 1 reply; 74+ messages in thread
From: Michał Mirosław @ 2011-01-20 19:01 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, joe, dilinger,
	Po-Yu Chuang

2011/1/20 Po-Yu Chuang <ratbert.chuang@gmail.com>:
[...]
> +/******************************************************************************
> + * internal functions (receive descriptor)
> + *****************************************************************************/
> +static bool ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
> +{
> +       return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_FRS;
> +}
> +
> +static bool ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
> +{
> +       return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_LRS;
> +}
> +
[...]

You can change these and similar functions to use:

rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_LRS)

This will be subject to constant folding by compiler and produce
better code for big-endian arches.

Best Regards,
Michał Mirosław

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 17:56     ` Joe Perches
@ 2011-01-21  3:35       ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-21  3:35 UTC (permalink / raw)
  To: Joe Perches
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, dilinger, Po-Yu Chuang

Dear Joe,

On Fri, Jan 21, 2011 at 1:56 AM, Joe Perches <joe@perches.com> wrote:
> On Thu, 2011-01-20 at 23:30 +0800, Po-Yu Chuang wrote:
>> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> []
>> +     /* map io memory */
>> +     priv->res = request_mem_region(res->start, res->end - res->start,
>> +                                    dev_name(&pdev->dev));
>
> Off by one?
>
>        priv->res = request_mem_region(res->start, resource_size(res),
>                                       dev_name(&pdev->dev));

Fixed.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 19:01     ` Michał Mirosław
@ 2011-01-21  3:37       ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-21  3:37 UTC (permalink / raw)
  To: Michał Mirosław
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, joe, dilinger,
	Po-Yu Chuang

Dear Michał ,

2011/1/21 Michał Mirosław <mirqus@gmail.com>:
> 2011/1/20 Po-Yu Chuang <ratbert.chuang@gmail.com>:
> [...]
>> +/******************************************************************************
>> + * internal functions (receive descriptor)
>> + *****************************************************************************/
>> +static bool ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
>> +{
>> +       return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_FRS;
>> +}
>> +
>> +static bool ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
>> +{
>> +       return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_LRS;
>> +}
>> +
> [...]
>
> You can change these and similar functions to use:
>
> rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_LRS)
>
> This will be subject to constant folding by compiler and produce
> better code for big-endian arches.

Fixed. It looks much better now.
Thanks a lot.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 19:00     ` Joe Perches
@ 2011-01-21  5:03       ` Po-Yu Chuang
       [not found]         ` <1295592411.6795.10.camel@Joe-Laptop>
  0 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-21  5:03 UTC (permalink / raw)
  To: Joe Perches
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, dilinger, Po-Yu Chuang

Dear Joe,

On Fri, Jan 21, 2011 at 3:00 AM, Joe Perches <joe@perches.com> wrote:
> On Thu, 2011-01-20 at 23:30 +0800, Po-Yu Chuang wrote:
>>  drivers/net/ftmac100.c | 1243 ++++++++++++++++++++++++++++++++++++++++++++++++
>
> []
>
>> +/******************************************************************************
>> + * struct napi_struct functions
>> + *****************************************************************************/
>> +static int ftmac100_poll(struct napi_struct *napi, int budget)
>> +{
>> +     struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
>> +     struct net_device *netdev = priv->netdev;
>> +     unsigned int status;
>> +     bool completed = true;
>> +     int rx = 0;
>> +
>> +     status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
>> +
>> +     if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
>> +             /*
>> +              * FTMAC100_INT_RPKT_FINISH:
>> +              *      RX DMA has received packets into RX buffer successfully
>> +              *
>> +              * FTMAC100_INT_NORXBUF:
>> +              *      RX buffer unavailable
>> +              */
>> +             bool retry;
>> +
>> +             do {
>> +                     retry = ftmac100_rx_packet(priv, &rx);
>> +             } while (retry && rx < budget);
>> +
>> +             if (retry && rx == budget)
>> +                     completed = false;
>
> Is it useful to retry the NORXBUF case?

The idea is that if I miss packet finished interrupts (then rx buffers used up),
I should retrieve the received packets ASAP to free buffers to HW.
Maybe this is really unnecessary.
I am not quite sure, but I'll do your advice now.

>> +     }
>> +
>> +     if (status & FTMAC100_INT_NORXBUF) {
>> +             /* RX buffer unavailable */
>> +             if (net_ratelimit())
>> +                     netdev_info(netdev, "INT_NORXBUF\n");
>> +
>> +             netdev->stats.rx_over_errors++;
>> +     }
>
> Perhaps this "if (status & FTMAC100_INT_NORXBUF)" block should be
> moved into the test block above it before the retry?

Since status is not changed in the function, it does not matter where
the test is.
But I agree that it is better to handle error cases earlier.

> It's possible to miss multiple states because of the ratelimit.
>
> If multiple ISR status bits are possible, it might be better to
> combine all netdev_info uses into a single call.
>
> Something like:
>
>        if ((status & (FTMAC100_INT_NORXBUF | FTMAC100_INT_RPKT_LOST |
>                       FTMAC100_INT_AHB_ERR | FTMAC100_INT_PHYSTS_CHG)) &&
>            net_ratelimit())
>                netdev_info(netdev, "ISR status: %x%s%s%s%s\n",
>                            status & FTMAC100_INT_NORXBUF ? ": INT_NORXBUF" : "",
>                            status & FTMAC100_INT_RPKT_LOST ? ": INT_RPKT_LOST" : "",
>                            status & FTMAC100_INT_AHB_ERR ? ": INT_AHB_ERR" : "",
>                            status & FTMAC100_INT_PHYSTS_CHG ? " : INT_PHYSTS_CHG" : "");

Agree. Thanks.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v3] net: add Faraday FTMAC100 10/100 Ethernet driver
       [not found]         ` <1295592411.6795.10.camel@Joe-Laptop>
@ 2011-01-21  7:06           ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-21  7:06 UTC (permalink / raw)
  To: Joe Perches; +Cc: netdev, linux-kernel, bhutchings, eric.dumazet, dilinger

Dear Joe,

On Fri, Jan 21, 2011 at 2:46 PM, Joe Perches <joe@perches.com> wrote:
> On Fri, 2011-01-21 at 13:03 +0800, Po-Yu Chuang wrote:
>> > Is it useful to retry the NORXBUF case?
>> The idea is that if I miss packet finished interrupts (then rx buffers used up),
>> I should retrieve the received packets ASAP to free buffers to HW.
>> Maybe this is really unnecessary.
>> I am not quite sure, but I'll do your advice now.
>
> I wasn't giving advice, just asking a question.
> Your concept makes sense to me.

I see. So I will leave it as is.

>> >> +     if (status & FTMAC100_INT_NORXBUF) {
>> >> +             /* RX buffer unavailable */
>> >> +             if (net_ratelimit())
>> >> +                     netdev_info(netdev, "INT_NORXBUF\n");
>> >> +
>> >> +             netdev->stats.rx_over_errors++;
>> >> +     }
>> >
>> > Perhaps this "if (status & FTMAC100_INT_NORXBUF)" block should be
>> > moved into the test block above it before the retry?
>>
>> Since status is not changed in the function, it does not matter where
>> the test is.
>> But I agree that it is better to handle error cases earlier.
>
> This wasn't so much a handle error case early, but
> a suggestion that
>        if (status & (foo | bar)) {
>                ...
>        }
>        if (status & bar) {
>                ...
>        }
> should be
>        if (status & (foo | bar)) {
>                ...
>                if (status & bar) {
>                        ...
>                }
>        }
>
> so that when the first test fails, a known
> subset of the first test isn't tested again.

Understand. Thanks.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-20 15:30   ` [PATCH v3] " Po-Yu Chuang
                       ` (4 preceding siblings ...)
  2011-01-20 19:01     ` Michał Mirosław
@ 2011-01-21  7:55     ` Po-Yu Chuang
  2011-01-21  9:08       ` Eric Dumazet
                         ` (2 more replies)
  5 siblings, 3 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-21  7:55 UTC (permalink / raw)
  To: netdev
  Cc: linux-kernel, bhutchings, eric.dumazet, joe, dilinger, mirqus,
	Po-Yu Chuang

From: Po-Yu Chuang <ratbert@faraday-tech.com>

FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII.  This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.

Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts

v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix

v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()

 drivers/net/Kconfig    |    9 +
 drivers/net/Makefile   |    1 +
 drivers/net/ftmac100.c | 1207 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/ftmac100.h |  180 +++++++
 4 files changed, 1397 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/ftmac100.c
 create mode 100644 drivers/net/ftmac100.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 4f1755b..26da0ee 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2014,6 +2014,15 @@ config BCM63XX_ENET
 	  This driver supports the ethernet MACs in the Broadcom 63xx
 	  MIPS chipset family (BCM63XX).
 
+config FTMAC100
+	tristate "Faraday FTMAC100 10/100 Ethernet support"
+	depends on ARM
+	select MII
+	help
+	  This driver supports the FTMAC100 Ethernet controller from
+	  Faraday. It is used on Faraday A320, Andes AG101, AG101P
+	  and some other ARM/NDS32 SoC's.
+
 source "drivers/net/fs_enet/Kconfig"
 
 source "drivers/net/octeon/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..7c21711 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_FORCEDETH) += forcedeth.o
 obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
 obj-$(CONFIG_AX88796) += ax88796.o
 obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
+obj-$(CONFIG_FTMAC100) += ftmac100.o
 
 obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
new file mode 100644
index 0000000..58b2d5f
--- /dev/null
+++ b/drivers/net/ftmac100.c
@@ -0,0 +1,1207 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#include "ftmac100.h"
+
+#define DRV_NAME	"ftmac100"
+#define DRV_VERSION	"0.2"
+
+#define RX_QUEUE_ENTRIES	128	/* must be power of 2 */
+#define TX_QUEUE_ENTRIES	16	/* must be power of 2 */
+
+#define MAX_PKT_SIZE		1518
+#define RX_BUF_SIZE		2044	/* must be smaller than 0x7ff */
+
+/******************************************************************************
+ * private data
+ *****************************************************************************/
+struct ftmac100_descs {
+	struct ftmac100_rxdes	rxdes[RX_QUEUE_ENTRIES];
+	struct ftmac100_txdes	txdes[TX_QUEUE_ENTRIES];
+};
+
+struct ftmac100 {
+	struct resource		*res;
+	void			*base;
+	int			irq;
+
+	struct ftmac100_descs	*descs;
+	dma_addr_t		descs_dma_addr;
+
+	unsigned int		rx_pointer;
+	unsigned int		tx_clean_pointer;
+	unsigned int		tx_pointer;
+	unsigned int		tx_pending;
+
+	spinlock_t		tx_lock;
+
+	struct net_device	*netdev;
+	struct device		*dev;
+	struct napi_struct	napi;
+
+	struct mii_if_info	mii;
+};
+
+/******************************************************************************
+ * internal functions (hardware register access)
+ *****************************************************************************/
+#define INT_MASK_ALL_ENABLED	(FTMAC100_INT_RPKT_FINISH	| \
+				 FTMAC100_INT_NORXBUF		| \
+				 FTMAC100_INT_XPKT_OK		| \
+				 FTMAC100_INT_XPKT_LOST		| \
+				 FTMAC100_INT_RPKT_LOST		| \
+				 FTMAC100_INT_AHB_ERR		| \
+				 FTMAC100_INT_PHYSTS_CHG)
+
+static void ftmac100_enable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_disable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_set_rx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
+}
+
+static void ftmac100_set_tx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_TXR_BADR);
+}
+
+static void ftmac100_txdma_start_polling(struct ftmac100 *priv)
+{
+	iowrite32(1, priv->base + FTMAC100_OFFSET_TXPD);
+}
+
+static int ftmac100_reset(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	int i;
+
+	/* NOTE: reset clears all registers */
+	iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
+
+	for (i = 0; i < 5; i++) {
+		unsigned int maccr;
+
+		maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
+		if (!(maccr & FTMAC100_MACCR_SW_RST)) {
+			/*
+			 * FTMAC100_MACCR_SW_RST cleared does not indicate
+			 * that hardware reset completed (what the f*ck).
+			 * We still need to wait for a while.
+			 */
+			usleep_range(500, 1000);
+			return 0;
+		}
+
+		usleep_range(1000, 10000);
+	}
+
+	netdev_err(netdev, "software reset failed\n");
+	return -EIO;
+}
+
+static void ftmac100_set_mac(struct ftmac100 *priv, const unsigned char *mac)
+{
+	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 + FTMAC100_OFFSET_MAC_MADR);
+	iowrite32(laddr, priv->base + FTMAC100_OFFSET_MAC_LADR);
+}
+
+#define MACCR_ENABLE_ALL	(FTMAC100_MACCR_XMT_EN	| \
+				 FTMAC100_MACCR_RCV_EN	| \
+				 FTMAC100_MACCR_XDMA_EN	| \
+				 FTMAC100_MACCR_RDMA_EN	| \
+				 FTMAC100_MACCR_CRC_APD	| \
+				 FTMAC100_MACCR_FULLDUP	| \
+				 FTMAC100_MACCR_RX_RUNT	| \
+				 FTMAC100_MACCR_RX_BROADPKT)
+
+static int ftmac100_start_hw(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+
+	if (ftmac100_reset(priv))
+		return -EIO;
+
+	/* setup ring buffer base registers */
+
+	ftmac100_set_rx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, rxdes));
+	ftmac100_set_tx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, txdes));
+
+	iowrite32(FTMAC100_APTC_RXPOLL_CNT(1), priv->base + FTMAC100_OFFSET_APTC);
+
+	ftmac100_set_mac(priv, netdev->dev_addr);
+
+	iowrite32(MACCR_ENABLE_ALL, priv->base + FTMAC100_OFFSET_MACCR);
+	return 0;
+}
+
+static void ftmac100_stop_hw(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_MACCR);
+}
+
+/******************************************************************************
+ * internal functions (receive descriptor)
+ *****************************************************************************/
+static bool ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FRS);
+}
+
+static bool ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_LRS);
+}
+
+static bool ftmac100_rxdes_owned_by_dma(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static void ftmac100_rxdes_set_dma_own(struct ftmac100_rxdes *rxdes)
+{
+	/* clear status bits */
+	rxdes->rxdes0 = cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static bool ftmac100_rxdes_rx_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ERR);
+}
+
+static bool ftmac100_rxdes_crc_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_CRC_ERR);
+}
+
+static bool ftmac100_rxdes_frame_too_long(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FTL);
+}
+
+static bool ftmac100_rxdes_runt(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RUNT);
+}
+
+static bool ftmac100_rxdes_odd_nibble(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ODD_NB);
+}
+
+static unsigned int ftmac100_rxdes_frame_length(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RFL);
+}
+
+static bool ftmac100_rxdes_multicast(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_MULTICAST);
+}
+
+static void ftmac100_rxdes_set_buffer_size(struct ftmac100_rxdes *rxdes,
+					   unsigned int size)
+{
+	rxdes->rxdes1 &= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_RXBUF_SIZE(size));
+}
+
+static void ftmac100_rxdes_set_end_of_ring(struct ftmac100_rxdes *rxdes)
+{
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+}
+
+static void ftmac100_rxdes_set_dma_addr(struct ftmac100_rxdes *rxdes,
+					dma_addr_t addr)
+{
+	rxdes->rxdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_rxdes_get_dma_addr(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes2);
+}
+
+/* rxdes3 is not used by hardware, we use it to keep track of buffer */
+static void ftmac100_rxdes_set_va(struct ftmac100_rxdes *rxdes, void *addr)
+{
+	rxdes->rxdes3 = cpu_to_le32(addr);
+}
+
+static void *ftmac100_rxdes_get_va(struct ftmac100_rxdes *rxdes)
+{
+	return (void *)le32_to_cpu(rxdes->rxdes3);
+}
+
+/******************************************************************************
+ * internal functions (receive)
+ *****************************************************************************/
+static int ftmac100_next_rx_pointer(int pointer)
+{
+	return (pointer + 1) & (RX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_rx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->rx_pointer = ftmac100_next_rx_pointer(priv->rx_pointer);
+}
+
+static struct ftmac100_rxdes *ftmac100_current_rxdes(struct ftmac100 *priv)
+{
+	return &priv->descs->rxdes[priv->rx_pointer];
+}
+
+static struct ftmac100_rxdes *
+ftmac100_rx_locate_first_segment(struct ftmac100 *priv)
+{
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+
+	while (!ftmac100_rxdes_owned_by_dma(rxdes)) {
+		if (ftmac100_rxdes_first_segment(rxdes))
+			return rxdes;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	}
+
+	return NULL;
+}
+
+static bool ftmac100_rx_packet_error(struct ftmac100 *priv,
+				     struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	bool error = false;
+
+	if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx err\n");
+
+		netdev->stats.rx_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_crc_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx crc err\n");
+
+		netdev->stats.rx_crc_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx frame too long\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_runt(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx runt\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_odd_nibble(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx odd nibble\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	}
+
+	return error;
+}
+
+static void ftmac100_rx_drop_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+	bool done = false;
+
+	if (net_ratelimit())
+		netdev_dbg(netdev, "drop packet %p\n", rxdes);
+
+	do {
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = true;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && !ftmac100_rxdes_owned_by_dma(rxdes));
+
+	netdev->stats.rx_dropped++;
+}
+
+static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes;
+	struct sk_buff *skb;
+	int length;
+	bool copied = false;
+	bool done = false;
+
+	rxdes = ftmac100_rx_locate_first_segment(priv);
+	if (!rxdes)
+		return false;
+
+	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	/* start processing */
+
+	length = ftmac100_rxdes_frame_length(rxdes);
+
+	skb = netdev_alloc_skb_ip_align(netdev, length);
+	if (unlikely(!skb)) {
+		if (net_ratelimit())
+			netdev_err(netdev, "rx skb alloc failed\n");
+
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	if (unlikely(ftmac100_rxdes_multicast(rxdes)))
+		netdev->stats.multicast++;
+
+	do {
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *buf = ftmac100_rxdes_get_va(rxdes);
+		int size;
+
+		size = min(length - copied, RX_BUF_SIZE);
+
+		dma_sync_single_for_cpu(priv->dev, d, RX_BUF_SIZE,
+					DMA_FROM_DEVICE);
+		memcpy(skb_put(skb, size), buf, size);
+
+		copied += size;
+
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = true;
+
+		dma_sync_single_for_device(priv->dev, d, RX_BUF_SIZE,
+					   DMA_FROM_DEVICE);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && copied < length);
+
+	skb->protocol = eth_type_trans(skb, netdev);
+
+	netdev->stats.rx_packets++;
+	netdev->stats.rx_bytes += skb->len;
+
+	/* push packet to protocol stack */
+	netif_receive_skb(skb);
+
+	(*processed)++;
+	return true;
+}
+
+/******************************************************************************
+ * internal functions (transmit descriptor)
+ *****************************************************************************/
+static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
+{
+	/* clear all except end of ring bit */
+	txdes->txdes0 = 0;
+	txdes->txdes1 &= FTMAC100_TXDES1_EDOTR;
+	txdes->txdes2 = 0;
+	txdes->txdes3 = 0;
+}
+
+static bool ftmac100_txdes_owned_by_dma(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
+{
+	/*
+	 * Make sure dma own bit will not be set before any other
+	 * descriptor fields.
+	 */
+	wmb();
+	txdes->txdes0 |= cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static bool ftmac100_txdes_excessive_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_EXSCOL);
+}
+
+static bool ftmac100_txdes_late_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_LATECOL);
+}
+
+static void ftmac100_txdes_set_end_of_ring(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_EDOTR);
+}
+
+static void ftmac100_txdes_set_first_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_FTS);
+}
+
+static void ftmac100_txdes_set_last_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_LTS);
+}
+
+static void ftmac100_txdes_set_txint(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXIC);
+}
+
+static void ftmac100_txdes_set_buffer_size(struct ftmac100_txdes *txdes,
+					   unsigned int len)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXBUF_SIZE(len));
+}
+
+static void ftmac100_txdes_set_dma_addr(struct ftmac100_txdes *txdes,
+					dma_addr_t addr)
+{
+	txdes->txdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_txdes_get_dma_addr(struct ftmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes2);
+}
+
+/* txdes3 is not used by hardware, we use it to keep track of socket buffer */
+static void ftmac100_txdes_set_skb(struct ftmac100_txdes *txdes,
+				   struct sk_buff *skb)
+{
+	txdes->txdes3 = cpu_to_le32(skb);
+}
+
+static struct sk_buff *ftmac100_txdes_get_skb(struct ftmac100_txdes *txdes)
+{
+	return (struct sk_buff *)le32_to_cpu(txdes->txdes3);
+}
+
+/******************************************************************************
+ * internal functions (transmit)
+ *****************************************************************************/
+static int ftmac100_next_tx_pointer(int pointer)
+{
+	return (pointer + 1) & (TX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_tx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_pointer = ftmac100_next_tx_pointer(priv->tx_pointer);
+}
+
+static void ftmac100_tx_clean_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_clean_pointer = ftmac100_next_tx_pointer(priv->tx_clean_pointer);
+}
+
+static struct ftmac100_txdes *ftmac100_current_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_pointer];
+}
+
+static struct ftmac100_txdes *
+ftmac100_current_clean_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_clean_pointer];
+}
+
+static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	struct sk_buff *skb;
+	dma_addr_t map;
+
+	if (priv->tx_pending == 0)
+		return false;
+
+	txdes = ftmac100_current_clean_txdes(priv);
+
+	if (ftmac100_txdes_owned_by_dma(txdes))
+		return false;
+
+	skb = ftmac100_txdes_get_skb(txdes);
+	map = ftmac100_txdes_get_dma_addr(txdes);
+
+	if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
+		     ftmac100_txdes_late_collision(txdes))) {
+		/*
+		 * packet transmitted to ethernet lost due to late collision
+		 * or excessive collision
+		 */
+		netdev->stats.tx_aborted_errors++;
+	} else {
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += skb->len;
+	}
+
+	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+
+	dev_kfree_skb_irq(skb);
+
+	ftmac100_txdes_reset(txdes);
+
+	ftmac100_tx_clean_pointer_advance(priv);
+
+	priv->tx_pending--;
+	netif_wake_queue(netdev);
+
+	return true;
+}
+
+static void ftmac100_tx_complete(struct ftmac100 *priv)
+{
+	spin_lock(&priv->tx_lock);
+	while (ftmac100_tx_complete_packet(priv))
+		;
+	spin_unlock(&priv->tx_lock);
+}
+
+static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
+			 dma_addr_t map)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
+
+	txdes = ftmac100_current_txdes(priv);
+	ftmac100_tx_pointer_advance(priv);
+
+	/* setup TX descriptor */
+
+	spin_lock(&priv->tx_lock);
+	ftmac100_txdes_set_skb(txdes, skb);
+	ftmac100_txdes_set_dma_addr(txdes, map);
+
+	ftmac100_txdes_set_first_segment(txdes);
+	ftmac100_txdes_set_last_segment(txdes);
+	ftmac100_txdes_set_txint(txdes);
+	ftmac100_txdes_set_buffer_size(txdes, len);
+
+	priv->tx_pending++;
+	if (priv->tx_pending == TX_QUEUE_ENTRIES) {
+		if (net_ratelimit())
+			netdev_info(netdev, "tx queue full\n");
+
+		netif_stop_queue(netdev);
+	}
+
+	/* start transmit */
+	ftmac100_txdes_set_dma_own(txdes);
+	spin_unlock(&priv->tx_lock);
+
+	ftmac100_txdma_start_polling(priv);
+
+	return NETDEV_TX_OK;
+}
+
+/******************************************************************************
+ * internal functions (buffer)
+ *****************************************************************************/
+static void ftmac100_free_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *page = ftmac100_rxdes_get_va(rxdes);
+
+		if (d)
+			dma_unmap_single(priv->dev, d, PAGE_SIZE,
+					 DMA_FROM_DEVICE);
+
+		if (page != NULL)
+			free_page((unsigned long)page);
+	}
+
+	for (i = 0; i < TX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_txdes *txdes = &priv->descs->txdes[i];
+		struct sk_buff *skb = ftmac100_txdes_get_skb(txdes);
+
+		if (skb) {
+			dma_addr_t map;
+
+			map = ftmac100_txdes_get_dma_addr(txdes);
+			dma_unmap_single(priv->dev, map, skb_headlen(skb),
+					 DMA_TO_DEVICE);
+			dev_kfree_skb(skb);
+		}
+	}
+
+	dma_free_coherent(priv->dev, sizeof(struct ftmac100_descs),
+			  priv->descs, priv->descs_dma_addr);
+}
+
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	priv->descs = dma_alloc_coherent(priv->dev,
+					 sizeof(struct ftmac100_descs),
+					 &priv->descs_dma_addr,
+					 GFP_KERNEL | GFP_DMA);
+	if (priv->descs == NULL)
+		return -ENOMEM;
+
+	memset(priv->descs, 0, sizeof(struct ftmac100_descs));
+
+	/* initialize RX ring */
+
+	ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		void *page;
+		dma_addr_t d;
+
+		page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
+		if (page == NULL)
+			goto err;
+
+		d = dma_map_single(priv->dev, page, PAGE_SIZE, DMA_FROM_DEVICE);
+		if (unlikely(dma_mapping_error(priv->dev, d))) {
+			free_page((unsigned long)page);
+			goto err;
+		}
+
+		/*
+		 * The hardware enforces a sub-2K maximum packet size, so we
+		 * put two buffers on every hardware page.
+		 */
+		ftmac100_rxdes_set_va(rxdes, page);
+		ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_dma_addr(rxdes, d);
+		ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
+		ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rxdes_set_dma_own(rxdes + 1);
+	}
+
+	/* initialize TX ring */
+
+	ftmac100_txdes_set_end_of_ring(&priv->descs->txdes[TX_QUEUE_ENTRIES - 1]);
+	return 0;
+
+err:
+	ftmac100_free_buffers(priv);
+	return -ENOMEM;
+}
+
+/******************************************************************************
+ * struct mii_if_info functions
+ *****************************************************************************/
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIRD;
+
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
+			return phycr & FTMAC100_PHYCR_MIIRDATA;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio read timed out\n");
+	return 0;
+}
+
+static void ftmac100_mdio_write(struct net_device *netdev, int phy_id, int reg,
+				int data)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIWR;
+
+	data = FTMAC100_PHYWDATA_MIIWDATA(data);
+
+	iowrite32(data, priv->base + FTMAC100_OFFSET_PHYWDATA);
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIWR) == 0)
+			return;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio write timed out\n");
+}
+
+/******************************************************************************
+ * struct ethtool_ops functions
+ *****************************************************************************/
+static void ftmac100_get_drvinfo(struct net_device *netdev,
+				 struct ethtool_drvinfo *info)
+{
+	strcpy(info->driver, DRV_NAME);
+	strcpy(info->version, DRV_VERSION);
+	strcpy(info->bus_info, dev_name(&netdev->dev));
+}
+
+static int ftmac100_get_settings(struct net_device *netdev,
+				 struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_gset(&priv->mii, cmd);
+}
+
+static int ftmac100_set_settings(struct net_device *netdev,
+				 struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_sset(&priv->mii, cmd);
+}
+
+static int ftmac100_nway_reset(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_nway_restart(&priv->mii);
+}
+
+static u32 ftmac100_get_link(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_link_ok(&priv->mii);
+}
+
+static const struct ethtool_ops ftmac100_ethtool_ops = {
+	.set_settings		= ftmac100_set_settings,
+	.get_settings		= ftmac100_get_settings,
+	.get_drvinfo		= ftmac100_get_drvinfo,
+	.nway_reset		= ftmac100_nway_reset,
+	.get_link		= ftmac100_get_link,
+};
+
+/******************************************************************************
+ * interrupt handler
+ *****************************************************************************/
+static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	if (likely(netif_running(netdev))) {
+		/* Disable interrupts for polling */
+		ftmac100_disable_all_int(priv);
+		napi_schedule(&priv->napi);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/******************************************************************************
+ * struct napi_struct functions
+ *****************************************************************************/
+static int ftmac100_poll(struct napi_struct *napi, int budget)
+{
+	struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
+	struct net_device *netdev = priv->netdev;
+	unsigned int status;
+	bool completed = true;
+	int rx = 0;
+
+	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
+
+	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
+		/*
+		 * FTMAC100_INT_RPKT_FINISH:
+		 *	RX DMA has received packets into RX buffer successfully
+		 *
+		 * FTMAC100_INT_NORXBUF:
+		 *	RX buffer unavailable
+		 */
+		bool retry;
+
+		do {
+			retry = ftmac100_rx_packet(priv, &rx);
+		} while (retry && rx < budget);
+
+		if (retry && rx == budget)
+			completed = false;
+	}
+
+	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
+		/*
+		 * FTMAC100_INT_XPKT_OK:
+		 *	packet transmitted to ethernet successfully
+		 *
+		 * FTMAC100_INT_XPKT_LOST:
+		 *	packet transmitted to ethernet lost due to late
+		 *	collision or excessive collision
+		 */
+		ftmac100_tx_complete(priv);
+	}
+
+	if (status & (FTMAC100_INT_NORXBUF | FTMAC100_INT_RPKT_LOST |
+		      FTMAC100_INT_AHB_ERR | FTMAC100_INT_PHYSTS_CHG)) {
+		if (net_ratelimit())
+			netdev_info(netdev, "[ISR] = 0x%x: %s%s%s%s\n", status,
+				    status & FTMAC100_INT_NORXBUF ? "NORXBUF " : "",
+				    status & FTMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "",
+				    status & FTMAC100_INT_AHB_ERR ? "AHB_ERR " : "",
+				    status & FTMAC100_INT_PHYSTS_CHG ? "PHYSTS_CHG" : "");
+
+		if (status & FTMAC100_INT_NORXBUF) {
+			/* RX buffer unavailable */
+			netdev->stats.rx_over_errors++;
+		}
+
+		if (status & FTMAC100_INT_RPKT_LOST) {
+			/* received packet lost due to RX FIFO full */
+			netdev->stats.rx_fifo_errors++;
+		}
+
+		if (status & FTMAC100_INT_PHYSTS_CHG) {
+			/* PHY link status change */
+			mii_check_link(&priv->mii);
+		}
+	}
+
+	if (completed) {
+		/* stop polling */
+		napi_complete(napi);
+		ftmac100_enable_all_int(priv);
+	}
+
+	return rx;
+}
+
+/******************************************************************************
+ * struct net_device_ops functions
+ *****************************************************************************/
+static int ftmac100_open(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int err;
+
+	err = ftmac100_alloc_buffers(priv);
+	if (err) {
+		netdev_err(netdev, "failed to allocate buffers\n");
+		goto err_alloc;
+	}
+
+	err = request_irq(priv->irq, ftmac100_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 = ftmac100_start_hw(priv);
+	if (err)
+		goto err_hw;
+
+	napi_enable(&priv->napi);
+	netif_start_queue(netdev);
+
+	ftmac100_enable_all_int(priv);
+	return 0;
+
+err_hw:
+	free_irq(priv->irq, netdev);
+err_irq:
+	ftmac100_free_buffers(priv);
+err_alloc:
+	return err;
+}
+
+static int ftmac100_stop(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	ftmac100_disable_all_int(priv);
+	netif_stop_queue(netdev);
+	napi_disable(&priv->napi);
+	ftmac100_stop_hw(priv);
+	free_irq(priv->irq, netdev);
+	ftmac100_free_buffers(priv);
+
+	return 0;
+}
+
+static int ftmac100_hard_start_xmit(struct sk_buff *skb,
+				    struct net_device *netdev)
+{
+	struct ftmac100 *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++;
+		dev_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++;
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	return ftmac100_xmit(priv, skb, map);
+}
+
+/* optional */
+static int ftmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr,
+			     int cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+
+	return generic_mii_ioctl(&priv->mii, data, cmd, NULL);
+}
+
+static const struct net_device_ops ftmac100_netdev_ops = {
+	.ndo_open		= ftmac100_open,
+	.ndo_stop		= ftmac100_stop,
+	.ndo_start_xmit		= ftmac100_hard_start_xmit,
+	.ndo_set_mac_address	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= ftmac100_do_ioctl,
+};
+
+/******************************************************************************
+ * struct platform_driver functions
+ *****************************************************************************/
+static int ftmac100_remove(struct platform_device *pdev)
+{
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+
+	netdev = platform_get_drvdata(pdev);
+	if (netdev == NULL)
+		return 0;
+
+	platform_set_drvdata(pdev, NULL);
+
+	priv = netdev_priv(netdev);
+
+	netif_napi_del(&priv->napi);
+	unregister_netdev(netdev);
+
+	if (priv->base != NULL)
+		iounmap(priv->base);
+
+	if (priv->res != NULL)
+		release_resource(priv->res);
+
+	free_netdev(netdev);
+	return 0;
+}
+
+static int ftmac100_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int irq;
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+	int err;
+
+	if (!pdev)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENXIO;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/* setup net_device */
+
+	netdev = alloc_etherdev(sizeof(struct ftmac100));
+	if (netdev == NULL) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	SET_NETDEV_DEV(netdev, &pdev->dev);
+	SET_ETHTOOL_OPS(netdev, &ftmac100_ethtool_ops);
+	netdev->netdev_ops = &ftmac100_netdev_ops;
+
+	platform_set_drvdata(pdev, netdev);
+
+	/* setup private data */
+
+	priv = netdev_priv(netdev);
+	priv->netdev = netdev;
+	priv->dev = &pdev->dev;
+
+	spin_lock_init(&priv->tx_lock);
+
+	/* initialize NAPI */
+	netif_napi_add(netdev, &priv->napi, ftmac100_poll, 64);
+
+	/* map io memory */
+	priv->res = request_mem_region(res->start, resource_size(res),
+				       dev_name(&pdev->dev));
+	if (priv->res == NULL) {
+		dev_err(&pdev->dev, "Could not reserve memory region\n");
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	priv->base = ioremap(res->start, res->end - res->start);
+	if (priv->base == NULL) {
+		dev_err(&pdev->dev, "Failed to ioremap ethernet registers\n");
+		err = -EIO;
+		goto err_out;
+	}
+
+	priv->irq = irq;
+
+	/* initialize struct mii_if_info */
+
+	priv->mii.phy_id	= 0;
+	priv->mii.phy_id_mask	= 0x1f;
+	priv->mii.reg_num_mask	= 0x1f;
+	priv->mii.dev		= netdev;
+	priv->mii.mdio_read	= ftmac100_mdio_read;
+	priv->mii.mdio_write	= ftmac100_mdio_write;
+
+	/* register network device */
+
+	err = register_netdev(netdev);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to register netdev\n");
+		goto err_out;
+	}
+
+	netdev_info(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base);
+
+	if (!is_valid_ether_addr(netdev->dev_addr)) {
+		random_ether_addr(netdev->dev_addr);
+		netdev_info(netdev, "generated random MAC address %pM\n",
+			    netdev->dev_addr);
+	}
+
+	return 0;
+
+err_out:
+	ftmac100_remove(pdev);
+	return err;
+}
+
+static struct platform_driver ftmac100_driver = {
+	.probe		= ftmac100_probe,
+	.remove		= ftmac100_remove,
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+/******************************************************************************
+ * initialization / finalization
+ *****************************************************************************/
+static int __init ftmac100_init(void)
+{
+	pr_info("Loading version " DRV_VERSION " ...\n");
+	return platform_driver_register(&ftmac100_driver);
+}
+
+static void __exit ftmac100_exit(void)
+{
+	platform_driver_unregister(&ftmac100_driver);
+}
+
+module_init(ftmac100_init);
+module_exit(ftmac100_exit);
+
+MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");
+MODULE_DESCRIPTION("FTMAC100 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ftmac100.h b/drivers/net/ftmac100.h
new file mode 100644
index 0000000..46a0c47
--- /dev/null
+++ b/drivers/net/ftmac100.h
@@ -0,0 +1,180 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __FTMAC100_H
+#define __FTMAC100_H
+
+#define	FTMAC100_OFFSET_ISR		0x00
+#define	FTMAC100_OFFSET_IMR		0x04
+#define	FTMAC100_OFFSET_MAC_MADR	0x08
+#define	FTMAC100_OFFSET_MAC_LADR	0x0c
+#define	FTMAC100_OFFSET_MAHT0		0x10
+#define	FTMAC100_OFFSET_MAHT1		0x14
+#define	FTMAC100_OFFSET_TXPD		0x18
+#define	FTMAC100_OFFSET_RXPD		0x1c
+#define	FTMAC100_OFFSET_TXR_BADR	0x20
+#define	FTMAC100_OFFSET_RXR_BADR	0x24
+#define	FTMAC100_OFFSET_ITC		0x28
+#define	FTMAC100_OFFSET_APTC		0x2c
+#define	FTMAC100_OFFSET_DBLAC		0x30
+#define	FTMAC100_OFFSET_MACCR		0x88
+#define	FTMAC100_OFFSET_MACSR		0x8c
+#define	FTMAC100_OFFSET_PHYCR		0x90
+#define	FTMAC100_OFFSET_PHYWDATA	0x94
+#define	FTMAC100_OFFSET_FCR		0x98
+#define	FTMAC100_OFFSET_BPR		0x9c
+#define	FTMAC100_OFFSET_TS		0xc4
+#define	FTMAC100_OFFSET_DMAFIFOS	0xc8
+#define	FTMAC100_OFFSET_TM		0xcc
+#define	FTMAC100_OFFSET_TX_MCOL_SCOL	0xd4
+#define	FTMAC100_OFFSET_RPF_AEP		0xd8
+#define	FTMAC100_OFFSET_XM_PG		0xdc
+#define	FTMAC100_OFFSET_RUNT_TLCC	0xe0
+#define	FTMAC100_OFFSET_CRCER_FTL	0xe4
+#define	FTMAC100_OFFSET_RLC_RCC		0xe8
+#define	FTMAC100_OFFSET_BROC		0xec
+#define	FTMAC100_OFFSET_MULCA		0xf0
+#define	FTMAC100_OFFSET_RP		0xf4
+#define	FTMAC100_OFFSET_XP		0xf8
+
+/*
+ * Interrupt status register & interrupt mask register
+ */
+#define	FTMAC100_INT_RPKT_FINISH	(1 << 0)
+#define	FTMAC100_INT_NORXBUF		(1 << 1)
+#define	FTMAC100_INT_XPKT_FINISH	(1 << 2)
+#define	FTMAC100_INT_NOTXBUF		(1 << 3)
+#define	FTMAC100_INT_XPKT_OK		(1 << 4)
+#define	FTMAC100_INT_XPKT_LOST		(1 << 5)
+#define	FTMAC100_INT_RPKT_SAV		(1 << 6)
+#define	FTMAC100_INT_RPKT_LOST		(1 << 7)
+#define	FTMAC100_INT_AHB_ERR		(1 << 8)
+#define	FTMAC100_INT_PHYSTS_CHG		(1 << 9)
+
+/*
+ * Interrupt timer control register
+ */
+#define FTMAC100_ITC_RXINT_CNT(x)	(((x) & 0xf) << 0)
+#define FTMAC100_ITC_RXINT_THR(x)	(((x) & 0x7) << 4)
+#define FTMAC100_ITC_RXINT_TIME_SEL	(1 << 7)
+#define FTMAC100_ITC_TXINT_CNT(x)	(((x) & 0xf) << 8)
+#define FTMAC100_ITC_TXINT_THR(x)	(((x) & 0x7) << 12)
+#define FTMAC100_ITC_TXINT_TIME_SEL	(1 << 15)
+
+/*
+ * Automatic polling timer control register
+ */
+#define	FTMAC100_APTC_RXPOLL_CNT(x)	(((x) & 0xf) << 0)
+#define	FTMAC100_APTC_RXPOLL_TIME_SEL	(1 << 4)
+#define	FTMAC100_APTC_TXPOLL_CNT(x)	(((x) & 0xf) << 8)
+#define	FTMAC100_APTC_TXPOLL_TIME_SEL	(1 << 12)
+
+/*
+ * DMA burst length and arbitration control register
+ */
+#define FTMAC100_DBLAC_INCR4_EN		(1 << 0)
+#define FTMAC100_DBLAC_INCR8_EN		(1 << 1)
+#define FTMAC100_DBLAC_INCR16_EN	(1 << 2)
+#define FTMAC100_DBLAC_RXFIFO_LTHR(x)	(((x) & 0x7) << 3)
+#define FTMAC100_DBLAC_RXFIFO_HTHR(x)	(((x) & 0x7) << 6)
+#define FTMAC100_DBLAC_RX_THR_EN	(1 << 9)
+
+/*
+ * MAC control register
+ */
+#define	FTMAC100_MACCR_XDMA_EN		(1 << 0)
+#define	FTMAC100_MACCR_RDMA_EN		(1 << 1)
+#define	FTMAC100_MACCR_SW_RST		(1 << 2)
+#define	FTMAC100_MACCR_LOOP_EN		(1 << 3)
+#define	FTMAC100_MACCR_CRC_DIS		(1 << 4)
+#define	FTMAC100_MACCR_XMT_EN		(1 << 5)
+#define	FTMAC100_MACCR_ENRX_IN_HALFTX	(1 << 6)
+#define	FTMAC100_MACCR_RCV_EN		(1 << 8)
+#define	FTMAC100_MACCR_HT_MULTI_EN	(1 << 9)
+#define	FTMAC100_MACCR_RX_RUNT		(1 << 10)
+#define	FTMAC100_MACCR_RX_FTL		(1 << 11)
+#define	FTMAC100_MACCR_RCV_ALL		(1 << 12)
+#define	FTMAC100_MACCR_CRC_APD		(1 << 14)
+#define	FTMAC100_MACCR_FULLDUP		(1 << 15)
+#define	FTMAC100_MACCR_RX_MULTIPKT	(1 << 16)
+#define	FTMAC100_MACCR_RX_BROADPKT	(1 << 17)
+
+/*
+ * PHY control register
+ */
+#define FTMAC100_PHYCR_MIIRDATA		0xffff
+#define FTMAC100_PHYCR_PHYAD(x)		(((x) & 0x1f) << 16)
+#define FTMAC100_PHYCR_REGAD(x)		(((x) & 0x1f) << 21)
+#define FTMAC100_PHYCR_MIIRD		(1 << 26)
+#define FTMAC100_PHYCR_MIIWR		(1 << 27)
+
+/*
+ * PHY write data register
+ */
+#define FTMAC100_PHYWDATA_MIIWDATA(x)	((x) & 0xffff)
+
+/*
+ * Transmit descriptor, aligned to 16 bytes
+ */
+struct ftmac100_txdes {
+	unsigned int	txdes0;
+	unsigned int	txdes1;
+	unsigned int	txdes2;	/* TXBUF_BADR */
+	unsigned int	txdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_TXDES0_TXPKT_LATECOL	(1 << 0)
+#define	FTMAC100_TXDES0_TXPKT_EXSCOL	(1 << 1)
+#define	FTMAC100_TXDES0_TXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_TXDES1_TXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_TXDES1_LTS		(1 << 27)
+#define	FTMAC100_TXDES1_FTS		(1 << 28)
+#define	FTMAC100_TXDES1_TX2FIC		(1 << 29)
+#define	FTMAC100_TXDES1_TXIC		(1 << 30)
+#define	FTMAC100_TXDES1_EDOTR		(1 << 31)
+
+/*
+ * Receive descriptor, aligned to 16 bytes
+ */
+struct ftmac100_rxdes {
+	unsigned int	rxdes0;
+	unsigned int	rxdes1;
+	unsigned int	rxdes2;	/* RXBUF_BADR */
+	unsigned int	rxdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_RXDES0_RFL		0x7ff
+#define	FTMAC100_RXDES0_MULTICAST	(1 << 16)
+#define	FTMAC100_RXDES0_BROADCAST	(1 << 17)
+#define	FTMAC100_RXDES0_RX_ERR		(1 << 18)
+#define	FTMAC100_RXDES0_CRC_ERR		(1 << 19)
+#define	FTMAC100_RXDES0_FTL		(1 << 20)
+#define	FTMAC100_RXDES0_RUNT		(1 << 21)
+#define	FTMAC100_RXDES0_RX_ODD_NB	(1 << 22)
+#define	FTMAC100_RXDES0_LRS		(1 << 28)
+#define	FTMAC100_RXDES0_FRS		(1 << 29)
+#define	FTMAC100_RXDES0_RXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_RXDES1_RXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_RXDES1_EDORR		(1 << 31)
+
+#endif /* __FTMAC100_H */
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-21  7:55     ` [PATCH v4] " Po-Yu Chuang
@ 2011-01-21  9:08       ` Eric Dumazet
  2011-01-24  8:07         ` Po-Yu Chuang
  2011-01-21 12:26       ` Michał Mirosław
  2011-01-24 12:39       ` [PATCH v5] " Po-Yu Chuang
  2 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-01-21  9:08 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, Po-Yu Chuang

Le vendredi 21 janvier 2011 à 15:55 +0800, Po-Yu Chuang a écrit :
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> 
> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
> MII.  This driver has been working on some ARM/NDS32 SoC's including
> Faraday A320 and Andes AG101.
> 
> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>


> +
> +static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)
> +{
...
> +
> +	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
> +
> +	dev_kfree_skb_irq(skb);
> +
> +	ftmac100_txdes_reset(txdes);
> +
> +	ftmac100_tx_clean_pointer_advance(priv);
> +
> +	priv->tx_pending--;
> +	netif_wake_queue(netdev);
> +
> +	return true;
> +}
> +

Thanks to NAPI, you can free skb directly, not queuing it via
NET_TX_SOFTIRQ softirq, using dev_kfree_skb() instead of
dev_kfree_skb_irq()




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-21  7:55     ` [PATCH v4] " Po-Yu Chuang
  2011-01-21  9:08       ` Eric Dumazet
@ 2011-01-21 12:26       ` Michał Mirosław
  2011-01-24  8:26         ` Po-Yu Chuang
  2011-01-24 12:39       ` [PATCH v5] " Po-Yu Chuang
  2 siblings, 1 reply; 74+ messages in thread
From: Michał Mirosław @ 2011-01-21 12:26 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, joe, dilinger,
	Po-Yu Chuang

2011/1/21 Po-Yu Chuang <ratbert.chuang@gmail.com>:
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
>
> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
> MII.  This driver has been working on some ARM/NDS32 SoC's including
> Faraday A320 and Andes AG101.
>
> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
[...]
> +static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
> +{
> +       /* clear all except end of ring bit */
> +       txdes->txdes0 = 0;
> +       txdes->txdes1 &= FTMAC100_TXDES1_EDOTR;
> +       txdes->txdes2 = 0;
> +       txdes->txdes3 = 0;
> +}

This also probably needs cpu_to_le32().

[...]
> +static void ftmac100_free_buffers(struct ftmac100 *priv)
> +{
> +       int i;
> +
> +       for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
> +               struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
> +               dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
> +               void *page = ftmac100_rxdes_get_va(rxdes);
> +
> +               if (d)
> +                       dma_unmap_single(priv->dev, d, PAGE_SIZE,
> +                                        DMA_FROM_DEVICE);
> +
> +               if (page != NULL)
> +                       free_page((unsigned long)page);
> +       }
> +
[...]

> +static int ftmac100_alloc_buffers(struct ftmac100 *priv)
> +{
> +       int i;
> +
> +       priv->descs = dma_alloc_coherent(priv->dev,
> +                                        sizeof(struct ftmac100_descs),
> +                                        &priv->descs_dma_addr,
> +                                        GFP_KERNEL | GFP_DMA);
> +       if (priv->descs == NULL)
> +               return -ENOMEM;
> +
> +       memset(priv->descs, 0, sizeof(struct ftmac100_descs));
> +
> +       /* initialize RX ring */
> +
> +       ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
> +
> +       for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
> +               struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
> +               void *page;
> +               dma_addr_t d;
> +
> +               page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
> +               if (page == NULL)
> +                       goto err;
> +
> +               d = dma_map_single(priv->dev, page, PAGE_SIZE, DMA_FROM_DEVICE);
> +               if (unlikely(dma_mapping_error(priv->dev, d))) {
> +                       free_page((unsigned long)page);
> +                       goto err;
> +               }
> +
> +               /*
> +                * The hardware enforces a sub-2K maximum packet size, so we
> +                * put two buffers on every hardware page.
> +                */
> +               ftmac100_rxdes_set_va(rxdes, page);
> +               ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);
> +
> +               ftmac100_rxdes_set_dma_addr(rxdes, d);
> +               ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);
> +
> +               ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
> +               ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);
> +
> +               ftmac100_rxdes_set_dma_own(rxdes);
> +               ftmac100_rxdes_set_dma_own(rxdes + 1);
> +       }
[...]

Did you test this? This looks like it will result in double free after
packet RX, as you are giving the same page (referenced once) to two
distinct RX descriptors, that may be assigned different packets.

Since your not implementing any RX offloads, you might just allocate
fresh skb's with alloc_skb() and store skb pointer in rxdes3. Since
hardware doesn't touch it, you can skip cpu_to_le32()/le32_to_cpu()
there (leave a comment, though).

Unless this needs to work for ISA devices, you should drop GFP_DMA
allocation flag.

Best Regards,
Michał Mirosław

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-21  9:08       ` Eric Dumazet
@ 2011-01-24  8:07         ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-24  8:07 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, Po-Yu Chuang

Dear Eric,

On Fri, Jan 21, 2011 at 5:08 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le vendredi 21 janvier 2011 à 15:55 +0800, Po-Yu Chuang a écrit :
> ...
>> +
>> +     dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
>> +
>> +     dev_kfree_skb_irq(skb);
>> +
>> +     ftmac100_txdes_reset(txdes);
>> +
>> +     ftmac100_tx_clean_pointer_advance(priv);
>> +
>> +     priv->tx_pending--;
>> +     netif_wake_queue(netdev);
>> +
>> +     return true;
>> +}
>> +
>
> Thanks to NAPI, you can free skb directly, not queuing it via
> NET_TX_SOFTIRQ softirq, using dev_kfree_skb() instead of
> dev_kfree_skb_irq()

Fixed. Thanks a lot.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-21 12:26       ` Michał Mirosław
@ 2011-01-24  8:26         ` Po-Yu Chuang
  2011-01-24 20:22           ` Michał Mirosław
  0 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-24  8:26 UTC (permalink / raw)
  To: Michał Mirosław
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, joe, dilinger,
	Po-Yu Chuang

Dear Michał,

2011/1/21 Michał Mirosław <mirqus@gmail.com>:
> 2011/1/21 Po-Yu Chuang <ratbert.chuang@gmail.com>:
>> From: Po-Yu Chuang <ratbert@faraday-tech.com>
>>
>> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
>> MII.  This driver has been working on some ARM/NDS32 SoC's including
>> Faraday A320 and Andes AG101.
>>
>> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
> [...]
>> +static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
>> +{
>> +       /* clear all except end of ring bit */
>> +       txdes->txdes0 = 0;
>> +       txdes->txdes1 &= FTMAC100_TXDES1_EDOTR;
>> +       txdes->txdes2 = 0;
>> +       txdes->txdes3 = 0;
>> +}
>
> This also probably needs cpu_to_le32().

Ah, I missed that. Fixed.

> [...]
>> +static void ftmac100_free_buffers(struct ftmac100 *priv)
>> +{
>> +       int i;
>> +
>> +       for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
>> +               struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
>> +               dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
>> +               void *page = ftmac100_rxdes_get_va(rxdes);
>> +
>> +               if (d)
>> +                       dma_unmap_single(priv->dev, d, PAGE_SIZE,
>> +                                        DMA_FROM_DEVICE);
>> +
>> +               if (page != NULL)
>> +                       free_page((unsigned long)page);
>> +       }
>> +
> [...]
>
>> +static int ftmac100_alloc_buffers(struct ftmac100 *priv)
>> +{
>> +       int i;
>> +
>> +       priv->descs = dma_alloc_coherent(priv->dev,
>> +                                        sizeof(struct ftmac100_descs),
>> +                                        &priv->descs_dma_addr,
>> +                                        GFP_KERNEL | GFP_DMA);
>> +       if (priv->descs == NULL)
>> +               return -ENOMEM;
>> +
>> +       memset(priv->descs, 0, sizeof(struct ftmac100_descs));
>> +
>> +       /* initialize RX ring */
>> +
>> +       ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
>> +
>> +       for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
>> +               struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
>> +               void *page;
>> +               dma_addr_t d;
>> +
>> +               page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
>> +               if (page == NULL)
>> +                       goto err;
>> +
>> +               d = dma_map_single(priv->dev, page, PAGE_SIZE, DMA_FROM_DEVICE);
>> +               if (unlikely(dma_mapping_error(priv->dev, d))) {
>> +                       free_page((unsigned long)page);
>> +                       goto err;
>> +               }
>> +
>> +               /*
>> +                * The hardware enforces a sub-2K maximum packet size, so we
>> +                * put two buffers on every hardware page.
>> +                */
>> +               ftmac100_rxdes_set_va(rxdes, page);
>> +               ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);
>> +
>> +               ftmac100_rxdes_set_dma_addr(rxdes, d);
>> +               ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);
>> +
>> +               ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
>> +               ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);
>> +
>> +               ftmac100_rxdes_set_dma_own(rxdes);
>> +               ftmac100_rxdes_set_dma_own(rxdes + 1);
>> +       }
> [...]
>
> Did you test this? This looks like it will result in double free after
> packet RX, as you are giving the same page (referenced once) to two
> distinct RX descriptors, that may be assigned different packets.

Yes, this is tested.

> Since your not implementing any RX offloads, you might just allocate
> fresh skb's with alloc_skb() and store skb pointer in rxdes3. Since

rxdes3 does not store virtual address of an skb.
It stores the address of the buffer allocated while open() and freed
only when stop().
The data in that buffer will be memcpy()ed to an skb allocated in
ftmac100_rx_packet().
No double free happens.

> hardware doesn't touch it, you can skip cpu_to_le32()/le32_to_cpu()
> there (leave a comment, though).

Agree. Thanks.

> Unless this needs to work for ISA devices, you should drop GFP_DMA
> allocation flag.

Ben mentioned about this in the previous mail. I thought that it is OK
to keep GFP_DMA
on ARM platform, but since you point out this flag is for ISA, I will drop it.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v5] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-21  7:55     ` [PATCH v4] " Po-Yu Chuang
  2011-01-21  9:08       ` Eric Dumazet
  2011-01-21 12:26       ` Michał Mirosław
@ 2011-01-24 12:39       ` Po-Yu Chuang
  2011-01-24 15:07         ` Eric Dumazet
  2 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-24 12:39 UTC (permalink / raw)
  To: netdev
  Cc: linux-kernel, bhutchings, eric.dumazet, joe, dilinger, mirqus,
	Po-Yu Chuang

From: Po-Yu Chuang <ratbert@faraday-tech.com>

FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII.  This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.

Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts

v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix

v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()

v5:
use dev_kfree_skb() in ftmac100_tx_complete_packet()
cpu_to_le32/le32_to_cpu usage fix
drop GFP_DMA

 drivers/net/Kconfig    |    9 +
 drivers/net/Makefile   |    1 +
 drivers/net/ftmac100.c | 1212 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/ftmac100.h |  180 +++++++
 4 files changed, 1402 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/ftmac100.c
 create mode 100644 drivers/net/ftmac100.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 4f1755b..26da0ee 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2014,6 +2014,15 @@ config BCM63XX_ENET
 	  This driver supports the ethernet MACs in the Broadcom 63xx
 	  MIPS chipset family (BCM63XX).
 
+config FTMAC100
+	tristate "Faraday FTMAC100 10/100 Ethernet support"
+	depends on ARM
+	select MII
+	help
+	  This driver supports the FTMAC100 Ethernet controller from
+	  Faraday. It is used on Faraday A320, Andes AG101, AG101P
+	  and some other ARM/NDS32 SoC's.
+
 source "drivers/net/fs_enet/Kconfig"
 
 source "drivers/net/octeon/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..7c21711 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_FORCEDETH) += forcedeth.o
 obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
 obj-$(CONFIG_AX88796) += ax88796.o
 obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
+obj-$(CONFIG_FTMAC100) += ftmac100.o
 
 obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
new file mode 100644
index 0000000..18f8036
--- /dev/null
+++ b/drivers/net/ftmac100.c
@@ -0,0 +1,1212 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#include "ftmac100.h"
+
+#define DRV_NAME	"ftmac100"
+#define DRV_VERSION	"0.2"
+
+#define RX_QUEUE_ENTRIES	128	/* must be power of 2 */
+#define TX_QUEUE_ENTRIES	16	/* must be power of 2 */
+
+#define MAX_PKT_SIZE		1518
+#define RX_BUF_SIZE		2044	/* must be smaller than 0x7ff */
+
+/******************************************************************************
+ * private data
+ *****************************************************************************/
+struct ftmac100_descs {
+	struct ftmac100_rxdes	rxdes[RX_QUEUE_ENTRIES];
+	struct ftmac100_txdes	txdes[TX_QUEUE_ENTRIES];
+};
+
+struct ftmac100 {
+	struct resource		*res;
+	void			*base;
+	int			irq;
+
+	struct ftmac100_descs	*descs;
+	dma_addr_t		descs_dma_addr;
+
+	unsigned int		rx_pointer;
+	unsigned int		tx_clean_pointer;
+	unsigned int		tx_pointer;
+	unsigned int		tx_pending;
+
+	spinlock_t		tx_lock;
+
+	struct net_device	*netdev;
+	struct device		*dev;
+	struct napi_struct	napi;
+
+	struct mii_if_info	mii;
+};
+
+/******************************************************************************
+ * internal functions (hardware register access)
+ *****************************************************************************/
+#define INT_MASK_ALL_ENABLED	(FTMAC100_INT_RPKT_FINISH	| \
+				 FTMAC100_INT_NORXBUF		| \
+				 FTMAC100_INT_XPKT_OK		| \
+				 FTMAC100_INT_XPKT_LOST		| \
+				 FTMAC100_INT_RPKT_LOST		| \
+				 FTMAC100_INT_AHB_ERR		| \
+				 FTMAC100_INT_PHYSTS_CHG)
+
+static void ftmac100_enable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_disable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_set_rx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
+}
+
+static void ftmac100_set_tx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_TXR_BADR);
+}
+
+static void ftmac100_txdma_start_polling(struct ftmac100 *priv)
+{
+	iowrite32(1, priv->base + FTMAC100_OFFSET_TXPD);
+}
+
+static int ftmac100_reset(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	int i;
+
+	/* NOTE: reset clears all registers */
+	iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
+
+	for (i = 0; i < 5; i++) {
+		unsigned int maccr;
+
+		maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
+		if (!(maccr & FTMAC100_MACCR_SW_RST)) {
+			/*
+			 * FTMAC100_MACCR_SW_RST cleared does not indicate
+			 * that hardware reset completed (what the f*ck).
+			 * We still need to wait for a while.
+			 */
+			usleep_range(500, 1000);
+			return 0;
+		}
+
+		usleep_range(1000, 10000);
+	}
+
+	netdev_err(netdev, "software reset failed\n");
+	return -EIO;
+}
+
+static void ftmac100_set_mac(struct ftmac100 *priv, const unsigned char *mac)
+{
+	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 + FTMAC100_OFFSET_MAC_MADR);
+	iowrite32(laddr, priv->base + FTMAC100_OFFSET_MAC_LADR);
+}
+
+#define MACCR_ENABLE_ALL	(FTMAC100_MACCR_XMT_EN	| \
+				 FTMAC100_MACCR_RCV_EN	| \
+				 FTMAC100_MACCR_XDMA_EN	| \
+				 FTMAC100_MACCR_RDMA_EN	| \
+				 FTMAC100_MACCR_CRC_APD	| \
+				 FTMAC100_MACCR_FULLDUP	| \
+				 FTMAC100_MACCR_RX_RUNT	| \
+				 FTMAC100_MACCR_RX_BROADPKT)
+
+static int ftmac100_start_hw(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+
+	if (ftmac100_reset(priv))
+		return -EIO;
+
+	/* setup ring buffer base registers */
+
+	ftmac100_set_rx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, rxdes));
+	ftmac100_set_tx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, txdes));
+
+	iowrite32(FTMAC100_APTC_RXPOLL_CNT(1), priv->base + FTMAC100_OFFSET_APTC);
+
+	ftmac100_set_mac(priv, netdev->dev_addr);
+
+	iowrite32(MACCR_ENABLE_ALL, priv->base + FTMAC100_OFFSET_MACCR);
+	return 0;
+}
+
+static void ftmac100_stop_hw(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_MACCR);
+}
+
+/******************************************************************************
+ * internal functions (receive descriptor)
+ *****************************************************************************/
+static bool ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FRS);
+}
+
+static bool ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_LRS);
+}
+
+static bool ftmac100_rxdes_owned_by_dma(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static void ftmac100_rxdes_set_dma_own(struct ftmac100_rxdes *rxdes)
+{
+	/* clear status bits */
+	rxdes->rxdes0 = cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static bool ftmac100_rxdes_rx_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ERR);
+}
+
+static bool ftmac100_rxdes_crc_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_CRC_ERR);
+}
+
+static bool ftmac100_rxdes_frame_too_long(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FTL);
+}
+
+static bool ftmac100_rxdes_runt(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RUNT);
+}
+
+static bool ftmac100_rxdes_odd_nibble(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ODD_NB);
+}
+
+static unsigned int ftmac100_rxdes_frame_length(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RFL);
+}
+
+static bool ftmac100_rxdes_multicast(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_MULTICAST);
+}
+
+static void ftmac100_rxdes_set_buffer_size(struct ftmac100_rxdes *rxdes,
+					   unsigned int size)
+{
+	rxdes->rxdes1 &= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_RXBUF_SIZE(size));
+}
+
+static void ftmac100_rxdes_set_end_of_ring(struct ftmac100_rxdes *rxdes)
+{
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+}
+
+static void ftmac100_rxdes_set_dma_addr(struct ftmac100_rxdes *rxdes,
+					dma_addr_t addr)
+{
+	rxdes->rxdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_rxdes_get_dma_addr(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes2);
+}
+
+/*
+ * rxdes3 is not used by hardware. We use it to keep track of buffer.
+ * Since hardware does not touch it, we can skip cpu_to_le32()/le32_to_cpu().
+ */
+static void ftmac100_rxdes_set_va(struct ftmac100_rxdes *rxdes, void *addr)
+{
+	rxdes->rxdes3 = (unsigned int)addr;
+}
+
+static void *ftmac100_rxdes_get_va(struct ftmac100_rxdes *rxdes)
+{
+	return (void *)rxdes->rxdes3;
+}
+
+/******************************************************************************
+ * internal functions (receive)
+ *****************************************************************************/
+static int ftmac100_next_rx_pointer(int pointer)
+{
+	return (pointer + 1) & (RX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_rx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->rx_pointer = ftmac100_next_rx_pointer(priv->rx_pointer);
+}
+
+static struct ftmac100_rxdes *ftmac100_current_rxdes(struct ftmac100 *priv)
+{
+	return &priv->descs->rxdes[priv->rx_pointer];
+}
+
+static struct ftmac100_rxdes *
+ftmac100_rx_locate_first_segment(struct ftmac100 *priv)
+{
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+
+	while (!ftmac100_rxdes_owned_by_dma(rxdes)) {
+		if (ftmac100_rxdes_first_segment(rxdes))
+			return rxdes;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	}
+
+	return NULL;
+}
+
+static bool ftmac100_rx_packet_error(struct ftmac100 *priv,
+				     struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	bool error = false;
+
+	if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx err\n");
+
+		netdev->stats.rx_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_crc_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx crc err\n");
+
+		netdev->stats.rx_crc_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx frame too long\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_runt(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx runt\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_odd_nibble(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx odd nibble\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	}
+
+	return error;
+}
+
+static void ftmac100_rx_drop_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+	bool done = false;
+
+	if (net_ratelimit())
+		netdev_dbg(netdev, "drop packet %p\n", rxdes);
+
+	do {
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = true;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && !ftmac100_rxdes_owned_by_dma(rxdes));
+
+	netdev->stats.rx_dropped++;
+}
+
+static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes;
+	struct sk_buff *skb;
+	int length;
+	bool copied = false;
+	bool done = false;
+
+	rxdes = ftmac100_rx_locate_first_segment(priv);
+	if (!rxdes)
+		return false;
+
+	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	/* start processing */
+
+	length = ftmac100_rxdes_frame_length(rxdes);
+
+	skb = netdev_alloc_skb_ip_align(netdev, length);
+	if (unlikely(!skb)) {
+		if (net_ratelimit())
+			netdev_err(netdev, "rx skb alloc failed\n");
+
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	if (unlikely(ftmac100_rxdes_multicast(rxdes)))
+		netdev->stats.multicast++;
+
+	do {
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *buf = ftmac100_rxdes_get_va(rxdes);
+		int size;
+
+		size = min(length - copied, RX_BUF_SIZE);
+
+		dma_sync_single_for_cpu(priv->dev, d, RX_BUF_SIZE,
+					DMA_FROM_DEVICE);
+		memcpy(skb_put(skb, size), buf, size);
+
+		copied += size;
+
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = true;
+
+		dma_sync_single_for_device(priv->dev, d, RX_BUF_SIZE,
+					   DMA_FROM_DEVICE);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && copied < length);
+
+	skb->protocol = eth_type_trans(skb, netdev);
+
+	netdev->stats.rx_packets++;
+	netdev->stats.rx_bytes += skb->len;
+
+	/* push packet to protocol stack */
+	netif_receive_skb(skb);
+
+	(*processed)++;
+	return true;
+}
+
+/******************************************************************************
+ * internal functions (transmit descriptor)
+ *****************************************************************************/
+static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
+{
+	/* clear all except end of ring bit */
+	txdes->txdes0 = 0;
+	txdes->txdes1 &= cpu_to_le32(FTMAC100_TXDES1_EDOTR);
+	txdes->txdes2 = 0;
+	txdes->txdes3 = 0;
+}
+
+static bool ftmac100_txdes_owned_by_dma(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
+{
+	/*
+	 * Make sure dma own bit will not be set before any other
+	 * descriptor fields.
+	 */
+	wmb();
+	txdes->txdes0 |= cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static bool ftmac100_txdes_excessive_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_EXSCOL);
+}
+
+static bool ftmac100_txdes_late_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_LATECOL);
+}
+
+static void ftmac100_txdes_set_end_of_ring(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_EDOTR);
+}
+
+static void ftmac100_txdes_set_first_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_FTS);
+}
+
+static void ftmac100_txdes_set_last_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_LTS);
+}
+
+static void ftmac100_txdes_set_txint(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXIC);
+}
+
+static void ftmac100_txdes_set_buffer_size(struct ftmac100_txdes *txdes,
+					   unsigned int len)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXBUF_SIZE(len));
+}
+
+static void ftmac100_txdes_set_dma_addr(struct ftmac100_txdes *txdes,
+					dma_addr_t addr)
+{
+	txdes->txdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_txdes_get_dma_addr(struct ftmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes2);
+}
+
+/*
+ * txdes3 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 ftmac100_txdes_set_skb(struct ftmac100_txdes *txdes,
+				   struct sk_buff *skb)
+{
+	txdes->txdes3 = (unsigned int)skb;
+}
+
+static struct sk_buff *ftmac100_txdes_get_skb(struct ftmac100_txdes *txdes)
+{
+	return (struct sk_buff *)txdes->txdes3;
+}
+
+/******************************************************************************
+ * internal functions (transmit)
+ *****************************************************************************/
+static int ftmac100_next_tx_pointer(int pointer)
+{
+	return (pointer + 1) & (TX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_tx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_pointer = ftmac100_next_tx_pointer(priv->tx_pointer);
+}
+
+static void ftmac100_tx_clean_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_clean_pointer = ftmac100_next_tx_pointer(priv->tx_clean_pointer);
+}
+
+static struct ftmac100_txdes *ftmac100_current_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_pointer];
+}
+
+static struct ftmac100_txdes *
+ftmac100_current_clean_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_clean_pointer];
+}
+
+static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	struct sk_buff *skb;
+	dma_addr_t map;
+
+	if (priv->tx_pending == 0)
+		return false;
+
+	txdes = ftmac100_current_clean_txdes(priv);
+
+	if (ftmac100_txdes_owned_by_dma(txdes))
+		return false;
+
+	skb = ftmac100_txdes_get_skb(txdes);
+	map = ftmac100_txdes_get_dma_addr(txdes);
+
+	if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
+		     ftmac100_txdes_late_collision(txdes))) {
+		/*
+		 * packet transmitted to ethernet lost due to late collision
+		 * or excessive collision
+		 */
+		netdev->stats.tx_aborted_errors++;
+	} else {
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += skb->len;
+	}
+
+	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+
+	dev_kfree_skb(skb);
+
+	ftmac100_txdes_reset(txdes);
+
+	ftmac100_tx_clean_pointer_advance(priv);
+
+	priv->tx_pending--;
+	netif_wake_queue(netdev);
+
+	return true;
+}
+
+static void ftmac100_tx_complete(struct ftmac100 *priv)
+{
+	spin_lock(&priv->tx_lock);
+	while (ftmac100_tx_complete_packet(priv))
+		;
+	spin_unlock(&priv->tx_lock);
+}
+
+static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
+			 dma_addr_t map)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
+
+	txdes = ftmac100_current_txdes(priv);
+	ftmac100_tx_pointer_advance(priv);
+
+	/* setup TX descriptor */
+
+	spin_lock(&priv->tx_lock);
+	ftmac100_txdes_set_skb(txdes, skb);
+	ftmac100_txdes_set_dma_addr(txdes, map);
+
+	ftmac100_txdes_set_first_segment(txdes);
+	ftmac100_txdes_set_last_segment(txdes);
+	ftmac100_txdes_set_txint(txdes);
+	ftmac100_txdes_set_buffer_size(txdes, len);
+
+	priv->tx_pending++;
+	if (priv->tx_pending == TX_QUEUE_ENTRIES) {
+		if (net_ratelimit())
+			netdev_info(netdev, "tx queue full\n");
+
+		netif_stop_queue(netdev);
+	}
+
+	/* start transmit */
+	ftmac100_txdes_set_dma_own(txdes);
+	spin_unlock(&priv->tx_lock);
+
+	ftmac100_txdma_start_polling(priv);
+
+	return NETDEV_TX_OK;
+}
+
+/******************************************************************************
+ * internal functions (buffer)
+ *****************************************************************************/
+static void ftmac100_free_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
+		void *page = ftmac100_rxdes_get_va(rxdes);
+
+		if (d)
+			dma_unmap_single(priv->dev, d, PAGE_SIZE,
+					 DMA_FROM_DEVICE);
+
+		if (page != NULL)
+			free_page((unsigned long)page);
+	}
+
+	for (i = 0; i < TX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_txdes *txdes = &priv->descs->txdes[i];
+		struct sk_buff *skb = ftmac100_txdes_get_skb(txdes);
+
+		if (skb) {
+			dma_addr_t map;
+
+			map = ftmac100_txdes_get_dma_addr(txdes);
+			dma_unmap_single(priv->dev, map, skb_headlen(skb),
+					 DMA_TO_DEVICE);
+			dev_kfree_skb(skb);
+		}
+	}
+
+	dma_free_coherent(priv->dev, sizeof(struct ftmac100_descs),
+			  priv->descs, priv->descs_dma_addr);
+}
+
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	priv->descs = dma_alloc_coherent(priv->dev,
+					 sizeof(struct ftmac100_descs),
+					 &priv->descs_dma_addr, GFP_KERNEL);
+	if (priv->descs == NULL)
+		return -ENOMEM;
+
+	memset(priv->descs, 0, sizeof(struct ftmac100_descs));
+
+	/* initialize RX ring */
+
+	ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		void *page;
+		dma_addr_t d;
+
+		page = (void *)__get_free_page(GFP_KERNEL);
+		if (page == NULL)
+			goto err;
+
+		d = dma_map_single(priv->dev, page, PAGE_SIZE, DMA_FROM_DEVICE);
+		if (unlikely(dma_mapping_error(priv->dev, d))) {
+			free_page((unsigned long)page);
+			goto err;
+		}
+
+		/*
+		 * The hardware enforces a sub-2K maximum packet size, so we
+		 * put two buffers on every hardware page.
+		 */
+		ftmac100_rxdes_set_va(rxdes, page);
+		ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_dma_addr(rxdes, d);
+		ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);
+
+		ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
+		ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rxdes_set_dma_own(rxdes + 1);
+	}
+
+	/* initialize TX ring */
+
+	ftmac100_txdes_set_end_of_ring(&priv->descs->txdes[TX_QUEUE_ENTRIES - 1]);
+	return 0;
+
+err:
+	ftmac100_free_buffers(priv);
+	return -ENOMEM;
+}
+
+/******************************************************************************
+ * struct mii_if_info functions
+ *****************************************************************************/
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIRD;
+
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
+			return phycr & FTMAC100_PHYCR_MIIRDATA;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio read timed out\n");
+	return 0;
+}
+
+static void ftmac100_mdio_write(struct net_device *netdev, int phy_id, int reg,
+				int data)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIWR;
+
+	data = FTMAC100_PHYWDATA_MIIWDATA(data);
+
+	iowrite32(data, priv->base + FTMAC100_OFFSET_PHYWDATA);
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIWR) == 0)
+			return;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio write timed out\n");
+}
+
+/******************************************************************************
+ * struct ethtool_ops functions
+ *****************************************************************************/
+static void ftmac100_get_drvinfo(struct net_device *netdev,
+				 struct ethtool_drvinfo *info)
+{
+	strcpy(info->driver, DRV_NAME);
+	strcpy(info->version, DRV_VERSION);
+	strcpy(info->bus_info, dev_name(&netdev->dev));
+}
+
+static int ftmac100_get_settings(struct net_device *netdev,
+				 struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_gset(&priv->mii, cmd);
+}
+
+static int ftmac100_set_settings(struct net_device *netdev,
+				 struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_sset(&priv->mii, cmd);
+}
+
+static int ftmac100_nway_reset(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_nway_restart(&priv->mii);
+}
+
+static u32 ftmac100_get_link(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_link_ok(&priv->mii);
+}
+
+static const struct ethtool_ops ftmac100_ethtool_ops = {
+	.set_settings		= ftmac100_set_settings,
+	.get_settings		= ftmac100_get_settings,
+	.get_drvinfo		= ftmac100_get_drvinfo,
+	.nway_reset		= ftmac100_nway_reset,
+	.get_link		= ftmac100_get_link,
+};
+
+/******************************************************************************
+ * interrupt handler
+ *****************************************************************************/
+static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	if (likely(netif_running(netdev))) {
+		/* Disable interrupts for polling */
+		ftmac100_disable_all_int(priv);
+		napi_schedule(&priv->napi);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/******************************************************************************
+ * struct napi_struct functions
+ *****************************************************************************/
+static int ftmac100_poll(struct napi_struct *napi, int budget)
+{
+	struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
+	struct net_device *netdev = priv->netdev;
+	unsigned int status;
+	bool completed = true;
+	int rx = 0;
+
+	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
+
+	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
+		/*
+		 * FTMAC100_INT_RPKT_FINISH:
+		 *	RX DMA has received packets into RX buffer successfully
+		 *
+		 * FTMAC100_INT_NORXBUF:
+		 *	RX buffer unavailable
+		 */
+		bool retry;
+
+		do {
+			retry = ftmac100_rx_packet(priv, &rx);
+		} while (retry && rx < budget);
+
+		if (retry && rx == budget)
+			completed = false;
+	}
+
+	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
+		/*
+		 * FTMAC100_INT_XPKT_OK:
+		 *	packet transmitted to ethernet successfully
+		 *
+		 * FTMAC100_INT_XPKT_LOST:
+		 *	packet transmitted to ethernet lost due to late
+		 *	collision or excessive collision
+		 */
+		ftmac100_tx_complete(priv);
+	}
+
+	if (status & (FTMAC100_INT_NORXBUF | FTMAC100_INT_RPKT_LOST |
+		      FTMAC100_INT_AHB_ERR | FTMAC100_INT_PHYSTS_CHG)) {
+		if (net_ratelimit())
+			netdev_info(netdev, "[ISR] = 0x%x: %s%s%s%s\n", status,
+				    status & FTMAC100_INT_NORXBUF ? "NORXBUF " : "",
+				    status & FTMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "",
+				    status & FTMAC100_INT_AHB_ERR ? "AHB_ERR " : "",
+				    status & FTMAC100_INT_PHYSTS_CHG ? "PHYSTS_CHG" : "");
+
+		if (status & FTMAC100_INT_NORXBUF) {
+			/* RX buffer unavailable */
+			netdev->stats.rx_over_errors++;
+		}
+
+		if (status & FTMAC100_INT_RPKT_LOST) {
+			/* received packet lost due to RX FIFO full */
+			netdev->stats.rx_fifo_errors++;
+		}
+
+		if (status & FTMAC100_INT_PHYSTS_CHG) {
+			/* PHY link status change */
+			mii_check_link(&priv->mii);
+		}
+	}
+
+	if (completed) {
+		/* stop polling */
+		napi_complete(napi);
+		ftmac100_enable_all_int(priv);
+	}
+
+	return rx;
+}
+
+/******************************************************************************
+ * struct net_device_ops functions
+ *****************************************************************************/
+static int ftmac100_open(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int err;
+
+	err = ftmac100_alloc_buffers(priv);
+	if (err) {
+		netdev_err(netdev, "failed to allocate buffers\n");
+		goto err_alloc;
+	}
+
+	err = request_irq(priv->irq, ftmac100_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 = ftmac100_start_hw(priv);
+	if (err)
+		goto err_hw;
+
+	napi_enable(&priv->napi);
+	netif_start_queue(netdev);
+
+	ftmac100_enable_all_int(priv);
+	return 0;
+
+err_hw:
+	free_irq(priv->irq, netdev);
+err_irq:
+	ftmac100_free_buffers(priv);
+err_alloc:
+	return err;
+}
+
+static int ftmac100_stop(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	ftmac100_disable_all_int(priv);
+	netif_stop_queue(netdev);
+	napi_disable(&priv->napi);
+	ftmac100_stop_hw(priv);
+	free_irq(priv->irq, netdev);
+	ftmac100_free_buffers(priv);
+
+	return 0;
+}
+
+static int ftmac100_hard_start_xmit(struct sk_buff *skb,
+				    struct net_device *netdev)
+{
+	struct ftmac100 *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++;
+		dev_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++;
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	return ftmac100_xmit(priv, skb, map);
+}
+
+/* optional */
+static int ftmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr,
+			     int cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+
+	return generic_mii_ioctl(&priv->mii, data, cmd, NULL);
+}
+
+static const struct net_device_ops ftmac100_netdev_ops = {
+	.ndo_open		= ftmac100_open,
+	.ndo_stop		= ftmac100_stop,
+	.ndo_start_xmit		= ftmac100_hard_start_xmit,
+	.ndo_set_mac_address	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= ftmac100_do_ioctl,
+};
+
+/******************************************************************************
+ * struct platform_driver functions
+ *****************************************************************************/
+static int ftmac100_remove(struct platform_device *pdev)
+{
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+
+	netdev = platform_get_drvdata(pdev);
+	if (netdev == NULL)
+		return 0;
+
+	platform_set_drvdata(pdev, NULL);
+
+	priv = netdev_priv(netdev);
+
+	netif_napi_del(&priv->napi);
+	unregister_netdev(netdev);
+
+	if (priv->base != NULL)
+		iounmap(priv->base);
+
+	if (priv->res != NULL)
+		release_resource(priv->res);
+
+	free_netdev(netdev);
+	return 0;
+}
+
+static int ftmac100_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int irq;
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+	int err;
+
+	if (!pdev)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENXIO;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/* setup net_device */
+
+	netdev = alloc_etherdev(sizeof(struct ftmac100));
+	if (netdev == NULL) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	SET_NETDEV_DEV(netdev, &pdev->dev);
+	SET_ETHTOOL_OPS(netdev, &ftmac100_ethtool_ops);
+	netdev->netdev_ops = &ftmac100_netdev_ops;
+
+	platform_set_drvdata(pdev, netdev);
+
+	/* setup private data */
+
+	priv = netdev_priv(netdev);
+	priv->netdev = netdev;
+	priv->dev = &pdev->dev;
+
+	spin_lock_init(&priv->tx_lock);
+
+	/* initialize NAPI */
+	netif_napi_add(netdev, &priv->napi, ftmac100_poll, 64);
+
+	/* map io memory */
+	priv->res = request_mem_region(res->start, resource_size(res),
+				       dev_name(&pdev->dev));
+	if (priv->res == NULL) {
+		dev_err(&pdev->dev, "Could not reserve memory region\n");
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	priv->base = ioremap(res->start, res->end - res->start);
+	if (priv->base == NULL) {
+		dev_err(&pdev->dev, "Failed to ioremap ethernet registers\n");
+		err = -EIO;
+		goto err_out;
+	}
+
+	priv->irq = irq;
+
+	/* initialize struct mii_if_info */
+
+	priv->mii.phy_id	= 0;
+	priv->mii.phy_id_mask	= 0x1f;
+	priv->mii.reg_num_mask	= 0x1f;
+	priv->mii.dev		= netdev;
+	priv->mii.mdio_read	= ftmac100_mdio_read;
+	priv->mii.mdio_write	= ftmac100_mdio_write;
+
+	/* register network device */
+
+	err = register_netdev(netdev);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to register netdev\n");
+		goto err_out;
+	}
+
+	netdev_info(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base);
+
+	if (!is_valid_ether_addr(netdev->dev_addr)) {
+		random_ether_addr(netdev->dev_addr);
+		netdev_info(netdev, "generated random MAC address %pM\n",
+			    netdev->dev_addr);
+	}
+
+	return 0;
+
+err_out:
+	ftmac100_remove(pdev);
+	return err;
+}
+
+static struct platform_driver ftmac100_driver = {
+	.probe		= ftmac100_probe,
+	.remove		= ftmac100_remove,
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+/******************************************************************************
+ * initialization / finalization
+ *****************************************************************************/
+static int __init ftmac100_init(void)
+{
+	pr_info("Loading version " DRV_VERSION " ...\n");
+	return platform_driver_register(&ftmac100_driver);
+}
+
+static void __exit ftmac100_exit(void)
+{
+	platform_driver_unregister(&ftmac100_driver);
+}
+
+module_init(ftmac100_init);
+module_exit(ftmac100_exit);
+
+MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");
+MODULE_DESCRIPTION("FTMAC100 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ftmac100.h b/drivers/net/ftmac100.h
new file mode 100644
index 0000000..46a0c47
--- /dev/null
+++ b/drivers/net/ftmac100.h
@@ -0,0 +1,180 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __FTMAC100_H
+#define __FTMAC100_H
+
+#define	FTMAC100_OFFSET_ISR		0x00
+#define	FTMAC100_OFFSET_IMR		0x04
+#define	FTMAC100_OFFSET_MAC_MADR	0x08
+#define	FTMAC100_OFFSET_MAC_LADR	0x0c
+#define	FTMAC100_OFFSET_MAHT0		0x10
+#define	FTMAC100_OFFSET_MAHT1		0x14
+#define	FTMAC100_OFFSET_TXPD		0x18
+#define	FTMAC100_OFFSET_RXPD		0x1c
+#define	FTMAC100_OFFSET_TXR_BADR	0x20
+#define	FTMAC100_OFFSET_RXR_BADR	0x24
+#define	FTMAC100_OFFSET_ITC		0x28
+#define	FTMAC100_OFFSET_APTC		0x2c
+#define	FTMAC100_OFFSET_DBLAC		0x30
+#define	FTMAC100_OFFSET_MACCR		0x88
+#define	FTMAC100_OFFSET_MACSR		0x8c
+#define	FTMAC100_OFFSET_PHYCR		0x90
+#define	FTMAC100_OFFSET_PHYWDATA	0x94
+#define	FTMAC100_OFFSET_FCR		0x98
+#define	FTMAC100_OFFSET_BPR		0x9c
+#define	FTMAC100_OFFSET_TS		0xc4
+#define	FTMAC100_OFFSET_DMAFIFOS	0xc8
+#define	FTMAC100_OFFSET_TM		0xcc
+#define	FTMAC100_OFFSET_TX_MCOL_SCOL	0xd4
+#define	FTMAC100_OFFSET_RPF_AEP		0xd8
+#define	FTMAC100_OFFSET_XM_PG		0xdc
+#define	FTMAC100_OFFSET_RUNT_TLCC	0xe0
+#define	FTMAC100_OFFSET_CRCER_FTL	0xe4
+#define	FTMAC100_OFFSET_RLC_RCC		0xe8
+#define	FTMAC100_OFFSET_BROC		0xec
+#define	FTMAC100_OFFSET_MULCA		0xf0
+#define	FTMAC100_OFFSET_RP		0xf4
+#define	FTMAC100_OFFSET_XP		0xf8
+
+/*
+ * Interrupt status register & interrupt mask register
+ */
+#define	FTMAC100_INT_RPKT_FINISH	(1 << 0)
+#define	FTMAC100_INT_NORXBUF		(1 << 1)
+#define	FTMAC100_INT_XPKT_FINISH	(1 << 2)
+#define	FTMAC100_INT_NOTXBUF		(1 << 3)
+#define	FTMAC100_INT_XPKT_OK		(1 << 4)
+#define	FTMAC100_INT_XPKT_LOST		(1 << 5)
+#define	FTMAC100_INT_RPKT_SAV		(1 << 6)
+#define	FTMAC100_INT_RPKT_LOST		(1 << 7)
+#define	FTMAC100_INT_AHB_ERR		(1 << 8)
+#define	FTMAC100_INT_PHYSTS_CHG		(1 << 9)
+
+/*
+ * Interrupt timer control register
+ */
+#define FTMAC100_ITC_RXINT_CNT(x)	(((x) & 0xf) << 0)
+#define FTMAC100_ITC_RXINT_THR(x)	(((x) & 0x7) << 4)
+#define FTMAC100_ITC_RXINT_TIME_SEL	(1 << 7)
+#define FTMAC100_ITC_TXINT_CNT(x)	(((x) & 0xf) << 8)
+#define FTMAC100_ITC_TXINT_THR(x)	(((x) & 0x7) << 12)
+#define FTMAC100_ITC_TXINT_TIME_SEL	(1 << 15)
+
+/*
+ * Automatic polling timer control register
+ */
+#define	FTMAC100_APTC_RXPOLL_CNT(x)	(((x) & 0xf) << 0)
+#define	FTMAC100_APTC_RXPOLL_TIME_SEL	(1 << 4)
+#define	FTMAC100_APTC_TXPOLL_CNT(x)	(((x) & 0xf) << 8)
+#define	FTMAC100_APTC_TXPOLL_TIME_SEL	(1 << 12)
+
+/*
+ * DMA burst length and arbitration control register
+ */
+#define FTMAC100_DBLAC_INCR4_EN		(1 << 0)
+#define FTMAC100_DBLAC_INCR8_EN		(1 << 1)
+#define FTMAC100_DBLAC_INCR16_EN	(1 << 2)
+#define FTMAC100_DBLAC_RXFIFO_LTHR(x)	(((x) & 0x7) << 3)
+#define FTMAC100_DBLAC_RXFIFO_HTHR(x)	(((x) & 0x7) << 6)
+#define FTMAC100_DBLAC_RX_THR_EN	(1 << 9)
+
+/*
+ * MAC control register
+ */
+#define	FTMAC100_MACCR_XDMA_EN		(1 << 0)
+#define	FTMAC100_MACCR_RDMA_EN		(1 << 1)
+#define	FTMAC100_MACCR_SW_RST		(1 << 2)
+#define	FTMAC100_MACCR_LOOP_EN		(1 << 3)
+#define	FTMAC100_MACCR_CRC_DIS		(1 << 4)
+#define	FTMAC100_MACCR_XMT_EN		(1 << 5)
+#define	FTMAC100_MACCR_ENRX_IN_HALFTX	(1 << 6)
+#define	FTMAC100_MACCR_RCV_EN		(1 << 8)
+#define	FTMAC100_MACCR_HT_MULTI_EN	(1 << 9)
+#define	FTMAC100_MACCR_RX_RUNT		(1 << 10)
+#define	FTMAC100_MACCR_RX_FTL		(1 << 11)
+#define	FTMAC100_MACCR_RCV_ALL		(1 << 12)
+#define	FTMAC100_MACCR_CRC_APD		(1 << 14)
+#define	FTMAC100_MACCR_FULLDUP		(1 << 15)
+#define	FTMAC100_MACCR_RX_MULTIPKT	(1 << 16)
+#define	FTMAC100_MACCR_RX_BROADPKT	(1 << 17)
+
+/*
+ * PHY control register
+ */
+#define FTMAC100_PHYCR_MIIRDATA		0xffff
+#define FTMAC100_PHYCR_PHYAD(x)		(((x) & 0x1f) << 16)
+#define FTMAC100_PHYCR_REGAD(x)		(((x) & 0x1f) << 21)
+#define FTMAC100_PHYCR_MIIRD		(1 << 26)
+#define FTMAC100_PHYCR_MIIWR		(1 << 27)
+
+/*
+ * PHY write data register
+ */
+#define FTMAC100_PHYWDATA_MIIWDATA(x)	((x) & 0xffff)
+
+/*
+ * Transmit descriptor, aligned to 16 bytes
+ */
+struct ftmac100_txdes {
+	unsigned int	txdes0;
+	unsigned int	txdes1;
+	unsigned int	txdes2;	/* TXBUF_BADR */
+	unsigned int	txdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_TXDES0_TXPKT_LATECOL	(1 << 0)
+#define	FTMAC100_TXDES0_TXPKT_EXSCOL	(1 << 1)
+#define	FTMAC100_TXDES0_TXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_TXDES1_TXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_TXDES1_LTS		(1 << 27)
+#define	FTMAC100_TXDES1_FTS		(1 << 28)
+#define	FTMAC100_TXDES1_TX2FIC		(1 << 29)
+#define	FTMAC100_TXDES1_TXIC		(1 << 30)
+#define	FTMAC100_TXDES1_EDOTR		(1 << 31)
+
+/*
+ * Receive descriptor, aligned to 16 bytes
+ */
+struct ftmac100_rxdes {
+	unsigned int	rxdes0;
+	unsigned int	rxdes1;
+	unsigned int	rxdes2;	/* RXBUF_BADR */
+	unsigned int	rxdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_RXDES0_RFL		0x7ff
+#define	FTMAC100_RXDES0_MULTICAST	(1 << 16)
+#define	FTMAC100_RXDES0_BROADCAST	(1 << 17)
+#define	FTMAC100_RXDES0_RX_ERR		(1 << 18)
+#define	FTMAC100_RXDES0_CRC_ERR		(1 << 19)
+#define	FTMAC100_RXDES0_FTL		(1 << 20)
+#define	FTMAC100_RXDES0_RUNT		(1 << 21)
+#define	FTMAC100_RXDES0_RX_ODD_NB	(1 << 22)
+#define	FTMAC100_RXDES0_LRS		(1 << 28)
+#define	FTMAC100_RXDES0_FRS		(1 << 29)
+#define	FTMAC100_RXDES0_RXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_RXDES1_RXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_RXDES1_EDORR		(1 << 31)
+
+#endif /* __FTMAC100_H */
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 74+ messages in thread

* Re: [PATCH v5] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-24 12:39       ` [PATCH v5] " Po-Yu Chuang
@ 2011-01-24 15:07         ` Eric Dumazet
  2011-01-25  2:46           ` Po-Yu Chuang
  0 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-01-24 15:07 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, Po-Yu Chuang

Le lundi 24 janvier 2011 à 20:39 +0800, Po-Yu Chuang a écrit :
> From: Po-Yu Chuang <ratbert@faraday-tech.com>


> +static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
> +			 dma_addr_t map)
> +{
> +	struct net_device *netdev = priv->netdev;
> +	struct ftmac100_txdes *txdes;
> +	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
> +
> +	txdes = ftmac100_current_txdes(priv);
> +	ftmac100_tx_pointer_advance(priv);
> +
> +	/* setup TX descriptor */
> +
> +	spin_lock(&priv->tx_lock);
> +	ftmac100_txdes_set_skb(txdes, skb);
> +	ftmac100_txdes_set_dma_addr(txdes, map);
> +
> +	ftmac100_txdes_set_first_segment(txdes);
> +	ftmac100_txdes_set_last_segment(txdes);
> +	ftmac100_txdes_set_txint(txdes);
> +	ftmac100_txdes_set_buffer_size(txdes, len);
> +
> +	priv->tx_pending++;
> +	if (priv->tx_pending == TX_QUEUE_ENTRIES) {
> +		if (net_ratelimit())
> +			netdev_info(netdev, "tx queue full\n");

Hmm, I guess you didnt tested your driver with a pktgen flood ;)

This 'netdev_info(netdev, "tx queue full\n");' is not necessary, since
its a pretty normal condition for a driver (to fill its TX ring buffer)

> +
> +		netif_stop_queue(netdev);
> +	}
> +
> +	/* start transmit */
> +	ftmac100_txdes_set_dma_own(txdes);
> +	spin_unlock(&priv->tx_lock);
> +
> +	ftmac100_txdma_start_polling(priv);
> +
> +	return NETDEV_TX_OK;
> +}



^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-24  8:26         ` Po-Yu Chuang
@ 2011-01-24 20:22           ` Michał Mirosław
  2011-01-25  2:46             ` Po-Yu Chuang
  0 siblings, 1 reply; 74+ messages in thread
From: Michał Mirosław @ 2011-01-24 20:22 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, joe, dilinger,
	Po-Yu Chuang

W dniu 24 stycznia 2011 09:26 użytkownik Po-Yu Chuang
<ratbert.chuang@gmail.com> napisał:
> 2011/1/21 Michał Mirosław <mirqus@gmail.com>:
>> 2011/1/21 Po-Yu Chuang <ratbert.chuang@gmail.com>:
>>> +static void ftmac100_free_buffers(struct ftmac100 *priv)
>>> +{
>>> +       int i;
>>> +
>>> +       for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
>>> +               struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
>>> +               dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);
>>> +               void *page = ftmac100_rxdes_get_va(rxdes);
>>> +
>>> +               if (d)
>>> +                       dma_unmap_single(priv->dev, d, PAGE_SIZE,
>>> +                                        DMA_FROM_DEVICE);
>>> +
>>> +               if (page != NULL)
>>> +                       free_page((unsigned long)page);
>>> +       }
>>> +
>> [...]
>>
>>> +static int ftmac100_alloc_buffers(struct ftmac100 *priv)
>>> +{
>>> +       int i;
>>> +
>>> +       priv->descs = dma_alloc_coherent(priv->dev,
>>> +                                        sizeof(struct ftmac100_descs),
>>> +                                        &priv->descs_dma_addr,
>>> +                                        GFP_KERNEL | GFP_DMA);
>>> +       if (priv->descs == NULL)
>>> +               return -ENOMEM;
>>> +
>>> +       memset(priv->descs, 0, sizeof(struct ftmac100_descs));
>>> +
>>> +       /* initialize RX ring */
>>> +
>>> +       ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
>>> +
>>> +       for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {
>>> +               struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
>>> +               void *page;
>>> +               dma_addr_t d;
>>> +
>>> +               page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
>>> +               if (page == NULL)
>>> +                       goto err;
>>> +
>>> +               d = dma_map_single(priv->dev, page, PAGE_SIZE, DMA_FROM_DEVICE);
>>> +               if (unlikely(dma_mapping_error(priv->dev, d))) {
>>> +                       free_page((unsigned long)page);
>>> +                       goto err;
>>> +               }
>>> +
>>> +               /*
>>> +                * The hardware enforces a sub-2K maximum packet size, so we
>>> +                * put two buffers on every hardware page.
>>> +                */
>>> +               ftmac100_rxdes_set_va(rxdes, page);
>>> +               ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);
>>> +
>>> +               ftmac100_rxdes_set_dma_addr(rxdes, d);
>>> +               ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);
>>> +
>>> +               ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
>>> +               ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);
>>> +
>>> +               ftmac100_rxdes_set_dma_own(rxdes);
>>> +               ftmac100_rxdes_set_dma_own(rxdes + 1);
>>> +       }
>> [...]
>>
>> Did you test this? This looks like it will result in double free after
>> packet RX, as you are giving the same page (referenced once) to two
>> distinct RX descriptors, that may be assigned different packets.
>
> Yes, this is tested.
>
>> Since your not implementing any RX offloads, you might just allocate
>> fresh skb's with alloc_skb() and store skb pointer in rxdes3. Since
>
> rxdes3 does not store virtual address of an skb.
> It stores the address of the buffer allocated while open() and freed
> only when stop().
> The data in that buffer will be memcpy()ed to an skb allocated in
> ftmac100_rx_packet().
> No double free happens.

Ah, I blindly assumed that you're just appending the buffers to the
skb (using skb_fill_page_desc() and friends). Since you have to mark
descriptors for the device anyway, it might be faster to allocate new
skbs and map those as rx buffers (changing the descriptor's buffer
address after every RX) instead of keeping static buffer and copying
every time. (For small packets it wastes lot of memory, though - so
the right choice depends on the expected workload.)

Best Regards,
Michał Mirosław

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v5] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-24 15:07         ` Eric Dumazet
@ 2011-01-25  2:46           ` Po-Yu Chuang
  0 siblings, 0 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-25  2:46 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, Po-Yu Chuang

Dear Eric,

On Mon, Jan 24, 2011 at 11:07 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le lundi 24 janvier 2011 à 20:39 +0800, Po-Yu Chuang a écrit :
>> From: Po-Yu Chuang <ratbert@faraday-tech.com>
>> +
>> +     priv->tx_pending++;
>> +     if (priv->tx_pending == TX_QUEUE_ENTRIES) {
>> +             if (net_ratelimit())
>> +                     netdev_info(netdev, "tx queue full\n");
>
> Hmm, I guess you didnt tested your driver with a pktgen flood ;)
>
> This 'netdev_info(netdev, "tx queue full\n");' is not necessary, since
> its a pretty normal condition for a driver (to fill its TX ring buffer)

Errr... No, I didn't test it with pktgen. I tested it with normal NFS usages.
I'll try it.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-24 20:22           ` Michał Mirosław
@ 2011-01-25  2:46             ` Po-Yu Chuang
  2011-02-01  3:56               ` Po-Yu Chuang
  0 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-01-25  2:46 UTC (permalink / raw)
  To: Michał Mirosław
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, joe, dilinger,
	Po-Yu Chuang

Dear Michał,

2011/1/25 Michał Mirosław <mirqus@gmail.com>:
> W dniu 24 stycznia 2011 09:26 użytkownik Po-Yu Chuang
> <ratbert.chuang@gmail.com> napisał:
>> 2011/1/21 Michał Mirosław <mirqus@gmail.com>:
>>> Did you test this? This looks like it will result in double free after
>>> packet RX, as you are giving the same page (referenced once) to two
>>> distinct RX descriptors, that may be assigned different packets.
>>
>> Yes, this is tested.
>>
>>> Since your not implementing any RX offloads, you might just allocate
>>> fresh skb's with alloc_skb() and store skb pointer in rxdes3. Since
>>
>> rxdes3 does not store virtual address of an skb.
>> It stores the address of the buffer allocated while open() and freed
>> only when stop().
>> The data in that buffer will be memcpy()ed to an skb allocated in
>> ftmac100_rx_packet().
>> No double free happens.
>
> Ah, I blindly assumed that you're just appending the buffers to the
> skb (using skb_fill_page_desc() and friends). Since you have to mark
> descriptors for the device anyway, it might be faster to allocate new
> skbs and map those as rx buffers (changing the descriptor's buffer
> address after every RX) instead of keeping static buffer and copying
> every time. (For small packets it wastes lot of memory, though - so
> the right choice depends on the expected workload.)

The reason I chose to use memcpy rx buffer to skb is that I didn't know
how to deal with multi-segment packets if I preallocated skb for each
rx descriptor. This function seems to be what I need.
Let me rework this. Thanks.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-01-25  2:46             ` Po-Yu Chuang
@ 2011-02-01  3:56               ` Po-Yu Chuang
  2011-02-01  4:35                 ` David Miller
  0 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-02-01  3:56 UTC (permalink / raw)
  To: Michał Mirosław
  Cc: netdev, linux-kernel, bhutchings, eric.dumazet, joe, dilinger,
	Po-Yu Chuang

Dear Michał

2011/1/25 Po-Yu Chuang <ratbert.chuang@gmail.com>:
> 2011/1/25 Michał Mirosław <mirqus@gmail.com>:
>>
>> Ah, I blindly assumed that you're just appending the buffers to the
>> skb (using skb_fill_page_desc() and friends). Since you have to mark
>> descriptors for the device anyway, it might be faster to allocate new
>> skbs and map those as rx buffers (changing the descriptor's buffer
>> address after every RX) instead of keeping static buffer and copying
>> every time. (For small packets it wastes lot of memory, though - so
>> the right choice depends on the expected workload.)
>
> The reason I chose to use memcpy rx buffer to skb is that I didn't know
> how to deal with multi-segment packets if I preallocated skb for each
> rx descriptor. This function seems to be what I need.
> Let me rework this. Thanks.

After looking at some drivers using skb_fill_page_desc(), I am still
confused.

It seems that this function is mainly for jumbo packet and the
beginning of data of received packet should still be in skb->data.

Scheme 1)
If I simply allocate a page for each rx ring entry, I still need to allocate
an skb and copy at least packet header in first page to skb->data. Then
add the page of rest of payload to skb by skb_fill_page_desc().

Scheme 2)
If I simply allocate an skb for each rx ring entry, zero copy can be easily
achieved if the packet contains only one segment (one rx ring entry).
However, if the received packet has more than one segment, I guess I
might need to allocate a bigger skb and copy data of all segments
(skb->data) to the new skb?


The problem is, in most cases, the received packets are one-segment only.
(TBH, I don't know when will multi-segment packets appear.)

If all packets are one-segment, I can just allocate an skb for each rx ring
enry and achieve zero-copy. However, since there might be multi-segment
packets, I need to deal with them.

How can I do?

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-01  3:56               ` Po-Yu Chuang
@ 2011-02-01  4:35                 ` David Miller
  2011-02-24  7:27                   ` Po-Yu Chuang
  0 siblings, 1 reply; 74+ messages in thread
From: David Miller @ 2011-02-01  4:35 UTC (permalink / raw)
  To: ratbert.chuang
  Cc: mirqus, netdev, linux-kernel, bhutchings, eric.dumazet, joe,
	dilinger, ratbert

From: Po-Yu Chuang <ratbert.chuang@gmail.com>
Date: Tue, 1 Feb 2011 11:56:16 +0800

> If I simply allocate a page for each rx ring entry, I still need to allocate
> an skb and copy at least packet header in first page to skb->data. Then
> add the page of rest of payload to skb by skb_fill_page_desc().

You should attach the pages, the use __pskb_pull_tail() to bring in the
headers to the linear skb->data area.

See drivers/net/niu.c:niu_process_rx_pkt().

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-01  4:35                 ` David Miller
@ 2011-02-24  7:27                   ` Po-Yu Chuang
  2011-02-24  7:51                     ` David Miller
  0 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-02-24  7:27 UTC (permalink / raw)
  To: David Miller
  Cc: mirqus, netdev, linux-kernel, bhutchings, eric.dumazet, joe,
	dilinger, ratbert

Hi David,

On Tue, Feb 1, 2011 at 12:35 PM, David Miller <davem@davemloft.net> wrote:
> From: Po-Yu Chuang <ratbert.chuang@gmail.com>
> Date: Tue, 1 Feb 2011 11:56:16 +0800
>
>> If I simply allocate a page for each rx ring entry, I still need to allocate
>> an skb and copy at least packet header in first page to skb->data. Then
>> add the page of rest of payload to skb by skb_fill_page_desc().
>
> You should attach the pages, the use __pskb_pull_tail() to bring in the
> headers to the linear skb->data area.
>
> See drivers/net/niu.c:niu_process_rx_pkt().

I tried two ways to implement zero-copy.
One is to preallocate skb big enough for any rx packet and use the skb
as rx buffer.
The other is use page as rx buffer, use skb_fill_page_desc() to add a
data page to
skb and then pull only header to skb by __pskb_pull_tail() as you suggested.

Two implementations are slower than the original memcpy version.
(benchmarked with iperf)

I guess the problem is because a HW restriction that the rx buffer must be
64 bits aligned. Since I cannot make rx buffer starts at offset 2 bytes, the
IP header, TCP header and data are not 4 bytes aligned. The performance
drops drastically.

Therefore, I will submit later a v6 which is still using memcpy().

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-24  7:27                   ` Po-Yu Chuang
@ 2011-02-24  7:51                     ` David Miller
  2011-02-24  8:07                       ` Po-Yu Chuang
  0 siblings, 1 reply; 74+ messages in thread
From: David Miller @ 2011-02-24  7:51 UTC (permalink / raw)
  To: ratbert.chuang
  Cc: mirqus, netdev, linux-kernel, bhutchings, eric.dumazet, joe,
	dilinger, ratbert

From: Po-Yu Chuang <ratbert.chuang@gmail.com>
Date: Thu, 24 Feb 2011 15:27:55 +0800

> I guess the problem is because a HW restriction that the rx buffer must be
> 64 bits aligned. Since I cannot make rx buffer starts at offset 2 bytes, the
> IP header, TCP header and data are not 4 bytes aligned. The performance
> drops drastically.

I cannot believe that after 20 years of commodity ethernet networking
chips were first designed, people are still designing hardware that
doesn't do this right.

Just emit garbage bytes into the sub-word alignment padding if the chip
wants to word align it's DMA writes.

Even the 15 year old Dec Tulip chips do this properly.


^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-24  7:51                     ` David Miller
@ 2011-02-24  8:07                       ` Po-Yu Chuang
  2011-02-24  8:22                         ` Eric Dumazet
  2011-02-24 18:43                         ` [PATCH v4] " David Miller
  0 siblings, 2 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-02-24  8:07 UTC (permalink / raw)
  To: David Miller
  Cc: mirqus, netdev, linux-kernel, bhutchings, eric.dumazet, joe,
	dilinger, ratbert

Hi David,

On Thu, Feb 24, 2011 at 3:51 PM, David Miller <davem@davemloft.net> wrote:
> From: Po-Yu Chuang <ratbert.chuang@gmail.com>
> Date: Thu, 24 Feb 2011 15:27:55 +0800
>
>> I guess the problem is because a HW restriction that the rx buffer must be
>> 64 bits aligned. Since I cannot make rx buffer starts at offset 2 bytes, the
>> IP header, TCP header and data are not 4 bytes aligned. The performance
>> drops drastically.
>
> I cannot believe that after 20 years of commodity ethernet networking
> chips were first designed, people are still designing hardware that
> doesn't do this right.

Ha ha...
Well, this restriction was removed in the later IPs of our company. :-p

>
> Just emit garbage bytes into the sub-word alignment padding if the chip
> wants to word align it's DMA writes.

Not sure what do you mean. The problem is that HW does not accept a
base address of RX buffer which is not 8 bytes aligned.

> Even the 15 year old Dec Tulip chips do this properly.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-24  8:07                       ` Po-Yu Chuang
@ 2011-02-24  8:22                         ` Eric Dumazet
  2011-02-24  9:29                           ` [PATCH ref0] " Po-Yu Chuang
  2011-02-24 18:43                         ` [PATCH v4] " David Miller
  1 sibling, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-02-24  8:22 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: David Miller, mirqus, netdev, linux-kernel, bhutchings, joe,
	dilinger, ratbert

Le jeudi 24 février 2011 à 16:07 +0800, Po-Yu Chuang a écrit :
> Hi David,
> 
> On Thu, Feb 24, 2011 at 3:51 PM, David Miller <davem@davemloft.net> wrote:
> > From: Po-Yu Chuang <ratbert.chuang@gmail.com>
> > Date: Thu, 24 Feb 2011 15:27:55 +0800
> >
> >> I guess the problem is because a HW restriction that the rx buffer must be
> >> 64 bits aligned. Since I cannot make rx buffer starts at offset 2 bytes, the
> >> IP header, TCP header and data are not 4 bytes aligned. The performance
> >> drops drastically.
> >
> > I cannot believe that after 20 years of commodity ethernet networking
> > chips were first designed, people are still designing hardware that
> > doesn't do this right.
> 
> Ha ha...
> Well, this restriction was removed in the later IPs of our company. :-p
> 
> >
> > Just emit garbage bytes into the sub-word alignment padding if the chip
> > wants to word align it's DMA writes.
> 
> Not sure what do you mean. The problem is that HW does not accept a
> base address of RX buffer which is not 8 bytes aligned.

I still dont understand the problem, maybe you should post your work as
RFC

Why not using for the first part the skb buffer (eventually copy the
first 128 bytes to get aligned IP/TCP header), and add frags for
following parts ?




^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-24  8:22                         ` Eric Dumazet
@ 2011-02-24  9:29                           ` Po-Yu Chuang
  2011-02-24 17:39                             ` Eric Dumazet
  2011-02-25  9:57                             ` [PATCH v6] " Po-Yu Chuang
  0 siblings, 2 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-02-24  9:29 UTC (permalink / raw)
  To: netdev
  Cc: linux-kernel, bhutchings, eric.dumazet, joe, dilinger, mirqus,
	davem, Po-Yu Chuang

From: Po-Yu Chuang <ratbert@faraday-tech.com>

*** NOTE ***
This patch is only for reference. Its performance is worse than v5.
Please do not apply this.

FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII.  This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.

Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts

v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix

v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()

v5:
use dev_kfree_skb() in ftmac100_tx_complete_packet()
cpu_to_le32/le32_to_cpu usage fix
drop GFP_DMA

ref0:
cpu_to_le32/le32_to_cpu usage fix
remove "tx queue full" message
reduce critical section protected by tx_lock
add check of MAX_PKT_SIZE and RX_BUF_SIZE
add __exit to ftmac100_remove()
zero copy - use skb_fill_page_desc() and __pskb_pull_tail().

 drivers/net/Kconfig    |    9 +
 drivers/net/Makefile   |    1 +
 drivers/net/ftmac100.c | 1196 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/ftmac100.h |  180 ++++++++
 4 files changed, 1386 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/ftmac100.c
 create mode 100644 drivers/net/ftmac100.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 4f1755b..6b12274 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2014,6 +2014,15 @@ config BCM63XX_ENET
 	  This driver supports the ethernet MACs in the Broadcom 63xx
 	  MIPS chipset family (BCM63XX).
 
+config FTMAC100
+	tristate "Faraday FTMAC100 10/100 Ethernet support"
+	depends on ARM
+	select MII
+	help
+	  This driver supports the FTMAC100 10/100 Ethernet controller
+	  from Faraday. It is used on Faraday A320, Andes AG101 and some
+	  other ARM/NDS32 SoC's.
+
 source "drivers/net/fs_enet/Kconfig"
 
 source "drivers/net/octeon/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..7c21711 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_FORCEDETH) += forcedeth.o
 obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
 obj-$(CONFIG_AX88796) += ax88796.o
 obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
+obj-$(CONFIG_FTMAC100) += ftmac100.o
 
 obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
new file mode 100644
index 0000000..eaa6544
--- /dev/null
+++ b/drivers/net/ftmac100.c
@@ -0,0 +1,1196 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#include "ftmac100.h"
+
+#define DRV_NAME	"ftmac100"
+#define DRV_VERSION	"0.2"
+
+#define RX_QUEUE_ENTRIES	128	/* must be power of 2 */
+#define TX_QUEUE_ENTRIES	16	/* must be power of 2 */
+
+#define MAX_PKT_SIZE		1518
+#define RX_BUF_SIZE		2044	/* must be smaller than 0x7ff */
+
+#if MAX_PKT_SIZE > 0x7ff
+#error invalid MAX_PKT_SIZE
+#endif
+
+#if RX_BUF_SIZE > 0x7ff || RX_BUF_SIZE > PAGE_SIZE
+#error invalid RX_BUF_SIZE
+#endif
+
+/******************************************************************************
+ * private data
+ *****************************************************************************/
+struct ftmac100_descs {
+	struct ftmac100_rxdes rxdes[RX_QUEUE_ENTRIES];
+	struct ftmac100_txdes txdes[TX_QUEUE_ENTRIES];
+};
+
+struct ftmac100 {
+	struct resource *res;
+	void __iomem *base;
+	int irq;
+
+	struct ftmac100_descs *descs;
+	dma_addr_t descs_dma_addr;
+
+	unsigned int rx_pointer;
+	unsigned int tx_clean_pointer;
+	unsigned int tx_pointer;
+	unsigned int tx_pending;
+
+	spinlock_t tx_lock;
+
+	struct net_device *netdev;
+	struct device *dev;
+	struct napi_struct napi;
+
+	struct mii_if_info mii;
+};
+
+static int ftmac100_alloc_rx_page(struct ftmac100 *priv, struct ftmac100_rxdes *rxdes);
+
+/******************************************************************************
+ * internal functions (hardware register access)
+ *****************************************************************************/
+#define INT_MASK_ALL_ENABLED	(FTMAC100_INT_RPKT_FINISH	| \
+				 FTMAC100_INT_NORXBUF		| \
+				 FTMAC100_INT_XPKT_OK		| \
+				 FTMAC100_INT_XPKT_LOST		| \
+				 FTMAC100_INT_RPKT_LOST		| \
+				 FTMAC100_INT_AHB_ERR		| \
+				 FTMAC100_INT_PHYSTS_CHG)
+
+#define INT_MASK_ALL_DISABLED	0
+
+static void ftmac100_enable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_disable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(INT_MASK_ALL_DISABLED, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_set_rx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
+}
+
+static void ftmac100_set_tx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_TXR_BADR);
+}
+
+static void ftmac100_txdma_start_polling(struct ftmac100 *priv)
+{
+	iowrite32(1, priv->base + FTMAC100_OFFSET_TXPD);
+}
+
+static int ftmac100_reset(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	int i;
+
+	/* NOTE: reset clears all registers */
+	iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
+
+	for (i = 0; i < 5; i++) {
+		unsigned int maccr;
+
+		maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
+		if (!(maccr & FTMAC100_MACCR_SW_RST)) {
+			/*
+			 * FTMAC100_MACCR_SW_RST cleared does not indicate
+			 * that hardware reset completed (what the f*ck).
+			 * We still need to wait for a while.
+			 */
+			usleep_range(500, 1000);
+			return 0;
+		}
+
+		usleep_range(1000, 10000);
+	}
+
+	netdev_err(netdev, "software reset failed\n");
+	return -EIO;
+}
+
+static void ftmac100_set_mac(struct ftmac100 *priv, const unsigned char *mac)
+{
+	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 + FTMAC100_OFFSET_MAC_MADR);
+	iowrite32(laddr, priv->base + FTMAC100_OFFSET_MAC_LADR);
+}
+
+#define MACCR_ENABLE_ALL	(FTMAC100_MACCR_XMT_EN	| \
+				 FTMAC100_MACCR_RCV_EN	| \
+				 FTMAC100_MACCR_XDMA_EN	| \
+				 FTMAC100_MACCR_RDMA_EN	| \
+				 FTMAC100_MACCR_CRC_APD	| \
+				 FTMAC100_MACCR_FULLDUP	| \
+				 FTMAC100_MACCR_RX_RUNT	| \
+				 FTMAC100_MACCR_RX_BROADPKT)
+
+static int ftmac100_start_hw(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+
+	if (ftmac100_reset(priv))
+		return -EIO;
+
+	/* setup ring buffer base registers */
+	ftmac100_set_rx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, rxdes));
+	ftmac100_set_tx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, txdes));
+
+	iowrite32(FTMAC100_APTC_RXPOLL_CNT(1), priv->base + FTMAC100_OFFSET_APTC);
+
+	ftmac100_set_mac(priv, netdev->dev_addr);
+
+	iowrite32(MACCR_ENABLE_ALL, priv->base + FTMAC100_OFFSET_MACCR);
+	return 0;
+}
+
+static void ftmac100_stop_hw(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_MACCR);
+}
+
+/******************************************************************************
+ * internal functions (receive descriptor)
+ *****************************************************************************/
+static bool ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FRS);
+}
+
+static bool ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_LRS);
+}
+
+static bool ftmac100_rxdes_owned_by_dma(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static void ftmac100_rxdes_set_dma_own(struct ftmac100_rxdes *rxdes)
+{
+	/* clear status bits */
+	rxdes->rxdes0 = cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static bool ftmac100_rxdes_rx_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ERR);
+}
+
+static bool ftmac100_rxdes_crc_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_CRC_ERR);
+}
+
+static bool ftmac100_rxdes_frame_too_long(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FTL);
+}
+
+static bool ftmac100_rxdes_runt(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RUNT);
+}
+
+static bool ftmac100_rxdes_odd_nibble(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ODD_NB);
+}
+
+static unsigned int ftmac100_rxdes_frame_length(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_RFL;
+}
+
+static bool ftmac100_rxdes_multicast(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_MULTICAST);
+}
+
+static void ftmac100_rxdes_set_buffer_size(struct ftmac100_rxdes *rxdes,
+					   unsigned int size)
+{
+	rxdes->rxdes1 &= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_RXBUF_SIZE(size));
+}
+
+static void ftmac100_rxdes_set_end_of_ring(struct ftmac100_rxdes *rxdes)
+{
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+}
+
+static void ftmac100_rxdes_set_dma_addr(struct ftmac100_rxdes *rxdes,
+					dma_addr_t addr)
+{
+	rxdes->rxdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_rxdes_get_dma_addr(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes2);
+}
+
+/*
+ * rxdes3 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 ftmac100_rxdes_set_page(struct ftmac100_rxdes *rxdes, struct page *page)
+{
+	rxdes->rxdes3 = (unsigned int)page;
+}
+
+static struct page *ftmac100_rxdes_get_page(struct ftmac100_rxdes *rxdes)
+{
+	return (struct page *)rxdes->rxdes3;
+}
+
+/******************************************************************************
+ * internal functions (receive)
+ *****************************************************************************/
+static int ftmac100_next_rx_pointer(int pointer)
+{
+	return (pointer + 1) & (RX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_rx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->rx_pointer = ftmac100_next_rx_pointer(priv->rx_pointer);
+}
+
+static struct ftmac100_rxdes *ftmac100_current_rxdes(struct ftmac100 *priv)
+{
+	return &priv->descs->rxdes[priv->rx_pointer];
+}
+
+static struct ftmac100_rxdes *
+ftmac100_rx_locate_first_segment(struct ftmac100 *priv)
+{
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+
+	while (!ftmac100_rxdes_owned_by_dma(rxdes)) {
+		if (ftmac100_rxdes_first_segment(rxdes))
+			return rxdes;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	}
+
+	return NULL;
+}
+
+static bool ftmac100_rx_packet_error(struct ftmac100 *priv,
+				     struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	bool error = false;
+
+	if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx err\n");
+
+		netdev->stats.rx_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_crc_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx crc err\n");
+
+		netdev->stats.rx_crc_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx frame too long\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_runt(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx runt\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_odd_nibble(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx odd nibble\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	}
+
+	return error;
+}
+
+static void ftmac100_rx_drop_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+	bool done = false;
+
+	if (net_ratelimit())
+		netdev_dbg(netdev, "drop packet %p\n", rxdes);
+
+	do {
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = true;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && !ftmac100_rxdes_owned_by_dma(rxdes));
+
+	netdev->stats.rx_dropped++;
+}
+
+static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes;
+	struct sk_buff *skb;
+	struct page *page;
+	dma_addr_t map;
+	int length;
+
+	rxdes = ftmac100_rx_locate_first_segment(priv);
+	if (!rxdes)
+		return false;
+
+	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	/*
+	 * It is impossible to get multi-segment packets
+	 * because we always provide big enough receive buffers.
+	 */
+	if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))
+		BUG();
+
+	/* start processing */
+	skb = netdev_alloc_skb_ip_align(netdev, ETH_HLEN);
+	if (unlikely(!skb)) {
+		if (net_ratelimit())
+			netdev_err(netdev, "rx skb alloc failed\n");
+
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	if (unlikely(ftmac100_rxdes_multicast(rxdes)))
+		netdev->stats.multicast++;
+
+	map = ftmac100_rxdes_get_dma_addr(rxdes);
+	dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
+
+	length = ftmac100_rxdes_frame_length(rxdes);
+	page = ftmac100_rxdes_get_page(rxdes);
+	skb_fill_page_desc(skb, 0, page, 0, length);
+	skb->len += length;
+	skb->data_len += length;
+	skb->truesize += length;
+	__pskb_pull_tail(skb, ETH_HLEN);
+
+	ftmac100_alloc_rx_page(priv, rxdes);
+
+	ftmac100_rx_pointer_advance(priv);
+
+	skb->protocol = eth_type_trans(skb, netdev);
+
+	netdev->stats.rx_packets++;
+	netdev->stats.rx_bytes += skb->len;
+
+	/* push packet to protocol stack */
+	netif_receive_skb(skb);
+
+	(*processed)++;
+	return true;
+}
+
+/******************************************************************************
+ * internal functions (transmit descriptor)
+ *****************************************************************************/
+static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
+{
+	/* clear all except end of ring bit */
+	txdes->txdes0 = 0;
+	txdes->txdes1 &= cpu_to_le32(FTMAC100_TXDES1_EDOTR);
+	txdes->txdes2 = 0;
+	txdes->txdes3 = 0;
+}
+
+static bool ftmac100_txdes_owned_by_dma(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
+{
+	/*
+	 * Make sure dma own bit will not be set before any other
+	 * descriptor fields.
+	 */
+	wmb();
+	txdes->txdes0 |= cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static bool ftmac100_txdes_excessive_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_EXSCOL);
+}
+
+static bool ftmac100_txdes_late_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_LATECOL);
+}
+
+static void ftmac100_txdes_set_end_of_ring(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_EDOTR);
+}
+
+static void ftmac100_txdes_set_first_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_FTS);
+}
+
+static void ftmac100_txdes_set_last_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_LTS);
+}
+
+static void ftmac100_txdes_set_txint(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXIC);
+}
+
+static void ftmac100_txdes_set_buffer_size(struct ftmac100_txdes *txdes,
+					   unsigned int len)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXBUF_SIZE(len));
+}
+
+static void ftmac100_txdes_set_dma_addr(struct ftmac100_txdes *txdes,
+					dma_addr_t addr)
+{
+	txdes->txdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_txdes_get_dma_addr(struct ftmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes2);
+}
+
+/*
+ * txdes3 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 ftmac100_txdes_set_skb(struct ftmac100_txdes *txdes, struct sk_buff *skb)
+{
+	txdes->txdes3 = (unsigned int)skb;
+}
+
+static struct sk_buff *ftmac100_txdes_get_skb(struct ftmac100_txdes *txdes)
+{
+	return (struct sk_buff *)txdes->txdes3;
+}
+
+/******************************************************************************
+ * internal functions (transmit)
+ *****************************************************************************/
+static int ftmac100_next_tx_pointer(int pointer)
+{
+	return (pointer + 1) & (TX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_tx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_pointer = ftmac100_next_tx_pointer(priv->tx_pointer);
+}
+
+static void ftmac100_tx_clean_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_clean_pointer = ftmac100_next_tx_pointer(priv->tx_clean_pointer);
+}
+
+static struct ftmac100_txdes *ftmac100_current_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_pointer];
+}
+
+static struct ftmac100_txdes *ftmac100_current_clean_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_clean_pointer];
+}
+
+static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	struct sk_buff *skb;
+	dma_addr_t map;
+
+	if (priv->tx_pending == 0)
+		return false;
+
+	txdes = ftmac100_current_clean_txdes(priv);
+
+	if (ftmac100_txdes_owned_by_dma(txdes))
+		return false;
+
+	skb = ftmac100_txdes_get_skb(txdes);
+	map = ftmac100_txdes_get_dma_addr(txdes);
+
+	if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
+		     ftmac100_txdes_late_collision(txdes))) {
+		/*
+		 * packet transmitted to ethernet lost due to late collision
+		 * or excessive collision
+		 */
+		netdev->stats.tx_aborted_errors++;
+	} else {
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += skb->len;
+	}
+
+	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+	dev_kfree_skb(skb);
+
+	ftmac100_txdes_reset(txdes);
+
+	ftmac100_tx_clean_pointer_advance(priv);
+
+	spin_lock(&priv->tx_lock);
+	priv->tx_pending--;
+	spin_unlock(&priv->tx_lock);
+	netif_wake_queue(netdev);
+
+	return true;
+}
+
+static void ftmac100_tx_complete(struct ftmac100 *priv)
+{
+	while (ftmac100_tx_complete_packet(priv))
+		;
+}
+
+static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
+			 dma_addr_t map)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
+
+	txdes = ftmac100_current_txdes(priv);
+	ftmac100_tx_pointer_advance(priv);
+
+	/* setup TX descriptor */
+	ftmac100_txdes_set_skb(txdes, skb);
+	ftmac100_txdes_set_dma_addr(txdes, map);
+
+	ftmac100_txdes_set_first_segment(txdes);
+	ftmac100_txdes_set_last_segment(txdes);
+	ftmac100_txdes_set_txint(txdes);
+	ftmac100_txdes_set_buffer_size(txdes, len);
+
+	spin_lock(&priv->tx_lock);
+	priv->tx_pending++;
+	if (priv->tx_pending == TX_QUEUE_ENTRIES)
+		netif_stop_queue(netdev);
+
+	/* start transmit */
+	ftmac100_txdes_set_dma_own(txdes);
+	spin_unlock(&priv->tx_lock);
+
+	ftmac100_txdma_start_polling(priv);
+	return NETDEV_TX_OK;
+}
+
+/******************************************************************************
+ * internal functions (buffer)
+ *****************************************************************************/
+static int ftmac100_alloc_rx_page(struct ftmac100 *priv, struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	struct page *page;
+	dma_addr_t map;
+
+	page = alloc_page(GFP_KERNEL);
+	if (!page) {
+		if (net_ratelimit())
+			netdev_err(netdev, "failed to allocate rx page\n");
+		return -ENOMEM;
+	}
+
+	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;
+	}
+
+	ftmac100_rxdes_set_page(rxdes, page);
+	ftmac100_rxdes_set_dma_addr(rxdes, map);
+	ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
+	ftmac100_rxdes_set_dma_own(rxdes);
+	return 0;
+}
+
+static void ftmac100_free_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		struct page *page = ftmac100_rxdes_get_page(rxdes);
+		dma_addr_t map = ftmac100_rxdes_get_dma_addr(rxdes);
+
+		if (!page)
+			continue;
+
+		dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
+		__free_page(page);
+	}
+
+	for (i = 0; i < TX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_txdes *txdes = &priv->descs->txdes[i];
+		struct sk_buff *skb = ftmac100_txdes_get_skb(txdes);
+		dma_addr_t map = ftmac100_txdes_get_dma_addr(txdes);
+
+		if (!skb)
+			continue;
+
+		dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+		dev_kfree_skb(skb);
+	}
+
+	dma_free_coherent(priv->dev, sizeof(struct ftmac100_descs),
+			  priv->descs, priv->descs_dma_addr);
+}
+
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	priv->descs = dma_alloc_coherent(priv->dev, sizeof(struct ftmac100_descs),
+					 &priv->descs_dma_addr, GFP_KERNEL);
+	if (!priv->descs)
+		return -ENOMEM;
+
+	memset(priv->descs, 0, sizeof(struct ftmac100_descs));
+
+	/* initialize RX ring */
+	ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+
+		if (ftmac100_alloc_rx_page(priv, rxdes))
+			goto err;
+	}
+
+	/* initialize TX ring */
+	ftmac100_txdes_set_end_of_ring(&priv->descs->txdes[TX_QUEUE_ENTRIES - 1]);
+	return 0;
+
+err:
+	ftmac100_free_buffers(priv);
+	return -ENOMEM;
+}
+
+/******************************************************************************
+ * struct mii_if_info functions
+ *****************************************************************************/
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIRD;
+
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
+			return phycr & FTMAC100_PHYCR_MIIRDATA;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio read timed out\n");
+	return 0;
+}
+
+static void ftmac100_mdio_write(struct net_device *netdev, int phy_id, int reg,
+				int data)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIWR;
+
+	data = FTMAC100_PHYWDATA_MIIWDATA(data);
+
+	iowrite32(data, priv->base + FTMAC100_OFFSET_PHYWDATA);
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIWR) == 0)
+			return;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio write timed out\n");
+}
+
+/******************************************************************************
+ * struct ethtool_ops functions
+ *****************************************************************************/
+static void ftmac100_get_drvinfo(struct net_device *netdev,
+				 struct ethtool_drvinfo *info)
+{
+	strcpy(info->driver, DRV_NAME);
+	strcpy(info->version, DRV_VERSION);
+	strcpy(info->bus_info, dev_name(&netdev->dev));
+}
+
+static int ftmac100_get_settings(struct net_device *netdev, struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_gset(&priv->mii, cmd);
+}
+
+static int ftmac100_set_settings(struct net_device *netdev, struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_sset(&priv->mii, cmd);
+}
+
+static int ftmac100_nway_reset(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_nway_restart(&priv->mii);
+}
+
+static u32 ftmac100_get_link(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_link_ok(&priv->mii);
+}
+
+static const struct ethtool_ops ftmac100_ethtool_ops = {
+	.set_settings		= ftmac100_set_settings,
+	.get_settings		= ftmac100_get_settings,
+	.get_drvinfo		= ftmac100_get_drvinfo,
+	.nway_reset		= ftmac100_nway_reset,
+	.get_link		= ftmac100_get_link,
+};
+
+/******************************************************************************
+ * interrupt handler
+ *****************************************************************************/
+static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	if (likely(netif_running(netdev))) {
+		/* Disable interrupts for polling */
+		ftmac100_disable_all_int(priv);
+		napi_schedule(&priv->napi);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/******************************************************************************
+ * struct napi_struct functions
+ *****************************************************************************/
+static int ftmac100_poll(struct napi_struct *napi, int budget)
+{
+	struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
+	struct net_device *netdev = priv->netdev;
+	unsigned int status;
+	bool completed = true;
+	int rx = 0;
+
+	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
+
+	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
+		/*
+		 * FTMAC100_INT_RPKT_FINISH:
+		 *	RX DMA has received packets into RX buffer successfully
+		 *
+		 * FTMAC100_INT_NORXBUF:
+		 *	RX buffer unavailable
+		 */
+		bool retry;
+
+		do {
+			retry = ftmac100_rx_packet(priv, &rx);
+		} while (retry && rx < budget);
+
+		if (retry && rx == budget)
+			completed = false;
+	}
+
+	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
+		/*
+		 * FTMAC100_INT_XPKT_OK:
+		 *	packet transmitted to ethernet successfully
+		 *
+		 * FTMAC100_INT_XPKT_LOST:
+		 *	packet transmitted to ethernet lost due to late
+		 *	collision or excessive collision
+		 */
+		ftmac100_tx_complete(priv);
+	}
+
+	if (status & (FTMAC100_INT_NORXBUF | FTMAC100_INT_RPKT_LOST |
+		      FTMAC100_INT_AHB_ERR | FTMAC100_INT_PHYSTS_CHG)) {
+		if (net_ratelimit())
+			netdev_info(netdev, "[ISR] = 0x%x: %s%s%s%s\n", status,
+				    status & FTMAC100_INT_NORXBUF ? "NORXBUF " : "",
+				    status & FTMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "",
+				    status & FTMAC100_INT_AHB_ERR ? "AHB_ERR " : "",
+				    status & FTMAC100_INT_PHYSTS_CHG ? "PHYSTS_CHG" : "");
+
+		if (status & FTMAC100_INT_NORXBUF) {
+			/* RX buffer unavailable */
+			netdev->stats.rx_over_errors++;
+		}
+
+		if (status & FTMAC100_INT_RPKT_LOST) {
+			/* received packet lost due to RX FIFO full */
+			netdev->stats.rx_fifo_errors++;
+		}
+
+		if (status & FTMAC100_INT_PHYSTS_CHG) {
+			/* PHY link status change */
+			mii_check_link(&priv->mii);
+		}
+	}
+
+	if (completed) {
+		/* stop polling */
+		napi_complete(napi);
+		ftmac100_enable_all_int(priv);
+	}
+
+	return rx;
+}
+
+/******************************************************************************
+ * struct net_device_ops functions
+ *****************************************************************************/
+static int ftmac100_open(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int err;
+
+	err = ftmac100_alloc_buffers(priv);
+	if (err) {
+		netdev_err(netdev, "failed to allocate buffers\n");
+		goto err_alloc;
+	}
+
+	err = request_irq(priv->irq, ftmac100_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 = ftmac100_start_hw(priv);
+	if (err)
+		goto err_hw;
+
+	napi_enable(&priv->napi);
+	netif_start_queue(netdev);
+
+	ftmac100_enable_all_int(priv);
+
+	return 0;
+
+err_hw:
+	free_irq(priv->irq, netdev);
+err_irq:
+	ftmac100_free_buffers(priv);
+err_alloc:
+	return err;
+}
+
+static int ftmac100_stop(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	ftmac100_disable_all_int(priv);
+	netif_stop_queue(netdev);
+	napi_disable(&priv->napi);
+	ftmac100_stop_hw(priv);
+	free_irq(priv->irq, netdev);
+	ftmac100_free_buffers(priv);
+
+	return 0;
+}
+
+static int ftmac100_hard_start_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+	struct ftmac100 *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++;
+		dev_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++;
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	return ftmac100_xmit(priv, skb, map);
+}
+
+/* optional */
+static int ftmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+
+	return generic_mii_ioctl(&priv->mii, data, cmd, NULL);
+}
+
+static const struct net_device_ops ftmac100_netdev_ops = {
+	.ndo_open		= ftmac100_open,
+	.ndo_stop		= ftmac100_stop,
+	.ndo_start_xmit		= ftmac100_hard_start_xmit,
+	.ndo_set_mac_address	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= ftmac100_do_ioctl,
+};
+
+/******************************************************************************
+ * struct platform_driver functions
+ *****************************************************************************/
+static int ftmac100_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int irq;
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+	int err;
+
+	if (!pdev)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENXIO;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/* setup net_device */
+	netdev = alloc_etherdev(sizeof(*priv));
+	if (!netdev) {
+		err = -ENOMEM;
+		goto err_alloc_etherdev;
+	}
+
+	SET_NETDEV_DEV(netdev, &pdev->dev);
+	SET_ETHTOOL_OPS(netdev, &ftmac100_ethtool_ops);
+	netdev->netdev_ops = &ftmac100_netdev_ops;
+
+	platform_set_drvdata(pdev, netdev);
+
+	/* setup private data */
+	priv = netdev_priv(netdev);
+	priv->netdev = netdev;
+	priv->dev = &pdev->dev;
+
+	spin_lock_init(&priv->tx_lock);
+
+	/* initialize NAPI */
+	netif_napi_add(netdev, &priv->napi, ftmac100_poll, 64);
+
+	/* map io memory */
+	priv->res = request_mem_region(res->start, resource_size(res),
+				       dev_name(&pdev->dev));
+	if (!priv->res) {
+		dev_err(&pdev->dev, "Could not reserve memory region\n");
+		err = -ENOMEM;
+		goto err_req_mem;
+	}
+
+	priv->base = ioremap(res->start, res->end - res->start);
+	if (!priv->base) {
+		dev_err(&pdev->dev, "Failed to ioremap ethernet registers\n");
+		err = -EIO;
+		goto err_ioremap;
+	}
+
+	priv->irq = irq;
+
+	/* initialize struct mii_if_info */
+	priv->mii.phy_id	= 0;
+	priv->mii.phy_id_mask	= 0x1f;
+	priv->mii.reg_num_mask	= 0x1f;
+	priv->mii.dev		= netdev;
+	priv->mii.mdio_read	= ftmac100_mdio_read;
+	priv->mii.mdio_write	= ftmac100_mdio_write;
+
+	/* register network device */
+	err = register_netdev(netdev);
+	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);
+
+	if (!is_valid_ether_addr(netdev->dev_addr)) {
+		random_ether_addr(netdev->dev_addr);
+		netdev_info(netdev, "generated random MAC address %pM\n",
+			    netdev->dev_addr);
+	}
+
+	return 0;
+
+err_register_netdev:
+	iounmap(priv->base);
+err_ioremap:
+	release_resource(priv->res);
+err_req_mem:
+	netif_napi_del(&priv->napi);
+	platform_set_drvdata(pdev, NULL);
+	free_netdev(netdev);
+err_alloc_etherdev:
+	return err;
+}
+
+static int __exit ftmac100_remove(struct platform_device *pdev)
+{
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+
+	netdev = platform_get_drvdata(pdev);
+	priv = netdev_priv(netdev);
+
+	unregister_netdev(netdev);
+
+	iounmap(priv->base);
+	release_resource(priv->res);
+
+	netif_napi_del(&priv->napi);
+	platform_set_drvdata(pdev, NULL);
+	free_netdev(netdev);
+	return 0;
+}
+
+static struct platform_driver ftmac100_driver = {
+	.probe		= ftmac100_probe,
+	.remove		= __exit_p(ftmac100_remove),
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+/******************************************************************************
+ * initialization / finalization
+ *****************************************************************************/
+static int __init ftmac100_init(void)
+{
+	pr_info("Loading version " DRV_VERSION " ...\n");
+	return platform_driver_register(&ftmac100_driver);
+}
+
+static void __exit ftmac100_exit(void)
+{
+	platform_driver_unregister(&ftmac100_driver);
+}
+
+module_init(ftmac100_init);
+module_exit(ftmac100_exit);
+
+MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");
+MODULE_DESCRIPTION("FTMAC100 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ftmac100.h b/drivers/net/ftmac100.h
new file mode 100644
index 0000000..46a0c47
--- /dev/null
+++ b/drivers/net/ftmac100.h
@@ -0,0 +1,180 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __FTMAC100_H
+#define __FTMAC100_H
+
+#define	FTMAC100_OFFSET_ISR		0x00
+#define	FTMAC100_OFFSET_IMR		0x04
+#define	FTMAC100_OFFSET_MAC_MADR	0x08
+#define	FTMAC100_OFFSET_MAC_LADR	0x0c
+#define	FTMAC100_OFFSET_MAHT0		0x10
+#define	FTMAC100_OFFSET_MAHT1		0x14
+#define	FTMAC100_OFFSET_TXPD		0x18
+#define	FTMAC100_OFFSET_RXPD		0x1c
+#define	FTMAC100_OFFSET_TXR_BADR	0x20
+#define	FTMAC100_OFFSET_RXR_BADR	0x24
+#define	FTMAC100_OFFSET_ITC		0x28
+#define	FTMAC100_OFFSET_APTC		0x2c
+#define	FTMAC100_OFFSET_DBLAC		0x30
+#define	FTMAC100_OFFSET_MACCR		0x88
+#define	FTMAC100_OFFSET_MACSR		0x8c
+#define	FTMAC100_OFFSET_PHYCR		0x90
+#define	FTMAC100_OFFSET_PHYWDATA	0x94
+#define	FTMAC100_OFFSET_FCR		0x98
+#define	FTMAC100_OFFSET_BPR		0x9c
+#define	FTMAC100_OFFSET_TS		0xc4
+#define	FTMAC100_OFFSET_DMAFIFOS	0xc8
+#define	FTMAC100_OFFSET_TM		0xcc
+#define	FTMAC100_OFFSET_TX_MCOL_SCOL	0xd4
+#define	FTMAC100_OFFSET_RPF_AEP		0xd8
+#define	FTMAC100_OFFSET_XM_PG		0xdc
+#define	FTMAC100_OFFSET_RUNT_TLCC	0xe0
+#define	FTMAC100_OFFSET_CRCER_FTL	0xe4
+#define	FTMAC100_OFFSET_RLC_RCC		0xe8
+#define	FTMAC100_OFFSET_BROC		0xec
+#define	FTMAC100_OFFSET_MULCA		0xf0
+#define	FTMAC100_OFFSET_RP		0xf4
+#define	FTMAC100_OFFSET_XP		0xf8
+
+/*
+ * Interrupt status register & interrupt mask register
+ */
+#define	FTMAC100_INT_RPKT_FINISH	(1 << 0)
+#define	FTMAC100_INT_NORXBUF		(1 << 1)
+#define	FTMAC100_INT_XPKT_FINISH	(1 << 2)
+#define	FTMAC100_INT_NOTXBUF		(1 << 3)
+#define	FTMAC100_INT_XPKT_OK		(1 << 4)
+#define	FTMAC100_INT_XPKT_LOST		(1 << 5)
+#define	FTMAC100_INT_RPKT_SAV		(1 << 6)
+#define	FTMAC100_INT_RPKT_LOST		(1 << 7)
+#define	FTMAC100_INT_AHB_ERR		(1 << 8)
+#define	FTMAC100_INT_PHYSTS_CHG		(1 << 9)
+
+/*
+ * Interrupt timer control register
+ */
+#define FTMAC100_ITC_RXINT_CNT(x)	(((x) & 0xf) << 0)
+#define FTMAC100_ITC_RXINT_THR(x)	(((x) & 0x7) << 4)
+#define FTMAC100_ITC_RXINT_TIME_SEL	(1 << 7)
+#define FTMAC100_ITC_TXINT_CNT(x)	(((x) & 0xf) << 8)
+#define FTMAC100_ITC_TXINT_THR(x)	(((x) & 0x7) << 12)
+#define FTMAC100_ITC_TXINT_TIME_SEL	(1 << 15)
+
+/*
+ * Automatic polling timer control register
+ */
+#define	FTMAC100_APTC_RXPOLL_CNT(x)	(((x) & 0xf) << 0)
+#define	FTMAC100_APTC_RXPOLL_TIME_SEL	(1 << 4)
+#define	FTMAC100_APTC_TXPOLL_CNT(x)	(((x) & 0xf) << 8)
+#define	FTMAC100_APTC_TXPOLL_TIME_SEL	(1 << 12)
+
+/*
+ * DMA burst length and arbitration control register
+ */
+#define FTMAC100_DBLAC_INCR4_EN		(1 << 0)
+#define FTMAC100_DBLAC_INCR8_EN		(1 << 1)
+#define FTMAC100_DBLAC_INCR16_EN	(1 << 2)
+#define FTMAC100_DBLAC_RXFIFO_LTHR(x)	(((x) & 0x7) << 3)
+#define FTMAC100_DBLAC_RXFIFO_HTHR(x)	(((x) & 0x7) << 6)
+#define FTMAC100_DBLAC_RX_THR_EN	(1 << 9)
+
+/*
+ * MAC control register
+ */
+#define	FTMAC100_MACCR_XDMA_EN		(1 << 0)
+#define	FTMAC100_MACCR_RDMA_EN		(1 << 1)
+#define	FTMAC100_MACCR_SW_RST		(1 << 2)
+#define	FTMAC100_MACCR_LOOP_EN		(1 << 3)
+#define	FTMAC100_MACCR_CRC_DIS		(1 << 4)
+#define	FTMAC100_MACCR_XMT_EN		(1 << 5)
+#define	FTMAC100_MACCR_ENRX_IN_HALFTX	(1 << 6)
+#define	FTMAC100_MACCR_RCV_EN		(1 << 8)
+#define	FTMAC100_MACCR_HT_MULTI_EN	(1 << 9)
+#define	FTMAC100_MACCR_RX_RUNT		(1 << 10)
+#define	FTMAC100_MACCR_RX_FTL		(1 << 11)
+#define	FTMAC100_MACCR_RCV_ALL		(1 << 12)
+#define	FTMAC100_MACCR_CRC_APD		(1 << 14)
+#define	FTMAC100_MACCR_FULLDUP		(1 << 15)
+#define	FTMAC100_MACCR_RX_MULTIPKT	(1 << 16)
+#define	FTMAC100_MACCR_RX_BROADPKT	(1 << 17)
+
+/*
+ * PHY control register
+ */
+#define FTMAC100_PHYCR_MIIRDATA		0xffff
+#define FTMAC100_PHYCR_PHYAD(x)		(((x) & 0x1f) << 16)
+#define FTMAC100_PHYCR_REGAD(x)		(((x) & 0x1f) << 21)
+#define FTMAC100_PHYCR_MIIRD		(1 << 26)
+#define FTMAC100_PHYCR_MIIWR		(1 << 27)
+
+/*
+ * PHY write data register
+ */
+#define FTMAC100_PHYWDATA_MIIWDATA(x)	((x) & 0xffff)
+
+/*
+ * Transmit descriptor, aligned to 16 bytes
+ */
+struct ftmac100_txdes {
+	unsigned int	txdes0;
+	unsigned int	txdes1;
+	unsigned int	txdes2;	/* TXBUF_BADR */
+	unsigned int	txdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_TXDES0_TXPKT_LATECOL	(1 << 0)
+#define	FTMAC100_TXDES0_TXPKT_EXSCOL	(1 << 1)
+#define	FTMAC100_TXDES0_TXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_TXDES1_TXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_TXDES1_LTS		(1 << 27)
+#define	FTMAC100_TXDES1_FTS		(1 << 28)
+#define	FTMAC100_TXDES1_TX2FIC		(1 << 29)
+#define	FTMAC100_TXDES1_TXIC		(1 << 30)
+#define	FTMAC100_TXDES1_EDOTR		(1 << 31)
+
+/*
+ * Receive descriptor, aligned to 16 bytes
+ */
+struct ftmac100_rxdes {
+	unsigned int	rxdes0;
+	unsigned int	rxdes1;
+	unsigned int	rxdes2;	/* RXBUF_BADR */
+	unsigned int	rxdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_RXDES0_RFL		0x7ff
+#define	FTMAC100_RXDES0_MULTICAST	(1 << 16)
+#define	FTMAC100_RXDES0_BROADCAST	(1 << 17)
+#define	FTMAC100_RXDES0_RX_ERR		(1 << 18)
+#define	FTMAC100_RXDES0_CRC_ERR		(1 << 19)
+#define	FTMAC100_RXDES0_FTL		(1 << 20)
+#define	FTMAC100_RXDES0_RUNT		(1 << 21)
+#define	FTMAC100_RXDES0_RX_ODD_NB	(1 << 22)
+#define	FTMAC100_RXDES0_LRS		(1 << 28)
+#define	FTMAC100_RXDES0_FRS		(1 << 29)
+#define	FTMAC100_RXDES0_RXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_RXDES1_RXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_RXDES1_EDORR		(1 << 31)
+
+#endif /* __FTMAC100_H */
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-24  9:29                           ` [PATCH ref0] " Po-Yu Chuang
@ 2011-02-24 17:39                             ` Eric Dumazet
  2011-02-24 17:48                               ` Eric Dumazet
  2011-02-25  9:57                             ` [PATCH v6] " Po-Yu Chuang
  1 sibling, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-02-24 17:39 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Le jeudi 24 février 2011 à 17:29 +0800, Po-Yu Chuang a écrit :
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> 


> +
> +static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
> +{
> +	struct net_device *netdev = priv->netdev;
> +	struct ftmac100_rxdes *rxdes;
> +	struct sk_buff *skb;
> +	struct page *page;
> +	dma_addr_t map;
> +	int length;
> +
> +	rxdes = ftmac100_rx_locate_first_segment(priv);
> +	if (!rxdes)
> +		return false;
> +
> +	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
> +		ftmac100_rx_drop_packet(priv);
> +		return true;
> +	}
> +
> +	/*
> +	 * It is impossible to get multi-segment packets
> +	 * because we always provide big enough receive buffers.
> +	 */
> +	if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))
> +		BUG();
> +
> +	/* start processing */
> +	skb = netdev_alloc_skb_ip_align(netdev, ETH_HLEN);

Oh I see... You should allocate a bigger head (say... 128 bytes)

And copy in it up to 128 bytes of first part... this to avoid upper
stack to reallocate skb head (because IP/TCP processing need to get
their headers in skb head)

> +	if (unlikely(!skb)) {
> +		if (net_ratelimit())
> +			netdev_err(netdev, "rx skb alloc failed\n");
> +
> +		ftmac100_rx_drop_packet(priv);
> +		return true;
> +	}
> +
> +	if (unlikely(ftmac100_rxdes_multicast(rxdes)))
> +		netdev->stats.multicast++;
> +
> +	map = ftmac100_rxdes_get_dma_addr(rxdes);
> +	dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
> +
> +	length = ftmac100_rxdes_frame_length(rxdes);
> +	page = ftmac100_rxdes_get_page(rxdes);
> +	skb_fill_page_desc(skb, 0, page, 0, length);
> +	skb->len += length;
> +	skb->data_len += length;
> +	skb->truesize += length;
> +	__pskb_pull_tail(skb, ETH_HLEN);
> +
> +	ftmac100_alloc_rx_page(priv, rxdes);
> +
> +	ftmac100_rx_pointer_advance(priv);
> +
> +	skb->protocol = eth_type_trans(skb, netdev);
> +
> +	netdev->stats.rx_packets++;
> +	netdev->stats.rx_bytes += skb->len;
> +
> +	/* push packet to protocol stack */
> +	netif_receive_skb(skb);
> +
> +	(*processed)++;
> +	return true;
> +}
> +


^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-24 17:39                             ` Eric Dumazet
@ 2011-02-24 17:48                               ` Eric Dumazet
  2011-02-25  2:32                                 ` Po-Yu Chuang
  0 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-02-24 17:48 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Le jeudi 24 février 2011 à 18:39 +0100, Eric Dumazet a écrit :
> Le jeudi 24 février 2011 à 17:29 +0800, Po-Yu Chuang a écrit :
> > From: Po-Yu Chuang <ratbert@faraday-tech.com>
> > 
> 
> 
> > +
> > +static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
> > +{
> > +	struct net_device *netdev = priv->netdev;
> > +	struct ftmac100_rxdes *rxdes;
> > +	struct sk_buff *skb;
> > +	struct page *page;
> > +	dma_addr_t map;
> > +	int length;
> > +
> > +	rxdes = ftmac100_rx_locate_first_segment(priv);
> > +	if (!rxdes)
> > +		return false;
> > +
> > +	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
> > +		ftmac100_rx_drop_packet(priv);
> > +		return true;
> > +	}
> > +
> > +	/*
> > +	 * It is impossible to get multi-segment packets
> > +	 * because we always provide big enough receive buffers.
> > +	 */
> > +	if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))
> > +		BUG();
> > +
> > +	/* start processing */
> > +	skb = netdev_alloc_skb_ip_align(netdev, ETH_HLEN);
> 
> Oh I see... You should allocate a bigger head (say... 128 bytes)
> 
> And copy in it up to 128 bytes of first part... this to avoid upper
> stack to reallocate skb head (because IP/TCP processing need to get
> their headers in skb head)

Take a look at drivers/net/niu.c :

#define RX_SKB_ALLOC_SIZE   128 + NET_IP_ALIGN

static int niu_process_rx_pkt(...)
{
	...
	skb = netdev_alloc_skb(np->dev, RX_SKB_ALLOC_SIZE);
	...
	while (1) {
	 	...
		niu_rx_skb_append(skb, page, off, append_size);
	}
}




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-24  8:07                       ` Po-Yu Chuang
  2011-02-24  8:22                         ` Eric Dumazet
@ 2011-02-24 18:43                         ` David Miller
  1 sibling, 0 replies; 74+ messages in thread
From: David Miller @ 2011-02-24 18:43 UTC (permalink / raw)
  To: ratbert.chuang
  Cc: mirqus, netdev, linux-kernel, bhutchings, eric.dumazet, joe,
	dilinger, ratbert

From: Po-Yu Chuang <ratbert.chuang@gmail.com>
Date: Thu, 24 Feb 2011 16:07:48 +0800

> On Thu, Feb 24, 2011 at 3:51 PM, David Miller <davem@davemloft.net> wrote:
>> Just emit garbage bytes into the sub-word alignment padding if the chip
>> wants to word align it's DMA writes.
> 
> Not sure what do you mean. The problem is that HW does not accept a
> base address of RX buffer which is not 8 bytes aligned.

I am saying this is what hardware should do if it has such a
restriction.

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-24 17:48                               ` Eric Dumazet
@ 2011-02-25  2:32                                 ` Po-Yu Chuang
  2011-02-25  9:45                                   ` Po-Yu Chuang
  0 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-02-25  2:32 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Hi Eric,

On Fri, Feb 25, 2011 at 1:48 AM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le jeudi 24 février 2011 à 18:39 +0100, Eric Dumazet a écrit :
>> Le jeudi 24 février 2011 à 17:29 +0800, Po-Yu Chuang a écrit :
>> > From: Po-Yu Chuang <ratbert@faraday-tech.com>
>> > +
>> > +static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
>> > +{
>> > +   struct net_device *netdev = priv->netdev;
>> > +   struct ftmac100_rxdes *rxdes;
>> > +   struct sk_buff *skb;
>> > +   struct page *page;
>> > +   dma_addr_t map;
>> > +   int length;
>> > +
>> > +   rxdes = ftmac100_rx_locate_first_segment(priv);
>> > +   if (!rxdes)
>> > +           return false;
>> > +
>> > +   if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
>> > +           ftmac100_rx_drop_packet(priv);
>> > +           return true;
>> > +   }
>> > +
>> > +   /*
>> > +    * It is impossible to get multi-segment packets
>> > +    * because we always provide big enough receive buffers.
>> > +    */
>> > +   if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))
>> > +           BUG();
>> > +
>> > +   /* start processing */
>> > +   skb = netdev_alloc_skb_ip_align(netdev, ETH_HLEN);
>>
>> Oh I see... You should allocate a bigger head (say... 128 bytes)
>>
>> And copy in it up to 128 bytes of first part... this to avoid upper
>> stack to reallocate skb head (because IP/TCP processing need to get
>> their headers in skb head)
>
> Take a look at drivers/net/niu.c :
>
> #define RX_SKB_ALLOC_SIZE   128 + NET_IP_ALIGN
>
> static int niu_process_rx_pkt(...)
> {
>        ...
>        skb = netdev_alloc_skb(np->dev, RX_SKB_ALLOC_SIZE);
>        ...
>        while (1) {
>                ...
>                niu_rx_skb_append(skb, page, off, append_size);
>        }
> }

Oh I got it.

I will try this and redo the benchmarking.

Thanks,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-25  2:32                                 ` Po-Yu Chuang
@ 2011-02-25  9:45                                   ` Po-Yu Chuang
  2011-02-25 10:52                                     ` Eric Dumazet
  0 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-02-25  9:45 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Hi Eric,

On Fri, Feb 25, 2011 at 10:32 AM, Po-Yu Chuang <ratbert.chuang@gmail.com> wrote:
> On Fri, Feb 25, 2011 at 1:48 AM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
>> Le jeudi 24 février 2011 à 18:39 +0100, Eric Dumazet a écrit :
>>> Le jeudi 24 février 2011 à 17:29 +0800, Po-Yu Chuang a écrit :
>>> > From: Po-Yu Chuang <ratbert@faraday-tech.com>
>>> > +
>>> > +static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
>>> > +{
>>> > +   struct net_device *netdev = priv->netdev;
>>> > +   struct ftmac100_rxdes *rxdes;
>>> > +   struct sk_buff *skb;
>>> > +   struct page *page;
>>> > +   dma_addr_t map;
>>> > +   int length;
>>> > +
>>> > +   rxdes = ftmac100_rx_locate_first_segment(priv);
>>> > +   if (!rxdes)
>>> > +           return false;
>>> > +
>>> > +   if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
>>> > +           ftmac100_rx_drop_packet(priv);
>>> > +           return true;
>>> > +   }
>>> > +
>>> > +   /*
>>> > +    * It is impossible to get multi-segment packets
>>> > +    * because we always provide big enough receive buffers.
>>> > +    */
>>> > +   if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))
>>> > +           BUG();
>>> > +
>>> > +   /* start processing */
>>> > +   skb = netdev_alloc_skb_ip_align(netdev, ETH_HLEN);
>>>
>>> Oh I see... You should allocate a bigger head (say... 128 bytes)
>>>
>>> And copy in it up to 128 bytes of first part... this to avoid upper
>>> stack to reallocate skb head (because IP/TCP processing need to get
>>> their headers in skb head)
>>
>> Take a look at drivers/net/niu.c :
>>
>> #define RX_SKB_ALLOC_SIZE   128 + NET_IP_ALIGN
>>
>> static int niu_process_rx_pkt(...)
>> {
>>        ...
>>        skb = netdev_alloc_skb(np->dev, RX_SKB_ALLOC_SIZE);
>>        ...
>>        while (1) {
>>                ...
>>                niu_rx_skb_append(skb, page, off, append_size);
>>        }
>> }
>
> Oh I got it.
>
> I will try this and redo the benchmarking.

It's a little faster than v5 now. Thanks.
I will submit the current version later.

One more question just curious, why 128 bytes?

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v6] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-24  9:29                           ` [PATCH ref0] " Po-Yu Chuang
  2011-02-24 17:39                             ` Eric Dumazet
@ 2011-02-25  9:57                             ` Po-Yu Chuang
  2011-02-25 11:40                               ` Eric Dumazet
  2011-03-01  6:48                               ` [PATCH] " Po-Yu Chuang
  1 sibling, 2 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-02-25  9:57 UTC (permalink / raw)
  To: netdev
  Cc: linux-kernel, bhutchings, eric.dumazet, joe, dilinger, mirqus,
	davem, Po-Yu Chuang

From: Po-Yu Chuang <ratbert@faraday-tech.com>

FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII.  This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.

Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts

v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix

v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()

v5:
use dev_kfree_skb() in ftmac100_tx_complete_packet()
cpu_to_le32/le32_to_cpu usage fix
drop GFP_DMA

v6:
cpu_to_le32/le32_to_cpu usage fix
remove "tx queue full" message
reduce critical section protected by tx_lock
add check of MAX_PKT_SIZE and RX_BUF_SIZE
add __exit to ftmac100_remove()
simplify ftmac100_rx_packet()
zero copy - use skb_fill_page_desc() and __pskb_pull_tail().
pull more data to skb head to include tcp/ip header

 drivers/net/Kconfig    |    9 +
 drivers/net/Makefile   |    1 +
 drivers/net/ftmac100.c | 1198 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/ftmac100.h |  180 ++++++++
 4 files changed, 1388 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/ftmac100.c
 create mode 100644 drivers/net/ftmac100.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 4f1755b..6b12274 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2014,6 +2014,15 @@ config BCM63XX_ENET
 	  This driver supports the ethernet MACs in the Broadcom 63xx
 	  MIPS chipset family (BCM63XX).
 
+config FTMAC100
+	tristate "Faraday FTMAC100 10/100 Ethernet support"
+	depends on ARM
+	select MII
+	help
+	  This driver supports the FTMAC100 10/100 Ethernet controller
+	  from Faraday. It is used on Faraday A320, Andes AG101 and some
+	  other ARM/NDS32 SoC's.
+
 source "drivers/net/fs_enet/Kconfig"
 
 source "drivers/net/octeon/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..7c21711 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_FORCEDETH) += forcedeth.o
 obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
 obj-$(CONFIG_AX88796) += ax88796.o
 obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
+obj-$(CONFIG_FTMAC100) += ftmac100.o
 
 obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
new file mode 100644
index 0000000..63852f8
--- /dev/null
+++ b/drivers/net/ftmac100.c
@@ -0,0 +1,1198 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#include "ftmac100.h"
+
+#define DRV_NAME	"ftmac100"
+#define DRV_VERSION	"0.2"
+
+#define RX_QUEUE_ENTRIES	128	/* must be power of 2 */
+#define TX_QUEUE_ENTRIES	16	/* must be power of 2 */
+
+#define RX_SKB_ALLOC_SIZE	128
+
+#define MAX_PKT_SIZE		1518
+#define RX_BUF_SIZE		2044	/* must be smaller than 0x7ff */
+
+#if MAX_PKT_SIZE > 0x7ff
+#error invalid MAX_PKT_SIZE
+#endif
+
+#if RX_BUF_SIZE > 0x7ff || RX_BUF_SIZE > PAGE_SIZE
+#error invalid RX_BUF_SIZE
+#endif
+
+/******************************************************************************
+ * private data
+ *****************************************************************************/
+struct ftmac100_descs {
+	struct ftmac100_rxdes rxdes[RX_QUEUE_ENTRIES];
+	struct ftmac100_txdes txdes[TX_QUEUE_ENTRIES];
+};
+
+struct ftmac100 {
+	struct resource *res;
+	void __iomem *base;
+	int irq;
+
+	struct ftmac100_descs *descs;
+	dma_addr_t descs_dma_addr;
+
+	unsigned int rx_pointer;
+	unsigned int tx_clean_pointer;
+	unsigned int tx_pointer;
+	unsigned int tx_pending;
+
+	spinlock_t tx_lock;
+
+	struct net_device *netdev;
+	struct device *dev;
+	struct napi_struct napi;
+
+	struct mii_if_info mii;
+};
+
+static int ftmac100_alloc_rx_page(struct ftmac100 *priv, struct ftmac100_rxdes *rxdes);
+
+/******************************************************************************
+ * internal functions (hardware register access)
+ *****************************************************************************/
+#define INT_MASK_ALL_ENABLED	(FTMAC100_INT_RPKT_FINISH	| \
+				 FTMAC100_INT_NORXBUF		| \
+				 FTMAC100_INT_XPKT_OK		| \
+				 FTMAC100_INT_XPKT_LOST		| \
+				 FTMAC100_INT_RPKT_LOST		| \
+				 FTMAC100_INT_AHB_ERR		| \
+				 FTMAC100_INT_PHYSTS_CHG)
+
+#define INT_MASK_ALL_DISABLED	0
+
+static void ftmac100_enable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_disable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(INT_MASK_ALL_DISABLED, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_set_rx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
+}
+
+static void ftmac100_set_tx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_TXR_BADR);
+}
+
+static void ftmac100_txdma_start_polling(struct ftmac100 *priv)
+{
+	iowrite32(1, priv->base + FTMAC100_OFFSET_TXPD);
+}
+
+static int ftmac100_reset(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	int i;
+
+	/* NOTE: reset clears all registers */
+	iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
+
+	for (i = 0; i < 5; i++) {
+		unsigned int maccr;
+
+		maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
+		if (!(maccr & FTMAC100_MACCR_SW_RST)) {
+			/*
+			 * FTMAC100_MACCR_SW_RST cleared does not indicate
+			 * that hardware reset completed (what the f*ck).
+			 * We still need to wait for a while.
+			 */
+			usleep_range(500, 1000);
+			return 0;
+		}
+
+		usleep_range(1000, 10000);
+	}
+
+	netdev_err(netdev, "software reset failed\n");
+	return -EIO;
+}
+
+static void ftmac100_set_mac(struct ftmac100 *priv, const unsigned char *mac)
+{
+	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 + FTMAC100_OFFSET_MAC_MADR);
+	iowrite32(laddr, priv->base + FTMAC100_OFFSET_MAC_LADR);
+}
+
+#define MACCR_ENABLE_ALL	(FTMAC100_MACCR_XMT_EN	| \
+				 FTMAC100_MACCR_RCV_EN	| \
+				 FTMAC100_MACCR_XDMA_EN	| \
+				 FTMAC100_MACCR_RDMA_EN	| \
+				 FTMAC100_MACCR_CRC_APD	| \
+				 FTMAC100_MACCR_FULLDUP	| \
+				 FTMAC100_MACCR_RX_RUNT	| \
+				 FTMAC100_MACCR_RX_BROADPKT)
+
+static int ftmac100_start_hw(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+
+	if (ftmac100_reset(priv))
+		return -EIO;
+
+	/* setup ring buffer base registers */
+	ftmac100_set_rx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, rxdes));
+	ftmac100_set_tx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, txdes));
+
+	iowrite32(FTMAC100_APTC_RXPOLL_CNT(1), priv->base + FTMAC100_OFFSET_APTC);
+
+	ftmac100_set_mac(priv, netdev->dev_addr);
+
+	iowrite32(MACCR_ENABLE_ALL, priv->base + FTMAC100_OFFSET_MACCR);
+	return 0;
+}
+
+static void ftmac100_stop_hw(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_MACCR);
+}
+
+/******************************************************************************
+ * internal functions (receive descriptor)
+ *****************************************************************************/
+static bool ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FRS);
+}
+
+static bool ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_LRS);
+}
+
+static bool ftmac100_rxdes_owned_by_dma(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static void ftmac100_rxdes_set_dma_own(struct ftmac100_rxdes *rxdes)
+{
+	/* clear status bits */
+	rxdes->rxdes0 = cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static bool ftmac100_rxdes_rx_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ERR);
+}
+
+static bool ftmac100_rxdes_crc_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_CRC_ERR);
+}
+
+static bool ftmac100_rxdes_frame_too_long(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FTL);
+}
+
+static bool ftmac100_rxdes_runt(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RUNT);
+}
+
+static bool ftmac100_rxdes_odd_nibble(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ODD_NB);
+}
+
+static unsigned int ftmac100_rxdes_frame_length(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_RFL;
+}
+
+static bool ftmac100_rxdes_multicast(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_MULTICAST);
+}
+
+static void ftmac100_rxdes_set_buffer_size(struct ftmac100_rxdes *rxdes,
+					   unsigned int size)
+{
+	rxdes->rxdes1 &= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_RXBUF_SIZE(size));
+}
+
+static void ftmac100_rxdes_set_end_of_ring(struct ftmac100_rxdes *rxdes)
+{
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+}
+
+static void ftmac100_rxdes_set_dma_addr(struct ftmac100_rxdes *rxdes,
+					dma_addr_t addr)
+{
+	rxdes->rxdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_rxdes_get_dma_addr(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes2);
+}
+
+/*
+ * rxdes3 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 ftmac100_rxdes_set_page(struct ftmac100_rxdes *rxdes, struct page *page)
+{
+	rxdes->rxdes3 = (unsigned int)page;
+}
+
+static struct page *ftmac100_rxdes_get_page(struct ftmac100_rxdes *rxdes)
+{
+	return (struct page *)rxdes->rxdes3;
+}
+
+/******************************************************************************
+ * internal functions (receive)
+ *****************************************************************************/
+static int ftmac100_next_rx_pointer(int pointer)
+{
+	return (pointer + 1) & (RX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_rx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->rx_pointer = ftmac100_next_rx_pointer(priv->rx_pointer);
+}
+
+static struct ftmac100_rxdes *ftmac100_current_rxdes(struct ftmac100 *priv)
+{
+	return &priv->descs->rxdes[priv->rx_pointer];
+}
+
+static struct ftmac100_rxdes *
+ftmac100_rx_locate_first_segment(struct ftmac100 *priv)
+{
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+
+	while (!ftmac100_rxdes_owned_by_dma(rxdes)) {
+		if (ftmac100_rxdes_first_segment(rxdes))
+			return rxdes;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	}
+
+	return NULL;
+}
+
+static bool ftmac100_rx_packet_error(struct ftmac100 *priv,
+				     struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	bool error = false;
+
+	if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx err\n");
+
+		netdev->stats.rx_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_crc_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx crc err\n");
+
+		netdev->stats.rx_crc_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx frame too long\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_runt(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx runt\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_odd_nibble(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx odd nibble\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	}
+
+	return error;
+}
+
+static void ftmac100_rx_drop_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+	bool done = false;
+
+	if (net_ratelimit())
+		netdev_dbg(netdev, "drop packet %p\n", rxdes);
+
+	do {
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = true;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && !ftmac100_rxdes_owned_by_dma(rxdes));
+
+	netdev->stats.rx_dropped++;
+}
+
+static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes;
+	struct sk_buff *skb;
+	struct page *page;
+	dma_addr_t map;
+	int length;
+
+	rxdes = ftmac100_rx_locate_first_segment(priv);
+	if (!rxdes)
+		return false;
+
+	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	/*
+	 * It is impossible to get multi-segment packets
+	 * because we always provide big enough receive buffers.
+	 */
+	if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))
+		BUG();
+
+	/* start processing */
+	skb = netdev_alloc_skb_ip_align(netdev, RX_SKB_ALLOC_SIZE);
+	if (unlikely(!skb)) {
+		if (net_ratelimit())
+			netdev_err(netdev, "rx skb alloc failed\n");
+
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	if (unlikely(ftmac100_rxdes_multicast(rxdes)))
+		netdev->stats.multicast++;
+
+	map = ftmac100_rxdes_get_dma_addr(rxdes);
+	dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
+
+	length = ftmac100_rxdes_frame_length(rxdes);
+	page = ftmac100_rxdes_get_page(rxdes);
+	skb_fill_page_desc(skb, 0, page, 0, length);
+	skb->len += length;
+	skb->data_len += length;
+	skb->truesize += length;
+	__pskb_pull_tail(skb, min(length, RX_SKB_ALLOC_SIZE));
+
+	ftmac100_alloc_rx_page(priv, rxdes);
+
+	ftmac100_rx_pointer_advance(priv);
+
+	skb->protocol = eth_type_trans(skb, netdev);
+
+	netdev->stats.rx_packets++;
+	netdev->stats.rx_bytes += skb->len;
+
+	/* push packet to protocol stack */
+	netif_receive_skb(skb);
+
+	(*processed)++;
+	return true;
+}
+
+/******************************************************************************
+ * internal functions (transmit descriptor)
+ *****************************************************************************/
+static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
+{
+	/* clear all except end of ring bit */
+	txdes->txdes0 = 0;
+	txdes->txdes1 &= cpu_to_le32(FTMAC100_TXDES1_EDOTR);
+	txdes->txdes2 = 0;
+	txdes->txdes3 = 0;
+}
+
+static bool ftmac100_txdes_owned_by_dma(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
+{
+	/*
+	 * Make sure dma own bit will not be set before any other
+	 * descriptor fields.
+	 */
+	wmb();
+	txdes->txdes0 |= cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static bool ftmac100_txdes_excessive_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_EXSCOL);
+}
+
+static bool ftmac100_txdes_late_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_LATECOL);
+}
+
+static void ftmac100_txdes_set_end_of_ring(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_EDOTR);
+}
+
+static void ftmac100_txdes_set_first_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_FTS);
+}
+
+static void ftmac100_txdes_set_last_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_LTS);
+}
+
+static void ftmac100_txdes_set_txint(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXIC);
+}
+
+static void ftmac100_txdes_set_buffer_size(struct ftmac100_txdes *txdes,
+					   unsigned int len)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXBUF_SIZE(len));
+}
+
+static void ftmac100_txdes_set_dma_addr(struct ftmac100_txdes *txdes,
+					dma_addr_t addr)
+{
+	txdes->txdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_txdes_get_dma_addr(struct ftmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes2);
+}
+
+/*
+ * txdes3 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 ftmac100_txdes_set_skb(struct ftmac100_txdes *txdes, struct sk_buff *skb)
+{
+	txdes->txdes3 = (unsigned int)skb;
+}
+
+static struct sk_buff *ftmac100_txdes_get_skb(struct ftmac100_txdes *txdes)
+{
+	return (struct sk_buff *)txdes->txdes3;
+}
+
+/******************************************************************************
+ * internal functions (transmit)
+ *****************************************************************************/
+static int ftmac100_next_tx_pointer(int pointer)
+{
+	return (pointer + 1) & (TX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_tx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_pointer = ftmac100_next_tx_pointer(priv->tx_pointer);
+}
+
+static void ftmac100_tx_clean_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_clean_pointer = ftmac100_next_tx_pointer(priv->tx_clean_pointer);
+}
+
+static struct ftmac100_txdes *ftmac100_current_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_pointer];
+}
+
+static struct ftmac100_txdes *ftmac100_current_clean_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_clean_pointer];
+}
+
+static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	struct sk_buff *skb;
+	dma_addr_t map;
+
+	if (priv->tx_pending == 0)
+		return false;
+
+	txdes = ftmac100_current_clean_txdes(priv);
+
+	if (ftmac100_txdes_owned_by_dma(txdes))
+		return false;
+
+	skb = ftmac100_txdes_get_skb(txdes);
+	map = ftmac100_txdes_get_dma_addr(txdes);
+
+	if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
+		     ftmac100_txdes_late_collision(txdes))) {
+		/*
+		 * packet transmitted to ethernet lost due to late collision
+		 * or excessive collision
+		 */
+		netdev->stats.tx_aborted_errors++;
+	} else {
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += skb->len;
+	}
+
+	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+	dev_kfree_skb(skb);
+
+	ftmac100_txdes_reset(txdes);
+
+	ftmac100_tx_clean_pointer_advance(priv);
+
+	spin_lock(&priv->tx_lock);
+	priv->tx_pending--;
+	spin_unlock(&priv->tx_lock);
+	netif_wake_queue(netdev);
+
+	return true;
+}
+
+static void ftmac100_tx_complete(struct ftmac100 *priv)
+{
+	while (ftmac100_tx_complete_packet(priv))
+		;
+}
+
+static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
+			 dma_addr_t map)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
+
+	txdes = ftmac100_current_txdes(priv);
+	ftmac100_tx_pointer_advance(priv);
+
+	/* setup TX descriptor */
+	ftmac100_txdes_set_skb(txdes, skb);
+	ftmac100_txdes_set_dma_addr(txdes, map);
+
+	ftmac100_txdes_set_first_segment(txdes);
+	ftmac100_txdes_set_last_segment(txdes);
+	ftmac100_txdes_set_txint(txdes);
+	ftmac100_txdes_set_buffer_size(txdes, len);
+
+	spin_lock(&priv->tx_lock);
+	priv->tx_pending++;
+	if (priv->tx_pending == TX_QUEUE_ENTRIES)
+		netif_stop_queue(netdev);
+
+	/* start transmit */
+	ftmac100_txdes_set_dma_own(txdes);
+	spin_unlock(&priv->tx_lock);
+
+	ftmac100_txdma_start_polling(priv);
+	return NETDEV_TX_OK;
+}
+
+/******************************************************************************
+ * internal functions (buffer)
+ *****************************************************************************/
+static int ftmac100_alloc_rx_page(struct ftmac100 *priv, struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	struct page *page;
+	dma_addr_t map;
+
+	page = alloc_page(GFP_KERNEL);
+	if (!page) {
+		if (net_ratelimit())
+			netdev_err(netdev, "failed to allocate rx page\n");
+		return -ENOMEM;
+	}
+
+	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;
+	}
+
+	ftmac100_rxdes_set_page(rxdes, page);
+	ftmac100_rxdes_set_dma_addr(rxdes, map);
+	ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
+	ftmac100_rxdes_set_dma_own(rxdes);
+	return 0;
+}
+
+static void ftmac100_free_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		struct page *page = ftmac100_rxdes_get_page(rxdes);
+		dma_addr_t map = ftmac100_rxdes_get_dma_addr(rxdes);
+
+		if (!page)
+			continue;
+
+		dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
+		__free_page(page);
+	}
+
+	for (i = 0; i < TX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_txdes *txdes = &priv->descs->txdes[i];
+		struct sk_buff *skb = ftmac100_txdes_get_skb(txdes);
+		dma_addr_t map = ftmac100_txdes_get_dma_addr(txdes);
+
+		if (!skb)
+			continue;
+
+		dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+		dev_kfree_skb(skb);
+	}
+
+	dma_free_coherent(priv->dev, sizeof(struct ftmac100_descs),
+			  priv->descs, priv->descs_dma_addr);
+}
+
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	priv->descs = dma_alloc_coherent(priv->dev, sizeof(struct ftmac100_descs),
+					 &priv->descs_dma_addr, GFP_KERNEL);
+	if (!priv->descs)
+		return -ENOMEM;
+
+	memset(priv->descs, 0, sizeof(struct ftmac100_descs));
+
+	/* initialize RX ring */
+	ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+
+		if (ftmac100_alloc_rx_page(priv, rxdes))
+			goto err;
+	}
+
+	/* initialize TX ring */
+	ftmac100_txdes_set_end_of_ring(&priv->descs->txdes[TX_QUEUE_ENTRIES - 1]);
+	return 0;
+
+err:
+	ftmac100_free_buffers(priv);
+	return -ENOMEM;
+}
+
+/******************************************************************************
+ * struct mii_if_info functions
+ *****************************************************************************/
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIRD;
+
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
+			return phycr & FTMAC100_PHYCR_MIIRDATA;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio read timed out\n");
+	return 0;
+}
+
+static void ftmac100_mdio_write(struct net_device *netdev, int phy_id, int reg,
+				int data)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIWR;
+
+	data = FTMAC100_PHYWDATA_MIIWDATA(data);
+
+	iowrite32(data, priv->base + FTMAC100_OFFSET_PHYWDATA);
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIWR) == 0)
+			return;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio write timed out\n");
+}
+
+/******************************************************************************
+ * struct ethtool_ops functions
+ *****************************************************************************/
+static void ftmac100_get_drvinfo(struct net_device *netdev,
+				 struct ethtool_drvinfo *info)
+{
+	strcpy(info->driver, DRV_NAME);
+	strcpy(info->version, DRV_VERSION);
+	strcpy(info->bus_info, dev_name(&netdev->dev));
+}
+
+static int ftmac100_get_settings(struct net_device *netdev, struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_gset(&priv->mii, cmd);
+}
+
+static int ftmac100_set_settings(struct net_device *netdev, struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_sset(&priv->mii, cmd);
+}
+
+static int ftmac100_nway_reset(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_nway_restart(&priv->mii);
+}
+
+static u32 ftmac100_get_link(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_link_ok(&priv->mii);
+}
+
+static const struct ethtool_ops ftmac100_ethtool_ops = {
+	.set_settings		= ftmac100_set_settings,
+	.get_settings		= ftmac100_get_settings,
+	.get_drvinfo		= ftmac100_get_drvinfo,
+	.nway_reset		= ftmac100_nway_reset,
+	.get_link		= ftmac100_get_link,
+};
+
+/******************************************************************************
+ * interrupt handler
+ *****************************************************************************/
+static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	if (likely(netif_running(netdev))) {
+		/* Disable interrupts for polling */
+		ftmac100_disable_all_int(priv);
+		napi_schedule(&priv->napi);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/******************************************************************************
+ * struct napi_struct functions
+ *****************************************************************************/
+static int ftmac100_poll(struct napi_struct *napi, int budget)
+{
+	struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
+	struct net_device *netdev = priv->netdev;
+	unsigned int status;
+	bool completed = true;
+	int rx = 0;
+
+	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
+
+	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
+		/*
+		 * FTMAC100_INT_RPKT_FINISH:
+		 *	RX DMA has received packets into RX buffer successfully
+		 *
+		 * FTMAC100_INT_NORXBUF:
+		 *	RX buffer unavailable
+		 */
+		bool retry;
+
+		do {
+			retry = ftmac100_rx_packet(priv, &rx);
+		} while (retry && rx < budget);
+
+		if (retry && rx == budget)
+			completed = false;
+	}
+
+	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
+		/*
+		 * FTMAC100_INT_XPKT_OK:
+		 *	packet transmitted to ethernet successfully
+		 *
+		 * FTMAC100_INT_XPKT_LOST:
+		 *	packet transmitted to ethernet lost due to late
+		 *	collision or excessive collision
+		 */
+		ftmac100_tx_complete(priv);
+	}
+
+	if (status & (FTMAC100_INT_NORXBUF | FTMAC100_INT_RPKT_LOST |
+		      FTMAC100_INT_AHB_ERR | FTMAC100_INT_PHYSTS_CHG)) {
+		if (net_ratelimit())
+			netdev_info(netdev, "[ISR] = 0x%x: %s%s%s%s\n", status,
+				    status & FTMAC100_INT_NORXBUF ? "NORXBUF " : "",
+				    status & FTMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "",
+				    status & FTMAC100_INT_AHB_ERR ? "AHB_ERR " : "",
+				    status & FTMAC100_INT_PHYSTS_CHG ? "PHYSTS_CHG" : "");
+
+		if (status & FTMAC100_INT_NORXBUF) {
+			/* RX buffer unavailable */
+			netdev->stats.rx_over_errors++;
+		}
+
+		if (status & FTMAC100_INT_RPKT_LOST) {
+			/* received packet lost due to RX FIFO full */
+			netdev->stats.rx_fifo_errors++;
+		}
+
+		if (status & FTMAC100_INT_PHYSTS_CHG) {
+			/* PHY link status change */
+			mii_check_link(&priv->mii);
+		}
+	}
+
+	if (completed) {
+		/* stop polling */
+		napi_complete(napi);
+		ftmac100_enable_all_int(priv);
+	}
+
+	return rx;
+}
+
+/******************************************************************************
+ * struct net_device_ops functions
+ *****************************************************************************/
+static int ftmac100_open(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int err;
+
+	err = ftmac100_alloc_buffers(priv);
+	if (err) {
+		netdev_err(netdev, "failed to allocate buffers\n");
+		goto err_alloc;
+	}
+
+	err = request_irq(priv->irq, ftmac100_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 = ftmac100_start_hw(priv);
+	if (err)
+		goto err_hw;
+
+	napi_enable(&priv->napi);
+	netif_start_queue(netdev);
+
+	ftmac100_enable_all_int(priv);
+
+	return 0;
+
+err_hw:
+	free_irq(priv->irq, netdev);
+err_irq:
+	ftmac100_free_buffers(priv);
+err_alloc:
+	return err;
+}
+
+static int ftmac100_stop(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	ftmac100_disable_all_int(priv);
+	netif_stop_queue(netdev);
+	napi_disable(&priv->napi);
+	ftmac100_stop_hw(priv);
+	free_irq(priv->irq, netdev);
+	ftmac100_free_buffers(priv);
+
+	return 0;
+}
+
+static int ftmac100_hard_start_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+	struct ftmac100 *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++;
+		dev_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++;
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	return ftmac100_xmit(priv, skb, map);
+}
+
+/* optional */
+static int ftmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+
+	return generic_mii_ioctl(&priv->mii, data, cmd, NULL);
+}
+
+static const struct net_device_ops ftmac100_netdev_ops = {
+	.ndo_open		= ftmac100_open,
+	.ndo_stop		= ftmac100_stop,
+	.ndo_start_xmit		= ftmac100_hard_start_xmit,
+	.ndo_set_mac_address	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= ftmac100_do_ioctl,
+};
+
+/******************************************************************************
+ * struct platform_driver functions
+ *****************************************************************************/
+static int ftmac100_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int irq;
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+	int err;
+
+	if (!pdev)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENXIO;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/* setup net_device */
+	netdev = alloc_etherdev(sizeof(*priv));
+	if (!netdev) {
+		err = -ENOMEM;
+		goto err_alloc_etherdev;
+	}
+
+	SET_NETDEV_DEV(netdev, &pdev->dev);
+	SET_ETHTOOL_OPS(netdev, &ftmac100_ethtool_ops);
+	netdev->netdev_ops = &ftmac100_netdev_ops;
+
+	platform_set_drvdata(pdev, netdev);
+
+	/* setup private data */
+	priv = netdev_priv(netdev);
+	priv->netdev = netdev;
+	priv->dev = &pdev->dev;
+
+	spin_lock_init(&priv->tx_lock);
+
+	/* initialize NAPI */
+	netif_napi_add(netdev, &priv->napi, ftmac100_poll, 64);
+
+	/* map io memory */
+	priv->res = request_mem_region(res->start, resource_size(res),
+				       dev_name(&pdev->dev));
+	if (!priv->res) {
+		dev_err(&pdev->dev, "Could not reserve memory region\n");
+		err = -ENOMEM;
+		goto err_req_mem;
+	}
+
+	priv->base = ioremap(res->start, res->end - res->start);
+	if (!priv->base) {
+		dev_err(&pdev->dev, "Failed to ioremap ethernet registers\n");
+		err = -EIO;
+		goto err_ioremap;
+	}
+
+	priv->irq = irq;
+
+	/* initialize struct mii_if_info */
+	priv->mii.phy_id	= 0;
+	priv->mii.phy_id_mask	= 0x1f;
+	priv->mii.reg_num_mask	= 0x1f;
+	priv->mii.dev		= netdev;
+	priv->mii.mdio_read	= ftmac100_mdio_read;
+	priv->mii.mdio_write	= ftmac100_mdio_write;
+
+	/* register network device */
+	err = register_netdev(netdev);
+	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);
+
+	if (!is_valid_ether_addr(netdev->dev_addr)) {
+		random_ether_addr(netdev->dev_addr);
+		netdev_info(netdev, "generated random MAC address %pM\n",
+			    netdev->dev_addr);
+	}
+
+	return 0;
+
+err_register_netdev:
+	iounmap(priv->base);
+err_ioremap:
+	release_resource(priv->res);
+err_req_mem:
+	netif_napi_del(&priv->napi);
+	platform_set_drvdata(pdev, NULL);
+	free_netdev(netdev);
+err_alloc_etherdev:
+	return err;
+}
+
+static int __exit ftmac100_remove(struct platform_device *pdev)
+{
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+
+	netdev = platform_get_drvdata(pdev);
+	priv = netdev_priv(netdev);
+
+	unregister_netdev(netdev);
+
+	iounmap(priv->base);
+	release_resource(priv->res);
+
+	netif_napi_del(&priv->napi);
+	platform_set_drvdata(pdev, NULL);
+	free_netdev(netdev);
+	return 0;
+}
+
+static struct platform_driver ftmac100_driver = {
+	.probe		= ftmac100_probe,
+	.remove		= __exit_p(ftmac100_remove),
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+/******************************************************************************
+ * initialization / finalization
+ *****************************************************************************/
+static int __init ftmac100_init(void)
+{
+	pr_info("Loading version " DRV_VERSION " ...\n");
+	return platform_driver_register(&ftmac100_driver);
+}
+
+static void __exit ftmac100_exit(void)
+{
+	platform_driver_unregister(&ftmac100_driver);
+}
+
+module_init(ftmac100_init);
+module_exit(ftmac100_exit);
+
+MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");
+MODULE_DESCRIPTION("FTMAC100 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ftmac100.h b/drivers/net/ftmac100.h
new file mode 100644
index 0000000..46a0c47
--- /dev/null
+++ b/drivers/net/ftmac100.h
@@ -0,0 +1,180 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __FTMAC100_H
+#define __FTMAC100_H
+
+#define	FTMAC100_OFFSET_ISR		0x00
+#define	FTMAC100_OFFSET_IMR		0x04
+#define	FTMAC100_OFFSET_MAC_MADR	0x08
+#define	FTMAC100_OFFSET_MAC_LADR	0x0c
+#define	FTMAC100_OFFSET_MAHT0		0x10
+#define	FTMAC100_OFFSET_MAHT1		0x14
+#define	FTMAC100_OFFSET_TXPD		0x18
+#define	FTMAC100_OFFSET_RXPD		0x1c
+#define	FTMAC100_OFFSET_TXR_BADR	0x20
+#define	FTMAC100_OFFSET_RXR_BADR	0x24
+#define	FTMAC100_OFFSET_ITC		0x28
+#define	FTMAC100_OFFSET_APTC		0x2c
+#define	FTMAC100_OFFSET_DBLAC		0x30
+#define	FTMAC100_OFFSET_MACCR		0x88
+#define	FTMAC100_OFFSET_MACSR		0x8c
+#define	FTMAC100_OFFSET_PHYCR		0x90
+#define	FTMAC100_OFFSET_PHYWDATA	0x94
+#define	FTMAC100_OFFSET_FCR		0x98
+#define	FTMAC100_OFFSET_BPR		0x9c
+#define	FTMAC100_OFFSET_TS		0xc4
+#define	FTMAC100_OFFSET_DMAFIFOS	0xc8
+#define	FTMAC100_OFFSET_TM		0xcc
+#define	FTMAC100_OFFSET_TX_MCOL_SCOL	0xd4
+#define	FTMAC100_OFFSET_RPF_AEP		0xd8
+#define	FTMAC100_OFFSET_XM_PG		0xdc
+#define	FTMAC100_OFFSET_RUNT_TLCC	0xe0
+#define	FTMAC100_OFFSET_CRCER_FTL	0xe4
+#define	FTMAC100_OFFSET_RLC_RCC		0xe8
+#define	FTMAC100_OFFSET_BROC		0xec
+#define	FTMAC100_OFFSET_MULCA		0xf0
+#define	FTMAC100_OFFSET_RP		0xf4
+#define	FTMAC100_OFFSET_XP		0xf8
+
+/*
+ * Interrupt status register & interrupt mask register
+ */
+#define	FTMAC100_INT_RPKT_FINISH	(1 << 0)
+#define	FTMAC100_INT_NORXBUF		(1 << 1)
+#define	FTMAC100_INT_XPKT_FINISH	(1 << 2)
+#define	FTMAC100_INT_NOTXBUF		(1 << 3)
+#define	FTMAC100_INT_XPKT_OK		(1 << 4)
+#define	FTMAC100_INT_XPKT_LOST		(1 << 5)
+#define	FTMAC100_INT_RPKT_SAV		(1 << 6)
+#define	FTMAC100_INT_RPKT_LOST		(1 << 7)
+#define	FTMAC100_INT_AHB_ERR		(1 << 8)
+#define	FTMAC100_INT_PHYSTS_CHG		(1 << 9)
+
+/*
+ * Interrupt timer control register
+ */
+#define FTMAC100_ITC_RXINT_CNT(x)	(((x) & 0xf) << 0)
+#define FTMAC100_ITC_RXINT_THR(x)	(((x) & 0x7) << 4)
+#define FTMAC100_ITC_RXINT_TIME_SEL	(1 << 7)
+#define FTMAC100_ITC_TXINT_CNT(x)	(((x) & 0xf) << 8)
+#define FTMAC100_ITC_TXINT_THR(x)	(((x) & 0x7) << 12)
+#define FTMAC100_ITC_TXINT_TIME_SEL	(1 << 15)
+
+/*
+ * Automatic polling timer control register
+ */
+#define	FTMAC100_APTC_RXPOLL_CNT(x)	(((x) & 0xf) << 0)
+#define	FTMAC100_APTC_RXPOLL_TIME_SEL	(1 << 4)
+#define	FTMAC100_APTC_TXPOLL_CNT(x)	(((x) & 0xf) << 8)
+#define	FTMAC100_APTC_TXPOLL_TIME_SEL	(1 << 12)
+
+/*
+ * DMA burst length and arbitration control register
+ */
+#define FTMAC100_DBLAC_INCR4_EN		(1 << 0)
+#define FTMAC100_DBLAC_INCR8_EN		(1 << 1)
+#define FTMAC100_DBLAC_INCR16_EN	(1 << 2)
+#define FTMAC100_DBLAC_RXFIFO_LTHR(x)	(((x) & 0x7) << 3)
+#define FTMAC100_DBLAC_RXFIFO_HTHR(x)	(((x) & 0x7) << 6)
+#define FTMAC100_DBLAC_RX_THR_EN	(1 << 9)
+
+/*
+ * MAC control register
+ */
+#define	FTMAC100_MACCR_XDMA_EN		(1 << 0)
+#define	FTMAC100_MACCR_RDMA_EN		(1 << 1)
+#define	FTMAC100_MACCR_SW_RST		(1 << 2)
+#define	FTMAC100_MACCR_LOOP_EN		(1 << 3)
+#define	FTMAC100_MACCR_CRC_DIS		(1 << 4)
+#define	FTMAC100_MACCR_XMT_EN		(1 << 5)
+#define	FTMAC100_MACCR_ENRX_IN_HALFTX	(1 << 6)
+#define	FTMAC100_MACCR_RCV_EN		(1 << 8)
+#define	FTMAC100_MACCR_HT_MULTI_EN	(1 << 9)
+#define	FTMAC100_MACCR_RX_RUNT		(1 << 10)
+#define	FTMAC100_MACCR_RX_FTL		(1 << 11)
+#define	FTMAC100_MACCR_RCV_ALL		(1 << 12)
+#define	FTMAC100_MACCR_CRC_APD		(1 << 14)
+#define	FTMAC100_MACCR_FULLDUP		(1 << 15)
+#define	FTMAC100_MACCR_RX_MULTIPKT	(1 << 16)
+#define	FTMAC100_MACCR_RX_BROADPKT	(1 << 17)
+
+/*
+ * PHY control register
+ */
+#define FTMAC100_PHYCR_MIIRDATA		0xffff
+#define FTMAC100_PHYCR_PHYAD(x)		(((x) & 0x1f) << 16)
+#define FTMAC100_PHYCR_REGAD(x)		(((x) & 0x1f) << 21)
+#define FTMAC100_PHYCR_MIIRD		(1 << 26)
+#define FTMAC100_PHYCR_MIIWR		(1 << 27)
+
+/*
+ * PHY write data register
+ */
+#define FTMAC100_PHYWDATA_MIIWDATA(x)	((x) & 0xffff)
+
+/*
+ * Transmit descriptor, aligned to 16 bytes
+ */
+struct ftmac100_txdes {
+	unsigned int	txdes0;
+	unsigned int	txdes1;
+	unsigned int	txdes2;	/* TXBUF_BADR */
+	unsigned int	txdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_TXDES0_TXPKT_LATECOL	(1 << 0)
+#define	FTMAC100_TXDES0_TXPKT_EXSCOL	(1 << 1)
+#define	FTMAC100_TXDES0_TXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_TXDES1_TXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_TXDES1_LTS		(1 << 27)
+#define	FTMAC100_TXDES1_FTS		(1 << 28)
+#define	FTMAC100_TXDES1_TX2FIC		(1 << 29)
+#define	FTMAC100_TXDES1_TXIC		(1 << 30)
+#define	FTMAC100_TXDES1_EDOTR		(1 << 31)
+
+/*
+ * Receive descriptor, aligned to 16 bytes
+ */
+struct ftmac100_rxdes {
+	unsigned int	rxdes0;
+	unsigned int	rxdes1;
+	unsigned int	rxdes2;	/* RXBUF_BADR */
+	unsigned int	rxdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_RXDES0_RFL		0x7ff
+#define	FTMAC100_RXDES0_MULTICAST	(1 << 16)
+#define	FTMAC100_RXDES0_BROADCAST	(1 << 17)
+#define	FTMAC100_RXDES0_RX_ERR		(1 << 18)
+#define	FTMAC100_RXDES0_CRC_ERR		(1 << 19)
+#define	FTMAC100_RXDES0_FTL		(1 << 20)
+#define	FTMAC100_RXDES0_RUNT		(1 << 21)
+#define	FTMAC100_RXDES0_RX_ODD_NB	(1 << 22)
+#define	FTMAC100_RXDES0_LRS		(1 << 28)
+#define	FTMAC100_RXDES0_FRS		(1 << 29)
+#define	FTMAC100_RXDES0_RXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_RXDES1_RXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_RXDES1_EDORR		(1 << 31)
+
+#endif /* __FTMAC100_H */
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-25  9:45                                   ` Po-Yu Chuang
@ 2011-02-25 10:52                                     ` Eric Dumazet
  2011-02-25 18:34                                       ` David Miller
  0 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-02-25 10:52 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Le vendredi 25 février 2011 à 17:45 +0800, Po-Yu Chuang a écrit :

> It's a little faster than v5 now. Thanks.
> I will submit the current version later.
> 
> One more question just curious, why 128 bytes?

Probably its best for NIU hardware specs

You could try 64, as it should be enough for most IP/TCP/UDP processing.




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v6] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-25  9:57                             ` [PATCH v6] " Po-Yu Chuang
@ 2011-02-25 11:40                               ` Eric Dumazet
  2011-03-01  5:20                                 ` Po-Yu Chuang
  2011-03-01  6:48                               ` [PATCH] " Po-Yu Chuang
  1 sibling, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-02-25 11:40 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Le vendredi 25 février 2011 à 17:57 +0800, Po-Yu Chuang a écrit :
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> 
> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
> MII.  This driver has been working on some ARM/NDS32 SoC's including
> Faraday A320 and Andes AG101.
> 
> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>

It seems fine to me, but I have somes questions 

1) On V5, the receive function ftmac100_rx_packet() was able to process
several segments per skb. On V6 you process one frag only.

Isnt this NIC able to handle large MTU (say... 9000) ?

2) ftmac100_alloc_rx_page() is called and allocate a full page for a
rxdes.

128*4K -> 512 Kbytes of memory for RX ring

In V5, you were using half pages only, so 256 kbytes of memory.

If you look at other drivers (NIU, BENET), they are able to use exactly
128*2 kbytes (for a rxring of 128 slots, and 2Kbytes per slot)




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-25 10:52                                     ` Eric Dumazet
@ 2011-02-25 18:34                                       ` David Miller
  2011-02-25 18:45                                         ` Eric Dumazet
  0 siblings, 1 reply; 74+ messages in thread
From: David Miller @ 2011-02-25 18:34 UTC (permalink / raw)
  To: eric.dumazet
  Cc: ratbert.chuang, netdev, linux-kernel, bhutchings, joe, dilinger,
	mirqus, ratbert

From: Eric Dumazet <eric.dumazet@gmail.com>
Date: Fri, 25 Feb 2011 11:52:07 +0100

> Le vendredi 25 février 2011 à 17:45 +0800, Po-Yu Chuang a écrit :
> 
>> It's a little faster than v5 now. Thanks.
>> I will submit the current version later.
>> 
>> One more question just curious, why 128 bytes?
> 
> Probably its best for NIU hardware specs
> 
> You could try 64, as it should be enough for most IP/TCP/UDP processing.

IPV6.

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-25 18:34                                       ` David Miller
@ 2011-02-25 18:45                                         ` Eric Dumazet
  2011-02-25 18:47                                           ` Eric Dumazet
  0 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-02-25 18:45 UTC (permalink / raw)
  To: David Miller
  Cc: ratbert.chuang, netdev, linux-kernel, bhutchings, joe, dilinger,
	mirqus, ratbert, Ajit Khaparde

Le vendredi 25 février 2011 à 10:34 -0800, David Miller a écrit :
> From: Eric Dumazet <eric.dumazet@gmail.com>
> Date: Fri, 25 Feb 2011 11:52:07 +0100
> 
> > Le vendredi 25 février 2011 à 17:45 +0800, Po-Yu Chuang a écrit :
> > 
> >> It's a little faster than v5 now. Thanks.
> >> I will submit the current version later.
> >> 
> >> One more question just curious, why 128 bytes?
> > 
> > Probably its best for NIU hardware specs
> > 
> > You could try 64, as it should be enough for most IP/TCP/UDP processing.
> 
> IPV6.

drivers/net/benet/be.h:70:#define BE_HDR_LEN            64

Maybe we should have a comment somewhere.

CC Ajit Khaparde <ajit.khaparde@emulex.com>



^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-25 18:45                                         ` Eric Dumazet
@ 2011-02-25 18:47                                           ` Eric Dumazet
  2011-03-01  5:45                                             ` Po-Yu Chuang
  0 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-02-25 18:47 UTC (permalink / raw)
  To: David Miller
  Cc: ratbert.chuang, netdev, linux-kernel, bhutchings, joe, dilinger,
	mirqus, ratbert, Ajit Khaparde

Le vendredi 25 février 2011 à 19:45 +0100, Eric Dumazet a écrit :
> Le vendredi 25 février 2011 à 10:34 -0800, David Miller a écrit :
> > From: Eric Dumazet <eric.dumazet@gmail.com>
> > Date: Fri, 25 Feb 2011 11:52:07 +0100
> > 
> > > Le vendredi 25 février 2011 à 17:45 +0800, Po-Yu Chuang a écrit :
> > > 
> > >> It's a little faster than v5 now. Thanks.
> > >> I will submit the current version later.
> > >> 
> > >> One more question just curious, why 128 bytes?
> > > 
> > > Probably its best for NIU hardware specs
> > > 
> > > You could try 64, as it should be enough for most IP/TCP/UDP processing.
> > 
> > IPV6.
> 
> drivers/net/benet/be.h:70:#define BE_HDR_LEN            64
> 
> Maybe we should have a comment somewhere.
> 
> CC Ajit Khaparde <ajit.khaparde@emulex.com>
> 


A compromise would be to use 128 for the allocation, but only copy 64
bytes.




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v6] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-25 11:40                               ` Eric Dumazet
@ 2011-03-01  5:20                                 ` Po-Yu Chuang
  2011-03-01  5:26                                   ` Eric Dumazet
  2011-03-01  5:45                                   ` Eric Dumazet
  0 siblings, 2 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-03-01  5:20 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Hi Eric,

On Fri, Feb 25, 2011 at 7:40 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le vendredi 25 février 2011 à 17:57 +0800, Po-Yu Chuang a écrit :
>> From: Po-Yu Chuang <ratbert@faraday-tech.com>
>>
>> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
>> MII.  This driver has been working on some ARM/NDS32 SoC's including
>> Faraday A320 and Andes AG101.
>>
>> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
>
> It seems fine to me, but I have somes questions
>
> 1) On V5, the receive function ftmac100_rx_packet() was able to process
> several segments per skb. On V6 you process one frag only.
>
> Isnt this NIC able to handle large MTU (say... 9000) ?

No, it isn't. Its max supported packet size is 0x7ff (2047 bytes).

I wasn't sure when will get multi-segment packet. After confirmed with
HW designer two weeks ago, he told me that only if the rx buffer described
by the first descriptor is not big enough, another rx buffer will be used.

That means, since every rx buffer is large enough, it is impossible to
get a multi-segment packets.

So I simplified ftmac100_rx_packet().

>
> 2) ftmac100_alloc_rx_page() is called and allocate a full page for a
> rxdes.
>
> 128*4K -> 512 Kbytes of memory for RX ring
>
> In V5, you were using half pages only, so 256 kbytes of memory.
>
> If you look at other drivers (NIU, BENET), they are able to use exactly
> 128*2 kbytes (for a rxring of 128 slots, and 2Kbytes per slot)

Both NIU and BENET refill rx ring after all packets receive work done,
but I allocate rx buffer right after a packet is received. I think it
is difficult
to use a page for two rx buffers in my driver now. Can we just keep it
the way it is? I can study how to achieve that after current driver is accepted.

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v6] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-03-01  5:20                                 ` Po-Yu Chuang
@ 2011-03-01  5:26                                   ` Eric Dumazet
  2011-03-01  5:45                                   ` Eric Dumazet
  1 sibling, 0 replies; 74+ messages in thread
From: Eric Dumazet @ 2011-03-01  5:26 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Le mardi 01 mars 2011 à 13:20 +0800, Po-Yu Chuang a écrit :
> Hi Eric,
> 
> On Fri, Feb 25, 2011 at 7:40 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:

> Both NIU and BENET refill rx ring after all packets receive work done,
> but I allocate rx buffer right after a packet is received. I think it
> is difficult
> to use a page for two rx buffers in my driver now. Can we just keep it
> the way it is? I can study how to achieve that after current driver is accepted.
> 

No problem, you are the author, I am only a reviewer and ask questions
after all ;)

Thanks !





^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-25 18:47                                           ` Eric Dumazet
@ 2011-03-01  5:45                                             ` Po-Yu Chuang
  2011-03-01  5:53                                               ` Eric Dumazet
  0 siblings, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-03-01  5:45 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David Miller, netdev, linux-kernel, bhutchings, joe, dilinger,
	mirqus, ratbert, Ajit Khaparde

Hi Eric,

On Sat, Feb 26, 2011 at 2:47 AM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le vendredi 25 février 2011 à 19:45 +0100, Eric Dumazet a écrit :
>> Le vendredi 25 février 2011 à 10:34 -0800, David Miller a écrit :
>> > From: Eric Dumazet <eric.dumazet@gmail.com>
>> > Date: Fri, 25 Feb 2011 11:52:07 +0100
>> >
>> > > Le vendredi 25 février 2011 à 17:45 +0800, Po-Yu Chuang a écrit :
>> > >
>> > >> It's a little faster than v5 now. Thanks.
>> > >> I will submit the current version later.
>> > >>
>> > >> One more question just curious, why 128 bytes?
>> > >
>> > > Probably its best for NIU hardware specs
>> > >
>> > > You could try 64, as it should be enough for most IP/TCP/UDP processing.
>> >
>> > IPV6.
>>
>> drivers/net/benet/be.h:70:#define BE_HDR_LEN            64
>>
>> Maybe we should have a comment somewhere.
>>
>> CC Ajit Khaparde <ajit.khaparde@emulex.com>
>>
>
>
> A compromise would be to use 128 for the allocation, but only copy 64
> bytes.

I will use this way and submit v7 later.

Thanks,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v6] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-03-01  5:20                                 ` Po-Yu Chuang
  2011-03-01  5:26                                   ` Eric Dumazet
@ 2011-03-01  5:45                                   ` Eric Dumazet
  2011-03-01  5:51                                     ` Po-Yu Chuang
  1 sibling, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-03-01  5:45 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Le mardi 01 mars 2011 à 13:20 +0800, Po-Yu Chuang a écrit :

> That means, since every rx buffer is large enough, it is impossible to
> get a multi-segment packets.
> 
> So I simplified ftmac100_rx_packet().

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>

I am only wondering then if not using fragments would be faster then
(eventually doing copybreaks for small frames like tg3)

But if you plan to get new hw soon, you have the infrastructure...




^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v6] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-03-01  5:45                                   ` Eric Dumazet
@ 2011-03-01  5:51                                     ` Po-Yu Chuang
  2011-03-01  5:54                                       ` Eric Dumazet
  2011-03-01  5:59                                       ` Eric Dumazet
  0 siblings, 2 replies; 74+ messages in thread
From: Po-Yu Chuang @ 2011-03-01  5:51 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Hi Eric,

On Tue, Mar 1, 2011 at 1:45 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le mardi 01 mars 2011 à 13:20 +0800, Po-Yu Chuang a écrit :
>
>> That means, since every rx buffer is large enough, it is impossible to
>> get a multi-segment packets.
>>
>> So I simplified ftmac100_rx_packet().
>
> Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>

Ah, I just want to submit a v7.
Could you resign again later, please? :-)

> I am only wondering then if not using fragments would be faster then
> (eventually doing copybreaks for small frames like tg3)

Although not many circumstances are tested.
iperf shows that it is a little faster to use fragments than memcpy, so...

> But if you plan to get new hw soon, you have the infrastructure...

best regards,
Po-Yu Chuang

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH ref0] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-03-01  5:45                                             ` Po-Yu Chuang
@ 2011-03-01  5:53                                               ` Eric Dumazet
  0 siblings, 0 replies; 74+ messages in thread
From: Eric Dumazet @ 2011-03-01  5:53 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: David Miller, netdev, linux-kernel, bhutchings, joe, dilinger,
	mirqus, ratbert, Ajit Khaparde

Le mardi 01 mars 2011 à 13:45 +0800, Po-Yu Chuang a écrit :
> Hi Eric,
> 
> On Sat, Feb 26, 2011 at 2:47 AM, Eric Dumazet <eric.dumazet@gmail.com> wrote:

> >
> > A compromise would be to use 128 for the allocation, but only copy 64
> > bytes.
> 
> I will use this way and submit v7 later.
> 

BTW, even with IPV6, the first 64 bytes contain "RPS" header

We changed NET_SKB_PAD a while back and added this comment in
include/linux/skbuff.h

NET_IP_ALIGN(2) + ethernet_header(14) + IP_header(20/40) + ports(8)



^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v6] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-03-01  5:51                                     ` Po-Yu Chuang
@ 2011-03-01  5:54                                       ` Eric Dumazet
  2011-03-01  5:59                                       ` Eric Dumazet
  1 sibling, 0 replies; 74+ messages in thread
From: Eric Dumazet @ 2011-03-01  5:54 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Le mardi 01 mars 2011 à 13:51 +0800, Po-Yu Chuang a écrit :

> Ah, I just want to submit a v7.
> Could you resign again later, please? :-)

Sure


^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v6] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-03-01  5:51                                     ` Po-Yu Chuang
  2011-03-01  5:54                                       ` Eric Dumazet
@ 2011-03-01  5:59                                       ` Eric Dumazet
  1 sibling, 0 replies; 74+ messages in thread
From: Eric Dumazet @ 2011-03-01  5:59 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Le mardi 01 mars 2011 à 13:51 +0800, Po-Yu Chuang a écrit :
> On Tue, Mar 1, 2011 at 1:45 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:

> > I am only wondering then if not using fragments would be faster then
> > (eventually doing copybreaks for small frames like tg3)
> 
> Although not many circumstances are tested.
> iperf shows that it is a little faster to use fragments than memcpy, so...

This might be a side effect of skb->truesize being smaller with
fragments than "regular packets" and socket backlog congestion.

If a full page was really accounted for in skb->truesize, instead of
used length, performance might be the same or lower :(

-	skb->truesize += length;
+	skb->truesize += PAGE_SIZE;

This has nothing to do with your driver, but a core implementation
detail.




^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-02-25  9:57                             ` [PATCH v6] " Po-Yu Chuang
  2011-02-25 11:40                               ` Eric Dumazet
@ 2011-03-01  6:48                               ` Po-Yu Chuang
  2011-03-01  7:27                                 ` Eric Dumazet
  1 sibling, 1 reply; 74+ messages in thread
From: Po-Yu Chuang @ 2011-03-01  6:48 UTC (permalink / raw)
  To: netdev
  Cc: linux-kernel, bhutchings, eric.dumazet, joe, dilinger, mirqus,
	davem, Po-Yu Chuang

From: Po-Yu Chuang <ratbert@faraday-tech.com>

FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII.  This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.

Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts

v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix

v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()

v5:
use dev_kfree_skb() in ftmac100_tx_complete_packet()
cpu_to_le32/le32_to_cpu usage fix
drop GFP_DMA

v6:
cpu_to_le32/le32_to_cpu usage fix
remove "tx queue full" message
reduce critical section protected by tx_lock
add check of MAX_PKT_SIZE and RX_BUF_SIZE
add __exit to ftmac100_remove()
simplify ftmac100_rx_packet()
zero copy - use skb_fill_page_desc() and __pskb_pull_tail().
pull more data to skb head to include tcp/ip header

v7:
allocate 128 bytes skb and pull 64 bytes only

 drivers/net/Kconfig    |    9 +
 drivers/net/Makefile   |    1 +
 drivers/net/ftmac100.c | 1196 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/ftmac100.h |  180 ++++++++
 4 files changed, 1386 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/ftmac100.c
 create mode 100644 drivers/net/ftmac100.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 4f1755b..6b12274 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2014,6 +2014,15 @@ config BCM63XX_ENET
 	  This driver supports the ethernet MACs in the Broadcom 63xx
 	  MIPS chipset family (BCM63XX).
 
+config FTMAC100
+	tristate "Faraday FTMAC100 10/100 Ethernet support"
+	depends on ARM
+	select MII
+	help
+	  This driver supports the FTMAC100 10/100 Ethernet controller
+	  from Faraday. It is used on Faraday A320, Andes AG101 and some
+	  other ARM/NDS32 SoC's.
+
 source "drivers/net/fs_enet/Kconfig"
 
 source "drivers/net/octeon/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..7c21711 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_FORCEDETH) += forcedeth.o
 obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
 obj-$(CONFIG_AX88796) += ax88796.o
 obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
+obj-$(CONFIG_FTMAC100) += ftmac100.o
 
 obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c
new file mode 100644
index 0000000..df70368
--- /dev/null
+++ b/drivers/net/ftmac100.c
@@ -0,0 +1,1196 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#include "ftmac100.h"
+
+#define DRV_NAME	"ftmac100"
+#define DRV_VERSION	"0.2"
+
+#define RX_QUEUE_ENTRIES	128	/* must be power of 2 */
+#define TX_QUEUE_ENTRIES	16	/* must be power of 2 */
+
+#define MAX_PKT_SIZE		1518
+#define RX_BUF_SIZE		2044	/* must be smaller than 0x7ff */
+
+#if MAX_PKT_SIZE > 0x7ff
+#error invalid MAX_PKT_SIZE
+#endif
+
+#if RX_BUF_SIZE > 0x7ff || RX_BUF_SIZE > PAGE_SIZE
+#error invalid RX_BUF_SIZE
+#endif
+
+/******************************************************************************
+ * private data
+ *****************************************************************************/
+struct ftmac100_descs {
+	struct ftmac100_rxdes rxdes[RX_QUEUE_ENTRIES];
+	struct ftmac100_txdes txdes[TX_QUEUE_ENTRIES];
+};
+
+struct ftmac100 {
+	struct resource *res;
+	void __iomem *base;
+	int irq;
+
+	struct ftmac100_descs *descs;
+	dma_addr_t descs_dma_addr;
+
+	unsigned int rx_pointer;
+	unsigned int tx_clean_pointer;
+	unsigned int tx_pointer;
+	unsigned int tx_pending;
+
+	spinlock_t tx_lock;
+
+	struct net_device *netdev;
+	struct device *dev;
+	struct napi_struct napi;
+
+	struct mii_if_info mii;
+};
+
+static int ftmac100_alloc_rx_page(struct ftmac100 *priv, struct ftmac100_rxdes *rxdes);
+
+/******************************************************************************
+ * internal functions (hardware register access)
+ *****************************************************************************/
+#define INT_MASK_ALL_ENABLED	(FTMAC100_INT_RPKT_FINISH	| \
+				 FTMAC100_INT_NORXBUF		| \
+				 FTMAC100_INT_XPKT_OK		| \
+				 FTMAC100_INT_XPKT_LOST		| \
+				 FTMAC100_INT_RPKT_LOST		| \
+				 FTMAC100_INT_AHB_ERR		| \
+				 FTMAC100_INT_PHYSTS_CHG)
+
+#define INT_MASK_ALL_DISABLED	0
+
+static void ftmac100_enable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_disable_all_int(struct ftmac100 *priv)
+{
+	iowrite32(INT_MASK_ALL_DISABLED, priv->base + FTMAC100_OFFSET_IMR);
+}
+
+static void ftmac100_set_rx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
+}
+
+static void ftmac100_set_tx_ring_base(struct ftmac100 *priv, dma_addr_t addr)
+{
+	iowrite32(addr, priv->base + FTMAC100_OFFSET_TXR_BADR);
+}
+
+static void ftmac100_txdma_start_polling(struct ftmac100 *priv)
+{
+	iowrite32(1, priv->base + FTMAC100_OFFSET_TXPD);
+}
+
+static int ftmac100_reset(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	int i;
+
+	/* NOTE: reset clears all registers */
+	iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);
+
+	for (i = 0; i < 5; i++) {
+		unsigned int maccr;
+
+		maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);
+		if (!(maccr & FTMAC100_MACCR_SW_RST)) {
+			/*
+			 * FTMAC100_MACCR_SW_RST cleared does not indicate
+			 * that hardware reset completed (what the f*ck).
+			 * We still need to wait for a while.
+			 */
+			usleep_range(500, 1000);
+			return 0;
+		}
+
+		usleep_range(1000, 10000);
+	}
+
+	netdev_err(netdev, "software reset failed\n");
+	return -EIO;
+}
+
+static void ftmac100_set_mac(struct ftmac100 *priv, const unsigned char *mac)
+{
+	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 + FTMAC100_OFFSET_MAC_MADR);
+	iowrite32(laddr, priv->base + FTMAC100_OFFSET_MAC_LADR);
+}
+
+#define MACCR_ENABLE_ALL	(FTMAC100_MACCR_XMT_EN	| \
+				 FTMAC100_MACCR_RCV_EN	| \
+				 FTMAC100_MACCR_XDMA_EN	| \
+				 FTMAC100_MACCR_RDMA_EN	| \
+				 FTMAC100_MACCR_CRC_APD	| \
+				 FTMAC100_MACCR_FULLDUP	| \
+				 FTMAC100_MACCR_RX_RUNT	| \
+				 FTMAC100_MACCR_RX_BROADPKT)
+
+static int ftmac100_start_hw(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+
+	if (ftmac100_reset(priv))
+		return -EIO;
+
+	/* setup ring buffer base registers */
+	ftmac100_set_rx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, rxdes));
+	ftmac100_set_tx_ring_base(priv,
+				  priv->descs_dma_addr +
+				  offsetof(struct ftmac100_descs, txdes));
+
+	iowrite32(FTMAC100_APTC_RXPOLL_CNT(1), priv->base + FTMAC100_OFFSET_APTC);
+
+	ftmac100_set_mac(priv, netdev->dev_addr);
+
+	iowrite32(MACCR_ENABLE_ALL, priv->base + FTMAC100_OFFSET_MACCR);
+	return 0;
+}
+
+static void ftmac100_stop_hw(struct ftmac100 *priv)
+{
+	iowrite32(0, priv->base + FTMAC100_OFFSET_MACCR);
+}
+
+/******************************************************************************
+ * internal functions (receive descriptor)
+ *****************************************************************************/
+static bool ftmac100_rxdes_first_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FRS);
+}
+
+static bool ftmac100_rxdes_last_segment(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_LRS);
+}
+
+static bool ftmac100_rxdes_owned_by_dma(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static void ftmac100_rxdes_set_dma_own(struct ftmac100_rxdes *rxdes)
+{
+	/* clear status bits */
+	rxdes->rxdes0 = cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);
+}
+
+static bool ftmac100_rxdes_rx_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ERR);
+}
+
+static bool ftmac100_rxdes_crc_error(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_CRC_ERR);
+}
+
+static bool ftmac100_rxdes_frame_too_long(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_FTL);
+}
+
+static bool ftmac100_rxdes_runt(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RUNT);
+}
+
+static bool ftmac100_rxdes_odd_nibble(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_RX_ODD_NB);
+}
+
+static unsigned int ftmac100_rxdes_frame_length(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes0) & FTMAC100_RXDES0_RFL;
+}
+
+static bool ftmac100_rxdes_multicast(struct ftmac100_rxdes *rxdes)
+{
+	return rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_MULTICAST);
+}
+
+static void ftmac100_rxdes_set_buffer_size(struct ftmac100_rxdes *rxdes,
+					   unsigned int size)
+{
+	rxdes->rxdes1 &= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_RXBUF_SIZE(size));
+}
+
+static void ftmac100_rxdes_set_end_of_ring(struct ftmac100_rxdes *rxdes)
+{
+	rxdes->rxdes1 |= cpu_to_le32(FTMAC100_RXDES1_EDORR);
+}
+
+static void ftmac100_rxdes_set_dma_addr(struct ftmac100_rxdes *rxdes,
+					dma_addr_t addr)
+{
+	rxdes->rxdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_rxdes_get_dma_addr(struct ftmac100_rxdes *rxdes)
+{
+	return le32_to_cpu(rxdes->rxdes2);
+}
+
+/*
+ * rxdes3 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 ftmac100_rxdes_set_page(struct ftmac100_rxdes *rxdes, struct page *page)
+{
+	rxdes->rxdes3 = (unsigned int)page;
+}
+
+static struct page *ftmac100_rxdes_get_page(struct ftmac100_rxdes *rxdes)
+{
+	return (struct page *)rxdes->rxdes3;
+}
+
+/******************************************************************************
+ * internal functions (receive)
+ *****************************************************************************/
+static int ftmac100_next_rx_pointer(int pointer)
+{
+	return (pointer + 1) & (RX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_rx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->rx_pointer = ftmac100_next_rx_pointer(priv->rx_pointer);
+}
+
+static struct ftmac100_rxdes *ftmac100_current_rxdes(struct ftmac100 *priv)
+{
+	return &priv->descs->rxdes[priv->rx_pointer];
+}
+
+static struct ftmac100_rxdes *
+ftmac100_rx_locate_first_segment(struct ftmac100 *priv)
+{
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+
+	while (!ftmac100_rxdes_owned_by_dma(rxdes)) {
+		if (ftmac100_rxdes_first_segment(rxdes))
+			return rxdes;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	}
+
+	return NULL;
+}
+
+static bool ftmac100_rx_packet_error(struct ftmac100 *priv,
+				     struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	bool error = false;
+
+	if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx err\n");
+
+		netdev->stats.rx_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_crc_error(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx crc err\n");
+
+		netdev->stats.rx_crc_errors++;
+		error = true;
+	}
+
+	if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx frame too long\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_runt(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx runt\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	} else if (unlikely(ftmac100_rxdes_odd_nibble(rxdes))) {
+		if (net_ratelimit())
+			netdev_info(netdev, "rx odd nibble\n");
+
+		netdev->stats.rx_length_errors++;
+		error = true;
+	}
+
+	return error;
+}
+
+static void ftmac100_rx_drop_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes = ftmac100_current_rxdes(priv);
+	bool done = false;
+
+	if (net_ratelimit())
+		netdev_dbg(netdev, "drop packet %p\n", rxdes);
+
+	do {
+		if (ftmac100_rxdes_last_segment(rxdes))
+			done = true;
+
+		ftmac100_rxdes_set_dma_own(rxdes);
+		ftmac100_rx_pointer_advance(priv);
+		rxdes = ftmac100_current_rxdes(priv);
+	} while (!done && !ftmac100_rxdes_owned_by_dma(rxdes));
+
+	netdev->stats.rx_dropped++;
+}
+
+static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_rxdes *rxdes;
+	struct sk_buff *skb;
+	struct page *page;
+	dma_addr_t map;
+	int length;
+
+	rxdes = ftmac100_rx_locate_first_segment(priv);
+	if (!rxdes)
+		return false;
+
+	if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	/*
+	 * It is impossible to get multi-segment packets
+	 * because we always provide big enough receive buffers.
+	 */
+	if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))
+		BUG();
+
+	/* start processing */
+	skb = netdev_alloc_skb_ip_align(netdev, 128);
+	if (unlikely(!skb)) {
+		if (net_ratelimit())
+			netdev_err(netdev, "rx skb alloc failed\n");
+
+		ftmac100_rx_drop_packet(priv);
+		return true;
+	}
+
+	if (unlikely(ftmac100_rxdes_multicast(rxdes)))
+		netdev->stats.multicast++;
+
+	map = ftmac100_rxdes_get_dma_addr(rxdes);
+	dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
+
+	length = ftmac100_rxdes_frame_length(rxdes);
+	page = ftmac100_rxdes_get_page(rxdes);
+	skb_fill_page_desc(skb, 0, page, 0, length);
+	skb->len += length;
+	skb->data_len += length;
+	skb->truesize += length;
+	__pskb_pull_tail(skb, min(length, 64));
+
+	ftmac100_alloc_rx_page(priv, rxdes);
+
+	ftmac100_rx_pointer_advance(priv);
+
+	skb->protocol = eth_type_trans(skb, netdev);
+
+	netdev->stats.rx_packets++;
+	netdev->stats.rx_bytes += skb->len;
+
+	/* push packet to protocol stack */
+	netif_receive_skb(skb);
+
+	(*processed)++;
+	return true;
+}
+
+/******************************************************************************
+ * internal functions (transmit descriptor)
+ *****************************************************************************/
+static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)
+{
+	/* clear all except end of ring bit */
+	txdes->txdes0 = 0;
+	txdes->txdes1 &= cpu_to_le32(FTMAC100_TXDES1_EDOTR);
+	txdes->txdes2 = 0;
+	txdes->txdes3 = 0;
+}
+
+static bool ftmac100_txdes_owned_by_dma(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)
+{
+	/*
+	 * Make sure dma own bit will not be set before any other
+	 * descriptor fields.
+	 */
+	wmb();
+	txdes->txdes0 |= cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);
+}
+
+static bool ftmac100_txdes_excessive_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_EXSCOL);
+}
+
+static bool ftmac100_txdes_late_collision(struct ftmac100_txdes *txdes)
+{
+	return txdes->txdes0 & cpu_to_le32(FTMAC100_TXDES0_TXPKT_LATECOL);
+}
+
+static void ftmac100_txdes_set_end_of_ring(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_EDOTR);
+}
+
+static void ftmac100_txdes_set_first_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_FTS);
+}
+
+static void ftmac100_txdes_set_last_segment(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_LTS);
+}
+
+static void ftmac100_txdes_set_txint(struct ftmac100_txdes *txdes)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXIC);
+}
+
+static void ftmac100_txdes_set_buffer_size(struct ftmac100_txdes *txdes,
+					   unsigned int len)
+{
+	txdes->txdes1 |= cpu_to_le32(FTMAC100_TXDES1_TXBUF_SIZE(len));
+}
+
+static void ftmac100_txdes_set_dma_addr(struct ftmac100_txdes *txdes,
+					dma_addr_t addr)
+{
+	txdes->txdes2 = cpu_to_le32(addr);
+}
+
+static dma_addr_t ftmac100_txdes_get_dma_addr(struct ftmac100_txdes *txdes)
+{
+	return le32_to_cpu(txdes->txdes2);
+}
+
+/*
+ * txdes3 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 ftmac100_txdes_set_skb(struct ftmac100_txdes *txdes, struct sk_buff *skb)
+{
+	txdes->txdes3 = (unsigned int)skb;
+}
+
+static struct sk_buff *ftmac100_txdes_get_skb(struct ftmac100_txdes *txdes)
+{
+	return (struct sk_buff *)txdes->txdes3;
+}
+
+/******************************************************************************
+ * internal functions (transmit)
+ *****************************************************************************/
+static int ftmac100_next_tx_pointer(int pointer)
+{
+	return (pointer + 1) & (TX_QUEUE_ENTRIES - 1);
+}
+
+static void ftmac100_tx_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_pointer = ftmac100_next_tx_pointer(priv->tx_pointer);
+}
+
+static void ftmac100_tx_clean_pointer_advance(struct ftmac100 *priv)
+{
+	priv->tx_clean_pointer = ftmac100_next_tx_pointer(priv->tx_clean_pointer);
+}
+
+static struct ftmac100_txdes *ftmac100_current_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_pointer];
+}
+
+static struct ftmac100_txdes *ftmac100_current_clean_txdes(struct ftmac100 *priv)
+{
+	return &priv->descs->txdes[priv->tx_clean_pointer];
+}
+
+static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	struct sk_buff *skb;
+	dma_addr_t map;
+
+	if (priv->tx_pending == 0)
+		return false;
+
+	txdes = ftmac100_current_clean_txdes(priv);
+
+	if (ftmac100_txdes_owned_by_dma(txdes))
+		return false;
+
+	skb = ftmac100_txdes_get_skb(txdes);
+	map = ftmac100_txdes_get_dma_addr(txdes);
+
+	if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||
+		     ftmac100_txdes_late_collision(txdes))) {
+		/*
+		 * packet transmitted to ethernet lost due to late collision
+		 * or excessive collision
+		 */
+		netdev->stats.tx_aborted_errors++;
+	} else {
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += skb->len;
+	}
+
+	dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+	dev_kfree_skb(skb);
+
+	ftmac100_txdes_reset(txdes);
+
+	ftmac100_tx_clean_pointer_advance(priv);
+
+	spin_lock(&priv->tx_lock);
+	priv->tx_pending--;
+	spin_unlock(&priv->tx_lock);
+	netif_wake_queue(netdev);
+
+	return true;
+}
+
+static void ftmac100_tx_complete(struct ftmac100 *priv)
+{
+	while (ftmac100_tx_complete_packet(priv))
+		;
+}
+
+static int ftmac100_xmit(struct ftmac100 *priv, struct sk_buff *skb,
+			 dma_addr_t map)
+{
+	struct net_device *netdev = priv->netdev;
+	struct ftmac100_txdes *txdes;
+	unsigned int len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len;
+
+	txdes = ftmac100_current_txdes(priv);
+	ftmac100_tx_pointer_advance(priv);
+
+	/* setup TX descriptor */
+	ftmac100_txdes_set_skb(txdes, skb);
+	ftmac100_txdes_set_dma_addr(txdes, map);
+
+	ftmac100_txdes_set_first_segment(txdes);
+	ftmac100_txdes_set_last_segment(txdes);
+	ftmac100_txdes_set_txint(txdes);
+	ftmac100_txdes_set_buffer_size(txdes, len);
+
+	spin_lock(&priv->tx_lock);
+	priv->tx_pending++;
+	if (priv->tx_pending == TX_QUEUE_ENTRIES)
+		netif_stop_queue(netdev);
+
+	/* start transmit */
+	ftmac100_txdes_set_dma_own(txdes);
+	spin_unlock(&priv->tx_lock);
+
+	ftmac100_txdma_start_polling(priv);
+	return NETDEV_TX_OK;
+}
+
+/******************************************************************************
+ * internal functions (buffer)
+ *****************************************************************************/
+static int ftmac100_alloc_rx_page(struct ftmac100 *priv, struct ftmac100_rxdes *rxdes)
+{
+	struct net_device *netdev = priv->netdev;
+	struct page *page;
+	dma_addr_t map;
+
+	page = alloc_page(GFP_KERNEL);
+	if (!page) {
+		if (net_ratelimit())
+			netdev_err(netdev, "failed to allocate rx page\n");
+		return -ENOMEM;
+	}
+
+	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;
+	}
+
+	ftmac100_rxdes_set_page(rxdes, page);
+	ftmac100_rxdes_set_dma_addr(rxdes, map);
+	ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);
+	ftmac100_rxdes_set_dma_own(rxdes);
+	return 0;
+}
+
+static void ftmac100_free_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+		struct page *page = ftmac100_rxdes_get_page(rxdes);
+		dma_addr_t map = ftmac100_rxdes_get_dma_addr(rxdes);
+
+		if (!page)
+			continue;
+
+		dma_unmap_page(priv->dev, map, RX_BUF_SIZE, DMA_FROM_DEVICE);
+		__free_page(page);
+	}
+
+	for (i = 0; i < TX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_txdes *txdes = &priv->descs->txdes[i];
+		struct sk_buff *skb = ftmac100_txdes_get_skb(txdes);
+		dma_addr_t map = ftmac100_txdes_get_dma_addr(txdes);
+
+		if (!skb)
+			continue;
+
+		dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);
+		dev_kfree_skb(skb);
+	}
+
+	dma_free_coherent(priv->dev, sizeof(struct ftmac100_descs),
+			  priv->descs, priv->descs_dma_addr);
+}
+
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)
+{
+	int i;
+
+	priv->descs = dma_alloc_coherent(priv->dev, sizeof(struct ftmac100_descs),
+					 &priv->descs_dma_addr, GFP_KERNEL);
+	if (!priv->descs)
+		return -ENOMEM;
+
+	memset(priv->descs, 0, sizeof(struct ftmac100_descs));
+
+	/* initialize RX ring */
+	ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);
+
+	for (i = 0; i < RX_QUEUE_ENTRIES; i++) {
+		struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];
+
+		if (ftmac100_alloc_rx_page(priv, rxdes))
+			goto err;
+	}
+
+	/* initialize TX ring */
+	ftmac100_txdes_set_end_of_ring(&priv->descs->txdes[TX_QUEUE_ENTRIES - 1]);
+	return 0;
+
+err:
+	ftmac100_free_buffers(priv);
+	return -ENOMEM;
+}
+
+/******************************************************************************
+ * struct mii_if_info functions
+ *****************************************************************************/
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIRD;
+
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)
+			return phycr & FTMAC100_PHYCR_MIIRDATA;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio read timed out\n");
+	return 0;
+}
+
+static void ftmac100_mdio_write(struct net_device *netdev, int phy_id, int reg,
+				int data)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	unsigned int phycr;
+	int i;
+
+	phycr = FTMAC100_PHYCR_PHYAD(phy_id) |
+		FTMAC100_PHYCR_REGAD(reg) |
+		FTMAC100_PHYCR_MIIWR;
+
+	data = FTMAC100_PHYWDATA_MIIWDATA(data);
+
+	iowrite32(data, priv->base + FTMAC100_OFFSET_PHYWDATA);
+	iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);
+
+	for (i = 0; i < 10; i++) {
+		phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);
+
+		if ((phycr & FTMAC100_PHYCR_MIIWR) == 0)
+			return;
+
+		usleep_range(100, 1000);
+	}
+
+	netdev_err(netdev, "mdio write timed out\n");
+}
+
+/******************************************************************************
+ * struct ethtool_ops functions
+ *****************************************************************************/
+static void ftmac100_get_drvinfo(struct net_device *netdev,
+				 struct ethtool_drvinfo *info)
+{
+	strcpy(info->driver, DRV_NAME);
+	strcpy(info->version, DRV_VERSION);
+	strcpy(info->bus_info, dev_name(&netdev->dev));
+}
+
+static int ftmac100_get_settings(struct net_device *netdev, struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_gset(&priv->mii, cmd);
+}
+
+static int ftmac100_set_settings(struct net_device *netdev, struct ethtool_cmd *cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_ethtool_sset(&priv->mii, cmd);
+}
+
+static int ftmac100_nway_reset(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_nway_restart(&priv->mii);
+}
+
+static u32 ftmac100_get_link(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	return mii_link_ok(&priv->mii);
+}
+
+static const struct ethtool_ops ftmac100_ethtool_ops = {
+	.set_settings		= ftmac100_set_settings,
+	.get_settings		= ftmac100_get_settings,
+	.get_drvinfo		= ftmac100_get_drvinfo,
+	.nway_reset		= ftmac100_nway_reset,
+	.get_link		= ftmac100_get_link,
+};
+
+/******************************************************************************
+ * interrupt handler
+ *****************************************************************************/
+static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	if (likely(netif_running(netdev))) {
+		/* Disable interrupts for polling */
+		ftmac100_disable_all_int(priv);
+		napi_schedule(&priv->napi);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/******************************************************************************
+ * struct napi_struct functions
+ *****************************************************************************/
+static int ftmac100_poll(struct napi_struct *napi, int budget)
+{
+	struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);
+	struct net_device *netdev = priv->netdev;
+	unsigned int status;
+	bool completed = true;
+	int rx = 0;
+
+	status = ioread32(priv->base + FTMAC100_OFFSET_ISR);
+
+	if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {
+		/*
+		 * FTMAC100_INT_RPKT_FINISH:
+		 *	RX DMA has received packets into RX buffer successfully
+		 *
+		 * FTMAC100_INT_NORXBUF:
+		 *	RX buffer unavailable
+		 */
+		bool retry;
+
+		do {
+			retry = ftmac100_rx_packet(priv, &rx);
+		} while (retry && rx < budget);
+
+		if (retry && rx == budget)
+			completed = false;
+	}
+
+	if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {
+		/*
+		 * FTMAC100_INT_XPKT_OK:
+		 *	packet transmitted to ethernet successfully
+		 *
+		 * FTMAC100_INT_XPKT_LOST:
+		 *	packet transmitted to ethernet lost due to late
+		 *	collision or excessive collision
+		 */
+		ftmac100_tx_complete(priv);
+	}
+
+	if (status & (FTMAC100_INT_NORXBUF | FTMAC100_INT_RPKT_LOST |
+		      FTMAC100_INT_AHB_ERR | FTMAC100_INT_PHYSTS_CHG)) {
+		if (net_ratelimit())
+			netdev_info(netdev, "[ISR] = 0x%x: %s%s%s%s\n", status,
+				    status & FTMAC100_INT_NORXBUF ? "NORXBUF " : "",
+				    status & FTMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "",
+				    status & FTMAC100_INT_AHB_ERR ? "AHB_ERR " : "",
+				    status & FTMAC100_INT_PHYSTS_CHG ? "PHYSTS_CHG" : "");
+
+		if (status & FTMAC100_INT_NORXBUF) {
+			/* RX buffer unavailable */
+			netdev->stats.rx_over_errors++;
+		}
+
+		if (status & FTMAC100_INT_RPKT_LOST) {
+			/* received packet lost due to RX FIFO full */
+			netdev->stats.rx_fifo_errors++;
+		}
+
+		if (status & FTMAC100_INT_PHYSTS_CHG) {
+			/* PHY link status change */
+			mii_check_link(&priv->mii);
+		}
+	}
+
+	if (completed) {
+		/* stop polling */
+		napi_complete(napi);
+		ftmac100_enable_all_int(priv);
+	}
+
+	return rx;
+}
+
+/******************************************************************************
+ * struct net_device_ops functions
+ *****************************************************************************/
+static int ftmac100_open(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	int err;
+
+	err = ftmac100_alloc_buffers(priv);
+	if (err) {
+		netdev_err(netdev, "failed to allocate buffers\n");
+		goto err_alloc;
+	}
+
+	err = request_irq(priv->irq, ftmac100_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 = ftmac100_start_hw(priv);
+	if (err)
+		goto err_hw;
+
+	napi_enable(&priv->napi);
+	netif_start_queue(netdev);
+
+	ftmac100_enable_all_int(priv);
+
+	return 0;
+
+err_hw:
+	free_irq(priv->irq, netdev);
+err_irq:
+	ftmac100_free_buffers(priv);
+err_alloc:
+	return err;
+}
+
+static int ftmac100_stop(struct net_device *netdev)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+
+	ftmac100_disable_all_int(priv);
+	netif_stop_queue(netdev);
+	napi_disable(&priv->napi);
+	ftmac100_stop_hw(priv);
+	free_irq(priv->irq, netdev);
+	ftmac100_free_buffers(priv);
+
+	return 0;
+}
+
+static int ftmac100_hard_start_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+	struct ftmac100 *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++;
+		dev_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++;
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	return ftmac100_xmit(priv, skb, map);
+}
+
+/* optional */
+static int ftmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
+{
+	struct ftmac100 *priv = netdev_priv(netdev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+
+	return generic_mii_ioctl(&priv->mii, data, cmd, NULL);
+}
+
+static const struct net_device_ops ftmac100_netdev_ops = {
+	.ndo_open		= ftmac100_open,
+	.ndo_stop		= ftmac100_stop,
+	.ndo_start_xmit		= ftmac100_hard_start_xmit,
+	.ndo_set_mac_address	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= ftmac100_do_ioctl,
+};
+
+/******************************************************************************
+ * struct platform_driver functions
+ *****************************************************************************/
+static int ftmac100_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int irq;
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+	int err;
+
+	if (!pdev)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENXIO;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/* setup net_device */
+	netdev = alloc_etherdev(sizeof(*priv));
+	if (!netdev) {
+		err = -ENOMEM;
+		goto err_alloc_etherdev;
+	}
+
+	SET_NETDEV_DEV(netdev, &pdev->dev);
+	SET_ETHTOOL_OPS(netdev, &ftmac100_ethtool_ops);
+	netdev->netdev_ops = &ftmac100_netdev_ops;
+
+	platform_set_drvdata(pdev, netdev);
+
+	/* setup private data */
+	priv = netdev_priv(netdev);
+	priv->netdev = netdev;
+	priv->dev = &pdev->dev;
+
+	spin_lock_init(&priv->tx_lock);
+
+	/* initialize NAPI */
+	netif_napi_add(netdev, &priv->napi, ftmac100_poll, 64);
+
+	/* map io memory */
+	priv->res = request_mem_region(res->start, resource_size(res),
+				       dev_name(&pdev->dev));
+	if (!priv->res) {
+		dev_err(&pdev->dev, "Could not reserve memory region\n");
+		err = -ENOMEM;
+		goto err_req_mem;
+	}
+
+	priv->base = ioremap(res->start, res->end - res->start);
+	if (!priv->base) {
+		dev_err(&pdev->dev, "Failed to ioremap ethernet registers\n");
+		err = -EIO;
+		goto err_ioremap;
+	}
+
+	priv->irq = irq;
+
+	/* initialize struct mii_if_info */
+	priv->mii.phy_id	= 0;
+	priv->mii.phy_id_mask	= 0x1f;
+	priv->mii.reg_num_mask	= 0x1f;
+	priv->mii.dev		= netdev;
+	priv->mii.mdio_read	= ftmac100_mdio_read;
+	priv->mii.mdio_write	= ftmac100_mdio_write;
+
+	/* register network device */
+	err = register_netdev(netdev);
+	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);
+
+	if (!is_valid_ether_addr(netdev->dev_addr)) {
+		random_ether_addr(netdev->dev_addr);
+		netdev_info(netdev, "generated random MAC address %pM\n",
+			    netdev->dev_addr);
+	}
+
+	return 0;
+
+err_register_netdev:
+	iounmap(priv->base);
+err_ioremap:
+	release_resource(priv->res);
+err_req_mem:
+	netif_napi_del(&priv->napi);
+	platform_set_drvdata(pdev, NULL);
+	free_netdev(netdev);
+err_alloc_etherdev:
+	return err;
+}
+
+static int __exit ftmac100_remove(struct platform_device *pdev)
+{
+	struct net_device *netdev;
+	struct ftmac100 *priv;
+
+	netdev = platform_get_drvdata(pdev);
+	priv = netdev_priv(netdev);
+
+	unregister_netdev(netdev);
+
+	iounmap(priv->base);
+	release_resource(priv->res);
+
+	netif_napi_del(&priv->napi);
+	platform_set_drvdata(pdev, NULL);
+	free_netdev(netdev);
+	return 0;
+}
+
+static struct platform_driver ftmac100_driver = {
+	.probe		= ftmac100_probe,
+	.remove		= __exit_p(ftmac100_remove),
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+/******************************************************************************
+ * initialization / finalization
+ *****************************************************************************/
+static int __init ftmac100_init(void)
+{
+	pr_info("Loading version " DRV_VERSION " ...\n");
+	return platform_driver_register(&ftmac100_driver);
+}
+
+static void __exit ftmac100_exit(void)
+{
+	platform_driver_unregister(&ftmac100_driver);
+}
+
+module_init(ftmac100_init);
+module_exit(ftmac100_exit);
+
+MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");
+MODULE_DESCRIPTION("FTMAC100 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ftmac100.h b/drivers/net/ftmac100.h
new file mode 100644
index 0000000..46a0c47
--- /dev/null
+++ b/drivers/net/ftmac100.h
@@ -0,0 +1,180 @@
+/*
+ * Faraday FTMAC100 10/100 Ethernet
+ *
+ * (C) Copyright 2009-2011 Faraday Technology
+ * Po-Yu Chuang <ratbert@faraday-tech.com>
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __FTMAC100_H
+#define __FTMAC100_H
+
+#define	FTMAC100_OFFSET_ISR		0x00
+#define	FTMAC100_OFFSET_IMR		0x04
+#define	FTMAC100_OFFSET_MAC_MADR	0x08
+#define	FTMAC100_OFFSET_MAC_LADR	0x0c
+#define	FTMAC100_OFFSET_MAHT0		0x10
+#define	FTMAC100_OFFSET_MAHT1		0x14
+#define	FTMAC100_OFFSET_TXPD		0x18
+#define	FTMAC100_OFFSET_RXPD		0x1c
+#define	FTMAC100_OFFSET_TXR_BADR	0x20
+#define	FTMAC100_OFFSET_RXR_BADR	0x24
+#define	FTMAC100_OFFSET_ITC		0x28
+#define	FTMAC100_OFFSET_APTC		0x2c
+#define	FTMAC100_OFFSET_DBLAC		0x30
+#define	FTMAC100_OFFSET_MACCR		0x88
+#define	FTMAC100_OFFSET_MACSR		0x8c
+#define	FTMAC100_OFFSET_PHYCR		0x90
+#define	FTMAC100_OFFSET_PHYWDATA	0x94
+#define	FTMAC100_OFFSET_FCR		0x98
+#define	FTMAC100_OFFSET_BPR		0x9c
+#define	FTMAC100_OFFSET_TS		0xc4
+#define	FTMAC100_OFFSET_DMAFIFOS	0xc8
+#define	FTMAC100_OFFSET_TM		0xcc
+#define	FTMAC100_OFFSET_TX_MCOL_SCOL	0xd4
+#define	FTMAC100_OFFSET_RPF_AEP		0xd8
+#define	FTMAC100_OFFSET_XM_PG		0xdc
+#define	FTMAC100_OFFSET_RUNT_TLCC	0xe0
+#define	FTMAC100_OFFSET_CRCER_FTL	0xe4
+#define	FTMAC100_OFFSET_RLC_RCC		0xe8
+#define	FTMAC100_OFFSET_BROC		0xec
+#define	FTMAC100_OFFSET_MULCA		0xf0
+#define	FTMAC100_OFFSET_RP		0xf4
+#define	FTMAC100_OFFSET_XP		0xf8
+
+/*
+ * Interrupt status register & interrupt mask register
+ */
+#define	FTMAC100_INT_RPKT_FINISH	(1 << 0)
+#define	FTMAC100_INT_NORXBUF		(1 << 1)
+#define	FTMAC100_INT_XPKT_FINISH	(1 << 2)
+#define	FTMAC100_INT_NOTXBUF		(1 << 3)
+#define	FTMAC100_INT_XPKT_OK		(1 << 4)
+#define	FTMAC100_INT_XPKT_LOST		(1 << 5)
+#define	FTMAC100_INT_RPKT_SAV		(1 << 6)
+#define	FTMAC100_INT_RPKT_LOST		(1 << 7)
+#define	FTMAC100_INT_AHB_ERR		(1 << 8)
+#define	FTMAC100_INT_PHYSTS_CHG		(1 << 9)
+
+/*
+ * Interrupt timer control register
+ */
+#define FTMAC100_ITC_RXINT_CNT(x)	(((x) & 0xf) << 0)
+#define FTMAC100_ITC_RXINT_THR(x)	(((x) & 0x7) << 4)
+#define FTMAC100_ITC_RXINT_TIME_SEL	(1 << 7)
+#define FTMAC100_ITC_TXINT_CNT(x)	(((x) & 0xf) << 8)
+#define FTMAC100_ITC_TXINT_THR(x)	(((x) & 0x7) << 12)
+#define FTMAC100_ITC_TXINT_TIME_SEL	(1 << 15)
+
+/*
+ * Automatic polling timer control register
+ */
+#define	FTMAC100_APTC_RXPOLL_CNT(x)	(((x) & 0xf) << 0)
+#define	FTMAC100_APTC_RXPOLL_TIME_SEL	(1 << 4)
+#define	FTMAC100_APTC_TXPOLL_CNT(x)	(((x) & 0xf) << 8)
+#define	FTMAC100_APTC_TXPOLL_TIME_SEL	(1 << 12)
+
+/*
+ * DMA burst length and arbitration control register
+ */
+#define FTMAC100_DBLAC_INCR4_EN		(1 << 0)
+#define FTMAC100_DBLAC_INCR8_EN		(1 << 1)
+#define FTMAC100_DBLAC_INCR16_EN	(1 << 2)
+#define FTMAC100_DBLAC_RXFIFO_LTHR(x)	(((x) & 0x7) << 3)
+#define FTMAC100_DBLAC_RXFIFO_HTHR(x)	(((x) & 0x7) << 6)
+#define FTMAC100_DBLAC_RX_THR_EN	(1 << 9)
+
+/*
+ * MAC control register
+ */
+#define	FTMAC100_MACCR_XDMA_EN		(1 << 0)
+#define	FTMAC100_MACCR_RDMA_EN		(1 << 1)
+#define	FTMAC100_MACCR_SW_RST		(1 << 2)
+#define	FTMAC100_MACCR_LOOP_EN		(1 << 3)
+#define	FTMAC100_MACCR_CRC_DIS		(1 << 4)
+#define	FTMAC100_MACCR_XMT_EN		(1 << 5)
+#define	FTMAC100_MACCR_ENRX_IN_HALFTX	(1 << 6)
+#define	FTMAC100_MACCR_RCV_EN		(1 << 8)
+#define	FTMAC100_MACCR_HT_MULTI_EN	(1 << 9)
+#define	FTMAC100_MACCR_RX_RUNT		(1 << 10)
+#define	FTMAC100_MACCR_RX_FTL		(1 << 11)
+#define	FTMAC100_MACCR_RCV_ALL		(1 << 12)
+#define	FTMAC100_MACCR_CRC_APD		(1 << 14)
+#define	FTMAC100_MACCR_FULLDUP		(1 << 15)
+#define	FTMAC100_MACCR_RX_MULTIPKT	(1 << 16)
+#define	FTMAC100_MACCR_RX_BROADPKT	(1 << 17)
+
+/*
+ * PHY control register
+ */
+#define FTMAC100_PHYCR_MIIRDATA		0xffff
+#define FTMAC100_PHYCR_PHYAD(x)		(((x) & 0x1f) << 16)
+#define FTMAC100_PHYCR_REGAD(x)		(((x) & 0x1f) << 21)
+#define FTMAC100_PHYCR_MIIRD		(1 << 26)
+#define FTMAC100_PHYCR_MIIWR		(1 << 27)
+
+/*
+ * PHY write data register
+ */
+#define FTMAC100_PHYWDATA_MIIWDATA(x)	((x) & 0xffff)
+
+/*
+ * Transmit descriptor, aligned to 16 bytes
+ */
+struct ftmac100_txdes {
+	unsigned int	txdes0;
+	unsigned int	txdes1;
+	unsigned int	txdes2;	/* TXBUF_BADR */
+	unsigned int	txdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_TXDES0_TXPKT_LATECOL	(1 << 0)
+#define	FTMAC100_TXDES0_TXPKT_EXSCOL	(1 << 1)
+#define	FTMAC100_TXDES0_TXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_TXDES1_TXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_TXDES1_LTS		(1 << 27)
+#define	FTMAC100_TXDES1_FTS		(1 << 28)
+#define	FTMAC100_TXDES1_TX2FIC		(1 << 29)
+#define	FTMAC100_TXDES1_TXIC		(1 << 30)
+#define	FTMAC100_TXDES1_EDOTR		(1 << 31)
+
+/*
+ * Receive descriptor, aligned to 16 bytes
+ */
+struct ftmac100_rxdes {
+	unsigned int	rxdes0;
+	unsigned int	rxdes1;
+	unsigned int	rxdes2;	/* RXBUF_BADR */
+	unsigned int	rxdes3;	/* not used by HW */
+} __attribute__ ((aligned(16)));
+
+#define	FTMAC100_RXDES0_RFL		0x7ff
+#define	FTMAC100_RXDES0_MULTICAST	(1 << 16)
+#define	FTMAC100_RXDES0_BROADCAST	(1 << 17)
+#define	FTMAC100_RXDES0_RX_ERR		(1 << 18)
+#define	FTMAC100_RXDES0_CRC_ERR		(1 << 19)
+#define	FTMAC100_RXDES0_FTL		(1 << 20)
+#define	FTMAC100_RXDES0_RUNT		(1 << 21)
+#define	FTMAC100_RXDES0_RX_ODD_NB	(1 << 22)
+#define	FTMAC100_RXDES0_LRS		(1 << 28)
+#define	FTMAC100_RXDES0_FRS		(1 << 29)
+#define	FTMAC100_RXDES0_RXDMA_OWN	(1 << 31)
+
+#define	FTMAC100_RXDES1_RXBUF_SIZE(x)	((x) & 0x7ff)
+#define	FTMAC100_RXDES1_EDORR		(1 << 31)
+
+#endif /* __FTMAC100_H */
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-03-01  6:48                               ` [PATCH] " Po-Yu Chuang
@ 2011-03-01  7:27                                 ` Eric Dumazet
  2011-03-03 20:19                                   ` David Miller
  0 siblings, 1 reply; 74+ messages in thread
From: Eric Dumazet @ 2011-03-01  7:27 UTC (permalink / raw)
  To: Po-Yu Chuang
  Cc: netdev, linux-kernel, bhutchings, joe, dilinger, mirqus, davem,
	Po-Yu Chuang

Le mardi 01 mars 2011 à 14:48 +0800, Po-Yu Chuang a écrit :
> From: Po-Yu Chuang <ratbert@faraday-tech.com>
> 
> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
> MII.  This driver has been working on some ARM/NDS32 SoC's including
> Faraday A320 and Andes AG101.
> 
> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
> ---
> v2:
> always use NAPI
> do not use our own net_device_stats structure
> don't set trans_start and last_rx
> stats.rx_packets and stats.rx_bytes include dropped packets
> add missed netif_napi_del()
> initialize spinlocks in probe function
> remove rx_lock and hw_lock
> use netdev_[err/info/dbg] instead of dev_* ones
> use netdev_alloc_skb_ip_align()
> remove ftmac100_get_stats()
> use is_valid_ether_addr() instead of is_zero_ether_addr()
> add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
> use net_ratelimit() instead of printk_ratelimit()
> no explicit inline
> use %pM to print MAC address
> add comment before wmb
> use napi poll() to handle all interrupts
> 
> v3:
> undo "stats.rx_packets and stats.rx_bytes include dropped packets"
> ftmac100_mdio_read() returns 0 if error
> fix comment typos
> use pr_fmt and pr_info
> define INT_MASK_ALL_ENABLED
> define MACCR_ENABLE_ALL
> do not count length error many times
> use bool/true/false
> use cpu_to_le32/le32_to_cpu to access descriptors
> indent style fix
> 
> v4:
> should not access skb after netif_receive_skb()
> use resource_size()
> better way to use cpu_to_le32/le32_to_cpu
> use spin_lock() for tx_lock
> combine all netdev_info() together in ftmac100_poll()
> 
> v5:
> use dev_kfree_skb() in ftmac100_tx_complete_packet()
> cpu_to_le32/le32_to_cpu usage fix
> drop GFP_DMA
> 
> v6:
> cpu_to_le32/le32_to_cpu usage fix
> remove "tx queue full" message
> reduce critical section protected by tx_lock
> add check of MAX_PKT_SIZE and RX_BUF_SIZE
> add __exit to ftmac100_remove()
> simplify ftmac100_rx_packet()
> zero copy - use skb_fill_page_desc() and __pskb_pull_tail().
> pull more data to skb head to include tcp/ip header
> 
> v7:
> allocate 128 bytes skb and pull 64 bytes only
> 
>  drivers/net/Kconfig    |    9 +
>  drivers/net/Makefile   |    1 +
>  drivers/net/ftmac100.c | 1196 ++++++++++++++++++++++++++++++++++++++++++++++++
>  drivers/net/ftmac100.h |  180 ++++++++
>  4 files changed, 1386 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/net/ftmac100.c
>  create mode 100644 drivers/net/ftmac100.h

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>



^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver
  2011-03-01  7:27                                 ` Eric Dumazet
@ 2011-03-03 20:19                                   ` David Miller
  0 siblings, 0 replies; 74+ messages in thread
From: David Miller @ 2011-03-03 20:19 UTC (permalink / raw)
  To: eric.dumazet
  Cc: ratbert.chuang, netdev, linux-kernel, bhutchings, joe, dilinger,
	mirqus, ratbert

From: Eric Dumazet <eric.dumazet@gmail.com>
Date: Tue, 01 Mar 2011 08:27:00 +0100

> Le mardi 01 mars 2011 à 14:48 +0800, Po-Yu Chuang a écrit :
>> From: Po-Yu Chuang <ratbert@faraday-tech.com>
>> 
>> FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
>> MII.  This driver has been working on some ARM/NDS32 SoC's including
>> Faraday A320 and Andes AG101.
>> 
>> Signed-off-by: Po-Yu Chuang <ratbert@faraday-tech.com>
 ...
> 
> Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>

Applied to net-next-2.6, thanks.

^ permalink raw reply	[flat|nested] 74+ messages in thread

end of thread, other threads:[~2011-03-03 20:18 UTC | newest]

Thread overview: 74+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-01-13 11:49 [PATCH] net: add Faraday FTMAC100 10/100 Ethernet driver Po-Yu Chuang
2011-01-13 14:03 ` Ben Hutchings
2011-01-14  5:37   ` Po-Yu Chuang
2011-01-13 14:22 ` Eric Dumazet
2011-01-13 16:29   ` Andres Salomon
2011-01-14  6:44     ` Po-Yu Chuang
2011-01-14  6:56   ` Po-Yu Chuang
2011-01-13 15:39 ` Joe Perches
2011-01-14  6:35   ` Po-Yu Chuang
     [not found] ` <1294959948.4114.189.camel@Joe-Laptop>
2011-01-14  6:49   ` Po-Yu Chuang
2011-01-17  9:21 ` [PATCH v2] " Po-Yu Chuang
2011-01-17 17:19   ` Joe Perches
2011-01-19  9:40     ` Po-Yu Chuang
2011-01-19 12:46       ` Ben Hutchings
2011-01-19 16:41       ` Joe Perches
2011-01-20  5:30         ` Po-Yu Chuang
2011-01-20  8:46           ` Joe Perches
2011-01-17 17:29   ` Eric Dumazet
2011-01-17 18:58     ` Ben Hutchings
2011-01-17 20:39       ` Eric Dumazet
2011-01-17 18:21   ` Eric Dumazet
2011-01-18  3:08     ` Po-Yu Chuang
2011-01-19  9:20     ` Po-Yu Chuang
2011-01-20 15:30   ` [PATCH v3] " Po-Yu Chuang
2011-01-20 15:35     ` Eric Dumazet
2011-01-20 15:43       ` Po-Yu Chuang
2011-01-20 15:41     ` Eric Dumazet
2011-01-20 15:54       ` Po-Yu Chuang
2011-01-20 17:56     ` Joe Perches
2011-01-21  3:35       ` Po-Yu Chuang
2011-01-20 19:00     ` Joe Perches
2011-01-21  5:03       ` Po-Yu Chuang
     [not found]         ` <1295592411.6795.10.camel@Joe-Laptop>
2011-01-21  7:06           ` Po-Yu Chuang
2011-01-20 19:01     ` Michał Mirosław
2011-01-21  3:37       ` Po-Yu Chuang
2011-01-21  7:55     ` [PATCH v4] " Po-Yu Chuang
2011-01-21  9:08       ` Eric Dumazet
2011-01-24  8:07         ` Po-Yu Chuang
2011-01-21 12:26       ` Michał Mirosław
2011-01-24  8:26         ` Po-Yu Chuang
2011-01-24 20:22           ` Michał Mirosław
2011-01-25  2:46             ` Po-Yu Chuang
2011-02-01  3:56               ` Po-Yu Chuang
2011-02-01  4:35                 ` David Miller
2011-02-24  7:27                   ` Po-Yu Chuang
2011-02-24  7:51                     ` David Miller
2011-02-24  8:07                       ` Po-Yu Chuang
2011-02-24  8:22                         ` Eric Dumazet
2011-02-24  9:29                           ` [PATCH ref0] " Po-Yu Chuang
2011-02-24 17:39                             ` Eric Dumazet
2011-02-24 17:48                               ` Eric Dumazet
2011-02-25  2:32                                 ` Po-Yu Chuang
2011-02-25  9:45                                   ` Po-Yu Chuang
2011-02-25 10:52                                     ` Eric Dumazet
2011-02-25 18:34                                       ` David Miller
2011-02-25 18:45                                         ` Eric Dumazet
2011-02-25 18:47                                           ` Eric Dumazet
2011-03-01  5:45                                             ` Po-Yu Chuang
2011-03-01  5:53                                               ` Eric Dumazet
2011-02-25  9:57                             ` [PATCH v6] " Po-Yu Chuang
2011-02-25 11:40                               ` Eric Dumazet
2011-03-01  5:20                                 ` Po-Yu Chuang
2011-03-01  5:26                                   ` Eric Dumazet
2011-03-01  5:45                                   ` Eric Dumazet
2011-03-01  5:51                                     ` Po-Yu Chuang
2011-03-01  5:54                                       ` Eric Dumazet
2011-03-01  5:59                                       ` Eric Dumazet
2011-03-01  6:48                               ` [PATCH] " Po-Yu Chuang
2011-03-01  7:27                                 ` Eric Dumazet
2011-03-03 20:19                                   ` David Miller
2011-02-24 18:43                         ` [PATCH v4] " David Miller
2011-01-24 12:39       ` [PATCH v5] " Po-Yu Chuang
2011-01-24 15:07         ` Eric Dumazet
2011-01-25  2:46           ` Po-Yu Chuang

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.