Skip to content
Snippets Groups Projects
fec_main.c 49.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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/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/pinctrl/consumer.h>
    
    #include <linux/regulator/consumer.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #include <asm/cacheflush.h>
    
    #ifndef CONFIG_ARM
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <asm/coldfire.h>
    #include <asm/mcfsim.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include "fec.h"
    
    
    #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)
    
    
    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,
    
    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], },
    
    	{ /* 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))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* The FEC stores dest/src/type, data, and checksum for receive packets.
     */
    #define PKT_MAXBUF_SIZE		1518
    #define PKT_MINBUF_SIZE		64
    #define PKT_MAXBLR_SIZE		1520
    
    /*
    
     * 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 < (len + 3) / 4; i++, buf++)
    		*buf = cpu_to_be32(*buf);
    
    	return bufaddr;
    }
    
    
    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;
    
    	unsigned short	status;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (!fep->link) {
    		/* Link is down or autonegotiation is in progress. */
    
    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
    		 */
    
    		printk("%s: tx queue full!.\n", ndev->name);
    
    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;
    
    			ebdp->cbd_esc = BD_ENET_TX_INT;
    		}
    
    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
    
    
    	/* 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
    
    
    	/* Reset all multicast.	*/
    	writel(0, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
    	writel(0, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
    #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);
    
    	/*
    	 * 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);
    
    
    	/* 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 thresh hold 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;
    	}
    
    
    	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);
    
    	/* 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);
    }
    
    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))
    			printk("fec_stop : 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++;
    
    	fec_restart(ndev, fep->full_duplex);
    	netif_wake_queue(ndev);
    }
    
    
    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 {
    
    		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)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			printk("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;
    
    #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)
    			printk("FEC ENET: 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);
    
    
    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 {
    
    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 */
    			skb_copy_to_linear_data(skb, data, pkt_len - 4);
    
    			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;
    
    				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);
    			}
    
    			if (!skb_defer_rx_timestamp(skb))
    
    				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
    		 * incoming frames.  On a heavily loaded network, we should be
    		 * able to keep up at the expense of system resources.
    		 */
    		writel(0, fep->hwp + FEC_R_DES_ACTIVE);
    	}
    
    	fep->cur_rx = bdp;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	return pkt_received;
    
    static irqreturn_t
    fec_enet_interrupt(int irq, void *dev_id)
    {
    	struct net_device *ndev = dev_id;
    	struct fec_enet_private *fep = netdev_priv(ndev);
    	uint int_events;
    	irqreturn_t ret = IRQ_NONE;
    
    	do {
    		int_events = readl(fep->hwp + FEC_IEVENT);
    		writel(int_events, fep->hwp + FEC_IEVENT);
    
    
    		if (int_events & (FEC_ENET_RXF | FEC_ENET_TXF)) {
    
    
    			/* Disable the RX interrupt */
    			if (napi_schedule_prep(&fep->napi)) {
    				writel(FEC_RX_DISABLED_IMASK,
    					fep->hwp + FEC_IMASK);
    				__napi_schedule(&fep->napi);
    			}
    
    		}
    
    		if (int_events & FEC_ENET_MII) {
    			ret = IRQ_HANDLED;
    			complete(&fep->mdio_done);
    		}
    	} while (int_events);
    
    	return ret;
    }
    
    
    static int fec_enet_rx_napi(struct napi_struct *napi, int budget)
    {
    	struct net_device *ndev = napi->dev;
    	int pkts = fec_enet_rx(ndev, budget);
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	if (pkts < budget) {
    		napi_complete(napi);
    		writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
    	}
    	return pkts;
    }
    
    /* ------------------------------------------------------------------------- */
    
    static void fec_get_mac(struct net_device *ndev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	struct fec_platform_data *pdata = fep->pdev->dev.platform_data;
    
    	unsigned char *iap, tmpaddr[ETH_ALEN];
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/*
    	 * try to get mac address in following order:
    	 *
    	 * 1) module parameter via kernel command line in form
    	 *    fec.macaddr=0x00,0x04,0x9f,0x01,0x30,0xe0
    	 */
    	iap = macaddr;
    
    
    	/*
    	 * 2) from device tree data
    	 */
    	if (!is_valid_ether_addr(iap)) {
    		struct device_node *np = fep->pdev->dev.of_node;
    		if (np) {
    			const char *mac = of_get_mac_address(np);
    			if (mac)
    				iap = (unsigned char *) mac;
    		}
    	}
    
    
    	 * 3) from flash or fuse (via platform data)
    
    	 */
    	if (!is_valid_ether_addr(iap)) {
    #ifdef CONFIG_M5272
    		if (FEC_FLASHMAC)
    			iap = (unsigned char *)FEC_FLASHMAC;
    #else
    		if (pdata)
    
    			iap = (unsigned char *)&pdata->mac;
    
    	 * 4) FEC mac registers set by bootloader
    
    	 */
    	if (!is_valid_ether_addr(iap)) {
    		*((unsigned long *) &tmpaddr[0]) =
    			be32_to_cpu(readl(fep->hwp + FEC_ADDR_LOW));
    		*((unsigned short *) &tmpaddr[4]) =
    			be16_to_cpu(readl(fep->hwp + FEC_ADDR_HIGH) >> 16);
    
    	memcpy(ndev->dev_addr, iap, ETH_ALEN);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Adjust MAC if using macaddr */
    	if (iap == macaddr)
    
    		 ndev->dev_addr[ETH_ALEN-1] = macaddr[ETH_ALEN-1] + fep->dev_id;
    
    /* ------------------------------------------------------------------------- */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void fec_enet_adjust_link(struct net_device *ndev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	struct phy_device *phy_dev = fep->phy_dev;
    	unsigned long flags;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	spin_lock_irqsave(&fep->hw_lock, flags);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Prevent a state halted on mii error */
    	if (fep->mii_timeout && phy_dev->state == PHY_HALTED) {
    		phy_dev->state = PHY_RESUMING;
    		goto spin_unlock;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		if (!fep->link) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		if (fep->full_duplex != phy_dev->duplex)
    			status_change = 1;
    
    		if (phy_dev->speed != fep->speed) {
    			fep->speed = phy_dev->speed;
    			status_change = 1;
    		}
    
    		/* if any of the above changed restart the FEC */
    		if (status_change)
    
    			fec_restart(ndev, phy_dev->duplex);
    
    	} else {
    		if (fep->link) {
    
    			status_change = 1;