[<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