Skip to content
Snippets Groups Projects
fec.c 38.6 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 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/pci.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>
    
    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"
    
    /* Controller is ENET-MAC */
    #define FEC_QUIRK_ENET_MAC		(1 << 0)
    /* Controller needs driver to swap frame */
    #define FEC_QUIRK_SWAP_FRAME		(1 << 1)
    
    static struct platform_device_id fec_devtype[] = {
    	{
    		.name = DRIVER_NAME,
    		.driver_data = 0,
    	}, {
    		.name = "imx28-fec",
    		.driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_SWAP_FRAME,
    	}
    };
    
    
    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)
    #define FEC_FLASHMAC 	0xffc0406b
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #else
    #define	FEC_FLASHMAC	0
    #endif
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* The number of Tx and Rx buffers.  These are allocated from the page
     * pool.  The code may assume these are power of two, so it it best
     * to keep them that size.
     * We don't need to allocate pages for the transmitter.  We just use
     * the skbuffer directly.
     */
    #define FEC_ENET_RX_PAGES	8
    #define FEC_ENET_RX_FRSIZE	2048
    #define FEC_ENET_RX_FRPPG	(PAGE_SIZE / FEC_ENET_RX_FRSIZE)
    #define RX_RING_SIZE		(FEC_ENET_RX_FRPPG * FEC_ENET_RX_PAGES)
    #define FEC_ENET_TX_FRSIZE	2048
    #define FEC_ENET_TX_FRPPG	(PAGE_SIZE / FEC_ENET_TX_FRSIZE)
    #define TX_RING_SIZE		16	/* Must be power of two */
    #define TX_RING_MOD_MASK	15	/*   for this to work */
    
    
    #if (((RX_RING_SIZE + TX_RING_SIZE) * 8) > 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)
    
    
    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
    
    /* The FEC buffer descriptors track the ring buffers.  The rx_bd_base and
     * tx_bd_base always point to the base of the buffer descriptors.  The
     * cur_rx and cur_tx point to the currently available buffer.
     * The dirty_tx tracks the current buffer that is being sent by the
     * controller.  The cur_tx and dirty_tx are equal under both completely
     * empty and completely full conditions.  The empty/ready indicator in
     * the buffer descriptor determines the actual condition.
     */
    struct fec_enet_private {
    	/* Hardware registers of the FEC device */
    
    	void __iomem *hwp;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	struct net_device *netdev;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* The saved address of a sent-in-place packet/buffer, for skfree(). */
    	unsigned char *tx_bounce[TX_RING_SIZE];
    	struct	sk_buff* tx_skbuff[TX_RING_SIZE];
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	struct	sk_buff* rx_skbuff[RX_RING_SIZE];
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	ushort	skb_cur;
    	ushort	skb_dirty;
    
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* CPM dual port RAM relative addresses */
    
    	dma_addr_t	bd_dma;
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* Address of Rx and Tx buffers */
    
    	struct bufdesc	*rx_bd_base;
    	struct bufdesc	*tx_bd_base;
    	/* The next free ring entry */
    
    	struct bufdesc	*cur_rx, *cur_tx;
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* The ring entries to be free()ed */
    
    	struct bufdesc	*dirty_tx;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	uint	tx_full;
    
    	/* hold while accessing the HW like ringbuffer for tx/rx but not MAC */
    	spinlock_t hw_lock;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	struct	platform_device *pdev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Phylib and MDIO interface */
    
    	struct	mii_bus *mii_bus;
    	struct	phy_device *phy_dev;
    	int	mii_timeout;
    	uint	phy_speed;
    
    	phy_interface_t	phy_interface;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int	link;
    	int	full_duplex;
    
    	struct	completion mdio_done;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    
    /* 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		1000 /* 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
    
    
    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;
    
    	unsigned long flags;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (!fep->link) {
    		/* Link is down or autonegotiation is in progress. */
    
    	spin_lock_irqsave(&fep->hw_lock, flags);
    
    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);
    
    		spin_unlock_irqrestore(&fep->hw_lock, flags);
    
    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 (((unsigned long) bufaddr) & FEC_ALIGNMENT) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		unsigned int index;
    		index = bdp - fep->tx_bd_base;
    
    		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 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fep->tx_skbuff[fep->skb_cur] = skb;
    
    
    	ndev->stats.tx_bytes += skb->len;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fep->skb_cur = (fep->skb_cur+1) & TX_RING_MOD_MASK;
    
    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
    
    	/* Trigger transmission start */
    
    	writel(0, fep->hwp + FEC_X_DES_ACTIVE);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    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
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		bdp++;
    
    	if (bdp == fep->dirty_tx) {
    		fep->tx_full = 1;
    
    	fep->cur_tx = bdp;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	spin_unlock_irqrestore(&fep->hw_lock, flags);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    /* 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 val, temp_mac[2];
    
    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
    
    
    	/* Set receive and transmit descriptor base. */
    	writel(fep->bd_dma, fep->hwp + FEC_R_DES_START);
    	writel((unsigned long)fep->bd_dma + sizeof(struct bufdesc) * RX_RING_SIZE,
    			fep->hwp + FEC_X_DES_START);
    
    	fep->dirty_tx = fep->cur_tx = fep->tx_bd_base;
    	fep->cur_rx = fep->rx_bd_base;
    
    	/* Reset SKB transmit buffers. */
    	fep->skb_cur = fep->skb_dirty = 0;
    	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
    		}
    
    	/* Enable MII mode */
    	if (duplex) {
    		/* MII enable / FD enable */
    		writel(OPT_FRAME_SIZE | 0x04, fep->hwp + FEC_R_CNTRL);
    		writel(0x04, fep->hwp + FEC_X_CNTRL);
    	} else {
    		/* MII enable / No Rcv on Xmit */
    		writel(OPT_FRAME_SIZE | 0x06, fep->hwp + FEC_R_CNTRL);
    		writel(0x0, fep->hwp + FEC_X_CNTRL);
    	}
    	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) {
    		val = readl(fep->hwp + FEC_R_CNTRL);
    
    		/* MII or RMII */
    		if (fep->phy_interface == PHY_INTERFACE_MODE_RMII)
    			val |= (1 << 8);
    		else
    			val &= ~(1 << 8);
    
    		/* 10M or 100M */
    		if (fep->phy_dev && fep->phy_dev->speed == SPEED_100)
    			val &= ~(1 << 9);
    		else
    			val |= (1 << 9);
    
    		writel(val, fep->hwp + FEC_R_CNTRL);
    	} else {
    #ifdef FEC_MIIGSK_ENR
    		if (fep->phy_interface == PHY_INTERFACE_MODE_RMII) {
    			/* 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
    			 */
    			writel(1, fep->hwp + FEC_MIIGSK_CFGR);
    
    			/* re-enable the gasket */
    			writel(2, fep->hwp + FEC_MIIGSK_ENR);
    
    	/* And last, enable the transmit and receive processing */
    	writel(2, fep->hwp + FEC_ECNTRL);
    	writel(0, fep->hwp + FEC_R_DES_ACTIVE);
    
    	/* 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);
    
    	/* 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);
    
    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;
    
    
    	spin_lock(&fep->hw_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	bdp = fep->dirty_tx;
    
    
    	while (((status = bdp->cbd_sc) & BD_ENET_TX_READY) == 0) {
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    		if (bdp == fep->cur_tx && fep->tx_full == 0)
    			break;
    
    
    		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[fep->skb_dirty];
    		/* 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 (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[fep->skb_dirty] = NULL;
    		fep->skb_dirty = (fep->skb_dirty + 1) & TX_RING_MOD_MASK;
    
    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++;
    
    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->tx_full) {
    			fep->tx_full = 0;
    
    			if (netif_queue_stopped(ndev))
    				netif_wake_queue(ndev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    	}
    
    	fep->dirty_tx = bdp;
    
    	spin_unlock(&fep->hw_lock);
    
    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 void
    
    fec_enet_rx(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
    	struct	sk_buff	*skb;
    	ushort	pkt_len;
    	__u8 *data;
    
    #ifdef CONFIG_M532x
    	flush_cache_all();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	spin_lock(&fep->hw_lock);
    
    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
    
    
    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.
    		 */
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    		skb = dev_alloc_skb(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
    			printk("%s: Memory squeeze, dropping packet.\n",
    
    					ndev->name);
    			ndev->stats.rx_dropped++;
    
    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);
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    			netif_rx(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;
    
    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++;
    		/* 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
    
    
    	spin_unlock(&fep->hw_lock);
    
    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) {
    			ret = IRQ_HANDLED;
    			fec_enet_rx(ndev);
    		}
    
    		/* Transmit OK, or non-fatal error. Update the buffer
    		 * descriptors. FEC handles all errors, we just discover
    		 * them as part of the transmit process.
    		 */
    		if (int_events & FEC_ENET_TXF) {
    			ret = IRQ_HANDLED;
    			fec_enet_tx(ndev);
    		}
    
    		if (int_events & FEC_ENET_MII) {
    			ret = IRQ_HANDLED;
    			complete(&fep->mdio_done);
    		}
    	} while (int_events);
    
    	return ret;
    }
    
    
    
    
    /* ------------------------------------------------------------------------- */
    
    static void __inline__ 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 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)
    			memcpy(iap, pdata->mac, ETH_ALEN);
    #endif
    	}
    
    	/*
    	 * 3) 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->pdev->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
    
    
    	/* Duplex link change */
    	if (phy_dev->link) {
    		if (fep->full_duplex != phy_dev->duplex) {
    
    			fec_restart(ndev, phy_dev->duplex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Link on or off change */
    	if (phy_dev->link != fep->link) {
    		fep->link = phy_dev->link;
    		if (phy_dev->link)
    
    			fec_restart(ndev, phy_dev->duplex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		else
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    spin_unlock:
    	spin_unlock_irqrestore(&fep->hw_lock, flags);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (status_change)
    		phy_print_status(phy_dev);
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static int fec_enet_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = bus->priv;
    
    	unsigned long time_left;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	init_completion(&fep->mdio_done);
    
    
    	/* start a read op */
    	writel(FEC_MMFR_ST | FEC_MMFR_OP_READ |
    		FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(regnum) |
    		FEC_MMFR_TA, fep->hwp + FEC_MII_DATA);
    
    	/* wait for end of transfer */
    
    	time_left = wait_for_completion_timeout(&fep->mdio_done,
    			usecs_to_jiffies(FEC_MII_TIMEOUT));
    	if (time_left == 0) {
    		fep->mii_timeout = 1;
    		printk(KERN_ERR "FEC: MDIO read timeout\n");
    		return -ETIMEDOUT;
    
    	/* return value */
    	return FEC_MMFR_DATA(readl(fep->hwp + FEC_MII_DATA));
    
    static int fec_enet_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
    			   u16 value)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = bus->priv;
    
    	unsigned long time_left;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	init_completion(&fep->mdio_done);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* start a write op */
    	writel(FEC_MMFR_ST | FEC_MMFR_OP_WRITE |
    
    		FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(regnum) |
    		FEC_MMFR_TA | FEC_MMFR_DATA(value),
    		fep->hwp + FEC_MII_DATA);
    
    	/* wait for end of transfer */
    
    	time_left = wait_for_completion_timeout(&fep->mdio_done,
    			usecs_to_jiffies(FEC_MII_TIMEOUT));
    	if (time_left == 0) {
    		fep->mii_timeout = 1;
    		printk(KERN_ERR "FEC: MDIO write timeout\n");
    		return -ETIMEDOUT;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static int fec_enet_mdio_reset(struct mii_bus *bus)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    static int fec_enet_mii_probe(struct net_device *ndev)
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	struct phy_device *phy_dev = NULL;
    
    	char mdio_bus_id[MII_BUS_ID_SIZE];
    	char phy_name[MII_BUS_ID_SIZE + 3];
    	int phy_id;
    
    	int dev_id = fep->pdev->id;
    
    	fep->phy_dev = NULL;
    
    
    	/* check for attached phy */
    	for (phy_id = 0; (phy_id < PHY_MAX_ADDR); phy_id++) {
    		if ((fep->mii_bus->phy_mask & (1 << phy_id)))
    			continue;
    		if (fep->mii_bus->phy_map[phy_id] == NULL)
    			continue;
    		if (fep->mii_bus->phy_map[phy_id]->phy_id == 0)
    			continue;
    
    		if (dev_id--)
    			continue;
    
    		strncpy(mdio_bus_id, fep->mii_bus->id, MII_BUS_ID_SIZE);
    		break;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (phy_id >= PHY_MAX_ADDR) {
    		printk(KERN_INFO "%s: no PHY, assuming direct connection "
    
    		strncpy(mdio_bus_id, "0", MII_BUS_ID_SIZE);
    		phy_id = 0;
    	}
    
    	snprintf(phy_name, MII_BUS_ID_SIZE, PHY_ID_FMT, mdio_bus_id, phy_id);
    
    	phy_dev = phy_connect(ndev, phy_name, &fec_enet_adjust_link, 0,
    
    		PHY_INTERFACE_MODE_MII);
    	if (IS_ERR(phy_dev)) {
    
    		printk(KERN_ERR "%s: could not attach to PHY\n", ndev->name);
    
    		return PTR_ERR(phy_dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* mask with MAC supported features */
    	phy_dev->supported &= PHY_BASIC_FEATURES;
    	phy_dev->advertising = phy_dev->supported;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	fep->phy_dev = phy_dev;
    	fep->link = 0;
    	fep->full_duplex = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	printk(KERN_INFO "%s: Freescale FEC PHY driver [%s] "
    
    		"(mii_bus:phy_addr=%s, irq=%d)\n", ndev->name,
    
    		fep->phy_dev->drv->name, dev_name(&fep->phy_dev->dev),
    		fep->phy_dev->irq);
    
    
    static int fec_enet_mii_init(struct platform_device *pdev)
    
    	static struct mii_bus *fec0_mii_bus;
    
    	struct net_device *ndev = platform_get_drvdata(pdev);
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	const struct platform_device_id *id_entry =
    				platform_get_device_id(fep->pdev);
    
    	/*
    	 * The dual fec interfaces are not equivalent with enet-mac.
    	 * Here are the differences:
    	 *
    	 *  - fec0 supports MII & RMII modes while fec1 only supports RMII
    	 *  - fec0 acts as the 1588 time master while fec1 is slave
    	 *  - external phys can only be configured by fec0
    	 *
    	 * That is to say fec1 can not work independently. It only works
    	 * when fec0 is working. The reason behind this design is that the
    	 * second interface is added primarily for Switch mode.
    	 *
    	 * Because of the last point above, both phys are attached on fec0
    	 * mdio interface in board design, and need to be configured by
    	 * fec0 mii_bus.
    	 */
    	if ((id_entry->driver_data & FEC_QUIRK_ENET_MAC) && pdev->id) {
    		/* fec1 uses fec0 mii_bus */
    		fep->mii_bus = fec0_mii_bus;
    		return 0;
    	}
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/*
    	 * Set MII speed to 2.5 MHz (= clk_get_rate() / 2 * phy_speed)
    	 */
    	fep->phy_speed = DIV_ROUND_UP(clk_get_rate(fep->clk), 5000000) << 1;
    	writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	fep->mii_bus = mdiobus_alloc();
    	if (fep->mii_bus == NULL) {
    		err = -ENOMEM;
    		goto err_out;
    
    	fep->mii_bus->name = "fec_enet_mii_bus";
    	fep->mii_bus->read = fec_enet_mdio_read;
    	fep->mii_bus->write = fec_enet_mdio_write;
    	fep->mii_bus->reset = fec_enet_mdio_reset;
    
    	snprintf(fep->mii_bus->id, MII_BUS_ID_SIZE, "%x", pdev->id + 1);
    
    	fep->mii_bus->priv = fep;
    	fep->mii_bus->parent = &pdev->dev;
    
    	fep->mii_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL);
    	if (!fep->mii_bus->irq) {
    		err = -ENOMEM;
    		goto err_out_free_mdiobus;
    
    	for (i = 0; i < PHY_MAX_ADDR; i++)
    		fep->mii_bus->irq[i] = PHY_POLL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	platform_set_drvdata(ndev, fep->mii_bus);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (mdiobus_register(fep->mii_bus))
    		goto err_out_free_mdio_irq;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* save fec0 mii_bus */
    	if (id_entry->driver_data & FEC_QUIRK_ENET_MAC)
    		fec0_mii_bus = fep->mii_bus;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    err_out_free_mdio_irq:
    	kfree(fep->mii_bus->irq);
    err_out_free_mdiobus:
    	mdiobus_free(fep->mii_bus);
    err_out:
    	return err;