//==========================================================================
//
//      if_at91.c
//
//
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006 Free Software Foundation, Inc.
//
// eCos is free software; you can redistribute it and/or modify it under    
// the terms of the GNU General Public License as published by the Free     
// Software Foundation; either version 2 or (at your option) any later      
// version.                                                                 
//
// eCos is distributed in the hope that it will be useful, but WITHOUT      
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or    
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License    
// for more details.                                                        
//
// You should have received a copy of the GNU General Public License        
// along with eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    Andrew Lunn, John Eigelaar
// Contributors:  
// Date:         2006-05-10
// Purpose:
// Description:
//
//####DESCRIPTIONEND####
//
//========================================================================*/

#include <pkgconf/system.h>
#include <pkgconf/hal.h>
#include <pkgconf/devs_eth_arm_at91.h>
#include <pkgconf/io_eth_drivers.h>
#if defined(CYGPKG_REDBOOT)
   #include <pkgconf/redboot.h>
#endif

#include <cyg/hal/hal_io.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_cache.h>
#include <cyg/hal/drv_api.h>
#include <cyg/hal/hal_diag.h>
#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/diag.h>
#include <cyg/io/eth/netdev.h>
#include <cyg/io/eth/eth_drv.h>
#include <cyg/io/eth/eth_drv_stats.h>
#include <cyg/io/eth_phy.h>
#include <errno.h>
#include <string.h>

#ifdef CYGACC_CALL_IF_DELAY_US
# define AT91_ETH_DELAY_US(us) CYGACC_CALL_IF_DELAY_US(us)
#else
# include <cyg/hal/hal_diag.h>
# define AT91_ETH_DELAY_US(us) HAL_DELAY_US(us)
#endif


//======================================================================

#ifdef CYGPKG_REDBOOT
static void db_printf( char *fmt, ... )
{
    extern int start_console(void);
    extern void end_console(int);
    va_list a;
    int old_console;
    va_start( a, fmt );
    old_console = start_console();  
    diag_vprintf( fmt, a );
    end_console(old_console);
    va_end( a );
}
static void db_dump_buf(void *p, CYG_ADDRWORD s)
{
    extern int start_console(void);
    extern void end_console(int);
    int old_console;
    old_console = start_console();  
    diag_vdump_buf_with_offset( diag_printf, p, s, 0 );
    end_console(old_console);
}
#elif defined(CYGACC_CALL_IF_SET_CONSOLE_COMM)
static void os_printf( char *fmt, ... )
{
    va_list a;
    int old_console = CYGACC_CALL_IF_SET_CONSOLE_COMM(CYGNUM_CALL_IF_SET_COMM_ID_QUERY_CURRENT);
    va_start( a, fmt );
    CYGACC_CALL_IF_SET_CONSOLE_COMM( 0 );
    diag_vprintf( fmt, a );
    CYGACC_CALL_IF_SET_CONSOLE_COMM(old_console);
    va_end( a );
}
#define db_printf os_printf
#define db_dump_buf(__buf, __size) diag_vdump_buf_with_offset( os_printf, __buf, __size, 0 )
#else
#define db_printf diag_vprintf
#define db_dump_buf(__buf, __size) diag_dump_buf(__buf, __size)
#endif

//======================================================================
// Set up the level of debug output

//#undef  CYGPKG_DEVS_ETH_ARM_AT91_DEBUG_LEVEL
//#define CYGPKG_DEVS_ETH_ARM_AT91_DEBUG_LEVEL 1
//#define CYGPKG_DEVS_ETH_ARM_AT91_DEBUG_LEVEL 2

#if CYGPKG_DEVS_ETH_ARM_AT91_DEBUG_LEVEL > 0
   #define debug1_printf( __fmt, ... ) db_printf("AT91_ETH: %30s[%4d]: " __fmt, __FUNCTION__, __LINE__, ## __VA_ARGS__ )
   #define debug1_dump_buf( __buf, __len ) db_dump_buf( __buf, __len )
#else
   #define debug1_printf(args...)
   #define debug1_dump_buf( __buf, __len ) 
#endif
#if CYGPKG_DEVS_ETH_ARM_AT91_DEBUG_LEVEL > 1
   #define debug2_printf( __fmt, ... ) db_printf("AT91_ETH: %30s[%4d]: " __fmt, __FUNCTION__, __LINE__, ## __VA_ARGS__ )
   #define debug2_dump_buf( __buf, __len ) db_dump_buf( __buf, __len )
#else
   #define debug2_printf(args...)
   #define debug2_dump_buf( __buf, __len )
#endif

//======================================================================
// Trace debugging.
// Only enable if you know what you are doing.

