//==========================================================================
//
//      if_lpc2xxx.c
//
//
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2008 Free Software Foundation, Inc.
// Copyright (C) 2008 eCosCentric Limited                                   
//
// 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):    nickg
// Contributors:  
// Date:         2008-02-11
// Purpose:
// Description:
//
//####DESCRIPTIONEND####
//
//========================================================================*/

#include <pkgconf/system.h>
#include <pkgconf/hal.h>
#include <pkgconf/devs_eth_arm_lpc2xxx.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 LPC2XXX_ETH_DELAY_US(us) CYGACC_CALL_IF_DELAY_US(us)
#else
# include <cyg/hal/hal_diag.h>
# define LPC2XXX_ETH_DELAY_US(us) HAL_DELAY_US(us)
#endif

#ifndef CYGARC_PHYSICAL_ADDRESS
#define CYGARC_PHYSICAL_ADDRESS(__x) (__x)
#endif

#ifndef CYGARC_UNCACHED_ADDRESS
#define CYGARC_UNCACHED_ADDRESS(__x) (__x)
#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);
}
#else
#define db_printf diag_printf
#define db_dump_buf diag_dump_buf
#endif

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

//#undef  CYGPKG_DEVS_ETH_ARM_LPC2XXX_DEBUG_LEVEL
//#define CYGPKG_DEVS_ETH_ARM_LPC2XXX_DEBUG_LEVEL 1
//#define CYGPKG_DEVS_ETH_ARM_LPC2XXX_DEBUG_LEVEL 2

#if CYGPKG_DEVS_ETH_ARM_LPC2XXX_DEBUG_LEVEL > 0
   #define debug1_printf( __fmt, ... ) db_printf("LPC2XXX_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_LPC2XXX_DEBUG_LEVEL > 1
   #define debug2_printf( __fmt, ... ) db_printf("LPC2XXX_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

// The following enables a low-intrusion trace mechanism.
#if 0
#define XLEN 256
cyg_uint32 x[XLEN];
cyg_uint32 xpos = 0;
#define X(__x)                                  \
{                                               \
    x[xpos++ & (XLEN-1)] = (__x);               \
    x[xpos & (XLEN-1)] = 0xFFFFFFFF;            \
}
#define XRES()                                          \
{                                                       \
    xpos = 0;                                           \
    x[xpos & (XLEN-1)] = 0xFFFFFFFF;                    \
}
#else
#define X(__x)
#define XRES()
#endif


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

// Include PHY stuff after debug macro defs.

#define phy_debug_printf debug2_printf
//#define phy_debug_printf(...) 
#include "lpc2xxx_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
lpc2xxx_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_LPC2XXX_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_LPC2XXX_REDBOOT_HOLDS_ESA

   #include <cyg/hal/hal_if.h>

   #ifndef CONFIG_ESA
      #define CONFIG_ESA (6)
   #endif

  #define CYGHWR_DEVS_ETH_ARM_LPC2XXX_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_LPC2XXX_ETH_REDBOOT_HOLDS_ESA

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

#define LPC2XXX_ETH_RAM 0x7FE00000

#ifndef LPC2XXX_EMAC_RX_BUFF_SIZE
#define LPC2XXX_EMAC_RX_BUFF_SIZE  0x600
#endif

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

// Receive Status Descriptor
typedef struct rsd_s
{
   cyg_uint32 info;
   cyg_uint32 hash_crc;
} rsd_t;


// Receive (and occasionally Transmit) Buffer
typedef struct rb_s 
{
   cyg_uint8 rb[LPC2XXX_EMAC_RX_BUFF_SIZE];
} rb_t;

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

// Transmit Status Descriptor
typedef struct tsd_s
{
   cyg_uint32 info;
} tsd_t;

