add driver for enc28j60 ethernet chip
Subsystems (2)
PATCH 1/2 add driver for enc28j60 ethernet chip
These patches add support for Microchip enc28j60 ethernet chip controlled via SPI. I tested it on my custom board (S162) with ARM9 s3c2442 SoC.
Any comments are welcome.
drivers/net/enc28j60.c | 1400 +++++++++++++++++++++++++++++++++++++++++++++ drivers/net/enc28j60_hw.h | 303 ++++++++++ 2 files changed, 1703 insertions(+), 0 deletions(-)
drivers/net/enc28j60.c new file +1400 -0
@@ -0,0 +1,1400 @@ +/* +* Microchip ENC28J60 ethernet driver (MAC + PHY) +* +* Copyright (C) 2007 Eurek srl +* Author: Claudio Lanconelli <[email protected]> +* based on enc28j60.c written by David Anders for 2.4 kernel version +* +* 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. +* +* $Id: enc28j60.c,v 1.10 2007/12/10 16:59:37 claudio Exp $ +*/ + +#include <linux/autoconf.h> + +#if CONFIG_ENC28J60_DBGLEVEL >1 +# define VERBOSE_DEBUG +#endif +#if CONFIG_ENC28J60_DBGLEVEL >0 +# define DEBUG +#endif + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <asm/semaphore.h> + +#include "enc28j60_hw.h" + +/* Buffer size required for the largest SPI transfer (i.e., reading a +* frame). */ +#define SPI_TRANSFER_BUF_LEN (4 + MAX_FRAMELEN) + +#define MY_TX_TIMEOUT ((500*HZ)/1000) + +/* Max TX retries in case of collision as suggested by errata datasheet */ +#define MAX_TX_RETRYCOUNT 16 + +/* Driver local data */ +struct enc28j60_net_local { +struct net_device_stats stats; + +struct net_device *netdev; +struct spi_device *spi; +struct semaphore semlock;/* protect spi_transfer_buf */ +uint8_t *spi_transfer_buf; +struct sk_buff *tx_skb; +struct work_struct tx_work; +struct work_struct irq_work; +int bank;/* current register bank selected */ +uint16_t next_pk_ptr;/* next packet pointer within FIFO */ +int max_pk_counter;/* statistics: max packet counter */ +int tx_retry_count; +int hw_enable; +}; + +/* Selects Full duplex vs. Half duplex mode */ +static int full_duplex =0 ; + +static int enc28j60_send_packet (struct sk_buff *skb,struct net_device *dev); +static int enc28j60_net_close (struct net_device *dev); +static struct net_device_stats *enc28j60_net_get_stats (struct net_device *dev); +static void enc28j60_set_multicast_list (struct net_device *dev); +static void enc28j60_net_tx_timeout (struct net_device *ndev); + +static int enc28j60_chipset_init (struct net_device *dev); +static void enc28j60_hw_disable (struct enc28j60_net_local *priv); +static void enc28j60_hw_enable (struct enc28j60_net_local *priv); +static void enc28j60_hw_rx (struct enc28j60_net_local *priv); +static void enc28j60_hw_tx (struct enc28j60_net_local *priv); + +/* Basic SPI operations */ +static int spi_read_buf (struct enc28j60_net_local *priv,int len, +uint8_t *data); +static int spi_write_buf (struct enc28j60_net_local *priv,int len, +const uint8_t * data); +static uint8_t spi_read_op (struct enc28j60_net_local *priv,uint8_t op, +uint8_t addr); +static int spi_write_op (struct enc28j60_net_local *priv,uint8_t op, +uint8_t addr,uint8_t val); + +/* Low level routines */ + +/* utility function for register access routines */ +static void enc28j60_set_bank (struct enc28j60_net_local *priv,uint8_t address); + +static inline int enc28j60_regb_read (struct enc28j60_net_local *priv, +uint8_t address); +static inline int enc28j60_regw_read (struct enc28j60_net_local *priv, +uint8_t address); +static inline void enc28j60_regb_write (struct enc28j60_net_local *priv, +uint8_t address,uint8_t data); +static inline void enc28j60_regw_write (struct enc28j60_net_local *priv, +uint8_t address,uint16_t data); + +static inline void enc28j60_reg_bfset (struct enc28j60_net_local *priv, +uint8_t reg,uint8_t mask); +static inline void enc28j60_reg_bfclr (struct enc28j60_net_local *priv, +uint8_t reg,uint8_t mask); + +static void enc28j60_soft_reset (struct enc28j60_net_local *priv); + +/* debug routines */ +static void dump_packet (struct enc28j60_net_local *priv,const char *msg, +int len,const char *data); +static void enc28j60_dump_tsv (struct enc28j60_net_local *priv,const char *msg, +uint8_t tsv[TSV_SIZE ]); +static void enc28j60_dump_rsv (struct enc28j60_net_local *priv,const char *msg, +uint16_t pk_ptr,int len,uint16_t sts); +static void enc28j60_dump_regs (struct enc28j60_net_local *priv, +const char *msg); + +/* +* SPI read buffer +* wait for the SPI transfer and copy received data to destination +*/ +static int +spi_read_buf (struct enc28j60_net_local *priv,int len,uint8_t *data) +{ +uint8_t *rx_buf; +uint8_t *tx_buf; +struct spi_transfer t; +struct spi_message msg; +int ret, slen; + + slen =1 ; +memset (&t,0 ,sizeof (t)); + t.tx_buf = tx_buf = priv->spi_transfer_buf; + t.rx_buf = rx_buf = priv->spi_transfer_buf +4 ; + t.len = slen + len; + +down (&priv->semlock); + tx_buf[0 ] =ENC28J60_READ_BUF_MEM ; + tx_buf[1 ] = tx_buf[2 ] = tx_buf[3 ] =0 ;/* don't care */ + +spi_message_init (&msg); +spi_message_add_tail (&t, &msg); + ret =spi_sync (priv->spi, &msg); +if (ret ==0 ) { +memcpy (data, &rx_buf[slen], len); + ret = msg.status; + } +up (&priv->semlock); +if (ret !=0 ) +dev_dbg (&priv->netdev->dev,"%s: failed: ret = %d\n" , + __FUNCTION__, ret); + +return ret; +} + +/* +* SPI write buffer +*/ +static int spi_write_buf (struct enc28j60_net_local *priv,int len, +const uint8_t * data) +{ +int ret; + +if (len >SPI_TRANSFER_BUF_LEN -1 || len <=0 ) + ret = -EINVAL ; +else { +down (&priv->semlock); + priv->spi_transfer_buf[0 ] =ENC28J60_WRITE_BUF_MEM ; +memcpy (&priv->spi_transfer_buf[1 ], data, len); + ret =spi_write (priv->spi, priv->spi_transfer_buf, len +1 ); +up (&priv->semlock); +if (ret !=0 ) +dev_dbg (&priv->netdev->dev,"%s: failed: ret = %d\n" , + __FUNCTION__, ret); + } +return ret; +} + +/* +* basic SPI read operation +*/ +static uint8_t spi_read_op (struct enc28j60_net_local *priv,uint8_t op, +uint8_t addr) +{ +uint8_t tx_buf[2 ]; +uint8_t rx_buf[4 ]; +uint8_t val =0 ; +int ret; +int slen; + + slen =1 ; +/* do dummy read if needed */ +if (addr &SPRD_MASK ) + slen++; + + tx_buf[0 ] = op | (addr &ADDR_MASK ); + ret =spi_write_then_read (priv->spi, tx_buf,1 , rx_buf, slen); +if (ret !=0 ) +dev_err (&priv->netdev->dev,"%s: failed: ret = %d\n" , + __FUNCTION__, ret); +else + val = rx_buf[slen -1 ]; + +return val; +} + +/* +* basic SPI write operation +*/ +static int spi_write_op (struct enc28j60_net_local *priv,uint8_t op, +uint8_t addr,uint8_t val) +{ +int ret; + +down (&priv->semlock); + priv->spi_transfer_buf[0 ] = op | (addr &ADDR_MASK ); + priv->spi_transfer_buf[1 ] = val; + ret =spi_write (priv->spi, priv->spi_transfer_buf,2 ); +up (&priv->semlock); +if (ret !=0 ) +dev_dbg (&priv->netdev->dev,"%s: failed: ret = %d\n" , + __FUNCTION__, ret); +return ret; +} + +/* +* Issue a soft reset command +*/ +static void enc28j60_soft_reset (struct enc28j60_net_local *priv) +{ +dev_vdbg (&priv->netdev->dev,"%s\n" , __FUNCTION__); + +spi_write_op (priv,ENC28J60_SOFT_RESET ,0 ,ENC28J60_SOFT_RESET ); +/* Errata workaround #1, CLKRDY check is unreliable, +* delay 1 mS instead */ +udelay (1000 ); +} + +/* +* select the current register bank if necessary +*/ +static void enc28j60_set_bank (struct enc28j60_net_local *priv,uint8_t addr) +{ +if ((addr &BANK_MASK ) != priv->bank) { +uint8_t b = (addr &BANK_MASK ) >>5 ; + +if (b != (ECON1_BSEL1 |ECON1_BSEL0 )) +spi_write_op (priv,ENC28J60_BIT_FIELD_CLR ,ECON1 , +ECON1_BSEL1 |ECON1_BSEL0 ); +if (b !=0 ) +spi_write_op (priv,ENC28J60_BIT_FIELD_SET ,ECON1 , b); + priv->bank = (addr &BANK_MASK ); + } +} + +/* +* Register bit field Set +*/ +static inline void enc28j60_reg_bfset (struct enc28j60_net_local *priv, +uint8_t addr,uint8_t mask) +{ +enc28j60_set_bank (priv, addr); +spi_write_op (priv,ENC28J60_BIT_FIELD_SET , addr, mask); +} + +/* +* Register bit field Clear +*/ +static inline void enc28j60_reg_bfclr (struct enc28j60_net_local *priv, +uint8_t addr,uint8_t mask) +{ +enc28j60_set_bank (priv, addr); +spi_write_op (priv,ENC28J60_BIT_FIELD_CLR , addr, mask); +} + +/* +* Register byte read +*/ +static inline int enc28j60_regb_read (struct enc28j60_net_local *priv, +uint8_t address) +{ +enc28j60_set_bank (priv, address); +return spi_read_op (priv,ENC28J60_READ_CTRL_REG , address); +} + +/* +* Register word read +*/ +static inline int enc28j60_regw_read (struct enc28j60_net_local *priv, +uint8_t address) +{ +int rl, rh; + +enc28j60_set_bank (priv, address); + rl =spi_read_op (priv,ENC28J60_READ_CTRL_REG , address); + rh =spi_read_op (priv,ENC28J60_READ_CTRL_REG , address +1 ); + +return (rh <<8 ) | rl; +} + +/* +* Register byte write +*/ +static inline void enc28j60_regb_write (struct enc28j60_net_local *priv, +uint8_t address,uint8_t data) +{ +enc28j60_set_bank (priv, address); +spi_write_op (priv,ENC28J60_WRITE_CTRL_REG , address, data); +} + +/* +* Register word write +*/ +static inline void enc28j60_regw_write (struct enc28j60_net_local *priv, +uint8_t address,uint16_t data) +{ +enc28j60_set_bank (priv, address); +spi_write_op (priv,ENC28J60_WRITE_CTRL_REG , address, (uint8_t ) data); +spi_write_op (priv,ENC28J60_WRITE_CTRL_REG , address +1 , + (uint8_t ) (data >>8 )); +} + +/* +* Buffer memory read +* Select the starting address and execute a SPI buffer read +*/ +static inline void enc28j60_mem_read (struct enc28j60_net_local *priv, +uint16_t addr,int len,uint8_t * data) +{ +enc28j60_regw_write (priv,ERDPTL , addr); + +#ifdef CONFIG_ENC28J60_WRITEVERIFY + { +uint16_t reg; + reg =enc28j60_regw_read (priv,ERDPTL ); +if (reg != addr) { +dev_dbg (&priv->netdev->dev, +"%s() error writing ERDPT (0x%04x - 0x%04x)\n" , + __FUNCTION__, reg, addr); + } + } +#endif +spi_read_buf (priv, len, data); +} + +/* +* Wait until the PHY operation is complete. +*/ +static inline int wait_phy_ready (struct enc28j60_net_local *priv) +{ +unsigned long timeout = jiffies +20 *HZ /1000 ; +int ret =1 ; + +/* 20msec timeout read */ +while ((enc28j60_regb_read (priv,MISTAT ) &MISTAT_BUSY ) !=0 ) { +if (time_after (jiffies, timeout)) { +dev_dbg (&priv->netdev->dev, +"enc28j60: PHY ready timeout!\n" ); + ret =0 ; +break ; + } +cpu_relax (); + } +return ret; +} + +/* +* PHY register read +* PHY registers are not accessed directly +*/ +static uint16_t enc28j60_phy_read (struct enc28j60_net_local *priv, +uint8_t address) +{ +/* set the PHY register address */ +enc28j60_regb_write (priv,MIREGADR , address); +/* start the register read operation */ +enc28j60_regb_write (priv,MICMD ,MICMD_MIIRD ); +udelay (11 ); +/* wait until the PHY read completes */ +wait_phy_ready (priv); +/* quit reading */ +enc28j60_regb_write (priv,MICMD ,0x00 ); +/* return the data */ +return enc28j60_regw_read (priv,MIRDL ); +} + +static int enc28j60_phy_write (struct enc28j60_net_local *priv,uint8_t address, +uint16_t data) +{ +/* set the PHY register address */ +enc28j60_regb_write (priv,MIREGADR , address); +/* write the PHY data */ +enc28j60_regw_write (priv,MIWRL , data); +udelay (11 ); +/* wait until the PHY write completes and return */ +return wait_phy_ready (priv); +} + +/* +* read MAC address registers +*/ +static void enc28j60_get_hw_macaddr (struct enc28j60_net_local *priv) +{ +struct net_device *ndev = priv->netdev; + +/* NOTE: MAC address in ENC28J60 is byte-backward */ + ndev->dev_addr[0 ] =enc28j60_regb_read (priv,MAADR5 ); + ndev->dev_addr[1 ] =enc28j60_regb_read (priv,MAADR4 ); + ndev->dev_addr[2 ] =enc28j60_regb_read (priv,MAADR3 ); + ndev->dev_addr[3 ] =enc28j60_regb_read (priv,MAADR2 ); + ndev->dev_addr[4 ] =enc28j60_regb_read (priv,MAADR1 ); + ndev->dev_addr[5 ] =enc28j60_regb_read (priv,MAADR0 ); + +dev_dbg (&priv->spi->dev, +"%s() Get MAC address %02x:%02x:%02x:%02x:%02x:%02x\n" , + __FUNCTION__, ndev->dev_addr[0 ], ndev->dev_addr[1 ], + ndev->dev_addr[2 ], ndev->dev_addr[3 ], ndev->dev_addr[4 ], + ndev->dev_addr[5 ]); +} + +/* +* Program the hardware MAC address from dev->dev_addr. +*/ +static void enc28j60_set_hw_macaddr (struct enc28j60_net_local *priv) +{ +struct net_device *ndev = priv->netdev; + +if (!priv->hw_enable) { +/* NOTE: MAC address in ENC28J60 is byte-backward */ +enc28j60_regb_write (priv,MAADR5 , ndev->dev_addr[0 ]); +enc28j60_regb_write (priv,MAADR4 , ndev->dev_addr[1 ]); +enc28j60_regb_write (priv,MAADR3 , ndev->dev_addr[2 ]); +enc28j60_regb_write (priv,MAADR2 , ndev->dev_addr[3 ]); +enc28j60_regb_write (priv,MAADR1 , ndev->dev_addr[4 ]); +enc28j60_regb_write (priv,MAADR0 , ndev->dev_addr[5 ]); + +dev_dbg (&ndev->dev, +"%s() [%s] Setting MAC address to " +"%02x:%02x:%02x:%02x:%02x:%02x\n" , + __FUNCTION__, ndev->name, ndev->dev_addr[0 ], + ndev->dev_addr[1 ], ndev->dev_addr[2 ], ndev->dev_addr[3 ], + ndev->dev_addr[4 ], ndev->dev_addr[5 ]); + }else +dev_dbg (&ndev->dev, +"%s() Warning: hw must be disabled to set hw " +"Mac address\n" , __FUNCTION__); +} + +/* +* Store the new hardware address in dev->dev_addr, and update the MAC. +*/ +static int enc28j60_set_mac_address (struct net_device *dev,void *addr) +{ +struct sockaddr *address = addr; +struct enc28j60_net_local *priv =netdev_priv (dev); + +if (!is_valid_ether_addr (address->sa_data)) +return -EADDRNOTAVAIL ; + +memcpy (dev->dev_addr, address->sa_data, dev->addr_len); +enc28j60_set_hw_macaddr (priv); + +return 0 ; +} + +static void enc28j60_dump_regs (struct enc28j60_net_local *priv,const char *msg) +{ +dev_dbg (&priv->spi->dev,"%s - HwRevID: 0x%02x\n" , msg, +enc28j60_regb_read (priv,EREVID )); + +#if CONFIG_ENC28J60_DBGLEVEL >2 +printk ("Cntrl: ECON1 ECON2 ESTAT EIR EIE\n" ); +printk (" 0x%02x " ,enc28j60_regb_read (priv,ECON1 )); +printk ("0x%02x " ,enc28j60_regb_read (priv,ECON2 )); +printk ("0x%02x " ,enc28j60_regb_read (priv,ESTAT )); +printk ("0x%02x " ,enc28j60_regb_read (priv,EIR )); +printk ("0x%02x\n" ,enc28j60_regb_read (priv,EIE )); + +printk ("MAC : MACON1 MACON3 MACON4 MAC-Address\n" ); +printk (" 0x%02x " ,enc28j60_regb_read (priv,MACON1 )); +printk ("0x%02x " ,enc28j60_regb_read (priv,MACON3 )); +printk ("0x%02x " ,enc28j60_regb_read (priv,MACON4 )); +printk ("%02x:" ,enc28j60_regb_read (priv,MAADR5 )); +printk ("%02x:" ,enc28j60_regb_read (priv,MAADR4 )); +printk ("%02x:" ,enc28j60_regb_read (priv,MAADR3 )); +printk ("%02x:" ,enc28j60_regb_read (priv,MAADR2 )); +printk ("%02x:" ,enc28j60_regb_read (priv,MAADR1 )); +printk ("%02x\n" ,enc28j60_regb_read (priv,MAADR0 )); + +printk ("Rx : ERXST ERXND ERXWRPT ERXRDPT ERXFCON EPKTCNT MAMXFL\n" ); +printk (" 0x%04x " ,enc28j60_regw_read (priv,ERXSTL )); +printk ("0x%04x " ,enc28j60_regw_read (priv,ERXNDL )); +printk ("0x%04x " ,enc28j60_regw_read (priv,ERXWRPTL )); +printk ("0x%04x " ,enc28j60_regw_read (priv,ERXRDPTL )); +printk ("0x%02x " ,enc28j60_regb_read (priv,ERXFCON )); +printk ("0x%02x " ,enc28j60_regb_read (priv,EPKTCNT )); +printk ("0x%04x\n" ,enc28j60_regw_read (priv,MAMXFLL )); + +printk ("Tx : ETXST ETXND MACLCON1 MACLCON2 MAPHSUP\n" ); +printk (" 0x%04x " ,enc28j60_regw_read (priv,ETXSTL )); +printk ("0x%04x " ,enc28j60_regw_read (priv,ETXNDL )); +printk ("0x%02x " ,enc28j60_regb_read (priv,MACLCON1 )); +printk ("0x%02x " ,enc28j60_regb_read (priv,MACLCON2 )); +printk ("0x%02x\n" ,enc28j60_regb_read (priv,MAPHSUP )); +#endif +} + +/* +* ERXRDPT need to be set always at odd addresses, refer to errata datasheet +*/ +static inline uint16_t erxrdpt_workaround (uint16_t next_packet_ptr, +uint16_t start,uint16_t end) +{ +uint16_t erxrdpt; + +if ((next_packet_ptr -1 < start) || (next_packet_ptr -1 > end)) { + erxrdpt = end; + }else + erxrdpt = next_packet_ptr -1 ; + +return erxrdpt; +} + +static void enc28j60_rxfifo_init (struct enc28j60_net_local *priv, +uint16_t start,uint16_t end) +{ +uint16_t erxrdpt; + +if (start >0x1FFF || end >0x1FFF || start > end) +dev_err (&priv->netdev->dev, +"%s(%d, %d) RXFIFO bad parameters, fatal error!!\n" , + __FUNCTION__, start, end); + + priv->next_pk_ptr = start; +/* set receive buffer start */ +enc28j60_regw_write (priv,ERXSTL , start); +/* set receive pointer address */ + erxrdpt =erxrdpt_workaround (priv->next_pk_ptr, start, end); +enc28j60_regw_write (priv,ERXRDPTL , erxrdpt); +/* set receive buffer end */ +enc28j60_regw_write (priv,ERXNDL , end); +} + +static void enc28j60_txfifo_init (struct enc28j60_net_local *priv, +uint16_t start,uint16_t end) +{ +if (start >0x1FFF || end >0x1FFF || start > end) +dev_err (&priv->netdev->dev, +"%s(%d, %d) TXFIFO bad parameters, fatal error!!\n" , + __FUNCTION__, start, end); + +/* set transmit buffer start */ +enc28j60_regw_write (priv,ETXSTL , start); +/* set transmit buffer end */ +enc28j60_regw_write (priv,ETXNDL , end); +} + +static int enc28j60_hw_init (struct enc28j60_net_local *priv) +{ +uint8_t reg; + +dev_dbg (&priv->spi->dev,"%s() - %s\n" , + __FUNCTION__, full_duplex ?"FullDuplex" :"HalfDuplex" ); +/* first soft reset the chip */ +enc28j60_soft_reset (priv); + +dev_vdbg (&priv->spi->dev,"%s() bank0\n" , __FUNCTION__); + +/* Clear ECON1 */ +spi_write_op (priv,ENC28J60_WRITE_CTRL_REG ,ECON1 ,0x00 ); + priv->bank =0 ; + priv->hw_enable =0 ; + priv->tx_retry_count =0 ; + +enc28j60_regb_write (priv,ECON2 ,ECON2_AUTOINC ); +enc28j60_rxfifo_init (priv,RXSTART_INIT ,RXEND_INIT ); +enc28j60_txfifo_init (priv,TXSTART_INIT ,TXEND_INIT ); + +/* +* Check the RevID. +* If it's 0x00 or 0xFF probably the enc28j60 is not mounted or +* damaged +*/ + reg =enc28j60_regb_read (priv,EREVID ); +if (reg ==0x00 || reg ==0xff ) +return 0 ; + +dev_vdbg (&priv->spi->dev,"%s() bank1\n" , __FUNCTION__); + +/* default filter mode: (unicast OR broadcast) AND crc valid */ +enc28j60_regb_write (priv,ERXFCON , +ERXFCON_UCEN |ERXFCON_CRCEN |ERXFCON_BCEN ); + +dev_vdbg (&priv->spi->dev,"%s() bank2\n" , __FUNCTION__); +/* enable MAC receive */ +enc28j60_regb_write (priv,MACON1 , +MACON1_MARXEN |MACON1_TXPAUS |MACON1_RXPAUS ); +/* enable automatic padding and CRC operations */ +if (full_duplex) { +enc28j60_regb_write (priv,MACON3 , +MACON3_PADCFG0 |MACON3_TXCRCEN | +MACON3_FRMLNEN |MACON3_FULDPX ); +/* set inter-frame gap (non-back-to-back) */ +enc28j60_regb_write (priv,MAIPGL ,0x12 ); +/* set inter-frame gap (back-to-back) */ +enc28j60_regb_write (priv,MABBIPG ,0x15 ); + }else { +enc28j60_regb_write (priv,MACON3 , +MACON3_PADCFG0 |MACON3_TXCRCEN | +MACON3_FRMLNEN ); +enc28j60_regb_write (priv,MACON4 ,1 <<6 );/* DEFER bit */ +/* set inter-frame gap (non-back-to-back) */ +enc28j60_regw_write (priv,MAIPGL ,0x0C12 ); +/* set inter-frame gap (back-to-back) */ +enc28j60_regb_write (priv,MABBIPG ,0x12 ); + } +/* +* MACLCON1 (default) +* MACLCON2 (default) +* Set the maximum packet size which the controller will accept +*/ +enc28j60_regw_write (priv,MAMXFLL ,MAX_FRAMELEN ); + +dev_vdbg (&priv->spi->dev,"%s() bank3\n" , __FUNCTION__); +/* NOTE: MAC address in ENC28J60 is byte-backward */ +enc28j60_regb_write (priv,MAADR5 ,ENC28J60_MAC0 ); +enc28j60_regb_write (priv,MAADR4 ,ENC28J60_MAC1 ); +enc28j60_regb_write (priv,MAADR3 ,ENC28J60_MAC2 ); +enc28j60_regb_write (priv,MAADR2 ,ENC28J60_MAC3 ); +enc28j60_regb_write (priv,MAADR1 ,ENC28J60_MAC4 ); +enc28j60_regb_write (priv,MAADR0 ,ENC28J60_MAC5 ); + +/* no loopback of transmitted frames */ +dev_vdbg (&priv->spi->dev,"%s() PHY\n" , __FUNCTION__); + +/* Configure LEDs */ +if (!enc28j60_phy_write (priv,PHLCON ,ENC28J60_LAMPS_MODE )) +return 0 ; + +if (full_duplex) { +if (!enc28j60_phy_write (priv,PHCON1 ,PHCON1_PDPXMD )) +return 0 ; +if (!enc28j60_phy_write (priv,PHCON2 ,0x00 )) +return 0 ; + }else { +if (!enc28j60_phy_write (priv,PHCON1 ,0x00 )) +return 0 ; +if (!enc28j60_phy_write (priv,PHCON2 ,PHCON2_HDLDIS )) +return 0 ; + } +enc28j60_dump_regs (priv,"enc28j60 initialized" ); + +return 1 ; +} + +static void enc28j60_hw_enable (struct enc28j60_net_local *priv) +{ +/* enable interrutps */ +dev_dbg (&priv->netdev->dev,"%s() enabling interrupts!\n" , + __FUNCTION__); + +enc28j60_reg_bfclr (priv,EIR ,EIR_DMAIF |EIR_LINKIF | +EIR_TXIF |EIR_TXERIF |EIR_RXERIF |EIR_PKTIF ); +enc28j60_regb_write (priv,EIE ,EIE_INTIE |EIE_PKTIE | +EIE_TXIE |EIE_TXERIE |EIE_RXERIE ); + +/* enable receive logic */ +enc28j60_reg_bfset (priv,ECON1 ,ECON1_RXEN ); + priv->hw_enable =1 ; +} + +static void enc28j60_hw_disable (struct enc28j60_net_local *priv) +{ +/* disable interrutps */ +enc28j60_regb_write (priv,EIE ,0x00 ); +/* disable packet reception */ +enc28j60_reg_bfclr (priv,ECON1 ,ECON1_RXEN ); + priv->hw_enable =0 ; +} + +/* +* Read the Transmit Status Vector +*/ +static inline void enc28j60_read_tsv (struct enc28j60_net_local *priv, +uint8_t tsv[TSV_SIZE ]) +{ +int endptr; + + endptr =enc28j60_regw_read (priv,ETXNDL ); +dev_vdbg (&priv->netdev->dev,"reading TSV at addr:0x%04x\n" , + endptr +1 ); +enc28j60_mem_read (priv, endptr +1 ,sizeof (tsv), tsv); +} + +static void enc28j60_dump_tsv (struct enc28j60_net_local *priv,const char *msg, +uint8_t tsv[TSV_SIZE ]) +{ +struct net_device *ndev = priv->netdev; +uint16_t tmp1, tmp2; + +dev_vdbg (&ndev->dev,"%s - TSV:\n" , msg); + tmp1 = tsv[1 ]; + tmp1 <<=8 ; + tmp1 |= tsv[0 ]; + + tmp2 = tsv[5 ]; + tmp2 <<=8 ; + tmp2 |= tsv[4 ]; + +dev_vdbg (&ndev->dev, +"ByteCount: %d, CollisionCount: %d, TotByteOnWire: %d\n" , tmp1, + tsv[2 ] &0x0f , tmp2); +dev_vdbg (&ndev->dev, +"TxDone: %d, CRCErr:%d, LenChkErr: %d, LenOutOfRange: %d\n" , +TSV_GETBIT (tsv,TSV_TXDONE ),TSV_GETBIT (tsv,TSV_TXCRCERROR ), +TSV_GETBIT (tsv,TSV_TXLENCHKERROR ), +TSV_GETBIT (tsv,TSV_TXLENOUTOFRANGE )); +dev_vdbg (&ndev->dev, +"Multicast: %d, Broadcast: %d, PacketDefer: %d, ExDefer: %d\n" , +TSV_GETBIT (tsv,TSV_TXMULTICAST ), +TSV_GETBIT (tsv,TSV_TXBROADCAST ), +TSV_GETBIT (tsv,TSV_TXPACKETDEFER ), +TSV_GETBIT (tsv,TSV_TXEXDEFER )); +dev_vdbg (&ndev->dev, +"ExCollision: %d, LateCollision: %d, Giant: %d, Underrun: %d\n" , +TSV_GETBIT (tsv,TSV_TXEXCOLLISION ), +TSV_GETBIT (tsv,TSV_TXLATECOLLISION ), +TSV_GETBIT (tsv,TSV_TXGIANT ),TSV_GETBIT (tsv,TSV_TXUNDERRUN )); +dev_vdbg (&ndev->dev, +"ControlFrame: %d, PauseFrame: %d, " +"BackPressApp: %d, VLanTagFrame: %d\n" , +TSV_GETBIT (tsv,TSV_TXCONTROLFRAME ), +TSV_GETBIT (tsv,TSV_TXPAUSEFRAME ), +TSV_GETBIT (tsv,TSV_BACKPRESSUREAPP ), +TSV_GETBIT (tsv,TSV_TXVLANTAGFRAME )); +} + +/* +* Calculate free space in RxFIFO +*/ +static inline int enc28j60_get_free_rxfifo (struct enc28j60_net_local *priv) +{ +int epkcnt, erxst, erxnd, erxwr, erxrd; +int free_space; + + epkcnt =enc28j60_regb_read (priv,EPKTCNT ); +if (epkcnt >=255 ) + free_space =-1 ; +else { + erxst =enc28j60_regw_read (priv,ERXSTL ); + erxnd =enc28j60_regw_read (priv,ERXNDL ); + erxwr =enc28j60_regw_read (priv,ERXWRPTL ); + erxrd =enc28j60_regw_read (priv,ERXRDPTL ); + +if (erxwr > erxrd) + free_space = (erxnd - erxst) - (erxwr - erxrd); +else if (erxwr == erxrd) + free_space = (erxnd - erxst); +else + free_space = erxrd - erxwr -1 ; + } +dev_vdbg (&priv->netdev->dev,"%s() free_space = %d\n" , __FUNCTION__, + free_space); + +return free_space; +} + +static void enc28j60_irq_work_handler (struct work_struct *work) +{ +struct enc28j60_net_local *priv = + container_of(work, struct enc28j60_net_local, irq_work); +int intflags, loop, pk_counter; + +dev_vdbg (&priv->netdev->dev,"%s(priv=%p)\n" , __FUNCTION__, priv); + +/* disable further interrupts */ +enc28j60_reg_bfclr (priv,EIE ,EIE_INTIE ); + +do { + loop =0 ; + intflags =enc28j60_regb_read (priv,EIR ); +/* DMA interrupt handler */ +if ((intflags &EIR_DMAIF ) !=0 ) { + loop++; +dev_vdbg (&priv->netdev->dev,"intDMA(%d)\n" , loop); +enc28j60_reg_bfclr (priv,EIR ,EIR_DMAIF ); + } +/* LINK changed handler */ +if ((intflags &EIR_LINKIF ) !=0 ) { + loop++; +dev_vdbg (&priv->netdev->dev,"intLINK(%d)\n" , loop); +/* read PHIR to clear the flag */ +enc28j60_phy_read (priv,PHIR ); + } +/* TX complete handler */ +if ((intflags &EIR_TXIF ) !=0 ) { + loop++; +dev_vdbg (&priv->netdev->dev,"intTX(%d,skb:%p)\n" , loop, + priv->tx_skb); + + priv->tx_retry_count =0 ; +if (enc28j60_regb_read (priv,ESTAT ) &ESTAT_TXABRT ) { +dev_err (&priv->netdev->dev, +"enc28j60 tx error (aborted)\n" ); + priv->stats.tx_errors++; + }else + priv->stats.tx_packets++; +if (priv->tx_skb) { +/* update statistics and free skb */ + priv->stats.tx_bytes += priv->tx_skb->len; +dev_kfree_skb (priv->tx_skb); + priv->tx_skb =NULL ; + } +netif_wake_queue (priv->netdev); +enc28j60_reg_bfclr (priv,ECON1 ,ECON1_TXRTS ); +enc28j60_reg_bfclr (priv,EIR ,EIR_TXIF ); + } +/* TX Error handler */ +if ((intflags &EIR_TXERIF ) !=0 ) { +uint8_t tsv[TSV_SIZE ]; + + loop++; +dev_vdbg (&priv->netdev->dev,"intTXErr(%d)\n" , loop); + +enc28j60_reg_bfclr (priv,ECON1 ,ECON1_TXRTS ); +enc28j60_read_tsv (priv, tsv); +enc28j60_dump_tsv (priv, __FUNCTION__, tsv); +/* Reset TX logic */ +enc28j60_reg_bfset (priv,ECON1 ,ECON1_TXRST ); +enc28j60_reg_bfclr (priv,ECON1 ,ECON1_TXRST ); +/* Transmit Late collision check for retransmit */ +if (TSV_GETBIT (tsv,TSV_TXLATECOLLISION )) { +if (priv->tx_retry_count++ <MAX_TX_RETRYCOUNT ) +enc28j60_reg_bfset (priv,ECON1 , +ECON1_TXRTS ); +else + priv->stats.tx_errors++; + }else + priv->stats.tx_errors++; +/* Clear IF flag */ +enc28j60_reg_bfclr (priv,EIR ,EIR_TXERIF ); + } +/* RX Error handler */ +if ((intflags &EIR_RXERIF ) !=0 ) { + loop++; +dev_vdbg (&priv->netdev->dev,"intRXErr(%d)\n" , loop); + +/* Check free FIFO space to flag RX overrun */ +if (enc28j60_get_free_rxfifo (priv) <=0 ) { +dev_err (&priv->netdev->dev, +"enc28j60 RX overrun\n" ); + priv->stats.rx_dropped++; + } +enc28j60_reg_bfclr (priv,EIR ,EIR_RXERIF ); + } +/* +* RX handler +* PKTIF don't work reliably!!! (look at the errata datasheet) +* check EPKTCNT is the suggested workaround +* process any packet pending (hw_rx MUST decrement PKCNT for +* any packet processed) +*/ +while ((pk_counter =enc28j60_regb_read (priv,EPKTCNT )) >0 ) { + loop++; +dev_vdbg (&priv->netdev->dev,"intRX(%d), pk_cnt: %d\n" , + loop, pk_counter); +/* update statistics */ +if (pk_counter > priv->max_pk_counter) { + priv->max_pk_counter = pk_counter; +if (priv->max_pk_counter >4 ) +dev_dbg (&priv->netdev->dev, +"enc28j60 RX, max_pk_cnt: %d\n" , + priv->max_pk_counter); +else if (priv->max_pk_counter >1 ) +dev_vdbg (&priv->netdev->dev, +"enc28j60 RX, max_pk_cnt: %d\n" , + priv->max_pk_counter); + } +/* +* don't need to clear interrupt flag, automatically done +* when enc28j60_hw_rx() decrements the packet counter +*/ +enc28j60_hw_rx (priv); + } + }while (loop); + +/* re-enable interrupts */ +enc28j60_reg_bfset (priv,EIE ,EIE_INTIE ); +dev_vdbg (&priv->netdev->dev,"%s() exit\n" , __FUNCTION__); +} + +static void dump_packet (struct enc28j60_net_local *priv,const char *msg, +int len,const char *data) +{ +#if CONFIG_ENC28J60_DBGLEVEL >2 +int k; +struct net_device *ndev = priv->netdev; + +dev_vdbg (&ndev->dev,"%s(), packet len:%d" , msg, len); +for (k =0 ; len--; k++) { +if (!(k %16 )) +printk ("\n%04x: " , k); +printk ("%02x " , data[k]); + } +printk ("\n" ); +#endif +} + +/* +* Hardware transmit function. +* Fill the buffer memory and send the contents of the transmit buffer +* onto the network +*/ +static void enc28j60_hw_tx (struct enc28j60_net_local *priv) +{ +dev_vdbg (&priv->netdev->dev,"%s(), packet len:%d\n" , + __FUNCTION__, priv->tx_skb->len); + +/* Set the write pointer to start of transmit buffer area */ +enc28j60_regw_write (priv,EWRPTL ,TXSTART_INIT ); +#ifdef CONFIG_ENC28J60_WRITEVERIFY + { +uint16_t reg; + reg =enc28j60_regw_read (priv,EWRPTL ); +if (reg !=TXSTART_INIT ) +dev_dbg (&priv->netdev->dev, +"%s() ERWPT:0x%04x != 0x%04x\n" , __FUNCTION__, + reg,TXSTART_INIT ); + } +#endif +/* Set the TXND pointer to correspond to the packet size given */ +enc28j60_regw_write (priv,ETXNDL ,TXSTART_INIT + priv->tx_skb->len); +/* write per-packet control byte */ +spi_write_op (priv,ENC28J60_WRITE_BUF_MEM ,0 ,0x00 ); +dev_vdbg (&priv->netdev->dev,"%s() after control byte ERWPT:0x%04x\n" , + __FUNCTION__,enc28j60_regw_read (priv,EWRPTL )); + +dump_packet (priv, __FUNCTION__, priv->tx_skb->len, priv->tx_skb->data); + +/* copy the packet into the transmit buffer */ +spi_write_buf (priv, priv->tx_skb->len, priv->tx_skb->data); +dev_vdbg (&priv->netdev->dev, +"%s() after write packet ERWPT:0x%04x, len=%d\n" , __FUNCTION__, +enc28j60_regw_read (priv,EWRPTL ), priv->tx_skb->len); + +#ifdef CONFIG_ENC28J60_WRITEVERIFY + {/* readback and verify written data */ +int test_len, k; +uint8_t test_buf[256 ]; +int okflag =1 ; + + test_len = priv->tx_skb->len; +if (test_len >sizeof (test_buf)) + test_len =sizeof (test_buf); + +/* + 1 to skip control byte */ +enc28j60_mem_read (priv,TXSTART_INIT +1 , test_len, test_buf); +for (k =0 ; k < test_len; k++) { +if (priv->tx_skb->data[k] != test_buf[k]) { +dev_vdbg (&priv->netdev->dev, +"%s(),Err! differ [%d] " +"0x%02x - 0x%02x\n" , + __FUNCTION__, k, priv->tx_skb->data[k], + test_buf[k]); + okflag =0 ; + } + } +if (!okflag) +dev_dbg (&priv->netdev->dev, +"%s(), Write buffer verify error!\n" , + __FUNCTION__); +else +dev_vdbg (&priv->netdev->dev, +"%s(), Write buffer verify OK\n" , + __FUNCTION__); + } +#endif +/* set TX request flag */ +enc28j60_reg_bfset (priv,ECON1 ,ECON1_TXRTS ); +} + +/* +* Read the Receive Status Vector +*/ +static void enc28j60_dump_rsv (struct enc28j60_net_local *priv,const char *msg, +uint16_t pk_ptr,int len,uint16_t sts) +{ +struct net_device *ndev = priv->netdev; + +dev_vdbg (&ndev->dev,"%s - NexPk: 0x%04x - RSV:\n" , msg, pk_ptr); +dev_vdbg (&ndev->dev,"ByteCount: %d, DribbleNibble: %d\n" , len, +RSV_GETBIT (sts,RSV_DRIBBLENIBBLE )); +dev_vdbg (&ndev->dev, +"RxOK: %d, CRCErr:%d, LenChkErr: %d, LenOutOfRange: %d\n" , +RSV_GETBIT (sts,RSV_RXOK ),RSV_GETBIT (sts,RSV_CRCERROR ), +RSV_GETBIT (sts,RSV_LENCHECKERR ), +RSV_GETBIT (sts,RSV_LENOUTOFRANGE )); +dev_vdbg (&ndev->dev, +"Multicast: %d, Broadcast: %d, " +"LongDropEvent: %d, CarrierEvent: %d\n" , +RSV_GETBIT (sts,RSV_RXMULTICAST ), +RSV_GETBIT (sts,RSV_RXBROADCAST ), +RSV_GETBIT (sts,RSV_RXLONGEVDROPEV ), +RSV_GETBIT (sts,RSV_CARRIEREV )); +dev_vdbg (&ndev->dev, +"ControlFrame: %d, PauseFrame: %d, UnknownOp: %d, " +"VLanTagFrame: %d\n" , +RSV_GETBIT (sts,RSV_RXCONTROLFRAME ), +RSV_GETBIT (sts,RSV_RXPAUSEFRAME ), +RSV_GETBIT (sts,RSV_RXUNKNOWNOPCODE ), +RSV_GETBIT (sts,RSV_RXTYPEVLAN )); +} + +/* +* Hardware receive function. +* Read the buffer memory, update the FIFO pointer, +* check the status vector and decrement the packet counter +*/ +static void enc28j60_hw_rx (struct enc28j60_net_local *priv) +{ +struct sk_buff *skb; +uint16_t erxrdpt, next_packet, rxstat; +uint8_t tmpv[6 ]; +int len; + +dev_vdbg (&priv->netdev->dev,"%s() pk_addr:0x%04x\n" , __FUNCTION__, + priv->next_pk_ptr); + +if (priv->next_pk_ptr >RXEND_INIT ) { +dev_err (&priv->netdev->dev, +"%s() Invalid packet address!! 0x%04x\n" , __FUNCTION__, + priv->next_pk_ptr); +return ; + } +/* Read next packet pointer and rx status vector */ +enc28j60_mem_read (priv, priv->next_pk_ptr,sizeof (tmpv), tmpv); + + next_packet = tmpv[1 ]; + next_packet <<=8 ; + next_packet |= tmpv[0 ]; + + len = tmpv[3 ]; + len <<=8 ; + len |= tmpv[2 ]; + + rxstat = tmpv[5 ]; + rxstat <<=8 ; + rxstat |= tmpv[4 ]; + +enc28j60_dump_rsv (priv, __FUNCTION__, next_packet, len, rxstat); + +if (!RSV_GETBIT (rxstat,RSV_RXOK )) { +dev_dbg (&priv->netdev->dev,"enc28j60: Rx Error (%04x)\n" , + rxstat); + + priv->stats.rx_errors++; +if (RSV_GETBIT (rxstat,RSV_CRCERROR )) + priv->stats.rx_crc_errors++; +if (RSV_GETBIT (rxstat,RSV_LENCHECKERR )) + priv->stats.rx_frame_errors++; + }else { + skb =dev_alloc_skb (len); +if (!skb) { +dev_err (&priv->netdev->dev, +"enc28j60: out of memory for Rx'd frame\n" ); + priv->stats.rx_dropped++; +return ; + } + skb->dev = priv->netdev; + +/* copy the packet from the receive buffer */ +enc28j60_mem_read (priv, priv->next_pk_ptr +sizeof (tmpv), len, +skb_put (skb, len)); + + priv->next_pk_ptr = next_packet; + +/* +* Move the RX read pointer to the start of the next +* received packet. +* This frees the memory we just read out +*/ + erxrdpt = +erxrdpt_workaround (priv->next_pk_ptr,RXSTART_INIT , +RXEND_INIT ); +enc28j60_regw_write (priv,ERXRDPTL , erxrdpt); + +dev_vdbg (&priv->netdev->dev, +"%s() RxSize:%d, RxStat:0x%04x ERXRDPT:0x%04x\n" , + __FUNCTION__, len, rxstat, erxrdpt); +dump_packet (priv, __FUNCTION__, skb->len, skb->data); + +/* we are done with this packet, decrement the packet counter */ +enc28j60_reg_bfset (priv,ECON2 ,ECON2_PKTDEC ); + + skb->protocol =eth_type_trans (skb, priv->netdev); + +/* update statistics */ + priv->stats.rx_packets++; + priv->stats.rx_bytes += len; + priv->netdev->last_rx = jiffies; +netif_rx (skb); + } +} + +static int enc28j60_send_packet (struct sk_buff *skb,struct net_device *dev) +{ +struct enc28j60_net_local *priv =netdev_priv (dev); + +dev_vdbg (&dev->dev,"%s()\n" , __FUNCTION__); + +/* If some error occurs while trying to transmit this +* packet, you should return '1' from this function. +* In such a case you _may not_ do anything to the +* SKB, it is still owned by the network queueing +* layer when an error is returned. This means you +* may not modify any SKB fields, you may not free +* the SKB, etc. +*/ +netif_stop_queue (dev); + +/* save the timestamp */ + priv->netdev->trans_start = jiffies; +/* Remember the skb for deferred processing */ + priv->tx_skb = skb; +schedule_work (&priv->tx_work); + +return 0 ; +} + +static void enc28j60_tx_work_handler (struct work_struct *work) +{ +struct enc28j60_net_local *priv = + container_of(work, struct enc28j60_net_local, tx_work); +dev_vdbg (&priv->netdev->dev,"%s()\n" , __FUNCTION__); + +/* actual delivery of data */ +enc28j60_hw_tx (priv); +} + +static irqreturn_t enc28j60_irq (int irq,void *dev_id) +{ +struct enc28j60_net_local *priv = dev_id; + +dev_vdbg (&priv->netdev->dev,"%s(priv=%p)\n" , __FUNCTION__, priv); + +/* +* Can't do anything in interrupt context so fire of the interrupt +* handling workqueue. +*/ +schedule_work (&priv->irq_work); + +return IRQ_HANDLED ; +} + +static void enc28j60_net_tx_timeout (struct net_device *ndev) +{ +struct enc28j60_net_local *priv =netdev_priv (ndev); + +dev_dbg (&ndev->dev,"enc28j60 transmit timed out\n" ); + +/* Reset the TX logic */ +enc28j60_reg_bfset (priv,ECON1 ,ECON1_TXRST ); +enc28j60_reg_bfclr (priv,ECON1 ,ECON1_TXRST ); +enc28j60_reg_bfclr (priv,EIR ,EIR_TXERIF |EIR_TXIF ); + priv->stats.tx_errors++; +netif_wake_queue (ndev); +} + +/* +* Open/initialize the board. This is called (in the current kernel) +* sometime after booting when the 'ifconfig' program is run. +* +* This routine should set everything up anew at each open, even +* registers that "should" only need to be set once at boot, so that +* there is non-reboot way to recover if something goes wrong. +*/ +static int enc28j60_net_open (struct net_device *dev) +{ +struct enc28j60_net_local *priv =netdev_priv (dev); + +dev_dbg (&dev->dev,"%s() enter [priv:%p]\n" , __FUNCTION__, priv); + +if (!is_valid_ether_addr (dev->dev_addr)) { +dev_err (&dev->dev,"%s() invalid MAC address\n" , __FUNCTION__); +return -EADDRNOTAVAIL ; + } + +/* Reset the hardware here */ +enc28j60_hw_disable (priv); +enc28j60_reg_bfset (priv,ECON1 ,ECON1_TXRST |ECON1_RXRST ); +enc28j60_reg_bfclr (priv,ECON1 ,ECON1_TXRST |ECON1_RXRST ); + +/* Update the MAC address (in case user has changed it) */ +enc28j60_set_hw_macaddr (priv); + +/* Enable interrupts */ +enc28j60_hw_enable (priv); + +/* We are now ready to accept transmit requests from +* the queueing layer of the networking. +*/ +netif_start_queue (dev); + +return 0 ; +} + +/* The inverse routine to net_open(). */ +static int enc28j60_net_close (struct net_device *dev) +{ +struct enc28j60_net_local *priv =netdev_priv (dev); + +dev_dbg (&dev->dev,"%s()\n" , __FUNCTION__); + +enc28j60_hw_disable (priv); +netif_stop_queue (dev); + +return 0 ; +} + +/* +* Get the current statistics. +* This may be called with the card open or closed. +*/ +static struct net_device_stats *enc28j60_net_get_stats (struct net_device *dev) +{ +struct enc28j60_net_local *priv =netdev_priv (dev); + +return &priv->stats; +} + +/* +* Set or clear the multicast filter for this adaptor. +* num_addrs == -1 Promiscuous mode, receive all packets +* num_addrs == 0 Normal mode, clear multicast list +* num_addrs > 0 Multicast mode, receive normal and MC packets, +* and do best-effort filtering. +*/ +static void enc28j60_set_multicast_list (struct net_device *dev) +{ +struct enc28j60_net_local *priv =netdev_priv (dev); + +if (!priv->hw_enable) { +if (dev->flags &IFF_PROMISC ) { +dev_dbg (&dev->dev,"%s() promiscuous mode\n" , + __FUNCTION__); +enc28j60_regb_write (priv,ERXFCON ,0x00 ); + }else if (dev->flags &IFF_ALLMULTI ) { +dev_dbg (&dev->dev,"%s() multicast mode\n" , + __FUNCTION__); +enc28j60_regb_write (priv,ERXFCON , +ERXFCON_UCEN |ERXFCON_CRCEN | +ERXFCON_BCEN |ERXFCON_MCEN ); + }else { +dev_dbg (&dev->dev,"%s() normal mode\n" , __FUNCTION__); +enc28j60_regb_write (priv,ERXFCON , +ERXFCON_UCEN |ERXFCON_CRCEN | +ERXFCON_BCEN ); + } + }else +dev_dbg (&dev->dev, +"%s() Warning: hw must be disabled to set rx filter\n" , + __FUNCTION__); +} + +static int enc28j60_chipset_init (struct net_device *dev) +{ +struct enc28j60_net_local *priv =netdev_priv (dev); + +return enc28j60_hw_init (priv); +} + +static int __devinit enc28j60_probe (struct spi_device *spi) +{ +struct net_device *dev; +struct enc28j60_net_local *priv; +int ret =0 ; + +dev_dbg (&spi->dev,"%s() start\n" , __FUNCTION__); + + dev =alloc_etherdev (sizeof (struct enc28j60_net_local )); +if (!dev) { + ret = -ENOMEM ; + goto error_alloc; + } + priv =netdev_priv (dev); + + priv->netdev = dev;/* priv to netdev reference */ + priv->spi = spi;/* priv to spi reference */ + priv->spi_transfer_buf =kmalloc (SPI_TRANSFER_BUF_LEN ,GFP_KERNEL ); +if (!priv->spi_transfer_buf) { + ret = -ENOMEM ; + goto error_buf; + } +init_MUTEX (&priv->semlock); + +INIT_WORK (&priv->tx_work, enc28j60_tx_work_handler); +INIT_WORK (&priv->irq_work, enc28j60_irq_work_handler); +dev_set_drvdata (&spi->dev, priv);/* spi to priv reference */ +SET_NETDEV_DEV (dev, &spi->dev); + +if (!enc28j60_chipset_init (dev)) { +dev_dbg (&spi->dev,"enc28j60 not found\n" ); + ret = -EIO ; + goto error_irq; + } +enc28j60_get_hw_macaddr (priv); + + ret =request_irq (spi->irq, enc28j60_irq,IRQF_TRIGGER_FALLING , +"enc28j60" , priv); +if (ret <0 ) { +dev_err (&spi->dev,"request irq %d failed (ret = %d)\n" , + spi->irq, ret); + goto error_irq; + } + + dev->irq = spi->irq; + + dev->open = enc28j60_net_open; + dev->stop = enc28j60_net_close; + dev->hard_start_xmit = enc28j60_send_packet; + dev->get_stats = enc28j60_net_get_stats; + dev->set_multicast_list = &enc28j60_set_multicast_list; + dev->set_mac_address = enc28j60_set_mac_address; + dev->tx_timeout = &enc28j60_net_tx_timeout; + dev->watchdog_timeo =MY_TX_TIMEOUT ; + + ret =register_netdev (dev); +if (ret) { +dev_err (&spi->dev, +"register netdev enc28j60 failed (ret = %d)\n" , ret); + goto error_register; + } +dev_info (&spi->dev,"%s: enc28j60 driver registered (interrupt %d)\n" , + dev->name, dev->irq); + +return 0 ; + + error_register: +free_irq (spi->irq, priv); + error_irq: +kfree (priv->spi_transfer_buf); + error_buf: +free_netdev (dev); + error_alloc: +return ret; +} + +static int enc28j60_remove (struct spi_device *spi) +{ +struct enc28j60_net_local *priv =dev_get_drvdata (&spi->dev); + +dev_dbg (&spi->dev,"%s: stop\n" , __FUNCTION__); + +unregister_netdev (priv->netdev); +free_irq (spi->irq, priv); +kfree (priv->spi_transfer_buf); +free_netdev (priv->netdev); + +return 0 ; +} + +static struct spi_driver enc28j60_driver = { + .driver = { + .name ="enc28j60" , + .bus = &spi_bus_type, + .owner =THIS_MODULE , + }, + .probe = enc28j60_probe, + .remove =__devexit_p (enc28j60_remove), +}; + +static int __init enc28j60_init (void ) +{ +return spi_register_driver (&enc28j60_driver); +} + +module_init (enc28j60_init); + +static void __exit enc28j60_exit(void) +{ +spi_unregister_driver (&enc28j60_driver); +} + +module_exit (enc28j60_exit); + +MODULE_DESCRIPTION ("ENC28J60 ethernet driver" ); +MODULE_AUTHOR ("Claudio Lanconelli <[email protected]>" ); +MODULE_LICENSE ("GPL" ); +module_param (full_duplex, int,0 ); +MODULE_PARM_DESC (full_duplex,"Enable full duplex mode" );
drivers/net/enc28j60_hw.h new file +303 -0
303 lines not on this page · raw message
Quoted 6 lines · On Tue, 11 Dec 2007 15:57:10 +0100 Claudio Lanconelli…
These patches add support for Microchip enc28j60 ethernet chip controlled via SPI.
I tested it on my custom board (S162) with ARM9 s3c2442 SoC. Any comments are welcome.Signed-off-by Claudio Lanconelli <[email protected]>
General comments:
* device driver does no carrier detection. This makes it useless for bridging, bonding, or any form of failover.
* use random_ether_addr rather than hardocoding constant mac address
* use msglevel method (via ethtool) to control debug messages rather than kernel configuration. This allows enabling debugging without recompilation which is important in distributions.
* kernel uses u8 rather than uint8_t for byte variables
* use netdev_priv(netdev) rather than netdev->priv
* Please add ethtool support
* Consider using NAPI
Output from checkpatch:
ERROR: do not initialise statics to 0 or NULL
#148: FILE: drivers/net/enc28j60.c:73:
+static int full_duplex = 0;
ERROR: "foo * bar" should be "foo *bar"
#166: FILE: drivers/net/enc28j60.c:91:
+ const uint8_t * data);
ERROR: "foo * bar" should be "foo *bar"
#245: FILE: drivers/net/enc28j60.c:170:
+ const uint8_t * data)
ERROR: "foo * bar" should be "foo *bar"
#413: FILE: drivers/net/enc28j60.c:338:
+ uint16_t addr, int len, uint8_t * data)
WARNING: printk() should include KERN_ facility level #557: FILE: drivers/net/enc28j60.c:482:
+ printk("Cntrl: ECON1 ECON2 ESTAT EIR EIE\n");
WARNING: printk() should include KERN_ facility level #558: FILE: drivers/net/enc28j60.c:483:
+ printk(" 0x%02x ", enc28j60_regb_read(priv, ECON1));
WARNING: printk() should include KERN_ facility level #564: FILE: drivers/net/enc28j60.c:489:
+ printk("MAC : MACON1 MACON3 MACON4 MAC-Address\n");
WARNING: printk() should include KERN_ facility level #565: FILE: drivers/net/enc28j60.c:490:
+ printk(" 0x%02x ", enc28j60_regb_read(priv, MACON1));
WARNING: printk() should include KERN_ facility level #575: FILE: drivers/net/enc28j60.c:500:
+ printk("Rx : ERXST ERXND ERXWRPT ERXRDPT ERXFCON EPKTCNT MAMXFL\n");
WARNING: printk() should include KERN_ facility level #576: FILE: drivers/net/enc28j60.c:501:
+ printk(" 0x%04x ", enc28j60_regw_read(priv, ERXSTL));
WARNING: printk() should include KERN_ facility level #584: FILE: drivers/net/enc28j60.c:509:
+ printk("Tx : ETXST ETXND MACLCON1 MACLCON2 MAPHSUP\n");
WARNING: printk() should include KERN_ facility level #585: FILE: drivers/net/enc28j60.c:510:
+ printk(" 0x%04x ", enc28j60_regw_read(priv, ETXSTL));
WARNING: braces {} are not necessary for single statement blocks #601: FILE: drivers/net/enc28j60.c:526:
+ if ((next_packet_ptr - 1 < start) || (next_packet_ptr - 1 > end)) {
+ erxrdpt = end;
+ } else
WARNING: line over 80 characters
#810: FILE: drivers/net/enc28j60.c:735:
+ "ExCollision: %d, LateCollision: %d, Giant: %d, Underrun: %d\n",
WARNING: line over 80 characters
#961: FILE: drivers/net/enc28j60.c:886:
+ "enc28j60 RX, max_pk_cnt: %d\n",
WARNING: line over 80 characters
#965: FILE: drivers/net/enc28j60.c:890:
+ * don't need to clear interrupt flag, automatically done
WARNING: printk() should include KERN_ facility level #987: FILE: drivers/net/enc28j60.c:912:
+ printk("\n%04x: ", k);
WARNING: labels should not be indented
#1423: FILE: drivers/net/enc28j60.c:1348:
+ error_register:
WARNING: labels should not be indented
#1425: FILE: drivers/net/enc28j60.c:1350:
+ error_irq:
WARNING: labels should not be indented
#1427: FILE: drivers/net/enc28j60.c:1352:
+ error_buf:
WARNING: labels should not be indented
#1429: FILE: drivers/net/enc28j60.c:1354:
+ error_alloc:
total: 4 errors, 17 warnings, 1703 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
My comments:
drivers/net/enc28j60.c new file +16 -0
@@ -0,0 +1,1400 @@ +/* +* Microchip ENC28J60 ethernet driver (MAC + PHY) +* +* Copyright (C) 2007 Eurek srl +* Author: Claudio Lanconelli <[email protected]> +* based on enc28j60.c written by David Anders for 2.4 kernel version +* +* 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. +* +* $Id: enc28j60.c,v 1.10 2007/12/10 16:59:37 claudio Exp $ +*/ + +#include <linux/autoconf.h>
Use msglvl instead see netdevice.h
+
+#if CONFIG_ENC28J60_DBGLEVEL > 1
+# define VERBOSE_DEBUG
+#endif
+#if CONFIG_ENC28J60_DBGLEVEL > 0
+# define DEBUG
+#endif
+
...
+
+#define MY_TX_TIMEOUT ((500*HZ)/1000)
That is a really short TX timeout, should be 2 seconds at least not 1/2 sec. Having it less than a second causes increased wakeups.
+
+/* Max TX retries in case of collision as suggested by errata datasheet */
+#define MAX_TX_RETRYCOUNT 16
+
+/* Driver local data */
+struct enc28j60_net_local {
Rename something shorter like enc28j60_net or just enc28j60?
+ struct net_device_stats stats;
net_device_stats are now in net_device.
+ struct net_device *netdev;
+ struct spi_device *spi;
+ struct semaphore semlock; /* protect spi_transfer_buf */
Use mutex (or spin_lock) rather than semaphore
+ uint8_t *spi_transfer_buf;
+ struct sk_buff *tx_skb;
+ struct work_struct tx_work;
+ struct work_struct irq_work;
Not sure why you need to have workqueue's for tx_work and irq_work, rather than using a spin_lock and doing directly.
+ int bank; /* current register bank selected */
bank is really unsigned.
+ uint16_t next_pk_ptr; /* next packet pointer within FIFO */
+ int max_pk_counter; /* statistics: max packet counter */
+ int tx_retry_count;
these are used as unsigned.
+ int hw_enable;
+};
+
+/* Selects Full duplex vs. Half duplex mode */
+static int full_duplex = 0;
Use ethtool for this.
+
+static int enc28j60_send_packet(struct sk_buff *skb, struct net_device *dev); +static int enc28j60_net_close(struct net_device *dev);
+static struct net_device_stats *enc28j60_net_get_stats(struct net_device *dev); +static void enc28j60_set_multicast_list(struct net_device *dev); +static void enc28j60_net_tx_timeout(struct net_device *ndev);
+
+static int enc28j60_chipset_init(struct net_device *dev);
+static void enc28j60_hw_disable(struct enc28j60_net_local *priv); +static void enc28j60_hw_enable(struct enc28j60_net_local *priv); +static void enc28j60_hw_rx(struct enc28j60_net_local *priv); +static void enc28j60_hw_tx(struct enc28j60_net_local *priv);
If you order functions correctly in code, you don't have to waste lots of space with all these forward declarations.
...
+ const char *msg);
+
+/*
+ * SPI read buffer
+ * wait for the SPI transfer and copy received data to destination
+ */
+static int
+spi_read_buf(struct enc28j60_net_local *priv, int len, uint8_t *data) +{
+ uint8_t *rx_buf;
+ uint8_t *tx_buf;
+ struct spi_transfer t;
+ struct spi_message msg;
+ int ret, slen;
+
+ slen = 1;
+ memset(&t, 0, sizeof(t));
+ t.tx_buf = tx_buf = priv->spi_transfer_buf;
+ t.rx_buf = rx_buf = priv->spi_transfer_buf + 4;
+ t.len = slen + len;
If you use structure initializer you can avoid having to do the memset
+
+ down(&priv->semlock);
+ tx_buf[0] = ENC28J60_READ_BUF_MEM;
+ tx_buf[1] = tx_buf[2] = tx_buf[3] = 0; /* don't care */
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&t, &msg);
+ ret = spi_sync(priv->spi, &msg);
+ if (ret == 0) {
+ memcpy(data, &rx_buf[slen], len);
+ ret = msg.status;
+ }
+ up(&priv->semlock);
+ if (ret != 0)
+ dev_dbg(&priv->netdev->dev, "%s: failed: ret = %d\n",
+ __FUNCTION__, ret);
+
+ return ret;
+}
...
+/*
+ * Register word read
+ */
+static inline int enc28j60_regw_read(struct enc28j60_net_local *priv,
+ uint8_t address)
+{
I wouldn't bother marking these as "inline" since the compiler will decide to inline in most cases. By telling the compiler to inline it may generate bigger/slower code.
+ int rl, rh;
+
+ enc28j60_set_bank(priv, address);
+ rl = spi_read_op(priv, ENC28J60_READ_CTRL_REG, address);
+ rh = spi_read_op(priv, ENC28J60_READ_CTRL_REG, address + 1);
+
+ return (rh << 8) | rl;
+}
...
+/*
+ * Program the hardware MAC address from dev->dev_addr.
+ */
+static void enc28j60_set_hw_macaddr(struct enc28j60_net_local *priv) +{
+ struct net_device *ndev = priv->netdev;
+
+ if (!priv->hw_enable) {
+ /* NOTE: MAC address in ENC28J60 is byte-backward */
+ enc28j60_regb_write(priv, MAADR5, ndev->dev_addr[0]);
+ enc28j60_regb_write(priv, MAADR4, ndev->dev_addr[1]);
+ enc28j60_regb_write(priv, MAADR3, ndev->dev_addr[2]);
+ enc28j60_regb_write(priv, MAADR2, ndev->dev_addr[3]);
+ enc28j60_regb_write(priv, MAADR1, ndev->dev_addr[4]);
+ enc28j60_regb_write(priv, MAADR0, ndev->dev_addr[5]);
+
+ dev_dbg(&ndev->dev,
+ "%s() [%s] Setting MAC address to "
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ __FUNCTION__, ndev->name, ndev->dev_addr[0],
+ ndev->dev_addr[1], ndev->dev_addr[2], ndev->dev_addr[3],
+ ndev->dev_addr[4], ndev->dev_addr[5]);
+ } else
+ dev_dbg(&ndev->dev,
+ "%s() Warning: hw must be disabled to set hw "
+ "Mac address\n", __FUNCTION__);
Should return -EINVAL/-EBUSY/... instead of printing message.
+}
+
+/*
+ * Store the new hardware address in dev->dev_addr, and update the MAC.
+ */
+static int enc28j60_set_mac_address(struct net_device *dev, void *addr) +{
+ struct sockaddr *address = addr;
+ struct enc28j60_net_local *priv = netdev_priv(dev);
+
+ if (!is_valid_ether_addr(address->sa_data))
+ return -EADDRNOTAVAIL;
+
+ memcpy(dev->dev_addr, address->sa_data, dev->addr_len);
+ enc28j60_set_hw_macaddr(priv);
+
+ return 0;
+}
...
+
+/*
+ * Get the current statistics.
+ * This may be called with the card open or closed.
+ */
+static struct net_device_stats *enc28j60_net_get_stats(struct net_device *dev) +{
+ struct enc28j60_net_local *priv = netdev_priv(dev);
+
+ return &priv->stats;
+}
If you use dev->stats, then you don't need your own get_stats function.
...
+static int enc28j60_hw_init(struct enc28j60_net_local *priv) +{
+ uint8_t reg;
+
+ dev_dbg(&priv->spi->dev, "%s() - %s\n",
+ __FUNCTION__, full_duplex ? "FullDuplex" : "HalfDuplex");
+ /* first soft reset the chip */
+ enc28j60_soft_reset(priv);
+
+ dev_vdbg(&priv->spi->dev, "%s() bank0\n", __FUNCTION__);
+
+ /* Clear ECON1 */
+ spi_write_op(priv, ENC28J60_WRITE_CTRL_REG, ECON1, 0x00);
+ priv->bank = 0;
+ priv->hw_enable = 0;
+ priv->tx_retry_count = 0;
+
+ enc28j60_regb_write(priv, ECON2, ECON2_AUTOINC);
+ enc28j60_rxfifo_init(priv, RXSTART_INIT, RXEND_INIT);
+ enc28j60_txfifo_init(priv, TXSTART_INIT, TXEND_INIT);
+
+ /*
+ * Check the RevID.
+ * If it's 0x00 or 0xFF probably the enc28j60 is not mounted or
+ * damaged
+ */
+ reg = enc28j60_regb_read(priv, EREVID);
+ if (reg == 0x00 || reg == 0xff)
+ return 0;
+
+ dev_vdbg(&priv->spi->dev, "%s() bank1\n", __FUNCTION__);
+
+ /* default filter mode: (unicast OR broadcast) AND crc valid */
+ enc28j60_regb_write(priv, ERXFCON,
+ ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_BCEN);
+
+ dev_vdbg(&priv->spi->dev, "%s() bank2\n", __FUNCTION__);
+ /* enable MAC receive */
+ enc28j60_regb_write(priv, MACON1,
+ MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS);
+ /* enable automatic padding and CRC operations */
+ if (full_duplex) {
+ enc28j60_regb_write(priv, MACON3,
+ MACON3_PADCFG0 | MACON3_TXCRCEN | + MACON3_FRMLNEN | MACON3_FULDPX);
+ /* set inter-frame gap (non-back-to-back) */
+ enc28j60_regb_write(priv, MAIPGL, 0x12);
+ /* set inter-frame gap (back-to-back) */
+ enc28j60_regb_write(priv, MABBIPG, 0x15);
+ } else {
+ enc28j60_regb_write(priv, MACON3,
+ MACON3_PADCFG0 | MACON3_TXCRCEN |
+ MACON3_FRMLNEN);
+ enc28j60_regb_write(priv, MACON4, 1 << 6); /* DEFER bit */
+ /* set inter-frame gap (non-back-to-back) */
+ enc28j60_regw_write(priv, MAIPGL, 0x0C12);
+ /* set inter-frame gap (back-to-back) */
+ enc28j60_regb_write(priv, MABBIPG, 0x12);
+ }
+ /*
+ * MACLCON1 (default)
+ * MACLCON2 (default)
+ * Set the maximum packet size which the controller will accept
+ */
+ enc28j60_regw_write(priv, MAMXFLL, MAX_FRAMELEN);
+
+ dev_vdbg(&priv->spi->dev, "%s() bank3\n", __FUNCTION__);
+ /* NOTE: MAC address in ENC28J60 is byte-backward */
+ enc28j60_regb_write(priv, MAADR5, ENC28J60_MAC0);
+ enc28j60_regb_write(priv, MAADR4, ENC28J60_MAC1);
+ enc28j60_regb_write(priv, MAADR3, ENC28J60_MAC2);
+ enc28j60_regb_write(priv, MAADR2, ENC28J60_MAC3);
+ enc28j60_regb_write(priv, MAADR1, ENC28J60_MAC4);
+ enc28j60_regb_write(priv, MAADR0, ENC28J60_MAC5);
Rather than having same address, please use random_ether_addr() to avoid problems with two devices with same ethernet address.
...
+static int __devinit enc28j60_probe(struct spi_device *spi) +{
+ struct net_device *dev;
+ struct enc28j60_net_local *priv;
+ int ret = 0;
+
+ dev_dbg(&spi->dev, "%s() start\n", __FUNCTION__);
+
+ dev = alloc_etherdev(sizeof(struct enc28j60_net_local));
+ if (!dev) {
+ ret = -ENOMEM;
+ goto error_alloc;
+ }
+ priv = netdev_priv(dev);
+
+ priv->netdev = dev; /* priv to netdev reference */
+ priv->spi = spi; /* priv to spi reference */
+ priv->spi_transfer_buf = kmalloc(SPI_TRANSFER_BUF_LEN, GFP_KERNEL);
Why not declare the transfer buffer as an array in spi?
+ if (!priv->spi_transfer_buf) {
+ ret = -ENOMEM;
+ goto error_buf;
+ }
+ init_MUTEX(&priv->semlock);
+
+ INIT_WORK(&priv->tx_work, enc28j60_tx_work_handler);
+ INIT_WORK(&priv->irq_work, enc28j60_irq_work_handler);
+ dev_set_drvdata(&spi->dev, priv); /* spi to priv reference */
+ SET_NETDEV_DEV(dev, &spi->dev);
Hi Stephen,
thank you for your suggestions.
I already applied trivial fixes, but I have questions on some points, see inline.
Quoted 4 lines · Stephen Hemminger
General comments:
* device driver does no carrier detection. This makes it useless for bridging, bonding, or any form of failover.
Quoted 9 lines
* use msglevel method (via ethtool) to control debug messages rather than kernel configuration. This allows enabling debugging without recompilation which is important in distributions.
* Please add ethtool support
* Consider using NAPI
Can you point me to a possibly simple driver that uses ethtool and NAPI? Or other example that I can use for reference. May be the skeleton should be updated.
Quoted 1 line
* use
netdev_priv(netdev)rather thannetdev->priv
I can't find where I used netdev->priv, may be do you mean priv->netdev?
Quoted 25 lines
My comments:
drivers/net/enc28j60.c new file +16
-0@@ -0,0 +1,1400 @@ +/* +* Microchip ENC28J60 ethernet driver (MAC + PHY) +* +* Copyright (C) 2007 Eurek srl +* Author: Claudio Lanconelli <[email protected]> +* based on enc28j60.c written by David Anders for 2.4 kernel version +* +* 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. +* +* $Id: enc28j60.c,v 1.10 2007/12/10 16:59:37 claudio Exp $ +*/ + +#include <linux/autoconf.h>
Quoted 2 lines
Use msglvl instead see
netdevice.h
Ok
Quoted 13 lines
drivers/net/enc28j60.c new file +10
-0+ +#if CONFIG_ENC28J60_DBGLEVEL >1 +# define VERBOSE_DEBUG +#endif +#if CONFIG_ENC28J60_DBGLEVEL >0 +# define DEBUG +#endif + + +#define MY_TX_TIMEOUT ((500*HZ)/1000)
Quoted 3 lines
That is a really short TX timeout, should be 2 seconds at least not 1/2 sec. Having it less than a second causes increased wakeups.
Ok
Quoted 9 lines
drivers/net/enc28j60.c new file +6
-0+ +/* Max TX retries in case of collision as suggested by errata datasheet */ +#define MAX_TX_RETRYCOUNT 16 + +/* Driver local data */ +struct enc28j60_net_local {Rename something shorter like
enc28j60_netor just enc28j60?
Ok, renamed enc28j60_net
Quoted 9 lines
drivers/net/enc28j60.c new file +1
-0+struct net_device_stats stats;
net_device_statsare now innet_device.drivers/net/enc28j60.c new file +3
-0+struct net_device *netdev; +struct spi_device *spi; +struct semaphore semlock;/* protect spi_transfer_buf */ Use mutex (or
spin_lock) rather than semaphore
Ok
Quoted 9 lines
drivers/net/enc28j60.c new file +4
-0+uint8_t *spi_transfer_buf; +struct sk_buff *tx_skb; +struct work_struct tx_work; +struct work_struct irq_work;Not sure why you need to have workqueue's for
tx_workandirq_work, rather than using aspin_lockand doing directly.
I need irq_work for sure because it needs to go sleep. Any
access to enc28j60 registers are through SPI blocking transaction, spi_sync(). I'm not sure if the hard_start_xmit() can go sleep, so I used a work queue to tx too.
Quoted 14 lines
drivers/net/enc28j60.c new file +1
-0+int bank;/* current register bank selected */ bank is really unsigned.
drivers/net/enc28j60.c new file +3
-0+uint16_t next_pk_ptr;/* next packet pointer within FIFO */ +int max_pk_counter;/* statistics: max packet counter */ +int tx_retry_count;these are used as unsigned.
drivers/net/enc28j60.c new file +5
-0+int hw_enable; +}; + +/* Selects Full duplex vs. Half duplex mode */ +static int full_duplex =0 ;
Quoted 2 lines
Use ethtool for this.
Ok
Quoted 13 lines
drivers/net/enc28j60.c new file +12
-0+ +static int enc28j60_send_packet (struct sk_buff *skb,struct net_device *dev); +static int enc28j60_net_close (struct net_device *dev); +static struct net_device_stats *enc28j60_net_get_stats (struct net_device *dev); +static void enc28j60_set_multicast_list (struct net_device *dev); +static void enc28j60_net_tx_timeout (struct net_device *ndev); + +static int enc28j60_chipset_init (struct net_device *dev); +static void enc28j60_hw_disable (struct enc28j60_net_local *priv); +static void enc28j60_hw_enable (struct enc28j60_net_local *priv); +static void enc28j60_hw_rx (struct enc28j60_net_local *priv); +static void enc28j60_hw_tx (struct enc28j60_net_local *priv);
Quoted 5 lines
If you order functions correctly in code, you don't have to waste lots of space with all these forward declarations.
...
Ok
Quoted 21 lines
drivers/net/enc28j60.c new file +20
-0+ const char *msg); + +/* +* SPI read buffer +* wait for the SPI transfer and copy received data to destination +*/ +static int +spi_read_buf (struct enc28j60_net_local *priv,int len,uint8_t *data) +{ +uint8_t *rx_buf; +uint8_t *tx_buf; +struct spi_transfer t; +struct spi_message msg; +int ret, slen; + + slen =1 ; +memset (&t,0 ,sizeof (t)); + t.tx_buf = tx_buf = priv->spi_transfer_buf; + t.rx_buf = rx_buf = priv->spi_transfer_buf +4 ; + t.len = slen + len;
Quoted 3 lines
If you use structure initializer you can avoid having to do the memset
Ok
Quoted 30 lines
drivers/net/enc28j60.c new file +25
-0+ +down (&priv->semlock); + tx_buf[0 ] =ENC28J60_READ_BUF_MEM ; + tx_buf[1 ] = tx_buf[2 ] = tx_buf[3 ] =0 ;/* don't care */ + +spi_message_init (&msg); +spi_message_add_tail (&t, &msg); + ret =spi_sync (priv->spi, &msg); +if (ret ==0 ) { +memcpy (data, &rx_buf[slen], len); + ret = msg.status; + } +up (&priv->semlock); +if (ret !=0 ) +dev_dbg (&priv->netdev->dev,"%s: failed: ret = %d\n" , + __FUNCTION__, ret); + +return ret; +} +/* +* Register word read +*/ +static inline int enc28j60_regw_read (struct enc28j60_net_local *priv, +uint8_t address) +{
Quoted 4 lines
I wouldn't bother marking these as "inline" since the compiler will decide to inline in most cases. By telling the compiler to inline it may generate bigger/slower code.
Ok
Quoted 39 lines
drivers/net/enc28j60.c new file +34
-0+int rl, rh; + +enc28j60_set_bank (priv, address); + rl =spi_read_op (priv,ENC28J60_READ_CTRL_REG , address); + rh =spi_read_op (priv,ENC28J60_READ_CTRL_REG , address +1 ); + +return (rh <<8 ) | rl; +} +/* +* Program the hardware MAC address from dev->dev_addr. +*/ +static void enc28j60_set_hw_macaddr (struct enc28j60_net_local *priv) +{ +struct net_device *ndev = priv->netdev; + +if (!priv->hw_enable) { +/* NOTE: MAC address in ENC28J60 is byte-backward */ +enc28j60_regb_write (priv,MAADR5 , ndev->dev_addr[0 ]); +enc28j60_regb_write (priv,MAADR4 , ndev->dev_addr[1 ]); +enc28j60_regb_write (priv,MAADR3 , ndev->dev_addr[2 ]); +enc28j60_regb_write (priv,MAADR2 , ndev->dev_addr[3 ]); +enc28j60_regb_write (priv,MAADR1 , ndev->dev_addr[4 ]); +enc28j60_regb_write (priv,MAADR0 , ndev->dev_addr[5 ]); + +dev_dbg (&ndev->dev, +"%s() [%s] Setting MAC address to " +"%02x:%02x:%02x:%02x:%02x:%02x\n" , + __FUNCTION__, ndev->name, ndev->dev_addr[0 ], + ndev->dev_addr[1 ], ndev->dev_addr[2 ], ndev->dev_addr[3 ], + ndev->dev_addr[4 ], ndev->dev_addr[5 ]); + }else +dev_dbg (&ndev->dev, +"%s() Warning: hw must be disabled to set hw " +"Mac address\n" , __FUNCTION__);
Quoted 2 lines
Should return -EINVAL/-EBUSY/... instead of printing message.
Ok
Quoted 32 lines
drivers/net/enc28j60.c new file +29
-0+} + +/* +* Store the new hardware address in dev->dev_addr, and update the MAC. +*/ +static int enc28j60_set_mac_address (struct net_device *dev,void *addr) +{ +struct sockaddr *address = addr; +struct enc28j60_net_local *priv =netdev_priv (dev); + +if (!is_valid_ether_addr (address->sa_data)) +return -EADDRNOTAVAIL ; + +memcpy (dev->dev_addr, address->sa_data, dev->addr_len); +enc28j60_set_hw_macaddr (priv); + +return 0 ; +} + +/* +* Get the current statistics. +* This may be called with the card open or closed. +*/ +static struct net_device_stats *enc28j60_net_get_stats (struct net_device *dev) +{ +struct enc28j60_net_local *priv =netdev_priv (dev); + +return &priv->stats; +}
Quoted 2 lines
If you use
dev->stats, then you don't need your ownget_statsfunction.
Ok
Quoted 78 lines
...
drivers/net/enc28j60.c new file +74
-0+static int enc28j60_hw_init (struct enc28j60_net_local *priv) +{ +uint8_t reg; + +dev_dbg (&priv->spi->dev,"%s() - %s\n" , + __FUNCTION__, full_duplex ?"FullDuplex" :"HalfDuplex" ); +/* first soft reset the chip */ +enc28j60_soft_reset (priv); + +dev_vdbg (&priv->spi->dev,"%s() bank0\n" , __FUNCTION__); + +/* Clear ECON1 */ +spi_write_op (priv,ENC28J60_WRITE_CTRL_REG ,ECON1 ,0x00 ); + priv->bank =0 ; + priv->hw_enable =0 ; + priv->tx_retry_count =0 ; + +enc28j60_regb_write (priv,ECON2 ,ECON2_AUTOINC ); +enc28j60_rxfifo_init (priv,RXSTART_INIT ,RXEND_INIT ); +enc28j60_txfifo_init (priv,TXSTART_INIT ,TXEND_INIT ); + +/* +* Check the RevID. +* If it's 0x00 or 0xFF probably the enc28j60 is not mounted or +* damaged +*/ + reg =enc28j60_regb_read (priv,EREVID ); +if (reg ==0x00 || reg ==0xff ) +return 0 ; + +dev_vdbg (&priv->spi->dev,"%s() bank1\n" , __FUNCTION__); + +/* default filter mode: (unicast OR broadcast) AND crc valid */ +enc28j60_regb_write (priv,ERXFCON , +ERXFCON_UCEN |ERXFCON_CRCEN |ERXFCON_BCEN ); + +dev_vdbg (&priv->spi->dev,"%s() bank2\n" , __FUNCTION__); +/* enable MAC receive */ +enc28j60_regb_write (priv,MACON1 , +MACON1_MARXEN |MACON1_TXPAUS |MACON1_RXPAUS ); +/* enable automatic padding and CRC operations */ +if (full_duplex) { +enc28j60_regb_write (priv,MACON3 , +MACON3_PADCFG0 |MACON3_TXCRCEN | +MACON3_FRMLNEN |MACON3_FULDPX ); +/* set inter-frame gap (non-back-to-back) */ +enc28j60_regb_write (priv,MAIPGL ,0x12 ); +/* set inter-frame gap (back-to-back) */ +enc28j60_regb_write (priv,MABBIPG ,0x15 ); + }else { +enc28j60_regb_write (priv,MACON3 , +MACON3_PADCFG0 |MACON3_TXCRCEN | +MACON3_FRMLNEN ); +enc28j60_regb_write (priv,MACON4 ,1 <<6 );/* DEFER bit */ +/* set inter-frame gap (non-back-to-back) */ +enc28j60_regw_write (priv,MAIPGL ,0x0C12 ); +/* set inter-frame gap (back-to-back) */ +enc28j60_regb_write (priv,MABBIPG ,0x12 ); + } +/* +* MACLCON1 (default) +* MACLCON2 (default) +* Set the maximum packet size which the controller will accept +*/ +enc28j60_regw_write (priv,MAMXFLL ,MAX_FRAMELEN ); + +dev_vdbg (&priv->spi->dev,"%s() bank3\n" , __FUNCTION__); +/* NOTE: MAC address in ENC28J60 is byte-backward */ +enc28j60_regb_write (priv,MAADR5 ,ENC28J60_MAC0 ); +enc28j60_regb_write (priv,MAADR4 ,ENC28J60_MAC1 ); +enc28j60_regb_write (priv,MAADR3 ,ENC28J60_MAC2 ); +enc28j60_regb_write (priv,MAADR2 ,ENC28J60_MAC3 ); +enc28j60_regb_write (priv,MAADR1 ,ENC28J60_MAC4 ); +enc28j60_regb_write (priv,MAADR0 ,ENC28J60_MAC5 );
Quoted 3 lines
Rather than having same address, please use
random_ether_addr()to avoid problems with two devices with same ethernet address.
Ok
Quoted 21 lines
...
drivers/net/enc28j60.c new file +18
-0+static int __devinit enc28j60_probe (struct spi_device *spi) +{ +struct net_device *dev; +struct enc28j60_net_local *priv; +int ret =0 ; + +dev_dbg (&spi->dev,"%s() start\n" , __FUNCTION__); + + dev =alloc_etherdev (sizeof (struct enc28j60_net_local )); +if (!dev) { + ret = -ENOMEM ; + goto error_alloc; + } + priv =netdev_priv (dev); + + priv->netdev = dev;/* priv to netdev reference */ + priv->spi = spi;/* priv to spi reference */ + priv->spi_transfer_buf =kmalloc (SPI_TRANSFER_BUF_LEN ,GFP_KERNEL );
Quoted 2 lines
Why not declare the transfer buffer as an array in spi?
I don't understand exactly what do you mean here. spi field point to struct spi_device from SPI subsystem. Other SPI client driver uses an allocated buffer too.
Cheers,
Claudio Lanconelli
Quoted 14 lines · On Fri, 14 Dec 2007 10:21:51 +0100 Claudio Lanconelli…
Hi Stephen,
thank you for your suggestions.
I already applied trivial fixes, but I have questions on some points, see inline.Quoted 8 lines · Stephen Hemminger
General comments:
* device driver does no carrier detection. This makes it useless for bridging, bonding, or any form of failover.* use msglevel method (via ethtool) to control debug messages rather than kernel configuration. This allows enabling debugging without recompilation which is important in distributions.
Quoted 6 lines
Quoted 5 lines
* Please add ethtool support
* Consider using NAPI
Can you point me to a possibly simple driver that uses ethtool and NAPI?
No driver stays simple! but look at tg3, sky2, r8169 for examples.
Quoted 6 lines
Or other example that I can use for reference. May be the skeleton should be updated.
Quoted 1 line
* use
netdev_priv(netdev)rather thannetdev->privI can't find where I used
netdev->priv, may be do you meanpriv->netdev?
yes
(skipping other comments)
Quoted 20 lines
Quoted 20 lines
(quoted diff) +18
-0+static int __devinit enc28j60_probe(struct spi_device *spi) +{ + struct net_device *dev; + struct enc28j60_net_local *priv; + int ret = 0; + + dev_dbg(&spi->dev, "%s() start\n", __FUNCTION__); + + dev = alloc_etherdev(sizeof(struct enc28j60_net_local)); + if (!dev) { + ret = -ENOMEM; + goto error_alloc; + } + priv = netdev_priv(dev); + + priv->netdev = dev; /* priv to netdev reference */ + priv->spi = spi; /* priv to spi reference */ + priv->spi_transfer_buf = kmalloc(SPI_TRANSFER_BUF_LEN, GFP_KERNEL);
Quoted 5 lines
Quoted 2 lines
Why not declare the transfer buffer as an array in spi?
I don't understand exactly what do you mean here. spi field point to struct
spi_devicefrom SPI subsystem. Other SPI client driver uses an allocated buffer too.
I just noticed that you alloc an ether device then do an additional allocation for the buffer. It makes sense if there is other uses. You do need to be careful for cases where transfer_buf might be used after free: module unload (your probably safe), and client driver using during shutdown.
Signature · 1 line
Stephen Hemminger <[email protected]>
Quoted 5 lines · Claudio Lanconelli
These patches add support for Microchip enc28j60 ethernet chip controlled via SPI. I tested it on my custom board (S162) with ARM9 s3c2442 SoC.
Any comments are welcome.Signed-off-by Claudio Lanconelli <[email protected]>
comments:
* overall: a clean driver that looks mostly acceptable, good work
* use stats in net_device rather than defining your own
* use ethtool rather than 'full_duplex' variable to select duplex
* [suggestion but not requirement] kernel prefers "u8" and "u32" types to the C99 types uint8_t or uint32_t
* remove the 'inline' markers from functions, and let the compiler make the decision
* udelay() in enc28j60_phy_write() -- and any similar code pattern -- may not actually delay for the specified amount of time, when you consider that writes may be posted. normally a read will flush a write.
* Why do interrupt work in a kernel thread? Your comment says you cannot, but does not explain.
* should use NAPI
* should be able to program multicast list while everything is active
Here a second version of the patch with applied all changes Jeff and Stephen suggest me.
All but NAPI, see comments below at this regard.
Furthermore I fixed also a bug that caused a crash after ifconfig down and up cycle and changed the mutex_lock policy.
Quoted 4 lines · Jeff Garzik
comments:
* overall: a clean driver that looks mostly acceptable, good work
Thanks
Quoted 1 line
* Why do interrupt work in a kernel thread? Your comment says you cannot, but does not explain.
The enc28j60 is a SPI to Ethernet adapter, so we cannot access register with simple in() out() instructions, but we need to use the SPI subsystem. The spi_sync() basic operation to read/write a register is a blocking operation, so can't be done in interrupt context. Since every basic operation like read interrupt flag register call spi_sync() we need the work queue for almost everything.
Quoted 3 lines
* should use NAPI
For the reason I just explained I don't think NAPI is a viable way for enc28j60.
Furthermore enc28j60 is a 10Mb only device and probably don't suffer to interrupt overload.
In my test there are a lot more interrupts from SPI than from enc28j60 and the enc28j60 interrupts are less than RX+TX packets. See the dump below:
[root@videotron /tmp]# ifconfig
eth0 Link encap:Ethernet HWaddr 7E:C4:3B:C7:EA:F3
inet addr:192.168.143.197 Bcast:192.168.143.255
Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:4972 errors:0 dropped:0 overruns:0 frame:0 TX packets:2832 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000
RX bytes:2009875 (1.9 MiB) TX bytes:131922 (128.8 KiB) Interrupt:18
[root@videotron /tmp]# cat /proc/interrupts
CPU0
18: 4316 s3c-ext0 enc28j60 30: 11591615 s3c S3C2410 Timer Tick 32: 0 s3c s162-lcd 33: 0 s3c s3c-mci 37: 21 s3c s3c-mci 38: 2707866 s3c s3c2410-spi 42: 0 s3c ohci_hcd:usb1 43: 28 s3c s3c2440-i2c 45: 1 s3c s3c2410-spi 62: 1 s3c-ext s3c-mci 73: 342 s3c-uart1 s3c2440-uart 74: 1225 s3c-uart1 s3c2440-uart 79: 0 s3c-adc s3c24xx_action 80: 0 s3c-adc s3c24xx_action
Err: 0
I think also USB-ethernet adapter have the same problem, and neither use NAPI
Cheers,
Claudio Lanconelli
drivers/net/enc28j60.c new file +1600 -0
@@ -0,0 +1,1600 @@ +/* +* Microchip ENC28J60 ethernet driver (MAC + PHY) +* +* Copyright (C) 2007 Eurek srl +* Author: Claudio Lanconelli <[email protected]> +* based on enc28j60.c written by David Anders for 2.4 kernel version +* +* 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. +* +* $Id: enc28j60.c,v 1.22 2007/12/20 10:47:01 claudio Exp $ +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/tcp.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> + +#include "enc28j60_hw.h" + +#define DRV_NAME "enc28j60" +#define DRV_VERSION "1.01" + +#define SPI_OPLEN 1 + +#define ENC28J60_MSG_DEFAULT \ + (NETIF_MSG_PROBE | NETIF_MSG_IFUP | NETIF_MSG_IFDOWN | NETIF_MSG_LINK) + +/* Buffer size required for the largest SPI transfer (i.e., reading a +* frame). */ +#define SPI_TRANSFER_BUF_LEN (4 + MAX_FRAMELEN) + +#define TX_TIMEOUT (4 * HZ) + +/* Max TX retries in case of collision as suggested by errata datasheet */ +#define MAX_TX_RETRYCOUNT 16 + +enum { +RXFILTER_NORMAL , +RXFILTER_MULTI , +RXFILTER_PROMISC +}; + +/* Driver local data */ +struct enc28j60_net { +struct net_device *netdev; +struct spi_device *spi; +struct mutex lock; +struct sk_buff *tx_skb; +struct work_struct tx_work; +struct work_struct irq_work; +struct work_struct setrx_work; +struct work_struct restart_work; +u8 bank;/* current register bank selected */ +u16 next_pk_ptr;/* next packet pointer within FIFO */ +u16 max_pk_counter;/* statistics: max packet counter */ +u16 tx_retry_count; +bool hw_enable; +bool full_duplex; +int rxfilter; +u32 msg_enable; +u8 spi_transfer_buf[SPI_TRANSFER_BUF_LEN ]; +}; + +/* use ethtool to change the level for any given device */ +static struct { +u32 msg_enable; +} debug = {-1 }; + +/* +* SPI read buffer +* wait for the SPI transfer and copy received data to destination +*/ +static int +spi_read_buf (struct enc28j60_net *priv,int len,u8 *data) +{ +u8 *rx_buf = priv->spi_transfer_buf +4 ; +u8 *tx_buf = priv->spi_transfer_buf; +struct spi_transfer t = { + .tx_buf = tx_buf, + .rx_buf = rx_buf, + .len =SPI_OPLEN + len, + }; +struct spi_message msg; +int ret; + + tx_buf[0 ] =ENC28J60_READ_BUF_MEM ; + tx_buf[1 ] = tx_buf[2 ] = tx_buf[3 ] =0 ;/* don't care */ + +spi_message_init (&msg); +spi_message_add_tail (&t, &msg); + ret =spi_sync (priv->spi, &msg); +if (ret ==0 ) { +memcpy (data, &rx_buf[SPI_OPLEN ], len); + ret = msg.status; + } +if (ret &&netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() failed: ret = %d\n", + __FUNCTION__, ret); + +return ret; +} + +/* +* SPI write buffer +*/ +static int spi_write_buf (struct enc28j60_net *priv,int len, +const u8 *data) +{ +int ret; + +if (len >SPI_TRANSFER_BUF_LEN -1 || len <=0 ) + ret = -EINVAL ; +else { + priv->spi_transfer_buf[0 ] =ENC28J60_WRITE_BUF_MEM ; +memcpy (&priv->spi_transfer_buf[1 ], data, len); + ret =spi_write (priv->spi, priv->spi_transfer_buf, len +1 ); +if (ret &&netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() failed: ret = %d\n", + __FUNCTION__, ret); + } +return ret; +} + +/* +* basic SPI read operation +*/ +static u8 spi_read_op (struct enc28j60_net *priv,u8 op, +u8 addr) +{ +u8 tx_buf[2 ]; +u8 rx_buf[4 ]; +u8 val =0 ; +int ret; +int slen =SPI_OPLEN ; + +/* do dummy read if needed */ +if (addr &SPRD_MASK ) + slen++; + + tx_buf[0 ] = op | (addr &ADDR_MASK ); + ret =spi_write_then_read (priv->spi, tx_buf,1 , rx_buf, slen); +if (ret) + printk(KERN_DEBUG DRV_NAME ": %s() failed: ret = %d\n", + __FUNCTION__, ret); +else + val = rx_buf[slen -1 ]; + +return val; +} + +/* +* basic SPI write operation +*/ +static int spi_write_op (struct enc28j60_net *priv,u8 op, +u8 addr,u8 val) +{ +int ret; + + priv->spi_transfer_buf[0 ] = op | (addr &ADDR_MASK ); + priv->spi_transfer_buf[1 ] = val; + ret =spi_write (priv->spi, priv->spi_transfer_buf,2 ); +if (ret &&netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() failed: ret = %d\n", + __FUNCTION__, ret); +return ret; +} + +static void enc28j60_soft_reset (struct enc28j60_net *priv) +{ +if (netif_msg_hw (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__); + +spi_write_op (priv,ENC28J60_SOFT_RESET ,0 ,ENC28J60_SOFT_RESET ); +/* Errata workaround #1, CLKRDY check is unreliable, +* delay at least 1 mS instead */ +udelay (2000 ); +} + +/* +* select the current register bank if necessary +*/ +static void enc28j60_set_bank (struct enc28j60_net *priv,u8 addr) +{ +if ((addr &BANK_MASK ) != priv->bank) { +u8 b = (addr &BANK_MASK ) >>5 ; + +if (b != (ECON1_BSEL1 |ECON1_BSEL0 )) +spi_write_op (priv,ENC28J60_BIT_FIELD_CLR ,ECON1 , +ECON1_BSEL1 |ECON1_BSEL0 ); +if (b !=0 ) +spi_write_op (priv,ENC28J60_BIT_FIELD_SET ,ECON1 , b); + priv->bank = (addr &BANK_MASK ); + } +} + +/* +* Register access routines through the SPI bus. +* Every register access comes in two flavours: +* - nolock_xxx: caller needs to invoke mutex_lock, usually to access +* atomically more than one register +* - locked_xxx: caller doesn't need to invoke mutex_lock, single access +* +* Some registers can be accessed through the bit field clear and +* bit field set to avoid a read modify write cycle. +*/ + +/* +* Register bit field Set +*/ +static void nolock_reg_bfset (struct enc28j60_net *priv, +u8 addr,u8 mask) +{ +enc28j60_set_bank (priv, addr); +spi_write_op (priv,ENC28J60_BIT_FIELD_SET , addr, mask); +} + +static void locked_reg_bfset (struct enc28j60_net *priv, +u8 addr,u8 mask) +{ +mutex_lock (&priv->lock); +nolock_reg_bfset (priv, addr, mask); +mutex_unlock (&priv->lock); +} + +/* +* Register bit field Clear +*/ +static void nolock_reg_bfclr (struct enc28j60_net *priv, +u8 addr,u8 mask) +{ +enc28j60_set_bank (priv, addr); +spi_write_op (priv,ENC28J60_BIT_FIELD_CLR , addr, mask); +} + +static void locked_reg_bfclr (struct enc28j60_net *priv, +u8 addr,u8 mask) +{ +mutex_lock (&priv->lock); +nolock_reg_bfclr (priv, addr, mask); +mutex_unlock (&priv->lock); +} + +/* +* Register byte read +*/ +static int nolock_regb_read (struct enc28j60_net *priv, +u8 address) +{ +enc28j60_set_bank (priv, address); +return spi_read_op (priv,ENC28J60_READ_CTRL_REG , address); +} + +static int locked_regb_read (struct enc28j60_net *priv, +u8 address) +{ +int ret; + +mutex_lock (&priv->lock); + ret =nolock_regb_read (priv, address); +mutex_unlock (&priv->lock); + +return ret; +} + +/* +* Register word read +*/ +static int nolock_regw_read (struct enc28j60_net *priv, +u8 address) +{ +int rl, rh; + +enc28j60_set_bank (priv, address); + rl =spi_read_op (priv,ENC28J60_READ_CTRL_REG , address); + rh =spi_read_op (priv,ENC28J60_READ_CTRL_REG , address +1 ); + +return (rh <<8 ) | rl; +} + +static int locked_regw_read (struct enc28j60_net *priv, +u8 address) +{ +int ret; + +mutex_lock (&priv->lock); + ret =nolock_regw_read (priv, address); +mutex_unlock (&priv->lock); + +return ret; +} + +/* +* Register byte write +*/ +static void nolock_regb_write (struct enc28j60_net *priv, +u8 address,u8 data) +{ +enc28j60_set_bank (priv, address); +spi_write_op (priv,ENC28J60_WRITE_CTRL_REG , address, data); +} + +static void locked_regb_write (struct enc28j60_net *priv, +u8 address,u8 data) +{ +mutex_lock (&priv->lock); +nolock_regb_write (priv, address, data); +mutex_unlock (&priv->lock); +} + +/* +* Register word write +*/ +static void nolock_regw_write (struct enc28j60_net *priv, +u8 address,u16 data) +{ +enc28j60_set_bank (priv, address); +spi_write_op (priv,ENC28J60_WRITE_CTRL_REG , address, (u8 ) data); +spi_write_op (priv,ENC28J60_WRITE_CTRL_REG , address +1 , + (u8) (data >>8 )); +} + +static void locked_regw_write (struct enc28j60_net *priv, +u8 address,u16 data) +{ +mutex_lock (&priv->lock); +nolock_regw_write (priv, address, data); +mutex_unlock (&priv->lock); +} + +/* +* Buffer memory read +* Select the starting address and execute a SPI buffer read +*/ +static void enc28j60_mem_read (struct enc28j60_net *priv, +u16 addr,int len,u8 *data) +{ +mutex_lock (&priv->lock); +nolock_regw_write (priv,ERDPTL , addr); +#ifdef CONFIG_ENC28J60_WRITEVERIFY +if (netif_msg_drv (priv)) { +u16 reg; + reg =nolock_regw_read (priv,ERDPTL ); +if (reg != addr) + printk(KERN_DEBUG DRV_NAME ": %s() error writing ERDPT " +"(0x%04x - 0x%04x)\n" , __FUNCTION__, reg, addr); + } +#endif +spi_read_buf (priv, len, data); +mutex_unlock (&priv->lock); +} + +/* +* Write packet to enc28j60 TX buffer memory +*/ +static void +enc28j60_packet_write (struct enc28j60_net *priv,int len,const u8 *data) +{ +mutex_lock (&priv->lock); +/* Set the write pointer to start of transmit buffer area */ +nolock_regw_write (priv,EWRPTL ,TXSTART_INIT ); +#ifdef CONFIG_ENC28J60_WRITEVERIFY +if (netif_msg_drv (priv)) { +u16 reg; + reg =nolock_regw_read (priv,EWRPTL ); +if (reg !=TXSTART_INIT ) + printk(KERN_DEBUG DRV_NAME +": %s() ERWPT:0x%04x != 0x%04x\n" , + __FUNCTION__, reg,TXSTART_INIT ); + } +#endif +/* Set the TXND pointer to correspond to the packet size given */ +nolock_regw_write (priv,ETXNDL ,TXSTART_INIT + len); +/* write per-packet control byte */ +spi_write_op (priv,ENC28J60_WRITE_BUF_MEM ,0 ,0x00 ); +if (netif_msg_hw (priv)) + printk(KERN_DEBUG DRV_NAME +": %s() after control byte ERWPT:0x%04x\n" , + __FUNCTION__,nolock_regw_read (priv,EWRPTL )); +/* copy the packet into the transmit buffer */ +spi_write_buf (priv, len, data); +if (netif_msg_hw (priv)) + printk(KERN_DEBUG DRV_NAME +": %s() after write packet ERWPT:0x%04x, len=%d\n" , + __FUNCTION__,nolock_regw_read (priv,EWRPTL ), len); +mutex_unlock (&priv->lock); +} + +/* +* Wait until the PHY operation is complete. +*/ +static int wait_phy_ready (struct enc28j60_net *priv) +{ +unsigned long timeout = jiffies +20 *HZ /1000 ; +int ret =1 ; + +/* 20 msec timeout read */ +while (nolock_regb_read (priv,MISTAT ) &MISTAT_BUSY ) { +if (time_after (jiffies, timeout)) { +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME +": PHY ready timeout!\n" ); + ret =0 ; +break ; + } +cpu_relax (); + } +return ret; +} + +/* +* PHY register read +* PHY registers are not accessed directly, but through the MII +*/ +static u16 enc28j60_phy_read (struct enc28j60_net *priv,u8 address) +{ +u16 ret; + +mutex_lock (&priv->lock); +/* set the PHY register address */ +nolock_regb_write (priv,MIREGADR , address); +/* start the register read operation */ +nolock_regb_write (priv,MICMD ,MICMD_MIIRD ); +/* wait until the PHY read completes */ +wait_phy_ready (priv); +/* quit reading */ +nolock_regb_write (priv,MICMD ,0x00 ); +/* return the data */ + ret =nolock_regw_read (priv,MIRDL ); +mutex_unlock (&priv->lock); + +return ret; +} + +static int enc28j60_phy_write (struct enc28j60_net *priv,u8 address,u16 data) +{ +int ret; + +mutex_lock (&priv->lock); +/* set the PHY register address */ +nolock_regb_write (priv,MIREGADR , address); +/* write the PHY data */ +nolock_regw_write (priv,MIWRL , data); +/* wait until the PHY write completes and return */ + ret =wait_phy_ready (priv); +mutex_unlock (&priv->lock); + +return ret; +} + +/* +* Program the hardware MAC address from dev->dev_addr. +*/ +static int enc28j60_set_hw_macaddr (struct net_device *ndev) +{ +int ret; +struct enc28j60_net *priv =netdev_priv (ndev); + +mutex_lock (&priv->lock); +if (!priv->hw_enable) { +if (netif_msg_drv (priv)) { +DECLARE_MAC_BUF (mac); + printk(KERN_INFO DRV_NAME +": %s: Setting MAC address to %s\n" , + ndev->name, print_mac(mac, ndev->dev_addr)); + } +/* NOTE: MAC address in ENC28J60 is byte-backward */ +nolock_regb_write (priv,MAADR5 , ndev->dev_addr[0 ]); +nolock_regb_write (priv,MAADR4 , ndev->dev_addr[1 ]); +nolock_regb_write (priv,MAADR3 , ndev->dev_addr[2 ]); +nolock_regb_write (priv,MAADR2 , ndev->dev_addr[3 ]); +nolock_regb_write (priv,MAADR1 , ndev->dev_addr[4 ]); +nolock_regb_write (priv,MAADR0 , ndev->dev_addr[5 ]); + ret =0 ; + }else { +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME +": %s() Hardware must be disabled to set " +"Mac address\n" , __FUNCTION__); + ret = -EBUSY ; + } +mutex_unlock (&priv->lock); +return ret; +} + +/* +* Store the new hardware address in dev->dev_addr, and update the MAC. +*/ +static int enc28j60_set_mac_address (struct net_device *dev,void *addr) +{ +struct sockaddr *address = addr; + +if (netif_running (dev)) +return -EBUSY ; +if (!is_valid_ether_addr (address->sa_data)) +return -EADDRNOTAVAIL ; + +memcpy (dev->dev_addr, address->sa_data, dev->addr_len); +return enc28j60_set_hw_macaddr (dev); +} + +/* +* Debug routine to dump useful register contents +*/ +static void enc28j60_dump_regs (struct enc28j60_net *priv,const char *msg) +{ +mutex_lock (&priv->lock); + printk(KERN_DEBUG DRV_NAME " %s\n" +"HwRevID: 0x%02x\n" +"Cntrl: ECON1 ECON2 ESTAT EIR EIE\n" +" 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n" +"MAC : MACON1 MACON3 MACON4\n" +" 0x%02x 0x%02x 0x%02x\n" +"Rx : ERXST ERXND ERXWRPT ERXRDPT ERXFCON EPKTCNT MAMXFL\n" +" 0x%04x 0x%04x 0x%04x 0x%04x " +"0x%02x 0x%02x 0x%04x\n" +"Tx : ETXST ETXND MACLCON1 MACLCON2 MAPHSUP\n" +" 0x%04x 0x%04x 0x%02x 0x%02x 0x%02x\n" , + msg,nolock_regb_read (priv,EREVID ), +nolock_regb_read (priv,ECON1 ),nolock_regb_read (priv,ECON2 ), +nolock_regb_read (priv,ESTAT ),nolock_regb_read (priv,EIR ), +nolock_regb_read (priv,EIE ),nolock_regb_read (priv,MACON1 ), +nolock_regb_read (priv,MACON3 ),nolock_regb_read (priv,MACON4 ), +nolock_regw_read (priv,ERXSTL ),nolock_regw_read (priv,ERXNDL ), +nolock_regw_read (priv,ERXWRPTL ), +nolock_regw_read (priv,ERXRDPTL ), +nolock_regb_read (priv,ERXFCON ), +nolock_regb_read (priv,EPKTCNT ), +nolock_regw_read (priv,MAMXFLL ),nolock_regw_read (priv,ETXSTL ), +nolock_regw_read (priv,ETXNDL ), +nolock_regb_read (priv,MACLCON1 ), +nolock_regb_read (priv,MACLCON2 ), + nolock_regb_read(priv, MAPHSUP)); +mutex_unlock (&priv->lock); +} + +/* +* ERXRDPT need to be set always at odd addresses, refer to errata datasheet +*/ +static u16 erxrdpt_workaround (u16 next_packet_ptr,u16 start,u16 end) +{ +u16 erxrdpt; + +if ((next_packet_ptr -1 < start) || (next_packet_ptr -1 > end)) + erxrdpt = end; +else + erxrdpt = next_packet_ptr -1 ; + +return erxrdpt; +} + +static void nolock_rxfifo_init (struct enc28j60_net *priv,u16 start,u16 end) +{ +u16 erxrdpt; + +if (start >0x1FFF || end >0x1FFF || start > end) { +if (netif_msg_drv (priv)) + printk(KERN_ERR DRV_NAME ": %s(%d, %d) RXFIFO " +"bad parameters!\n" , __FUNCTION__, start, end); +return ; + } +/* set receive buffer start + end */ + priv->next_pk_ptr = start; +nolock_regw_write (priv,ERXSTL , start); + erxrdpt =erxrdpt_workaround (priv->next_pk_ptr, start, end); +nolock_regw_write (priv,ERXRDPTL , erxrdpt); +nolock_regw_write (priv,ERXNDL , end); +} + +static void nolock_txfifo_init (struct enc28j60_net *priv,u16 start,u16 end) +{ +if (start >0x1FFF || end >0x1FFF || start > end) { +if (netif_msg_drv (priv)) + printk(KERN_ERR DRV_NAME ": %s(%d, %d) TXFIFO " +"bad parameters!\n" , __FUNCTION__, start, end); +return ; + } +/* set transmit buffer start + end */ +nolock_regw_write (priv,ETXSTL , start); +nolock_regw_write (priv,ETXNDL , end); +} + +static int enc28j60_hw_init (struct enc28j60_net *priv) +{ +u8 reg; + +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() - %s\n", __FUNCTION__, + priv->full_duplex ?"FullDuplex" :"HalfDuplex" ); + +mutex_lock (&priv->lock); +/* first reset the chip */ +enc28j60_soft_reset (priv); +/* Clear ECON1 */ +spi_write_op (priv,ENC28J60_WRITE_CTRL_REG ,ECON1 ,0x00 ); + priv->bank =0 ; + priv->hw_enable = false; + priv->tx_retry_count =0 ; + priv->max_pk_counter =0 ; + priv->rxfilter =RXFILTER_NORMAL ; +/* enable address auto increment */ +nolock_regb_write (priv,ECON2 ,ECON2_AUTOINC ); + +nolock_rxfifo_init (priv,RXSTART_INIT ,RXEND_INIT ); +nolock_txfifo_init (priv,TXSTART_INIT ,TXEND_INIT ); +mutex_unlock (&priv->lock); + +/* +* Check the RevID. +* If it's 0x00 or 0xFF probably the enc28j60 is not mounted or +* damaged +*/ + reg =locked_regb_read (priv,EREVID ); +if (netif_msg_drv (priv)) + printk(KERN_INFO DRV_NAME ": chip RevID: 0x%02x\n", reg); +if (reg ==0x00 || reg ==0xff ) { +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() Invalid RevId %d\n", + __FUNCTION__, reg); +return 0 ; + } + +/* default filter mode: (unicast OR broadcast) AND crc valid */ +locked_regb_write (priv,ERXFCON , +ERXFCON_UCEN |ERXFCON_CRCEN |ERXFCON_BCEN ); + +/* enable MAC receive */ +locked_regb_write (priv,MACON1 , +MACON1_MARXEN |MACON1_TXPAUS |MACON1_RXPAUS ); +/* enable automatic padding and CRC operations */ +if (priv->full_duplex) { +locked_regb_write (priv,MACON3 , +MACON3_PADCFG0 |MACON3_TXCRCEN | +MACON3_FRMLNEN |MACON3_FULDPX ); +/* set inter-frame gap (non-back-to-back) */ +locked_regb_write (priv,MAIPGL ,0x12 ); +/* set inter-frame gap (back-to-back) */ +locked_regb_write (priv,MABBIPG ,0x15 ); + }else { +locked_regb_write (priv,MACON3 , +MACON3_PADCFG0 |MACON3_TXCRCEN | +MACON3_FRMLNEN ); +locked_regb_write (priv,MACON4 ,1 <<6 );/* DEFER bit */ +/* set inter-frame gap (non-back-to-back) */ +locked_regw_write (priv,MAIPGL ,0x0C12 ); +/* set inter-frame gap (back-to-back) */ +locked_regb_write (priv,MABBIPG ,0x12 ); + } +/* +* MACLCON1 (default) +* MACLCON2 (default) +* Set the maximum packet size which the controller will accept +*/ +locked_regw_write (priv,MAMXFLL ,MAX_FRAMELEN ); + +/* Configure LEDs */ +if (!enc28j60_phy_write (priv,PHLCON ,ENC28J60_LAMPS_MODE )) +return 0 ; + +if (priv->full_duplex) { +if (!enc28j60_phy_write (priv,PHCON1 ,PHCON1_PDPXMD )) +return 0 ; +if (!enc28j60_phy_write (priv,PHCON2 ,0x00 )) +return 0 ; + }else { +if (!enc28j60_phy_write (priv,PHCON1 ,0x00 )) +return 0 ; +if (!enc28j60_phy_write (priv,PHCON2 ,PHCON2_HDLDIS )) +return 0 ; + } +if (netif_msg_hw (priv)) +enc28j60_dump_regs (priv,"Hw initialized." ); + +return 1 ; +} + +static void enc28j60_hw_enable (struct enc28j60_net *priv) +{ +/* enable interrutps */ +if (netif_msg_hw (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() enabling interrupts.\n", + __FUNCTION__); + +enc28j60_phy_write (priv,PHIE ,PHIE_PGEIE |PHIE_PLNKIE ); + +mutex_lock (&priv->lock); +nolock_reg_bfclr (priv,EIR ,EIR_DMAIF |EIR_LINKIF | +EIR_TXIF |EIR_TXERIF |EIR_RXERIF |EIR_PKTIF ); +nolock_regb_write (priv,EIE ,EIE_INTIE |EIE_PKTIE |EIE_LINKIE | +EIE_TXIE |EIE_TXERIE |EIE_RXERIE ); + +/* enable receive logic */ +nolock_reg_bfset (priv,ECON1 ,ECON1_RXEN ); + priv->hw_enable = true; +mutex_unlock (&priv->lock); +} + +static void enc28j60_hw_disable (struct enc28j60_net *priv) +{ +mutex_lock (&priv->lock); +/* disable interrutps and packet reception */ +nolock_regb_write (priv,EIE ,0x00 ); +nolock_reg_bfclr (priv,ECON1 ,ECON1_RXEN ); + priv->hw_enable = false; +mutex_unlock (&priv->lock); +} + +static int +enc28j60_setlink (struct net_device *ndev,u8 autoneg,u16 speed,u8 duplex) +{ +struct enc28j60_net *priv =netdev_priv (ndev); +int ret =0 ; + +if (!priv->hw_enable) { +if (autoneg ==AUTONEG_DISABLE && speed ==SPEED_10 ) { + priv->full_duplex = (duplex ==DUPLEX_FULL ); +if (!enc28j60_hw_init (priv)) { +if (netif_msg_drv (priv)) +dev_err (&ndev->dev, +"hw_reset() failed\n" ); + ret = -EINVAL ; + } + }else { +if (netif_msg_link (priv)) +dev_warn (&ndev->dev, +"unsupported link setting\n" ); + ret = -EOPNOTSUPP ; + } + }else { +if (netif_msg_link (priv)) +dev_warn (&ndev->dev,"Warning: hw must be disabled " +"to set link mode\n" ); + ret = -EBUSY ; + } +return ret; +} + +/* +* Read the Transmit Status Vector +*/ +static void enc28j60_read_tsv (struct enc28j60_net *priv,u8 tsv[TSV_SIZE ]) +{ +int endptr; + + endptr =locked_regw_read (priv,ETXNDL ); +if (netif_msg_hw (priv)) + printk(KERN_DEBUG DRV_NAME ": reading TSV at addr:0x%04x\n", + endptr +1 ); +enc28j60_mem_read (priv, endptr +1 ,sizeof (tsv), tsv); +} + +static void enc28j60_dump_tsv (struct enc28j60_net *priv,const char *msg, +u8 tsv[TSV_SIZE ]) +{ +u16 tmp1, tmp2; + + printk(KERN_DEBUG DRV_NAME ": %s - TSV:\n", msg); + tmp1 = tsv[1 ]; + tmp1 <<=8 ; + tmp1 |= tsv[0 ]; + + tmp2 = tsv[5 ]; + tmp2 <<=8 ; + tmp2 |= tsv[4 ]; + + printk(KERN_DEBUG DRV_NAME ": ByteCount: %d, CollisionCount: %d," + " TotByteOnWire: %d\n", tmp1, tsv[2] & 0x0f, tmp2); + printk(KERN_DEBUG DRV_NAME ": TxDone: %d, CRCErr:%d, LenChkErr: %d," +" LenOutOfRange: %d\n" ,TSV_GETBIT (tsv,TSV_TXDONE ), +TSV_GETBIT (tsv,TSV_TXCRCERROR ), +TSV_GETBIT (tsv,TSV_TXLENCHKERROR ), + TSV_GETBIT(tsv, TSV_TXLENOUTOFRANGE)); + printk(KERN_DEBUG DRV_NAME ": Multicast: %d, Broadcast: %d, " +"PacketDefer: %d, ExDefer: %d\n" , +TSV_GETBIT (tsv,TSV_TXMULTICAST ), +TSV_GETBIT (tsv,TSV_TXBROADCAST ), +TSV_GETBIT (tsv,TSV_TXPACKETDEFER ), + TSV_GETBIT(tsv, TSV_TXEXDEFER)); + printk(KERN_DEBUG DRV_NAME ": ExCollision: %d, LateCollision: %d, " +"Giant: %d, Underrun: %d\n" , +TSV_GETBIT (tsv,TSV_TXEXCOLLISION ), +TSV_GETBIT (tsv,TSV_TXLATECOLLISION ), + TSV_GETBIT(tsv, TSV_TXGIANT), TSV_GETBIT(tsv, TSV_TXUNDERRUN)); + printk(KERN_DEBUG DRV_NAME ": ControlFrame: %d, PauseFrame: %d, " +"BackPressApp: %d, VLanTagFrame: %d\n" , +TSV_GETBIT (tsv,TSV_TXCONTROLFRAME ), +TSV_GETBIT (tsv,TSV_TXPAUSEFRAME ), +TSV_GETBIT (tsv,TSV_BACKPRESSUREAPP ), + TSV_GETBIT(tsv, TSV_TXVLANTAGFRAME)); +} + +/* +* Receive Status vector +*/ +static void enc28j60_dump_rsv (struct enc28j60_net *priv,const char *msg, +u16 pk_ptr,int len,u16 sts) +{ + printk(KERN_DEBUG DRV_NAME ": %s - NextPk: 0x%04x - RSV:\n", + msg, pk_ptr); + printk(KERN_DEBUG DRV_NAME ": ByteCount: %d, DribbleNibble: %d\n", len, + RSV_GETBIT(sts, RSV_DRIBBLENIBBLE)); + printk(KERN_DEBUG DRV_NAME ": RxOK: %d, CRCErr:%d, LenChkErr: %d," +" LenOutOfRange: %d\n" ,RSV_GETBIT (sts,RSV_RXOK ), +RSV_GETBIT (sts,RSV_CRCERROR ), +RSV_GETBIT (sts,RSV_LENCHECKERR ), + RSV_GETBIT(sts, RSV_LENOUTOFRANGE)); + printk(KERN_DEBUG DRV_NAME ": Multicast: %d, Broadcast: %d, " +"LongDropEvent: %d, CarrierEvent: %d\n" , +RSV_GETBIT (sts,RSV_RXMULTICAST ), +RSV_GETBIT (sts,RSV_RXBROADCAST ), +RSV_GETBIT (sts,RSV_RXLONGEVDROPEV ), + RSV_GETBIT(sts, RSV_CARRIEREV)); + printk(KERN_DEBUG DRV_NAME ": ControlFrame: %d, PauseFrame: %d," +" UnknownOp: %d, VLanTagFrame: %d\n" , +RSV_GETBIT (sts,RSV_RXCONTROLFRAME ), +RSV_GETBIT (sts,RSV_RXPAUSEFRAME ), +RSV_GETBIT (sts,RSV_RXUNKNOWNOPCODE ), + RSV_GETBIT(sts, RSV_RXTYPEVLAN)); +} + +static void dump_packet (const char *msg,int len,const char *data) +{ + printk(KERN_DEBUG DRV_NAME ": %s - packet len:%d\n", msg, len); +print_hex_dump (KERN_DEBUG ,"pk data: " ,DUMP_PREFIX_OFFSET ,16 ,1 , + data, len, true); +} + +/* +* Hardware receive function. +* Read the buffer memory, update the FIFO pointer to free the buffer, +* check the status vector and decrement the packet counter. +*/ +static void enc28j60_hw_rx (struct net_device *ndev) +{ +struct enc28j60_net *priv =netdev_priv (ndev); +struct sk_buff *skb =NULL ; +u16 erxrdpt, next_packet, rxstat; +u8 rsv[RSV_SIZE ]; +int len; + +if (netif_msg_rx_status (priv)) + printk(KERN_DEBUG DRV_NAME ": RX pk_addr:0x%04x\n", + priv->next_pk_ptr); + +if (unlikely (priv->next_pk_ptr >RXEND_INIT )) { +if (netif_msg_rx_err (priv)) +dev_err (&ndev->dev, +"%s() Invalid packet address!! 0x%04x\n" , + __FUNCTION__, priv->next_pk_ptr); +/* packet address corrupted: reset RX logic */ +mutex_lock (&priv->lock); +nolock_reg_bfclr (priv,ECON1 ,ECON1_RXEN ); +nolock_reg_bfset (priv,ECON1 ,ECON1_RXRST ); +nolock_reg_bfclr (priv,ECON1 ,ECON1_RXRST ); +nolock_rxfifo_init (priv,RXSTART_INIT ,RXEND_INIT ); +nolock_reg_bfclr (priv,EIR ,EIR_RXERIF ); +nolock_reg_bfset (priv,ECON1 ,ECON1_RXEN ); +mutex_unlock (&priv->lock); + ndev->stats.rx_errors++; +return ; + } +/* Read next packet pointer and rx status vector */ +enc28j60_mem_read (priv, priv->next_pk_ptr,sizeof (rsv), rsv); + + next_packet = rsv[1 ]; + next_packet <<=8 ; + next_packet |= rsv[0 ]; + + len = rsv[3 ]; + len <<=8 ; + len |= rsv[2 ]; + + rxstat = rsv[5 ]; + rxstat <<=8 ; + rxstat |= rsv[4 ]; + +if (netif_msg_rx_status (priv)) +enc28j60_dump_rsv (priv, __FUNCTION__, next_packet, len, rxstat); + +if (!RSV_GETBIT (rxstat,RSV_RXOK )) { +if (netif_msg_rx_err (priv)) +dev_err (&ndev->dev,"Rx Error (%04x)\n" , rxstat); + ndev->stats.rx_errors++; +if (RSV_GETBIT (rxstat,RSV_CRCERROR )) + ndev->stats.rx_crc_errors++; +if (RSV_GETBIT (rxstat,RSV_LENCHECKERR )) + ndev->stats.rx_frame_errors++; + }else { + skb =dev_alloc_skb (len); +if (!skb) { +if (netif_msg_rx_err (priv)) +dev_err (&ndev->dev, +"out of memory for Rx'd frame\n" ); + ndev->stats.rx_dropped++; + }else { + skb->dev = ndev; +/* copy the packet from the receive buffer */ +enc28j60_mem_read (priv, priv->next_pk_ptr +sizeof (rsv), + len,skb_put (skb, len)); +if (netif_msg_pktdata (priv)) +dump_packet (__FUNCTION__, skb->len, skb->data); + skb->protocol =eth_type_trans (skb, ndev); +/* update statistics */ + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += len; + ndev->last_rx = jiffies; +netif_rx (skb); + } + } +/* +* Move the RX read pointer to the start of the next +* received packet. +* This frees the memory we just read out +*/ + erxrdpt =erxrdpt_workaround (next_packet,RXSTART_INIT ,RXEND_INIT ); +if (netif_msg_hw (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() ERXRDPT:0x%04x\n", + __FUNCTION__, erxrdpt); + +mutex_lock (&priv->lock); +nolock_regw_write (priv,ERXRDPTL , erxrdpt); +#ifdef CONFIG_ENC28J60_WRITEVERIFY +if (netif_msg_drv (priv)) { +u16 reg; + reg =nolock_regw_read (priv,ERXRDPTL ); +if (reg != erxrdpt) + printk(KERN_DEBUG DRV_NAME ": %s() ERXRDPT verify " +"error (0x%04x - 0x%04x)\n" , __FUNCTION__, + reg, erxrdpt); + } +#endif + priv->next_pk_ptr = next_packet; +/* we are done with this packet, decrement the packet counter */ +nolock_reg_bfset (priv,ECON2 ,ECON2_PKTDEC ); +mutex_unlock (&priv->lock); +} + +/* +* Calculate free space in RxFIFO +*/ +static int enc28j60_get_free_rxfifo (struct enc28j60_net *priv) +{ +int epkcnt, erxst, erxnd, erxwr, erxrd; +int free_space; + +mutex_lock (&priv->lock); + epkcnt =nolock_regb_read (priv,EPKTCNT ); +if (epkcnt >=255 ) + free_space =-1 ; +else { + erxst =nolock_regw_read (priv,ERXSTL ); + erxnd =nolock_regw_read (priv,ERXNDL ); + erxwr =nolock_regw_read (priv,ERXWRPTL ); + erxrd =nolock_regw_read (priv,ERXRDPTL ); + +if (erxwr > erxrd) + free_space = (erxnd - erxst) - (erxwr - erxrd); +else if (erxwr == erxrd) + free_space = (erxnd - erxst); +else + free_space = erxrd - erxwr -1 ; + } +mutex_unlock (&priv->lock); +if (netif_msg_rx_status (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() free_space = %d\n", + __FUNCTION__, free_space); +return free_space; +} + +/* +* Access the PHY to determine link status +*/ +static void enc28j60_check_link_status (struct net_device *ndev) +{ +struct enc28j60_net *priv =netdev_priv (ndev); +u16 reg; +int duplex; + + reg =enc28j60_phy_read (priv,PHSTAT2 ); +if (netif_msg_hw (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() PHSTAT1: %04x, " +"PHSTAT2: %04x\n" , __FUNCTION__, +enc28j60_phy_read (priv,PHSTAT1 ), reg); + duplex = reg &PHSTAT2_DPXSTAT ; + +if (reg &PHSTAT2_LSTAT ) { +netif_carrier_on (ndev); +if (netif_msg_ifup (priv)) +dev_info (&ndev->dev,"link up - %s\n" , + duplex ?"Full duplex" :"Half duplex" ); + }else { +if (netif_msg_ifdown (priv)) +dev_info (&ndev->dev,"link down\n" ); +netif_carrier_off (ndev); + } +} + +static void enc28j60_tx_clear (struct net_device *ndev,bool err) +{ +struct enc28j60_net *priv =netdev_priv (ndev); + +if (err) + ndev->stats.tx_errors++; +else + ndev->stats.tx_packets++; + +if (priv->tx_skb) { +if (!err) + ndev->stats.tx_bytes += priv->tx_skb->len; +dev_kfree_skb (priv->tx_skb); + priv->tx_skb =NULL ; + } +locked_reg_bfclr (priv,ECON1 ,ECON1_TXRTS ); +netif_wake_queue (ndev); +} + +/* +* RX handler +* ignore PKTIF because is unreliable! (look at the errata datasheet) +* check EPKTCNT is the suggested workaround. +* We don't need to clear interrupt flag, automatically done when +* enc28j60_hw_rx() decrements the packet counter. +* Returns how many packet processed. +*/ +static int enc28j60_rx_interrupt (struct net_device *ndev) +{ +struct enc28j60_net *priv =netdev_priv (ndev); +int pk_counter, ret; + + pk_counter =locked_regb_read (priv,EPKTCNT ); +if (pk_counter &&netif_msg_intr (priv)) + printk(KERN_DEBUG DRV_NAME ": intRX, pk_cnt: %d\n", pk_counter); +if (pk_counter > priv->max_pk_counter) { +/* update statistics */ + priv->max_pk_counter = pk_counter; +if (netif_msg_rx_status (priv) && priv->max_pk_counter >1 ) + printk(KERN_DEBUG DRV_NAME ": RX max_pk_cnt: %d\n", + priv->max_pk_counter); + } + ret = pk_counter; +while (pk_counter-- >0 ) +enc28j60_hw_rx (ndev); + +return ret; +} + +static void enc28j60_irq_work_handler (struct work_struct *work) +{ +struct enc28j60_net *priv = + container_of(work, struct enc28j60_net, irq_work); +struct net_device *ndev = priv->netdev; +int intflags, loop; + +if (netif_msg_intr (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__); +/* disable further interrupts */ +locked_reg_bfclr (priv,EIE ,EIE_INTIE ); + +do { + loop =0 ; + intflags =locked_regb_read (priv,EIR ); +/* DMA interrupt handler (not currently used) */ +if ((intflags &EIR_DMAIF ) !=0 ) { + loop++; +if (netif_msg_intr (priv)) + printk(KERN_DEBUG DRV_NAME +": intDMA(%d)\n" , loop); +locked_reg_bfclr (priv,EIR ,EIR_DMAIF ); + } +/* LINK changed handler */ +if ((intflags &EIR_LINKIF ) !=0 ) { + loop++; +if (netif_msg_intr (priv)) + printk(KERN_DEBUG DRV_NAME +": intLINK(%d)\n" , loop); +enc28j60_check_link_status (ndev); +/* read PHIR to clear the flag */ +enc28j60_phy_read (priv,PHIR ); + } +/* TX complete handler */ +if ((intflags &EIR_TXIF ) !=0 ) { +bool err = false; + loop++; +if (netif_msg_intr (priv)) + printk(KERN_DEBUG DRV_NAME +": intTX(%d)\n" , loop); + priv->tx_retry_count =0 ; +if (locked_regb_read (priv,ESTAT ) &ESTAT_TXABRT ) { +if (netif_msg_tx_err (priv)) +dev_err (&ndev->dev, +"Tx Error (aborted)\n" ); + err = true; + } +if (netif_msg_tx_done (priv)) { +u8 tsv[TSV_SIZE ]; +enc28j60_read_tsv (priv, tsv); +enc28j60_dump_tsv (priv,"Tx Done" , tsv); + } +enc28j60_tx_clear (ndev, err); +locked_reg_bfclr (priv,EIR ,EIR_TXIF ); + } +/* TX Error handler */ +if ((intflags &EIR_TXERIF ) !=0 ) { +u8 tsv[TSV_SIZE ]; + + loop++; +if (netif_msg_intr (priv)) + printk(KERN_DEBUG DRV_NAME +": intTXErr(%d)\n" , loop); +locked_reg_bfclr (priv,ECON1 ,ECON1_TXRTS ); +enc28j60_read_tsv (priv, tsv); +if (netif_msg_tx_err (priv)) +enc28j60_dump_tsv (priv,"Tx Error" , tsv); +/* Reset TX logic */ +mutex_lock (&priv->lock); +nolock_reg_bfset (priv,ECON1 ,ECON1_TXRST ); +nolock_reg_bfclr (priv,ECON1 ,ECON1_TXRST ); +nolock_txfifo_init (priv,TXSTART_INIT ,TXEND_INIT ); +mutex_unlock (&priv->lock); +/* Transmit Late collision check for retransmit */ +if (TSV_GETBIT (tsv,TSV_TXLATECOLLISION )) { +if (netif_msg_tx_err (priv)) + printk(KERN_DEBUG DRV_NAME +": LateCollision TXErr (%d)\n" , + priv->tx_retry_count); +if (priv->tx_retry_count++ <MAX_TX_RETRYCOUNT ) +locked_reg_bfset (priv,ECON1 , +ECON1_TXRTS ); +else +enc28j60_tx_clear (ndev, true); + }else +enc28j60_tx_clear (ndev, true); +locked_reg_bfclr (priv,EIR ,EIR_TXERIF ); + } +/* RX Error handler */ +if ((intflags &EIR_RXERIF ) !=0 ) { + loop++; +if (netif_msg_intr (priv)) + printk(KERN_DEBUG DRV_NAME +": intRXErr(%d)\n" , loop); +/* Check free FIFO space to flag RX overrun */ +if (enc28j60_get_free_rxfifo (priv) <=0 ) { +if (netif_msg_rx_err (priv)) + printk(KERN_DEBUG DRV_NAME +": RX Overrun\n" ); + ndev->stats.rx_dropped++; + } +locked_reg_bfclr (priv,EIR ,EIR_RXERIF ); + } +/* RX handler */ +if (enc28j60_rx_interrupt (ndev)) + loop++; + }while (loop); + +/* re-enable interrupts */ +locked_reg_bfset (priv,EIE ,EIE_INTIE ); +if (netif_msg_intr (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() exit\n", __FUNCTION__); +} + +/* +* Hardware transmit function. +* Fill the buffer memory and send the contents of the transmit buffer +* onto the network +*/ +static void enc28j60_hw_tx (struct enc28j60_net *priv) +{ +if (netif_msg_tx_queued (priv)) + printk(KERN_DEBUG DRV_NAME +": Tx Packet Len:%d\n" , priv->tx_skb->len); + +if (netif_msg_pktdata (priv)) +dump_packet (__FUNCTION__, + priv->tx_skb->len, priv->tx_skb->data); +enc28j60_packet_write (priv, priv->tx_skb->len, priv->tx_skb->data); + +#ifdef CONFIG_ENC28J60_WRITEVERIFY +/* readback and verify written data */ +if (netif_msg_drv (priv)) { +int test_len, k; +u8 test_buf[64 ];/* limit the test to the first 64 bytes */ +int okflag; + + test_len = priv->tx_skb->len; +if (test_len >sizeof (test_buf)) + test_len =sizeof (test_buf); + +/* + 1 to skip control byte */ +enc28j60_mem_read (priv,TXSTART_INIT +1 , test_len, test_buf); + okflag =1 ; +for (k =0 ; k < test_len; k++) { +if (priv->tx_skb->data[k] != test_buf[k]) { + printk(KERN_DEBUG DRV_NAME +": Error, %d location differ: " +"0x%02x-0x%02x\n" , k, + priv->tx_skb->data[k], test_buf[k]); + okflag =0 ; + } + } +if (!okflag) + printk(KERN_DEBUG DRV_NAME ": Tx write buffer, " +"verify ERROR!\n" ); + } +#endif +/* set TX request flag */ +locked_reg_bfset (priv,ECON1 ,ECON1_TXRTS ); +} + +static int enc28j60_send_packet (struct sk_buff *skb,struct net_device *dev) +{ +struct enc28j60_net *priv =netdev_priv (dev); + +if (netif_msg_tx_queued (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__); + +/* If some error occurs while trying to transmit this +* packet, you should return '1' from this function. +* In such a case you _may not_ do anything to the +* SKB, it is still owned by the network queueing +* layer when an error is returned. This means you +* may not modify any SKB fields, you may not free +* the SKB, etc. +*/ +netif_stop_queue (dev); + +/* save the timestamp */ + priv->netdev->trans_start = jiffies; +/* Remember the skb for deferred processing */ + priv->tx_skb = skb; +schedule_work (&priv->tx_work); + +return 0 ; +} + +static void enc28j60_tx_work_handler (struct work_struct *work) +{ +struct enc28j60_net *priv = + container_of(work, struct enc28j60_net, tx_work); + +/* actual delivery of data */ +enc28j60_hw_tx (priv); +} + +static irqreturn_t enc28j60_irq (int irq,void *dev_id) +{ +struct enc28j60_net *priv = dev_id; + +/* +* Can't do anything in interrupt context because we need to +* block (spi_sync() is blocking) so fire of the interrupt +* handling workqueue. +* Remember that we access enc28j60 registers through SPI bus +* via spi_sync() call. +*/ +schedule_work (&priv->irq_work); + +return IRQ_HANDLED ; +} + +static void enc28j60_tx_timeout (struct net_device *ndev) +{ +struct enc28j60_net *priv =netdev_priv (ndev); + +if (netif_msg_timer (priv)) +dev_err (&ndev->dev,DRV_NAME " tx timeout\n" ); + + ndev->stats.tx_errors++; +/* can't restart safely under softirq */ +schedule_work (&priv->restart_work); +} + +/* +* Open/initialize the board. This is called (in the current kernel) +* sometime after booting when the 'ifconfig' program is run. +* +* This routine should set everything up anew at each open, even +* registers that "should" only need to be set once at boot, so that +* there is non-reboot way to recover if something goes wrong. +*/ +static int enc28j60_net_open (struct net_device *dev) +{ +struct enc28j60_net *priv =netdev_priv (dev); + +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__); + +if (!is_valid_ether_addr (dev->dev_addr)) { +if (netif_msg_ifup (priv)) { +DECLARE_MAC_BUF (mac); +dev_err (&dev->dev,"invalid MAC address %s\n" , +print_mac (mac, dev->dev_addr)); + } +return -EADDRNOTAVAIL ; + } +/* Reset the hardware here */ +enc28j60_hw_disable (priv); +if (!enc28j60_hw_init (priv)) { +if (netif_msg_ifup (priv)) +dev_err (&dev->dev,"hw_reset() failed\n" ); +return -EINVAL ; + } +/* Update the MAC address (in case user has changed it) */ +enc28j60_set_hw_macaddr (dev); +/* Enable interrupts */ +enc28j60_hw_enable (priv); +/* check link status */ +enc28j60_check_link_status (dev); +/* We are now ready to accept transmit requests from +* the queueing layer of the networking. +*/ +netif_start_queue (dev); + +return 0 ; +} + +/* The inverse routine to net_open(). */ +static int enc28j60_net_close (struct net_device *dev) +{ +struct enc28j60_net *priv =netdev_priv (dev); + +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__); + +enc28j60_hw_disable (priv); +netif_stop_queue (dev); + +return 0 ; +} + +/* +* Set or clear the multicast filter for this adapter +* num_addrs == -1 Promiscuous mode, receive all packets +* num_addrs == 0 Normal mode, filter out multicast packets +* num_addrs > 0 Multicast mode, receive normal and MC packets +*/ +static void enc28j60_set_multicast_list (struct net_device *dev) +{ +struct enc28j60_net *priv =netdev_priv (dev); +int oldfilter = priv->rxfilter; + +if (dev->flags &IFF_PROMISC ) { +if (netif_msg_link (priv)) +dev_info (&dev->dev,"promiscuous mode\n" ); + priv->rxfilter =RXFILTER_PROMISC ; + }else if ((dev->flags &IFF_ALLMULTI ) || dev->mc_count) { +if (netif_msg_link (priv)) +dev_info (&dev->dev,"%smulticast mode\n" , + (dev->flags &IFF_ALLMULTI ) ?"all-" :"" ); + priv->rxfilter =RXFILTER_MULTI ; + }else { +if (netif_msg_link (priv)) +dev_info (&dev->dev,"normal mode\n" ); + priv->rxfilter =RXFILTER_NORMAL ; + } + +if (oldfilter != priv->rxfilter) +schedule_work (&priv->setrx_work); +} + +static void enc28j60_setrx_work_handler (struct work_struct *work) +{ +struct enc28j60_net *priv = + container_of(work, struct enc28j60_net, setrx_work); + +if (priv->rxfilter ==RXFILTER_PROMISC ) { +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": promiscuous mode\n"); +locked_regb_write (priv,ERXFCON ,0x00 ); + }else if (priv->rxfilter ==RXFILTER_MULTI ) { +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": multicast mode\n"); +locked_regb_write (priv,ERXFCON , +ERXFCON_UCEN |ERXFCON_CRCEN | +ERXFCON_BCEN |ERXFCON_MCEN ); + }else { +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": normal mode\n"); +locked_regb_write (priv,ERXFCON , +ERXFCON_UCEN |ERXFCON_CRCEN | +ERXFCON_BCEN ); + } +} + +static void enc28j60_restart_work_handler (struct work_struct *work) +{ +struct enc28j60_net *priv = + container_of(work, struct enc28j60_net, restart_work); +struct net_device *ndev = priv->netdev; +int ret; + +rtnl_lock (); +if (netif_running (ndev)) { +enc28j60_net_close (ndev); + ret =enc28j60_net_open (ndev); +if (unlikely (ret)) { +dev_info (&ndev->dev," could not restart %d\n" , ret); +dev_close (ndev); + } + } +rtnl_unlock (); +} + +/* ......................... ETHTOOL SUPPORT ........................... */ + +static void +enc28j60_get_drvinfo (struct net_device *dev,struct ethtool_drvinfo *info) +{ +strlcpy (info->driver,DRV_NAME ,sizeof (info->driver)); +strlcpy (info->version,DRV_VERSION ,sizeof (info->version)); +strlcpy (info->bus_info, + dev->dev.parent->bus_id,sizeof (info->bus_info)); +} + +static int +enc28j60_get_settings (struct net_device *dev,struct ethtool_cmd *cmd) +{ +struct enc28j60_net *priv =netdev_priv (dev); + + cmd->transceiver =XCVR_INTERNAL ; + cmd->supported = SUPPORTED_10baseT_Half + | SUPPORTED_10baseT_Full + |SUPPORTED_TP ; + cmd->speed =SPEED_10 ; + cmd->duplex = priv->full_duplex ?DUPLEX_FULL :DUPLEX_HALF ; + cmd->port =PORT_TP ; + cmd->autoneg =AUTONEG_DISABLE ; + +return 0 ; +} + +static int +enc28j60_set_settings (struct net_device *dev,struct ethtool_cmd *cmd) +{ +return enc28j60_setlink (dev, cmd->autoneg, cmd->speed, cmd->duplex); +} + +static u32 enc28j60_get_msglevel (struct net_device *dev) +{ +struct enc28j60_net *priv =netdev_priv (dev); +return priv->msg_enable; +} + +static void enc28j60_set_msglevel (struct net_device *dev,u32 val) +{ +struct enc28j60_net *priv =netdev_priv (dev); + priv->msg_enable = val; +} + +static const struct ethtool_ops enc28j60_ethtool_ops = { + .get_settings = enc28j60_get_settings, + .set_settings = enc28j60_set_settings, + .get_drvinfo = enc28j60_get_drvinfo, + .get_msglevel = enc28j60_get_msglevel, + .set_msglevel = enc28j60_set_msglevel, +}; + +static int enc28j60_chipset_init (struct net_device *dev) +{ +struct enc28j60_net *priv =netdev_priv (dev); + +return enc28j60_hw_init (priv); +} + +static int __devinit enc28j60_probe (struct spi_device *spi) +{ +struct net_device *dev; +struct enc28j60_net *priv; +int ret =0 ; + +if (netif_msg_drv (&debug)) +dev_info (&spi->dev,DRV_NAME " Ethernet driver %s loaded\n" , +DRV_VERSION ); + + dev =alloc_etherdev (sizeof (struct enc28j60_net )); +if (!dev) { +if (netif_msg_drv (&debug)) +dev_err (&spi->dev,DRV_NAME +": unable to alloc new ethernet\n" ); + ret = -ENOMEM ; + goto error_alloc; + } + priv =netdev_priv (dev); + + priv->netdev = dev;/* priv to netdev reference */ + priv->spi = spi;/* priv to spi reference */ + priv->msg_enable =netif_msg_init (debug.msg_enable, +ENC28J60_MSG_DEFAULT ); +mutex_init (&priv->lock); +INIT_WORK (&priv->tx_work, enc28j60_tx_work_handler); +INIT_WORK (&priv->setrx_work, enc28j60_setrx_work_handler); +INIT_WORK (&priv->irq_work, enc28j60_irq_work_handler); +INIT_WORK (&priv->restart_work, enc28j60_restart_work_handler); +dev_set_drvdata (&spi->dev, priv);/* spi to priv reference */ +SET_NETDEV_DEV (dev, &spi->dev); + +if (!enc28j60_chipset_init (dev)) { +if (netif_msg_probe (priv)) +dev_info (&spi->dev,DRV_NAME " chip not found\n" ); + ret = -EIO ; + goto error_irq; + } +random_ether_addr (dev->dev_addr); +enc28j60_set_hw_macaddr (dev); + + ret =request_irq (spi->irq, enc28j60_irq,IRQF_TRIGGER_FALLING , +DRV_NAME , priv); +if (ret <0 ) { +if (netif_msg_probe (priv)) +dev_err (&spi->dev,DRV_NAME ": request irq %d failed " +"(ret = %d)\n" , spi->irq, ret); + goto error_irq; + } + + dev->if_port =IF_PORT_10BASET ; + dev->irq = spi->irq; + dev->open = enc28j60_net_open; + dev->stop = enc28j60_net_close; + dev->hard_start_xmit = enc28j60_send_packet; + dev->set_multicast_list = &enc28j60_set_multicast_list; + dev->set_mac_address = enc28j60_set_mac_address; + dev->tx_timeout = &enc28j60_tx_timeout; + dev->watchdog_timeo =TX_TIMEOUT ; +SET_ETHTOOL_OPS (dev, &enc28j60_ethtool_ops); + + ret =register_netdev (dev); +if (ret) { +if (netif_msg_probe (priv)) +dev_err (&spi->dev,"register netdev " DRV_NAME +" failed (ret = %d)\n" , ret); + goto error_register; + } +dev_info (&dev->dev,DRV_NAME " driver registered\n" ); + +return 0 ; + +error_register: +free_irq (spi->irq, priv); +error_irq: +free_netdev (dev); +error_alloc: +return ret; +} + +static int enc28j60_remove (struct spi_device *spi) +{ +struct enc28j60_net *priv =dev_get_drvdata (&spi->dev); + +if (netif_msg_drv (priv)) + printk(KERN_DEBUG DRV_NAME ": remove\n"); + +unregister_netdev (priv->netdev); +free_irq (spi->irq, priv); +free_netdev (priv->netdev); + +return 0 ; +} + +static struct spi_driver enc28j60_driver = { + .driver = { + .name =DRV_NAME , + .bus = &spi_bus_type, + .owner =THIS_MODULE , + }, + .probe = enc28j60_probe, + .remove =__devexit_p (enc28j60_remove), +}; + +static int __init enc28j60_init (void ) +{ +return spi_register_driver (&enc28j60_driver); +} + +module_init (enc28j60_init); + +static void __exit enc28j60_exit(void) +{ +spi_unregister_driver (&enc28j60_driver); +} + +module_exit (enc28j60_exit); + +MODULE_DESCRIPTION (DRV_NAME " ethernet driver" ); +MODULE_AUTHOR ("Claudio Lanconelli <[email protected]>" ); +MODULE_LICENSE ("GPL" ); +module_param_named (debug, debug.msg_enable, int,0 ); +MODULE_PARM_DESC (debug,"Debug verbosity level (0=none, ..., ffff=all)" );
drivers/net/enc28j60_hw.h new file +309 -0
309 lines not on this page · raw message
drivers/net/Kconfig +18 -0
@@ -922,6 +922,24 @@ config DM9000 To compile this driver as a module, choose M here. The module will be called dm9000. +config ENC28J60 + tristate "ENC28J60 support" + depends on EXPERIMENTAL && SPI && NET_ETHERNET + select CRC32 + ---help--- + Support for the Microchip EN28J60 ethernet chip. + + To compile this driver as a module, choose M here and read + <file:Documentation/networking/net-modules.txt>. The module will be + called enc28j60. + +config ENC28J60_WRITEVERIFY + bool "Enable write verify" + depends on ENC28J60 + ---help--- + Enable the verify after the buffer write useful for debugging purpose. + If unsure, say N. + config SMC911X tristate "SMSC LAN911[5678] support" select CRC32
drivers/net/Makefile +1 -0
@@ -216,6 +216,7 @@ obj-$(CONFIG_DM9000) += dm9000.o obj-$(CONFIG_FEC_8XX) += fec_8xx/ obj-$(CONFIG_PASEMI_MAC) += pasemi_mac.o obj-$(CONFIG_MLX4_CORE) += mlx4/ +obj-$(CONFIG_ENC28J60) += enc28j60.o obj-$(CONFIG_MACB) += macb.o
oh yeah: make sure your Kconfig/Makefile stuff is in the _same_ patch as your driver.