/**
 * @file IxEthMii.c
 *
 * @author Intel Corporation
 * @date
 *
 * @brief  MII control functions
 *
 * Design Notes:
 *
 * 
 * @par
 * IXP400 SW Release version 2.0
 * 
 * -- Copyright Notice --
 * 
 * @par
 * Copyright 2001-2005, Intel Corporation.
 * All rights reserved.
 * 
 * @par
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * @par
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 * @par
 * -- End of Copyright Notice --
 */

#include "IxOsal.h"

#include "IxEthAcc.h"
#include "IxEthMii_p.h"

#ifdef __wince
#include "IxOsPrintf.h"
#endif

/* Array to store the phy IDs of the discovered phys */
PRIVATE UINT32 ixEthMiiPhyId[IXP425_ETH_ACC_MII_MAX_ADDR];

/*********************************************************
 *
 * Scan for PHYs on the MII bus. This function returns
 * an array of booleans, one for each PHY address.
 * If a PHY is found at a particular address, the
 * corresponding entry in the array is set to true.
 *
 */

PUBLIC IX_STATUS
ixEthMiiPhyScan(BOOL phyPresent[], UINT32 maxPhyCount)
{
    UINT32 i;
    UINT16 regval, regvalId1, regvalId2;

    /*Search for PHYs on the MII*/
    /*Search for existant phys on the MDIO bus*/

    if ((phyPresent == NULL) || 
	(maxPhyCount > IXP425_ETH_ACC_MII_MAX_ADDR))
    {
	return IX_FAIL;
    }

    /* fill the array */
    for(i=0;
        i<IXP425_ETH_ACC_MII_MAX_ADDR;
	i++)
    {
	phyPresent[i] = false;
    }

    /* iterate through the PHY addresses */
    for(i=0;
	maxPhyCount > 0 && i<IXP425_ETH_ACC_MII_MAX_ADDR;
	i++)
    {
	ixEthMiiPhyId[i] = IX_ETH_MII_INVALID_PHY_ID;
	if(ixEthAccMiiReadRtn(i,
			      IX_ETH_MII_CTRL_REG,
			      &regval) == IX_ETH_ACC_SUCCESS)
	{
	    if((regval & 0xffff) != 0xffff)
	    {
		maxPhyCount--;
		/*Need to read the register twice here to flush PHY*/
		ixEthAccMiiReadRtn(i,  IX_ETH_MII_PHY_ID1_REG, &regvalId1);
		ixEthAccMiiReadRtn(i,  IX_ETH_MII_PHY_ID1_REG, &regvalId1);
		ixEthAccMiiReadRtn(i,  IX_ETH_MII_PHY_ID2_REG, &regvalId2);
		ixEthMiiPhyId[i] = (regvalId1 << IX_ETH_MII_REG_SHL) | regvalId2;
		if ((ixEthMiiPhyId[i] == IX_ETH_MII_KS8995_PHY_ID)
		    || (ixEthMiiPhyId[i] == IX_ETH_MII_LXT971_PHY_ID)
		    || (ixEthMiiPhyId[i] == IX_ETH_MII_LXT972_PHY_ID)
		    || (ixEthMiiPhyId[i] == IX_ETH_MII_LXT973_PHY_ID)
		    || (ixEthMiiPhyId[i] == IX_ETH_MII_LXT973A3_PHY_ID)
		    || (ixEthMiiPhyId[i] == IX_ETH_MII_LXT9785_PHY_ID)
		    )
		{
		    /* supported phy */
		    phyPresent[i] = true;
		} /* end of if(ixEthMiiPhyId) */
		else
		{
		    if (ixEthMiiPhyId[i] != IX_ETH_MII_INVALID_PHY_ID)
		    {
			/* unsupported phy */
                        ixOsalLog (IX_OSAL_LOG_LVL_ERROR,
                                   IX_OSAL_LOG_DEV_STDOUT,
				    "ixEthMiiPhyScan : unexpected Mii PHY ID %8.8x\n", 
				    ixEthMiiPhyId[i], 2, 3, 4, 5, 6);
			ixEthMiiPhyId[i] = IX_ETH_MII_UNKNOWN_PHY_ID;
			phyPresent[i] = true;
		    }
		} 
	    }
	}
    }
    return IX_SUCCESS;
}

/************************************************************
 *
 * Configure the PHY at the specified address
 *
 */