#if 0
#define XLEN 256
cyg_uint32 x[XLEN];
cyg_uint32 xpos = 0;
#define X(__x)                                  \
{                                               \
    x[xpos++ & (XLEN-1)] = (cyg_uint32)(__x);   \
    x[xpos & (XLEN-1)] = 0xFFFFFFFF;            \
}
#define XT(__x)                                 \
{                                               \
    cyg_uint32 ht;                              \
    X(__x);                                     \
    X((long)cyg_current_time());                \
    HAL_CLOCK_READ( &ht );                      \
    X(ht);                                      \
}
#define XRES()                                          \
{                                                       \
    xpos = 0;                                           \
    x[xpos & (XLEN-1)] = 0xFFFFFFFF;                    \
}
#else
#define X(__x)
#define XT(__x)
#define XRES()
#endif

//======================================================================

// Include PHY stuff after debug macro defs.

#define phy_debug_printf debug2_printf
#include "at91_phy.h"

//======================================================================
//Driver interface callbacks

#define _eth_drv_init(sc,mac)			\
  (sc->funs->eth_drv->init)(sc,(unsigned char *)mac)
#define _eth_drv_tx_done(sc,key,status)		\
  (sc->funs->eth_drv->tx_done)(sc,key,status) 
#define _eth_drv_recv(sc,len)			\
  (sc->funs->eth_drv->recv)(sc,len) 

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
static cyg_uint32
at91_eth_isr (cyg_vector_t vector, cyg_addrword_t data);
#endif

// ---------------------------------------------------------------------
// RedBoot configuration options for managing ESAs for us

// Decide whether to have redboot config vars for it...
#if defined(CYGSEM_REDBOOT_FLASH_CONFIG) && defined(CYGPKG_REDBOOT_NETWORKING)
   #include <redboot.h>
   #include <flash_config.h>

   #ifdef CYGSEM_DEVS_ETH_ARM_AT91_REDBOOT_HOLDS_ESA_ETH0
RedBoot_config_option("Network hardware address [MAC] for eth0",
                      eth0_esa_data,
                      ALWAYS_ENABLED, true,
                      CONFIG_ESA, 0);
   #endif

#endif  // CYGPKG_REDBOOT_NETWORKING && CYGSEM_REDBOOT_FLASH_CONFIG

// and initialization code to read them
// - independent of whether we are building RedBoot right now:
#ifdef CYGPKG_DEVS_ETH_ARM_AT91_REDBOOT_HOLDS_ESA

   #include <cyg/hal/hal_if.h>

   #ifndef CONFIG_ESA
      #define CONFIG_ESA (6)
   #endif

  #define CYGHWR_DEVS_ETH_ARM_AT91_GET_ESA( mac_address, ok )           \
  CYG_MACRO_START                                                       \
  ok = CYGACC_CALL_IF_FLASH_CFG_OP( CYGNUM_CALL_IF_FLASH_CFG_GET,       \
                                    "eth0_esa_data",                    \
                                    mac_address,                        \
                                    CONFIG_ESA);                        \
  CYG_MACRO_END

#endif // CYGPKG_DEVS_ETH_AT91_ETH_REDBOOT_HOLDS_ESA

//============================================================================
// Private Data structures

#ifndef AT91_EMAC_RX_BUFF_SIZE
#define AT91_EMAC_RX_BUFF_SIZE  128
#endif

// Receive Buffer Descriptor
typedef struct rbd_s
{
   cyg_uint32 addr;
   cyg_uint32 sr;
} rbd_t;

// Receive Buffer
typedef struct rb_s 
{
   cyg_uint8 rb[AT91_EMAC_RX_BUFF_SIZE];
} rb_t;

// Transmit Buffer Descriptor
typedef struct tbd_s
{
   cyg_uint32 addr;
   cyg_uint32 sr;
} tbd_t;

// AT91 Ethernet private data
typedef struct at91_eth_priv_s 
{
    cyg_uint32          intr_vector;
    char                *esa_key;      // RedBoot 'key' for device ESA
    cyg_uint8           *enaddr;
    cyg_uint32          base;    // Base address of device
    eth_phy_access_t    *phy;
    rbd_t               *rbd;
    rb_t                *rb;
    tbd_t               *tbd;
    cyg_uint64          pad1[4];        // Align and cache line spacing
    rbd_t               rbd_c[CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS];
    cyg_uint64          pad2[4];        // Align and cache line spacing
    rb_t                rb_c[CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS];
    cyg_uint64          pad3[4];        // Align and cache line spacing
    tbd_t               tbd_c[CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS];
    cyg_uint64          pad4[4];        // Align and cache line spacing
    unsigned long       curr_tx_key;
    cyg_bool            tx_busy;
    cyg_uint32          last_tbd_idx;
    cyg_uint32          curr_tbd_idx;
    cyg_uint32          curr_rbd_idx;
    cyg_uint32          recv_idx;
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    cyg_interrupt       intr;
    cyg_handle_t        intr_handle;
#endif
} at91_eth_priv_t;

//======================================================================
// Receiver buffer handling

