#include <common.h>
#include <command.h>
#include <net.h>
#include <malloc.h>

#ifdef CONFIG_SMSC911X

#include "911x.h"

static int bus_configured = 0;
/*------------------------------------*/
/*     Phy-related functions          */
/*------------------------------------*/

//Check for not busy
static BOOLEAN MacNotBusy(struct eth_device *dev)
{
	int i = 0;

	//Assuming MacPhyAccessLock has already been acquired

	// wait for MAC not busy, w/ timeout
	for (i = 0; i < 40; i++)
	{
		if ((Lan_GetRegDW(MAC_CSR_CMD) & MAC_CSR_CMD_CSR_BUSY_) == (0UL))
		{
			return TRUE;
		}
	}
	SMSC_WARNING("timeout waiting for MAC not BUSY. MAC_CSR_CMD = 0x%08lX",
		     Lan_GetRegDW(MAC_CSR_CMD));
	return FALSE;
}

//Gets a mac register value
DWORD Mac_GetRegDW(struct eth_device *dev, DWORD dwRegOffset)
{
	DWORD result = 0xFFFFFFFFUL;
	DWORD dwTemp = 0;

	//Assuming MacPhyAccessLock has already been acquired

	// wait until not busy
	if (Lan_GetRegDW(MAC_CSR_CMD) & MAC_CSR_CMD_CSR_BUSY_)
	{
		SMSC_WARNING
		    ("Mac_GetRegDW() failed, MAC already busy at entry");
		goto DONE;
	}
	// send the MAC Cmd w/ offset
	Lan_SetRegDW(MAC_CSR_CMD, ((dwRegOffset & 0x000000FFUL) | MAC_CSR_CMD_CSR_BUSY_ | MAC_CSR_CMD_R_NOT_W_));
	dwTemp = Lan_GetRegDW(BYTE_TEST);	//to flush previous write
	dwTemp = dwTemp;

	// wait for the read to happen, w/ timeout
	if (!MacNotBusy(dev))
	{
		SMSC_WARNING
		    ("Mac_GetRegDW() failed, waiting for MAC not busy after read");
		goto DONE;
	}
	else
	{
		// finally, return the read data
		result = Lan_GetRegDW(MAC_CSR_DATA);
	}
      DONE:
	return result;
}

//Sets a Mac register
void Mac_SetRegDW(struct eth_device *dev, DWORD dwRegOffset, DWORD dwVal)
{
	DWORD dwTemp = 0;

	//Assuming MacPhyAccessLock has already been acquired

	if (Lan_GetRegDW(MAC_CSR_CMD) & MAC_CSR_CMD_CSR_BUSY_)
	{
		SMSC_WARNING
		    ("Mac_SetRegDW() failed, MAC already busy at entry");
		goto DONE;
	}
	// send the data to write
	Lan_SetRegDW(MAC_CSR_DATA, dwVal);

	// do the actual write
	Lan_SetRegDW(MAC_CSR_CMD, ((dwRegOffset & 0x000000FFUL) | MAC_CSR_CMD_CSR_BUSY_));
	dwTemp = Lan_GetRegDW(BYTE_TEST);	//force flush of previous write
	dwTemp = dwTemp;

	// wait for the write to complete, w/ timeout
	if (!MacNotBusy(dev))
	{
		SMSC_WARNING
		    ("Mac_SetRegDW() failed, waiting for MAC not busy after write");
	}
      DONE:
	return;
}

//Gets a phy register
WORD Phy_GetRegW(struct eth_device *dev, DWORD dwRegIndex)
{
	DWORD dwAddr = 0;
	int i = 0;
	WORD result = 0xFFFFU;
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;

	// confirm MII not busy
	if ((Mac_GetRegDW(dev, MII_ACC) & MII_ACC_MII_BUSY_) != 0UL)
	{
		SMSC_WARNING("MII is busy in Phy_GetRegW???");
		result = 0;
		goto DONE;
	}
	// set the address, index & direction (read from PHY)
	dwAddr = (((priv->dwPhyAddress) & 0x1FUL) << 11) | ((dwRegIndex & 0x1FUL) << 6);
	Mac_SetRegDW(dev, MII_ACC, dwAddr);

	// wait for read to complete w/ timeout
	for (i = 0; i < 100; i++)
	{
		// see if MII is finished yet
		if ((Mac_GetRegDW(dev, MII_ACC) & MII_ACC_MII_BUSY_) ==  0UL)
		{
			// get the read data from the MAC & return i
			result = ((WORD) Mac_GetRegDW(dev, MII_DATA));
			goto DONE;
		}
	}
	SMSC_WARNING("timeout waiting for MII write to finish");

      DONE:
	return result;
}

//Sets a phy register
void Phy_SetRegW(struct eth_device *dev, DWORD dwRegIndex, WORD wVal)
{
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;
	DWORD dwAddr = 0;
	int i = 0;

	// confirm MII not busy
	if ((Mac_GetRegDW(dev, MII_ACC) & MII_ACC_MII_BUSY_) != 0UL)
	{
		SMSC_WARNING("MII is busy in Phy_SetRegW???");
		goto DONE;
	}
	// put the data to write in the MAC
	Mac_SetRegDW(dev, MII_DATA, (DWORD) wVal);

	// set the address, index & direction (write to PHY)
	dwAddr = (((priv->dwPhyAddress) & 0x1FUL) << 11) | ((dwRegIndex & 0x1FUL) << 6) | MII_ACC_MII_WRITE_;
	Mac_SetRegDW(dev, MII_ACC, dwAddr);

	// wait for write to complete w/ timeout
	for (i = 0; i < 100; i++)
	{
		// see if MII is finished yet
		if ((Mac_GetRegDW(dev, MII_ACC) & MII_ACC_MII_BUSY_) == 0UL)
		{
			goto DONE;
		}
	}
	SMSC_WARNING("timeout waiting for MII write to finish");
      DONE:
	return;
}