// LPC2XXX Ethernet private data
typedef struct lpc2xxx_eth_priv_s 
{
    cyg_uint32          vector;
    char                *esa_key;      // RedBoot 'key' for device ESA
    cyg_uint8           *enaddr;
    cyg_uint32          base;           // Base address of device
    
    cyg_int32           phy_vector;     // PHY interrupt vector
    cyg_int32           phy_pin;        // GPIO pin for phy interrupt
    eth_phy_access_t    *phy;
    
    rbd_t               *rbd;
    rsd_t               *rsd;
    rb_t                *rb;
    cyg_uint32          curr_rbd_idx;

#ifdef CYGPKG_DEVS_ETH_ARM_LPC2XXX_USE_EXTERNAL_RX_BUFFERS
    rb_t                rbb[CYGNUM_DEVS_ETH_ARM_LPC2XXX_RX_BUFS];
#endif

#ifndef CYGPKG_DEVS_ETH_ARM_LPC2XXX_USE_EXTERNAL_TX_BUFFERS    
    rb_t                *tb;
#endif
    
    tbd_t               *tbd;
    tsd_t               *tsd;
    unsigned long       curr_tx_key;
    cyg_bool            tx_busy;
    
    cyg_uint32          last_tbd_idx;
    cyg_uint32          curr_tbd_idx;
    
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    cyg_interrupt       intr;
    cyg_handle_t        intr_handle;
    cyg_interrupt       phy_intr;
    cyg_handle_t        phy_intr_handle;
#endif
    
} lpc2xxx_eth_priv_t;

//======================================================================
// Receiver buffer handling
//
// Initialize the receiver buffers and descriptors

static void
lpc2xxx_rb_init(lpc2xxx_eth_priv_t *priv)
{
   int i;
   debug2_printf("\n");
   for (i = 0 ; i < CYGNUM_DEVS_ETH_ARM_LPC2XXX_RX_BUFS; i++)
   {
      priv->rbd[i].addr = CYGARC_PHYSICAL_ADDRESS((cyg_uint32)&priv->rb[i]);
      priv->rbd[i].ctrl = LPC2XXX_EMAC_RX_BUFF_SIZE | CYGARC_HAL_LPC2XXX_RXBD_CTRL_INTR;
      priv->rsd[i].info = 0;
      priv->rsd[i].hash_crc = 0;
   }
}

//======================================================================
// Transmit buffer handling
//
// Initialize the transmit buffer descriptors

static void 
lpc2xxx_tb_init(lpc2xxx_eth_priv_t *priv)
{
   int i;
   debug2_printf("\n");
   for (i = 0 ; i < CYGNUM_DEVS_ETH_ARM_LPC2XXX_TX_BUFS; i++)
   {
#ifdef CYGPKG_DEVS_ETH_ARM_LPC2XXX_USE_EXTERNAL_TX_BUFFERS       
      priv->tbd[i].addr = 0;
#else
      priv->tbd[i].addr = CYGARC_PHYSICAL_ADDRESS((cyg_uint32)&priv->tb[i]);
#endif
      priv->tbd[i].ctrl = 0;
      priv->tsd[i].info = 0;
   }
}

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

static void
lpc2xxx_disable_rx(lpc2xxx_eth_priv_t *priv)
{
   cyg_uint32 cmd;
   debug2_printf("\n");
   
   HAL_READ_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd);
   cmd &= ~CYGARC_HAL_LPC2XXX_REG_ETH_CMD_RXENABLE;
   HAL_WRITE_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd);
}

static void
lpc2xxx_disable_tx(lpc2xxx_eth_priv_t *priv)
{
   cyg_uint32 cmd;

   debug2_printf("\n");
   HAL_READ_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd);
   cmd &= ~CYGARC_HAL_LPC2XXX_REG_ETH_CMD_TXENABLE;
   HAL_WRITE_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd);
}

static void
lpc2xxx_enable_rx(lpc2xxx_eth_priv_t *priv)
{
   cyg_uint32 cmd;

   debug2_printf("\n");
   HAL_READ_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd);
   cmd |= CYGARC_HAL_LPC2XXX_REG_ETH_CMD_RXENABLE;
   HAL_WRITE_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd);
}

static void
lpc2xxx_enable_tx(lpc2xxx_eth_priv_t *priv)
{
   cyg_uint32 cmd;

   debug2_printf("\n");
   HAL_READ_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd);
   cmd |= CYGARC_HAL_LPC2XXX_REG_ETH_CMD_TXENABLE;
   HAL_WRITE_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd);
}

static void 
lpc2xxx_enable(lpc2xxx_eth_priv_t *priv)
{
   debug2_printf("\n");
   lpc2xxx_enable_tx(priv);
   lpc2xxx_enable_rx(priv);
}

