Skip to content
Snippets Groups Projects
fec_main.c 69.4 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/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 <net/tso.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>
#include <linux/pinctrl/consumer.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"

/* 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
 * 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
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

#define TSO_HEADER_SIZE		128
/* Max number of allowed TCP segments for software TSO */
#define FEC_MAX_TSO_SEGS	100
#define FEC_MAX_SKB_DESCS	(FEC_MAX_TSO_SEGS * 2 + MAX_SKB_FRAGS)

#define IS_TSO_HEADER(txq, addr) \
	((addr >= txq->tso_hdrs_dma) && \
	(addr < txq->tso_hdrs_dma + txq->tx_ring_size * TSO_HEADER_SIZE))

static int mii_cnt;

static inline
struct bufdesc *fec_enet_get_nextdesc(struct bufdesc *bdp, struct fec_enet_private *fep)
	struct bufdesc *new_bd = bdp + 1;
	struct bufdesc_ex *ex_new_bd = (struct bufdesc_ex *)bdp + 1;
	struct bufdesc_ex *ex_base;
	struct bufdesc *base;
	int ring_size;

	if (bdp >= fep->tx_bd_base) {
		base = fep->tx_bd_base;
		ring_size = fep->tx_ring_size;
		ex_base = (struct bufdesc_ex *)fep->tx_bd_base;
	} else {
		base = fep->rx_bd_base;
		ring_size = fep->rx_ring_size;
		ex_base = (struct bufdesc_ex *)fep->rx_bd_base;
	}

	if (fep->bufdesc_ex)
		return (struct bufdesc *)((ex_new_bd >= (ex_base + ring_size)) ?
			ex_base : ex_new_bd);
		return (new_bd >= (base + ring_size)) ?
			base : new_bd;
static inline
struct bufdesc *fec_enet_get_prevdesc(struct bufdesc *bdp, struct fec_enet_private *fep)
	struct bufdesc *new_bd = bdp - 1;
	struct bufdesc_ex *ex_new_bd = (struct bufdesc_ex *)bdp - 1;
	struct bufdesc_ex *ex_base;
	struct bufdesc *base;
	int ring_size;

	if (bdp >= fep->tx_bd_base) {
		base = fep->tx_bd_base;
		ring_size = fep->tx_ring_size;
		ex_base = (struct bufdesc_ex *)fep->tx_bd_base;
	} else {
		base = fep->rx_bd_base;
		ring_size = fep->rx_ring_size;
		ex_base = (struct bufdesc_ex *)fep->rx_bd_base;
	}

	if (fep->bufdesc_ex)
		return (struct bufdesc *)((ex_new_bd < ex_base) ?
			(ex_new_bd + ring_size) : ex_new_bd);
		return (new_bd < base) ? (new_bd + ring_size) : new_bd;
static int fec_enet_get_bd_index(struct bufdesc *base, struct bufdesc *bdp,
				struct fec_enet_private *fep)
{
	return ((const char *)bdp - (const char *)base) / fep->bufdesc_size;
}

static int fec_enet_get_free_txdesc_num(struct fec_enet_private *fep)
{
	int entries;

	entries = ((const char *)fep->dirty_tx -
			(const char *)fep->cur_tx) / fep->bufdesc_size - 1;

	return entries > 0 ? entries : entries + fep->tx_ring_size;
}

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;

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

	return 0;
}

static void
fec_enet_submit_work(struct bufdesc *bdp, struct fec_enet_private *fep)
{
	const struct platform_device_id *id_entry =
				platform_get_device_id(fep->pdev);
	struct bufdesc *bdp_pre;

	bdp_pre = fec_enet_get_prevdesc(bdp, fep);
	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));
	}
}