//Sets the link mode
void Phy_SetLink(struct eth_device *dev)
{
	WORD wTemp;

	//Because this is part of the single threaded initialization
	//  path there is no need to acquire the MacPhyAccessLock

	wTemp = Phy_GetRegW(dev, PHY_ANEG_ADV);
	//Advertise all speeds and pause capabilities
	wTemp |= (PHY_ANEG_ADV_PAUSE_ | PHY_ANEG_ADV_SPEED_);
	Phy_SetRegW(dev, PHY_ANEG_ADV, wTemp);

	// begin to establish link
	Phy_SetRegW(dev,
		    PHY_BCR,
		    PHY_BCR_AUTO_NEG_ENABLE_ | PHY_BCR_RESTART_AUTO_NEG_);
}

//gets the current link mode
void Phy_GetLinkMode(struct eth_device *dev)
{
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;
	DWORD result = LINK_OFF;
	WORD wRegVal = 0;
	WORD wRegBSR = 0;

	//Assuming MacPhyAccessLock has already been acquired

	wRegBSR = Phy_GetRegW(dev, PHY_BSR);
	priv->dwLinkSettings = LINK_OFF;
	if (wRegBSR & PHY_BSR_LINK_STATUS_)
	{
		wRegVal = Phy_GetRegW(dev, PHY_BCR);
		if (wRegVal & PHY_BCR_AUTO_NEG_ENABLE_)
		{
			DWORD linkSettings = LINK_AUTO_NEGOTIATE;
			WORD wRegADV = Phy_GetRegW(dev, PHY_ANEG_ADV);
			WORD wRegLPA = Phy_GetRegW(dev, PHY_ANEG_LPA);
			if (wRegADV & PHY_ANEG_ADV_ASYMP_)
			{
				linkSettings |= LINK_ASYMMETRIC_PAUSE;
			}
			if (wRegADV & PHY_ANEG_ADV_SYMP_)
			{
				linkSettings |= LINK_SYMMETRIC_PAUSE;
			}
			if (wRegADV & PHY_ANEG_LPA_100FDX_)
			{
				linkSettings |= LINK_SPEED_100FD;
			}
			if (wRegADV & PHY_ANEG_LPA_100HDX_)
			{
				linkSettings |= LINK_SPEED_100HD;
			}
			if (wRegADV & PHY_ANEG_LPA_10FDX_)
			{
				linkSettings |= LINK_SPEED_10FD;
			}
			if (wRegADV & PHY_ANEG_LPA_10HDX_)
			{
				linkSettings |= LINK_SPEED_10HD;
			}
			priv->dwLinkSettings = linkSettings;
			wRegLPA &= wRegADV;
			if (wRegLPA & PHY_ANEG_LPA_100FDX_)
			{
				result = LINK_SPEED_100FD;
			}
			else if (wRegLPA & PHY_ANEG_LPA_100HDX_)
			{
				result = LINK_SPEED_100HD;
			}
			else if (wRegLPA & PHY_ANEG_LPA_10FDX_)
			{
				result = LINK_SPEED_10FD;
			}
			else if (wRegLPA & PHY_ANEG_LPA_10HDX_)
			{
				result = LINK_SPEED_10HD;
			}
		}
		else
		{
			if (wRegVal & PHY_BCR_SPEED_SELECT_)
			{
				if (wRegVal & PHY_BCR_DUPLEX_MODE_)
				{
					priv->dwLinkSettings = result = LINK_SPEED_100FD;
				}
				else
				{
					priv->dwLinkSettings = result = LINK_SPEED_100HD;
				}
			}
			else
			{
				if (wRegVal & PHY_BCR_DUPLEX_MODE_)
				{
					priv->dwLinkSettings = result = LINK_SPEED_10FD;
				}
				else
				{
					priv->dwLinkSettings = result = LINK_SPEED_10HD;
				}
			}
		}
	}
	priv->dwLinkSpeed = result;
}

