860 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			860 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
| /***********************************************************************
 | |
|  *
 | |
|  * 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;
 | |
| }
 |