static int
fec_enet_txq_submit_frag_skb(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 = fep->cur_tx;
	struct bufdesc_ex *ebdp;
	int nr_frags = skb_shinfo(skb)->nr_frags;
	int frag, frag_len;
	unsigned short status;
	unsigned int estatus = 0;
	skb_frag_t *this_frag;
	void *bufaddr;
	int i;
Linus Torvalds's avatar
Linus Torvalds committed

	for (frag = 0; frag < nr_frags; frag++) {
		this_frag = &skb_shinfo(skb)->frags[frag];
		bdp = fec_enet_get_nextdesc(bdp, fep);
		ebdp = (struct bufdesc_ex *)bdp;

		status = bdp->cbd_sc;
		status &= ~BD_ENET_TX_STATS;
		status |= (BD_ENET_TX_TC | BD_ENET_TX_READY);
		frag_len = skb_shinfo(skb)->frags[frag].size;

		/* Handle the last BD specially */
		if (frag == nr_frags - 1) {
			status |= (BD_ENET_TX_INTR | BD_ENET_TX_LAST);
			if (fep->bufdesc_ex) {
				estatus |= BD_ENET_TX_INT;
				if (unlikely(skb_shinfo(skb)->tx_flags &
					SKBTX_HW_TSTAMP && fep->hwts_tx_en))
					estatus |= BD_ENET_TX_TS;
			}
		}

		if (fep->bufdesc_ex) {
			if (skb->ip_summed == CHECKSUM_PARTIAL)
				estatus |= BD_ENET_TX_PINS | BD_ENET_TX_IINS;
			ebdp->cbd_bdu = 0;
			ebdp->cbd_esc = estatus;
		}

		bufaddr = page_address(this_frag->page.p) + this_frag->page_offset;

		index = fec_enet_get_bd_index(fep->tx_bd_base, bdp, fep);
		if (((unsigned long) bufaddr) & FEC_ALIGNMENT ||
			id_entry->driver_data & FEC_QUIRK_SWAP_FRAME) {
			memcpy(fep->tx_bounce[index], bufaddr, frag_len);
			bufaddr = fep->tx_bounce[index];

			if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
				swap_buffer(bufaddr, frag_len);
		}

		bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, bufaddr,
						frag_len, DMA_TO_DEVICE);
		if (dma_mapping_error(&fep->pdev->dev, bdp->cbd_bufaddr)) {
			dev_kfree_skb_any(skb);
			if (net_ratelimit())
				netdev_err(ndev, "Tx DMA memory map failed\n");
			goto dma_mapping_error;
		}

		bdp->cbd_datlen = frag_len;
		bdp->cbd_sc = status;
	}

	fep->cur_tx = bdp;

	return 0;

dma_mapping_error:
Linus Torvalds's avatar
Linus Torvalds committed
	bdp = fep->cur_tx;
	for (i = 0; i < frag; i++) {
		bdp = fec_enet_get_nextdesc(bdp, fep);
		dma_unmap_single(&fep->pdev->dev, bdp->cbd_bufaddr,
				bdp->cbd_datlen, DMA_TO_DEVICE);
	}
	return NETDEV_TX_OK;
}
Linus Torvalds's avatar
Linus Torvalds committed

static int fec_enet_txq_submit_skb(struct sk_buff *skb, 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);
	int nr_frags = skb_shinfo(skb)->nr_frags;
	struct bufdesc *bdp, *last_bdp;
	void *bufaddr;
	unsigned short status;
	unsigned short buflen;
	unsigned int estatus = 0;
	unsigned int index;
	int entries_free;
	entries_free = fec_enet_get_free_txdesc_num(fep);
	if (entries_free < MAX_SKB_FRAGS + 1) {
		dev_kfree_skb_any(skb);
		if (net_ratelimit())
			netdev_err(ndev, "NOT enough BD for SG!\n");
		return NETDEV_TX_OK;
	}

	/* Protocol checksum off-load for TCP and UDP. */
	if (fec_enet_clear_csum(skb, ndev)) {
		dev_kfree_skb_any(skb);
	/* Fill in a Tx ring entry */
	bdp = fep->cur_tx;
	status = bdp->cbd_sc;
	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;
	buflen = skb_headlen(skb);
Linus Torvalds's avatar
Linus Torvalds committed

	index = fec_enet_get_bd_index(fep->tx_bd_base, bdp, fep);
	if (((unsigned long) bufaddr) & FEC_ALIGNMENT ||
		id_entry->driver_data & FEC_QUIRK_SWAP_FRAME) {
		memcpy(fep->tx_bounce[index], skb->data, buflen);
		bufaddr = fep->tx_bounce[index];
Linus Torvalds's avatar
Linus Torvalds committed

		if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
			swap_buffer(bufaddr, buflen);
	}
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,
					buflen, DMA_TO_DEVICE);
	if (dma_mapping_error(&fep->pdev->dev, bdp->cbd_bufaddr)) {
		dev_kfree_skb_any(skb);
		if (net_ratelimit())
			netdev_err(ndev, "Tx DMA memory map failed\n");
		return NETDEV_TX_OK;
	}