//Initializes the phy
BOOLEAN Phy_Initialize(struct eth_device *dev, DWORD dwPhyAddr)
{
	BOOLEAN result = FALSE;
#ifndef USE_PHY_WORK_AROUND
	WORD wTemp = 0;
#endif
	WORD wPhyId1 = 0;
	WORD wPhyId2 = 0;
#ifndef USE_PHY_WORK_AROUND
	DWORD dwLoopCount = 0;
#endif
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;

	SMSC_TRACE("-->Phy_Initialize");

	if (dwPhyAddr != 0xFFFFFFFFUL)
	{
		switch (priv->dwIdRev & 0xFFFF0000)
		{
			case 0x01170000UL:
			case 0x01150000UL:
				{
					DWORD dwHwCfg = Lan_GetRegDW(HW_CFG);
					if (dwHwCfg & HW_CFG_EXT_PHY_DET_)
					{
						//External phy is requested, supported, and detected
						//Attempt to switch
						//NOTE: Assuming Rx and Tx are stopped
						//  because Phy_Initialize is called before 
						//  Rx_Initialize and Tx_Initialize

						//Disable phy clocks to the mac
						dwHwCfg &= (~HW_CFG_PHY_CLK_SEL_);
						dwHwCfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
						Lan_SetRegDW(HW_CFG, dwHwCfg);
						udelay(10);	//wait for clocks to acutally stop

						//switch to external phy
						dwHwCfg |= HW_CFG_EXT_PHY_EN_;
						Lan_SetRegDW(HW_CFG, dwHwCfg);

						//Enable phy clocks to the mac
						dwHwCfg &= (~HW_CFG_PHY_CLK_SEL_);
						dwHwCfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
						Lan_SetRegDW(HW_CFG, dwHwCfg);
						udelay(10);	//wait for clocks to actually start

						dwHwCfg |= HW_CFG_SMI_SEL_;
						Lan_SetRegDW(HW_CFG, dwHwCfg);

						//Because this is part of the single threaded initialization
						//  path there is no need to acquire the MacPhyAccessLock
						if (dwPhyAddr <= 31)
						{
							//only check the phy address specified
							priv->dwPhyAddress = dwPhyAddr;
							wPhyId1 = Phy_GetRegW(dev, PHY_ID_1);
							wPhyId2 = Phy_GetRegW(dev, PHY_ID_2);
						}
						else
						{
							//auto detect phy
							DWORD address = 0;
							for (address = 0; address <= 31; address++)
							{
								priv->dwPhyAddress = address;
								wPhyId1 = Phy_GetRegW(dev, PHY_ID_1);
								wPhyId2 = Phy_GetRegW (dev, PHY_ID_2);
								if ((wPhyId1 != 0xFFFFU) || (wPhyId2 != 0xFFFFU))
								{
									SMSC_TRACE
									    ("Detected Phy at address = 0x%02lX = %ld",
									     address,
									     address);
									break;
								}
							}
							if (address >= 32)
							{
								SMSC_WARNING
								    ("Failed to auto detect external phy");
							}
						}
						if ((wPhyId1 == 0xFFFFU) && (wPhyId2 == 0xFFFFU))
						{
							SMSC_WARNING("External Phy is not accessable");
							SMSC_WARNING("  using internal phy instead");
							//revert back to interal phy settings.

							//Disable phy clocks to the mac
							dwHwCfg &= (~HW_CFG_PHY_CLK_SEL_);
							dwHwCfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
							Lan_SetRegDW(HW_CFG, dwHwCfg);
							udelay(10);	//wait for clocks to actually stop

							//switch to internal phy
							dwHwCfg &= (~HW_CFG_EXT_PHY_EN_);
							Lan_SetRegDW(HW_CFG, dwHwCfg);

							//Enable phy clocks to the mac
							dwHwCfg &= (~HW_CFG_PHY_CLK_SEL_);
							dwHwCfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_;
							Lan_SetRegDW(HW_CFG, dwHwCfg);
							udelay(10);	//wait for clocks to actually start

							dwHwCfg &= (~HW_CFG_SMI_SEL_);
							Lan_SetRegDW(HW_CFG, dwHwCfg);
							goto USE_INTERNAL_PHY;
						}
						else
						{
							SMSC_TRACE("Successfully switched to external phy");
#ifdef USE_LED1_WORK_AROUND
							priv->NotUsingExtPhy = 0;
#endif
						}
					}
					else
					{
						SMSC_WARNING("No External Phy Detected");
						SMSC_WARNING("  using internal phy instead");
						goto USE_INTERNAL_PHY;
					}
				};
				break;
			default:
				SMSC_WARNING("External Phy is not supported");
				SMSC_WARNING("  using internal phy instead");
				goto USE_INTERNAL_PHY;
		}
	}
	else
	{
	      USE_INTERNAL_PHY:
		priv->dwPhyAddress = 1;
#ifdef USE_LED1_WORK_AROUND
		priv->NotUsingExtPhy = 1;
#endif
	}

	//Because this is part of the single threaded initialization
	//  path there is no need to acquire the MacPhyAccessLock
	wPhyId1 = Phy_GetRegW(dev, PHY_ID_1);
	wPhyId2 = Phy_GetRegW(dev, PHY_ID_2);
	if ((wPhyId1 == 0xFFFFU) && (wPhyId2 == 0xFFFFU)) {
		SMSC_WARNING("Phy Not detected");
		goto DONE;
	}

	priv->dwLinkSpeed = LINK_OFF;
	priv->dwLinkSettings = LINK_OFF;

	//reset the PHY
#ifdef USE_PHY_WORK_AROUND
	Phy_Reset(dev);
	if (!Phy_LoopBackTest(dev))
	{
		SMSC_WARNING("Failed Loop Back Test");
		goto DONE;
	}
	else
	{
		SMSC_TRACE("Passed Loop Back Test");
	}
#else
	Phy_SetRegW(dev, PHY_BCR, PHY_BCR_RESET_);
	dwLoopCount = 100000;
	do
	{
		udelay(10);
		wTemp = Phy_GetRegW(dev, PHY_BCR);
		dwLoopCount--;
	} while ((dwLoopCount > 0) && (wTemp & PHY_BCR_RESET_));
	if (wTemp & PHY_BCR_RESET_)
	{
		SMSC_WARNING("PHY reset failed to complete.");
		goto DONE;
	}
#endif				//not USE_PHY_WORK_AROUND

	Phy_SetLink(dev);

#if 0
// TODO: we may need link check implemented. Here is Linux check polling.
	init_timer(&(priv->LinkPollingTimer));
	priv->LinkPollingTimer.function = Phy_CheckLink;
	priv->LinkPollingTimer.data = (unsigned long)privateData;
	priv->LinkPollingTimer.expires = jiffies + HZ;
	add_timer(&(priv->LinkPollingTimer));
#endif
	result = TRUE;
      DONE:
	SMSC_TRACE("<--Phy_Initialize, result=%s", result ? "TRUE" : "FALSE");
	return result;
}

