/******************************************************************************
 *
 * Copyright:
 *    (C) 2000 - 2009 Embedded Artists AB
 *
 * Description:
 *    Ethernet driver for Davicom DM9000A
 *
 *****************************************************************************/

/******************************************************************************
 * Includes
 *****************************************************************************/

#include "general.h"
#include "ethdrv_dm9000.h"
#include <string.h>
#include "ea3141_board.h"
#include "lpc313x_timer_driver.h"

/******************************************************************************
 * Defines and typedefs
 *****************************************************************************/
#define delayMs(ms) timer_wait_ms(NULL, ms);

#define m_nic_read(reg) m_dm9000_rd(reg)
#define m_nic_read_data m_dm9000_rd_data
#define m_nic_write(reg, data) m_dm9000_wr(reg, data)
#define m_nic_write_data(data) m_dm9000_wr_data(data)

#define FAILSAFE_LIMIT 50


#define DM9000_INDEX_ADDRESS (EXT_SRAM0_1_BASE)
#define DM9000_DATA_ADDRESS  (EXT_SRAM0_1_BASE + 0x10000)
#define DM9000_INDEX_REG     (*((volatile unsigned short *) DM9000_INDEX_ADDRESS))
#define DM9000_DATA_REG      (*((volatile unsigned short *) DM9000_DATA_ADDRESS))


/******************************************************************************
 * Local variables
 *****************************************************************************/

/* Ethernet address (MAC) */
static tU8 macAddr[6];

/******************************************************************************
 * Implementation of local functions
 *****************************************************************************/




/******************************************************************************
 *
 * Description:
 *    Read data from NIC
 *
 *****************************************************************************/
static tU8 
m_dm9000_rd(tU8 reg)
{
  tU8 inByte = 0;

  DM9000_INDEX_REG = reg;
  inByte = DM9000_DATA_REG;

  return inByte;
}

/******************************************************************************
 *
 * Description:
 *    Read data from NIC
 *
 *****************************************************************************/
static tU16 
m_dm9000_rd_data(void)
{
  tU16 inByte = 0;

  inByte = DM9000_DATA_REG;

  return inByte;
}

/******************************************************************************
 *
 * Description:
 *    Write data to NIC
 *
 *****************************************************************************/
static void 
m_dm9000_wr(tU8  reg, 
            tU16 data)
{
  DM9000_INDEX_REG = reg;
  DM9000_DATA_REG = data;
  timer_wait_us(NULL, 1);
}

/******************************************************************************
 *
 * Description:
 *    Write data to NIC
 *
 *****************************************************************************/
static void 
m_dm9000_wr_data(tU16 data)
{
  DM9000_DATA_REG = data;
}

/******************************************************************************
 *
 * Description:
 *    Phy WR
 *
 *****************************************************************************/
static void 
m_dm9000_phy_wr(tU8 reg, tU16 data)
{
  tU32 failsafe;

  m_nic_write(NIC_EPAR, 0x40 | (reg & 0x1f));     //set address
  m_nic_write(NIC_EPDRH, (data >> 8) & 0xff);     //set high data
  m_nic_write(NIC_EPDRL, data & 0xff);            //set low data
  m_nic_write(NIC_EPCR, 0x0a);                    //issue a write command to PHY

  failsafe = 0;
  while (((m_nic_read(NIC_EPCR) & 0x01) == 0x01) || (failsafe > FAILSAFE_LIMIT))
    failsafe++;

  //check if OK
  if (failsafe <= FAILSAFE_LIMIT)
  {
    delayMs(1);
    m_nic_write(NIC_EPCR, 0x00);
  }

}

/******************************************************************************
 *
 * Description:
 *    Initialize DM9000
 *
 *****************************************************************************/
