diff options
| author | wdenk <wdenk> | 2004-02-26 23:46:20 +0000 | 
|---|---|---|
| committer | wdenk <wdenk> | 2004-02-26 23:46:20 +0000 | 
| commit | 80885a9d526b6b9666500d17ec7941b9dad8de44 (patch) | |
| tree | f294ef9147fb7212a2072c614952a3417c53a89f /drivers/ns9750_eth.c | |
| parent | 0c852a2886fb51222f8fb07e6cde3b72fa4e566d (diff) | |
| download | olio-uboot-2014.01-80885a9d526b6b9666500d17ec7941b9dad8de44.tar.xz olio-uboot-2014.01-80885a9d526b6b9666500d17ec7941b9dad8de44.zip | |
* Patch by Markus Pietrek, 24 Feb 2004:
  NS9750 DevBoard added
* Patch by Pierre AUBERT, 24 Feb 2004
  add USB support for MPC5200
* Patch by Steven Scholz, 24 Feb 2004:
  - fix MII commands to use values from last command
* Patch by Torsten Demke, 24 Feb 2004:
  Add support for the eXalion platform (SPSW-8240, F-30, F-300)
Diffstat (limited to 'drivers/ns9750_eth.c')
| -rw-r--r-- | drivers/ns9750_eth.c | 797 | 
1 files changed, 797 insertions, 0 deletions
| diff --git a/drivers/ns9750_eth.c b/drivers/ns9750_eth.c new file mode 100644 index 000000000..067ff8efa --- /dev/null +++ b/drivers/ns9750_eth.c @@ -0,0 +1,797 @@ +/*********************************************************************** + * + * Copyright (C) 2004 by FS Forth-Systeme GmbH. + * All rights reserved. + * + * $Id: ns9750_eth.c,v 1.2 2004/02/24 14:09:39 mpietrek Exp $ + * @Author: Markus Pietrek + * @Descr: Ethernet driver for the NS9750. Uses DMA Engine with polling + *	   interrupt status. But interrupts are not enabled. + *	   Only one tx buffer descriptor and the RXA buffer descriptor are used + *	   Currently no transmit lockup handling is included. eth_send has a 5s + *	   timeout for sending frames. No retransmits are performed when an + *	   error occurs. + * @References: [1] NS9750 Hardware Reference, December 2003 + *		[2] Intel LXT971 Datasheet #249414 Rev. 02 + *		[3] NS7520 Linux Ethernet Driver + * + * 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>		/* NetSendPacket */ + +#include "ns9750_eth.h"		/* for Ethernet and PHY */ + +#ifdef CONFIG_DRIVER_NS9750_ETHERNET + +/* some definition to make transistion to linux easier */ + +#define NS9750_DRIVER_NAME	"eth" +#define KERN_WARNING		"Warning:" +#define KERN_ERR		"Error:" +#define KERN_INFO		"Info:" + +#if 0 +# 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 NS9750_MII_NEG_DELAY		(5*CFG_HZ) /* in s */ +#define TX_TIMEOUT			(5*CFG_HZ) /* in s */ + +/* @TODO move it to eeprom.h */ +#define FS_EEPROM_AUTONEG_MASK		0x7 +#define FS_EEPROM_AUTONEG_SPEED_MASK	0x1 +#define FS_EEPROM_AUTONEG_SPEED_10	0x0 +#define FS_EEPROM_AUTONEG_SPEED_100	0x1 +#define FS_EEPROM_AUTONEG_DUPLEX_MASK	0x2 +#define FS_EEPROM_AUTONEG_DUPLEX_HALF	0x0 +#define FS_EEPROM_AUTONEG_DUPLEX_FULL	0x2 +#define FS_EEPROM_AUTONEG_ENABLE_MASK	0x4 +#define FS_EEPROM_AUTONEG_DISABLE	0x0 +#define FS_EEPROM_AUTONEG_ENABLE	0x4 + +/* buffer descriptors taken from [1] p.306 */ +typedef struct +{ +	unsigned int* punSrc; +	unsigned int unLen;	/* 11 bits */ +	unsigned int* punDest;	/* unused */ +	union { +		unsigned int unReg; +		struct { +			unsigned uStatus : 16; +			unsigned uRes : 12; +			unsigned uFull : 1; +			unsigned uEnable : 1; +			unsigned uInt : 1; +			unsigned uWrap : 1; +		} bits; +	} s; +} rx_buffer_desc_t; + +typedef struct +{ +	unsigned int* punSrc; +	unsigned int unLen;	/* 10 bits */ +	unsigned int* punDest;	/* unused */ +	union { +		unsigned int unReg; /* only 32bit accesses may done to NS9750 +				     * eth engine */ +		struct { +			unsigned uStatus : 16; +			unsigned uRes : 12; +			unsigned uFull : 1; +			unsigned uLast : 1; +			unsigned uInt : 1; +			unsigned uWrap : 1; +		} bits; +	} s; +} tx_buffer_desc_t; + +static int ns9750_eth_reset( void ); + +static void ns9750_link_force( void ); +static void ns9750_link_auto_negotiate( void ); +static void ns9750_link_update_egcr( void ); +static void ns9750_link_print_changed( void ); + +/* the PHY stuff */ + +static char ns9750_mii_identify_phy( void ); +static unsigned short ns9750_mii_read( unsigned short uiRegister ); +static void ns9750_mii_write( unsigned short uiRegister, unsigned short uiData ); +static unsigned int ns9750_mii_get_clock_divisor( unsigned int unMaxMDIOClk ); +static unsigned int ns9750_mii_poll_busy( void ); + +static unsigned int nPhyMaxMdioClock = PHY_MDIO_MAX_CLK; +static unsigned char ucLinkMode =      FS_EEPROM_AUTONEG_ENABLE; +static unsigned int uiLastLinkStatus; +static PhyType phyDetected = PHY_NONE; + +/* we use only one tx buffer descriptor */ +static tx_buffer_desc_t* pTxBufferDesc = +	(tx_buffer_desc_t*) get_eth_reg_addr( NS9750_ETH_TXBD ); + +/* we use only one rx buffer descriptor of the 4 */ +static rx_buffer_desc_t aRxBufferDesc[ 4 ]; + +/*********************************************************************** + * @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) +{ +	/* This default MAC Addr is reserved by FS Forth-Systeme for the case of +	   EEPROM failures */ +	unsigned char aucMACAddr[6] = { 0x00, 0x04, 0xf3, 0x00, 0x06, 0x35 }; +	char *pcTmp = getenv ("ethaddr"); +	char *pcEnd; +	int i; + +	DEBUG_FN (DEBUG_INIT); + +	/* no need to check for hardware */ + +	if (!ns9750_eth_reset ()) +		return -1; + +	if (pcTmp != NULL) +		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 (NS9750_ETH_SA1) = +		aucMACAddr[5] << 8 | aucMACAddr[4]; +	*get_eth_reg_addr (NS9750_ETH_SA2) = +		aucMACAddr[3] << 8 | aucMACAddr[2]; +	*get_eth_reg_addr (NS9750_ETH_SA3) = +		aucMACAddr[1] << 8 | aucMACAddr[0]; + +	/* enable hardware */ + +	*get_eth_reg_addr (NS9750_ETH_MAC1) = NS9750_ETH_MAC1_RXEN; + +	/* the linux kernel may give packets < 60 bytes, for example arp */ +	*get_eth_reg_addr (NS9750_ETH_MAC2) = NS9750_ETH_MAC2_CRCEN | +		NS9750_ETH_MAC2_PADEN | NS9750_ETH_MAC2_HUGE; + +	/* enable receive and transmit FIFO, use 10/100 Mbps MII */ +	*get_eth_reg_addr (NS9750_ETH_EGCR1) = +		NS9750_ETH_EGCR1_ETXWM | +		NS9750_ETH_EGCR1_ERX | +		NS9750_ETH_EGCR1_ERXDMA | +		NS9750_ETH_EGCR1_ETX | +		NS9750_ETH_EGCR1_ETXDMA | NS9750_ETH_EGCR1_ITXA; + +	/* prepare DMA descriptors */ +	for (i = 0; i < 4; i++) { +		aRxBufferDesc[i].punSrc = 0; +		aRxBufferDesc[i].unLen = 0; +		aRxBufferDesc[i].s.bits.uWrap = 1; +		aRxBufferDesc[i].s.bits.uInt = 1; +		aRxBufferDesc[i].s.bits.uEnable = 0; +		aRxBufferDesc[i].s.bits.uFull = 0; +	} + +	/* NetRxPackets[ 0 ] is initialized before eth_init is called and never +	   changes. NetRxPackets is 32bit aligned */ +	aRxBufferDesc[0].punSrc = (unsigned int *) NetRxPackets[0]; +	aRxBufferDesc[0].s.bits.uEnable = 1; +	aRxBufferDesc[0].unLen = 1522;	/* as stated in [1] p.307 */ + +	*get_eth_reg_addr (NS9750_ETH_RXAPTR) = +		(unsigned int) &aRxBufferDesc[0]; + +	/* [1] Tab. 221 states less than 5us */ +	*get_eth_reg_addr (NS9750_ETH_EGCR1) |= NS9750_ETH_EGCR1_ERXINIT; +	while (! +	       (*get_eth_reg_addr (NS9750_ETH_EGSR) & NS9750_ETH_EGSR_RXINIT)) +		/* wait for finish */ +		udelay (1); + +	/* @TODO do we need to clear RXINIT? */ +	*get_eth_reg_addr (NS9750_ETH_EGCR1) &= ~NS9750_ETH_EGCR1_ERXINIT; + +	*get_eth_reg_addr (NS9750_ETH_RXFREE) = 0x1; + +	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) +{ +	ulong ulTimeout; + +	DEBUG_FN (DEBUG_TX); + +	/* clear old status values */ +	*get_eth_reg_addr (NS9750_ETH_EINTR) &= +		*get_eth_reg_addr (NS9750_ETH_EINTR) & NS9750_ETH_EINTR_TX_MA; + +	/* prepare Tx Descriptors */ + +	pTxBufferDesc->punSrc = (unsigned int *) pPacket;	/* pPacket is 32bit +								 * aligned */ +	pTxBufferDesc->unLen = nLen; +	/* only 32bit accesses allowed. wrap, full, interrupt and enabled to 1 */ +	pTxBufferDesc->s.unReg = 0xf0000000; +	/* pTxBufferDesc is the first possible buffer descriptor */ +	*get_eth_reg_addr (NS9750_ETH_TXPTR) = 0x0; + +	/* enable processor for next frame */ + +	*get_eth_reg_addr (NS9750_ETH_EGCR2) &= ~NS9750_ETH_EGCR2_TCLER; +	*get_eth_reg_addr (NS9750_ETH_EGCR2) |= NS9750_ETH_EGCR2_TCLER; + +	ulTimeout = get_timer (0); + +	DEBUG_ARGS0 (DEBUG_TX | DEBUG_MINOR, +		     "Waiting for transmission to finish\n"); +	while (! +	       (*get_eth_reg_addr (NS9750_ETH_EINTR) & +		(NS9750_ETH_EINTR_TXDONE | NS9750_ETH_EINTR_TXERR))) { +		/* do nothing, wait for completion */ +		if (get_timer (0) - ulTimeout > TX_TIMEOUT) { +			DEBUG_ARGS0 (DEBUG_TX, "Transmit Timed out\n"); +			return -1; +		} +	} +	DEBUG_ARGS0 (DEBUG_TX | DEBUG_MINOR, "transmitted...\n"); + +	return 0; +} + +/*********************************************************************** + * @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 nLen = 0; +	unsigned int unStatus; + +	unStatus = +		*get_eth_reg_addr (NS9750_ETH_EINTR) & NS9750_ETH_EINTR_RX_MA; + +	if (!unStatus) +		/* no packet available, return immediately */ +		return 0; + +	DEBUG_FN (DEBUG_RX); + +	/* unLen always < max(nLen) and discard checksum */ +	nLen = (int) aRxBufferDesc[0].unLen - 4; + +	/* acknowledge status register */ +	*get_eth_reg_addr (NS9750_ETH_EINTR) = unStatus; + +	aRxBufferDesc[0].unLen = 1522; +	aRxBufferDesc[0].s.bits.uFull = 0; + +	/* Buffer A descriptor available again */ +	*get_eth_reg_addr (NS9750_ETH_RXFREE) |= 0x1; + +	/* NetReceive may call eth_send. Due to a possible bug of the NS9750 we +	 * have to acknowledge the received frame before sending a new one */ +	if (unStatus & NS9750_ETH_EINTR_RXDONEA) +		NetReceive (NetRxPackets[0], nLen); + +	return nLen; +} + +/*********************************************************************** + * @Function: eth_halt + * @Return: n/a + * @Descr: stops the ethernet engine + ***********************************************************************/ + +void eth_halt (void) +{ +	DEBUG_FN (DEBUG_INIT); + +	*get_eth_reg_addr (NS9750_ETH_MAC1) &= ~NS9750_ETH_MAC1_RXEN; +	*get_eth_reg_addr (NS9750_ETH_EGCR1) &= ~(NS9750_ETH_EGCR1_ERX | +						  NS9750_ETH_EGCR1_ERXDMA | +						  NS9750_ETH_EGCR1_ETX | +						  NS9750_ETH_EGCR1_ETXDMA); +} + +/*********************************************************************** + * @Function: ns9750_eth_reset + * @Return: 0 on failure otherwise 1 + * @Descr: resets the ethernet interface and the PHY, + *	   performs auto negotiation or fixed modes + ***********************************************************************/ + +static int ns9750_eth_reset (void) +{ +	DEBUG_FN (DEBUG_MINOR); + +	/* Reset MAC */ +	*get_eth_reg_addr (NS9750_ETH_EGCR1) |= NS9750_ETH_EGCR1_MAC_HRST; +	udelay (5);		/* according to [1], p.322 */ +	*get_eth_reg_addr (NS9750_ETH_EGCR1) &= ~NS9750_ETH_EGCR1_MAC_HRST; + +	/* reset and initialize PHY */ + +	*get_eth_reg_addr (NS9750_ETH_MAC1) &= ~NS9750_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 (NS9750_ETH_MCFG) = +		ns9750_mii_get_clock_divisor (nPhyMaxMdioClock); + +	/* reset PHY */ +	ns9750_mii_write (PHY_COMMON_CTRL, PHY_COMMON_CTRL_RESET); +	ns9750_mii_write (PHY_COMMON_CTRL, 0); + +	/* @TODO check time */ +	udelay (3000);		/* [2] p.70 says at least 300us reset recovery time. But +				   go sure, it didn't worked stable at higher timer +				   frequencies under LxNETES-2.x */ + +	/* MII clock has been setup to default, ns9750_mii_identify_phy should +	   work for all */ + +	if (!ns9750_mii_identify_phy ()) { +		printk (KERN_ERR NS9750_DRIVER_NAME +			": Unsupported PHY, aborting\n"); +		return 0; +	} + +	/* now take the highest MDIO clock possible after detection */ +	*get_eth_reg_addr (NS9750_ETH_MCFG) = +		ns9750_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 */ + +	if ((ucLinkMode & FS_EEPROM_AUTONEG_ENABLE_MASK) == +	    FS_EEPROM_AUTONEG_DISABLE) +		/* use parameters defined */ +		ns9750_link_force (); +	else +		ns9750_link_auto_negotiate (); + +	if (phyDetected == PHY_LXT971A) +		/* set LED2 to link mode */ +		ns9750_mii_write (PHY_LXT971_LED_CFG, +				  PHY_LXT971_LED_CFG_LINK_ACT << +				  PHY_LXT971_LED_CFG_SHIFT_LED2); + +	return 1; +} + +/*********************************************************************** + * @Function: ns9750_link_force + * @Return: void + * @Descr: configures eth and MII to use the link mode defined in + *	   ucLinkMode + ***********************************************************************/ + +static void ns9750_link_force (void) +{ +	unsigned short uiControl; + +	DEBUG_FN (DEBUG_LINK); + +	uiControl = ns9750_mii_read (PHY_COMMON_CTRL); +	uiControl &= ~(PHY_COMMON_CTRL_SPD_MA | +		       PHY_COMMON_CTRL_AUTO_NEG | PHY_COMMON_CTRL_DUPLEX); + +	uiLastLinkStatus = 0; + +	if ((ucLinkMode & FS_EEPROM_AUTONEG_SPEED_MASK) == +	    FS_EEPROM_AUTONEG_SPEED_100) { +		uiControl |= PHY_COMMON_CTRL_SPD_100; +		uiLastLinkStatus |= PHY_LXT971_STAT2_100BTX; +	} else +		uiControl |= PHY_COMMON_CTRL_SPD_10; + +	if ((ucLinkMode & FS_EEPROM_AUTONEG_DUPLEX_MASK) == +	    FS_EEPROM_AUTONEG_DUPLEX_FULL) { +		uiControl |= PHY_COMMON_CTRL_DUPLEX; +		uiLastLinkStatus |= PHY_LXT971_STAT2_DUPLEX_MODE; +	} + +	ns9750_mii_write (PHY_COMMON_CTRL, uiControl); + +	ns9750_link_print_changed (); +	ns9750_link_update_egcr (); +} + +/*********************************************************************** + * @Function: ns9750_link_auto_negotiate + * @Return: void + * @Descr: performs auto-negotation of link. + ***********************************************************************/ + +static void ns9750_link_auto_negotiate (void) +{ +	unsigned long ulStartJiffies; +	unsigned short uiStatus; + +	DEBUG_FN (DEBUG_LINK); + +	/* run auto-negotation */ +	/* define what we are capable of */ +	ns9750_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 */ +	ns9750_mii_write (PHY_COMMON_CTRL, +			  PHY_COMMON_CTRL_AUTO_NEG | +			  PHY_COMMON_CTRL_RES_AUTO); + +	/* wait for completion */ + +	ulStartJiffies = get_ticks (); +	while (get_ticks () < ulStartJiffies + NS9750_MII_NEG_DELAY) { +		uiStatus = ns9750_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 */ +			ns9750_link_print_changed (); +			ns9750_link_update_egcr (); +			return; +		} +	} + +	DEBUG_ARGS0 (DEBUG_LINK, "auto-negotiation timed out\n"); +	/* ignore invalid link settings */ +} + +/*********************************************************************** + * @Function: ns9750_link_update_egcr + * @Return: void + * @Descr: updates the EGCR and MAC2 link status after mode change or + *	   auto-negotation + ***********************************************************************/ + +static void ns9750_link_update_egcr (void) +{ +	unsigned int unEGCR; +	unsigned int unMAC2; +	unsigned int unIPGT; + +	DEBUG_FN (DEBUG_LINK); + +	unEGCR = *get_eth_reg_addr (NS9750_ETH_EGCR1); +	unMAC2 = *get_eth_reg_addr (NS9750_ETH_MAC2); +	unIPGT = *get_eth_reg_addr (NS9750_ETH_IPGT) & ~NS9750_ETH_IPGT_MA; + +	unMAC2 &= ~NS9750_ETH_MAC2_FULLD; +	if ((uiLastLinkStatus & PHY_LXT971_STAT2_DUPLEX_MODE) +	    == PHY_LXT971_STAT2_DUPLEX_MODE) { +		unMAC2 |= NS9750_ETH_MAC2_FULLD; +		unIPGT |= 0x15; /* see [1] p. 339 */ +	} else +		unIPGT |= 0x12; /* see [1] p. 339 */ + +	*get_eth_reg_addr (NS9750_ETH_MAC2) = unMAC2; +	*get_eth_reg_addr (NS9750_ETH_EGCR1) = unEGCR; +	*get_eth_reg_addr (NS9750_ETH_IPGT) = unIPGT; +} + +/*********************************************************************** + * @Function: ns9750_link_print_changed + * @Return: void + * @Descr: checks whether the link status has changed and if so prints + *	   the new mode + ***********************************************************************/ + +static void ns9750_link_print_changed (void) +{ +	unsigned short uiStatus; +	unsigned short uiControl; + +	DEBUG_FN (DEBUG_LINK); + +	uiControl = ns9750_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 = ns9750_mii_read (PHY_COMMON_STAT); + +		if (!(uiStatus & PHY_COMMON_STAT_LNK_STAT)) { +			printk (KERN_WARNING NS9750_DRIVER_NAME +				": link down\n"); +			/* @TODO Linux: carrier_off */ +		} else { +			/* @TODO Linux: carrier_on */ +			if (phyDetected == PHY_LXT971A) { +				uiStatus = ns9750_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 NS9750_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: ns9750_mii_identify_phy + * @Return: 1 if supported PHY has been detected otherwise 0 + * @Descr: checks for supported PHY and prints the IDs. + ***********************************************************************/ + +static char ns9750_mii_identify_phy (void) +{ +	unsigned short uiID1; +	unsigned short uiID2; +	unsigned char *szName; +	char cRes = 0; + +	DEBUG_FN (DEBUG_MII); + +	phyDetected = (PhyType) uiID1 = ns9750_mii_read (PHY_COMMON_ID1); + +	switch (phyDetected) { +	case PHY_LXT971A: +		szName = "LXT971A"; +		uiID2 = ns9750_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 NS9750_ETH_MCFG_CLKS */ + +		uiID2 = 0; +		szName = "unknown"; +		nPhyMaxMdioClock = PHY_MDIO_MAX_CLK; +		phyDetected = PHY_NONE; +	} + +	printk (KERN_INFO NS9750_DRIVER_NAME +		": PHY (0x%x, 0x%x) = %s detected\n", uiID1, uiID2, szName); + +	return cRes; +} + +/*********************************************************************** + * @Function: ns9750_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 ns9750_mii_read (unsigned short uiRegister) +{ +	DEBUG_FN (DEBUG_MII_LOW); + +	/* write MII register to be read */ +	*get_eth_reg_addr (NS9750_ETH_MADR) = +		NS9750_ETH_PHY_ADDRESS << 8 | uiRegister; + +	*get_eth_reg_addr (NS9750_ETH_MCMD) = NS9750_ETH_MCMD_READ; + +	if (!ns9750_mii_poll_busy ()) +		printk (KERN_WARNING NS9750_DRIVER_NAME +			": MII still busy in read\n"); +	/* continue to read */ + +	*get_eth_reg_addr (NS9750_ETH_MCMD) = 0; + +	return (unsigned short) (*get_eth_reg_addr (NS9750_ETH_MRDD)); +} + + +/*********************************************************************** + * @Function: ns9750_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 ns9750_mii_write (unsigned short uiRegister, +			      unsigned short uiData) +{ +	DEBUG_FN (DEBUG_MII_LOW); + +	/* write MII register to be written */ +	*get_eth_reg_addr (NS9750_ETH_MADR) = +		NS9750_ETH_PHY_ADDRESS << 8 | uiRegister; + +	*get_eth_reg_addr (NS9750_ETH_MWTD) = uiData; + +	if (!ns9750_mii_poll_busy ()) { +		printf (KERN_WARNING NS9750_DRIVER_NAME +			": MII still busy in write\n"); +	} +} + + +/*********************************************************************** + * @Function: ns9750_mii_get_clock_divisor + * @Return: the clock divisor that should be used in NS9750_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 ns9750_mii_get_clock_divisor (unsigned int unMaxMDIOClk) +{ +	struct { +		unsigned int unSysClkDivisor; +		unsigned int unClks;	/* field for NS9750_ETH_MCFG_CLKS */ +	} PHYClockDivisors[] = { +		{ +		4, NS9750_ETH_MCFG_CLKS_4}, { +		6, NS9750_ETH_MCFG_CLKS_6}, { +		8, NS9750_ETH_MCFG_CLKS_8}, { +		10, NS9750_ETH_MCFG_CLKS_10}, { +		20, NS9750_ETH_MCFG_CLKS_20}, { +		30, NS9750_ETH_MCFG_CLKS_30}, { +		40, NS9750_ETH_MCFG_CLKS_40} +	}; + +	int nIndexSysClkDiv; +	int nArraySize = +		sizeof (PHYClockDivisors) / sizeof (PHYClockDivisors[0]); +	unsigned int unClks = NS9750_ETH_MCFG_CLKS_40;	/* 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 (AHB_CLK_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: ns9750_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 ns9750_mii_poll_busy (void) +{ +	unsigned int unTimeout = 10000; + +	DEBUG_FN (DEBUG_MII_LOW); + +	while (((*get_eth_reg_addr (NS9750_ETH_MIND) & NS9750_ETH_MIND_BUSY) +		== NS9750_ETH_MIND_BUSY) && unTimeout) +		unTimeout--; + +	return unTimeout; +} + +#endif /* CONFIG_DRIVER_NS9750_ETHERNET */ |