//Update link mode if any thing has changed
void Phy_UpdateLinkMode(struct eth_device *dev)
{
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;
	DWORD dwOldLinkSpeed = priv->dwLinkSpeed;

	Phy_GetLinkMode(dev);

	if (dwOldLinkSpeed != (priv->dwLinkSpeed))
	{
		if (priv->dwLinkSpeed != LINK_OFF)
		{
			DWORD dwRegVal = 0;
			switch (priv->dwLinkSpeed)
			{
				case LINK_SPEED_10HD:
					SMSC_TRACE("Link is now UP at 10Mbps HD");
					break;
				case LINK_SPEED_10FD:
					SMSC_TRACE("Link is now UP at 10Mbps FD");
					break;
				case LINK_SPEED_100HD:
					SMSC_TRACE("Link is now UP at 100Mbps HD");
					break;
				case LINK_SPEED_100FD:
						SMSC_TRACE("Link is now UP at 100Mbps FD");
					break;
				default:
					SMSC_WARNING
					    ("Link is now UP at Unknown Link Speed, dwLinkSpeed=0x%08lX",
					     priv->dwLinkSpeed);
					break;
			}

			dwRegVal = Mac_GetRegDW(dev, MAC_CR);
			dwRegVal &= ~(MAC_CR_FDPX_ | MAC_CR_RCVOWN_);
			switch (priv->dwLinkSpeed)
			{
				case LINK_SPEED_10HD:
				case LINK_SPEED_100HD:
					dwRegVal |= MAC_CR_RCVOWN_;
					break;
				case LINK_SPEED_10FD:
				case LINK_SPEED_100FD:
					dwRegVal |= MAC_CR_FDPX_;
					break;
				default:
					SMSC_WARNING
					    ("Unknown Link Speed, dwLinkSpeed=0x%08lX",
					     priv->dwLinkSpeed);
					break;
			}

			Mac_SetRegDW(dev, MAC_CR, dwRegVal);

			if (priv->dwLinkSettings & LINK_AUTO_NEGOTIATE)
			{
				WORD linkPartner = 0;
				WORD localLink = 0;
				localLink = Phy_GetRegW(dev, 4);
				linkPartner = Phy_GetRegW(dev, 5);
				switch (priv->dwLinkSpeed)
				{
					case LINK_SPEED_10FD:
					case LINK_SPEED_100FD:
						if (((localLink & linkPartner) & ((WORD) 0x0400U)) != ((WORD) 0U))
						{
							//Enable PAUSE receive and transmit
							Mac_SetRegDW(dev, FLOW, 0xFFFF0002UL);
							Lan_SetBitsDW(AFC_CFG, 0x0000000FUL);
						}
						else if (((localLink & ((WORD) 0x0C00U)) == ((WORD) 0x0C00U))
							&& ((linkPartner & ((WORD) 0x0C00U)) == ((WORD) 0x0800U)))
						{
							//Enable PAUSE receive, disable PAUSE transmit
							Mac_SetRegDW(dev, FLOW, 0xFFFF0002UL);
							Lan_ClrBitsDW(AFC_CFG, 0x0000000FUL);
						}
						else
						{
							//Disable PAUSE receive and transmit
							Mac_SetRegDW(dev, FLOW, 0UL);
							Lan_ClrBitsDW(AFC_CFG, 0x0000000FUL);
						};
						break;
					case LINK_SPEED_10HD:
					case LINK_SPEED_100HD:
						Mac_SetRegDW(dev, FLOW, 0UL);
						Lan_SetBitsDW(AFC_CFG, 0x0000000FUL);
						break;
					default:
						SMSC_WARNING
						    ("Unknown Link Speed, dwLinkSpeed=0x%08lX\n",
						     priv->dwLinkSpeed);
						break;
				}

				SMSC_TRACE("LAN911x: %s,%s,%s,%s,%s,%s",
					   (localLink & PHY_ANEG_ADV_ASYMP_) ?
					   "ASYMP" : "     ",
					   (localLink & PHY_ANEG_ADV_SYMP_) ?
					   "SYMP " : "     ",
					   (localLink & PHY_ANEG_ADV_100F_) ?
					   "100FD" : "     ",
					   (localLink & PHY_ANEG_ADV_100H_) ?
					   "100HD" : "     ",
					   (localLink & PHY_ANEG_ADV_10F_) ?
					   "10FD " : "     ",
					   (localLink & PHY_ANEG_ADV_10H_) ?
					   "10HD " : "     ");

				SMSC_TRACE("Partner: %s,%s,%s,%s,%s,%s",
					   (linkPartner & PHY_ANEG_LPA_ASYMP_) ?
					   "ASYMP" : "     ",
					   (linkPartner & PHY_ANEG_LPA_SYMP_) ?
					   "SYMP " : "     ",
					   (linkPartner & PHY_ANEG_LPA_100FDX_)
					   ? "100FD" : "     ",
					   (linkPartner & PHY_ANEG_LPA_100HDX_)
					   ? "100HD" : "     ",
					   (linkPartner & PHY_ANEG_LPA_10FDX_) ?
					   "10FD " : "     ",
					   (linkPartner & PHY_ANEG_LPA_10HDX_) ?
					   "10HD " : "     ");
			}
			else
			{
				switch (priv->dwLinkSpeed)
				{
					case LINK_SPEED_10HD:
					case LINK_SPEED_100HD:
						Mac_SetRegDW(dev, FLOW, 0x0UL);
						Lan_SetBitsDW(AFC_CFG, 0x0000000FUL);
						break;
					default:
						Mac_SetRegDW(dev, FLOW, 0x0UL);
						Lan_ClrBitsDW(AFC_CFG, 0x0000000FUL);
						break;
				}
			}

#ifdef USE_LED1_WORK_AROUND
			if ((g_GpioSettingOriginal & GPIO_CFG_LED1_EN_) && priv->NotUsingExtPhy)
			{
				// Restore orginal GPIO configuration
				g_GpioSetting = g_GpioSettingOriginal;
				Lan_SetRegDW(GPIO_CFG, g_GpioSetting);
			}
#endif				// USE_LED1_WORK_AROUND
		}
		else
		{
			SMSC_TRACE("Link is now DOWN");

			Mac_SetRegDW(dev, FLOW, 0UL);
			Lan_ClrBitsDW(AFC_CFG, 0x0000000FUL);
#ifdef USE_LED1_WORK_AROUND
			// Check global setting that LED1 usage is 10/100 indicator
			g_GpioSetting = Lan_GetRegDW(GPIO_CFG);
			if ((g_GpioSetting & GPIO_CFG_LED1_EN_) && priv->NotUsingExtPhy)
			{
				//Force 10/100 LED off, after saving orginal GPIO configuration
				g_GpioSettingOriginal = g_GpioSetting;

				g_GpioSetting &= ~GPIO_CFG_LED1_EN_;
				g_GpioSetting |= (GPIO_CFG_GPIOBUF0_ | GPIO_CFG_GPIODIR0_ | GPIO_CFG_GPIOD0_);
				Lan_SetRegDW(GPIO_CFG, g_GpioSetting);
			}
#endif				// USE_LED1_WORK_AROUND
		}
	}
}