Linus Torvalds's avatar
Linus Torvalds committed

	if (nr_frags) {
		ret = fec_enet_txq_submit_frag_skb(skb, ndev);
		if (ret)
			return ret;
	} else {
		status |= (BD_ENET_TX_INTR | BD_ENET_TX_LAST);
		if (fep->bufdesc_ex) {
			estatus = BD_ENET_TX_INT;
			if (unlikely(skb_shinfo(skb)->tx_flags &
				SKBTX_HW_TSTAMP && fep->hwts_tx_en))
				estatus |= BD_ENET_TX_TS;
		}
	}

	if (fep->bufdesc_ex) {

		struct bufdesc_ex *ebdp = (struct bufdesc_ex *)bdp;
		if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
			fep->hwts_tx_en))
			skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
		if (skb->ip_summed == CHECKSUM_PARTIAL)
			estatus |= BD_ENET_TX_PINS | BD_ENET_TX_IINS;

		ebdp->cbd_bdu = 0;
		ebdp->cbd_esc = estatus;
	last_bdp = fep->cur_tx;
	index = fec_enet_get_bd_index(fep->tx_bd_base, last_bdp, fep);
	/* Save skb pointer */
	fep->tx_skbuff[index] = skb;

	bdp->cbd_datlen = buflen;

	/* 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.
	 */
	status |= (BD_ENET_TX_READY | BD_ENET_TX_TC);
	fec_enet_submit_work(bdp, fep);
Sascha Hauer's avatar
Sascha Hauer committed
	/* If this was the last BD in the ring, start at the beginning again. */
	bdp = fec_enet_get_nextdesc(last_bdp, fep);
Linus Torvalds's avatar
Linus Torvalds committed

	skb_tx_timestamp(skb);

	fep->cur_tx = bdp;

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

	return 0;
static int
fec_enet_txq_put_data_tso(struct sk_buff *skb, struct net_device *ndev,
			struct bufdesc *bdp, int index, char *data,
			int size, bool last_tcp, bool is_last)
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	const struct platform_device_id *id_entry =
				platform_get_device_id(fep->pdev);
	struct bufdesc_ex *ebdp = (struct bufdesc_ex *)bdp;
	unsigned short status;
	unsigned int estatus = 0;

	status = bdp->cbd_sc;
	status &= ~BD_ENET_TX_STATS;
	status |= (BD_ENET_TX_TC | BD_ENET_TX_READY);
	bdp->cbd_datlen = size;

	if (((unsigned long) data) & FEC_ALIGNMENT ||
		id_entry->driver_data & FEC_QUIRK_SWAP_FRAME) {
		memcpy(fep->tx_bounce[index], data, size);
		data = fep->tx_bounce[index];

		if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
			swap_buffer(data, size);
	}

	bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, data,
					size, DMA_TO_DEVICE);
	if (dma_mapping_error(&fep->pdev->dev, bdp->cbd_bufaddr)) {
		dev_kfree_skb_any(skb);
		if (net_ratelimit())
			netdev_err(ndev, "Tx DMA memory map failed\n");
	if (fep->bufdesc_ex) {
		if (skb->ip_summed == CHECKSUM_PARTIAL)
			estatus |= BD_ENET_TX_PINS | BD_ENET_TX_IINS;
		ebdp->cbd_bdu = 0;
		ebdp->cbd_esc = estatus;
	}

	/* Handle the last BD specially */
	if (last_tcp)
		status |= (BD_ENET_TX_LAST | BD_ENET_TX_TC);
	if (is_last) {
		status |= BD_ENET_TX_INTR;
		if (fep->bufdesc_ex)
			ebdp->cbd_esc |= BD_ENET_TX_INT;
	}

	bdp->cbd_sc = status;

	return 0;
}

static int
fec_enet_txq_put_hdr_tso(struct sk_buff *skb, struct net_device *ndev,
			struct bufdesc *bdp, int index)
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	const struct platform_device_id *id_entry =
				platform_get_device_id(fep->pdev);
	int hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb);
	struct bufdesc_ex *ebdp = (struct bufdesc_ex *)bdp;
	void *bufaddr;
	unsigned long dmabuf;
	unsigned short status;
	unsigned int estatus = 0;

	status = bdp->cbd_sc;
	status &= ~BD_ENET_TX_STATS;
	status |= (BD_ENET_TX_TC | BD_ENET_TX_READY);

	bufaddr = fep->tso_hdrs + index * TSO_HEADER_SIZE;
	dmabuf = fep->tso_hdrs_dma + index * TSO_HEADER_SIZE;
	if (((unsigned long) bufaddr) & FEC_ALIGNMENT ||
		id_entry->driver_data & FEC_QUIRK_SWAP_FRAME) {
		memcpy(fep->tx_bounce[index], skb->data, hdr_len);
		bufaddr = fep->tx_bounce[index];

		if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
			swap_buffer(bufaddr, hdr_len);

		dmabuf = dma_map_single(&fep->pdev->dev, bufaddr,
					hdr_len, DMA_TO_DEVICE);
		if (dma_mapping_error(&fep->pdev->dev, dmabuf)) {
			dev_kfree_skb_any(skb);
			if (net_ratelimit())
				netdev_err(ndev, "Tx DMA memory map failed\n");
			return NETDEV_TX_BUSY;
		}
	}

	bdp->cbd_bufaddr = dmabuf;
	bdp->cbd_datlen = hdr_len;

	if (fep->bufdesc_ex) {
		if (skb->ip_summed == CHECKSUM_PARTIAL)
			estatus |= BD_ENET_TX_PINS | BD_ENET_TX_IINS;
		ebdp->cbd_bdu = 0;
		ebdp->cbd_esc = estatus;
	}

	bdp->cbd_sc = status;

	return 0;
}

