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