Skip to content
Snippets Groups Projects
fec_main.c 58.9 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
/*
 * Fast Ethernet Controller (FEC) driver for Motorola MPC8xx.
 * Copyright (c) 1997 Dan Malek (dmalek@jlc.net)
 *
 * Right now, I am very wasteful with the buffers.  I allocate memory
Linus Torvalds's avatar
Linus Torvalds committed
 * pages and then divide them into 2K frame buffers.  This way I know I
 * have buffers large enough to hold one frame within one buffer descriptor.
 * Once I get this working, I will use 64 or 128 byte CPM buffers, which
 * will be much more memory efficient and will easily handle lots of
 * small packets.
 *
 * Much better multiple PHY support by Magnus Damm.
 * Copyright (c) 2000 Ericsson Radio Systems AB.
 *
 * Support for FEC controller of ColdFire processors.
 * Copyright (c) 2001-2005 Greg Ungerer (gerg@snapgear.com)
 *
 * Bug fixes and cleanup by Philippe De Muyter (phdm@macqel.be)
 * Copyright (c) 2004-2006 Macq Electronique SA.
 * Copyright (C) 2010-2011 Freescale Semiconductor, Inc.
Linus Torvalds's avatar
Linus Torvalds committed
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ptrace.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <net/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
Linus Torvalds's avatar
Linus Torvalds committed
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/fec.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_net.h>
#include <linux/regulator/consumer.h>
#include <linux/if_vlan.h>
Linus Torvalds's avatar
Linus Torvalds committed

#include <asm/cacheflush.h>
Linus Torvalds's avatar
Linus Torvalds committed
#include "fec.h"

static void set_multicast_list(struct net_device *ndev);

#if defined(CONFIG_ARM)
#define FEC_ALIGNMENT	0xf
#else
#define FEC_ALIGNMENT	0x3
#endif

#define DRIVER_NAME	"fec"
#define FEC_NAPI_WEIGHT	64
/* Pause frame feild and FIFO threshold */
#define FEC_ENET_FCE	(1 << 5)
#define FEC_ENET_RSEM_V	0x84
#define FEC_ENET_RSFL_V	16
#define FEC_ENET_RAEM_V	0x8
#define FEC_ENET_RAFL_V	0x8
#define FEC_ENET_OPD_V	0xFFF0

/* Controller is ENET-MAC */
#define FEC_QUIRK_ENET_MAC		(1 << 0)
/* Controller needs driver to swap frame */
#define FEC_QUIRK_SWAP_FRAME		(1 << 1)
/* Controller uses gasket */
#define FEC_QUIRK_USE_GASKET		(1 << 2)
/* Controller has GBIT support */
#define FEC_QUIRK_HAS_GBIT		(1 << 3)
/* Controller has extend desc buffer */
#define FEC_QUIRK_HAS_BUFDESC_EX	(1 << 4)
/* Controller has hardware checksum support */
#define FEC_QUIRK_HAS_CSUM		(1 << 5)
/* Controller has hardware vlan support */
#define FEC_QUIRK_HAS_VLAN		(1 << 6)
/* ENET IP errata ERR006358
 *
 * If the ready bit in the transmit buffer descriptor (TxBD[R]) is previously
 * detected as not set during a prior frame transmission, then the
 * ENET_TDAR[TDAR] bit is cleared at a later time, even if additional TxBDs
 * were added to the ring and the ENET_TDAR[TDAR] bit is set. This results in
 * If the ready bit in the transmit buffer descriptor (TxBD[R]) is previously
 * detected as not set during a prior frame transmission, then the
 * ENET_TDAR[TDAR] bit is cleared at a later time, even if additional TxBDs
 * were added to the ring and the ENET_TDAR[TDAR] bit is set. This results in
 * frames not being transmitted until there is a 0-to-1 transition on
 * ENET_TDAR[TDAR].
 */
#define FEC_QUIRK_ERR006358            (1 << 7)

