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 + * Daniel Laird + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 9fe8cb7..d140375 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -1882,6 +1882,16 @@ config ATL2 To compile this driver as a module, choose M here. The module will be called atl2. +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/fs_enet/Kconfig" endif # NET_ETHERNET 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