// Initialize the receiver buffers and descriptors
static void
at91_rb_init(at91_eth_priv_t *priv)
{
   int i;
   for (i = 0 ; i < CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS; i++)
   {
      priv->rbd[i].addr = CYGARC_PHYSICAL_ADDRESS(((cyg_uint32)&priv->rb_c[i]) & AT91_EMAC_RBD_ADDR_MASK);
      priv->rbd[i].sr = 0;
   }
   // Set the wrap bit on the last entry
   priv->rbd[CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS-1].addr |= 
     AT91_EMAC_RBD_ADDR_WRAP;
}

//======================================================================
// Transmit buffer handling

// Initialize the transmit buffer descriptors
static void 
at91_tb_init(at91_eth_priv_t *priv)
{
   int i;
   for (i = 0 ; i < CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS; i++)
   {
      priv->tbd[i].addr = 0;
      priv->tbd[i].sr = AT91_EMAC_TBD_SR_USED;
   }
   // Set the wrap bit on the last entry
   priv->tbd[CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS-1].sr |= AT91_EMAC_TBD_SR_WRAP;
}

//======================================================================
// Enable and Disable of the receiver and transmitter.

static void
at91_disable_rx(at91_eth_priv_t *priv)
{
   cyg_uint32 ctl;

   HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
   ctl &= ~AT91_EMAC_NCR_RE;
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}

static void
at91_disable_tx(at91_eth_priv_t *priv)
{
   cyg_uint32 ctl;

   HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
   ctl &= ~AT91_EMAC_NCR_TX;
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}

static void
at91_enable_rx(at91_eth_priv_t *priv)
{
   cyg_uint32 ctl;

   HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
   ctl |= AT91_EMAC_NCR_RE;
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}

static void
at91_enable_tx(at91_eth_priv_t *priv)
{
   cyg_uint32 ctl;

   HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
   ctl |= AT91_EMAC_NCR_TX;
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}

static void 
at91_enable(at91_eth_priv_t *priv)
{
   at91_enable_tx(priv);
   at91_enable_rx(priv);
}

static void 
at91_disable(at91_eth_priv_t *priv)
{
   at91_disable_tx(priv);
   at91_disable_rx(priv);
}

static void
at91_start_transmitter(at91_eth_priv_t *priv)
{
   cyg_uint32 ctl;

   HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
   ctl |= AT91_EMAC_NCR_TSTART;
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}


//======================================================================
// Initialization code

// Configure the pins so that the EMAC has control of them. 
static void
at91_cfg_pins(void)
{
#if defined(CYGHWR_HAL_ARM_AT91SAM7)
   // This assumes the MII is used, not the RMII    
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_EREFCK);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ECRS);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ECOL);

   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERXDV);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX0);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX1);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX2);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX3);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERXER);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERXCK);

   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETXEN);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX0);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX1);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX2);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX3);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETXER);

   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_EMDC);
   HAL_ARM_AT91_PIO_CFG(AT91_EMAC_EMDIO);
   
#elif defined(CYGHWR_HAL_ARM_ARM9_SAM9_SAM9260)

   // This assumes RMII is used
   HAL_WRITE_UINT32( SAM9_PIOA+_PIO_PDR, 0x003FF000 );
   HAL_WRITE_UINT32( SAM9_PIOA+_PIO_ASR, 0x003FF000 );

   // TODO: Add option and code to select RMII vs MII
   
#elif defined(CYGHWR_HAL_ARM_ARM9_SAM9_SAM9263)

   // This assumes RMII is used
   HAL_WRITE_UINT32( SAM9_PIOE+_PIO_PDR, 0xFFE00000 );
   HAL_WRITE_UINT32( SAM9_PIOE+_PIO_ASR, 0xFFE00000 );
   
#else
   
#error Unknown AT91 variant
   
#endif
}

//======================================================================
// Set a specific address match to a given address. Packets received which
// match this address will be passed on.

static void
at91_set_mac(at91_eth_priv_t * priv, cyg_uint8 * enaddr, int sa)
{
   cyg_uint32 hi, lo;

   CYG_ASSERTC(sa > 0 && sa < 5);
   sa--;

   lo = ((enaddr[3] << 24) |
         (enaddr[2] << 16) |
         (enaddr[1] <<  8) |
         (enaddr[0]));

   hi = ((enaddr[5] <<  8) |
         (enaddr[4]));

   HAL_WRITE_UINT32(priv->base + AT91_EMAC_SA1L + (8*sa), lo);
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_SA1H + (8*sa), hi);
}

//======================================================================

static void
at91_clear_stats(at91_eth_priv_t *priv)
{
   cyg_uint32 ctl;

   HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
   ctl |= AT91_EMAC_NCR_CSR;
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}

//======================================================================
// Enable and Disable of the receiver and transmitter.
// Initialize the interface. This configures the interface ready for use.
// Interrupts are grabbed etc. This means the start function has
// little to do except enable the receiver

