mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-14 17:07:38 +00:00
798 lines
24 KiB
C
798 lines
24 KiB
C
|
/***********************************************************************
|
||
|
*
|
||
|
* 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 */
|