static void 
lpc2xxx_disable(lpc2xxx_eth_priv_t *priv)
{
   debug2_printf("\n");
   lpc2xxx_disable_tx(priv);
   lpc2xxx_disable_rx(priv);
}

static void
lpc2xxx_start_transmitter(lpc2xxx_eth_priv_t *priv)
{
}


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

// Configure the pins so that the EMAC has control of them. 

static void
lpc2xxx_cfg_pins(void)
{

#if defined(CYGHWR_HAL_ARM_LPC2XXX_LPC2468) || \
    defined(CYGHWR_HAL_ARM_LPC2XXX_LPC2478) || \
    defined(CYGHWR_HAL_ARM_LPC2XXX_LPC23XX)

    debug2_printf("\n");
    cyg_uint32 base = CYGARC_HAL_LPC2XXX_REG_PIN_BASE;

    // The following is something of a kludge to detect the CPU
    // revision and set up PINSEL2 correctly. There is a bug in the
    // engineering samples that requires P1.6, ENET-TX_CLK, to be
    // allocated.
    // This works around the Ethernet.1 erratum.

    cyg_uint32 id;

    HAL_READ_UINT32( CYGARC_HAL_LPC2XXX_REG_ETH_BASE+0xFFC, id );

    if( id == ((0x3902 << 16) | 0x2000) )
    {
        // Engineering sample
        HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_PINSEL2, 0x50151105 );
    }
    else
    {
        // All other revisions
        HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_PINSEL2, 0x50150105 );
    }
    
    HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_PINSEL3, 0x00000005 );
    
#else

#error Unknown LPC2XXX variant
   
#endif
}

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

static void
lpc2xxx_set_mac(lpc2xxx_eth_priv_t * priv, cyg_uint8 * enaddr)
{
   cyg_uint32 hi, mid, lo;

   debug2_printf("\n");
   hi  = ((enaddr[1] << 8) |
          (enaddr[0] << 0));

   mid = ((enaddr[3] << 8) |
          (enaddr[2] << 0));
       
   lo  = ((enaddr[5] << 8) |
          (enaddr[4] << 0));

   HAL_WRITE_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_SA0, lo);
   HAL_WRITE_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_SA1, mid);
   HAL_WRITE_UINT32(priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_SA2, hi);
}

//======================================================================
// PHY control

static cyg_bool lpc2xxx_phy_init( lpc2xxx_eth_priv_t *priv)
{
   CYG_ADDRESS base = priv->base;    
   unsigned short phy_state = 0;
   cyg_uint32 mac2 = 0;
   cyg_uint32 cmd = 0, supp = 0;
   cyg_uint32 ipgt = 0;
    
   // Get the current mode and print it. This will also wait for the
   // autonegotiation to complete.
   phy_state = _eth_phy_state(priv->phy);

   debug2_printf("phy_state %04x\n", phy_state);

   // If the link fails to come up, fail the initialization.
   if ((phy_state & ETH_PHY_STAT_LINK) == 0)
       return false;

   // Set up MAC according to negotiated PHY characteristics
   
   mac2 = CYGARC_HAL_LPC2XXX_REG_ETH_MAC2_CRC_ENABLE |
          CYGARC_HAL_LPC2XXX_REG_ETH_MAC2_PAD;

   supp = 0;

   ipgt = 18;
   
   cmd = CYGARC_HAL_LPC2XXX_REG_ETH_CMD_PASSRUNT |
         CYGARC_HAL_LPC2XXX_REG_ETH_CMD_RMII;


   if( phy_state & ETH_PHY_STAT_100MB )
       supp |= CYGARC_HAL_LPC2XXX_REG_ETH_SUPP_SPEED_100;
       
   if( phy_state & ETH_PHY_STAT_FDX )
       mac2 |= CYGARC_HAL_LPC2XXX_REG_ETH_MAC2_FULL_DUPLEX,
       cmd |= CYGARC_HAL_LPC2XXX_REG_ETH_CMD_DUPLEX,
       ipgt = 21;
           
   debug2_printf("mac2 %08x supp %08x cmd %08x ipgt %08x\n", mac2, supp, cmd, ipgt );
   
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_MAC2, mac2 );
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_SUPP, supp );
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd );
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_IPGT, ipgt );

   return true;
}