static struct platform_device_id fec_devtype[] = {
	{
		/* keep it for coldfire */
		.name = DRIVER_NAME,
		.driver_data = 0,
	}, {
		.name = "imx25-fec",
		.driver_data = FEC_QUIRK_USE_GASKET,
	}, {
		.name = "imx27-fec",
		.driver_data = 0,
	}, {
		.name = "imx28-fec",
		.driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_SWAP_FRAME,
	}, {
		.name = "imx6q-fec",
		.driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_GBIT |
				FEC_QUIRK_HAS_BUFDESC_EX | FEC_QUIRK_HAS_CSUM |
				FEC_QUIRK_HAS_VLAN | FEC_QUIRK_ERR006358,
		.driver_data = FEC_QUIRK_ENET_MAC,
MODULE_DEVICE_TABLE(platform, fec_devtype);
enum imx_fec_type {
Lothar Waßmann's avatar
Lothar Waßmann committed
	IMX25_FEC = 1,	/* runs on i.mx25/50/53 */
	IMX27_FEC,	/* runs on i.mx27/35/51 */
	IMX28_FEC,
	IMX6Q_FEC,
};

static const struct of_device_id fec_dt_ids[] = {
	{ .compatible = "fsl,imx25-fec", .data = &fec_devtype[IMX25_FEC], },
	{ .compatible = "fsl,imx27-fec", .data = &fec_devtype[IMX27_FEC], },
	{ .compatible = "fsl,imx28-fec", .data = &fec_devtype[IMX28_FEC], },
	{ .compatible = "fsl,imx6q-fec", .data = &fec_devtype[IMX6Q_FEC], },
	{ .compatible = "fsl,mvf600-fec", .data = &fec_devtype[MVF600_FEC], },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fec_dt_ids);

static unsigned char macaddr[ETH_ALEN];
module_param_array(macaddr, byte, NULL, 0);
MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address");
Linus Torvalds's avatar
Linus Torvalds committed

Linus Torvalds's avatar
Linus Torvalds committed
/*
 * Some hardware gets it MAC address out of local flash memory.
 * if this is non-zero then assume it is the address to get MAC from.
 */
#if defined(CONFIG_NETtel)
#define	FEC_FLASHMAC	0xf0006006
#elif defined(CONFIG_GILBARCONAP) || defined(CONFIG_SCALES)
#define	FEC_FLASHMAC	0xf0006000
#elif defined(CONFIG_CANCam)
#define	FEC_FLASHMAC	0xf0020000
#elif defined (CONFIG_M5272C3)
#define	FEC_FLASHMAC	(0xffe04000 + 4)
#elif defined(CONFIG_MOD5272)
Lothar Waßmann's avatar
Lothar Waßmann committed
#define FEC_FLASHMAC	0xffc0406b
Linus Torvalds's avatar
Linus Torvalds committed
#else
#define	FEC_FLASHMAC	0
#endif
#if (((RX_RING_SIZE + TX_RING_SIZE) * 32) > PAGE_SIZE)
#error "FEC: descriptor ring size constants too large"
Sascha Hauer's avatar
Sascha Hauer committed
/* Interrupt events/masks. */
Linus Torvalds's avatar
Linus Torvalds committed
#define FEC_ENET_HBERR	((uint)0x80000000)	/* Heartbeat error */
#define FEC_ENET_BABR	((uint)0x40000000)	/* Babbling receiver */
#define FEC_ENET_BABT	((uint)0x20000000)	/* Babbling transmitter */
#define FEC_ENET_GRA	((uint)0x10000000)	/* Graceful stop complete */
#define FEC_ENET_TXF	((uint)0x08000000)	/* Full frame transmitted */
#define FEC_ENET_TXB	((uint)0x04000000)	/* A buffer was transmitted */
#define FEC_ENET_RXF	((uint)0x02000000)	/* Full frame received */
#define FEC_ENET_RXB	((uint)0x01000000)	/* A buffer was received */
#define FEC_ENET_MII	((uint)0x00800000)	/* MII interrupt */
#define FEC_ENET_EBERR	((uint)0x00400000)	/* SDMA bus error */

#define FEC_DEFAULT_IMASK (FEC_ENET_TXF | FEC_ENET_RXF | FEC_ENET_MII)
#define FEC_RX_DISABLED_IMASK (FEC_DEFAULT_IMASK & (~FEC_ENET_RXF))
/* The FEC stores dest/src/type/vlan, data, and checksum for receive packets.
Linus Torvalds's avatar
Linus Torvalds committed
 */
#define PKT_MAXBUF_SIZE		1522
Linus Torvalds's avatar
Linus Torvalds committed
#define PKT_MINBUF_SIZE		64
#define PKT_MAXBLR_SIZE		1536
Linus Torvalds's avatar
Linus Torvalds committed

/* FEC receive acceleration */
#define FEC_RACC_IPDIS		(1 << 1)
#define FEC_RACC_PRODIS		(1 << 2)
#define FEC_RACC_OPTIONS	(FEC_RACC_IPDIS | FEC_RACC_PRODIS)

Linus Torvalds's avatar
Linus Torvalds committed
/*
 * The 5270/5271/5280/5282/532x RX control register also contains maximum frame
Linus Torvalds's avatar
Linus Torvalds committed
 * size bits. Other FEC hardware does not, so we need to take that into
 * account when setting it.
 */
#if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \
    defined(CONFIG_M520x) || defined(CONFIG_M532x) || defined(CONFIG_ARM)
Linus Torvalds's avatar
Linus Torvalds committed
#define	OPT_FRAME_SIZE	(PKT_MAXBUF_SIZE << 16)
#else
#define	OPT_FRAME_SIZE	0
#endif

/* FEC MII MMFR bits definition */
#define FEC_MMFR_ST		(1 << 30)
#define FEC_MMFR_OP_READ	(2 << 28)
#define FEC_MMFR_OP_WRITE	(1 << 28)
#define FEC_MMFR_PA(v)		((v & 0x1f) << 23)
#define FEC_MMFR_RA(v)		((v & 0x1f) << 18)
#define FEC_MMFR_TA		(2 << 16)
#define FEC_MMFR_DATA(v)	(v & 0xffff)
Linus Torvalds's avatar
Linus Torvalds committed

#define FEC_MII_TIMEOUT		30000 /* us */
Linus Torvalds's avatar
Linus Torvalds committed

Sascha Hauer's avatar
Sascha Hauer committed
/* Transmitter timeout */
#define TX_TIMEOUT (2 * HZ)
Linus Torvalds's avatar
Linus Torvalds committed

#define FEC_PAUSE_FLAG_AUTONEG	0x1
#define FEC_PAUSE_FLAG_ENABLE	0x2

static int mii_cnt;

static struct bufdesc *fec_enet_get_nextdesc(struct bufdesc *bdp, int is_ex)
{
	struct bufdesc_ex *ex = (struct bufdesc_ex *)bdp;
	if (is_ex)
		return (struct bufdesc *)(ex + 1);
	else
		return bdp + 1;
}

static struct bufdesc *fec_enet_get_prevdesc(struct bufdesc *bdp, int is_ex)
{
	struct bufdesc_ex *ex = (struct bufdesc_ex *)bdp;
	if (is_ex)
		return (struct bufdesc *)(ex - 1);
	else
		return bdp - 1;
}

static void *swap_buffer(void *bufaddr, int len)
{
	int i;
	unsigned int *buf = bufaddr;

	for (i = 0; i < DIV_ROUND_UP(len, 4); i++, buf++)
		*buf = cpu_to_be32(*buf);

	return bufaddr;
}

static int
fec_enet_clear_csum(struct sk_buff *skb, struct net_device *ndev)
{
	/* Only run for packets requiring a checksum. */
	if (skb->ip_summed != CHECKSUM_PARTIAL)
		return 0;

	if (unlikely(skb_cow_head(skb, 0)))
		return -1;

	*(__sum16 *)(skb->head + skb->csum_start + skb->csum_offset) = 0;

	return 0;
}

static netdev_tx_t
fec_enet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	const struct platform_device_id *id_entry =
				platform_get_device_id(fep->pdev);
	struct bufdesc *bdp, *bdp_pre;
	unsigned short	status;
Linus Torvalds's avatar
Linus Torvalds committed

	/* Fill in a Tx ring entry */
	bdp = fep->cur_tx;

	status = bdp->cbd_sc;
	if (status & BD_ENET_TX_READY) {
Linus Torvalds's avatar
Linus Torvalds committed
		/* Ooops.  All transmit buffers are full.  Bail out.
		 * This should not happen, since ndev->tbusy should be set.
Linus Torvalds's avatar
Linus Torvalds committed
		 */
		netdev_err(ndev, "tx queue full!\n");
	/* Protocol checksum off-load for TCP and UDP. */
	if (fec_enet_clear_csum(skb, ndev)) {
		kfree_skb(skb);
		return NETDEV_TX_OK;
	}

Sascha Hauer's avatar
Sascha Hauer committed
	/* Clear all of the status flags */
	status &= ~BD_ENET_TX_STATS;
Linus Torvalds's avatar
Linus Torvalds committed

Sascha Hauer's avatar
Sascha Hauer committed
	/* Set buffer length and buffer pointer */
	bufaddr = skb->data;
Linus Torvalds's avatar
Linus Torvalds committed
	bdp->cbd_datlen = skb->len;

	/*
Sascha Hauer's avatar
Sascha Hauer committed
	 * On some FEC implementations data must be aligned on
	 * 4-byte boundaries. Use bounce buffers to copy data
	 * and get it aligned. Ugh.
Linus Torvalds's avatar
Linus Torvalds committed
	 */
	if (fep->bufdesc_ex)
		index = (struct bufdesc_ex *)bdp -
			(struct bufdesc_ex *)fep->tx_bd_base;
	else
		index = bdp - fep->tx_bd_base;

	if (((unsigned long) bufaddr) & FEC_ALIGNMENT) {
		memcpy(fep->tx_bounce[index], skb->data, skb->len);
		bufaddr = fep->tx_bounce[index];
	/*
	 * Some design made an incorrect assumption on endian mode of
	 * the system that it's running on. As the result, driver has to
	 * swap every frame going to and coming from the controller.
	 */
	if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
		swap_buffer(bufaddr, skb->len);

Sascha Hauer's avatar
Sascha Hauer committed
	/* Save skb pointer */
	fep->tx_skbuff[index] = skb;
Linus Torvalds's avatar
Linus Torvalds committed
	/* Push the data cache so the CPM does not get stale memory
	 * data.
	 */
	bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, bufaddr,
Sascha Hauer's avatar
Sascha Hauer committed
			FEC_ENET_TX_FRSIZE, DMA_TO_DEVICE);
Linus Torvalds's avatar
Linus Torvalds committed

	/* Send it on its way.  Tell FEC it's ready, interrupt when done,
	 * it's the last BD of the frame, and to put the CRC on the end.
Linus Torvalds's avatar
Linus Torvalds committed
	 */
	status |= (BD_ENET_TX_READY | BD_ENET_TX_INTR
Linus Torvalds's avatar
Linus Torvalds committed
			| BD_ENET_TX_LAST | BD_ENET_TX_TC);
	bdp->cbd_sc = status;
Linus Torvalds's avatar
Linus Torvalds committed

	if (fep->bufdesc_ex) {

		struct bufdesc_ex *ebdp = (struct bufdesc_ex *)bdp;
		ebdp->cbd_bdu = 0;
		if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
			fep->hwts_tx_en)) {
			ebdp->cbd_esc = (BD_ENET_TX_TS | BD_ENET_TX_INT);
			skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
		} else {
			ebdp->cbd_esc = BD_ENET_TX_INT;

			/* Enable protocol checksum flags
			 * We do not bother with the IP Checksum bits as they
			 * are done by the kernel
			 */
			if (skb->ip_summed == CHECKSUM_PARTIAL)
				ebdp->cbd_esc |= BD_ENET_TX_PINS;

	bdp_pre = fec_enet_get_prevdesc(bdp, fep->bufdesc_ex);
	if ((id_entry->driver_data & FEC_QUIRK_ERR006358) &&
	    !(bdp_pre->cbd_sc & BD_ENET_TX_READY)) {
		fep->delay_work.trig_tx = true;
		schedule_delayed_work(&(fep->delay_work.delay_work),
					msecs_to_jiffies(1));
	}

Sascha Hauer's avatar
Sascha Hauer committed
	/* If this was the last BD in the ring, start at the beginning again. */
	if (status & BD_ENET_TX_WRAP)
Linus Torvalds's avatar
Linus Torvalds committed
		bdp = fep->tx_bd_base;
Sascha Hauer's avatar
Sascha Hauer committed
	else
		bdp = fec_enet_get_nextdesc(bdp, fep->bufdesc_ex);
Linus Torvalds's avatar
Linus Torvalds committed

	fep->cur_tx = bdp;

	if (fep->cur_tx == fep->dirty_tx)
Linus Torvalds's avatar
Linus Torvalds committed

	/* Trigger transmission start */
	writel(0, fep->hwp + FEC_X_DES_ACTIVE);
Linus Torvalds's avatar
Linus Torvalds committed

/* Init RX & TX buffer descriptors
 */
static void fec_enet_bd_init(struct net_device *dev)
{
	struct fec_enet_private *fep = netdev_priv(dev);
	struct bufdesc *bdp;
	unsigned int i;

	/* Initialize the receive buffer descriptors. */
	bdp = fep->rx_bd_base;
	for (i = 0; i < RX_RING_SIZE; i++) {

		/* Initialize the BD for every fragment in the page. */
		if (bdp->cbd_bufaddr)
			bdp->cbd_sc = BD_ENET_RX_EMPTY;
		else
			bdp->cbd_sc = 0;
		bdp = fec_enet_get_nextdesc(bdp, fep->bufdesc_ex);
	}

	/* Set the last buffer to wrap */
	bdp = fec_enet_get_prevdesc(bdp, fep->bufdesc_ex);
	bdp->cbd_sc |= BD_SC_WRAP;

	fep->cur_rx = fep->rx_bd_base;

	/* ...and the same for transmit */
	bdp = fep->tx_bd_base;
	fep->cur_tx = bdp;
	for (i = 0; i < TX_RING_SIZE; i++) {

		/* Initialize the BD for every fragment in the page. */
		bdp->cbd_sc = 0;
		if (bdp->cbd_bufaddr && fep->tx_skbuff[i]) {
			dev_kfree_skb_any(fep->tx_skbuff[i]);
			fep->tx_skbuff[i] = NULL;
		}
		bdp->cbd_bufaddr = 0;
		bdp = fec_enet_get_nextdesc(bdp, fep->bufdesc_ex);
	}

	/* Set the last buffer to wrap */
	bdp = fec_enet_get_prevdesc(bdp, fep->bufdesc_ex);
	bdp->cbd_sc |= BD_SC_WRAP;
	fep->dirty_tx = bdp;
}

/* This function is called to start or restart the FEC during a link
 * change.  This only happens when switching between half and full
 * duplex.
 */
Linus Torvalds's avatar
Linus Torvalds committed
static void
fec_restart(struct net_device *ndev, int duplex)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	const struct platform_device_id *id_entry =
				platform_get_device_id(fep->pdev);
	int i;
	u32 temp_mac[2];
	u32 rcntl = OPT_FRAME_SIZE | 0x04;
	u32 ecntl = 0x2; /* ETHEREN */
Linus Torvalds's avatar
Linus Torvalds committed

	if (netif_running(ndev)) {
		netif_device_detach(ndev);
		napi_disable(&fep->napi);
		netif_stop_queue(ndev);
		netif_tx_lock_bh(ndev);
	/* Whack a reset.  We should wait for this. */
	writel(1, fep->hwp + FEC_ECNTRL);
	udelay(10);
Linus Torvalds's avatar
Linus Torvalds committed

	/*
	 * enet-mac reset will reset mac address registers too,
	 * so need to reconfigure it.
	 */
	if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) {
		memcpy(&temp_mac, ndev->dev_addr, ETH_ALEN);
		writel(cpu_to_be32(temp_mac[0]), fep->hwp + FEC_ADDR_LOW);
		writel(cpu_to_be32(temp_mac[1]), fep->hwp + FEC_ADDR_HIGH);
	}
Linus Torvalds's avatar
Linus Torvalds committed

	/* Clear any outstanding interrupt. */
	writel(0xffc00000, fep->hwp + FEC_IEVENT);
Linus Torvalds's avatar
Linus Torvalds committed

	/* Setup multicast filter. */
	set_multicast_list(ndev);
#ifndef CONFIG_M5272
	writel(0, fep->hwp + FEC_HASH_TABLE_HIGH);
	writel(0, fep->hwp + FEC_HASH_TABLE_LOW);
#endif
Linus Torvalds's avatar
Linus Torvalds committed

	/* Set maximum receive buffer size. */
	writel(PKT_MAXBLR_SIZE, fep->hwp + FEC_R_BUFF_SIZE);
Linus Torvalds's avatar
Linus Torvalds committed

	fec_enet_bd_init(ndev);

	/* Set receive and transmit descriptor base. */
	writel(fep->bd_dma, fep->hwp + FEC_R_DES_START);
	if (fep->bufdesc_ex)
		writel((unsigned long)fep->bd_dma + sizeof(struct bufdesc_ex)
			* RX_RING_SIZE, fep->hwp + FEC_X_DES_START);
	else
		writel((unsigned long)fep->bd_dma + sizeof(struct bufdesc)
			* RX_RING_SIZE,	fep->hwp + FEC_X_DES_START);


	for (i = 0; i <= TX_RING_MOD_MASK; i++) {
		if (fep->tx_skbuff[i]) {
			dev_kfree_skb_any(fep->tx_skbuff[i]);
			fep->tx_skbuff[i] = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
		}
		writel(0x04, fep->hwp + FEC_X_CNTRL);
	} else {
		/* No Rcv on Xmit */
		rcntl |= 0x02;
	fep->full_duplex = duplex;

	/* Set MII speed */
	writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);

#if !defined(CONFIG_M5272)
	/* set RX checksum */
	val = readl(fep->hwp + FEC_RACC);
	if (fep->csum_flags & FLAG_RX_CSUM_ENABLED)
		val |= FEC_RACC_OPTIONS;
	else
		val &= ~FEC_RACC_OPTIONS;
	writel(val, fep->hwp + FEC_RACC);
	/*
	 * The phy interface and speed need to get configured
	 * differently on enet-mac.
	 */
	if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) {
		/* Enable flow control and length check */
		rcntl |= 0x40000000 | 0x00000020;
		/* RGMII, RMII or MII */
		if (fep->phy_interface == PHY_INTERFACE_MODE_RGMII)
			rcntl |= (1 << 6);
		else if (fep->phy_interface == PHY_INTERFACE_MODE_RMII)
		/* 1G, 100M or 10M */
		if (fep->phy_dev) {
			if (fep->phy_dev->speed == SPEED_1000)
				ecntl |= (1 << 5);
			else if (fep->phy_dev->speed == SPEED_100)
				rcntl &= ~(1 << 9);
			else
				rcntl |= (1 << 9);
		}
		if (id_entry->driver_data & FEC_QUIRK_USE_GASKET) {
			/* disable the gasket and wait */
			writel(0, fep->hwp + FEC_MIIGSK_ENR);
			while (readl(fep->hwp + FEC_MIIGSK_ENR) & 4)
				udelay(1);

			/*
			 * configure the gasket:
			 *   RMII, 50 MHz, no loopback, no echo
			 *   MII, 25 MHz, no loopback, no echo
			cfgr = (fep->phy_interface == PHY_INTERFACE_MODE_RMII)
				? BM_MIIGSK_CFGR_RMII : BM_MIIGSK_CFGR_MII;
			if (fep->phy_dev && fep->phy_dev->speed == SPEED_10)
				cfgr |= BM_MIIGSK_CFGR_FRCONT_10M;
			writel(cfgr, fep->hwp + FEC_MIIGSK_CFGR);

			/* re-enable the gasket */
			writel(2, fep->hwp + FEC_MIIGSK_ENR);
#if !defined(CONFIG_M5272)
	/* enable pause frame*/
	if ((fep->pause_flag & FEC_PAUSE_FLAG_ENABLE) ||
	    ((fep->pause_flag & FEC_PAUSE_FLAG_AUTONEG) &&
	     fep->phy_dev && fep->phy_dev->pause)) {
		rcntl |= FEC_ENET_FCE;

		/* set FIFO threshold parameter to reduce overrun */
		writel(FEC_ENET_RSEM_V, fep->hwp + FEC_R_FIFO_RSEM);
		writel(FEC_ENET_RSFL_V, fep->hwp + FEC_R_FIFO_RSFL);
		writel(FEC_ENET_RAEM_V, fep->hwp + FEC_R_FIFO_RAEM);
		writel(FEC_ENET_RAFL_V, fep->hwp + FEC_R_FIFO_RAFL);

		/* OPD */
		writel(FEC_ENET_OPD_V, fep->hwp + FEC_OPD);
	} else {
		rcntl &= ~FEC_ENET_FCE;
	}
#endif /* !defined(CONFIG_M5272) */
	writel(rcntl, fep->hwp + FEC_R_CNTRL);
	if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) {
		/* enable ENET endian swap */
		ecntl |= (1 << 8);
		/* enable ENET store and forward mode */
		writel(1 << 8, fep->hwp + FEC_X_WMRK);
	}

	if (fep->bufdesc_ex)
		ecntl |= (1 << 4);
#ifndef CONFIG_M5272
	/* Enable the MIB statistic event counters */
	writel(0 << 31, fep->hwp + FEC_MIB_CTRLSTAT);
	/* And last, enable the transmit and receive processing */
	writel(ecntl, fep->hwp + FEC_ECNTRL);
	if (fep->bufdesc_ex)
		fec_ptp_start_cyclecounter(ndev);

	/* Enable interrupts we wish to service */
	writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
		netif_tx_unlock_bh(ndev);
		netif_wake_queue(ndev);
		napi_enable(&fep->napi);
		netif_device_attach(ndev);
}

static void
fec_stop(struct net_device *ndev)
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	const struct platform_device_id *id_entry =
				platform_get_device_id(fep->pdev);
	u32 rmii_mode = readl(fep->hwp + FEC_R_CNTRL) & (1 << 8);

	/* We cannot expect a graceful transmit stop without link !!! */
	if (fep->link) {
		writel(1, fep->hwp + FEC_X_CNTRL); /* Graceful transmit stop */
		udelay(10);
		if (!(readl(fep->hwp + FEC_IEVENT) & FEC_ENET_GRA))
			netdev_err(ndev, "Graceful transmit stop did not complete!\n");
	}

	/* Whack a reset.  We should wait for this. */
	writel(1, fep->hwp + FEC_ECNTRL);
	udelay(10);
	writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
	writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);