//TODO: no link check at the moment. Here is Linux implementation.
#if 0
//entry point for the link poller
void Phy_CheckLink(unsigned long ptr)
{
	PPRIVATE_DATA privateData = (PPRIVATE_DATA) ptr;
	if (privateData == NULL) {
		SMSC_WARNING("Phy_CheckLink(ptr==0)");
		return;
	}
	//must call this twice
	Phy_UpdateLinkMode(privateData);
	Phy_UpdateLinkMode(privateData);

	if (!(priv->StopLinkPolling)) {
		priv->LinkPollingTimer.expires = jiffies + HZ;
		add_timer(&(priv->LinkPollingTimer));
	}
}
#endif


#define MAX_INSTANCES	8

DWORD mac_addr_hi16 = 0xFFFFFFFFUL;
DWORD mac_addr_lo32 = 0xFFFFFFFFUL;

#ifdef USE_DEBUG
DWORD debug_mode = 0x3UL;
#else
DWORD debug_mode = 0x0UL;
#endif

DWORD phy_addr = 0xFFFFFFFFUL;
#define TX_FIFO_LOW_THRESHOLD	(1600)

//initializes the Transmitter
void Tx_Initialize(struct eth_device *dev)
{
	DWORD dwRegVal = 0;

	dwRegVal = Lan_GetRegDW(HW_CFG);
	dwRegVal &= HW_CFG_TX_FIF_SZ_;
	dwRegVal |= HW_CFG_SF_;
	Lan_SetRegDW(HW_CFG, dwRegVal);

	Lan_SetBitsDW(FIFO_INT, 0xFFFF0000UL);

	Lan_SetBitsDW(INT_EN, INT_EN_TDFA_EN_);

	//Because this is part of the single threaded initialization
	//  path there is no need to acquire the MacPhyAccessLock
	{
		DWORD dwMacCr = Mac_GetRegDW(dev, MAC_CR);
		dwMacCr |= (MAC_CR_TXEN_ | MAC_CR_HBDIS_);
		Mac_SetRegDW(dev, MAC_CR, dwMacCr);
		Lan_SetRegDW(TX_CFG, TX_CFG_TX_ON_);
	}
}

//Writes a packet to the TX_DATA_FIFO
static void Tx_WriteFifo(struct eth_device *dev, DWORD * pdwBuf, DWORD dwDwordCount)
{
	DWORD i = 0;
	DWORD val;
	volatile DWORD *pdwReg;

	pdwReg = (volatile DWORD *)(dev->iobase + TX_DATA_FIFO);

	while (dwDwordCount)
	{
		unsigned short *dst = (unsigned short *)pdwBuf;

		if (i == 0)
		{
			val = ((dst[0]) << 16) | 0xFFFFUL;
			*pdwReg = val;
			i++;
		}
		else
		{
			val = ((dst[2]) << 16) | ((dst[1]));
			*pdwReg = val;
			pdwBuf++;
		}
		dwDwordCount--;
	}
}

//Gets the number of Tx Statuses in the fifo
static DWORD Tx_GetTxStatusCount(struct eth_device *dev)
{
	DWORD result = 0;
	result = Lan_GetRegDW(TX_FIFO_INF);
	result &= TX_FIFO_INF_TSUSED_;
	result >>= 16;
	return result;
}

//gets a tx status out of the status fifo
static DWORD Tx_CompleteTx(struct eth_device *dev)
{
	DWORD result = 0;

	result = Lan_GetRegDW(TX_FIFO_INF);

	result &= TX_FIFO_INF_TSUSED_;
	if (result != 0x00000000UL)
	{
		result = Lan_GetRegDW(TX_STATUS_FIFO);
	}
	else
	{
		result = 0;
	}

	return result;
}