//----------------------------------------------------------------------
// PHY interrupt ISR, DSR and init

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
static cyg_uint32
lpc2xxx_phy_isr (cyg_vector_t vector, cyg_addrword_t data)
{
    cyg_uint16 icsr;    
    lpc2xxx_eth_priv_t *priv = (lpc2xxx_eth_priv_t *)data;

    cyg_drv_interrupt_mask(priv->phy_vector);

    // Ack interrupt in INTC and PHY
    cyg_drv_interrupt_acknowledge(priv->phy_vector);
    _eth_phy_read( priv->phy, 0x1b, priv->phy->phy_addr, &icsr );
    
    debug2_printf("icsr %04x\n", icsr);

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

static void
lpc2xxx_phy_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    lpc2xxx_eth_priv_t *priv = (lpc2xxx_eth_priv_t *)data;

    debug2_printf("\n");
    
    lpc2xxx_phy_init( priv );

    cyg_drv_interrupt_unmask(priv->phy_vector);    
}

static void lpc2xxx_phy_int_init( lpc2xxx_eth_priv_t *priv)
{
    cyg_uint16 icsr;
    
    debug2_printf("phy_vector %d\n", priv->phy_vector);

    // Set interrupt pin function
    __LPC2XXX_PINSEL_FUNCTION( priv->phy_pin );
    
    cyg_drv_interrupt_create(priv->phy_vector,
                             CYGNUM_DEVS_ETH_ARM_LPC2XXX_INTRPRI,
                             (cyg_addrword_t)priv,
                             lpc2xxx_phy_isr,
                             lpc2xxx_phy_dsr,
                             &priv->phy_intr_handle,
                             &priv->phy_intr);

    cyg_drv_interrupt_attach(priv->phy_intr_handle);
    cyg_drv_interrupt_unmask(priv->phy_vector);

    // Configure interrupt to be active low
    cyg_drv_interrupt_configure( priv->phy_vector, true, false );
   
    // Enable interrupts in PHY
    // WARNING: At present this only supports the Micrel KSZ8001 on
    // the EA LPC2468-16 board. Some more configury will be needed if
    // this is to be used on a different board.
    _eth_phy_read( priv->phy, 0x1b, priv->phy->phy_addr, &icsr );
    debug2_printf("icsr %04x\n", icsr);   
    icsr |= 1<<8;
    _eth_phy_write( priv->phy, 0x1b, priv->phy->phy_addr, icsr );
    debug2_printf("icsr %04x\n", icsr);   
}
#endif

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

static void
lpc2xxx_clear_stats(lpc2xxx_eth_priv_t *priv)
{
   debug2_printf("\n");
}

//======================================================================
// 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
lpc2xxx_eth_init(struct cyg_netdevtab_entry *tab)
{
   struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance;
   lpc2xxx_eth_priv_t *priv = (lpc2xxx_eth_priv_t *)sc->driver_private;
   CYG_ADDRESS base = priv->base;
   bool esa_ok = false;
   unsigned char enaddr[6] = { CYGDAT_DEVS_ETH_ARM_LPC2XXX_MACADDR};
   cyg_uint32 mac1 = 0;//, mac2 = 0;
   cyg_uint32 cmd = 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(LPC2XXX_ETH_RAM);
   priv->rsd = (rsd_t*)CYGARC_UNCACHED_ADDRESS(priv->rbd+CYGNUM_DEVS_ETH_ARM_LPC2XXX_RX_BUFS);
   priv->tbd = (tbd_t*)CYGARC_UNCACHED_ADDRESS(priv->rsd+CYGNUM_DEVS_ETH_ARM_LPC2XXX_RX_BUFS);
   priv->tsd = (tsd_t*)CYGARC_UNCACHED_ADDRESS(priv->tbd+CYGNUM_DEVS_ETH_ARM_LPC2XXX_TX_BUFS);

#ifdef CYGPKG_DEVS_ETH_ARM_LPC2XXX_USE_EXTERNAL_RX_BUFFERS   
   priv->rb  = (rb_t*)CYGARC_UNCACHED_ADDRESS(&priv->rbb[0]);
#else
   priv->rb  = (rb_t*)CYGARC_UNCACHED_ADDRESS(priv->tsd+CYGNUM_DEVS_ETH_ARM_LPC2XXX_TX_BUFS);
#endif
#ifndef CYGPKG_DEVS_ETH_ARM_LPC2XXX_USE_EXTERNAL_TX_BUFFERS   
   priv->tb  = (rb_t*)CYGARC_UNCACHED_ADDRESS(priv->rb+CYGNUM_DEVS_ETH_ARM_LPC2XXX_RX_BUFS);
#endif

   
   lpc2xxx_cfg_pins();

   // Reset all
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_MAC1, 0x0000CF00 );   
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, 0x00000038 );

   LPC2XXX_ETH_DELAY_US(10000);

   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_MAC1, 0x00000000 );

   // Disable TX and RX
   HAL_READ_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd );
   cmd &= ~(CYGARC_HAL_LPC2XXX_REG_ETH_CMD_TXENABLE|CYGARC_HAL_LPC2XXX_REG_ETH_CMD_RXENABLE);
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_CMD, cmd );

   // Set default interpacket gap
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_IPGR, 18 );

   // Collision window/retry register. Magic value from EA/manual.
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_CLRT, 0x370F );

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

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