	/* We have to keep ENET enabled to have MII interrupt stay working */
	if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) {
		writel(2, fep->hwp + FEC_ECNTRL);
		writel(rmii_mode, fep->hwp + FEC_R_CNTRL);
	}
static void
fec_timeout(struct net_device *ndev)
{
	struct fec_enet_private *fep = netdev_priv(ndev);

	ndev->stats.tx_errors++;

	fep->delay_work.timeout = true;
	schedule_delayed_work(&(fep->delay_work.delay_work), 0);
}

static void fec_enet_work(struct work_struct *work)
{
	struct fec_enet_private *fep =
		container_of(work,
			     struct fec_enet_private,
			     delay_work.delay_work.work);

	if (fep->delay_work.timeout) {
		fep->delay_work.timeout = false;
		fec_restart(fep->netdev, fep->full_duplex);
		netif_wake_queue(fep->netdev);
	}

	if (fep->delay_work.trig_tx) {
		fep->delay_work.trig_tx = false;
		writel(0, fep->hwp + FEC_X_DES_ACTIVE);
	}
Linus Torvalds's avatar
Linus Torvalds committed
static void
fec_enet_tx(struct net_device *ndev)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct	fec_enet_private *fep;
	struct bufdesc *bdp;
	unsigned short status;
Linus Torvalds's avatar
Linus Torvalds committed
	struct	sk_buff	*skb;
Linus Torvalds's avatar
Linus Torvalds committed

Linus Torvalds's avatar
Linus Torvalds committed
	bdp = fep->dirty_tx;

	/* get next bdp of dirty_tx */
	if (bdp->cbd_sc & BD_ENET_TX_WRAP)
		bdp = fep->tx_bd_base;
	else
		bdp = fec_enet_get_nextdesc(bdp, fep->bufdesc_ex);

	while (((status = bdp->cbd_sc) & BD_ENET_TX_READY) == 0) {

		/* current queue is empty */
		if (bdp == fep->cur_tx)
Sascha Hauer's avatar
Sascha Hauer committed
			break;

		if (fep->bufdesc_ex)
			index = (struct bufdesc_ex *)bdp -
				(struct bufdesc_ex *)fep->tx_bd_base;
		else
			index = bdp - fep->tx_bd_base;

		dma_unmap_single(&fep->pdev->dev, bdp->cbd_bufaddr,
				FEC_ENET_TX_FRSIZE, DMA_TO_DEVICE);
Sascha Hauer's avatar
Sascha Hauer committed
		bdp->cbd_bufaddr = 0;
Linus Torvalds's avatar
Linus Torvalds committed

		skb = fep->tx_skbuff[index];

Linus Torvalds's avatar
Linus Torvalds committed
		/* Check for errors. */
		if (status & (BD_ENET_TX_HB | BD_ENET_TX_LC |
Linus Torvalds's avatar
Linus Torvalds committed
				   BD_ENET_TX_RL | BD_ENET_TX_UN |
				   BD_ENET_TX_CSL)) {
			if (status & BD_ENET_TX_HB)  /* No heartbeat */
				ndev->stats.tx_heartbeat_errors++;
			if (status & BD_ENET_TX_LC)  /* Late collision */
				ndev->stats.tx_window_errors++;
			if (status & BD_ENET_TX_RL)  /* Retrans limit */
				ndev->stats.tx_aborted_errors++;
			if (status & BD_ENET_TX_UN)  /* Underrun */
				ndev->stats.tx_fifo_errors++;
			if (status & BD_ENET_TX_CSL) /* Carrier lost */
				ndev->stats.tx_carrier_errors++;
Linus Torvalds's avatar
Linus Torvalds committed
		} else {
			ndev->stats.tx_bytes += bdp->cbd_datlen;
		if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS) &&
			fep->bufdesc_ex) {
			struct skb_shared_hwtstamps shhwtstamps;
			unsigned long flags;
			struct bufdesc_ex *ebdp = (struct bufdesc_ex *)bdp;

			memset(&shhwtstamps, 0, sizeof(shhwtstamps));
			spin_lock_irqsave(&fep->tmreg_lock, flags);
			shhwtstamps.hwtstamp = ns_to_ktime(
				timecounter_cyc2time(&fep->tc, ebdp->ts));
			spin_unlock_irqrestore(&fep->tmreg_lock, flags);
			skb_tstamp_tx(skb, &shhwtstamps);
		}
		if (status & BD_ENET_TX_READY)
			netdev_err(ndev, "HEY! Enet xmit interrupt and TX_READY\n");
Linus Torvalds's avatar
Linus Torvalds committed
		/* Deferred means some collisions occurred during transmit,
		 * but we eventually sent the packet OK.
		 */
		if (status & BD_ENET_TX_DEF)
Sascha Hauer's avatar
Sascha Hauer committed
		/* Free the sk buffer associated with this last transmit */
Linus Torvalds's avatar
Linus Torvalds committed
		dev_kfree_skb_any(skb);
		fep->tx_skbuff[index] = NULL;

		fep->dirty_tx = bdp;
Sascha Hauer's avatar
Sascha Hauer committed
		/* Update pointer to next buffer descriptor to be transmitted */
		if (status & BD_ENET_TX_WRAP)
Linus Torvalds's avatar
Linus Torvalds committed
			bdp = fep->tx_bd_base;
		else
			bdp = fec_enet_get_nextdesc(bdp, fep->bufdesc_ex);
Sascha Hauer's avatar
Sascha Hauer committed
		/* Since we have freed up a buffer, the ring is no longer full
Linus Torvalds's avatar
Linus Torvalds committed
		 */
		if (fep->dirty_tx != fep->cur_tx) {
			if (netif_queue_stopped(ndev))
				netif_wake_queue(ndev);
Linus Torvalds's avatar
Linus Torvalds committed
		}
	}
