/*            
 *            Copyright (c) 1998-2000 by NETsilicon Inc.
 *
 *  This software is copyrighted by and is the sole property of 
 *  NETsilicon.  All rights, title, ownership, or other interests
 *  in the software remain the property of NETsilicon.  This
 *  software may only be used in accordance with the corresponding
 *  license agreement.  Any unauthorized use, duplication, transmission,  
 *  distribution, or disclosure of this software is expressly forbidden.
 *
 *  This Copyright notice may not be removed or modified without prior
 *  written consent of NETsilicon.
 *
 *  NETsilicon, reserves the right to modify this software
 *  without notice.
 *
 *  NETsilicon
 *  411 Waverley Oaks Road                  USA 781.647.1234
 *  Suite 227                               http://www.netsilicon.com
 *  Waltham, MA 02452                       AmericaSales@netsilicon.com
 *
 *************************************************************************
 *
 *     Module Name: eth_reset.c
 *         Version: 1.00
 *   Original Date: 10-24-2002
 *          Author: Julia Rakhvalska
 *        Language: Ansi C
 * Compile Options: 
 * Compile defines: 
 *       Libraries: 
 *    Link Options: 
 *
 *    Entry Points: eth_reset()
 *                  eth_enable_efe
 *                  eth_disable_efe()
 *                  eth_init_clk()
 *
 * Description.
 * =======================================================================
 * This file contains Ethernet hardware initialization routines.
 *                                                                        
 *                                                                        
 * Edit Date/Ver   Edit Description
 * ==============  ===================================================
 * jzw 9/3/04		In function eth_reset, fixed a problem relative to
 *                  the NS7520 errata and the Station address filter register.
 *                  See vantive 14002
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "Npttypes.h"
#include "reg_def.h"
#include "Efe_def.h"
#include "eth.h"
#include "MII.h"
#include "sysClock.h"
#include "bsp_api.h"
#include "boardParams.h"


/* NET+20UM MII clock settings structure */
typedef struct
{
    int          div;       /* divisor */
    unsigned int setting;   /* setting */

} ethClkSetting20UM;

/* NET+20UM MII clock settings table */
static ethClkSetting20UM eth_clk_settings_20UM[] =
{
    {4,  0x1},
    {6,  0x2},
    {8,  0x3},
    {10, 0x4},
    {14, 0x5},
    {20, 0x6},
    {28, 0x7}
};

#define MHZ33                   33177600    /* 33 MHz value                */

#define B2BIPGGTR20UM_FD        0x15        /* NET+20UM B2B timer fulld setting */
#define B2BIPGGTR20UM_HD        0x12        /* NET+20UM B2B timer halfd setting */
#define NB2BIPGGTR20UM          0x0c12      /* NET+20UM NonB2B timer            */

#define B2BIPGGTR50_MII         0x15        /* NET+50 B2B timer setting for MII    */
#define NB2BIPGGTR50_MII        0x0c12      /* NonNET+50 B2B timer setting for MII */
#define B2BIPGGTR50_ENDEC       0x5D        /* NET+50 B2B timer setting for ENDEC  */
#define NB2BIPGGTR50_ENDEC      0x365a      /* NET+50 NonB2B timer setting for ENDEC*/


#ifndef _LITTLE_ENDIAN
#define SWAP16(x) ((x >> 8) | ((x & 0xff) << 8))
#else
#define SWAP16(x) (x)
#endif  


/*** Static functions ***/

static void eth_load_address (void);
static unsigned int eth_20UM_clk(void);
static unsigned int eth_50_clk(void);
static void eth_reset_for_speed(int speed);
static void eth_reset_for_duplex(int duplex);


void eth_init_clk(void);
extern int customizePhyReset(void);

WORD32 eth_link_down_cnt = 0;

/**** Following routines interface other Ethernet driver modules ****/

/*
 *
 *  Function: int is20UM(void)
 *
 *  Description:
 *
 *      This routine determines if the chip is NET+20UM
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     TRUE              NET+20UM   
 *     FALSE             other
 *
 */
int is20UM(void)
{

    if (narm_read_reg (NARM_GSSR_REG, NARM_GSSR_ADDR, rev) >= NETA20UM)
        return 1;
    else
        return 0;
}