#ifdef CYGHWR_DEVS_ETH_ARM_LPC2XXX_GET_ESA

   // Get MAC address from RedBoot configuration variables
   CYGHWR_DEVS_ETH_ARM_LPC2XXX_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("LPC2XXX_ETH - Warning! ESA unknown\n");
   }
   debug1_printf("LPC2XXX_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
   lpc2xxx_set_mac(priv, enaddr);

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

   // And tell the EMAC where the to find the descriptors
   HAL_WRITE_UINT32(base + CYGARC_HAL_LPC2XXX_REG_ETH_RXDESC, (CYG_ADDRWORD)priv->rbd);
   HAL_WRITE_UINT32(base + CYGARC_HAL_LPC2XXX_REG_ETH_RXSTATUS, (CYG_ADDRWORD)priv->rsd);
   HAL_WRITE_UINT32(base + CYGARC_HAL_LPC2XXX_REG_ETH_RXDESCNUM, CYGNUM_DEVS_ETH_ARM_LPC2XXX_RX_BUFS-1);
   HAL_WRITE_UINT32(base + CYGARC_HAL_LPC2XXX_REG_ETH_RXCONSIX, 0);
   
   // Setup the transmit descriptors
   lpc2xxx_tb_init(priv);

   // And tell the EMAC where the to find the descriptors
   HAL_WRITE_UINT32(base + CYGARC_HAL_LPC2XXX_REG_ETH_TXDESC, (CYG_ADDRWORD)priv->tbd);
   HAL_WRITE_UINT32(base + CYGARC_HAL_LPC2XXX_REG_ETH_TXSTATUS, (CYG_ADDRWORD)priv->tsd);
   HAL_WRITE_UINT32(base + CYGARC_HAL_LPC2XXX_REG_ETH_TXDESCNUM, CYGNUM_DEVS_ETH_ARM_LPC2XXX_TX_BUFS-1);
   HAL_WRITE_UINT32(base + CYGARC_HAL_LPC2XXX_REG_ETH_TXPRODIX, 0);

   
   // Setup the PHY
   CYG_ASSERTC(priv->phy);
   
   if (!_eth_phy_init(priv->phy))
      return (false);

    // Reset PHY
   {
       long timeout = 100000;
       cyg_uint16 bmcr;

       _eth_phy_write(priv->phy, 0, priv->phy->phy_addr, 0x8000 );       

       LPC2XXX_ETH_DELAY_US(100);    

       while (--timeout != 0)
       {
           _eth_phy_read(priv->phy, 0, priv->phy->phy_addr, &bmcr );
           debug2_printf("bmcr %04x\n", bmcr );
           if ((bmcr & 0x8000 ) == 0)
               break;
       }

       if (timeout == 0)
           diag_printf("  Error: failed to reset PHY\n");
   }

   // Force an autonegotiation
   {
       unsigned short reg;
       _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 );
   }

   // Init MAC from PHY
   lpc2xxx_phy_init( priv );

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED   
   if( priv->phy_vector != 0 )
       lpc2xxx_phy_int_init( priv );
