lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Date:	Fri, 6 Jun 2008 13:01:03 +0100
From:	"Daniel Laird" <daniel.j.laird@....com>
To:	netdev@...r.kernel.org
Subject: [PATCH] Add support for IP3902 on-board ethernet device on NXP PNX833x SOC

The following patch add support for the NXP PNX833x SOC onboard ethernet
device (IP3902).
The SOC code is pending with linux-mips.

 Kconfig  |    9
 Makefile |    1
 ip3902.c | 1510 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1520 insertions(+)

Signed-off-by: daniel.j.laird <daniel.j.laird@....com>

diff -urN --exclude=.svn linux-2.6.26-rc4.orig/drivers/net/ip3902.c
linux-2.6.26-rc4/drivers/net/ip3902.c
--- linux-2.6.26-rc4.orig/drivers/net/ip3902.c	1970-01-01
01:00:00.000000000 +0100
+++ linux-2.6.26-rc4/drivers/net/ip3902.c	2008-06-06 11:29:24.000000000 +0100
@@ -0,0 +1,1510 @@
+/*
+ *  ip3902.c: NXP ip3902 embedded 10/100 Ethernet controller support
+ *  Copyright 2008 NXP Semiconductors
+ *	  Chris Steel <chris.steel@....com>
+ *    Daniel Laird <daniel.j.laird@....com>
+ *
+ *  Based on ax88796.c, by Ben Dooks.
+ *	Based on previous ip3902.c by Nikita V. Youshchenko
+ *
+ *  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/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/isapnp.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/crc32.h>
+#include <linux/inet_lro.h>
+#include <asm/system.h>
+#include <linux/io.h>
+
+#define DRVNAME "ip3902-eth"
+#define DRVVERSION "1.00"
+
+/* "Strange hardware" support macros */
+
+/* These control endianness of descriptors and statuses.
+ * If none if LITTLE_ENDIAN_xxx and BIG_ENDIAN_xxx is defined, system endian
+ * is used for xxx */
+#define LITTLE_ENDIAN_DESCRIPTORS
+#undef BIG_ENDIAN_DESCRIPTORS
+#undef LITTLE_ENDIAN_STATUSES
+#define BIG_ENDIAN_STATUSES
+
+#define ETH_RX_SKB_SIZE        0x600   /* 1536 bytes, just over max mtu */
+#define TX_RING_SIZE           64
+#define RX_RING_SIZE           64
+#define IP3902_NAPI_WEIGHT     48
+#define MAX_LRO_DESCRIPTORS    6
+#define LRO_THRESHOLD          3
+
+#define BYTES_IN_ETHERNET_CRC   4
+#define MAX_DESCS_PER_SKB      (MAX_SKB_FRAGS + 1)
+
+#define NEXT_TX(i) (((i) == TX_RING_SIZE-1) ? 0 : (i)+1)
+#define NEXT_RX(i) (((i) == RX_RING_SIZE-1) ? 0 : (i)+1)
+
+/* Access to IP3902 registers */
+
+/* Alcatel (Packet Engines) core registers */
+#define MAC1_REG		0x000	/* R/W: MAC configuration register 1 */
+#define MAC2_REG		0x004	/* R/W: MAC configuration register 2 */
+#define IPGT_REG		0x008	/* R/W: Back-to-Back Inter-Packet-Gap register */
+#define IPGR_REG		0x00c	/* R/W: Non Back-to-Back Inter-Packet-Gap register */
+#define CLRT_REG		0x010	/* R/W: Collision window / Retry register */
+#define MAXF_REG		0x014	/* R/W: Maximum Frame register */
+#define SUPP_REG		0x018	/* R/W: PHY Support register */
+#define TEST_REG		0x01C	/* R/W: Test register */
+#define MCFG_REG		0x020	/* R/W: MII Mgmt Con???guration register */
+#define MCMD_REG		0x024	/* R/W: MII Mgmt Command register */
+#define MADR_REG		0x028	/* R/W: MII Mgmt Address register */
+#define MWTD_REG		0x02C	/* WO:  MII Mgmt Write Data register */
+#define MRDD_REG		0x030	/* RO:  MII Mgmt Read Data register */
+#define MIND_REG		0x034	/* RO:  MII Mgmt Indicators register */
+#define SA0_REG			0x040	/* R/W: Station Address 0 register */
+#define SA1_REG			0x044	/* R/W: Station Address 1 register */
+#define SA2_REG			0x048	/* R/W: Station Address 2 register */
+
+/* Control registers */
+#define COMMAND_REG		0x100	/* R/W: Command register */
+#define STATUS_REG		0x104	/* RO:  Status register */
+#define RX_DESC_REG		0x108	/* R/W: Receive descriptor base address register */
+#define RX_STATUS_REG		0x10C	/* R/W: Receive status base address register */
+#define RX_DESC_NUMBER_REG	0x110	/* R/W: Receive number of
descriptors register */
+#define RX_PRODUCE_INDEX_REG	0x114	/* RO:  Receive produce index register */
+#define RX_CONSUME_INDEX_REG	0x118	/* R/W: Receive consume index register */
+#define TX_DESC_REG		0x11C	/* R/W: Non real-time transmit descriptor
base address register */
+#define TX_STATUS_REG		0x120	/* R/W: Non real-time transmit status
base address register */
+#define TX_DESC_NUMBER_REG	0x124	/* R/W: Non real-time transmit
number of descriptors register */
+#define TX_PRODUCE_INDEX_REG	0x128	/* R/W: Non real-time transmit
produce index register */
+#define TX_CONSUME_INDEX_REG	0x12C	/* RO:  Non real-time transmit
consume index register */
+#define TX_RT_DESC_REG		0x130	/* R/W: Real-time transmit descriptor
base address register */
+#define TX_RT_STATUS_REG	0x134	/* R/W: Real-time transmit status base
address register */
+#define TX_RT_DESC_NUMBER_REG	0x138	/* R/W: Real-time transmit number
of descriptors register */
+#define TX_RT_PRODUCE_INDEX_REG	0x13C	/* R/W: Real-time transmit
produce index register */
+#define TX_RT_CONSUME_INDEX_REG	0x140	/* RO:  Real-time transmit
consume index register */
+#define QOS_TIMEOUT_REG		0x148	/* R/W: Transmit quality of service
time-out register */
+#define TSV0_REG		0x158	/* RO:  Transmit status vector 0 register */
+#define TSV1_REG		0x15C	/* RO:  Transmit status vector 1 register */
+#define RSV_REG			0x160	/* RO:  Receive status vector register */
+#define FC_COUNTER_REG		0x170	/* R/W: Flow control counter register */
+#define FC_STATUS_REG		0x174	/* RO:  Flow control status register */
+
+/* Rx filter registers */
+#define FILTER_CTRL_REG		0x200	/* R/W: Receive filter control register */
+#define FILTER_WOL_STATUS_REG	0x204	/* RO:  Receive filter WoL status
register */
+#define FILTER_WOL_CLEAR_REG	0x208	/* WO:  Receive filter WoL clear register */
+#define HASH_FILTER_L_REG	0x210	/* R/W: Hash filter table LSBs register */
+#define HASH_FILTER_H_REG	0x214	/* R/W: Hash filter table MSBs register */
+
+/* DVP Standard registers */
+#define INT_STATUS_REG		0xFE0	/* RO:  Interrupt status register */
+#define INT_ENABLE_REG		0xFE4	/* R/W: Interrupt enable register */
+#define INT_CLEAR_REG		0xFE8	/* WO:  Interrupt clear register */
+#define INT_SET_REG		0xFEC	/* WO:  Interrupt set register */
+#define POWERDOWN_REG		0xFF4	/* R/W: Power-down register */
+#define MODULE_ID_REG		0xFFC	/* RO:  Module ID register */
+
+/* Bits for MAC1 register */
+#define MAC1_SOFT_RESET		(1 << 15)
+#define MAC1_TX_FLOW_CONTROL	(1 << 3)
+#define MAC1_RX_FLOW_CONTROL	(1 << 2)
+#define MAC1_RECEIVE_PASS_ALL	(1 << 1)
+#define MAC1_RECEIVE_ENABLE	(1 << 0)
+
+/* Bits for MAC2 register */
+#define MAC2_AUTO_DETECT_PAD_ENABLE	(1 << 7)
+#define MAC2_VLAN_PAD_ENABLE		(1 << 6)
+#define MAC2_PAD_CRC_ENABLE		(1 << 5)
+#define MAC2_CRC_ENABLE			(1 << 4)
+#define MAC2_FULL_DUPLEX		(1 << 0)
+
+#define INITIAL_MAC2	(MAC2_AUTO_DETECT_PAD_ENABLE |
MAC2_VLAN_PAD_ENABLE | MAC2_PAD_CRC_ENABLE | MAC2_CRC_ENABLE)
+
+/* Recommended values for IPGT register (see sec. 3.3.2.3 0f datasheet */
+#define IPGT_FD_VALUE	0x15
+#define IPGT_HD_VALUE	0x12
+
+/* Bits for MCMD register */
+#define MCMD_READ		(1 << 0)
+
+/* Bits for MIND register */
+#define MIND_NOT_VALID		(1 << 2)
+#define MIND_BUSY		(1 << 0)
+
+/* Bits for command register */
+#define COMMAND_ENABLE_QOS	(1 << 11)
+#define COMMAND_FULL_DUPLEX	(1 << 10)
+#define COMMAND_RMII_MODE	(1 << 9)
+#define COMMAND_TX_FLOW_CONTROL	(1 << 8)
+#define COMMAND_PROMISC		(1 << 7)
+#define COMMAND_ALLOW_SHORT	(1 << 6)
+#define COMMAND_RX_RESET	(1 << 5)
+#define COMMAND_TX_RESET	(1 << 4)
+#define COMMAND_RESET		(1 << 3)
+#define COMMAND_TX_RT_ENABLE	(1 << 2)
+#define COMMAND_TX_ENABLE	(1 << 1)
+#define COMMAND_RX_ENABLE	(1 << 0)
+
+/* Bits for receive filter control register */
+#define FILTER_ACCEPT_SELF		(1 << 5)
+#define FILTER_ACCEPT_MCAST_HASH	(1 << 4)
+#define FILTER_ACCEPT_UCAST_HASH	(1 << 3)
+#define FILTER_ACCEPT_MCAST_ANY		(1 << 2)
+#define FILTER_ACCEPT_BCAST_ANY		(1 << 1)
+#define FILTER_ACCEPT_UCAST_ANY		(1 << 0)
+
+/* Bits for interrupt registers */
+#define WAKEUP_INT		(1 << 13)
+#define SOFT_INT		(1 << 12)
+#define TX_RT_DONE_INT		(1 << 11)
+#define TX_RT_FINISHED_INT	(1 << 10)
+#define TX_RT_ERROR_INT		(1 << 9)
+#define TX_RT_UNDERRUN_INT	(1 << 8)
+#define TX_DONE_INT		(1 << 7)
+#define TX_FINISHED_INT		(1 << 6)
+#define TX_ERROR_INT		(1 << 5)
+#define TX_UNDERRUN_INT		(1 << 4)
+#define RX_DONE_INT		(1 << 3)
+#define RX_FINISHED_INT		(1 << 2)
+#define RX_ERROR_INT		(1 << 1)
+#define RX_OVERRUN_INT		(1 << 0)
+
+/* Bit for POWERDOWN register */
+#define POWERDOWN_VALUE		(1 << 31)
+
+/* Bits for TX control */
+#define TX_CONTROL_INT		(1 << 31)
+#define TX_CONTROL_LAST		(1 << 30)
+#define TX_CONTROL_CRC		(1 << 29)
+#define TX_CONTROL_PAD		(1 << 28)
+#define TX_CONTROL_HUGE		(1 << 27)
+#define TX_CONTROL_OVERRIDE	(1 << 26)
+
+/* these flags used for non-last fragment of a frame */
+#define TX_CONTROL_ALL_NOTLAST	(TX_CONTROL_CRC | TX_CONTROL_PAD |
TX_CONTROL_OVERRIDE)
+/* these flags used for last fragment of a frame, and for single-fragment
+ * frames */
+#define TX_CONTROL_ALL_LAST	(TX_CONTROL_ALL_NOTLAST | TX_CONTROL_LAST
| TX_CONTROL_INT)
+
+/* Bits for TX status */
+#define TX_STATUS_ERROR			(1 << 31)
+#define TX_STATUS_UNDERRUN		(1 << 29)
+#define TX_STATUS_LATE_COLLISION	(1 << 28)
+#define TX_STATUS_MANY_COLLISIONS	(1 << 27)
+#define TX_STATUS_MANY_DEFER		(1 << 26)
+#define TX_STATUS_COLLISIONS(s)	((s >> 21) & 15)
+
+/* Bits for RX control */
+#define RX_CONTROL_INT		(1 << 31)
+
+/* Bits for RX status */
+#define RX_STATUS_ERROR			(1 << 31)
+#define RX_STATUS_LAST_FRAG		(1 << 30)
+#define RX_STATUS_OVERRUN		(1 << 28)
+#define RX_STATUS_ALIGNMENT_ERROR	(1 << 27)
+#define RX_STATUS_RANGE_ERROR		(1 << 26)
+#define RX_STATUS_LENGTH_ERROR		(1 << 25)
+#define RX_STATUS_SYMBOL_ERROR		(1 << 24)
+#define RX_STATUS_CRC_ERROR		(1 << 23)
+#define RX_STATUS_BROADCAST		(1 << 22)
+#define RX_STATUS_MULTICAST		(1 << 21)
+#define RX_STATUS_FAIL_FILTER		(1 << 20)
+#define RX_STATUS_VLAN			(1 << 19)
+#define RX_STATUS_CONTROL_FRAME		(1 << 18)
+#define RX_STATUS_LENGTH(s)		((s & 0x7ff) + 1)
+
+/* Bits for RSV register */
+#define RSV_VLAN			(1 << 30)
+#define RSV_CONTROL_FRAME		(1 << 27)
+#define RSV_DRIBBLE_NIBBLE		(1 << 26)
+#define RSV_BROADCAST			(1 << 25)
+#define RSV_MULTICAST			(1 << 24)
+#define RSV_LENGTH_OUT_OF_RANGE		(1 << 22)
+#define RSV_LENGTH_CHECK_ERROR		(1 << 21)
+#define RSV_CRC_ERROR			(1 << 20)
+#define RSV_RECEIVE_CODE_VIOLATION	(1 << 19)
+#define RSV_MASK			0xFFFF
+
+static char *mac_address;
+module_param(mac_address, charp, S_IRUGO);
+MODULE_PARM_DESC(mac_address, "MAC address of the device");
+
+
+/* device private data */
+struct ip3902_descriptor {
+	unsigned long address;
+	unsigned long control;
+};
+
+struct ip3902_rx_status {
+	unsigned long status;
+	unsigned long hash_crc;
+};
+
+struct ip3902_dma_struct {
+	struct ip3902_descriptor rx_desc[RX_RING_SIZE];
+	struct ip3902_rx_status rx_status[RX_RING_SIZE];
+	struct ip3902_descriptor tx_desc[TX_RING_SIZE];
+	unsigned long tx_status[TX_RING_SIZE];
+};
+
+struct ip3902_private {
+	spinlock_t      mii_lock;
+	struct mii_if_info  mii;
+	u32         msg_enable;
+
+	spinlock_t      lock;
+	struct net_device   *ndev;
+	struct platform_device  *pdev;
+	struct resource     *bus;
+	void __iomem        *mem;
+	struct napi_struct napi;
+
+	struct ip3902_dma_struct *ds;		/* descriptors and statuses */
+	dma_addr_t ds_dma;
+
+	struct sk_buff *rx_skb[RX_RING_SIZE];	/* where to recieve to */
+	struct sk_buff *tx_skb[TX_RING_SIZE];	/* where to send from */
+	bool tx_first_desc[TX_RING_SIZE];	/* true if this is the first desc
of an skb */
+
+	int rx_next_allocate;			/* index in rx ring where skb should be allocated */
+	int rx_next_consume;			/* index in rx ring where data should be read
when available */
+	int tx_next_produce;			/* index in tx ring where new data should be put */
+	int tx_next_deallocate;			/* index in tx ring of first not freed skb */
+
+#ifdef CONFIG_INET_LRO
+	bool                use_lro;
+	int                 lro_count;
+	struct net_lro_mgr  lro_mgr;
+	struct net_lro_desc lro_desc[MAX_LRO_DESCRIPTORS];
+	struct timer_list   lro_timer;
+#endif
+
+	unsigned char running;
+	unsigned char resume_open;
+
+};
+
+static inline unsigned long ip3902_read_reg(struct net_device *ndev, int reg)
+{
+	unsigned long value = readl((void * __iomem)(ndev->base_addr + reg));
+	return value;
+}
+
+static inline void ip3902_write_reg(struct net_device *ndev, int reg,
+									unsigned long val)
+{
+	writel(val, (void * __iomem)(ndev->base_addr + reg));
+}
+
+static inline void ip3902_write_tx_desc(struct ip3902_private
*ip3902_priv, int pos, unsigned long address, unsigned long control)
+{
+#if defined(BIG_ENDIAN_DESCRIPTORS)
+	ip3902_priv->ds->tx_desc[pos].address = cpu_to_be32(address);
+	ip3902_priv->ds->tx_desc[pos].control = cpu_to_be32(control);
+#elif defined(LITTLE_ENDIAN_DESCRIPTORS)
+	ip3902_priv->ds->tx_desc[pos].address = cpu_to_le32(address);
+	ip3902_priv->ds->tx_desc[pos].control = cpu_to_le32(control);
+#else
+	ip3902_priv->ds->tx_desc[pos].address = address;
+	ip3902_priv->ds->tx_desc[pos].control = control;
+#endif
+	wmb();
+}
+
+static inline void ip3902_read_tx_desc(struct ip3902_private
*ip3902_priv, int pos, dma_addr_t *address, int *length)
+{
+#if defined(BIG_ENDIAN_DESCRIPTORS)
+	*address = (dma_addr_t)be32_to_cpu(ip3902_priv->ds->tx_desc[pos].address);
+	*length  = (int)be32_to_cpu(ip3902_priv->ds->tx_desc[pos].control) & 0xffff;
+#elif defined(LITTLE_ENDIAN_DESCRIPTORS)
+	*address = (dma_addr_t)le32_to_cpu(ip3902_priv->ds->tx_desc[pos].address);
+	*length  = (int)le32_to_cpu(ip3902_priv->ds->tx_desc[pos].control) & 0xffff;
+#else
+	*address = (dma_addr_t)ip3902_priv->ds->tx_desc[pos].address;
+	*length  = (int)ip3902_priv->ds->tx_desc[pos].control & 0xffff;
+#endif
+}
+
+static inline unsigned long ip3902_read_tx_status(struct
ip3902_private *ip3902_priv, int pos)
+{
+#if defined(BIG_ENDIAN_STATUSES)
+	return be32_to_cpu(ip3902_priv->ds->tx_status[pos]);
+#elif defined(LITTLE_ENDIAN_STATUSES)
+	return le32_to_cpu(ip3902_priv->ds->tx_status[pos]);
+#else
+	return ip3902_priv->ds->tx_status[pos];
+#endif
+}
+
+static inline void ip3902_write_rx_desc(struct ip3902_private
*ip3902_priv, int pos, unsigned long address, unsigned long control)
+{
+#if defined(BIG_ENDIAN_DESCRIPTORS)
+	ip3902_priv->ds->rx_desc[pos].address = cpu_to_be32(address);
+	ip3902_priv->ds->rx_desc[pos].control = cpu_to_be32(control);
+#elif defined(LITTLE_ENDIAN_DESCRIPTORS)
+	ip3902_priv->ds->rx_desc[pos].address = cpu_to_le32(address);
+	ip3902_priv->ds->rx_desc[pos].control = cpu_to_le32(control);
+#else
+	ip3902_priv->ds->rx_desc[pos].address = address;
+	ip3902_priv->ds->rx_desc[pos].control = control;
+#endif
+	wmb();
+}
+
+static inline void ip3902_read_rx_desc(struct ip3902_private
*ip3902_priv, int pos, dma_addr_t *address, int *length)
+{
+#if defined(BIG_ENDIAN_DESCRIPTORS)
+	*address = (dma_addr_t)be32_to_cpu(ip3902_priv->ds->rx_desc[pos].address);
+	*length  = (int)be32_to_cpu(ip3902_priv->ds->rx_desc[pos].control) & 0xffff;
+#elif defined(LITTLE_ENDIAN_DESCRIPTORS)
+	*address = (dma_addr_t)le32_to_cpu(ip3902_priv->ds->rx_desc[pos].address);
+	*length  = (int)le32_to_cpu(ip3902_priv->ds->rx_desc[pos].control) & 0xffff;
+#else
+	*address = (dma_addr_t)ip3902_priv->ds->rx_desc[pos].address;
+	*length  = (int)ip3902_priv->ds->rx_desc[pos].control & 0xffff;
+#endif
+}
+
+static inline unsigned long ip3902_read_rx_status(struct net_device
*ndev, struct ip3902_private *ip3902_priv, int pos)
+{
+#if defined(BIG_ENDIAN_STATUSES)
+	return be32_to_cpu(ip3902_priv->ds->rx_status[pos].status);
+#elif defined(LITTLE_ENDIAN_STATUSES)
+	return le32_to_cpu(ip3902_priv->ds->rx_status[pos].status);
+#else
+	return ip3902_priv->ds->rx_status[pos].status;
+#endif
+}
+
+static inline void ip3902_write_madr_reg(struct net_device *ndev, int
phy_id, int location)
+{
+	/* assume ranges of phy_id and location are correct - we set masks in
+	 * struct mii_if_info for that */
+
+	unsigned long val = (phy_id << 8) | location;
+	ip3902_write_reg(ndev, MADR_REG, val);
+}
+
+static inline int ip3902_wait_mdio_op_complete(struct net_device
*ndev, unsigned long mask)
+{
+	int timeout = 10000;		/* to avoid hangup in case of unexpected badness ... */
+
+	while (--timeout > 0) {
+		if ((ip3902_read_reg(ndev, MIND_REG) & mask) == 0)
+			return 0;
+		udelay(1);
+	}
+
+	return -EIO;
+}
+
+static int ip3902_mdio_read(struct net_device *ndev, int phy_id, int location)
+{
+	ip3902_write_madr_reg(ndev, phy_id, location);
+	ip3902_write_reg(ndev, MCMD_REG, 0);
+	ip3902_write_reg(ndev, MCMD_REG, MCMD_READ);
+	if (ip3902_wait_mdio_op_complete(ndev, MIND_NOT_VALID | MIND_BUSY) < 0)
+		return 0;
+	else
+		return ip3902_read_reg(ndev, MRDD_REG) & 0xffff;
+}
+
+static void ip3902_mdio_write(struct net_device *ndev, int phy_id,
int location, int val)
+{
+	ip3902_write_madr_reg(ndev, phy_id, location);
+	ip3902_write_reg(ndev, MWTD_REG, val & 0xffff);
+	ip3902_wait_mdio_op_complete(ndev, MIND_BUSY);
+}
+
+static inline int ip3902_nr_free_descs(int head, int tail, int size)
+{
+	int free;
+
+	if (head >= tail)
+		free = (tail + size) - head;
+	else
+		free = tail - head;
+
+	return free;
+}
+
+static void ip3902_eth_rx_refill_descs(struct net_device *ndev,
struct ip3902_private *ip3902_priv)
+{
+	do {
+		int rx_index = ip3902_priv->rx_next_allocate;
+		struct sk_buff *skb = netdev_alloc_skb(ndev, ETH_RX_SKB_SIZE +
dma_get_cache_alignment());
+
+		if (skb) {
+			int unaligned = (((u32)skb->data) + ETH_HLEN) &
(dma_get_cache_alignment() - 1);
+			unsigned long desc_address;
+
+			if (unaligned)
+				skb_reserve(skb, (dma_get_cache_alignment() - unaligned));
+
+			desc_address = dma_map_single(NULL, skb->data, ETH_RX_SKB_SIZE,
DMA_FROM_DEVICE);
+			ip3902_write_rx_desc(ip3902_priv, rx_index, desc_address,
(ETH_RX_SKB_SIZE - 1) | RX_CONTROL_INT);
+
+			ip3902_priv->rx_skb[rx_index] = skb;
+			ip3902_priv->rx_next_allocate = NEXT_RX(rx_index);
+		} else {
+			ip3902_write_reg(ndev, RX_CONSUME_INDEX_REG, ip3902_priv->rx_next_allocate);
+			return;
+		}
+	} while (ip3902_priv->rx_next_allocate != ip3902_priv->rx_next_consume);
+
+	ip3902_write_reg(ndev, RX_CONSUME_INDEX_REG, ip3902_priv->rx_next_allocate);
+}
+
+static int ip3902_eth_receive_queue(struct net_device *ndev, struct
ip3902_private *ip3902_priv, int budget)
+{
+	int rx_index    = ip3902_priv->rx_next_consume;
+	int write_index = ip3902_read_reg(ndev, RX_PRODUCE_INDEX_REG);
+	int received = 0;
+	int limit;
+
+	do {
+		limit = write_index;
+		spin_lock(&ip3902_priv->lock);
+		while (rx_index != limit) {
+			unsigned long status = ip3902_read_rx_status(ndev, ip3902_priv, rx_index);
+
+			if (!(status & RX_STATUS_LAST_FRAG)) {
+				printk(DRVNAME ": broken RX status: %08lx\n", status);
+				continue;
+			}
+
+			if (status & RX_STATUS_FAIL_FILTER)
+				continue;
+
+			/* Looks like hardware returns RANGE_ERROR for each frame */
+			if (status & (RX_STATUS_OVERRUN | RX_STATUS_ALIGNMENT_ERROR |
RX_STATUS_LENGTH_ERROR | RX_STATUS_CRC_ERROR)) {
+				ndev->stats.rx_errors++;
+
+				if (status & RX_STATUS_OVERRUN)
+					ndev->stats.rx_fifo_errors++;
+
+				if (status & RX_STATUS_ALIGNMENT_ERROR)
+					ndev->stats.rx_frame_errors++;
+
+				if (status & (RX_STATUS_RANGE_ERROR | RX_STATUS_LENGTH_ERROR))
+					ndev->stats.rx_length_errors++;
+
+				if (status & RX_STATUS_CRC_ERROR)
+					ndev->stats.rx_crc_errors++;
+
+			} else {
+				if (--budget < 0) {
+					/* we got packets, but no quota */
+					/* store current ring pointer state */
+					ip3902_priv->rx_next_consume = rx_index;
+					return received;
+				} else {
+					struct sk_buff *skb    = ip3902_priv->rx_skb[rx_index];
+					int             length = RX_STATUS_LENGTH(status);
+					dma_addr_t      data_addr;
+					int             data_length;
+
+					ndev->stats.rx_packets++;
+					ndev->stats.rx_bytes += length;
+					if (status & RX_STATUS_MULTICAST)
+						ndev->stats.multicast++;
+
+					skb_put(skb, length - BYTES_IN_ETHERNET_CRC);
+					skb->protocol = eth_type_trans(skb, ndev);
+
+#ifdef CONFIG_INET_LRO
+					if (ip3902_priv->use_lro)
+						lro_receive_skb(&ip3902_priv->lro_mgr, skb, ip3902_priv);
+					else
+						netif_receive_skb(skb);
+
+					ip3902_priv->lro_count++;
+#else
+					netif_receive_skb(skb);
+#endif
+
+					ip3902_read_rx_desc(ip3902_priv, rx_index, &data_addr, &data_length);
+					dma_unmap_single(NULL, data_addr, ETH_RX_SKB_SIZE, DMA_FROM_DEVICE);
+
+					ip3902_priv->rx_skb[rx_index] = NULL;
+					ndev->last_rx = jiffies;
+					received++;
+				}
+			}
+			rx_index = NEXT_RX(rx_index);
+		}
+
+		spin_unlock(&ip3902_priv->lock);
+		ip3902_priv->rx_next_consume = rx_index;
+		ip3902_eth_rx_refill_descs(ndev, ip3902_priv);
+		write_index = ip3902_read_reg(ndev, RX_PRODUCE_INDEX_REG);
+	} while (limit != write_index);
+
+#ifdef CONFIG_INET_LRO
+	if (ip3902_priv->use_lro) {
+		if (timer_pending(&ip3902_priv->lro_timer)) {
+			mod_timer(&ip3902_priv->lro_timer, jiffies + 2);
+		} else {
+			ip3902_priv->lro_timer.expires  = jiffies + 2;
+			add_timer(&ip3902_priv->lro_timer);
+		}
+	}
+#endif
+
+	return received;
+}
+
+static int ip3902_poll(struct napi_struct *napi, int budget)
+{
+	struct ip3902_private *ip3902_priv = container_of(napi, struct
ip3902_private, napi);
+	struct net_device *ndev = ip3902_priv->ndev;
+	int work_done;
+
+	work_done = ip3902_eth_receive_queue(ndev, ip3902_priv, budget);
+
+	if (work_done < budget) {
+		ip3902_write_reg(ndev, INT_CLEAR_REG, RX_DONE_INT);
+		ip3902_write_reg(ndev, INT_CLEAR_REG, 0);
+		netif_rx_complete(ndev, napi);
+		ip3902_write_reg(ndev, INT_ENABLE_REG, (TX_UNDERRUN_INT |
RX_DONE_INT | RX_OVERRUN_INT));
+	}
+
+	return work_done;
+}
+
+#ifdef CONFIG_INET_LRO
+static void ip3902_lro_timeout(unsigned long data)
+{
+	struct ip3902_private *ip3902_priv = (struct ip3902_private *)data;
+
+	spin_lock(&ip3902_priv->lock);
+	if (ip3902_priv->lro_count <= LRO_THRESHOLD) {
+		ip3902_priv->use_lro = false;
+		ip3902_priv->lro_count = 0;
+	}
+	lro_flush_all(&ip3902_priv->lro_mgr);
+	spin_unlock(&ip3902_priv->lock);
+}
+#endif
+
+#define ip3902_eth_free_completed_tx_descs(ndev, priv)
ip3902_eth_free_tx_descs(ndev, priv, 0)
+#define ip3902_eth_free_all_tx_descs(ndev, priv)
ip3902_eth_free_tx_descs(ndev, priv, 1)
+
+static void ip3902_eth_free_tx_descs(struct net_device *ndev, struct
ip3902_private *ip3902_priv, int force)
+{
+	int limit;
+
+	if (force)
+		limit = ip3902_priv->tx_next_produce;
+	else
+		limit = ip3902_read_reg(ndev, TX_CONSUME_INDEX_REG);
+
+	while (ip3902_priv->tx_next_deallocate != limit) {
+		int             length;
+		int             tx_index;
+		unsigned long   status;
+		dma_addr_t      addr;
+		struct sk_buff *skb;
+
+		tx_index = ip3902_priv->tx_next_deallocate;
+
+		ip3902_priv->tx_next_deallocate = NEXT_TX(tx_index);
+		ip3902_read_tx_desc(ip3902_priv, tx_index, &addr, &length);
+		skb = ip3902_priv->tx_skb[tx_index];
+
+		status = ip3902_read_tx_status(ip3902_priv, tx_index);
+		if (status & TX_STATUS_ERROR) {
+			ndev->stats.tx_errors++;
+			if (status & TX_STATUS_LATE_COLLISION)
+				ndev->stats.tx_aborted_errors++;
+
+			if (status & (TX_STATUS_MANY_COLLISIONS | TX_STATUS_MANY_DEFER))
+				ndev->stats.tx_window_errors++;
+
+		} else {
+			ndev->stats.tx_packets++;
+			ndev->stats.tx_bytes   += skb->len;
+			ndev->stats.collisions += TX_STATUS_COLLISIONS(status);
+		}
+
+		if (skb)
+			ip3902_priv->tx_skb[tx_index] = NULL;
+
+		if (ip3902_priv->tx_first_desc[tx_index] == true)
+			dma_unmap_single(NULL, addr, length, DMA_TO_DEVICE);
+		else
+			dma_unmap_page(NULL, addr, length, DMA_TO_DEVICE);
+
+		if (skb)
+			dev_kfree_skb_irq(skb);
+	}
+}
+
+static void ip3902_reset_tx(struct net_device *ndev, struct
ip3902_private *ip3902_priv, int initial)
+{
+	unsigned long val;
+
+	/* Reset Tx hardware */
+	val = ip3902_read_reg(ndev, COMMAND_REG);
+	val &= ~(COMMAND_TX_RT_ENABLE | COMMAND_TX_ENABLE);
+	val |= COMMAND_TX_RESET;
+	ip3902_write_reg(ndev, COMMAND_REG, val);
+
+	if (!initial)
+		ip3902_eth_free_all_tx_descs(ndev, ip3902_priv);
+
+	ip3902_priv->tx_next_produce    = 0;
+	ip3902_priv->tx_next_deallocate = 0;
+
+	/* Configure Tx registers */
+	ip3902_write_reg(ndev, TX_DESC_REG,   ip3902_priv->ds_dma +
offsetof(struct ip3902_dma_struct, tx_desc));
+	ip3902_write_reg(ndev, TX_STATUS_REG, ip3902_priv->ds_dma +
offsetof(struct ip3902_dma_struct, tx_status));
+	ip3902_write_reg(ndev, TX_DESC_NUMBER_REG, TX_RING_SIZE - 1);
+	ip3902_write_reg(ndev, TX_PRODUCE_INDEX_REG, ip3902_priv->tx_next_produce);
+	ip3902_write_reg(ndev, TX_CONSUME_INDEX_REG, ip3902_priv->tx_next_deallocate);
+}
+
+static void ip3902_reset_rx(struct net_device *ndev, struct
ip3902_private *ip3902_priv, int init)
+{
+	unsigned long val;
+
+	/* Reset Rx hardware */
+	val = ip3902_read_reg(ndev, COMMAND_REG);
+	val &= ~COMMAND_RX_ENABLE;
+	val |= COMMAND_RX_RESET;
+	ip3902_write_reg(ndev, COMMAND_REG, val);
+
+	/* Set maximum frame size register */
+	ip3902_write_reg(ndev, MAXF_REG, ETH_RX_SKB_SIZE);
+
+	if (init) {
+		ip3902_priv->rx_next_allocate   = 0;
+		ip3902_priv->rx_next_consume    = 0;
+		ip3902_eth_rx_refill_descs(ndev, ip3902_priv);
+	}
+
+	/* Prepare skb's for Rx (any skb's already prepared will be reused)
+	 * and configure Rx registers */
+	ip3902_write_reg(ndev, RX_DESC_REG, ip3902_priv->ds_dma +
offsetof(struct ip3902_dma_struct, rx_desc));
+	ip3902_write_reg(ndev, RX_STATUS_REG, ip3902_priv->ds_dma +
offsetof(struct ip3902_dma_struct, rx_status));
+	ip3902_write_reg(ndev, RX_DESC_NUMBER_REG, RX_RING_SIZE - 1);
+	ip3902_write_reg(ndev, RX_PRODUCE_INDEX_REG, ip3902_priv->rx_next_consume);
+}
+
+static inline void ip3902_start_tx(struct net_device *ndev)
+{
+	unsigned long val;
+
+	val = ip3902_read_reg(ndev, COMMAND_REG);
+	val |= COMMAND_TX_ENABLE;
+	ip3902_write_reg(ndev, COMMAND_REG, val);
+}
+
+static inline void ip3902_start_rx(struct net_device *ndev)
+{
+	unsigned long val;
+
+	/* First on high-level ... */
+	val = ip3902_read_reg(ndev, COMMAND_REG);
+	val |= (COMMAND_RX_ENABLE | COMMAND_ALLOW_SHORT);
+	ip3902_write_reg(ndev, COMMAND_REG, val);
+
+	/* ... and then on low-level (after high level is ready to receive) */
+	val = ip3902_read_reg(ndev, MAC1_REG);
+	val |= MAC1_RECEIVE_ENABLE;	/* flow control frames won't be passed
to driver */
+	ip3902_write_reg(ndev, MAC1_REG, val);
+}
+
+/* Interrupt handler body - split out to use both in interrupt handler
+ * and in net poll controller.
+ *
+ * Internal routine, called with lock held. */
+static void ip3902_do_handle_interrupt(struct net_device *ndev,
struct ip3902_private *ip3902_priv, unsigned long status)
+{
+	ip3902_write_reg(ndev, INT_CLEAR_REG, status);
+	ip3902_write_reg(ndev, INT_CLEAR_REG, 0);
+
+	if (status & TX_UNDERRUN_INT) {
+		printk(KERN_ERR DRVNAME ": %s: fatal Tx underrun, resetting Tx\n",
ndev->name);
+		ip3902_reset_tx(ndev, ip3902_priv, 0);
+		ip3902_start_tx(ndev);
+	}
+
+	if (status & RX_OVERRUN_INT) {
+		printk(KERN_ERR DRVNAME ": %s: fatal Rx overrun, resetting Rx\n",
ndev->name);
+		ip3902_reset_rx(ndev, ip3902_priv, 0);
+		ip3902_start_rx(ndev);
+	} else if (status & RX_DONE_INT) {
+		/* Disable the Rx interrupt */
+		ip3902_write_reg(ndev, INT_ENABLE_REG, (RX_OVERRUN_INT | TX_UNDERRUN_INT));
+		netif_rx_schedule(ndev, &ip3902_priv->napi);
+	}
+}
+
+static irqreturn_t ip3902_interrupt(int irq, void *dev_instance)
+{
+	struct net_device *ndev = (struct net_device *) dev_instance;
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+	unsigned long status;
+
+	status = ip3902_read_reg(ndev, INT_STATUS_REG) & (TX_DONE_INT |
TX_UNDERRUN_INT | RX_DONE_INT | RX_OVERRUN_INT);
+	do {
+		if (!status) {
+			return IRQ_NONE;
+		} else {
+			ip3902_do_handle_interrupt(ndev, ip3902_priv, status);
+			status = ip3902_read_reg(ndev, INT_STATUS_REG) & (TX_DONE_INT |
TX_UNDERRUN_INT | RX_DONE_INT | RX_OVERRUN_INT);
+		}
+	} while (status);
+
+	return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+static void ip3902_net_poll(struct net_device *ndev)
+{
+	disable_irq_lockdep(ndev->irq);
+	ip3902_interrupt(ndev->irq, ndev);
+	enable_irq_lockdep(ndev->irq);
+}
+#endif
+
+static int eth_alloc_tx_desc_index(struct ip3902_private *ip3902_priv)
+{
+	int tx_desc_curr;
+
+	tx_desc_curr = ip3902_priv->tx_next_produce;
+	ip3902_priv->tx_next_produce = NEXT_TX(tx_desc_curr);
+
+	return tx_desc_curr;
+}
+
+static void eth_tx_fill_frag_descs(struct ip3902_private
*ip3902_priv, struct sk_buff *skb)
+{
+	int frag = 0;
+	int tx_index;
+
+	do {
+		skb_frag_t   *this_frag = &skb_shinfo(skb)->frags[frag];
+		unsigned long desc_address;
+		unsigned long desc_control;
+
+		tx_index = eth_alloc_tx_desc_index(ip3902_priv);
+
+		if (frag == (skb_shinfo(skb)->nr_frags - 1)) {
+			desc_control = (this_frag->size - 1) | TX_CONTROL_ALL_NOTLAST;
+			ip3902_priv->tx_skb[tx_index] = skb;
+		} else {
+			desc_control = (this_frag->size - 1) | TX_CONTROL_ALL_LAST;
+			ip3902_priv->tx_skb[tx_index] = NULL;
+		}
+
+		ip3902_priv->tx_first_desc[tx_index] = false;
+		desc_address = dma_map_page(NULL, this_frag->page,
+									this_frag->page_offset,
+									this_frag->size,
+									DMA_TO_DEVICE);
+		ip3902_write_tx_desc(ip3902_priv, tx_index, desc_address, desc_control);
+	} while (++frag < skb_shinfo(skb)->nr_frags);
+}
+
+static int ip3902_submit_skb_for_tx(struct net_device *ndev, struct
sk_buff *skb)
+{
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+	int nr_frags;
+	int free_desc;
+	int ret = 0;
+
+#ifdef CONFIG_INET_LRO
+	if (ip3902_priv->lro_count > LRO_THRESHOLD)
+		ip3902_priv->use_lro = true;
+
+	ip3902_priv->lro_count = 0;
+#endif
+
+	free_desc = ip3902_nr_free_descs(ip3902_priv->tx_next_produce,
ip3902_priv->tx_next_deallocate, TX_RING_SIZE);
+	nr_frags  = skb_shinfo(skb)->nr_frags;
+
+	if (free_desc <= nr_frags) {
+		ip3902_eth_free_completed_tx_descs(ndev, ip3902_priv);
+		free_desc = ip3902_nr_free_descs(ip3902_priv->tx_next_produce,
ip3902_priv->tx_next_deallocate, TX_RING_SIZE);
+	}
+
+	if (free_desc > nr_frags) {
+		unsigned long desc_address;
+		unsigned long desc_control;
+		int tx_index;
+		int length;
+
+		tx_index = eth_alloc_tx_desc_index(ip3902_priv);
+
+		if (nr_frags) {
+			eth_tx_fill_frag_descs(ip3902_priv, skb);
+			length = skb_headlen(skb);
+			desc_control = (length - 1) | TX_CONTROL_ALL_NOTLAST;
+			ip3902_priv->tx_skb[tx_index] = NULL;
+		} else {
+			length = skb->len;
+			desc_control = (length - 1) | TX_CONTROL_ALL_LAST;
+			ip3902_priv->tx_skb[tx_index] = skb;
+		}
+
+		ip3902_priv->tx_first_desc[tx_index] = true;
+		desc_address  = dma_map_single(NULL, skb->data, length, DMA_TO_DEVICE);
+
+		ip3902_write_tx_desc(ip3902_priv, tx_index, desc_address, desc_control);
+		ip3902_write_reg(ndev, TX_PRODUCE_INDEX_REG, ip3902_priv->tx_next_produce);
+		ip3902_eth_free_completed_tx_descs(ndev, ip3902_priv);
+	} else {
+		ret = -ENOMEM;
+	}
+
+	return ret;
+}
+
+static int ip3902_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	int ret;
+
+	BUG_ON(netif_queue_stopped(ndev));
+	BUG_ON(skb == NULL);
+	ret = ip3902_submit_skb_for_tx(ndev, skb);
+
+	if (ret) {
+		printk(KERN_ERR "%s: transmit with queue full\n", ndev->name);
+		netif_stop_queue(ndev);
+	} else {
+		ndev->stats.tx_bytes += skb->len;
+		ndev->stats.tx_packets++;
+		ndev->trans_start = jiffies;
+	}
+
+	return ret;		/* success */
+}
+
+static void ip3902_do_set_rx_filter(struct net_device *ndev, struct
ip3902_private *ip3902_priv)
+{
+	unsigned long creg, freg;
+
+	creg = ip3902_read_reg(ndev, COMMAND_REG);
+	if (ndev->flags & IFF_PROMISC) {
+		/* If interface is in promiscuous mode, just disable filter */
+		ip3902_write_reg(ndev, COMMAND_REG, creg | COMMAND_PROMISC);
+		return;
+	}
+	/* Enable filter */
+	ip3902_write_reg(ndev, COMMAND_REG, creg & ~COMMAND_PROMISC);
+
+	/* Frames for self address and broadcast frames are always accepted */
+	freg = FILTER_ACCEPT_SELF | FILTER_ACCEPT_BCAST_ANY;
+
+	if (ndev->flags & IFF_ALLMULTI) {
+		/* Accept all multicast frames */
+		freg |= FILTER_ACCEPT_MCAST_ANY;
+	} else if (ndev->mc_count > 0) {
+		/* Accept some multicast frames */
+		u64 hash_mask = 0;
+		struct dev_mc_list *mc;
+
+		freg |= FILTER_ACCEPT_MCAST_HASH;
+		for (mc = ndev->mc_list; mc; mc = mc->next) {
+			int b = (ether_crc(ETH_ALEN, mc->dmi_addr) >> 23) & 0x3f;
+			hash_mask |= (1 << b);
+		}
+		ip3902_write_reg(ndev, HASH_FILTER_L_REG, hash_mask & 0xffffffff);
+		ip3902_write_reg(ndev, HASH_FILTER_H_REG, hash_mask >> 32);
+	}
+
+	ip3902_write_reg(ndev, FILTER_CTRL_REG, freg);
+}
+
+static void ip3902_set_rx_filter(struct net_device *ndev)
+{
+	struct ip3902_private *ip3902_priv = (struct ip3902_private
*)netdev_priv(ndev);
+
+	ip3902_do_set_rx_filter(ndev, ip3902_priv);
+}
+
+static void set_duplex_mode(struct net_device *ndev, int duplex)
+{
+	unsigned long val;
+
+	if (duplex) {
+		/* Full Duplex mode */
+
+		val = ip3902_read_reg(ndev, MAC2_REG);
+		val |= MAC2_FULL_DUPLEX;
+		ip3902_write_reg(ndev, MAC2_REG, val);
+
+		ip3902_write_reg(ndev, IPGT_REG, IPGT_FD_VALUE);
+
+		val = ip3902_read_reg(ndev, COMMAND_REG);
+		val |= COMMAND_FULL_DUPLEX;
+		ip3902_write_reg(ndev, COMMAND_REG, val);
+	} else {
+		/* Half Duplex mode */
+
+		val = ip3902_read_reg(ndev, MAC2_REG);
+		val &= ~MAC2_FULL_DUPLEX;
+		ip3902_write_reg(ndev, MAC2_REG, val);
+
+		ip3902_write_reg(ndev, IPGT_REG, IPGT_HD_VALUE);
+
+		val = ip3902_read_reg(ndev, COMMAND_REG);
+		val &= ~COMMAND_FULL_DUPLEX;
+		ip3902_write_reg(ndev, COMMAND_REG, val);
+	}
+}
+
+static int ip3902_ioctl(struct net_device *ndev, struct ifreq *req, int cmd)
+{
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+	unsigned int duplex_changed;
+	unsigned long flags;
+	int rc;
+
+	if (!netif_running(ndev))
+		return -EINVAL;
+
+	spin_lock_irqsave(&ip3902_priv->mii_lock, flags);
+	rc = generic_mii_ioctl(&ip3902_priv->mii, if_mii(req), cmd, &duplex_changed);
+	spin_unlock_irqrestore(&ip3902_priv->mii_lock, flags);
+	if (duplex_changed)
+		set_duplex_mode(ndev, ip3902_priv->mii.full_duplex);
+
+	return rc;
+}
+
+/* ethtool ops */
+
+static void ip3902_get_drvinfo(struct net_device *ndev,
+							   struct ethtool_drvinfo *info)
+{
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+
+	strcpy(info->driver, DRVNAME);
+	strcpy(info->version, DRVVERSION);
+	strcpy(info->bus_info, ip3902_priv->ndev->name);
+}
+
+static int ip3902_get_settings(struct net_device *ndev, struct
ethtool_cmd *cmd)
+{
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ip3902_priv->mii_lock, flags);
+	mii_ethtool_gset(&ip3902_priv->mii, cmd);
+	spin_lock_irqsave(&ip3902_priv->mii_lock, flags);
+
+	return 0;
+}
+
+static int ip3902_set_settings(struct net_device *ndev, struct
ethtool_cmd *cmd)
+{
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+	unsigned long flags;
+	int rc;
+
+	spin_lock_irqsave(&ip3902_priv->mii_lock, flags);
+	rc = mii_ethtool_sset(&ip3902_priv->mii, cmd);
+	spin_lock_irqsave(&ip3902_priv->mii_lock, flags);
+
+	return rc;
+}
+
+static int ip3902_nway_reset(struct net_device *ndev)
+{
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+	return mii_nway_restart(&ip3902_priv->mii);
+}
+
+static u32 ip3902_get_link(struct net_device *ndev)
+{
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+	return mii_link_ok(&ip3902_priv->mii);
+}
+
+static const struct ethtool_ops ip3902_ethtool_ops = {
+	.get_drvinfo    = ip3902_get_drvinfo,
+	.get_settings   = ip3902_get_settings,
+	.set_settings   = ip3902_set_settings,
+	.nway_reset     = ip3902_nway_reset,
+	.get_link       = ip3902_get_link,
+	.get_sg         = ethtool_op_get_sg,
+	.set_sg         = ethtool_op_set_sg,
+};
+
+/* setup code */
+
+static void ip3902_eth_update_mac_address(struct net_device *ndev)
+{
+	ip3902_write_reg(ndev, SA0_REG, (ndev->dev_addr[5] << 8) | ndev->dev_addr[4]);
+	ip3902_write_reg(ndev, SA1_REG, (ndev->dev_addr[3] << 8) | ndev->dev_addr[2]);
+	ip3902_write_reg(ndev, SA2_REG, (ndev->dev_addr[1] << 8) | ndev->dev_addr[0]);
+}
+
+static int ip3902_eth_set_mac_address(struct net_device *ndev, void *addr)
+{
+	int i;
+
+	for (i = 0; i < 6; i++)
+		/* +2 is for the offset of the HW addr type */
+		ndev->dev_addr[i] = ((unsigned char *)addr)[i + 2];
+
+	ip3902_eth_update_mac_address(ndev);
+	return 0;
+}
+
+static void ip3902_hw_deinit(struct net_device *ndev)
+{
+	unsigned long val;
+
+	/* Stop Rx and Tx hardware and disable interrupts */
+	val = ip3902_read_reg(ndev, COMMAND_REG);
+	val &= ~(COMMAND_TX_ENABLE | COMMAND_RX_ENABLE);
+	ip3902_write_reg(ndev, COMMAND_REG, val);
+	ip3902_write_reg(ndev, INT_ENABLE_REG, 0);
+
+	/* Put low-level hardware into reset, and high-level into poweroff */
+	ip3902_write_reg(ndev, MAC1_REG, MAC1_SOFT_RESET);
+	ip3902_write_reg(ndev, POWERDOWN_REG, POWERDOWN_VALUE);
+}
+
+static int ethernet_phy_get(struct net_device *ndev)
+{
+	int addr;
+
+	for (addr = 1; addr < 32; addr++) {
+		int stat;
+		stat = ip3902_mdio_read(ndev, addr, MII_BMSR);
+		if ((stat != 0) && (stat != 0xffff))
+			return addr;
+	}
+	printk(KERN_ERR DRVNAME ": could not locate PHY\n");
+	return -EIO;
+}
+
+static int ip3902_hw_init(struct net_device *ndev, struct
ip3902_private *ip3902_priv)
+{
+	int ret = 0;
+
+	/* Poweron hardware */
+	ip3902_write_reg(ndev, POWERDOWN_REG, 0);
+
+	/* Move low level out of reset (also initialize the registers)*/
+	ip3902_write_reg(ndev, MAC1_REG, 0);
+	ip3902_write_reg(ndev, MAC2_REG, INITIAL_MAC2);
+
+	ip3902_priv->mii.phy_id = ethernet_phy_get(ndev);
+
+	if (ip3902_priv->mii.phy_id < 0) {
+		ret = ip3902_priv->mii.phy_id;
+	} else {
+		ip3902_eth_update_mac_address(ndev);
+
+		/* "Initialize" command register (before resets - those routines
+		 * use read-modify-write operations on that register */
+		ip3902_write_reg(ndev, COMMAND_REG, COMMAND_ALLOW_SHORT);
+
+		/* Reset and configure Rx and Tx */
+		ip3902_reset_tx(ndev, ip3902_priv, 1);
+		ip3902_reset_rx(ndev, ip3902_priv, 1);
+
+		/* Initialize Rx filtering */
+		ip3902_do_set_rx_filter(ndev, ip3902_priv);
+
+		/* Clear all interrupts, and enable interesting interrupts */
+		ip3902_write_reg(ndev, INT_CLEAR_REG, 0xffffffff);
+		ip3902_write_reg(ndev, INT_CLEAR_REG, 0);
+		ip3902_write_reg(ndev, INT_ENABLE_REG, (TX_UNDERRUN_INT |
RX_DONE_INT | RX_OVERRUN_INT));
+
+		/* Start Tx and Rx hardware */
+		ip3902_start_tx(ndev);
+		ip3902_start_rx(ndev);
+	}
+	return 0;
+}
+
+static int ip3902_open(struct net_device *ndev)
+{
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+	int ret;
+
+	dev_dbg(&ip3902_priv->ndev->dev, "%s: open\n", ndev->name);
+
+	ret = request_irq(ndev->irq, ip3902_interrupt, 0, ndev->name, ndev);
+	if (ret)
+		return ret;
+
+	ret = ip3902_hw_init(ndev, ip3902_priv);
+
+	if (ret)
+		return ret;
+
+	mii_check_media(&ip3902_priv->mii, netif_msg_link(ip3902_priv), 1);
+	set_duplex_mode(ndev, ip3902_priv->mii.full_duplex);
+
+#ifdef CONFIG_INET_LRO
+	init_timer(&ip3902_priv->lro_timer);
+	ip3902_priv->lro_timer.data     = (unsigned long) ip3902_priv;
+	ip3902_priv->lro_timer.function = ip3902_lro_timeout;
+#endif
+
+	netif_start_queue(ndev);
+	napi_enable(&ip3902_priv->napi);
+
+	ip3902_priv->running = 1;
+
+	return 0;
+}
+
+static int ip3902_close(struct net_device *ndev)
+{
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+
+	dev_dbg(&ip3902_priv->ndev->dev, "%s: close\n", ndev->name);
+
+	ip3902_priv->running = 0;
+	wmb();
+
+#ifdef CONFIG_INET_LRO
+	del_timer(&ip3902_priv->lro_timer);
+#endif
+
+	napi_disable(&ip3902_priv->napi);
+
+	ip3902_hw_deinit(ndev);
+
+	netif_stop_queue(ndev);
+
+	ip3902_eth_free_all_tx_descs(ndev, ip3902_priv);
+
+	free_irq(ndev->irq, ndev);
+	return 0;
+}
+
+static int parse_mac_address(struct net_device *ndev)
+{
+	int n = sscanf(mac_address, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+				   &ndev->dev_addr[0], &ndev->dev_addr[1],
+				   &ndev->dev_addr[2], &ndev->dev_addr[3],
+				   &ndev->dev_addr[4], &ndev->dev_addr[5]);
+
+	if (n == 6)
+		return 0;
+
+	printk(KERN_WARNING DRVNAME": failed to parse mac address string
\"%s\"\n", mac_address);
+	return -EINVAL;
+}
+
+static void ip3902_hw_shutdown(struct net_device *ndev, struct
ip3902_private *ip3902_priv)
+{
+	dma_free_coherent(NULL, sizeof(*(ip3902_priv->ds)), ip3902_priv->ds,
ip3902_priv->ds_dma);
+}
+
+static int ip3902_hw_startup(struct net_device *ndev, struct
ip3902_private *ip3902_priv)
+{
+	ip3902_priv->ds = dma_alloc_coherent(NULL,
sizeof(*(ip3902_priv->ds)), &ip3902_priv->ds_dma, GFP_KERNEL);
+	if (!ip3902_priv->ds) {
+		printk(KERN_ERR DRVNAME ": can't allocate DMA structure\n");
+		ip3902_hw_shutdown(ndev, ip3902_priv);
+		return -ENOMEM;
+	}
+
+	/* Poweron hardware */
+	ip3902_write_reg(ndev, POWERDOWN_REG, 0);
+
+	/* set mii clock */
+	ip3902_write_reg(ndev, MCFG_REG, 0x1c);
+
+	/* Move low level out of reset (also initialize the registers)*/
+	ip3902_write_reg(ndev, MAC1_REG, 0);
+	ip3902_write_reg(ndev, MAC2_REG, INITIAL_MAC2);
+
+	if (!mac_address || parse_mac_address(ndev) < 0) {
+		unsigned long val;
+
+		val = ip3902_read_reg(ndev, SA0_REG);
+		ndev->dev_addr[5] = (val >> 8) & 255;
+		ndev->dev_addr[4] = val & 255;
+		val = ip3902_read_reg(ndev, SA1_REG);
+		ndev->dev_addr[3] = (val >> 8) & 255;
+		ndev->dev_addr[2] = val & 255;
+		val = ip3902_read_reg(ndev, SA2_REG);
+		ndev->dev_addr[1] = (val >> 8) & 255;
+		ndev->dev_addr[0] = val & 255;
+	}
+
+	/* Put low-level hardware into reset, and high-level into poweroff */
+	ip3902_write_reg(ndev, MAC1_REG, MAC1_SOFT_RESET);
+	ip3902_write_reg(ndev, POWERDOWN_REG, POWERDOWN_VALUE);
+
+	return 0;
+}
+
+static int ip3902_init_dev(struct net_device *ndev, struct
ip3902_private *ip3902_priv)
+{
+	int ret;
+
+	ret = ip3902_hw_startup(ndev, ip3902_priv);
+
+	if (!ret) {
+		ndev->hard_start_xmit    = ip3902_start_xmit;
+		ndev->set_mac_address    = ip3902_eth_set_mac_address;
+		ndev->set_multicast_list = ip3902_set_rx_filter;
+		ndev->open               = ip3902_open;
+		ndev->stop               = ip3902_close;
+		ndev->do_ioctl           = ip3902_ioctl;
+		ndev->features           = 0;
+#ifdef CONFIG_NET_POLL_CONTROLLER
+		ndev->poll_controller    = ip3902_net_poll;
+#endif
+		SET_ETHTOOL_OPS(ndev, &ip3902_ethtool_ops);
+
+		ip3902_priv->msg_enable     = NETIF_MSG_LINK;
+		ip3902_priv->mii.phy_id_mask    = 0x1f;
+		ip3902_priv->mii.reg_num_mask   = 0x1f;
+		ip3902_priv->mii.mdio_read  = ip3902_mdio_read;
+		ip3902_priv->mii.mdio_write = ip3902_mdio_write;
+		ip3902_priv->mii.dev        = ndev;
+
+		spin_lock_init(&ip3902_priv->lock);
+
+		ret = register_netdev(ndev);
+
+		if (ret)
+			ip3902_hw_shutdown(ndev, ip3902_priv);
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_INET_LRO
+static int ip3902_get_skb_hdr(struct sk_buff *skb, void **iphdr, void
**tcph, u64 *hdr_flags, void *priv)
+{
+	unsigned int ip_len;
+	struct iphdr *iph;
+
+	/* non tcp packet */
+	skb_reset_network_header(skb);
+	iph = ip_hdr(skb);
+	if (iph->protocol != IPPROTO_TCP)
+		return -1;
+
+	ip_len = ip_hdrlen(skb);
+	skb_set_transport_header(skb, ip_len);
+	*tcph = tcp_hdr(skb);
+
+	/* check if ip header and tcp header are complete */
+	if (iph->tot_len < ip_len + tcp_hdrlen(skb))
+		return -1;
+
+	*hdr_flags = LRO_IPV4 | LRO_TCP;
+	*iphdr = iph;
+
+	return 0;
+}
+#endif
+
+static int ip3902_remove(struct platform_device *pdev)
+{
+	struct net_device     *ndev = platform_get_drvdata(pdev);
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+
+	platform_set_drvdata(pdev, NULL);
+
+	unregister_netdev(ndev);
+
+	iounmap(ip3902_priv->mem);
+	release_resource(ip3902_priv->bus);
+	kfree(ip3902_priv->bus);
+
+	free_netdev(ndev);
+
+	return 0;
+}
+
+/* ip3902_probe
+ *
+ * This is the entry point when the platform device system uses to
+ * notify us of a new device to attach to. Allocate memory, find
+ * the resources and information passed, and map the necessary registers.
+*/
+
+static int ip3902_probe(struct platform_device *pdev)
+{
+	struct net_device     *ndev;
+	struct ip3902_private *ip3902_priv;
+	struct resource       *res;
+	size_t                 size;
+	int                    ret;
+
+	ndev = alloc_etherdev(sizeof(struct ip3902_private));
+
+	if (ndev == NULL)
+		return -ENOMEM;
+
+	ip3902_priv = netdev_priv(ndev);
+
+	memset(ip3902_priv, 0, sizeof(struct ip3902_private));
+
+	spin_lock_init(&ip3902_priv->mii_lock);
+
+	ip3902_priv->ndev = ndev;
+	ip3902_priv->pdev = pdev;
+
+	netif_napi_add(ndev, &ip3902_priv->napi, ip3902_poll, IP3902_NAPI_WEIGHT);
+
+#ifdef CONFIG_INET_LRO
+	ip3902_priv->use_lro   = false;
+	ip3902_priv->lro_count = 0;
+	ip3902_priv->lro_mgr.max_aggr = IP3902_NAPI_WEIGHT;
+	ip3902_priv->lro_mgr.max_desc = MAX_LRO_DESCRIPTORS;
+	ip3902_priv->lro_mgr.lro_arr = ip3902_priv->lro_desc;
+	ip3902_priv->lro_mgr.get_skb_header = ip3902_get_skb_hdr;
+	ip3902_priv->lro_mgr.features = LRO_F_NAPI;
+	ip3902_priv->lro_mgr.dev = ndev;
+	ip3902_priv->lro_mgr.ip_summed = 0;
+	ip3902_priv->lro_mgr.ip_summed_aggr = 0;
+#endif
+
+	platform_set_drvdata(pdev, ndev);
+
+	/* find the platform resources */
+	ndev->irq  = platform_get_irq(pdev, 0);
+	if (ndev->irq < 0) {
+		dev_err(&pdev->dev, "no IRQ specified\n");
+		ret = -ENXIO;
+		goto exit_mem;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "no MEM specified\n");
+		ret = -ENXIO;
+		goto exit_mem;
+	}
+	size = (res->end - res->start) + 1;
+
+	ip3902_priv->bus = request_mem_region(res->start & 0x1fffffff, size,
pdev->name);
+	if (ip3902_priv->bus == NULL) {
+		dev_err(&pdev->dev, "cannot reserve registers\n");
+		ret = -ENXIO;
+		goto exit_mem;
+	}
+
+	ip3902_priv->mem = ioremap(res->start & 0x1fffffff, size);
+	ndev->base_addr = (unsigned long)ip3902_priv->mem;
+
+	if (ip3902_priv->mem == NULL) {
+		dev_err(&pdev->dev, "Cannot ioremap area (%08llx,%08llx)\n",
+				(unsigned long long)res->start,
+				(unsigned long long)res->end);
+
+		ret = -ENXIO;
+		goto exit_req;
+	}
+
+	SET_NETDEV_DEV(ndev, &pdev->dev);
+
+	/* got resources, now initialise and register device */
+	ret = ip3902_init_dev(ndev, ip3902_priv);
+	if (!ret) {
+		printk(KERN_INFO "NXP ip3902 10/100 Ethernet platform driver irq %d
base %08lx\n", ndev->irq, ndev->base_addr);
+		return 0;
+	}
+
+exit_req:
+	release_resource(ip3902_priv->bus);
+	kfree(ip3902_priv->bus);
+
+exit_mem:
+	free_netdev(ndev);
+
+	return ret;
+}
+
+/* suspend and resume */
+
+#ifdef CONFIG_PM
+static int ip3902_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct net_device     *ndev = platform_get_drvdata(pdev);
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+
+	ip3902_priv->resume_open = ip3902_priv->running;
+
+	netif_device_detach(ndev);
+	ip3902_close(ndev);
+
+	return 0;
+}
+
+static int ip3902_resume(struct platform_device *pdev)
+{
+	struct net_device     *ndev = platform_get_drvdata(pdev);
+	struct ip3902_private *ip3902_priv = netdev_priv(ndev);
+
+	netif_device_attach(ndev);
+
+	if (ip3902_priv->resume_open)
+		ip3902_open(ndev);
+
+	return 0;
+}
+
+#else
+	#define ip3902_suspend NULL
+	#define ip3902_resume  NULL
+#endif
+
+static struct platform_driver ip3902drv = {
+	.driver = {
+		.name       = "ip3902-eth",
+		.owner      = THIS_MODULE,
+	},
+	.probe      = ip3902_probe,
+	.remove     = ip3902_remove,
+	.suspend    = ip3902_suspend,
+	.resume     = ip3902_resume,
+};
+
+static int __init ip3902drv_init(void)
+{
+	return platform_driver_register(&ip3902drv);
+}
+
+static void __exit ip3902drv_exit(void)
+{
+	platform_driver_unregister(&ip3902drv);
+}
+
+module_init(ip3902drv_init);
+module_exit(ip3902drv_exit);
+
+MODULE_DESCRIPTION("NXP IP3902 10/100 Ethernet platform driver");
+MODULE_AUTHOR("Chris Steel, <chris.steel@....com>");
+MODULE_LICENSE("GPL v2");
diff -urN --exclude=.svn linux-2.6.26-rc4.orig/drivers/net/Kconfig
linux-2.6.26-rc4/drivers/net/Kconfig
--- linux-2.6.26-rc4.orig/drivers/net/Kconfig	2008-06-03
10:56:55.000000000 +0100
+++ linux-2.6.26-rc4/drivers/net/Kconfig	2008-06-03 17:16:59.000000000 +0100
@@ -1884,6 +1884,15 @@
 	  Say Y here if you want to use the NE2000 compatible
 	  controller on the Renesas H8/300 processor.

+config IP3902
+	tristate "NXP IP3902 ethernet hardware support"
+	depends on SOC_PNX8335 && NET_ETHERNET
+	select MII
+	select CRC32
+	help
+	  This is a driver for NXP IP3902 ethernet hardware found
+	  in PNX8335 and probably other SOCs.
+
 source "drivers/net/fec_8xx/Kconfig"
 source "drivers/net/fs_enet/Kconfig"

diff -urN --exclude=.svn linux-2.6.26-rc4.orig/drivers/net/Makefile
linux-2.6.26-rc4/drivers/net/Makefile
--- linux-2.6.26-rc4.orig/drivers/net/Makefile	2008-06-03
10:56:55.000000000 +0100
+++ linux-2.6.26-rc4/drivers/net/Makefile	2008-06-03 17:17:11.000000000 +0100
@@ -122,6 +122,7 @@
 obj-$(CONFIG_FORCEDETH) += forcedeth.o
 obj-$(CONFIG_NE_H8300) += ne-h8300.o
 obj-$(CONFIG_AX88796) += ax88796.o
+obj-$(CONFIG_IP3902) += ip3902.o

 obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