diff options
| author | Ilya Yanok <yanok@emcraft.com> | 2009-02-09 18:45:28 +0100 | 
|---|---|---|
| committer | Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> | 2009-02-10 00:16:27 +0100 | 
| commit | 62cbc408f52fc9a5eb849e0b882c504780c9d183 (patch) | |
| tree | f9a061d34c164f0091d2d0b211374946740866da /drivers/net/dnet.c | |
| parent | 54dc517328709c204a9cbf7a253d9f8e6c4b26ec (diff) | |
| download | olio-uboot-2014.01-62cbc408f52fc9a5eb849e0b882c504780c9d183.tar.xz olio-uboot-2014.01-62cbc408f52fc9a5eb849e0b882c504780c9d183.zip | |
dnet: driver for Dave DNET ethernet controller
Driver for Dave DNET ethernet controller (used on Dave/DENX
QongEVB-LITE board).
Signed-off-by: Ilya Yanok <yanok@emcraft.com>
Acked-by: Ben Warren <biggerbadderben@gmail.com>
Diffstat (limited to 'drivers/net/dnet.c')
| -rw-r--r-- | drivers/net/dnet.c | 396 | 
1 files changed, 396 insertions, 0 deletions
| diff --git a/drivers/net/dnet.c b/drivers/net/dnet.c new file mode 100644 index 000000000..efac746e3 --- /dev/null +++ b/drivers/net/dnet.c @@ -0,0 +1,396 @@ +/* + * Dave Ethernet Controller driver + * + * Copyright (C) 2008 Dave S.r.l. <www.dave.eu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <common.h> + +#ifndef CONFIG_DNET_AUTONEG_TIMEOUT +#define CONFIG_DNET_AUTONEG_TIMEOUT	5000000	/* default value */ +#endif + +#include <net.h> +#include <malloc.h> +#include <linux/mii.h> + +#include <miiphy.h> +#include <asm/io.h> + +#include "dnet.h" + +struct dnet_device { +	struct dnet_registers	*regs; +	const struct device	*dev; +	struct eth_device	netdev; +	unsigned short		phy_addr; +}; + +/* get struct dnet_device from given struct netdev */ +#define to_dnet(_nd) container_of(_nd, struct dnet_device, netdev) + +/* function for reading internal MAC register */ +u16 dnet_readw_mac(struct dnet_device *dnet, u16 reg) +{ +	u16 data_read; + +	/* issue a read */ +	writel(reg, &dnet->regs->MACREG_ADDR); + +	/* since a read/write op to the MAC is very slow, +	 * we must wait before reading the data */ +	udelay(1); + +	/* read data read from the MAC register */ +	data_read = readl(&dnet->regs->MACREG_DATA); + +	/* all done */ +	return data_read; +} + +/* function for writing internal MAC register */ +void dnet_writew_mac(struct dnet_device *dnet, u16 reg, u16 val) +{ +	/* load data to write */ +	writel(val, &dnet->regs->MACREG_DATA); + +	/* issue a write */ +	writel(reg | DNET_INTERNAL_WRITE, &dnet->regs->MACREG_ADDR); + +	/* since a read/write op to the MAC is very slow, +	 * we must wait before exiting */ +	udelay(1); +} + +static void dnet_mdio_write(struct dnet_device *dnet, u8 reg, u16 value) +{ +	u16 tmp; + +	debug(DRIVERNAME "dnet_mdio_write %02x:%02x <- %04x\n", +			dnet->phy_addr, reg, value); + +	while (!(dnet_readw_mac(dnet, DNET_INTERNAL_GMII_MNG_CTL_REG) & +				DNET_INTERNAL_GMII_MNG_CMD_FIN)) +		; + +	/* prepare for a write operation */ +	tmp = (1 << 13); + +	/* only 5 bits allowed for register offset */ +	reg &= 0x1f; + +	/* prepare reg_value for a write */ +	tmp |= (dnet->phy_addr << 8); +	tmp |= reg; + +	/* write data to write first */ +	dnet_writew_mac(dnet, DNET_INTERNAL_GMII_MNG_DAT_REG, value); + +	/* write control word */ +	dnet_writew_mac(dnet, DNET_INTERNAL_GMII_MNG_CTL_REG, tmp); + +	while (!(dnet_readw_mac(dnet, DNET_INTERNAL_GMII_MNG_CTL_REG) & +				DNET_INTERNAL_GMII_MNG_CMD_FIN)) +		; +} + +static u16 dnet_mdio_read(struct dnet_device *dnet, u8 reg) +{ +	u16 value; + +	while (!(dnet_readw_mac(dnet, DNET_INTERNAL_GMII_MNG_CTL_REG) & +				DNET_INTERNAL_GMII_MNG_CMD_FIN)) +		; + +	/* only 5 bits allowed for register offset*/ +	reg &= 0x1f; + +	/* prepare reg_value for a read */ +	value = (dnet->phy_addr << 8); +	value |= reg; + +	/* write control word */ +	dnet_writew_mac(dnet, DNET_INTERNAL_GMII_MNG_CTL_REG, value); + +	/* wait for end of transfer */ +	while (!(dnet_readw_mac(dnet, DNET_INTERNAL_GMII_MNG_CTL_REG) & +				DNET_INTERNAL_GMII_MNG_CMD_FIN)) +		; + +	value = dnet_readw_mac(dnet, DNET_INTERNAL_GMII_MNG_DAT_REG); + +	debug(DRIVERNAME "dnet_mdio_read %02x:%02x <- %04x\n", +		dnet->phy_addr, reg, value); + +	return value; +} + +static int dnet_send(struct eth_device *netdev, volatile void *packet, +		     int length) +{ +	struct dnet_device *dnet = to_dnet(netdev); +	int i, len, wrsz; +	unsigned int *bufp; +	unsigned int tx_cmd; + +	debug(DRIVERNAME "[%s] Sending %u bytes\n", __func__, length); + +	/* frame size (words) */ +	len = (length + 3) >> 2; + +	bufp = (unsigned int *) (((u32)packet) & 0xFFFFFFFC); +	wrsz = (u32)length + 3; +	wrsz += ((u32)packet) & 0x3; +	wrsz >>= 2; +	tx_cmd = ((((unsigned int)(packet)) & 0x03) << 16) | (u32)length; + +	/* check if there is enough room for the current frame */ +	if (wrsz < (DNET_FIFO_SIZE - readl(&dnet->regs->TX_FIFO_WCNT))) { +		for (i = 0; i < wrsz; i++) +			writel(*bufp++, &dnet->regs->TX_DATA_FIFO); +		/* +		 * inform MAC that a packet's written and ready +		 * to be shipped out +		 */ +		writel(tx_cmd, &dnet->regs->TX_LEN_FIFO); +	} else { +		printf(DRIVERNAME "No free space (actual %d, required %d " +				"(words))\n", DNET_FIFO_SIZE - +				readl(&dnet->regs->TX_FIFO_WCNT), wrsz); +	} + +	/* No one cares anyway */ +	return 0; +} + + +static int dnet_recv(struct eth_device *netdev) +{ +	struct dnet_device *dnet = to_dnet(netdev); +	unsigned int *data_ptr; +	int pkt_len, poll, i; +	u32 cmd_word; + +	debug("Waiting for pkt (polling)\n"); +	poll = 50; +	while ((readl(&dnet->regs->RX_FIFO_WCNT) >> 16) == 0) { +		udelay(10);  /* wait 10 usec */ +		if (--poll == 0) +			return 0;	/* no pkt available */ +	} + +	cmd_word = readl(&dnet->regs->RX_LEN_FIFO); +	pkt_len = cmd_word & 0xFFFF; + +	debug("Got pkt with size %d bytes\n", pkt_len); + +	if (cmd_word & 0xDF180000) +		printf("%s packet receive error %x\n", __func__, cmd_word); + +	data_ptr = (unsigned int *) NetRxPackets[0]; + +	for (i = 0; i < (pkt_len + 3) >> 2; i++) +		*data_ptr++ = readl(&dnet->regs->RX_DATA_FIFO); + +	NetReceive(NetRxPackets[0], pkt_len + 5); /* ok + 5 ?? */ + +	return 0; +} + +static void dnet_set_hwaddr(struct eth_device *netdev) +{ +	struct dnet_device *dnet = to_dnet(netdev); +	u16 tmp; + +	tmp = cpu_to_be16(*((u16 *)netdev->enetaddr)); +	dnet_writew_mac(dnet, DNET_INTERNAL_MAC_ADDR_0_REG, tmp); +	tmp = cpu_to_be16(*((u16 *)(netdev->enetaddr + 2))); +	dnet_writew_mac(dnet, DNET_INTERNAL_MAC_ADDR_1_REG, tmp); +	tmp = cpu_to_be16(*((u16 *)(netdev->enetaddr + 4))); +	dnet_writew_mac(dnet, DNET_INTERNAL_MAC_ADDR_2_REG, tmp); +} + +static void dnet_phy_reset(struct dnet_device *dnet) +{ +	struct eth_device *netdev = &dnet->netdev; +	int i; +	u16 status, adv; + +	adv = ADVERTISE_CSMA | ADVERTISE_ALL; +	dnet_mdio_write(dnet, MII_ADVERTISE, adv); +	printf("%s: Starting autonegotiation...\n", netdev->name); +	dnet_mdio_write(dnet, MII_BMCR, (BMCR_ANENABLE +					 | BMCR_ANRESTART)); + +	for (i = 0; i < CONFIG_DNET_AUTONEG_TIMEOUT / 100; i++) { +		status = dnet_mdio_read(dnet, MII_BMSR); +		if (status & BMSR_ANEGCOMPLETE) +			break; +		udelay(100); +	} + +	if (status & BMSR_ANEGCOMPLETE) +		printf("%s: Autonegotiation complete\n", netdev->name); +	else +		printf("%s: Autonegotiation timed out (status=0x%04x)\n", +		       netdev->name, status); +} + +static int dnet_phy_init(struct dnet_device *dnet) +{ +	struct eth_device *netdev = &dnet->netdev; +	u16 phy_id, status, adv, lpa; +	int media, speed, duplex; +	int i; +	u32 ctl_reg; + +	/* Find a PHY */ +	for (i = 0; i < 32; i++) { +		dnet->phy_addr = i; +		phy_id = dnet_mdio_read(dnet, MII_PHYSID1); +		if (phy_id != 0xffff) { +			/* ok we found it */ +			printf("Found PHY at address %d PHYID (%04x:%04x)\n", +					i, phy_id, +					dnet_mdio_read(dnet, MII_PHYSID2)); +			break; +		} +	} + +	/* Check if the PHY is up to snuff... */ +	phy_id = dnet_mdio_read(dnet, MII_PHYSID1); +	if (phy_id == 0xffff) { +		printf("%s: No PHY present\n", netdev->name); +		return -1; +	} + +	status = dnet_mdio_read(dnet, MII_BMSR); +	if (!(status & BMSR_LSTATUS)) { +		/* Try to re-negotiate if we don't have link already. */ +		dnet_phy_reset(dnet); + +		for (i = 0; i < CONFIG_DNET_AUTONEG_TIMEOUT / 100; i++) { +			status = dnet_mdio_read(dnet, MII_BMSR); +			if (status & BMSR_LSTATUS) +				break; +			udelay(100); +		} +	} + +	if (!(status & BMSR_LSTATUS)) { +		printf("%s: link down (status: 0x%04x)\n", +		       netdev->name, status); +		return -1; +	} else { +		adv = dnet_mdio_read(dnet, MII_ADVERTISE); +		lpa = dnet_mdio_read(dnet, MII_LPA); +		media = mii_nway_result(lpa & adv); +		speed = (media & (ADVERTISE_100FULL | ADVERTISE_100HALF) +			 ? 1 : 0); +		duplex = (media & ADVERTISE_FULL) ? 1 : 0; +		/* 1000BaseT ethernet is not supported */ +		printf("%s: link up, %sMbps %s-duplex (lpa: 0x%04x)\n", +		       netdev->name, +		       speed ? "100" : "10", +		       duplex ? "full" : "half", +		       lpa); + +		ctl_reg = dnet_readw_mac(dnet, DNET_INTERNAL_RXTX_CONTROL_REG); + +		if (duplex) +			ctl_reg &= ~(DNET_INTERNAL_RXTX_CONTROL_ENABLEHALFDUP); +		else +			ctl_reg |= DNET_INTERNAL_RXTX_CONTROL_ENABLEHALFDUP; + +		dnet_writew_mac(dnet, DNET_INTERNAL_RXTX_CONTROL_REG, ctl_reg); + +		return 0; +	} +} + +static int dnet_init(struct eth_device *netdev, bd_t *bd) +{ +	struct dnet_device *dnet = to_dnet(netdev); +	u32 config; + +	/* +	 * dnet_halt should have been called at some point before now, +	 * so we'll assume the controller is idle. +	 */ + +	/* set hardware address */ +	dnet_set_hwaddr(netdev); + +	if (dnet_phy_init(dnet) < 0) +		return -1; + +	/* flush rx/tx fifos */ +	writel(DNET_SYS_CTL_RXFIFOFLUSH | DNET_SYS_CTL_TXFIFOFLUSH, +			&dnet->regs->SYS_CTL); +	udelay(1000); +	writel(0, &dnet->regs->SYS_CTL); + +	config = dnet_readw_mac(dnet, DNET_INTERNAL_RXTX_CONTROL_REG); + +	config |= DNET_INTERNAL_RXTX_CONTROL_RXPAUSE | +			DNET_INTERNAL_RXTX_CONTROL_RXBROADCAST | +			DNET_INTERNAL_RXTX_CONTROL_DROPCONTROL | +			DNET_INTERNAL_RXTX_CONTROL_DISCFXFCS; + +	dnet_writew_mac(dnet, DNET_INTERNAL_RXTX_CONTROL_REG, config); + +	/* Enable TX and RX */ +	dnet_writew_mac(dnet, DNET_INTERNAL_MODE_REG, +			DNET_INTERNAL_MODE_RXEN | DNET_INTERNAL_MODE_TXEN); + +	return 0; +} + +static void dnet_halt(struct eth_device *netdev) +{ +	struct dnet_device *dnet = to_dnet(netdev); + +	/* Disable TX and RX */ +	dnet_writew_mac(dnet, DNET_INTERNAL_MODE_REG, 0); +} + +int dnet_eth_initialize(int id, void *regs, unsigned int phy_addr) +{ +	struct dnet_device *dnet; +	struct eth_device *netdev; +	unsigned int dev_capa; + +	dnet = malloc(sizeof(struct dnet_device)); +	if (!dnet) { +		printf("Error: Failed to allocate memory for DNET%d\n", id); +		return -1; +	} +	memset(dnet, 0, sizeof(struct dnet_device)); + +	netdev = &dnet->netdev; + +	dnet->regs = (struct dnet_registers *)regs; +	dnet->phy_addr = phy_addr; + +	sprintf(netdev->name, "dnet%d", id); +	netdev->init = dnet_init; +	netdev->halt = dnet_halt; +	netdev->send = dnet_send; +	netdev->recv = dnet_recv; + +	dev_capa = readl(&dnet->regs->VERCAPS) & 0xFFFF; +	debug("%s: has %smdio, %sirq, %sgigabit, %sdma \n", netdev->name, +		(dev_capa & DNET_HAS_MDIO) ? "" : "no ", +		(dev_capa & DNET_HAS_IRQ) ? "" : "no ", +		(dev_capa & DNET_HAS_GIGABIT) ? "" : "no ", +		(dev_capa & DNET_HAS_DMA) ? "" : "no "); + +	eth_register(netdev); + +	return 0; +} + |