#endif
   
   // Clear the Statistics counters;
   lpc2xxx_clear_stats(priv);

   // Enable RX, pass all RX frames
   HAL_READ_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_MAC1, mac1 );
   mac1 |= CYGARC_HAL_LPC2XXX_REG_ETH_MAC1_RX_ENABLE;
   mac1 |= CYGARC_HAL_LPC2XXX_REG_ETH_MAC1_RX_PASS_ALL;
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_MAC1, mac1 );   

   // set up the Rx filter 
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_RXFILTERCTL, 0x0022 );

   // Clear all interrupts
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, 0xFFFF );      

   // Enable useful interrupts
   HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTENABLE, 0x00FF );         
   
   // Initialize the upper layer driver
   _eth_drv_init(sc,enaddr);

   return (true);
}

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

static void 
lpc2xxx_eth_stop(struct eth_drv_sc *sc)
{
   lpc2xxx_eth_priv_t *priv = (lpc2xxx_eth_priv_t *)sc->driver_private;
   
   debug2_printf("\n");
   lpc2xxx_disable(priv);
}

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

static void
lpc2xxx_eth_start(struct eth_drv_sc *sc, unsigned char *enaddr, int flags)
{
   lpc2xxx_eth_priv_t *priv = (lpc2xxx_eth_priv_t *)sc->driver_private;
   
   debug2_printf("\n");
   // Enable the receiver and transmitter
   lpc2xxx_enable(priv);

}

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

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

   debug2_printf("\n");
   switch (key)
   {
      case ETH_DRV_SET_MAC_ADDRESS:
         {
            if(length >= ETHER_ADDR_LEN)
            {
               lpc2xxx_eth_stop(sc);

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

               lpc2xxx_set_mac((lpc2xxx_eth_priv_t *)sc->driver_private,enaddr);
               lpc2xxx_eth_start(sc,enaddr,0);
               return 0;
            }
            return 1;
         }
      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
lpc2xxx_eth_can_send(struct eth_drv_sc *sc)
{
   lpc2xxx_eth_priv_t *priv = (lpc2xxx_eth_priv_t *)sc->driver_private;

   return !priv->tx_busy;
}

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

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

   debug2_printf("sglen %d len %d key %08x\n", sg_len, total_len, key);

   priv->tx_busy = true;
   
   priv->last_tbd_idx = priv->curr_tbd_idx;
   
#ifdef CYGPKG_DEVS_ETH_ARM_LPC2XXX_USE_EXTERNAL_TX_BUFFERS      
   
   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 );
      
      ctrl = (sg_list[i].len-1) & CYGARC_HAL_LPC2XXX_TXBD_CTRL_SIZE;

      // Set interrupt and last bits in last descriptor of frame
      if(i == (sg_len-1))
      {
         ctrl |= CYGARC_HAL_LPC2XXX_TXBD_CTRL_INTR | CYGARC_HAL_LPC2XXX_TXBD_CTRL_LAST;
      }

      priv->tbd[priv->curr_tbd_idx].ctrl = ctrl;
      priv->curr_tbd_idx++;
      
      if(priv->curr_tbd_idx == CYGNUM_DEVS_ETH_ARM_LPC2XXX_TX_BUFS)
          priv->curr_tbd_idx = 0;
      
   }

#else

   cyg_uint32 pos = 0;
   
   for(i = 0;i<sg_len;i++)
   {
      debug2_printf("buf %p len %08x pos %d tbd[%d].addr %08x\n", sg_list[i].buf, sg_list[i].len, pos,
                    priv->curr_tbd_idx, ((char *)priv->tbd[priv->curr_tbd_idx].addr)+pos );

      if( priv->tbd[priv->curr_tbd_idx].addr == 0 )
          for(;;);
      
      memcpy( ((char *)priv->tbd[priv->curr_tbd_idx].addr)+pos, (char *)sg_list[i].buf, sg_list[i].len );

      pos += sg_list[i].len;
   }

   ctrl = ((total_len-1) & CYGARC_HAL_LPC2XXX_TXBD_CTRL_SIZE);
   ctrl |= CYGARC_HAL_LPC2XXX_TXBD_CTRL_INTR | CYGARC_HAL_LPC2XXX_TXBD_CTRL_LAST;
   
   priv->tbd[priv->curr_tbd_idx].ctrl = ctrl;
   priv->curr_tbd_idx++;
      
   if(priv->curr_tbd_idx == CYGNUM_DEVS_ETH_ARM_LPC2XXX_TX_BUFS)
       priv->curr_tbd_idx = 0;
   
