diff options
| author | Michal Simek <monstr@monstr.eu> | 2012-09-13 20:23:34 +0000 | 
|---|---|---|
| committer | Joe Hershberger <joe.hershberger@ni.com> | 2012-09-26 12:36:32 -0500 | 
| commit | 185f7d9afc9b5c77bbedc386a43019e07e5d0c04 (patch) | |
| tree | cb34221583a08ed3d8c9f6d456248ca195625423 | |
| parent | 1dff9d0f54a0cec2dfda6022862bbdc38ba97d77 (diff) | |
| download | olio-uboot-2014.01-185f7d9afc9b5c77bbedc386a43019e07e5d0c04.tar.xz olio-uboot-2014.01-185f7d9afc9b5c77bbedc386a43019e07e5d0c04.zip | |
net: Add driver for Zynq Gem IP
Device driver for Zynq Gem IP.
Signed-off-by: Michal Simek <monstr@monstr.eu>
CC: Joe Hershberger <joe.hershberger@gmail.com>
CC: Marek Vasut <marex@denx.de>
Acked-by: Marek Vasut <marex@denx.de>
| -rw-r--r-- | drivers/net/Makefile | 1 | ||||
| -rw-r--r-- | drivers/net/zynq_gem.c | 440 | ||||
| -rw-r--r-- | include/netdev.h | 2 | 
3 files changed, 442 insertions, 1 deletions
| diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 011cd5191..e4abac7c8 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -80,6 +80,7 @@ COBJS-$(CONFIG_XILINX_AXIEMAC) += xilinx_axi_emac.o  COBJS-$(CONFIG_XILINX_EMACLITE) += xilinx_emaclite.o  COBJS-$(CONFIG_XILINX_LL_TEMAC) += xilinx_ll_temac.o xilinx_ll_temac_mdio.o \  		xilinx_ll_temac_fifo.o xilinx_ll_temac_sdma.o +COBJS-$(CONFIG_ZYNQ_GEM) += zynq_gem.o  COBJS	:= $(sort $(COBJS-y))  SRCS	:= $(COBJS:.o=.c) diff --git a/drivers/net/zynq_gem.c b/drivers/net/zynq_gem.c new file mode 100644 index 000000000..359606569 --- /dev/null +++ b/drivers/net/zynq_gem.c @@ -0,0 +1,440 @@ +/* + * (C) Copyright 2011 Michal Simek + * + * Michal SIMEK <monstr@monstr.eu> + * + * Based on Xilinx gmac driver: + * (C) Copyright 2011 Xilinx + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <net.h> +#include <config.h> +#include <malloc.h> +#include <asm/io.h> +#include <phy.h> +#include <miiphy.h> +#include <watchdog.h> + +#if !defined(CONFIG_PHYLIB) +# error XILINX_GEM_ETHERNET requires PHYLIB +#endif + +/* Bit/mask specification */ +#define ZYNQ_GEM_PHYMNTNC_OP_MASK	0x40020000 /* operation mask bits */ +#define ZYNQ_GEM_PHYMNTNC_OP_R_MASK	0x20000000 /* read operation */ +#define ZYNQ_GEM_PHYMNTNC_OP_W_MASK	0x10000000 /* write operation */ +#define ZYNQ_GEM_PHYMNTNC_PHYAD_SHIFT_MASK	23 /* Shift bits for PHYAD */ +#define ZYNQ_GEM_PHYMNTNC_PHREG_SHIFT_MASK	18 /* Shift bits for PHREG */ + +#define ZYNQ_GEM_RXBUF_EOF_MASK		0x00008000 /* End of frame. */ +#define ZYNQ_GEM_RXBUF_SOF_MASK		0x00004000 /* Start of frame. */ +#define ZYNQ_GEM_RXBUF_LEN_MASK		0x00003FFF /* Mask for length field */ + +#define ZYNQ_GEM_RXBUF_WRAP_MASK	0x00000002 /* Wrap bit, last BD */ +#define ZYNQ_GEM_RXBUF_NEW_MASK		0x00000001 /* Used bit.. */ +#define ZYNQ_GEM_RXBUF_ADD_MASK		0xFFFFFFFC /* Mask for address */ + +/* Wrap bit, last descriptor */ +#define ZYNQ_GEM_TXBUF_WRAP_MASK	0x40000000 +#define ZYNQ_GEM_TXBUF_LAST_MASK	0x00008000 /* Last buffer */ + +#define ZYNQ_GEM_TXSR_HRESPNOK_MASK	0x00000100 /* Transmit hresp not OK */ +#define ZYNQ_GEM_TXSR_URUN_MASK		0x00000040 /* Transmit underrun */ +/* Transmit buffs exhausted mid frame */ +#define ZYNQ_GEM_TXSR_BUFEXH_MASK	0x00000010 + +#define ZYNQ_GEM_NWCTRL_TXEN_MASK	0x00000008 /* Enable transmit */ +#define ZYNQ_GEM_NWCTRL_RXEN_MASK	0x00000004 /* Enable receive */ +#define ZYNQ_GEM_NWCTRL_MDEN_MASK	0x00000010 /* Enable MDIO port */ +#define ZYNQ_GEM_NWCTRL_STARTTX_MASK	0x00000200 /* Start tx (tx_go) */ + +#define ZYNQ_GEM_NWCFG_SPEED		0x00000001 /* 100 Mbps operation */ +#define ZYNQ_GEM_NWCFG_FDEN		0x00000002 /* Full Duplex mode */ +#define ZYNQ_GEM_NWCFG_FSREM		0x00020000 /* FCS removal */ +#define ZYNQ_GEM_NWCFG_MDCCLKDIV	0x000080000 /* Div pclk by 32, 80MHz */ + +#define ZYNQ_GEM_NWCFG_INIT		(ZYNQ_GEM_NWCFG_SPEED | \ +					ZYNQ_GEM_NWCFG_FDEN | \ +					ZYNQ_GEM_NWCFG_FSREM | \ +					ZYNQ_GEM_NWCFG_MDCCLKDIV) + +#define ZYNQ_GEM_NWSR_MDIOIDLE_MASK	0x00000004 /* PHY management idle */ + +#define ZYNQ_GEM_DMACR_BLENGTH		0x00000004 /* INCR4 AHB bursts */ +/* Use full configured addressable space (8 Kb) */ +#define ZYNQ_GEM_DMACR_RXSIZE		0x00000300 +/* Use full configured addressable space (4 Kb) */ +#define ZYNQ_GEM_DMACR_TXSIZE		0x00000400 +/* Set with binary 00011000 to use 1536 byte(1*max length frame/buffer) */ +#define ZYNQ_GEM_DMACR_RXBUF		0x00180000 + +#define ZYNQ_GEM_DMACR_INIT		(ZYNQ_GEM_DMACR_BLENGTH | \ +					ZYNQ_GEM_DMACR_RXSIZE | \ +					ZYNQ_GEM_DMACR_TXSIZE | \ +					ZYNQ_GEM_DMACR_RXBUF) + +/* Device registers */ +struct zynq_gem_regs { +	u32 nwctrl; /* Network Control reg */ +	u32 nwcfg; /* Network Config reg */ +	u32 nwsr; /* Network Status reg */ +	u32 reserved1; +	u32 dmacr; /* DMA Control reg */ +	u32 txsr; /* TX Status reg */ +	u32 rxqbase; /* RX Q Base address reg */ +	u32 txqbase; /* TX Q Base address reg */ +	u32 rxsr; /* RX Status reg */ +	u32 reserved2[2]; +	u32 idr; /* Interrupt Disable reg */ +	u32 reserved3; +	u32 phymntnc; /* Phy Maintaince reg */ +	u32 reserved4[18]; +	u32 hashl; /* Hash Low address reg */ +	u32 hashh; /* Hash High address reg */ +#define LADDR_LOW	0 +#define LADDR_HIGH	1 +	u32 laddr[4][LADDR_HIGH + 1]; /* Specific1 addr low/high reg */ +	u32 match[4]; /* Type ID1 Match reg */ +	u32 reserved6[18]; +	u32 stat[44]; /* Octects transmitted Low reg - stat start */ +}; + +/* BD descriptors */ +struct emac_bd { +	u32 addr; /* Next descriptor pointer */ +	u32 status; +}; + +#define RX_BUF 3 + +/* Initialized, rxbd_current, rx_first_buf must be 0 after init */ +struct zynq_gem_priv { +	struct emac_bd tx_bd; +	struct emac_bd rx_bd[RX_BUF]; +	char rxbuffers[RX_BUF * PKTSIZE_ALIGN]; +	u32 rxbd_current; +	u32 rx_first_buf; +	int phyaddr; +	struct phy_device *phydev; +	struct mii_dev *bus; +}; + +static inline int mdio_wait(struct eth_device *dev) +{ +	struct zynq_gem_regs *regs = (struct zynq_gem_regs *)dev->iobase; +	u32 timeout = 200; + +	/* Wait till MDIO interface is ready to accept a new transaction. */ +	while (--timeout) { +		if (readl(®s->nwsr) & ZYNQ_GEM_NWSR_MDIOIDLE_MASK) +			break; +		WATCHDOG_RESET(); +	} + +	if (!timeout) { +		printf("%s: Timeout\n", __func__); +		return 1; +	} + +	return 0; +} + +static u32 phy_setup_op(struct eth_device *dev, u32 phy_addr, u32 regnum, +							u32 op, u16 *data) +{ +	u32 mgtcr; +	struct zynq_gem_regs *regs = (struct zynq_gem_regs *)dev->iobase; + +	if (mdio_wait(dev)) +		return 1; + +	/* Construct mgtcr mask for the operation */ +	mgtcr = ZYNQ_GEM_PHYMNTNC_OP_MASK | op | +		(phy_addr << ZYNQ_GEM_PHYMNTNC_PHYAD_SHIFT_MASK) | +		(regnum << ZYNQ_GEM_PHYMNTNC_PHREG_SHIFT_MASK) | *data; + +	/* Write mgtcr and wait for completion */ +	writel(mgtcr, ®s->phymntnc); + +	if (mdio_wait(dev)) +		return 1; + +	if (op == ZYNQ_GEM_PHYMNTNC_OP_R_MASK) +		*data = readl(®s->phymntnc); + +	return 0; +} + +static u32 phyread(struct eth_device *dev, u32 phy_addr, u32 regnum, u16 *val) +{ +	return phy_setup_op(dev, phy_addr, regnum, +				ZYNQ_GEM_PHYMNTNC_OP_R_MASK, val); +} + +static u32 phywrite(struct eth_device *dev, u32 phy_addr, u32 regnum, u16 data) +{ +	return phy_setup_op(dev, phy_addr, regnum, +				ZYNQ_GEM_PHYMNTNC_OP_W_MASK, &data); +} + +static int zynq_gem_setup_mac(struct eth_device *dev) +{ +	u32 i, macaddrlow, macaddrhigh; +	struct zynq_gem_regs *regs = (struct zynq_gem_regs *)dev->iobase; + +	/* Set the MAC bits [31:0] in BOT */ +	macaddrlow = dev->enetaddr[0]; +	macaddrlow |= dev->enetaddr[1] << 8; +	macaddrlow |= dev->enetaddr[2] << 16; +	macaddrlow |= dev->enetaddr[3] << 24; + +	/* Set MAC bits [47:32] in TOP */ +	macaddrhigh = dev->enetaddr[4]; +	macaddrhigh |= dev->enetaddr[5] << 8; + +	for (i = 0; i < 4; i++) { +		writel(0, ®s->laddr[i][LADDR_LOW]); +		writel(0, ®s->laddr[i][LADDR_HIGH]); +		/* Do not use MATCHx register */ +		writel(0, ®s->match[i]); +	} + +	writel(macaddrlow, ®s->laddr[0][LADDR_LOW]); +	writel(macaddrhigh, ®s->laddr[0][LADDR_HIGH]); + +	return 0; +} + +static int zynq_gem_init(struct eth_device *dev, bd_t * bis) +{ +	u32 i; +	struct phy_device *phydev; +	const u32 stat_size = (sizeof(struct zynq_gem_regs) - +				offsetof(struct zynq_gem_regs, stat)) / 4; +	struct zynq_gem_regs *regs = (struct zynq_gem_regs *)dev->iobase; +	struct zynq_gem_priv *priv = dev->priv; +	const u32 supported = SUPPORTED_10baseT_Half | +			SUPPORTED_10baseT_Full | +			SUPPORTED_100baseT_Half | +			SUPPORTED_100baseT_Full | +			SUPPORTED_1000baseT_Half | +			SUPPORTED_1000baseT_Full; + +	/* Disable all interrupts */ +	writel(0xFFFFFFFF, ®s->idr); + +	/* Disable the receiver & transmitter */ +	writel(0, ®s->nwctrl); +	writel(0, ®s->txsr); +	writel(0, ®s->rxsr); +	writel(0, ®s->phymntnc); + +	/* Clear the Hash registers for the mac address pointed by AddressPtr */ +	writel(0x0, ®s->hashl); +	/* Write bits [63:32] in TOP */ +	writel(0x0, ®s->hashh); + +	/* Clear all counters */ +	for (i = 0; i <= stat_size; i++) +		readl(®s->stat[i]); + +	/* Setup RxBD space */ +	memset(&(priv->rx_bd), 0, sizeof(priv->rx_bd)); +	/* Create the RxBD ring */ +	memset(&(priv->rxbuffers), 0, sizeof(priv->rxbuffers)); + +	for (i = 0; i < RX_BUF; i++) { +		priv->rx_bd[i].status = 0xF0000000; +		priv->rx_bd[i].addr = (u32)((char *) &(priv->rxbuffers) + +							(i * PKTSIZE_ALIGN)); +	} +	/* WRAP bit to last BD */ +	priv->rx_bd[--i].addr |= ZYNQ_GEM_RXBUF_WRAP_MASK; +	/* Write RxBDs to IP */ +	writel((u32) &(priv->rx_bd), ®s->rxqbase); + +	/* MAC Setup */ +	/* Setup Network Configuration register */ +	writel(ZYNQ_GEM_NWCFG_INIT, ®s->nwcfg); + +	/* Setup for DMA Configuration register */ +	writel(ZYNQ_GEM_DMACR_INIT, ®s->dmacr); + +	/* Setup for Network Control register, MDIO, Rx and Tx enable */ +	setbits_le32(®s->nwctrl, ZYNQ_GEM_NWCTRL_MDEN_MASK | +			ZYNQ_GEM_NWCTRL_RXEN_MASK | ZYNQ_GEM_NWCTRL_TXEN_MASK); + +	/* interface - look at tsec */ +	phydev = phy_connect(priv->bus, priv->phyaddr, dev, 0); + +	phydev->supported &= supported; +	phydev->advertising = phydev->supported; +	priv->phydev = phydev; +	phy_config(phydev); +	phy_startup(phydev); + +	return 0; +} + +static int zynq_gem_send(struct eth_device *dev, void *ptr, int len) +{ +	u32 status; +	struct zynq_gem_priv *priv = dev->priv; +	struct zynq_gem_regs *regs = (struct zynq_gem_regs *)dev->iobase; +	const u32 mask = ZYNQ_GEM_TXSR_HRESPNOK_MASK | \ +			ZYNQ_GEM_TXSR_URUN_MASK | ZYNQ_GEM_TXSR_BUFEXH_MASK; + +	/* setup BD */ +	writel((u32)&(priv->tx_bd), ®s->txqbase); + +	/* Setup Tx BD */ +	memset((void *) &(priv->tx_bd), 0, sizeof(struct emac_bd)); + +	priv->tx_bd.addr = (u32)ptr; +	priv->tx_bd.status = len | ZYNQ_GEM_TXBUF_LAST_MASK | +						ZYNQ_GEM_TXBUF_WRAP_MASK; + +	/* Start transmit */ +	setbits_le32(®s->nwctrl, ZYNQ_GEM_NWCTRL_STARTTX_MASK); + +	/* Read the stat register to know if the packet has been transmitted */ +	status = readl(®s->txsr); +	if (status & mask) +		printf("Something has gone wrong here!? Status is 0x%x.\n", +		       status); + +	/* Clear Tx status register before leaving . */ +	writel(status, ®s->txsr); +	return 0; +} + +/* Do not check frame_recd flag in rx_status register 0x20 - just poll BD */ +static int zynq_gem_recv(struct eth_device *dev) +{ +	int frame_len; +	struct zynq_gem_priv *priv = dev->priv; +	struct emac_bd *current_bd = &priv->rx_bd[priv->rxbd_current]; +	struct emac_bd *first_bd; + +	if (!(current_bd->addr & ZYNQ_GEM_RXBUF_NEW_MASK)) +		return 0; + +	if (!(current_bd->status & +			(ZYNQ_GEM_RXBUF_SOF_MASK | ZYNQ_GEM_RXBUF_EOF_MASK))) { +		printf("GEM: SOF or EOF not set for last buffer received!\n"); +		return 0; +	} + +	frame_len = current_bd->status & ZYNQ_GEM_RXBUF_LEN_MASK; +	if (frame_len) { +		NetReceive((u8 *) (current_bd->addr & +					ZYNQ_GEM_RXBUF_ADD_MASK), frame_len); + +		if (current_bd->status & ZYNQ_GEM_RXBUF_SOF_MASK) +			priv->rx_first_buf = priv->rxbd_current; +		else { +			current_bd->addr &= ~ZYNQ_GEM_RXBUF_NEW_MASK; +			current_bd->status = 0xF0000000; /* FIXME */ +		} + +		if (current_bd->status & ZYNQ_GEM_RXBUF_EOF_MASK) { +			first_bd = &priv->rx_bd[priv->rx_first_buf]; +			first_bd->addr &= ~ZYNQ_GEM_RXBUF_NEW_MASK; +			first_bd->status = 0xF0000000; +		} + +		if ((++priv->rxbd_current) >= RX_BUF) +			priv->rxbd_current = 0; + +		return frame_len; +	} + +	return 0; +} + +static void zynq_gem_halt(struct eth_device *dev) +{ +	struct zynq_gem_regs *regs = (struct zynq_gem_regs *)dev->iobase; + +	/* Disable the receiver & transmitter */ +	writel(0, ®s->nwctrl); +} + +static int zynq_gem_miiphyread(const char *devname, uchar addr, +							uchar reg, ushort *val) +{ +	struct eth_device *dev = eth_get_dev(); +	int ret; + +	ret = phyread(dev, addr, reg, val); +	debug("%s 0x%x, 0x%x, 0x%x\n", __func__, addr, reg, *val); +	return ret; +} + +static int zynq_gem_miiphy_write(const char *devname, uchar addr, +							uchar reg, ushort val) +{ +	struct eth_device *dev = eth_get_dev(); + +	debug("%s 0x%x, 0x%x, 0x%x\n", __func__, addr, reg, val); +	return phywrite(dev, addr, reg, val); +} + +int zynq_gem_initialize(bd_t *bis, int base_addr) +{ +	struct eth_device *dev; +	struct zynq_gem_priv *priv; + +	dev = calloc(1, sizeof(*dev)); +	if (dev == NULL) +		return -1; + +	dev->priv = calloc(1, sizeof(struct zynq_gem_priv)); +	if (dev->priv == NULL) { +		free(dev); +		return -1; +	} +	priv = dev->priv; + +#ifdef CONFIG_PHY_ADDR +	priv->phyaddr = CONFIG_PHY_ADDR; +#else +	priv->phyaddr = -1; +#endif + +	sprintf(dev->name, "Gem.%x", base_addr); + +	dev->iobase = base_addr; + +	dev->init = zynq_gem_init; +	dev->halt = zynq_gem_halt; +	dev->send = zynq_gem_send; +	dev->recv = zynq_gem_recv; +	dev->write_hwaddr = zynq_gem_setup_mac; + +	eth_register(dev); + +	miiphy_register(dev->name, zynq_gem_miiphyread, zynq_gem_miiphy_write); +	priv->bus = miiphy_get_dev_by_name(dev->name); + +	return 1; +} diff --git a/include/netdev.h b/include/netdev.h index d1aaf0cd2..b8d303d08 100644 --- a/include/netdev.h +++ b/include/netdev.h @@ -104,7 +104,7 @@ int xilinx_emaclite_initialize(bd_t *bis, unsigned long base_addr,  							int txpp, int rxpp);  int xilinx_ll_temac_eth_init(bd_t *bis, unsigned long base_addr, int flags,  						unsigned long ctrl_addr); - +int zynq_gem_initialize(bd_t *bis, int base_addr);  /*   * As long as the Xilinx xps_ll_temac ethernet driver has not its own interface   * exported by a public hader file, we need a global definition at this point. |