bpDuplexLevelType ethGetDefaultDuplex(void)
{
    bpDuplexLevelType duplex_level = bpDefaultDuplex;

    if (eth_datap != NULL)
        duplex_level = eth_datap->duplex_level;

    return duplex_level;
}

/*
 *
 *  Function: int eth_reset(void)
 *
 *  Description:
 *
 *      This routine initializes and enables the Ethernet controller.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     0                - success
 *     ETH_MII_ERROR    could not initialize PHY.
 *
 */
int eth_reset(void)
{
    /* initialize MII register addresses and the MII clock */
    customizeMiiInit();

    /* PHY specific reset */
    if (MII_PHY)
    {
        /* reset mii */
        if (customizeMiiReset())
            return ETH_MII_ERROR;

        /* Try to negotiate the link */
        customizeMiiNegotiate();
    }
    else
    {
        if (customizePhyReset())
            return ETH_MII_ERROR;
    }
    
    eth_datap->efe_mii_100 = (char) customizeMiiCheckSpeed();
    eth_datap->fulld       = (char) customizeMiiCheckDuplex();
    /* Check speed and duplex */
  
    eth_reset_for_speed ((int) eth_datap->efe_mii_100);
    eth_reset_for_duplex((int) eth_datap->fulld);

    /* If PHY interrupt is supported - enable it and install ISR
       to monitor the link 
     */

    if (NABspUsePhyInterrupt == TRUE)
        customizeEnableMiiInterrupt();

        /* enable pNA receiver format, accept bad receive packets */
    /* transmit FIFO water mark - 75% full for 20UM, 100% full for 50 */

    narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, erxbad, 1);
    if (is20UM())
        narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, etxwm, NARM_ETXWM_75); 
    else
        narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, etxwm, NARM_ETXWM_100);

    if (eth_datap->pna_mode)
       narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, pna, 1);
    
    /* accept all broadcast packets, support multicast filtering */
	if (is20UM())
	/* 
	 * the ns7520 has a problem with multicast station address filtering.
	 * In the ns7520 errata section entitled "Register hash table errata",
	 * users are instructed to set bit D01 of the station address
	 * filter register (address FF80 05c0) to 0.
	 * This if statement sets bit D01 of the station address
	 * filter register (address FF80 05c0) to 0 for the NS7520 and to 
	 * 1 for all other chips
	 * In addition, by just setting the pra bit to 0, the NS7520 becomes 
	 * non communicative (for broadcast, unicast and multicast. We found 
	 * through experimentation that by having bro and prm set to 1 and
	 * pra sent to 0, that we fit the letter of the errata and successfully
	 * received packets.
	 * See vantive 14002 	 
	 */

	{
        narm_write_reg (NARM_ESAFR_REG, NARM_ESAFR_ADDR, pra, 0);
        narm_write_reg (NARM_ESAFR_REG, NARM_ESAFR_ADDR, prm, 1);
	}
	else
	{
        narm_write_reg (NARM_ESAFR_REG, NARM_ESAFR_ADDR, pra, 1);
	}

    narm_write_reg (NARM_ESAFR_REG, NARM_ESAFR_ADDR, broad, 1);

    /* 
       enable Ethernet receiver logic, clear soft reset for NET+20UM, 
       insert transmit source address, auto zero statistics for NET+50,
       append CRC, auto pad enable 
    */
    if (is20UM())
    {
        narm_write_reg (NARM_EMAC1CFR_REG, NARM_EMAC1CFR_ADDR, rxen, 1);
        narm_write_reg (NARM_EMAC1CFR_REG, NARM_EMAC1CFR_ADDR, srst, 0);
        narm_write_reg (NARM_EGCR20UM_REG, NARM_EGCR_ADDR, itxa, 1);
        narm_write_reg (NARM_EMAC2CFR_REG, NARM_EMAC2CFR_ADDR, crcen, 1);
        narm_write_reg (NARM_EMAC2CFR_REG, NARM_EMAC2CFR_ADDR, paden, 1);
    }
    else
    {
        narm_write_reg (NARM_ESTLCR_REG, NARM_ESTLCR_ADDR, autoz, 1);
        narm_write_reg (NARM_ESTLCR_REG, NARM_ESTLCR_ADDR, itxa, 1);
        narm_write_reg (NARM_ESTLCR_REG, NARM_ESTLCR_ADDR, rxen, 1);
        narm_write_reg (NARM_EMACCFR_REG, NARM_EMACCFR_ADDR, crcen, 1);
        narm_write_reg (NARM_EMACCFR_REG, NARM_EMACCFR_ADDR, paden, 1);
    }
    
    /* 
      Set back-to-back and non back-to-back ipg gap timers 
      IPG of 0.96 ms in 100 Mb/s or 9.6 mS in 10 Mb/s.
    */
    if (is20UM())
    {
        /* For NET+20UM we set back to back timer in eth_reset_for_duplex */
        *NARM_ENB2BIPGGTR20UM_ADDR = NB2BIPGGTR20UM;  /* non back-to-ack ipg gap timer */     
    }
    else
    {
        if (MII_PHY)
        {
            *NARM_EB2BIPGGTR50_ADDR  = B2BIPGGTR50_MII;    /* back-to-ack ipg gap timer */
            *NARM_ENB2BIPGGTR50_ADDR = NB2BIPGGTR50_MII;   /* non back-to-ack ipg gap timer */
        }
        else
        {
            *NARM_EB2BIPGGTR50_ADDR  = B2BIPGGTR50_ENDEC;    /* back-to-ack ipg gap timer */
            *NARM_ENB2BIPGGTR50_ADDR = NB2BIPGGTR50_ENDEC;   /* non back-to-ack ipg gap timer */
        }
    }
    
    /* For NET+50 clear out tx and rx statistics by reading them */
    if (is20UM() == 0)
    {
	int tmp;
	
	for (tmp = 0; tmp < 10000; tmp++) ;
	
        tmp =  narm_read_reg (NARM_ESTATS_REG,    NARM_ELCC_ADDR, cnt); 
        tmp += narm_read_reg (NARM_ESTATS_REG,    NARM_EEDC_ADDR, cnt); 
        tmp += narm_read_reg (NARM_ESTATS_REG,    NARM_EMCC_ADDR, cnt); 
        tmp += narm_read_reg (NARM_ESTATS_REG,    NARM_ESHRTFC_ADDR, cnt); 
        tmp += narm_read_reg (NARM_ESTATS_REG,    NARM_ELNGFC_ADDR, cnt); 
        tmp += narm_read_reg (NARM_ESTATS_REG,    NARM_EAEC_ADDR, cnt); 
        tmp += narm_read_reg (NARM_ESTATS_REG,    NARM_ECRCEC_ADDR, cnt); 
        tmp += narm_read_reg (NARM_ESTATS_REG,    NARM_ECEC_ADDR, cnt); 
    }
 
    /* load MAC address */
    eth_load_address();     

    return 0;
}


