diff options
Diffstat (limited to 'drivers/ns7520_eth.c')
| -rw-r--r-- | drivers/ns7520_eth.c | 859 | 
1 files changed, 859 insertions, 0 deletions
| diff --git a/drivers/ns7520_eth.c b/drivers/ns7520_eth.c new file mode 100644 index 000000000..a5a20dfd7 --- /dev/null +++ b/drivers/ns7520_eth.c @@ -0,0 +1,859 @@ +/*********************************************************************** + * + * Copyright (C) 2005 by Videon Central, Inc. + * + * $Id$ + * @Author: Arthur Shipkowski + * @Descr: Ethernet driver for the NS7520. Uses polled Ethernet, like + *     the older netarmeth driver.  Note that attempting to filter + *     broadcast and multicast out in the SAFR register will cause + *     bad things due to released errata. + * @References: [1] NS7520 Hardware Reference, December 2003 + *		[2] Intel LXT971 Datasheet #249414 Rev. 02 + * + ***********************************************************************/ + +#include <common.h> + +#if defined(CONFIG_DRIVER_NS7520_ETHERNET) + +#include <net.h>		/* NetSendPacket */ +#include <asm/arch/netarm_registers.h> +#include <asm/arch/netarm_dma_module.h> + +#include "ns7520_eth.h"		/* for Ethernet and PHY */ + +/** + * Send an error message to the terminal. + */ +#define ERROR(x) \ +do { \ +	char *__foo = strrchr(__FILE__, '/'); \ +	\ +	printf("%s: %d: %s(): ", (__foo == NULL ? __FILE__ : (__foo + 1)), \ +			__LINE__, __FUNCTION__); \ +	printf x; printf("\n"); \ +} while (0); + +/* some definition to make transistion to linux easier */ + +#define NS7520_DRIVER_NAME	"eth" +#define KERN_WARNING		"Warning:" +#define KERN_ERR		"Error:" +#define KERN_INFO		"Info:" + +#if 1 +# define DEBUG +#endif + +#ifdef	DEBUG +# define printk			printf + +# define DEBUG_INIT		0x0001 +# define DEBUG_MINOR		0x0002 +# define DEBUG_RX		0x0004 +# define DEBUG_TX		0x0008 +# define DEBUG_INT		0x0010 +# define DEBUG_POLL		0x0020 +# define DEBUG_LINK		0x0040 +# define DEBUG_MII		0x0100 +# define DEBUG_MII_LOW		0x0200 +# define DEBUG_MEM		0x0400 +# define DEBUG_ERROR		0x4000 +# define DEBUG_ERROR_CRIT	0x8000 + +static int nDebugLvl = DEBUG_ERROR_CRIT; + +# define DEBUG_ARGS0( FLG, a0 ) if( ( nDebugLvl & (FLG) ) == (FLG) ) \ +		printf("%s: " a0, __FUNCTION__, 0, 0, 0, 0, 0, 0 ) +# define DEBUG_ARGS1( FLG, a0, a1 ) if( ( nDebugLvl & (FLG) ) == (FLG)) \ +		printf("%s: " a0, __FUNCTION__, (int)(a1), 0, 0, 0, 0, 0 ) +# define DEBUG_ARGS2( FLG, a0, a1, a2 ) if( (nDebugLvl & (FLG)) ==(FLG))\ +		printf("%s: " a0, __FUNCTION__, (int)(a1), (int)(a2), 0, 0,0,0 ) +# define DEBUG_ARGS3( FLG, a0, a1, a2, a3 ) if((nDebugLvl &(FLG))==(FLG))\ +		printf("%s: "a0,__FUNCTION__,(int)(a1),(int)(a2),(int)(a3),0,0,0) +# define DEBUG_FN( FLG ) if( (nDebugLvl & (FLG)) == (FLG) ) \ +		printf("\r%s:line %d\n", (int)__FUNCTION__, __LINE__, 0,0,0,0); +# define ASSERT( expr, func ) if( !( expr ) ) { \ +		printf( "Assertion failed! %s:line %d %s\n", \ +		(int)__FUNCTION__,__LINE__,(int)(#expr),0,0,0); \ +		func } +#else				/* DEBUG */ +# define printk(...) +# define DEBUG_ARGS0( FLG, a0 ) +# define DEBUG_ARGS1( FLG, a0, a1 ) +# define DEBUG_ARGS2( FLG, a0, a1, a2 ) +# define DEBUG_ARGS3( FLG, a0, a1, a2, a3 ) +# define DEBUG_FN( n ) +# define ASSERT(expr, func) +#endif				/* DEBUG */ + +#define NS7520_MII_NEG_DELAY		(5*CFG_HZ)	/* in s */ +#define TX_TIMEOUT			(5*CFG_HZ)	/* in s */ +#define RX_STALL_WORKAROUND_CNT 100 + +static int ns7520_eth_reset(void); + +static void ns7520_link_auto_negotiate(void); +static void ns7520_link_update_egcr(void); +static void ns7520_link_print_changed(void); + +/* the PHY stuff */ + +static char ns7520_mii_identify_phy(void); +static unsigned short ns7520_mii_read(unsigned short uiRegister); +static void ns7520_mii_write(unsigned short uiRegister, +			     unsigned short uiData); +static unsigned int ns7520_mii_get_clock_divisor(unsigned int +						 unMaxMDIOClk); +static unsigned int ns7520_mii_poll_busy(void); + +static unsigned int nPhyMaxMdioClock = PHY_MDIO_MAX_CLK; +static unsigned int uiLastLinkStatus; +static PhyType phyDetected = PHY_NONE; + +/*********************************************************************** + * @Function: eth_init + * @Return: -1 on failure otherwise 0 + * @Descr: Initializes the ethernet engine and uses either FS Forth's default + *	   MAC addr or the one in environment + ***********************************************************************/ + +int eth_init(bd_t * pbis) +{ +	unsigned char aucMACAddr[6]; +	char *pcTmp = getenv("ethaddr"); +	char *pcEnd; +	int i; + +	DEBUG_FN(DEBUG_INIT); + +	/* no need to check for hardware */ + +	if (!ns7520_eth_reset()) +		return -1; + +	if (NULL == pcTmp) +		return -1; + +	for (i = 0; i < 6; i++) { +		aucMACAddr[i] = +		    pcTmp ? simple_strtoul(pcTmp, &pcEnd, 16) : 0; +		pcTmp = (*pcTmp) ? pcEnd + 1 : pcEnd; +	} + +	/* configure ethernet address */ + +	*get_eth_reg_addr(NS7520_ETH_SA1) = +	    aucMACAddr[5] << 8 | aucMACAddr[4]; +	*get_eth_reg_addr(NS7520_ETH_SA2) = +	    aucMACAddr[3] << 8 | aucMACAddr[2]; +	*get_eth_reg_addr(NS7520_ETH_SA3) = +	    aucMACAddr[1] << 8 | aucMACAddr[0]; + +	/* enable hardware */ + +	*get_eth_reg_addr(NS7520_ETH_MAC1) = NS7520_ETH_MAC1_RXEN; +	*get_eth_reg_addr(NS7520_ETH_SUPP) = NS7520_ETH_SUPP_JABBER; +	*get_eth_reg_addr(NS7520_ETH_MAC1) = NS7520_ETH_MAC1_RXEN; + +	/* the linux kernel may give packets < 60 bytes, for example arp */ +	*get_eth_reg_addr(NS7520_ETH_MAC2) = NS7520_ETH_MAC2_CRCEN | +	    NS7520_ETH_MAC2_PADEN | NS7520_ETH_MAC2_HUGE; + +	/* Broadcast/multicast allowed; if you don't set this even unicast chokes */ +	/* Based on NS7520 errata documentation */ +	*get_eth_reg_addr(NS7520_ETH_SAFR) = +	    NS7520_ETH_SAFR_BROAD | NS7520_ETH_SAFR_PRM; + +	/* enable receive and transmit FIFO, use 10/100 Mbps MII */ +	*get_eth_reg_addr(NS7520_ETH_EGCR) |= +	    NS7520_ETH_EGCR_ETXWM_75 | +	    NS7520_ETH_EGCR_ERX | +	    NS7520_ETH_EGCR_ERXREG | +	    NS7520_ETH_EGCR_ERXBR | NS7520_ETH_EGCR_ETX; + +	return 0; +} + +/*********************************************************************** + * @Function: eth_send + * @Return: -1 on timeout otherwise 1 + * @Descr: sends one frame by DMA + ***********************************************************************/ + +int eth_send(volatile void *pPacket, int nLen) +{ +	int i, length32, retval = 1; +	char *pa; +	unsigned int *pa32, lastp = 0, rest; +	unsigned int status; + +	pa = (char *) pPacket; +	pa32 = (unsigned int *) pPacket; +	length32 = nLen / 4; +	rest = nLen % 4; + +	/* make sure there's no garbage in the last word */ +	switch (rest) { +	case 0: +		lastp = pa32[length32 - 1]; +		length32--; +		break; +	case 1: +		lastp = pa32[length32] & 0x000000ff; +		break; +	case 2: +		lastp = pa32[length32] & 0x0000ffff; +		break; +	case 3: +		lastp = pa32[length32] & 0x00ffffff; +		break; +	} + +	while (((*get_eth_reg_addr(NS7520_ETH_EGSR)) & +		NS7520_ETH_EGSR_TXREGE) +	       == 0) { +	} + +	/* write to the fifo */ +	for (i = 0; i < length32; i++) +		*get_eth_reg_addr(NS7520_ETH_FIFO) = pa32[i]; + +	/* the last word is written to an extra register, this +	   starts the transmission */ +	*get_eth_reg_addr(NS7520_ETH_FIFOL) = lastp; + +	/* Wait for it to be done */ +	while ((*get_eth_reg_addr(NS7520_ETH_EGSR) & NS7520_ETH_EGSR_TXBC) +	       == 0) { +	} +	status = (*get_eth_reg_addr(NS7520_ETH_ETSR)); +	*get_eth_reg_addr(NS7520_ETH_EGSR) = NS7520_ETH_EGSR_TXBC;	/* Clear it now */ + +	if (status & NS7520_ETH_ETSR_TXOK) { +		retval = 0;	/* We're OK! */ +	} else if (status & NS7520_ETH_ETSR_TXDEF) { +		printf("Deferred, we'll see.\n"); +		retval = 0; +	} else if (status & NS7520_ETH_ETSR_TXAL) { +		printf("Late collision error, %d collisions.\n", +		       (*get_eth_reg_addr(NS7520_ETH_ETSR)) & +		       NS7520_ETH_ETSR_TXCOLC); +	} else if (status & NS7520_ETH_ETSR_TXAEC) { +		printf("Excessive collisions: %d\n", +		       (*get_eth_reg_addr(NS7520_ETH_ETSR)) & +		       NS7520_ETH_ETSR_TXCOLC); +	} else if (status & NS7520_ETH_ETSR_TXAED) { +		printf("Excessive deferral on xmit.\n"); +	} else if (status & NS7520_ETH_ETSR_TXAUR) { +		printf("Packet underrun.\n"); +	} else if (status & NS7520_ETH_ETSR_TXAJ) { +		printf("Jumbo packet error.\n"); +	} else { +		printf("Error: Should never get here.\n"); +	} + +	return (retval); +} + +/*********************************************************************** + * @Function: eth_rx + * @Return: size of last frame in bytes or 0 if no frame available + * @Descr: gives one frame to U-Boot which has been copied by DMA engine already + *	   to NetRxPackets[ 0 ]. + ***********************************************************************/ + +int eth_rx(void) +{ +	int i; +	unsigned short rxlen; +	unsigned short totrxlen = 0; +	unsigned int *addr; +	unsigned int rxstatus, lastrxlen; +	char *pa; + +	/* If RXBR is 1, data block was received */ +	while (((*get_eth_reg_addr(NS7520_ETH_EGSR)) & +		NS7520_ETH_EGSR_RXBR) == NS7520_ETH_EGSR_RXBR) { + +		/* get status register and the length of received block */ +		rxstatus = *get_eth_reg_addr(NS7520_ETH_ERSR); +		rxlen = (rxstatus & NS7520_ETH_ERSR_RXSIZE) >> 16; + +		/* clear RXBR to make fifo available */ +		*get_eth_reg_addr(NS7520_ETH_EGSR) = NS7520_ETH_EGSR_RXBR; + +		if (rxstatus & NS7520_ETH_ERSR_ROVER) { +			printf("Receive overrun, resetting FIFO.\n"); +			*get_eth_reg_addr(NS7520_ETH_EGCR) &= +			    ~NS7520_ETH_EGCR_ERX; +			udelay(20); +			*get_eth_reg_addr(NS7520_ETH_EGCR) |= +			    NS7520_ETH_EGCR_ERX; +		} +		if (rxlen == 0) { +			printf("Nothing.\n"); +			return 0; +		} + +		addr = (unsigned int *) NetRxPackets[0]; +		pa = (char *) NetRxPackets[0]; + +		/* read the fifo */ +		for (i = 0; i < rxlen / 4; i++) { +			*addr = *get_eth_reg_addr(NS7520_ETH_FIFO); +			addr++; +		} + +		if ((*get_eth_reg_addr(NS7520_ETH_EGSR)) & +		    NS7520_ETH_EGSR_RXREGR) { +			/* RXFDB indicates wether the last word is 1,2,3 or 4 bytes long */ +			lastrxlen = +			    ((*get_eth_reg_addr(NS7520_ETH_EGSR)) & +			     NS7520_ETH_EGSR_RXFDB_MA) >> 28; +			*addr = *get_eth_reg_addr(NS7520_ETH_FIFO); +			switch (lastrxlen) { +			case 1: +				*addr &= 0xff000000; +				break; +			case 2: +				*addr &= 0xffff0000; +				break; +			case 3: +				*addr &= 0xffffff00; +				break; +			} +		} + +		/* Pass the packet up to the protocol layers. */ +		NetReceive(NetRxPackets[0], rxlen - 4); +		totrxlen += rxlen - 4; +	} + +	return totrxlen; +} + +/*********************************************************************** + * @Function: eth_halt + * @Return: n/a + * @Descr: stops the ethernet engine + ***********************************************************************/ + +void eth_halt(void) +{ +	DEBUG_FN(DEBUG_INIT); + +	*get_eth_reg_addr(NS7520_ETH_MAC1) &= ~NS7520_ETH_MAC1_RXEN; +	*get_eth_reg_addr(NS7520_ETH_EGCR) &= ~(NS7520_ETH_EGCR_ERX | +						NS7520_ETH_EGCR_ERXDMA | +						NS7520_ETH_EGCR_ERXREG | +						NS7520_ETH_EGCR_ERXBR | +						NS7520_ETH_EGCR_ETX | +						NS7520_ETH_EGCR_ETXDMA); +} + +/*********************************************************************** + * @Function: ns7520_eth_reset + * @Return: 0 on failure otherwise 1 + * @Descr: resets the ethernet interface and the PHY, + *	   performs auto negotiation or fixed modes + ***********************************************************************/ + +static int ns7520_eth_reset(void) +{ +	DEBUG_FN(DEBUG_MINOR); + +	/* Reset important registers */ +	*get_eth_reg_addr(NS7520_ETH_EGCR) = 0;	/* Null it out! */ +	*get_eth_reg_addr(NS7520_ETH_MAC1) &= NS7520_ETH_MAC1_SRST; +	*get_eth_reg_addr(NS7520_ETH_MAC2) = 0; +	/* Reset MAC */ +	*get_eth_reg_addr(NS7520_ETH_EGCR) |= NS7520_ETH_EGCR_MAC_RES; +	udelay(5); +	*get_eth_reg_addr(NS7520_ETH_EGCR) &= ~NS7520_ETH_EGCR_MAC_RES; + +	/* reset and initialize PHY */ + +	*get_eth_reg_addr(NS7520_ETH_MAC1) &= ~NS7520_ETH_MAC1_SRST; + +	/* we don't support hot plugging of PHY, therefore we don't reset +	   phyDetected and nPhyMaxMdioClock here. The risk is if the setting is +	   incorrect the first open +	   may detect the PHY correctly but succeding will fail +	   For reseting the PHY and identifying we have to use the standard +	   MDIO CLOCK value 2.5 MHz only after hardware reset +	   After having identified the PHY we will do faster */ + +	*get_eth_reg_addr(NS7520_ETH_MCFG) = +	    ns7520_mii_get_clock_divisor(nPhyMaxMdioClock); + +	/* reset PHY */ +	ns7520_mii_write(PHY_COMMON_CTRL, PHY_COMMON_CTRL_RESET); +	ns7520_mii_write(PHY_COMMON_CTRL, 0); + +	udelay(3000);		/* [2] p.70 says at least 300us reset recovery time. */ + +	/* MII clock has been setup to default, ns7520_mii_identify_phy should +	   work for all */ + +	if (!ns7520_mii_identify_phy()) { +		printk(KERN_ERR NS7520_DRIVER_NAME +		       ": Unsupported PHY, aborting\n"); +		return 0; +	} + +	/* now take the highest MDIO clock possible after detection */ +	*get_eth_reg_addr(NS7520_ETH_MCFG) = +	    ns7520_mii_get_clock_divisor(nPhyMaxMdioClock); + +	/* PHY has been detected, so there can be no abort reason and we can +	   finish initializing ethernet */ + +	uiLastLinkStatus = 0xff;	/* undefined */ + +	ns7520_link_auto_negotiate(); + +	if (phyDetected == PHY_LXT971A) +		/* set LED2 to link mode */ +		ns7520_mii_write(PHY_LXT971_LED_CFG, +				 (PHY_LXT971_LED_CFG_LINK_ACT << +				  PHY_LXT971_LED_CFG_SHIFT_LED2) | +				 (PHY_LXT971_LED_CFG_TRANSMIT << +				  PHY_LXT971_LED_CFG_SHIFT_LED1)); + +	return 1; +} + +/*********************************************************************** + * @Function: ns7520_link_auto_negotiate + * @Return: void + * @Descr: performs auto-negotation of link. + ***********************************************************************/ + +static void ns7520_link_auto_negotiate(void) +{ +	unsigned long ulStartJiffies; +	unsigned short uiStatus; + +	DEBUG_FN(DEBUG_LINK); + +	/* run auto-negotation */ +	/* define what we are capable of */ +	ns7520_mii_write(PHY_COMMON_AUTO_ADV, +			 PHY_COMMON_AUTO_ADV_100BTXFD | +			 PHY_COMMON_AUTO_ADV_100BTX | +			 PHY_COMMON_AUTO_ADV_10BTFD | +			 PHY_COMMON_AUTO_ADV_10BT | +			 PHY_COMMON_AUTO_ADV_802_3); +	/* start auto-negotiation */ +	ns7520_mii_write(PHY_COMMON_CTRL, +			 PHY_COMMON_CTRL_AUTO_NEG | +			 PHY_COMMON_CTRL_RES_AUTO); + +	/* wait for completion */ + +	ulStartJiffies = get_timer(0); +	while (get_timer(0) < ulStartJiffies + NS7520_MII_NEG_DELAY) { +		uiStatus = ns7520_mii_read(PHY_COMMON_STAT); +		if ((uiStatus & +		     (PHY_COMMON_STAT_AN_COMP | PHY_COMMON_STAT_LNK_STAT)) +		    == +		    (PHY_COMMON_STAT_AN_COMP | PHY_COMMON_STAT_LNK_STAT)) { +			/* lucky we are, auto-negotiation succeeded */ +			ns7520_link_print_changed(); +			ns7520_link_update_egcr(); +			return; +		} +	} + +	DEBUG_ARGS0(DEBUG_LINK, "auto-negotiation timed out\n"); +	/* ignore invalid link settings */ +} + +/*********************************************************************** + * @Function: ns7520_link_update_egcr + * @Return: void + * @Descr: updates the EGCR and MAC2 link status after mode change or + *	   auto-negotation + ***********************************************************************/ + +static void ns7520_link_update_egcr(void) +{ +	unsigned int unEGCR; +	unsigned int unMAC2; +	unsigned int unIPGT; + +	DEBUG_FN(DEBUG_LINK); + +	unEGCR = *get_eth_reg_addr(NS7520_ETH_EGCR); +	unMAC2 = *get_eth_reg_addr(NS7520_ETH_MAC2); +	unIPGT = +	    *get_eth_reg_addr(NS7520_ETH_IPGT) & ~NS7520_ETH_IPGT_IPGT; + +	unEGCR &= ~NS7520_ETH_EGCR_EFULLD; +	unMAC2 &= ~NS7520_ETH_MAC2_FULLD; +	if ((uiLastLinkStatus & PHY_LXT971_STAT2_DUPLEX_MODE) +	    == PHY_LXT971_STAT2_DUPLEX_MODE) { +		unEGCR |= NS7520_ETH_EGCR_EFULLD; +		unMAC2 |= NS7520_ETH_MAC2_FULLD; +		unIPGT |= 0x15;	/* see [1] p. 167 */ +	} else +		unIPGT |= 0x12;	/* see [1] p. 167 */ + +	*get_eth_reg_addr(NS7520_ETH_MAC2) = unMAC2; +	*get_eth_reg_addr(NS7520_ETH_EGCR) = unEGCR; +	*get_eth_reg_addr(NS7520_ETH_IPGT) = unIPGT; +} + +/*********************************************************************** + * @Function: ns7520_link_print_changed + * @Return: void + * @Descr: checks whether the link status has changed and if so prints + *	   the new mode + ***********************************************************************/ + +static void ns7520_link_print_changed(void) +{ +	unsigned short uiStatus; +	unsigned short uiControl; + +	DEBUG_FN(DEBUG_LINK); + +	uiControl = ns7520_mii_read(PHY_COMMON_CTRL); + +	if ((uiControl & PHY_COMMON_CTRL_AUTO_NEG) == +	    PHY_COMMON_CTRL_AUTO_NEG) { +		/* PHY_COMMON_STAT_LNK_STAT is only set on autonegotiation */ +		uiStatus = ns7520_mii_read(PHY_COMMON_STAT); + +		if (!(uiStatus & PHY_COMMON_STAT_LNK_STAT)) { +			printk(KERN_WARNING NS7520_DRIVER_NAME +			       ": link down\n"); +			/* @TODO Linux: carrier_off */ +		} else { +			/* @TODO Linux: carrier_on */ +			if (phyDetected == PHY_LXT971A) { +				uiStatus = +				    ns7520_mii_read(PHY_LXT971_STAT2); +				uiStatus &= +				    (PHY_LXT971_STAT2_100BTX | +				     PHY_LXT971_STAT2_DUPLEX_MODE | +				     PHY_LXT971_STAT2_AUTO_NEG); + +				/* mask out all uninteresting parts */ +			} +			/* other PHYs must store there link information in +			   uiStatus as PHY_LXT971 */ +		} +	} else { +		/* mode has been forced, so uiStatus should be the same as the +		   last link status, enforce printing */ +		uiStatus = uiLastLinkStatus; +		uiLastLinkStatus = 0xff; +	} + +	if (uiStatus != uiLastLinkStatus) { +		/* save current link status */ +		uiLastLinkStatus = uiStatus; + +		/* print new link status */ + +		printk(KERN_INFO NS7520_DRIVER_NAME +		       ": link mode %i Mbps %s duplex %s\n", +		       (uiStatus & PHY_LXT971_STAT2_100BTX) ? 100 : 10, +		       (uiStatus & PHY_LXT971_STAT2_DUPLEX_MODE) ? "full" : +		       "half", +		       (uiStatus & PHY_LXT971_STAT2_AUTO_NEG) ? "(auto)" : +		       ""); +	} +} + +/*********************************************************************** + * the MII low level stuff + ***********************************************************************/ + +/*********************************************************************** + * @Function: ns7520_mii_identify_phy + * @Return: 1 if supported PHY has been detected otherwise 0 + * @Descr: checks for supported PHY and prints the IDs. + ***********************************************************************/ + +static char ns7520_mii_identify_phy(void) +{ +	unsigned short uiID1; +	unsigned short uiID2; +	unsigned char *szName; +	char cRes = 0; + +	DEBUG_FN(DEBUG_MII); + +	phyDetected = (PhyType) uiID1 = ns7520_mii_read(PHY_COMMON_ID1); + +	switch (phyDetected) { +	case PHY_LXT971A: +		szName = "LXT971A"; +		uiID2 = ns7520_mii_read(PHY_COMMON_ID2); +		nPhyMaxMdioClock = PHY_LXT971_MDIO_MAX_CLK; +		cRes = 1; +		break; +	case PHY_NONE: +	default: +		/* in case uiID1 == 0 && uiID2 == 0 we may have the wrong +		   address or reset sets the wrong NS7520_ETH_MCFG_CLKS */ + +		uiID2 = 0; +		szName = "unknown"; +		nPhyMaxMdioClock = PHY_MDIO_MAX_CLK; +		phyDetected = PHY_NONE; +	} + +	printk(KERN_INFO NS7520_DRIVER_NAME +	       ": PHY (0x%x, 0x%x) = %s detected\n", uiID1, uiID2, szName); + +	return cRes; +} + +/*********************************************************************** + * @Function: ns7520_mii_read + * @Return: the data read from PHY register uiRegister + * @Descr: the data read may be invalid if timed out. If so, a message + *	   is printed but the invalid data is returned. + *	   The fixed device address is being used. + ***********************************************************************/ + +static unsigned short ns7520_mii_read(unsigned short uiRegister) +{ +	DEBUG_FN(DEBUG_MII_LOW); + +	/* write MII register to be read */ +	*get_eth_reg_addr(NS7520_ETH_MADR) = +	    CONFIG_PHY_ADDR << 8 | uiRegister; + +	*get_eth_reg_addr(NS7520_ETH_MCMD) = NS7520_ETH_MCMD_READ; + +	if (!ns7520_mii_poll_busy()) +		printk(KERN_WARNING NS7520_DRIVER_NAME +		       ": MII still busy in read\n"); +	/* continue to read */ + +	*get_eth_reg_addr(NS7520_ETH_MCMD) = 0; + +	return (unsigned short) (*get_eth_reg_addr(NS7520_ETH_MRDD)); +} + +/*********************************************************************** + * @Function: ns7520_mii_write + * @Return: nothing + * @Descr: writes the data to the PHY register. In case of a timeout, + *	   no special handling is performed but a message printed + *	   The fixed device address is being used. + ***********************************************************************/ + +static void ns7520_mii_write(unsigned short uiRegister, +			     unsigned short uiData) +{ +	DEBUG_FN(DEBUG_MII_LOW); + +	/* write MII register to be written */ +	*get_eth_reg_addr(NS7520_ETH_MADR) = +	    CONFIG_PHY_ADDR << 8 | uiRegister; + +	*get_eth_reg_addr(NS7520_ETH_MWTD) = uiData; + +	if (!ns7520_mii_poll_busy()) { +		printf(KERN_WARNING NS7520_DRIVER_NAME +		       ": MII still busy in write\n"); +	} +} + +/*********************************************************************** + * @Function: ns7520_mii_get_clock_divisor + * @Return: the clock divisor that should be used in NS7520_ETH_MCFG_CLKS + * @Descr: if no clock divisor can be calculated for the + *	   current SYSCLK and the maximum MDIO Clock, a warning is printed + *	   and the greatest divisor is taken + ***********************************************************************/ + +static unsigned int ns7520_mii_get_clock_divisor(unsigned int unMaxMDIOClk) +{ +	struct { +		unsigned int unSysClkDivisor; +		unsigned int unClks;	/* field for NS7520_ETH_MCFG_CLKS */ +	} PHYClockDivisors[] = { +		{ +		4, NS7520_ETH_MCFG_CLKS_4}, { +		6, NS7520_ETH_MCFG_CLKS_6}, { +		8, NS7520_ETH_MCFG_CLKS_8}, { +		10, NS7520_ETH_MCFG_CLKS_10}, { +		14, NS7520_ETH_MCFG_CLKS_14}, { +		20, NS7520_ETH_MCFG_CLKS_20}, { +		28, NS7520_ETH_MCFG_CLKS_28} +	}; + +	int nIndexSysClkDiv; +	int nArraySize = +	    sizeof(PHYClockDivisors) / sizeof(PHYClockDivisors[0]); +	unsigned int unClks = NS7520_ETH_MCFG_CLKS_28;	/* defaults to +							   greatest div */ + +	DEBUG_FN(DEBUG_INIT); + +	for (nIndexSysClkDiv = 0; nIndexSysClkDiv < nArraySize; +	     nIndexSysClkDiv++) { +		/* find first sysclock divisor that isn't higher than 2.5 MHz +		   clock */ +		if (NETARM_XTAL_FREQ / +		    PHYClockDivisors[nIndexSysClkDiv].unSysClkDivisor <= +		    unMaxMDIOClk) { +			unClks = PHYClockDivisors[nIndexSysClkDiv].unClks; +			break; +		} +	} + +	DEBUG_ARGS2(DEBUG_INIT, +		    "Taking MDIO Clock bit mask 0x%0x for max clock %i\n", +		    unClks, unMaxMDIOClk); + +	/* return greatest divisor */ +	return unClks; +} + +/*********************************************************************** + * @Function: ns7520_mii_poll_busy + * @Return: 0 if timed out otherwise the remaing timeout + * @Descr: waits until the MII has completed a command or it times out + *	   code may be interrupted by hard interrupts. + *	   It is not checked what happens on multiple actions when + *	   the first is still being busy and we timeout. + ***********************************************************************/ + +static unsigned int ns7520_mii_poll_busy(void) +{ +	unsigned int unTimeout = 1000; + +	DEBUG_FN(DEBUG_MII_LOW); + +	while (((*get_eth_reg_addr(NS7520_ETH_MIND) & NS7520_ETH_MIND_BUSY) +		== NS7520_ETH_MIND_BUSY) && unTimeout) +		unTimeout--; + +	return unTimeout; +} + +/* ---------------------------------------------------------------------------- + * Net+ARM ethernet MII functionality. + */ +#if defined(CONFIG_MII) + +/** + * Maximum MII address we support + */ +#define MII_ADDRESS_MAX			(31) + +/** + * Maximum MII register address we support + */ +#define MII_REGISTER_MAX		(31) + +/** + * Ethernet MII interface return values for public functions. + */ +enum mii_status { +	MII_STATUS_SUCCESS = 0, +	MII_STATUS_FAILURE = 1, +}; + +/** + * Read a 16-bit value from an MII register. + */ +extern int ns7520_miiphy_read(char *devname, unsigned char const addr, +		unsigned char const reg, unsigned short *const value) +{ +	int ret = MII_STATUS_FAILURE; + +	/* Parameter checks */ +	if (addr > MII_ADDRESS_MAX) { +		ERROR(("invalid addr, 0x%02X", addr)); +		goto miiphy_read_failed_0; +	} + +	if (reg > MII_REGISTER_MAX) { +		ERROR(("invalid reg, 0x%02X", reg)); +		goto miiphy_read_failed_0; +	} + +	if (value == NULL) { +		ERROR(("NULL value")); +		goto miiphy_read_failed_0; +	} + +	DEBUG_FN(DEBUG_MII_LOW); + +	/* write MII register to be read */ +	*get_eth_reg_addr(NS7520_ETH_MADR) = (addr << 8) | reg; + +	*get_eth_reg_addr(NS7520_ETH_MCMD) = NS7520_ETH_MCMD_READ; + +	if (!ns7520_mii_poll_busy()) +		printk(KERN_WARNING NS7520_DRIVER_NAME +		       ": MII still busy in read\n"); +	/* continue to read */ + +	*get_eth_reg_addr(NS7520_ETH_MCMD) = 0; + +	*value = (*get_eth_reg_addr(NS7520_ETH_MRDD)); +	ret = MII_STATUS_SUCCESS; +	/* Fall through */ + +      miiphy_read_failed_0: +	return (ret); +} + +/** + * Write a 16-bit value to an MII register. + */ +extern int ns7520_miiphy_write(char *devname, unsigned char const addr, +		unsigned char const reg, unsigned short const value) +{ +	int ret = MII_STATUS_FAILURE; + +	/* Parameter checks */ +	if (addr > MII_ADDRESS_MAX) { +		ERROR(("invalid addr, 0x%02X", addr)); +		goto miiphy_write_failed_0; +	} + +	if (reg > MII_REGISTER_MAX) { +		ERROR(("invalid reg, 0x%02X", reg)); +		goto miiphy_write_failed_0; +	} + +	/* write MII register to be written */ +	*get_eth_reg_addr(NS7520_ETH_MADR) = (addr << 8) | reg; + +	*get_eth_reg_addr(NS7520_ETH_MWTD) = value; + +	if (!ns7520_mii_poll_busy()) { +		printf(KERN_WARNING NS7520_DRIVER_NAME +		       ": MII still busy in write\n"); +	} + +	ret = MII_STATUS_SUCCESS; +	/* Fall through */ + +      miiphy_write_failed_0: +	return (ret); +} +#endif				/* defined(CONFIG_MII) */ +#endif				/* CONFIG_DRIVER_NS7520_ETHERNET */ + +int ns7520_miiphy_initialize(bd_t *bis) +{ +#if defined(CONFIG_DRIVER_NS7520_ETHERNET) +#if defined(CONFIG_MII) +	miiphy_register("ns7520phy", ns7520_miiphy_read, ns7520_miiphy_write); +#endif +#endif +	return 0; +} |