//reads tx statuses and increments counters where necessary
void Tx_UpdateTxCounters(struct eth_device *dev)
{
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;
	DWORD dwTxStatus = 0;
	while ((dwTxStatus = Tx_CompleteTx(dev)) != 0)
	{
		if (dwTxStatus & 0x80000000UL)
		{
			SMSC_WARNING("Packet tag reserved bit is high");
			//In this driver the packet tag is used as the packet
			//  length. Since a packet length can never reach
			//  the size of 0x8000, I made this bit reserved
			//  so if I ever decided to use packet tracking 
			//  tags then those tracking tags would set the 
			//  reserved bit. And I would use this control path
			//  to look up the packet and perhaps free it.
			//  As you can see I never persued this idea.
			//  because it never provided any benefit in this
			//  linux environment.
			//But it is worth noting that the "reserved bit"
			//  in the warning above does not reference a
			//  hardware defined reserved bit but rather a 
			//  driver defined reserved bit. 
		}
		else
		{
			if (dwTxStatus & 0x00008000UL)
			{
				priv->tx_errors++;
			}
			else
			{
				priv->tx_packets++;
				priv->tx_bytes += (dwTxStatus >> 16);
			}
			if (dwTxStatus & 0x00000100UL)
			{
				priv->collisions += 16;
				priv->tx_aborted_errors += 1;
			}
			else
			{
				priv->collisions += ((dwTxStatus >> 3) & 0xFUL);
			}
			if (dwTxStatus & 0x00000800UL)
			{
				priv->tx_carrier_errors += 1;
			}
			if (dwTxStatus & 0x00000200UL)
			{
				priv->collisions++;
				priv->tx_aborted_errors++;
			}
		}
	}
}

//Sends a packet from u-boot to ethernet controller
static int smsc911x_send(struct eth_device *dev, volatile void *data, int len)
{
	DWORD dwFreeSpace = 0;
	DWORD dwTxCmdA = 0;
	DWORD dwTxCmdB = 0;
	SMSC_TRACE("eth_send: len=%d\n", len);
	dwFreeSpace = Lan_GetRegDW(TX_FIFO_INF);
	dwFreeSpace &= TX_FIFO_INF_TDFREE_;
	if (dwFreeSpace < TX_FIFO_LOW_THRESHOLD) {
		SMSC_WARNING("Tx Data Fifo Low, space available = %ld",
			     dwFreeSpace);
	}
	dwTxCmdA =
//              ((((DWORD)(data))&0x03UL)<<16) | //DWORD alignment adjustment
	    (2 << 16) |
	    TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_ | ((DWORD) (len));
	dwTxCmdB = (((DWORD) (len)) << 16) | ((DWORD) (len));
	Lan_SetRegDW(TX_DATA_FIFO, dwTxCmdA);
	Lan_SetRegDW(TX_DATA_FIFO, dwTxCmdB);
	SMSC_TRACE("cwA:0x%8x cmB:0x%8x\n", dwTxCmdA, dwTxCmdB);
	Tx_WriteFifo(dev,
		     (DWORD *) (((DWORD) (data)) & 0xFFFFFFFCUL),
		     (((DWORD) (len)) + 3 + 2 +
		     (((DWORD) (data)) & 0x03UL)) >> 2);
	dwFreeSpace -= (len + 32);

	if (Tx_GetTxStatusCount(dev) >= 30)
	{
		Tx_UpdateTxCounters(dev);
	}
	if (dwFreeSpace < TX_FIFO_LOW_THRESHOLD)
	{
		SMSC_WARNING("TX_FIFO_LOW_THRESHOLD\n");
	}

	return 0;
}

//initializes the receiver
void Rx_Initialize(struct eth_device *dev)
{
	DWORD dwMacCr = 0;

	Lan_SetRegDW(RX_CFG, 0x00000000UL);	// we don't need ip align

	dwMacCr = Mac_GetRegDW(dev, MAC_CR);
	dwMacCr |= MAC_CR_RXEN_;
	Mac_SetRegDW(dev, MAC_CR, dwMacCr);

	Lan_ClrBitsDW(FIFO_INT, 0x000000FFUL);
}

//reads a packet out of the RX_DATA_FIFO
static void Rx_ReadFifo(struct eth_device *dev, DWORD * pdwBuf, DWORD dwDwordCount)
{
	const volatile DWORD *const pdwReg = (const volatile DWORD * const)(dev->iobase + RX_DATA_FIFO);

	while (dwDwordCount)
	{
		*pdwBuf = *pdwReg;
		pdwBuf++;
		dwDwordCount--;
	}
}

//Gets the next rx status
static DWORD Rx_PopRxStatus(struct eth_device *dev)
{
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;
	DWORD result = Lan_GetRegDW(RX_FIFO_INF);

	if ((priv->RxCongested == FALSE) ||
	    ((priv->RxCongested == TRUE) &&
	    ((result & 0x00FF0000UL) == 0UL)))
	{
		if (result & 0x00FF0000UL)
		{
			//Rx status is available, read it
			result = Lan_GetRegDW(RX_STATUS_FIFO);
		}
		else
		{
			result = 0;
		}
	}
	else
	{
		result = 0;
	}
	return result;
}

//Increments the Rx error counters
void Rx_CountErrors(struct eth_device *dev, DWORD dwRxStatus)
{
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;
	BOOLEAN crcError = FALSE;
	if (dwRxStatus & 0x00008000UL)
	{
		priv->rx_errors++;
		if (dwRxStatus & 0x00000002UL)
		{
			priv->rx_crc_errors++;
			crcError = TRUE;
		}
	}
	if (!crcError)
	{
		if (dwRxStatus & 0x00001020UL)
		{
			//Frame type indicates length, and length error is set
			priv->rx_length_errors++;
		}
		if (dwRxStatus & RX_STS_MCAST_)
		{
			priv->multicast++;
		}
	}
}

//This function is used to quickly dump bad packets
void Rx_FastForward(struct eth_device *dev, DWORD dwDwordCount)
{
	if (dwDwordCount >= 4)
	{
		DWORD dwTimeOut = 500;
		Lan_SetRegDW(RX_DP_CTRL, RX_DP_CTRL_RX_FFWD_);
		while ((dwTimeOut) && (Lan_GetRegDW(RX_DP_CTRL) & RX_DP_CTRL_RX_FFWD_))
		{
			udelay(1);
			dwTimeOut--;
		}
		if (dwTimeOut == 0)
		{
			SMSC_WARNING
			    ("timed out waiting for RX FFWD to finish, RX_DP_CTRL=0x%08lX",
			     Lan_GetRegDW(RX_DP_CTRL));
		}
	}
	else
	{
		while (dwDwordCount)
		{
			DWORD dwTemp = Lan_GetRegDW(RX_DATA_FIFO);
			dwTemp = dwTemp;
			dwDwordCount--;
		}
	}
}