/*
 *
 *  Function: int eth_enable_efe(void)
 *
 *  Description:
 *
 *      This routine enables Ethernet MAC, receive and transmit FIFO and DMA in EGCR.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     none
 *
 */
void eth_enable_efe(void)
{
    /* disable receive FIFO,  receive DMA */
    narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, erxdma, 1);
    narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, erx, 1);

    /* disable transmit FIFO, transmit DMA */
    narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, etxdma, 1);
    narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, etx, 1);

    /* disable Ethernet receiver logic */
    if (is20UM())
    {
        narm_write_reg (NARM_EMAC1CFR_REG, NARM_EMAC1CFR_ADDR, rxen, 1);
    }
    else
    {   
        narm_write_reg (NARM_ESTLCR_REG, NARM_ESTLCR_ADDR, rxen, 1);
    }
}

/*
 *
 *  Function: int eth_disable_efe(void)
 *
 *  Description:
 *
 *      This routine disables Ethernet MAC, receive and transmit FIFO and DMA in EGCR.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     none
 *
 */
void eth_disable_efe(void)
{
    /* disable Ethernet receiver logic */
    if (is20UM())
    {
        narm_write_reg (NARM_EMAC1CFR_REG, NARM_EMAC1CFR_ADDR, rxen, 0);
    }
    else
    {   
        narm_write_reg (NARM_ESTLCR_REG, NARM_ESTLCR_ADDR, rxen, 0);
    }
  
    /* disable receive FIFO,  receive DMA */
    narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, erxdma, 0);
    narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, erx, 0);

    /* disable transmit FIFO, transmit DMA */
    narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, etxdma, 0);
    narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, etx, 0);
}