static int fec_enet_txq_submit_tso(struct sk_buff *skb, struct net_device *ndev)
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	int hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb);
	int total_len, data_left;
	struct bufdesc *bdp = fep->cur_tx;
	struct tso_t tso;
	unsigned int index = 0;
	int ret;

	if (tso_count_descs(skb) >= fec_enet_get_free_txdesc_num(fep)) {
		dev_kfree_skb_any(skb);
		if (net_ratelimit())
			netdev_err(ndev, "NOT enough BD for TSO!\n");
		return NETDEV_TX_OK;
	}

	/* Protocol checksum off-load for TCP and UDP. */
	if (fec_enet_clear_csum(skb, ndev)) {
		dev_kfree_skb_any(skb);
		return NETDEV_TX_OK;
	}

	/* Initialize the TSO handler, and prepare the first payload */
	tso_start(skb, &tso);

	total_len = skb->len - hdr_len;
	while (total_len > 0) {
		char *hdr;

		index = fec_enet_get_bd_index(fep->tx_bd_base, bdp, fep);
		data_left = min_t(int, skb_shinfo(skb)->gso_size, total_len);
		total_len -= data_left;

		/* prepare packet headers: MAC + IP + TCP */
		hdr = fep->tso_hdrs + index * TSO_HEADER_SIZE;
		tso_build_hdr(skb, hdr, &tso, data_left, total_len == 0);
		ret = fec_enet_txq_put_hdr_tso(skb, ndev, bdp, index);
		if (ret)
			goto err_release;

		while (data_left > 0) {
			int size;

			size = min_t(int, tso.size, data_left);
			bdp = fec_enet_get_nextdesc(bdp, fep);
			index = fec_enet_get_bd_index(fep->tx_bd_base, bdp, fep);
			ret = fec_enet_txq_put_data_tso(skb, ndev, bdp, index, tso.data,
							size, size == data_left,
							total_len == 0);
			if (ret)
				goto err_release;

			data_left -= size;
			tso_build_data(skb, &tso, size);
		}

		bdp = fec_enet_get_nextdesc(bdp, fep);
	}

	/* Save skb pointer */
	fep->tx_skbuff[index] = skb;

	fec_enet_submit_work(bdp, fep);

	skb_tx_timestamp(skb);
	fep->cur_tx = bdp;

	/* Trigger transmission start */
	writel(0, fep->hwp + FEC_X_DES_ACTIVE);

	return 0;

err_release:
	/* TODO: Release all used data descriptors for TSO */
	return ret;
}

static netdev_tx_t
fec_enet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	int entries_free;
	int ret;

	if (skb_is_gso(skb))
		ret = fec_enet_txq_submit_tso(skb, ndev);
	else
		ret = fec_enet_txq_submit_skb(skb, ndev);
	if (ret)
		return ret;
	entries_free = fec_enet_get_free_txdesc_num(fep);
	if (entries_free <= fep->tx_stop_threshold)
		netif_stop_queue(ndev);

	return NETDEV_TX_OK;
}

/* 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 < fep->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);
	}

	/* Set the last buffer to wrap */
	bdp = fec_enet_get_prevdesc(bdp, fep);
	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 < fep->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);
	}

	/* Set the last buffer to wrap */
	bdp = fec_enet_get_prevdesc(bdp, fep);
	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

	/* 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)
			* fep->rx_ring_size, fep->hwp + FEC_X_DES_START);
	else
		writel((unsigned long)fep->bd_dma + sizeof(struct bufdesc)
			* fep->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);
	/* 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

	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);