static bool
at91_eth_init(struct cyg_netdevtab_entry *tab)
{
   struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance;
   at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
   bool esa_ok = false;
   unsigned char enaddr[6] = { CYGDAT_DEVS_ETH_ARM_AT91_MACADDR};
   unsigned char enzero[6] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
   unsigned short phy_state = 0;
   cyg_uint32 ncfg = 0, usrio = 0;

   debug1_printf("Initialising @ %x\n",priv->base);

   priv->tx_busy = false;
   priv->curr_tbd_idx = 0;
   priv->curr_rbd_idx = 0;

   priv->rbd = (rbd_t*)CYGARC_UNCACHED_ADDRESS(priv->rbd_c);
   priv->rb  = (rb_t*)CYGARC_UNCACHED_ADDRESS(priv->rb_c );
   priv->tbd = (tbd_t*)CYGARC_UNCACHED_ADDRESS(priv->tbd_c);
   
#if defined(CYGHWR_HAL_ARM_AT91SAM7)
   // Enable the clock to the EMAC
   HAL_WRITE_UINT32(AT91_PMC + AT91_PMC_PCER, AT91_PMC_PCER_EMAC);
   HAL_WRITE_UINT32(AT91_PMC + AT91_PMC_PCER, AT91_PMC_PCER_PIOB);
   HAL_WRITE_UINT32(AT91_PMC + AT91_PMC_PCER, AT91_PMC_PCER_PIOA);
#elif defined(CYGHWR_HAL_ARM_ARM9_SAM9_SAM9260)
   HAL_WRITE_UINT32(_PMC_PCER, (1<<21));
#endif
   
   at91_disable(priv);
   at91_cfg_pins();

   /* Enable  IO Clock */
   usrio = AT91_EMAC_USRIO_CLKEN;
#if !defined(CYGHWR_HAL_ARM_AT91SAM7)
   usrio |= AT91_EMAC_USRIO_RMII;
#endif
   HAL_WRITE_UINT32(priv->base+AT91_EMAC_USRIO, usrio);
   
   /* Disable all the interrupts for the moment            */
   /* The Start function actually enables all that we need */
   //HAL_WRITE_UINT32(priv->base + AT91_EMAC_IDR, 0x3FFF);

   // If we are building an interrupt enabled version, install the
   // interrupt handler
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
   debug1_printf("AT91_ETH: Installing Interrupts on IRQ %d\n",
		 priv->intr_vector);
   cyg_drv_interrupt_create(priv->intr_vector,
                            CYGNUM_DEVS_ETH_ARM_AT91_INTRPRI,
                            (cyg_addrword_t)sc,
                            at91_eth_isr,
                            eth_drv_dsr,
                            &priv->intr_handle,
                            &priv->intr);

   cyg_drv_interrupt_attach(priv->intr_handle);
   cyg_drv_interrupt_unmask(priv->intr_vector);
#endif

#ifdef CYGHWR_DEVS_ETH_ARM_AT91_GET_ESA
   // Get MAC address from RedBoot configuration variables
   CYGHWR_DEVS_ETH_ARM_AT91_GET_ESA(&enaddr[0], esa_ok);
   // If this call fails myMacAddr is unchanged and MAC address from
   // CDL is used
#endif

   if (!esa_ok)
   {
      // Can't figure out ESA
      debug1_printf("AT91_ETH - Warning! ESA unknown\n");
   }
   debug1_printf("AT91_ETH: %02x:%02x:%02x:%02x:%02x:%02x\n",
                 enaddr[0],enaddr[1],enaddr[2],
                 enaddr[3],enaddr[4],enaddr[5]);

   // Give the EMAC its address
   at91_set_mac(priv, enaddr, 1);
   at91_set_mac(priv, enzero, 2);
   at91_set_mac(priv, enzero, 3);
   at91_set_mac(priv, enzero, 4);

   // Setup the receiver buffers and descriptors
   at91_rb_init(priv);

   // And tell the EMAC where the first receive buffer descriptor is
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_RBQP, CYGARC_PHYSICAL_ADDRESS(priv->rbd_c));

   // Setup the transmit descriptors
   at91_tb_init(priv);

   // And tell the EMAC where the first transmit buffer descriptor is
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_TBQP, CYGARC_PHYSICAL_ADDRESS(priv->tbd_c));

   // Setup the PHY
   CYG_ASSERTC(priv->phy);

   at91_mdio_enable();
   
   if (!_eth_phy_init(priv->phy))
   {
      at91_mdio_disable();
      return (false);
   }

   // On powerup, on the SAM9260EK, the PHY seems to have difficulty
   // coming up and negotiating with the other end. The following is
   // an attempt to fix this by resetting the PHY and then restarting
   // autonegotiation.  Unfortunately none of this seems to be
   // reliable, but the PHY can eventually be brought up by
   // power-cycling the board; the die appears to be cast before any
   // software gets to run.
   {
       int i;
       unsigned short reg;

       // Reset the PHY
       _eth_phy_write(priv->phy, 0, priv->phy->phy_addr, (1<<15) );

       debug1_printf("AT91_ETH: Waiting for PHY to reset");
       for( i = 0; i < 20; i++ )
       {
           AT91_ETH_DELAY_US(100000);   // 1/10 second
           debug1_printf(".");
           _eth_phy_read(priv->phy, 0, priv->phy->phy_addr, &reg );
            if ((reg & (1<<15)) == 0)
                break;
       }
       debug1_printf("\n");
       
       // Force an autonegotiation
       _eth_phy_read(priv->phy, 0, priv->phy->phy_addr, &reg );
       reg |= (1<<12)|(1<<9);   // Set autonegotiation bits
       _eth_phy_write(priv->phy, 0, priv->phy->phy_addr, reg );
       
       debug1_printf("AT91_ETH: Waiting for link to come up");
       for( i = 0; i < 20; i++ )
       {
           AT91_ETH_DELAY_US(100000);   // 1/10 second
           debug1_printf(".");
           phy_state = _eth_phy_state(priv->phy);
            if ((phy_state & ETH_PHY_STAT_LINK) != 0)
                break;
       }
       debug1_printf("\n");
   }
   
   // Get the current mode and print it
   phy_state = _eth_phy_state(priv->phy);

   at91_mdio_disable();

   debug2_printf("phy_state %04x\n", phy_state);
   
   HAL_READ_UINT32(priv->base + AT91_EMAC_NCFG,ncfg);


   if ((phy_state & ETH_PHY_STAT_LINK) != 0)
   {
      if (((phy_state & ETH_PHY_STAT_100MB) != 0))
      {
         debug1_printf("AT91_ETH: 100Mbyte/s\n");
         ncfg |= AT91_EMAC_NCFG_SPD_100Mbps;
      }
      else
      {
         debug1_printf("AT91_ETH: 10Mbyte/s\n");
         ncfg &= ~(AT91_EMAC_NCFG_SPD_100Mbps);
      }
      if((phy_state & ETH_PHY_STAT_FDX))
      {
         debug1_printf(" Full Duplex\n");
         ncfg |= AT91_EMAC_NCFG_FD;
      }
      else
      {
         debug1_printf(" Half Duplex\n");
         ncfg &= ~(AT91_EMAC_NCFG_FD);
      }
   }
   else
   {
      debug1_printf("AT91_ETH: No Link\n");
      return false;
   }

   //Setup the network configuration
   ncfg |= (AT91_EMAC_NCFG_RLCE|AT91_EMAC_NCFG_DRFCS);

   HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCFG,ncfg);

   // Clear the Statistics counters;
   at91_clear_stats(priv);


   /* Clear the status registers */
   HAL_READ_UINT32(priv->base + AT91_EMAC_ISR,ncfg);
   HAL_READ_UINT32(priv->base + AT91_EMAC_RSR,ncfg);
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_RSR,ncfg);
   HAL_READ_UINT32(priv->base + AT91_EMAC_TSR,ncfg);
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_TSR,ncfg);   

   // Enable the interrupts we are interested in
   HAL_WRITE_UINT32(priv->base + AT91_EMAC_IER, 0xFFFF);   
   
   // Initialize the upper layer driver
   _eth_drv_init(sc,enaddr);

   return (true);
}