PUBLIC IX_STATUS
ixEthMiiPhyConfig(UINT32 phyAddr,
		  BOOL speed100,
		  BOOL fullDuplex,
		  BOOL autonegotiate)
{
    UINT16 regval=0;

    /* parameter check */
    if ((phyAddr < IXP425_ETH_ACC_MII_MAX_ADDR) &&
	(ixEthMiiPhyId[phyAddr] != IX_ETH_MII_INVALID_PHY_ID))
    {
    /*
     * set the control register
     */
	if(autonegotiate)
	{
	    regval |= IX_ETH_MII_CR_AUTO_EN | IX_ETH_MII_CR_RESTART;
	}
	else
	{
	    if(speed100)
	    {
		regval |= IX_ETH_MII_CR_100;
	    }
	    if(fullDuplex)
	    {
		regval |= IX_ETH_MII_CR_FDX;
	    }
	} /* end of if-else() */
	if (ixEthAccMiiWriteRtn(phyAddr, 
				IX_ETH_MII_CTRL_REG, 
				regval) == IX_ETH_ACC_SUCCESS)
	{
	    return IX_SUCCESS;
	}
    } /* end of if(phyAddr) */
    return IX_FAIL;
}

/******************************************************************
 *
 *  Enable the PHY Loopback at the specified address
 */
PUBLIC IX_STATUS
ixEthMiiPhyLoopbackEnable (UINT32 phyAddr)
{
  UINT16 regval ;  

  if ((phyAddr < IXP425_ETH_ACC_MII_MAX_ADDR) && 
      (IX_ETH_MII_INVALID_PHY_ID != ixEthMiiPhyId[phyAddr]))
  {
      /* read/write the control register */
      if(ixEthAccMiiReadRtn (phyAddr,
			     IX_ETH_MII_CTRL_REG, 
			     &regval) 
	 == IX_ETH_ACC_SUCCESS)
      {
	  if(ixEthAccMiiWriteRtn (phyAddr, 
				  IX_ETH_MII_CTRL_REG, 
				  regval | IX_ETH_MII_CR_LOOPBACK)
	     == IX_ETH_ACC_SUCCESS)
	  {
	      return IX_SUCCESS;
	  }
      }
  }
  return IX_FAIL;
}

/******************************************************************
 *
 *  Disable the PHY Loopback at the specified address
 */
PUBLIC IX_STATUS
ixEthMiiPhyLoopbackDisable (UINT32 phyAddr)
{
  UINT16 regval ;  

  if ((phyAddr < IXP425_ETH_ACC_MII_MAX_ADDR) && 
      (IX_ETH_MII_INVALID_PHY_ID != ixEthMiiPhyId[phyAddr]))
  {
      /* read/write the control register */
      if(ixEthAccMiiReadRtn (phyAddr,
			     IX_ETH_MII_CTRL_REG, 
			     &regval) 
	 == IX_ETH_ACC_SUCCESS)
      {
	  if(ixEthAccMiiWriteRtn (phyAddr, 
				  IX_ETH_MII_CTRL_REG, 
				  regval & (~IX_ETH_MII_CR_LOOPBACK))
	     == IX_ETH_ACC_SUCCESS)
	  {
	      return IX_SUCCESS;
	  }
      }
  }
  return IX_FAIL;
}

/******************************************************************
 *
 *  Reset the PHY at the specified address
 */