//This is the main function for reading packets out of the LAN911x
int Rx_ProcessPackets(struct eth_device *dev)
{
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;
	DWORD dwRxStatus = 0;
	priv->RxCongested = FALSE;
	DWORD dwTotalLenth = 0;

	while ((dwRxStatus = Rx_PopRxStatus(dev)) != 0) {
		DWORD dwPacketLength = ((dwRxStatus & 0x3FFF0000UL) >> 16);
		Rx_CountErrors(dev, dwRxStatus);

		if ((dwRxStatus & RX_STS_ES_) == 0) {
			//update counters
			priv->rx_packets++;
			priv->rx_bytes += (dwPacketLength - 4);

			Rx_ReadFifo(dev, ((DWORD *) (NetRxPackets[0])), (dwPacketLength + 3) >> 2);

			NetReceive(NetRxPackets[0], dwPacketLength);
			dwTotalLenth += dwPacketLength;
			continue;
		}
		//if we get here then the packet is to be read
		//  out of the fifo and discarded
		dwPacketLength += (2 + 3);
		dwPacketLength >>= 2;
		SMSC_TRACE("Fast forvarding\n");
		Rx_FastForward(dev, dwPacketLength);
	}
	priv->rx_dropped += Lan_GetRegDW(RX_DROP);
	//  TODO :currently net.c doesn't use the return value? 
	return dwTotalLenth;
}

#ifdef USE_LED1_WORK_AROUND
volatile DWORD g_GpioSetting = 0x00000000UL;
volatile DWORD g_GpioSettingOriginal = 0x00000000UL;
#endif

BOOLEAN Lan_Initialize(struct eth_device *dev, DWORD dwIntCfg)
{
	BOOLEAN result = FALSE;
	DWORD dwTimeOut = 0;
	DWORD dwTemp = 0;
	SMSC_TRACE("-->Lan_Initialize(dwIntCfg=0x%08lX)", dwIntCfg);

	//Reset the LAN911x
	Lan_SetRegDW(HW_CFG, HW_CFG_SRST_);
	dwTimeOut = 10;
	do
	{
		udelay(10);
		dwTemp = Lan_GetRegDW(HW_CFG);
		dwTimeOut--;
	} while ((dwTimeOut > 0) && (dwTemp & HW_CFG_SRST_));
	if (dwTemp & HW_CFG_SRST_)
	{
		SMSC_WARNING("  Failed to complete reset.");
		goto DONE;
	}

	Lan_SetRegDW(HW_CFG, 0x00050000UL);
	Lan_SetRegDW(AFC_CFG, 0x006E3740UL);

	//make sure EEPROM has finished loading before setting GPIO_CFG
	dwTimeOut = 50;
	while ((dwTimeOut > 0) && (Lan_GetRegDW(E2P_CMD) & E2P_CMD_EPC_BUSY_))
	{
		udelay(10);
		dwTimeOut--;
	}
	if (dwTimeOut == 0)
	{
		SMSC_WARNING("Lan_Initialize: Timed out waiting for EEPROM busy bit to clear\n");
	}

	if (debug_mode & 0x04UL)
	{
		Lan_SetRegDW(GPIO_CFG, 0x00670700UL);
	} else {
		Lan_SetRegDW(GPIO_CFG, 0x70070000UL);
	}

	//initialize interrupts
	Lan_SetRegDW(INT_EN, 0);
	Lan_SetRegDW(INT_STS, 0xFFFFFFFFUL);
	Lan_SetRegDW(INT_CFG, dwIntCfg);

	result = TRUE;

      DONE:
	SMSC_TRACE("<--Lan_Initialize");
	return result;
}

/*********************************************************
 *          eth_init
 ********************************************************/