//======================================================================
// This function is called to stop the interface.

static void 
at91_eth_stop(struct eth_drv_sc *sc)
{
   at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
   
   at91_disable(priv);

   // The TXQP gets reset on start, so we need to reset the index here.
   priv->curr_tbd_idx = 0;
}

//======================================================================
// This function is called to "start up" the interface. It may be called
// multiple times, even when the hardware is already running.

static void
at91_eth_start(struct eth_drv_sc *sc, unsigned char *enaddr, int flags)
{
   at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
   
   // Enable the receiver and transmitter
   at91_enable(priv);

}

//======================================================================
// This function is called for low level "control" operations

static int
at91_eth_control(struct eth_drv_sc *sc, unsigned long key,
                 void *data, int length)
{

   switch (key)
   {
      case ETH_DRV_SET_MAC_ADDRESS:
         {
            if(length >= ETHER_ADDR_LEN)
            {
               at91_eth_stop(sc);

               cyg_uint8 * enaddr = (cyg_uint8 *)data;
               debug1_printf("AT91_ETH: %02x:%02x:%02x:%02x:%02x:%02x\n",
                             enaddr[0],enaddr[1],enaddr[2],
                             enaddr[3],enaddr[4],enaddr[5]);

               at91_set_mac((at91_eth_priv_t *)sc->driver_private,enaddr,1);
               at91_eth_start(sc,enaddr,0);
               return 0;
            }
            return 1;
         }

#ifdef ETH_DRV_SET_MC_ALL
    case ETH_DRV_SET_MC_ALL:
    case ETH_DRV_SET_MC_LIST:
    {
        at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
        cyg_uint32 ncfg;

        at91_disable_rx(priv);
        
        // Set all bits in hash table and enable multicast in config.
        HAL_WRITE_UINT32( priv->base+AT91_EMAC_HRB, 0xffffffff );
        HAL_WRITE_UINT32( priv->base+AT91_EMAC_HRT, 0xffffffff );
        HAL_READ_UINT32( priv->base+AT91_EMAC_NCFG, ncfg );
        ncfg |= AT91_EMAC_NCFG_MTI;
        HAL_WRITE_UINT32( priv->base+AT91_EMAC_NCFG, ncfg );

        at91_enable_rx(priv);        
        return 1;
    }
#endif
         
      default:
         {
            debug2_printf("%s.%d: key %lx\n", __FUNCTION__, __LINE__, key);
            return (1);
         }
   }

}