#endif
   
   
   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;

   // Start transmission by setting producer register in MAC to end
   // of new packet.

   HAL_WRITE_UINT32( priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_TXPRODIX, priv->curr_tbd_idx );
   
   lpc2xxx_start_transmitter(priv);
   
}

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

static void 
lpc2xxx_eth_receive(struct eth_drv_sc *sc)
{
    lpc2xxx_eth_priv_t *priv = (lpc2xxx_eth_priv_t *)sc->driver_private;

    for(;;)
    {
        cyg_uint32 stat;
        cyg_uint32 pix, cix;

        HAL_READ_UINT32( priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_RXPRODIX, pix );
        HAL_READ_UINT32( priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_RXCONSIX, cix );

        debug2_printf("cix %d pix %d\n", cix, pix);

        if( pix == cix )
            break;

        stat = priv->rsd[cix].info;        

        debug2_printf("cix %d stat %08x\n", cix, stat);

        // We cannot test easily for errors, since the MAC reports a
        // range error for all IP frames since the frame type/length
        // word does not contain a valid length. Instead, if the LAST
        // bit is set, then we assume the frame is OK, it will be
        // discarded by upper layers if this proves mistaken.
        
        if( (stat & CYGARC_HAL_LPC2XXX_RXSD_INFO_LAST) != 0 &&
            (stat & CYGARC_HAL_LPC2XXX_RXSD_INFO_SIZE) >= 13 )
            _eth_drv_recv(sc, (stat & CYGARC_HAL_LPC2XXX_RXSD_INFO_SIZE) + 1 );

        priv->rbd[cix].ctrl = LPC2XXX_EMAC_RX_BUFF_SIZE | CYGARC_HAL_LPC2XXX_RXBD_CTRL_INTR;
        priv->rsd[cix].info = 0;
        priv->rsd[cix].hash_crc = 0;
        
        cix++;
        if( cix == CYGNUM_DEVS_ETH_ARM_LPC2XXX_RX_BUFS )
            cix = 0;

        HAL_WRITE_UINT32( priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_RXCONSIX, cix );
    }
}

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

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

    cyg_drv_interrupt_mask(priv->vector);

    debug2_printf("\n");
    
    return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR);  // Run the DSR
}
#endif

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

static void 
lpc2xxx_eth_deliver(struct eth_drv_sc *sc)
{
    lpc2xxx_eth_priv_t *priv = (lpc2xxx_eth_priv_t *)sc->driver_private;
    CYG_ADDRESS base = priv->base;
    
    for(;;)
    {
        cyg_uint32 isr;


        HAL_READ_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTSTATUS, isr );

//        debug2_printf("isr %08x\n", isr);
        
        if( isr == 0 )
            break;

        if( priv->tx_busy && (isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_TXDONE) )            
        {
            // Transmit done interrupt

            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_TXDONE );
            
            debug2_printf("Tx done: isr %08x\n", isr);

            X(0x11110000|isr);

            _eth_drv_tx_done(sc,priv->curr_tx_key,0);

            priv->tx_busy = false;
                        
        }

        if( isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_RXDONE )
        {
            // Receive interrupt

            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_RXDONE );
            
            debug2_printf("Rx interrupt: isr %08x\n", isr);

            X(0x22110000|isr);
                        
            lpc2xxx_eth_receive( sc );
        }

        if( isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_RXOVR )
        {
            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_RXOVR );
            X(0x33110000);
            debug2_printf("RX overflow\n");
        }

        if( isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_RXERROR )
        {
            cyg_uint32 rsv;            
            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_RXERROR );
            X(0x33220000);
            HAL_READ_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_RSV, rsv );
            X(rsv);
            debug2_printf("RX error\n");
        }

        if( isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_RXFINISH )
        {
            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_RXFINISH );
            X(0x33440000);
            debug2_printf("RX finish\n");
        }

        if( isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_TXUNDR )
        {
            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_TXUNDR );
            X(0x33550000);
            debug2_printf("TX underflow\n");
        }

        if( isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_TXERROR )
        {
            cyg_uint32 tsv0, tsv1;

            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_TXERROR );
            
            X(0x33660000);
            HAL_READ_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_TSV0, tsv0 );
            HAL_READ_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_TSV1, tsv1 );
            X(tsv0);
            X(tsv1);
            
            debug2_printf("TX error\n");
        }

        if( isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_TXFINISH )
        {
            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_TXFINISH );
            X(0x33770000);
            debug2_printf("TX finish\n");
        }

        if( isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_SOFT )
        {
            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_SOFT );
            X(0x33880000);
            debug2_printf("Soft interrupt\n");
        }

        if( isr & CYGARC_HAL_LPC2XXX_REG_ETH_INT_WAKE )
        {
            HAL_WRITE_UINT32( base + CYGARC_HAL_LPC2XXX_REG_ETH_INTCLEAR, CYGARC_HAL_LPC2XXX_REG_ETH_INT_WAKE );
            X(0x33990000);
            debug2_printf("Wake interrupt\n");
        }
    }
    
    cyg_drv_interrupt_acknowledge(priv->vector);
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    cyg_drv_interrupt_unmask(priv->vector);
#endif
   
}

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