static int smsc911x_init(struct eth_device *dev, bd_t *bd)
{
	struct smsc911x_private *priv = (struct smsc911x_private*)dev->priv;
	SMSC_TRACE("eth_init()\n");
	int result = -1;
	if (bd == NULL) {
		SMSC_WARNING("eth_init(bd==NULL)");
		goto DONE;
	}

	{
		int attempt;
		unsigned long chip_id;
		for (attempt = 2; attempt > 0; attempt--)
		{
			/*configure LAN 911x endiannes */
			Lan_SetRegDW(0x98, 0xffffffff);

			chip_id = Lan_GetRegDW(0x50) >> 16;
			if (chip_id >= 0x0115 && chip_id <= 0x0118)
			{
				SMSC_TRACE("Found LAN9%03x chip\n", chip_id);
				attempt = 3;
				bus_configured = 1;
				break;
			}
		}

		if (attempt != 3) {
			SMSC_TRACE("Can not find LAN911x chip");
			result = -1;
			goto DONE;
		}
	}

	priv->dwIdRev = Lan_GetRegDW(ID_REV);
	if (HIWORD(priv->dwIdRev) == LOWORD(priv->dwIdRev))
	{
		//this may mean the chip is set for 32 bit 
		//  while the bus is reading as 16 bit
	      UNKNOWN_CHIP:
		SMSC_WARNING("LAN911x NOT Identified, dwIdRev==0x%08lX", priv->dwIdRev);
		result = -1;
		goto DONE;
	}
	switch (priv->dwIdRev & 0xFFFF0000UL)
	{
		case 0x01180000UL:
			SMSC_TRACE("LAN9118 identified, dwIdRev==0x%08lX", priv->dwIdRev);
			break;
		case 0x01170000UL:
			SMSC_TRACE("LAN9117 identified, dwIdRev==0x%08lX", priv->dwIdRev);
			break;
		case 0x01160000UL:
			SMSC_TRACE("LAN9116 identified, dwIdRev==0x%08lX", priv->dwIdRev);
			break;
		case 0x01150000UL:
			SMSC_TRACE("LAN9115 identified, dwIdRev==0x%08lX", priv->dwIdRev);
			break;
		case 0x01120000UL:
			SMSC_TRACE("LAN9112 identified, dwIdRev==0x%08lX", priv->dwIdRev);
			break;
		default:
			goto UNKNOWN_CHIP;
	}

	{
		//initialize the LAN911x
		{
			const DWORD dwIntCfg = (((DWORD) 22UL) << 24);	//set interrupt deassertion to 220uS
			if (!Lan_Initialize(dev, dwIntCfg)) {
				goto DONE;
			}
		}
		{		//get mac address
			DWORD dwHigh16 = 0;
			DWORD dwLow32 = 0;

			if (mac_addr_hi16 == 0xFFFFFFFF)
			{
				dwHigh16 = Mac_GetRegDW(dev, ADDRH);
				dwLow32  = Mac_GetRegDW(dev, ADDRL);
				if ((dwHigh16 == 0x0000FFFFUL) && (dwLow32 == 0xFFFFFFFF))
				{
                                       /* no mac address already set. Set it once */
					dwLow32  = bd->bi_enetaddr[0] | bd->bi_enetaddr[1] << 8 | bd->bi_enetaddr[2] << 16 | bd->bi_enetaddr[3] << 24;
					dwHigh16 = bd->bi_enetaddr[4] | bd->bi_enetaddr[5] << 8; 
 
					Mac_SetRegDW(dev, ADDRH, dwHigh16);
					Mac_SetRegDW(dev, ADDRL, dwLow32);
					SMSC_TRACE
					    ("Mac Address is set by default to 0x%04lX%08lX",
					     dwHigh16, dwLow32);
				} else {
					SMSC_TRACE
					    ("Mac Address is read from LAN911x as 0x%04lX%08lX",
					     dwHigh16, dwLow32);
				}
			} else {
				dwHigh16 = mac_addr_hi16;
				dwLow32 = mac_addr_lo32;
				Mac_SetRegDW(dev, ADDRH, dwHigh16);
				Mac_SetRegDW(dev, ADDRL, dwLow32);
				SMSC_TRACE
				    ("Mac Address is set by parameter to 0x%04lX%08lX",
				     dwHigh16, dwLow32);
			}
			bd->bi_enetaddr[0] = LOBYTE(LOWORD(dwLow32));
			bd->bi_enetaddr[1] = HIBYTE(LOWORD(dwLow32));
			bd->bi_enetaddr[2] = LOBYTE(HIWORD(dwLow32));
			bd->bi_enetaddr[3] = HIBYTE(HIWORD(dwLow32));
			bd->bi_enetaddr[4] = LOBYTE(LOWORD(dwHigh16));
			bd->bi_enetaddr[5] = HIBYTE(LOWORD(dwHigh16));
		}

		if (!Phy_Initialize(dev, phy_addr)) {
			SMSC_WARNING("Failed to initialize Phy");
			result = -1;
			goto DONE;
		}
		//set multicast mode - local MAC addresses only
		{
			DWORD dwMacCr = 0;
			dwMacCr = Mac_GetRegDW(dev, MAC_CR);
			dwMacCr &= (~MAC_CR_PRMS_);
			dwMacCr &= (~MAC_CR_MCPAS_);
			dwMacCr &= (~MAC_CR_HPFILT_);
			Mac_SetRegDW(dev, HASHH, 0);
			Mac_SetRegDW(dev, HASHL, 0);
			Mac_SetRegDW(dev, MAC_CR, dwMacCr);
		}
		Tx_Initialize(dev);
		Rx_Initialize(dev);

		result = 0;

	      DONE:

		SMSC_TRACE("<--eth_init(), result=%d", result);

	}
	printf(".\n");
	return result;

}

static void smsc911x_halt(struct eth_device *dev)
{
	SMSC_TRACE("eth_halt\n");
#if 0
	DWORD dwMacCr;
        privateData = &pd;
	if (!bus_configured)
		return;
	Lan_SetBitsDW(TX_CFG, TX_CFG_STOP_TX_);
	dwMacCr = Mac_GetRegDW(privateData, MAC_CR);
	dwMacCr &= ~MAC_CR_RXEN_;
	Mac_SetRegDW(privateData, MAC_CR, dwMacCr);
#endif
	return;

}

static int smsc911x_rx(struct eth_device *dev)
{
	int ret;
	SMSC_TRACE("eth_rx()\n");
	ret = Rx_ProcessPackets(dev);

	return ret;
}

int smsc911x_initialize(bd_t *bd)
{
	struct eth_device *dev;
	struct smsc911x_private *tp;

	dev = (struct eth_device *) malloc(sizeof(*dev));
	strcpy(dev->name, "smsc911x");
	dev->iobase = CFG_SMSC911X_IOBASE;
	memcpy(dev->enetaddr, bd->bi_enetaddr, 6);

	tp = (struct smsc911x_private *) malloc(sizeof(*tp));
	memset(tp, 0, sizeof(*tp));
	dev->priv = tp;

	dev->init = smsc911x_init;
	dev->halt = smsc911x_halt;
	dev->send = smsc911x_send;
	dev->recv = smsc911x_rx;
	eth_register(dev);

	return 1;
}

#endif				//#ifdef CONFIG_SMSC911X