//======================================================================
// This function is called to see if another packet can be sent.
// It should return the number of packets which can be handled.
// Zero should be returned if the interface is busy and can not send
// any more.
//
// We allocate one buffer descriptor per scatter/gather entry. We assume that
// a typical packet will not have more than 3 such entries, and so we say we
// can send a packet when we have 3 or more buffer descriptors free
//
// TODO: Implement what the comment actually says!

static int
at91_eth_can_send(struct eth_drv_sc *sc)
{
   int can_send;
   at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
   if(priv->tx_busy)
   {
      can_send = 0;
   }
   else
   {
      can_send = 1;
   }
   return (can_send);
}

//======================================================================
// This routine is called to send data to the hardware

static void
at91_eth_send(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, 
              int total_len, unsigned long key)
{
   at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
   int i;
   cyg_uint32 sr;

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    cyg_drv_interrupt_mask(priv->intr_vector);
#endif
   
   priv->tx_busy = true;

   priv->last_tbd_idx = priv->curr_tbd_idx;

   XT(0xdddd0000|priv->last_tbd_idx);
   
   for(i = 0;i<sg_len;i++)
   {
      priv->tbd[priv->curr_tbd_idx].addr = CYGARC_PHYSICAL_ADDRESS(sg_list[i].buf);

      debug2_printf("buf %p len %08x\n", sg_list[i].buf, sg_list[i].len );
      
      sr = (sg_list[i].len & AT91_EMAC_TBD_SR_LEN_MASK);

      // Set the End Of Frame bit in the last descriptor
      if(i == (sg_len-1))
      {
         sr |= AT91_EMAC_TBD_SR_EOF;
      }

      if(priv->curr_tbd_idx < (CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS-1))
      {
         priv->tbd[priv->curr_tbd_idx].sr = sr;
         priv->curr_tbd_idx++;
      }
      else
      {
         priv->tbd[priv->curr_tbd_idx].sr = (sr | AT91_EMAC_TBD_SR_WRAP);
         priv->curr_tbd_idx = 0;
      }

      
   }

   HAL_DCACHE_SYNC();
   
   // Store away the key for when the transmit has completed
   // and we need to tell the stack which transmit has completed.
   priv->curr_tx_key = key;

   X(0xddff0000|priv->curr_tbd_idx);
   X(key);
   
//   debug2_printf("c %d l %d\n", priv->curr_tbd_idx, priv->last_tbd_idx);   
   at91_start_transmitter(priv);

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    cyg_drv_interrupt_unmask(priv->intr_vector);
#endif
   
}

//======================================================================

static void at91_reset_tbd(at91_eth_priv_t *priv)
{
     while(priv->curr_tbd_idx != priv->last_tbd_idx)
     {
        if(priv->last_tbd_idx == (CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS-1))
        {
           priv->tbd[priv->last_tbd_idx].sr = 
             (AT91_EMAC_TBD_SR_USED|AT91_EMAC_TBD_SR_WRAP);
           priv->last_tbd_idx = 0;
        }
        else
        {
           priv->tbd[priv->last_tbd_idx].sr = AT91_EMAC_TBD_SR_USED;
           priv->last_tbd_idx++;
        }
     }
}

//======================================================================

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
static cyg_uint32
at91_eth_isr (cyg_vector_t vector, cyg_addrword_t data)
{
    struct eth_drv_sc *sc = (struct eth_drv_sc *)data;
    at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;

    cyg_drv_interrupt_mask(priv->intr_vector);

    return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR);  // Run the DSR
}
#endif

//======================================================================

void at91_eth_release( at91_eth_priv_t *priv, int first, int last )
{
    debug2_printf("release RXBDs %d to %d\n", first, last );
    X(0xaa330000|first);
    
    while ( first != last )
    {
        priv->rbd[first].sr = 0;
        priv->rbd[first].addr &= ~AT91_EMAC_RBD_ADDR_OWNER_SW;
        first++;
        if( first >= CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS )
            first = 0;
    }
}

//======================================================================