PUBLIC IX_STATUS
ixEthMiiPhyReset(UINT32 phyAddr)
{
    UINT32 timeout;
    UINT16 regval;

    if ((phyAddr < IXP425_ETH_ACC_MII_MAX_ADDR) &&
	(ixEthMiiPhyId[phyAddr] != IX_ETH_MII_INVALID_PHY_ID))
    {
	if ((ixEthMiiPhyId[phyAddr] == IX_ETH_MII_LXT971_PHY_ID)	||
	    (ixEthMiiPhyId[phyAddr] == IX_ETH_MII_LXT972_PHY_ID)	||
	    (ixEthMiiPhyId[phyAddr] == IX_ETH_MII_LXT973_PHY_ID)	||
	    (ixEthMiiPhyId[phyAddr] == IX_ETH_MII_LXT973A3_PHY_ID)	||
		(ixEthMiiPhyId[phyAddr] == IX_ETH_MII_LXT9785_PHY_ID)
	    )
	{
	    /* use the control register to reset the phy */
	    ixEthAccMiiWriteRtn(phyAddr, 
				IX_ETH_MII_CTRL_REG,
				IX_ETH_MII_CR_RESET);
	 
	    /* poll until the reset bit is cleared */
	    timeout = 0;
	    do
	    {
		ixOsalSleep (IX_ETH_MII_RESET_POLL_MS);

		/* read the control register and check for timeout */
		ixEthAccMiiReadRtn(phyAddr, 
				   IX_ETH_MII_CTRL_REG,
				   &regval);
		if ((regval & IX_ETH_MII_CR_RESET) == 0)
		{
		    /* timeout bit is self-cleared */
		    break;
		}
		timeout += IX_ETH_MII_RESET_POLL_MS;
	    }
	    while (timeout < IX_ETH_MII_RESET_DELAY_MS);

	    /* check for timeout */
	    if (timeout >= IX_ETH_MII_RESET_DELAY_MS)
	    {
		ixEthAccMiiWriteRtn(phyAddr, IX_ETH_MII_CTRL_REG,
				    IX_ETH_MII_CR_NORM_EN);
		return IX_FAIL;
	    }

	    return IX_SUCCESS;
	} /* end of if(ixEthMiiPhyId) */
	else if (ixEthMiiPhyId[phyAddr] == IX_ETH_MII_KS8995_PHY_ID)
	{
	    /* reset bit is reserved, just reset the control register */
	    ixEthAccMiiWriteRtn(phyAddr, IX_ETH_MII_CTRL_REG,
				IX_ETH_MII_CR_NORM_EN);
	    return IX_SUCCESS;
	}
	else
	{
	    /* unknown PHY, set the control register reset bit,
	     * wait 2 s. and clear the control register.
	     */
	    ixEthAccMiiWriteRtn(phyAddr, IX_ETH_MII_CTRL_REG,
				IX_ETH_MII_CR_RESET);
	    
	    ixOsalSleep (IX_ETH_MII_RESET_DELAY_MS);
	    
	    ixEthAccMiiWriteRtn(phyAddr, IX_ETH_MII_CTRL_REG,
				IX_ETH_MII_CR_NORM_EN);
	    return IX_SUCCESS;
	} /* end of if-else(ixEthMiiPhyId) */
    } /* end of if(phyAddr) */
    return IX_FAIL;
}

/*****************************************************************
 *
 *  Link state query functions
 */