static void
lpc2xxx_eth_recv(struct eth_drv_sc *sc,
              struct eth_drv_sg *sg_list,
              int sg_len)
{
   lpc2xxx_eth_priv_t *priv = (lpc2xxx_eth_priv_t *)sc->driver_private;
   int i;

   cyg_uint32 cix;
   cyg_uint8 *buf;

   if( sg_list == NULL || sg_len == 0 )
       return;
   
   HAL_READ_UINT32( priv->base + CYGARC_HAL_LPC2XXX_REG_ETH_RXCONSIX, cix );

   buf = (cyg_uint8 *)priv->rbd[cix].addr;

   debug2_printf("sg_len %d cix %d\n", sg_len, cix);
   debug2_dump_buf( buf, 64 );
         
   for(i = 0 ; i<sg_len ; i++)
   {
       cyg_uint32 len = sg_list[i].len;

       if( sg_list[i].buf == 0 )
           break;
       
       memcpy( (cyg_uint8 *)sg_list[i].buf, buf, len );

       buf += len;
   }
}

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

static void 
lpc2xxx_eth_poll(struct eth_drv_sc *sc)
{
   /* Service the buffers */
   lpc2xxx_eth_deliver(sc);
}

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

static int
lpc2xxx_eth_int_vector(struct eth_drv_sc *sc)
{
   return(CYGNUM_HAL_INTERRUPT_ETHERNET);
}

lpc2xxx_eth_priv_t lpc2xxx_priv_data =
{
   .vector = CYGNUM_HAL_INTERRUPT_ETHERNET,
   .base = CYGARC_HAL_LPC2XXX_REG_ETH_BASE,

#ifdef CYGPKG_DEVS_ETH_ARM_LPC2XXX_PHYINT
   .phy_vector = CYGPKG_DEVS_ETH_ARM_LPC2XXX_PHYINT_VECTOR,
   .phy_pin    = CYGPKG_DEVS_ETH_ARM_LPC2XXX_PHYINT_PIN,
#else
   .phy_vector = -1,
   .phy_pin    = 0,
#endif
   .phy = &lpc2xxx_phy
};

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

ETH_DRV_SC(lpc2xxx_sc,
           &lpc2xxx_priv_data,       // Driver specific data
           "eth0",                // Name for this interface
           lpc2xxx_eth_start,
           lpc2xxx_eth_stop,
           lpc2xxx_eth_control,
           lpc2xxx_eth_can_send,
           lpc2xxx_eth_send,
           lpc2xxx_eth_recv,
           lpc2xxx_eth_deliver,
           lpc2xxx_eth_poll,
           lpc2xxx_eth_int_vector);

NETDEVTAB_ENTRY(lpc2xxx_netdev,
                "lpc2xxx",
                lpc2xxx_eth_init,
                &lpc2xxx_sc);

//======================================================================
// EOF if_lpc2xxx.c