static tBool
initDM9000(void)
{
  m_nic_write(NIC_GPR, 0x00);
  delayMs(20);

  //SW reset
  m_nic_write(NIC_NCR, 0x03);
  delayMs(1);
  m_nic_write(NIC_NCR, 0x00);
  delayMs(1);
  m_nic_write(NIC_NCR, 0x03);
  delayMs(1);
  m_nic_write(NIC_NCR, 0x00);
  delayMs(1);


{
  tU8 vidh = 0;
  tU8 vidl = 0;
  tU8 pidh = 0;
  tU8 pidl = 0;

  vidh = m_nic_read(NIC_VIDH);
  vidl = m_nic_read(NIC_VIDL);

  pidh = m_nic_read(NIC_PIDH);
  pidl = m_nic_read(NIC_PIDL);

  pidl = pidl;
}

  //check if we got contact with DM9000 chip
  //check vendor and product id
  if (m_nic_read(NIC_VIDH) == 0x0A && m_nic_read(NIC_VIDL) == 0x46 &&
      m_nic_read(NIC_PIDH) == 0x90 && m_nic_read(NIC_PIDL) == 0x00)
  {
    //check that chip is in 16-bit mode
    if ((m_nic_read(NIC_ISR) & 0x80) == 0x00)
    {
      m_dm9000_phy_wr(0x04, 0x01e1);  //PHY RESET
      m_dm9000_phy_wr(0x00, 0x1200);  //PHY RESET

      //Activate internal PHY
      m_nic_write(NIC_GPCR, 0x01);
      m_nic_write(NIC_GPR, 0x00);

      //Clear TX status
      m_nic_write(NIC_TCR, 0x00);

      //set normal mode
      m_nic_write(NIC_SMCR, 0x00);

      //Set MAC address
      m_nic_write(NIC_PAR0, macAddr[0]);
      m_nic_write(NIC_PAR1, macAddr[1]);
      m_nic_write(NIC_PAR2, macAddr[2]);
      m_nic_write(NIC_PAR3, macAddr[3]);
      m_nic_write(NIC_PAR4, macAddr[4]);
      m_nic_write(NIC_PAR5, macAddr[5]);

      //Set Multicast hash table
      m_nic_write(NIC_MAR0, 0x00);
      m_nic_write(NIC_MAR1, 0x00);
      m_nic_write(NIC_MAR2, 0x00);
      m_nic_write(NIC_MAR3, 0x00);
      m_nic_write(NIC_MAR4, 0x00);
      m_nic_write(NIC_MAR5, 0x00);
      m_nic_write(NIC_MAR6, 0x00);
      m_nic_write(NIC_MAR7, 0x80);  //broadcast address

      //Clear TX status
      m_nic_write(NIC_NSR, 0x2c);

      //Set flow control
      m_nic_write(NIC_FCR, 0xff);

      //Set interrupt status
      m_nic_write(NIC_ISR, 0x0f);

      m_nic_write(NIC_BPTR, 0x3f);
      m_nic_write(NIC_FCTR, 0x38);

      //
      //Start NIC
      //

      //enable RX
      m_nic_write(NIC_RCR, 0x31);

      //Set interrupt mask
      m_nic_write(NIC_IMR, 0x83);  //Enable RX and TX. Auto rollover for rx address pointer


      return TRUE;
    }
  }

  return FALSE;
}

/*****************************************************************************
 *
 * Description:
 *    Get the next packet from the receive ring buffer. 
 *
 * Params:
 *    [in/out] pBuf - data is copied to this buffer
 *    [in]     len  - length of buffer
 *
 * Returns:
 *    number of copied bytes to the supplied buffer
 *
 ****************************************************************************/