void at91_eth_rx( struct eth_drv_sc *sc )
{
    at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;    
    int current_rxbd;
    int start_of_frame = -1;
    cyg_uint32 addr, sr;

    for( current_rxbd = priv->curr_rbd_idx                                                      ;
         true                                                                                   ;
         current_rxbd = ((current_rxbd+1) < CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS)?(current_rxbd+1):0)
    {
        addr = priv->rbd[current_rxbd].addr;
        sr = priv->rbd[current_rxbd].sr;

        debug2_printf("rxbd[%d] %08x %08x\n", current_rxbd, addr, sr );
        X(0xaa110000|current_rxbd);
        X(addr);
        X(sr);

        // Terminate search on a buffer still owned by EMAC
        if( (addr & AT91_EMAC_RBD_ADDR_OWNER_SW) == 0 )
            break;

        if ( sr & AT91_EMAC_RBD_SR_SOF )
        {
            // Found a SOF bit.
            debug2_printf("SOF @ %d\n", current_rxbd );
            X(0xaa220000|current_rxbd);
            
            if ( start_of_frame >= 0 )
            {
                // Repeat SOF, release the buffers we have already found.
                
                at91_eth_release( priv, start_of_frame, current_rxbd );
            }
            start_of_frame = current_rxbd;
        }

        if ( sr & AT91_EMAC_RBD_SR_EOF )
        {
            // Found an EOF bit.
            if( start_of_frame < 0 )
            {
                // EOF with no SOF, return this RBD to hardware
                X(0xaa550000|current_rxbd);
                priv->rbd[current_rxbd].sr = 0;
                priv->rbd[current_rxbd].addr &= ~AT91_EMAC_RBD_ADDR_OWNER_SW;
            }
            else
            {
                // Found SOF...EOF sequence, pass it up the stack.
                debug2_printf("EOF @ %d SOF @ %d\n", current_rxbd, start_of_frame );
                X(0xaa440000|(start_of_frame<<8)|current_rxbd);

                priv->recv_idx = start_of_frame;
                _eth_drv_recv( sc, priv->rbd[current_rxbd].sr&AT91_EMAC_RBD_SR_LEN_MASK);
                
                start_of_frame = -1;
            }
        }
    }

    if( start_of_frame != -1 )
        priv->curr_rbd_idx = start_of_frame;
    else
        priv->curr_rbd_idx = current_rxbd;
    
}

//======================================================================

static void 
at91_eth_deliver(struct eth_drv_sc *sc)
{
    at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;

    cyg_uint32 tsr;
    cyg_uint32 rsr;
    cyg_uint32 isr;

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    cyg_drv_interrupt_mask(priv->intr_vector);
#endif
    
    for(;;)
    {
        /* Clear the interrupt status */
        HAL_READ_UINT32(priv->base+AT91_EMAC_ISR,isr);

//        HAL_READ_UINT32(priv->base+AT91_EMAC_IMR,imr);
//        debug2_printf("isr %08x imr %08x\n", isr, imr);
   
        /* Get the Transmit Status */
        HAL_READ_UINT32(priv->base+AT91_EMAC_TSR,tsr);
        HAL_WRITE_UINT32(priv->base+AT91_EMAC_TSR,tsr);
           
        /* Get the Receive Status */
        HAL_READ_UINT32(priv->base+AT91_EMAC_RSR,rsr);
        HAL_WRITE_UINT32(priv->base+AT91_EMAC_RSR,rsr);
        debug2_printf("tsr %08x rsr %08x\n", tsr, rsr);

        // Abort if there are no events to process
        if( tsr == 0 && rsr == 0 )
            break;
        
        if (tsr&(AT91_EMAC_TSR_COL|AT91_EMAC_TSR_RLE|AT91_EMAC_TSR_BEX|AT91_EMAC_TSR_UND) )
        {
            // An error condition, abort the tx and reset the transmitter.
            
            debug1_printf("Tx error\n");
            X(0xccaa0000|tsr);

            while( tsr & AT91_EMAC_TSR_TGO )
            {
                HAL_READ_UINT32(priv->base+AT91_EMAC_TSR,tsr);
            }            
            at91_reset_tbd(priv);
            // Reset the transmitter
            at91_disable_tx(priv);
            priv->curr_tbd_idx = 0;
            at91_enable_tx(priv);
                
            if( priv->curr_tx_key )                
                _eth_drv_tx_done(sc,priv->curr_tx_key,0);
            priv->tx_busy = false;
            priv->curr_tx_key = 0;
            tsr = 0;
        }
        

       /* Service the TX buffers */
        
        /* Check that the last transmission is completed */
        if ( tsr&AT91_EMAC_TSR_COMP && priv->tx_busy )
        {
            XT(0xcc220000|priv->curr_tbd_idx);
            X(tsr);
            // Wait for transmitter to really stop. On the SAM9, at least,
            // there appears to be a race condition between setting the
            // COMP and UBR bits and the MAC actually stopping. If we call
            // at91_reset_tbd() immediately here, it is possible to catch
            // up with the MAC and cause it to generate BEX and UND
            // errors. These seem to happen when wrapping the buffer chain.
            while( tsr & AT91_EMAC_TSR_TGO )
            {
                HAL_READ_UINT32(priv->base+AT91_EMAC_TSR,tsr);
            }
            XT(0xcc330000|priv->curr_tbd_idx);
            X(tsr);

            at91_reset_tbd(priv);
            
            if( priv->curr_tx_key )
                _eth_drv_tx_done(sc,priv->curr_tx_key,0);
            priv->tx_busy = false;
            priv->curr_tx_key = 0;            
        }

        
        /* Service the RX buffers when we get something */
        
        at91_eth_rx( sc );

        if (rsr&AT91_EMAC_RSR_BNA)
        {
            // Receive buffer not available. We have already consumed
            // any full packets available above, so just reset the
            // buffer queue to its original state.

            X(0x88880000|rsr);
            debug2_printf("Rx BNA\n");
            
            at91_disable_rx(priv);
            at91_rb_init(priv);
            HAL_WRITE_UINT32(priv->base + AT91_EMAC_RBQP, CYGARC_PHYSICAL_ADDRESS(priv->rbd_c));
            priv->curr_rbd_idx = 0;
            at91_enable_rx(priv);
        }
        if (rsr&AT91_EMAC_RSR_OVR)
        {
            debug1_printf("Rx OVR\n");
        }
    }
    
    cyg_drv_interrupt_acknowledge(priv->intr_vector);
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    cyg_drv_interrupt_unmask(priv->intr_vector);
#endif
   
}