Linus Torvalds's avatar
Linus Torvalds committed
}


/* During a receive, the cur_rx points to the current incoming buffer.
 * When we update through the ring, if the next incoming buffer has
 * not been given to the system, we just set the empty indicator,
 * effectively tossing the packet.
 */
static int
fec_enet_rx(struct net_device *ndev, int budget)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	const struct platform_device_id *id_entry =
				platform_get_device_id(fep->pdev);
	struct bufdesc *bdp;
	unsigned short status;
Linus Torvalds's avatar
Linus Torvalds committed
	struct	sk_buff	*skb;
	ushort	pkt_len;
	__u8 *data;
	int	pkt_received = 0;
	struct	bufdesc_ex *ebdp = NULL;
	bool	vlan_packet_rcvd = false;
	u16	vlan_tag;
#ifdef CONFIG_M532x
	flush_cache_all();
Linus Torvalds's avatar
Linus Torvalds committed

	/* First, grab all of the stats for the incoming packet.
	 * These get messed up if we get called due to a busy condition.
	 */
	bdp = fep->cur_rx;

Sascha Hauer's avatar
Sascha Hauer committed
	while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) {
Linus Torvalds's avatar
Linus Torvalds committed

		if (pkt_received >= budget)
			break;
		pkt_received++;

Sascha Hauer's avatar
Sascha Hauer committed
		/* Since we have allocated space to hold a complete frame,
		 * the last indicator should be set.
		 */
		if ((status & BD_ENET_RX_LAST) == 0)
			netdev_err(ndev, "rcv is not +last\n");
Linus Torvalds's avatar
Linus Torvalds committed

Sascha Hauer's avatar
Sascha Hauer committed
		if (!fep->opened)
			goto rx_processing_done;
Linus Torvalds's avatar
Linus Torvalds committed

Sascha Hauer's avatar
Sascha Hauer committed
		/* Check for errors. */
		if (status & (BD_ENET_RX_LG | BD_ENET_RX_SH | BD_ENET_RX_NO |
Linus Torvalds's avatar
Linus Torvalds committed
			   BD_ENET_RX_CR | BD_ENET_RX_OV)) {
Sascha Hauer's avatar
Sascha Hauer committed
			if (status & (BD_ENET_RX_LG | BD_ENET_RX_SH)) {
				/* Frame too long or too short. */
				ndev->stats.rx_length_errors++;
Sascha Hauer's avatar
Sascha Hauer committed
			}
			if (status & BD_ENET_RX_NO)	/* Frame alignment */
				ndev->stats.rx_frame_errors++;
Sascha Hauer's avatar
Sascha Hauer committed
			if (status & BD_ENET_RX_CR)	/* CRC Error */
Sascha Hauer's avatar
Sascha Hauer committed
			if (status & BD_ENET_RX_OV)	/* FIFO overrun */
				ndev->stats.rx_fifo_errors++;
Sascha Hauer's avatar
Sascha Hauer committed
		/* Report late collisions as a frame error.
		 * On this error, the BD is closed, but we don't know what we
		 * have in the buffer.  So, just drop this frame on the floor.
		 */
		if (status & BD_ENET_RX_CL) {
			ndev->stats.rx_errors++;
			ndev->stats.rx_frame_errors++;
Sascha Hauer's avatar
Sascha Hauer committed
			goto rx_processing_done;
		}
Linus Torvalds's avatar
Linus Torvalds committed

Sascha Hauer's avatar
Sascha Hauer committed
		/* Process the incoming frame. */
Sascha Hauer's avatar
Sascha Hauer committed
		pkt_len = bdp->cbd_datlen;
		ndev->stats.rx_bytes += pkt_len;
Sascha Hauer's avatar
Sascha Hauer committed
		data = (__u8*)__va(bdp->cbd_bufaddr);
Linus Torvalds's avatar
Linus Torvalds committed

		dma_unmap_single(&fep->pdev->dev, bdp->cbd_bufaddr,
				FEC_ENET_TX_FRSIZE, DMA_FROM_DEVICE);
		if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
			swap_buffer(data, pkt_len);

		/* Extract the enhanced buffer descriptor */
		ebdp = NULL;
		if (fep->bufdesc_ex)
			ebdp = (struct bufdesc_ex *)bdp;

		/* If this is a VLAN packet remove the VLAN Tag */
		vlan_packet_rcvd = false;
		if ((ndev->features & NETIF_F_HW_VLAN_CTAG_RX) &&
		    fep->bufdesc_ex && (ebdp->cbd_esc & BD_ENET_RX_VLAN)) {
			/* Push and remove the vlan tag */
			struct vlan_hdr *vlan_header =
					(struct vlan_hdr *) (data + ETH_HLEN);
			vlan_tag = ntohs(vlan_header->h_vlan_TCI);
			pkt_len -= VLAN_HLEN;

			vlan_packet_rcvd = true;
		}

Sascha Hauer's avatar
Sascha Hauer committed
		/* This does 16 byte alignment, exactly what we need.
		 * The packet length includes FCS, but we don't want to
		 * include that when passing upstream as it messes up
		 * bridging applications.
		 */
		skb = netdev_alloc_skb(ndev, pkt_len - 4 + NET_IP_ALIGN);
Linus Torvalds's avatar
Linus Torvalds committed

Sascha Hauer's avatar
Sascha Hauer committed
		if (unlikely(!skb)) {
Sascha Hauer's avatar
Sascha Hauer committed
		} else {
			int payload_offset = (2 * ETH_ALEN);
Sascha Hauer's avatar
Sascha Hauer committed
			skb_reserve(skb, NET_IP_ALIGN);
Sascha Hauer's avatar
Sascha Hauer committed
			skb_put(skb, pkt_len - 4);	/* Make room */

			/* Extract the frame data without the VLAN header. */
			skb_copy_to_linear_data(skb, data, (2 * ETH_ALEN));
			if (vlan_packet_rcvd)
				payload_offset = (2 * ETH_ALEN) + VLAN_HLEN;
			skb_copy_to_linear_data_offset(skb, (2 * ETH_ALEN),
						       data + payload_offset,
						       pkt_len - 4 - (2 * ETH_ALEN));

			skb->protocol = eth_type_trans(skb, ndev);
			/* Get receive timestamp from the skb */
			if (fep->hwts_rx_en && fep->bufdesc_ex) {
				struct skb_shared_hwtstamps *shhwtstamps =
							    skb_hwtstamps(skb);
				unsigned long flags;

				memset(shhwtstamps, 0, sizeof(*shhwtstamps));

				spin_lock_irqsave(&fep->tmreg_lock, flags);
				shhwtstamps->hwtstamp = ns_to_ktime(
				    timecounter_cyc2time(&fep->tc, ebdp->ts));
				spin_unlock_irqrestore(&fep->tmreg_lock, flags);
			}
			if (fep->bufdesc_ex &&
			    (fep->csum_flags & FLAG_RX_CSUM_ENABLED)) {
				if (!(ebdp->cbd_esc & FLAG_RX_CSUM_ERROR)) {
					/* don't check it */
					skb->ip_summed = CHECKSUM_UNNECESSARY;
				} else {
					skb_checksum_none_assert(skb);
				}
			}

			/* Handle received VLAN packets */
			if (vlan_packet_rcvd)
				__vlan_hwaccel_put_tag(skb,
						       htons(ETH_P_8021Q),
						       vlan_tag);

			napi_gro_receive(&fep->napi, skb);
Sascha Hauer's avatar
Sascha Hauer committed

		bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, data,
				FEC_ENET_TX_FRSIZE, DMA_FROM_DEVICE);
Sascha Hauer's avatar
Sascha Hauer committed
rx_processing_done:
		/* Clear the status flags for this buffer */
		status &= ~BD_ENET_RX_STATS;
Linus Torvalds's avatar
Linus Torvalds committed

Sascha Hauer's avatar
Sascha Hauer committed
		/* Mark the buffer empty */
		status |= BD_ENET_RX_EMPTY;
		bdp->cbd_sc = status;
		if (fep->bufdesc_ex) {
			struct bufdesc_ex *ebdp = (struct bufdesc_ex *)bdp;

			ebdp->cbd_esc = BD_ENET_RX_INT;
			ebdp->cbd_prot = 0;
			ebdp->cbd_bdu = 0;
		}
Sascha Hauer's avatar
Sascha Hauer committed
		/* Update BD pointer to next entry */
		if (status & BD_ENET_RX_WRAP)
			bdp = fep->rx_bd_base;
		else
			bdp = fec_enet_get_nextdesc(bdp, fep->bufdesc_ex);
Sascha Hauer's avatar
Sascha Hauer committed
		/* Doing this here will keep the FEC running while we process