All of lore.kernel.org
 help / color / mirror / Atom feed
From: Claudio Lanconelli <lanconelli.claudio@eptar.com>
To: netdev@vger.kernel.org
Cc: jgarzik@pobox.com
Subject: [PATCH 1/2] add driver for enc28j60 ethernet chip
Date: Tue, 11 Dec 2007 15:57:10 +0100	[thread overview]
Message-ID: <475EA546.7030202@eptar.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 237 bytes --]

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 <lanconelli.claudio@eptar.com>

[-- Attachment #2: enc28j60_p1.diff --]
[-- Type: text/x-patch, Size: 51496 bytes --]

 drivers/net/enc28j60.c    | 1400 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/enc28j60_hw.h |  303 ++++++++++
 2 files changed, 1703 insertions(+), 0 deletions(-)

diff --git a/drivers/net/enc28j60.c b/drivers/net/enc28j60.c
new file mode 100644
index 0000000..6182473
--- /dev/null
+++ b/drivers/net/enc28j60.c
@@ -0,0 +1,1400 @@
+/*
+ * Microchip ENC28J60 ethernet driver (MAC + PHY)
+ *
+ * Copyright (C) 2007 Eurek srl
+ * Author: Claudio Lanconelli <lanconelli.claudio@eptar.com>
+ * 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 <lanconelli.claudio@eptar.com>");
+MODULE_LICENSE("GPL");
+module_param(full_duplex, int, 0);
+MODULE_PARM_DESC(full_duplex, "Enable full duplex mode");
diff --git a/drivers/net/enc28j60_hw.h b/drivers/net/enc28j60_hw.h
new file mode 100644
index 0000000..78f415a
--- /dev/null
+++ b/drivers/net/enc28j60_hw.h
@@ -0,0 +1,303 @@
+/*
+ * enc28j60_hw.h: EDTP FrameThrower style enc28j60 registers
+ *
+ * $Id: enc28j60_hw.h,v 1.5 2007/12/11 10:35:40 claudio Exp $
+ */
+
+#ifndef _ENC28J60_HW_H
+#define _ENC28J60_HW_H
+
+/*
+ * ENC28J60 Control Registers
+ * Control register definitions are a combination of address,
+ * bank number, and Ethernet/MAC/PHY indicator bits.
+ * - Register address   (bits 0-4)
+ * - Bank number        (bits 5-6)
+ * - MAC/MII indicator  (bit 7)
+ */
+#define ADDR_MASK   0x1F
+#define BANK_MASK   0x60
+#define SPRD_MASK   0x80
+/* All-bank registers */
+#define EIE         0x1B
+#define EIR         0x1C
+#define ESTAT       0x1D
+#define ECON2       0x1E
+#define ECON1       0x1F
+/* Bank 0 registers */
+#define ERDPTL      (0x00|0x00)
+#define ERDPTH      (0x01|0x00)
+#define EWRPTL      (0x02|0x00)
+#define EWRPTH      (0x03|0x00)
+#define ETXSTL      (0x04|0x00)
+#define ETXSTH      (0x05|0x00)
+#define ETXNDL      (0x06|0x00)
+#define ETXNDH      (0x07|0x00)
+#define ERXSTL      (0x08|0x00)
+#define ERXSTH      (0x09|0x00)
+#define ERXNDL      (0x0A|0x00)
+#define ERXNDH      (0x0B|0x00)
+#define ERXRDPTL    (0x0C|0x00)
+#define ERXRDPTH    (0x0D|0x00)
+#define ERXWRPTL    (0x0E|0x00)
+#define ERXWRPTH    (0x0F|0x00)
+#define EDMASTL     (0x10|0x00)
+#define EDMASTH     (0x11|0x00)
+#define EDMANDL     (0x12|0x00)
+#define EDMANDH     (0x13|0x00)
+#define EDMADSTL    (0x14|0x00)
+#define EDMADSTH    (0x15|0x00)
+#define EDMACSL     (0x16|0x00)
+#define EDMACSH     (0x17|0x00)
+/* Bank 1 registers */
+#define EHT0        (0x00|0x20)
+#define EHT1        (0x01|0x20)
+#define EHT2        (0x02|0x20)
+#define EHT3        (0x03|0x20)
+#define EHT4        (0x04|0x20)
+#define EHT5        (0x05|0x20)
+#define EHT6        (0x06|0x20)
+#define EHT7        (0x07|0x20)
+#define EPMM0       (0x08|0x20)
+#define EPMM1       (0x09|0x20)
+#define EPMM2       (0x0A|0x20)
+#define EPMM3       (0x0B|0x20)
+#define EPMM4       (0x0C|0x20)
+#define EPMM5       (0x0D|0x20)
+#define EPMM6       (0x0E|0x20)
+#define EPMM7       (0x0F|0x20)
+#define EPMCSL      (0x10|0x20)
+#define EPMCSH      (0x11|0x20)
+#define EPMOL       (0x14|0x20)
+#define EPMOH       (0x15|0x20)
+#define EWOLIE      (0x16|0x20)
+#define EWOLIR      (0x17|0x20)
+#define ERXFCON     (0x18|0x20)
+#define EPKTCNT     (0x19|0x20)
+/* Bank 2 registers */
+#define MACON1      (0x00|0x40|SPRD_MASK)
+/* #define MACON2      (0x01|0x40|SPRD_MASK) */
+#define MACON3      (0x02|0x40|SPRD_MASK)
+#define MACON4      (0x03|0x40|SPRD_MASK)
+#define MABBIPG     (0x04|0x40|SPRD_MASK)
+#define MAIPGL      (0x06|0x40|SPRD_MASK)
+#define MAIPGH      (0x07|0x40|SPRD_MASK)
+#define MACLCON1    (0x08|0x40|SPRD_MASK)
+#define MACLCON2    (0x09|0x40|SPRD_MASK)
+#define MAMXFLL     (0x0A|0x40|SPRD_MASK)
+#define MAMXFLH     (0x0B|0x40|SPRD_MASK)
+#define MAPHSUP     (0x0D|0x40|SPRD_MASK)
+#define MICON       (0x11|0x40|SPRD_MASK)
+#define MICMD       (0x12|0x40|SPRD_MASK)
+#define MIREGADR    (0x14|0x40|SPRD_MASK)
+#define MIWRL       (0x16|0x40|SPRD_MASK)
+#define MIWRH       (0x17|0x40|SPRD_MASK)
+#define MIRDL       (0x18|0x40|SPRD_MASK)
+#define MIRDH       (0x19|0x40|SPRD_MASK)
+/* Bank 3 registers */
+#define MAADR1      (0x00|0x60|SPRD_MASK)
+#define MAADR0      (0x01|0x60|SPRD_MASK)
+#define MAADR3      (0x02|0x60|SPRD_MASK)
+#define MAADR2      (0x03|0x60|SPRD_MASK)
+#define MAADR5      (0x04|0x60|SPRD_MASK)
+#define MAADR4      (0x05|0x60|SPRD_MASK)
+#define EBSTSD      (0x06|0x60)
+#define EBSTCON     (0x07|0x60)
+#define EBSTCSL     (0x08|0x60)
+#define EBSTCSH     (0x09|0x60)
+#define MISTAT      (0x0A|0x60|SPRD_MASK)
+#define EREVID      (0x12|0x60)
+#define ECOCON      (0x15|0x60)
+#define EFLOCON     (0x17|0x60)
+#define EPAUSL      (0x18|0x60)
+#define EPAUSH      (0x19|0x60)
+/* PHY registers */
+#define PHCON1      0x00
+#define PHSTAT1     0x01
+#define PHHID1      0x02
+#define PHHID2      0x03
+#define PHCON2      0x10
+#define PHSTAT2     0x11
+#define PHIE        0x12
+#define PHIR        0x13
+#define PHLCON      0x14
+
+/* ENC28J60 EIE Register Bit Definitions */
+#define EIE_INTIE       0x80
+#define EIE_PKTIE       0x40
+#define EIE_DMAIE       0x20
+#define EIE_LINKIE      0x10
+#define EIE_TXIE        0x08
+#define EIE_WOLIE       0x04
+#define EIE_TXERIE      0x02
+#define EIE_RXERIE      0x01
+/* ENC28J60 EIR Register Bit Definitions */
+#define EIR_PKTIF       0x40
+#define EIR_DMAIF       0x20
+#define EIR_LINKIF      0x10
+#define EIR_TXIF        0x08
+#define EIR_WOLIF       0x04
+#define EIR_TXERIF      0x02
+#define EIR_RXERIF      0x01
+/* ENC28J60 ESTAT Register Bit Definitions */
+#define ESTAT_INT       0x80
+#define ESTAT_LATECOL   0x10
+#define ESTAT_RXBUSY    0x04
+#define ESTAT_TXABRT    0x02
+#define ESTAT_CLKRDY    0x01
+/* ENC28J60 ECON2 Register Bit Definitions */
+#define ECON2_AUTOINC   0x80
+#define ECON2_PKTDEC    0x40
+#define ECON2_PWRSV     0x20
+#define ECON2_VRPS      0x08
+/* ENC28J60 ECON1 Register Bit Definitions */
+#define ECON1_TXRST     0x80
+#define ECON1_RXRST     0x40
+#define ECON1_DMAST     0x20
+#define ECON1_CSUMEN    0x10
+#define ECON1_TXRTS     0x08
+#define ECON1_RXEN      0x04
+#define ECON1_BSEL1     0x02
+#define ECON1_BSEL0     0x01
+/* ENC28J60 MACON1 Register Bit Definitions */
+#define MACON1_LOOPBK   0x10
+#define MACON1_TXPAUS   0x08
+#define MACON1_RXPAUS   0x04
+#define MACON1_PASSALL  0x02
+#define MACON1_MARXEN   0x01
+/* ENC28J60 MACON2 Register Bit Definitions */
+#define MACON2_MARST    0x80
+#define MACON2_RNDRST   0x40
+#define MACON2_MARXRST  0x08
+#define MACON2_RFUNRST  0x04
+#define MACON2_MATXRST  0x02
+#define MACON2_TFUNRST  0x01
+/* ENC28J60 MACON3 Register Bit Definitions */
+#define MACON3_PADCFG2  0x80
+#define MACON3_PADCFG1  0x40
+#define MACON3_PADCFG0  0x20
+#define MACON3_TXCRCEN  0x10
+#define MACON3_PHDRLEN  0x08
+#define MACON3_HFRMLEN  0x04
+#define MACON3_FRMLNEN  0x02
+#define MACON3_FULDPX   0x01
+/* ENC28J60 MICMD Register Bit Definitions */
+#define MICMD_MIISCAN   0x02
+#define MICMD_MIIRD     0x01
+/* ENC28J60 MISTAT Register Bit Definitions */
+#define MISTAT_NVALID   0x04
+#define MISTAT_SCAN     0x02
+#define MISTAT_BUSY     0x01
+/* ENC28J60 ERXFCON Register Bit Definitions */
+#define ERXFCON_UCEN	0x80
+#define ERXFCON_ANDOR	0x40
+#define ERXFCON_CRCEN	0x20
+#define ERXFCON_PMEN	0x10
+#define ERXFCON_MPEN	0x08
+#define ERXFCON_HTEN	0x04
+#define ERXFCON_MCEN	0x02
+#define ERXFCON_BCEN	0x01
+
+/* ENC28J60 PHY PHCON1 Register Bit Definitions */
+#define PHCON1_PRST     0x8000
+#define PHCON1_PLOOPBK  0x4000
+#define PHCON1_PPWRSV   0x0800
+#define PHCON1_PDPXMD   0x0100
+/* ENC28J60 PHY PHSTAT1 Register Bit Definitions */
+#define PHSTAT1_PFDPX   0x1000
+#define PHSTAT1_PHDPX   0x0800
+#define PHSTAT1_LLSTAT  0x0004
+#define PHSTAT1_JBSTAT  0x0002
+/* ENC28J60 PHY PHCON2 Register Bit Definitions */
+#define PHCON2_FRCLINK  0x4000
+#define PHCON2_TXDIS    0x2000
+#define PHCON2_JABBER   0x0400
+#define PHCON2_HDLDIS   0x0100
+
+/* ENC28J60 Packet Control Byte Bit Definitions */
+#define PKTCTRL_PHUGEEN     0x08
+#define PKTCTRL_PPADEN      0x04
+#define PKTCTRL_PCRCEN      0x02
+#define PKTCTRL_POVERRIDE   0x01
+
+/* ENC28J60 Transmit Status Vector */
+#define TSV_TXBYTECNT		0
+#define TSV_TXCOLLISIONCNT	16
+#define TSV_TXCRCERROR		20
+#define TSV_TXLENCHKERROR	21
+#define TSV_TXLENOUTOFRANGE	22
+#define TSV_TXDONE		23
+#define TSV_TXMULTICAST		24
+#define TSV_TXBROADCAST		25
+#define TSV_TXPACKETDEFER	26
+#define TSV_TXEXDEFER		27
+#define TSV_TXEXCOLLISION	28
+#define TSV_TXLATECOLLISION	29
+#define TSV_TXGIANT		30
+#define TSV_TXUNDERRUN		31
+#define TSV_TOTBYTETXONWIRE	32
+#define TSV_TXCONTROLFRAME	48
+#define TSV_TXPAUSEFRAME	49
+#define TSV_BACKPRESSUREAPP	50
+#define TSV_TXVLANTAGFRAME	51
+
+#define TSV_SIZE 7
+#define TSV_BYTEOF(x)		((x) / 8)
+#define TSV_BITMASK(x)		(1 << ((x) % 8))
+#define TSV_GETBIT(x, y)	(((x)[TSV_BYTEOF(y)] & TSV_BITMASK(y)) ? 1 : 0)
+
+/* ENC28J60 Receive Status Vector */
+#define RSV_RXLONGEVDROPEV	16
+#define RSV_CARRIEREV		18
+#define RSV_CRCERROR		20
+#define RSV_LENCHECKERR		21
+#define RSV_LENOUTOFRANGE	22
+#define RSV_RXOK		23
+#define RSV_RXMULTICAST		24
+#define RSV_RXBROADCAST		25
+#define RSV_DRIBBLENIBBLE	26
+#define RSV_RXCONTROLFRAME	27
+#define RSV_RXPAUSEFRAME	28
+#define RSV_RXUNKNOWNOPCODE	29
+#define RSV_RXTYPEVLAN		30
+
+#define RSV_BITMASK(x)		(1 << ((x) - 16))
+#define RSV_GETBIT(x, y)	(((x) & RSV_BITMASK(y)) ? 1 : 0)
+
+
+/* SPI operation codes */
+#define ENC28J60_READ_CTRL_REG  0x00
+#define ENC28J60_READ_BUF_MEM   0x3A
+#define ENC28J60_WRITE_CTRL_REG 0x40
+#define ENC28J60_WRITE_BUF_MEM  0x7A
+#define ENC28J60_BIT_FIELD_SET  0x80
+#define ENC28J60_BIT_FIELD_CLR  0xA0
+#define ENC28J60_SOFT_RESET     0xFF
+
+
+/* buffer boundaries applied to internal 8K ram
+ * entire available packet buffer space is allocated.
+ * Give TX buffer space for one full ethernet frame (~1500 bytes)
+ * receive buffer gets the rest */
+#define TXSTART_INIT	0x1A00
+#define TXEND_INIT	0x1FFF
+
+/* Put RX buffer at 0 as suggested by the Errata datasheet */
+#define RXSTART_INIT	0x0000
+#define RXEND_INIT	0x19FF
+
+/* maximum ethernet frame length */
+#define MAX_FRAMELEN    1518
+
+/* Prefered half duplex: LEDA: Link status LEDB: Rx/Tx activity */
+#define ENC28J60_LAMPS_MODE	0x3476
+
+/* Default MAC address for this interface */
+#define ENC28J60_MAC0 0x00
+#define ENC28J60_MAC1 0x00
+#define ENC28J60_MAC2 'F'
+#define ENC28J60_MAC3 'I'
+#define ENC28J60_MAC4 'C'
+#define ENC28J60_MAC5 'E'
+
+#endif

             reply	other threads:[~2007-12-11 15:03 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-12-11 14:57 Claudio Lanconelli [this message]
2007-12-11 17:06 ` [PATCH 1/2] add driver for enc28j60 ethernet chip Stephen Hemminger
2007-12-14  9:21   ` Claudio Lanconelli
2007-12-14 18:56     ` Stephen Hemminger
2007-12-17 23:49 ` Jeff Garzik
2007-12-20 11:47   ` Claudio Lanconelli
2007-12-17 23:49 ` Jeff Garzik

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=475EA546.7030202@eptar.com \
    --to=lanconelli.claudio@eptar.com \
    --cc=jgarzik@pobox.com \
    --cc=netdev@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.