diff options
Diffstat (limited to 'drivers/net/ns7520_eth.c')
-rw-r--r-- | drivers/net/ns7520_eth.c | 859 |
1 files changed, 859 insertions, 0 deletions
diff --git a/drivers/net/ns7520_eth.c b/drivers/net/ns7520_eth.c new file mode 100644 index 0000000..a5a20df --- /dev/null +++ b/drivers/net/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; +} |