PUBLIC IX_STATUS
ixEthMiiLinkStatus(UINT32 phyAddr,
           BOOL *linkUp,
           BOOL *speed100,
           BOOL *fullDuplex,
           BOOL *autoneg)
{
    UINT16 ctrlRegval, statRegval, regval, regval4, regval5;

    /* check the parameters */
    if ((linkUp == NULL) || 
	(speed100 == NULL) || 
	(fullDuplex == NULL) ||
	(autoneg == NULL))
    {
	return IX_FAIL;
    }

    *linkUp = false;
    *speed100 = false;
    *fullDuplex = false;
    *autoneg = false;

    if ((phyAddr < IXP425_ETH_ACC_MII_MAX_ADDR) &&
	(ixEthMiiPhyId[phyAddr] != IX_ETH_MII_INVALID_PHY_ID))
    {
	if ((ixEthMiiPhyId[phyAddr] == IX_ETH_MII_LXT971_PHY_ID)	||
	    (ixEthMiiPhyId[phyAddr] == IX_ETH_MII_LXT972_PHY_ID)	||
	    (ixEthMiiPhyId[phyAddr] == IX_ETH_MII_LXT9785_PHY_ID)
		)
	{
	    /* --------------------------------------------------*/
	    /* Retrieve information from PHY specific register   */
	    /* --------------------------------------------------*/
	    if (ixEthAccMiiReadRtn(phyAddr, 
				   IX_ETH_MII_STAT2_REG, 
				   &regval) != IX_ETH_ACC_SUCCESS)
	    {
		return IX_FAIL;
	    }
	    *linkUp = ((regval & IX_ETH_MII_SR2_LINK) != 0);
	    *speed100 = ((regval & IX_ETH_MII_SR2_100) != 0);
	    *fullDuplex = ((regval & IX_ETH_MII_SR2_FD) != 0);
	    *autoneg = ((regval & IX_ETH_MII_SR2_AUTO) != 0);
	    return IX_SUCCESS;
	} /* end of if(ixEthMiiPhyId) */
	else
	{    
	    /* ----------------------------------------------------*/
	    /* Retrieve information from status and ctrl registers */
	    /* ----------------------------------------------------*/
	    if (ixEthAccMiiReadRtn(phyAddr,  
				   IX_ETH_MII_CTRL_REG, 
				   &ctrlRegval) != IX_ETH_ACC_SUCCESS)
	    {
		return IX_FAIL;
	    }
	    ixEthAccMiiReadRtn(phyAddr,  IX_ETH_MII_STAT_REG, &statRegval);
	    
	    *linkUp = ((statRegval & IX_ETH_MII_SR_LINK_STATUS) != 0);
	    if (*linkUp)
	    {
		*autoneg = ((ctrlRegval & IX_ETH_MII_CR_AUTO_EN) != 0) &&
		    ((statRegval &  IX_ETH_MII_SR_AUTO_SEL) != 0) &&
		    ((statRegval & IX_ETH_MII_SR_AUTO_NEG) != 0);
		
		if (*autoneg)
		{
		    /* mask the current stat values with the capabilities */
		    ixEthAccMiiReadRtn(phyAddr, IX_ETH_MII_AN_ADS_REG, &regval4);
		    ixEthAccMiiReadRtn(phyAddr, IX_ETH_MII_AN_PRTN_REG, &regval5);
		    /* merge the flags from the 3 registers */
		    regval = (statRegval & ((regval4 & regval5) << 6));
		    /* initialise from status register values */
		    if ((regval & IX_ETH_MII_SR_TX_FULL_DPX) != 0)
		    {
			/* 100 Base X full dplx */
			*speed100 = true;
			*fullDuplex = true;
			return IX_SUCCESS;
		    }
		    if ((regval & IX_ETH_MII_SR_TX_HALF_DPX) != 0)
		    {
			/* 100 Base X half dplx */
			*speed100 = true;
			return IX_SUCCESS;
		    }
		    if ((regval & IX_ETH_MII_SR_10T_FULL_DPX) != 0)
		    {
			/* 10 mb full dplx */
			*fullDuplex = true;
			return IX_SUCCESS;
		    }
		    if ((regval & IX_ETH_MII_SR_10T_HALF_DPX) != 0)
		    {
			/* 10 mb half dplx */
			return IX_SUCCESS;
		    }
		} /* end of if(autoneg) */
		else
		{
		    /* autonegotiate not complete, return setup parameters */
		    *speed100 = ((ctrlRegval & IX_ETH_MII_CR_100) != 0);
		    *fullDuplex = ((ctrlRegval & IX_ETH_MII_CR_FDX) != 0);
		}
	    } /* end of if(linkUp) */
	} /* end of if-else(ixEthMiiPhyId) */
    } /* end of if(phyAddr) */
    else
    {
	return IX_FAIL;
    } /* end of if-else(phyAddr) */
    return IX_SUCCESS;
}

/*****************************************************************
 *
 *  Link state display functions
 */

PUBLIC IX_STATUS
ixEthMiiPhyShow (UINT32 phyAddr)
{
    BOOL linkUp, speed100, fullDuplex, autoneg;
    UINT16 cregval;
    UINT16 sregval;
    

    ixEthAccMiiReadRtn(phyAddr,  IX_ETH_MII_STAT_REG, &sregval);
    ixEthAccMiiReadRtn(phyAddr,  IX_ETH_MII_CTRL_REG, &cregval);

    /* get link information */
    if (ixEthMiiLinkStatus(phyAddr,
			   &linkUp,
			   &speed100,
			   &fullDuplex,
			   &autoneg) != IX_ETH_ACC_SUCCESS)
    {
	printf("PHY Status unknown\n");
	return IX_FAIL;
    }

    printf("PHY ID [phyAddr]: %8.8x\n",ixEthMiiPhyId[phyAddr]);
    printf( " Status reg:  %4.4x\n",sregval);
    printf( " control reg: %4.4x\n",cregval);
    /* display link information */
    printf("PHY Status:\n");
    printf("    Link is %s\n",
	   (linkUp ? "Up" : "Down"));
    if((sregval & IX_ETH_MII_SR_REMOTE_FAULT) != 0)
    {
	printf("    Remote fault detected\n");
    }
    printf("    Auto Negotiation %s\n",
	   (autoneg ? "Completed" : "Not Completed"));

    printf("PHY Configuration:\n");
    printf("    Speed %sMb/s\n",
	   (speed100 ? "100" : "10"));
    printf("    %s Duplex\n",
	   (fullDuplex ? "Full" : "Half"));
    printf("    Auto Negotiation %s\n",
	   (autoneg ? "Enabled" : "Disabled"));
    return IX_SUCCESS;
}