/*
 *
 *  Function: void eth_init_clk(void)
 *
 *  Description:
 *
 *      This routine initializes the Ethernet clock setting.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     none
 *
 */
void eth_init_clk(void)
{
    if (is20UM())
    {
        /* Clear MAC SRST, it resets to 1 */
        narm_write_reg (NARM_EMAC1CFR_REG, NARM_EMAC1CFR_ADDR, srst, 0);

        /* determine MII clock setting for NET+20UM andset it */ 
        narm_write_reg (NARM_EMCFG_REG, NARM_EMCFG_ADDR, clks, eth_20UM_clk());
    }
    else
    {
        /* clock select 33/25 Mhz */
        narm_write_reg (NARM_EPCSCR_REG, NARM_EPCSCR_ADDR, clks, eth_50_clk());
    }
}    



/*
 *
 *  Function: void read_autosense (void)
 *
 *  Description:
 *
 *      This routine determines which ethernet connector we should use.
 *      The possibilities are:
 *
 *              10-Base T manually selected
 *              Thinnet manually selected
 *              Automatic selection based on Link integrity signal
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     none
 *
 */

void eth_read_autosense (INT16 *connector_type, INT16 *connector_autosense)
{
    *connector_autosense = 0;

    if (MII_PHY)
    {
        if (eth_datap->efe_mii_100) 
            *connector_type = ETH_MII_100;
        else
            *connector_type = ETH_MII_10;
    }
    else
    /* PCW TODO  - this needs to be redone to work with
       the MII 10/100 stuff */
    if (!narm_read_reg (NARM_EGSR_REG, NARM_EGSR_ADDR, automan))
    {
      /* auto detect */
        if (narm_read_reg (NARM_EGSR_REG, NARM_EGSR_ADDR, linkpul))    
            *connector_type = ETH_THINNET;
        else
            *connector_type = ETH_10BASET;

        *connector_autosense = TRUE;
    }
    else
    {
       /* manual selection */
       if (narm_read_reg (NARM_EGSR_REG, NARM_EGSR_ADDR, automan))   
           *connector_type = ETH_10BASET;
       else
          *connector_type = ETH_THINNET;
    }
}



/********** Static Functions **********/

/*
 *
 *  Function: unsigned int eth_20UM_clk(void)
 *
 *  Description:
 *
 *      This routine determines correct Ethernet clock setting for
 *      NET+20UM chip based on the system clock speed.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     clock setting
 *
 */
static unsigned int eth_20UM_clk(void)
{
    unsigned int sys_clk;
    int max_settings, i;
    
    max_settings = sizeof(eth_clk_settings_20UM) / sizeof(ethClkSetting20UM);

    /* get sys clock */
    sys_clk = NAgetSysClkFreq();

    /* find the correct setting - <= ETH_MAX_PHY_CLK */
    for (i = 0; i < max_settings; i++)
    {
        if (sys_clk / eth_clk_settings_20UM[i].div <= ETH_MAX_PHY_CLK)
            return eth_clk_settings_20UM[i].setting;
    }

    /* still too fast - set to slowest */
    return eth_clk_settings_20UM[max_settings - 1].setting;
}

/*
 *
 *  Function: unsigned int eth_50_clk(void)
 *
 *  Description:
 *
 *      This routine determines correct Ethernet clock setting for
 *      NET+50 (15/40/20) chip based on the system clock speed.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     clock setting
 *
 */
static unsigned int eth_50_clk(void)
{
    /* get sys clock */
    unsigned int sys_clk = NAgetSysClkFreq();

    if (sys_clk < MHZ33)    /* If sys clock < 33 MHz */
        return 0;           /* setting = 0 */
    else
        return 1;           /* else setting = 1 */

}


/*
 *
 *  Function: void eth_load_address (void)
 *
 *  Description:
 *
 *      This routine loads MAC address to the chip.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     none
 *
 */