static tU16
getPacket(tU8* pBuf, 
          tU16 len)
{
  tU8   rxReady     = 0;
  tU16  rxStatus    = 0;
  tU16  rxLength    = 0;
  tBool ignoreFrame = FALSE;
  tBool resetDM9000 = FALSE;
  tU16  i           = 0;

  //get rx chain status
  rxReady = m_nic_read(NIC_MRCMDX);  //perform dummy read
  rxReady = m_nic_read_data();       //get current value

  //check if OK
  if ((rxReady & 0x0f) == 0x01)
  {
    DM9000_INDEX_REG = NIC_MRCMD;
    rxStatus = (tU16)m_nic_read_data();
    rxLength = (tU16)m_nic_read_data();

    /* check if packet length is valid */
    if (rxLength > 1536)
    {
      resetDM9000 = TRUE;
      rxLength    = 0;
    }
    else
    {
      if (rxLength < 64)
        ignoreFrame = TRUE;

      //check status
      if (rxStatus & 0xbf00)
        ignoreFrame = TRUE;


      /* perform the read */
      if (pBuf != NULL && ignoreFrame != TRUE)
      {
	      tU16 tempLen = (rxLength + 1) / 2;
        for (i = 0; i < tempLen; i++)
        {
          tU16 data;
		      data = (tU16)m_nic_read_data();
          *pBuf++ = data & 0xff;
          *pBuf++ = (data >> 8) & 0xff;
        }
      }
      else
      {
	      tU16 tempLen = (rxLength + 1) / 2;
        for (i = 0; i < tempLen; i++)
		      m_nic_read_data();

        rxLength = 0;
      }

      return rxLength;
    }
  }

  return rxLength;
}


/******************************************************************************
 * Implementation of public functions
 *****************************************************************************/

/*****************************************************************************
 *
 * Description:
 *    Resets the NIC and initializes all required hardware registers. 
 *
 * Params:
 *    [in] pEthAddr - the ethernet address (MAC) that should be assigned to 
 *                    the driver. 
 *
 ****************************************************************************/
tBool
ethIf_init(tU8* pEthAddr)
{

  memcpy(macAddr, pEthAddr, 6);

  /* the reset failed */
  if (initDM9000() == FALSE)
    return FALSE;

  return TRUE;
}

/*****************************************************************************
 *
 * Description:
 *    Send an ethernet packet. 
 *
 * Params:
 *    [in] pData - the data to send
 *    [in] len   - length of the data to send
 *
 ****************************************************************************/
void
ethIf_send(tU8* pData,
           tU16 len)
{
  tU16 i       = 0;

  if (len > 0x0600)
  {
    return;
  }

  /* disable all interrupt */
  m_nic_write(NIC_IMR, 0x80);

  /* the number of bytes to be transmitted  */
  m_nic_write(NIC_TXPLL, (len & 0xff));
  m_nic_write(NIC_TXPLH, ((len >> 8) & 0xff));

  /* activate write command (with auto increment of write pointer) */
  DM9000_INDEX_REG = NIC_MWCMD;
  
  /* copy data to transmit */
  {
    tU16 tempLen = (len + 1) / 2;
	  for (i = 0; i < tempLen; i++)
	  {
	    tU16 data;
	    data  = (tU16)*pData++;
	    data |= ((tU16)*pData++) << 8;
      m_nic_write_data(data);
	  }
  }

  /* start transmission */
  m_nic_write(NIC_TCR, 0x01);

  /* re-enable all interrupt */
  m_nic_write(NIC_IMR, 0xA3);

}

/*****************************************************************************
 *
 * Description:
 *    Poll the driver
 *
 * Params:
 *    [in] pBuf - allocated buffer to which data will be copied
 *    [in] len  - length of buffer
 *
 * Returns:
 *    Number of copied bytes to the supplied buffer
 *
 ****************************************************************************/
tU16
ethIf_poll(tU8* pBuf,
           tU16 len)

{
  tU8 isr = 0;

  //disable all interrupts from DM9000
  m_nic_write(NIC_IMR, 0x80);

  //get irq status and reset immediately
  isr = m_nic_read(NIC_ISR);
  m_nic_write(NIC_ISR, isr);

  //reenable all interrupts from DM9000
  m_nic_write(NIC_IMR, 0x83);

  /*
   * If this is a transmit interrupt, then a
   * packet has been sent. 
   */
  if ((isr & 0x02) != 0)
  {
    volatile tU8 test,status;
    test = m_nic_read(NIC_NSR);
    if (test & 0x0c)
    {
      if (test & 0x04)
        status = m_nic_read(NIC_TSR1);
      else
        status = m_nic_read(NIC_TSR2);
    }

  }

  return getPacket(pBuf, len);
}