//======================================================================

static void
at91_eth_recv(struct eth_drv_sc *sc,
              struct eth_drv_sg *sg_list,
              int sg_len)
{
    at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
    int i;
    cyg_uint32 bytes_in_buffer = 0;
    cyg_uint32 bytes_needed_list = 0;
    cyg_uint32 buffer_pos = 0;
    cyg_uint8 * sg_buf;
    cyg_uint32 total_bytes = 0;
    cyg_uint32 idx = priv->recv_idx;

    X(0xbb110000|sg_len);
    X(idx);
    
    for(i = 0;i<sg_len;i++)
    {
        cyg_uint32 bytes_in_list = 0;
       
        debug2_printf("sg.buf[%d] %p sg.len %d\n", i, sg_list[i].buf, sg_list[i].len );
       
        while(bytes_in_list < sg_list[i].len)
        {
            bytes_needed_list = sg_list[i].len - bytes_in_list;

            debug2_printf("rbd: %p: %08x %08x\n", &priv->rbd[idx], priv->rbd[idx].addr, priv->rbd[idx].sr );
            if(priv->rbd[idx].sr & AT91_EMAC_RBD_SR_EOF)
            {
                bytes_in_buffer = 
                    ((priv->rbd[idx].sr & AT91_EMAC_RBD_SR_LEN_MASK) - total_bytes);
            }
            else
            {
                bytes_in_buffer = AT91_EMAC_RX_BUFF_SIZE - buffer_pos;
            }

            sg_buf = (cyg_uint8 *)(sg_list[i].buf);
            X(0xbb440000);
            X(sg_buf);
            
            if(bytes_needed_list < bytes_in_buffer)
            {
                if(sg_buf != NULL)
                {
                    X(0xbb550000|bytes_needed_list);
                    X(&sg_buf[bytes_in_list]);
                    memcpy(&sg_buf[bytes_in_list],
                           &priv->rb[idx].rb[buffer_pos],
                           bytes_needed_list);
                }
                bytes_in_list += bytes_needed_list;
                buffer_pos += bytes_needed_list;
                total_bytes += bytes_needed_list;
            }
            else
            {
                if(sg_buf != NULL)
                {
                    X(0xbb660000|bytes_in_buffer);
                    X(&sg_buf[bytes_in_list]);
                    memcpy(&sg_buf[bytes_in_list],
                           &priv->rb[idx].rb[buffer_pos],
                           bytes_in_buffer);
                }
                bytes_in_list += bytes_in_buffer;
                total_bytes += bytes_in_buffer;

                /* Step our buffer on one */
                priv->rbd[idx].sr = 0;
                priv->rbd[idx].addr &= ~(AT91_EMAC_RBD_ADDR_OWNER_SW);
                idx++;
                if(idx >= CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS)
                    idx = 0;
                buffer_pos = 0;
            }
        }
    }

    X(0xbbff0000);
}

//======================================================================
// routine called to handle ethernet controller in polled mode

static void 
at91_eth_poll(struct eth_drv_sc *sc)
{
    /* Service the buffers */
    at91_eth_deliver(sc);
}

//======================================================================

static int
at91_eth_int_vector(struct eth_drv_sc *sc)
{
    return(CYGNUM_HAL_INTERRUPT_EMAC);
}

at91_eth_priv_t at91_priv_data =
{
    .intr_vector = CYGNUM_HAL_INTERRUPT_EMAC,
    .base = AT91_EMAC,
    .phy = &at91_phy
};

//======================================================================

ETH_DRV_SC(at91_sc,
           &at91_priv_data,       // Driver specific data
           "eth0",                // Name for this interface
           at91_eth_start,
           at91_eth_stop,
           at91_eth_control,
           at91_eth_can_send,
           at91_eth_send,
           at91_eth_recv,
           at91_eth_deliver,
           at91_eth_poll,
           at91_eth_int_vector);

NETDEVTAB_ENTRY(at91_netdev,
                "at91",
                at91_eth_init,
                &at91_sc);

//======================================================================
// EOF if_at91.c