static void eth_load_address (void)
{
    WORD16   value16;
    
    /* setup WORD16 pointer to address*/
    WORD16 *address = (WORD16 *) eth_datap->mac_addr;    

    /* Note that NET+20UM and NET+50 have different word order of SA registers */

    if (is20UM())
    {   
        memcpy (&value16, &address[2], sizeof (WORD16));
        *NARM_ESA3R20UM_ADDR = SWAP16(value16) & 0x0000ffff;

        memcpy (&value16, &address[1], sizeof (WORD16));
        *NARM_ESA2R20UM_ADDR = SWAP16(value16) & 0x0000ffff;

        memcpy (&value16, &address[0], sizeof (WORD16));
        *NARM_ESA1R20UM_ADDR = SWAP16(value16) & 0x0000ffff;

    }
    else
    {
        memcpy (&value16, &address[2], sizeof (WORD16));
        *NARM_ESA3R50_ADDR = SWAP16(value16) & 0x0000ffff;

        memcpy (&value16, &address[1], sizeof (WORD16));
        *NARM_ESA2R50_ADDR = SWAP16(value16) & 0x0000ffff;

        memcpy (&value16, &address[0], sizeof (WORD16));
        *NARM_ESA1R50_ADDR = SWAP16(value16) & 0x0000ffff;

    }
}

/*
 *
 *  Function: void eth_reset_for_duplex(int duplex)
 *
 *  Description:
 *
 *      This routine writes Ethernet settings necessary for 
 *      full or half duplex operation.
 *
 *  Parameters:
 *
 *      duplex           1 - full, 0 - half
 *
 *  Return Values:
 *
 *     none
 *
 */
static void eth_reset_for_duplex(int duplex)
{
       narm_write_reg (NARM_EGCR_REG, NARM_EGCR_ADDR, efulld, duplex);

       if (is20UM())
       {
           narm_write_reg (NARM_EMAC2CFR_REG, NARM_EMAC2CFR_ADDR, fulld, duplex);

           /* In Full Duplex Inter-packet Gap Delay = 0.96uS for 100Mb and 9.6uS for 10Mb */
           /* In Half Duplex Inter-packet Gap Delay = 0.06uS for 100Mb and 9.6uS for 10Mb */
           if (duplex)
               *NARM_EB2BIPGGTR20UM_ADDR  = B2BIPGGTR20UM_FD; /* back-to-ack ipg gap timer */
           else
               *NARM_EB2BIPGGTR20UM_ADDR  = B2BIPGGTR20UM_HD; /* back-to-ack ipg gap timer */
       }
       else
          narm_write_reg (NARM_EMACCFR_REG, NARM_EMACCFR_ADDR, fulld, duplex);
}

/*
 *
 *  Function: void eth_reset_for_speed(int speed)
 *
 *  Description:
 *
 *      This routine writes Ethernet settings necessary for 
 *      100 or 10 Mbit operation.
 *
 *  Parameters:
 *
 *      speed           1 - 100Mbit, 0 - 10Mbit
 *
 *  Return Values:
 *
 *     none
 *
 */
static void eth_reset_for_speed(int speed)
{
   /* disable burst for NET+40-0 ERRATA  on 100BaseT  */
    if (narm_read_reg (NARM_GSSR_REG, NARM_GSSR_ADDR, rev) == NETA40)
    {
        int ram_burst = speed ? 0 : 1;
 
        narm_write_reg (NARM_CSAR_REG, NARM_CSAR1_ADDR, burst, ram_burst);
        narm_write_reg (NARM_CSAR_REG, NARM_CSAR2_ADDR, burst, ram_burst);
    }
}

/*
 *
 *  Function: void eth_update_link(void)
 *
 *  Description:
 *
 *      This routine checks speed and duplex of the Ethernet link and 
 *      configures MAC / EFE accordingly.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *     none
 *
 */
void eth_update_link (void)
{
     /* Check speed */
     if (customizeMiiCheckSpeed()) 
          eth_datap->efe_mii_100 = 1;
     else
	      eth_datap->efe_mii_100 = 0;

     /* Check duplex */
     if (customizeMiiCheckDuplex())
          eth_datap->fulld   = 1;
     else
	      eth_datap->fulld   = 0;

    /* set EFE full duplex to MAC full duplex */
    eth_reset_for_speed ((int) eth_datap->efe_mii_100);
    eth_reset_for_duplex((int) eth_datap->fulld);
}

