//==========================================================================
//
//      if_omap.c
//
//      TI OMAP ethernet driver
//
//==========================================================================
// ####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, 2010 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:         2010-02-02
// Purpose:
// Description:
//
//####DESCRIPTIONEND####
//
//========================================================================*/

#include <pkgconf/system.h>
#include <pkgconf/hal.h>
#include <pkgconf/devs_eth_arm_omap.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 OMAP_ETH_DELAY_US(us) CYGACC_CALL_IF_DELAY_US(us)
#else
# include <cyg/hal/hal_diag.h>
# define OMAP_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_OMAP_DEBUG_LEVEL
//#define CYGPKG_DEVS_ETH_ARM_OMAP_DEBUG_LEVEL 1
//#define CYGPKG_DEVS_ETH_ARM_OMAP_DEBUG_LEVEL 2

#if CYGPKG_DEVS_ETH_ARM_OMAP_DEBUG_LEVEL > 0
   #define debug1_printf( __fmt, ... ) db_printf("OMAP_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_OMAP_DEBUG_LEVEL > 1
   #define debug2_printf( __fmt, ... ) db_printf("OMAP_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

//======================================================================
// Include PHY stuff after debug macro defs.

//#define phy_debug_printf debug2_printf
#define phy_debug_printf(...) 
#include "omap_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
omap_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_OMAP_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_OMAP_REDBOOT_HOLDS_ESA

   #include <cyg/hal/hal_if.h>

   #ifndef CONFIG_ESA
      #define CONFIG_ESA (6)
   #endif

  #define CYGHWR_DEVS_ETH_ARM_OMAP_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_OMAP_ETH_REDBOOT_HOLDS_ESA

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

#ifndef OMAP_EMAC_RX_BUFF_SIZE
#define OMAP_EMAC_RX_BUFF_SIZE  0x600
#endif

// ---------------------------------------------------------------------
// Buffer Descriptor for both TX and RX

struct bd_s;
typedef struct bd_s bd_t;

struct bd_s
{
    bd_t        *next;
    cyg_uint32  addr;
    cyg_uint16  buf_len;
    cyg_uint16  buf_off;
    cyg_uint16  pkt_len;
    cyg_uint16  flags;
};

// ---------------------------------------------------------------------
// Receive Buffer

typedef struct rb_s 
{
   cyg_uint8 rb[OMAP_EMAC_RX_BUFF_SIZE];
} rb_t;

// ---------------------------------------------------------------------
// OMAP Ethernet private data

typedef struct omap_eth_priv_s 
{
    cyg_uint32          tx_vector;
    cyg_uint32          rx_vector;
    cyg_int32           phy_vector;     // PHY interrupt vector
    cyg_uint32          cm;             // Control module base
    cyg_uint32          ram;            // Descriptor RAM
    cyg_uint32          emac;           // EMAC module base
    cyg_uint32          mdio;           // MDIO module base
    cyg_uint32          *pins;          // Array of pins to configure
    cyg_uint32          pin_num;        // Number of pins
    cyg_uint32          power;          // Power control
    
    char                *esa_key;      // RedBoot 'key' for device ESA
    cyg_uint8           *enaddr;
    
    eth_phy_access_t    *phy;
    
    bd_t                *rbd;
    rb_t                *rb;
    cyg_uint32          rbd_idx;
    rb_t                rbb[CYGNUM_DEVS_ETH_ARM_OMAP_RX_BUFS];

    bd_t                *tbd;
    bd_t                *tx_tbd[8];
    unsigned long       tx_key[8];
    int                 tx_busy;
    
//    cyg_uint32          last_tbd_idx;
//    cyg_uint32          curr_tbd_idx;
    
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    cyg_interrupt       tx_intr;
    cyg_handle_t        tx_intr_handle;
    cyg_interrupt       rx_intr;
    cyg_handle_t        rx_intr_handle;
    cyg_interrupt       phy_intr;
    cyg_handle_t        phy_intr_handle;
    cyg_bool            tx_masked;
    cyg_bool            rx_masked;
    cyg_bool            phy_masked;
#endif
    
} omap_eth_priv_t;

//======================================================================
// Receiver buffer handling
//
// Initialize the receive buffers and descriptors into a circular list.

static void
omap_rb_init(omap_eth_priv_t *priv)
{
   int i;

   CYG_ASSERT( sizeof(bd_t) == 16, "Buffer descriptor size check" );
   
   debug2_printf("\n");
   for (i = 0 ; i < CYGNUM_DEVS_ETH_ARM_OMAP_RX_BUFS; i++)
   {
       priv->rbd[i].next        = (bd_t *)CYGARC_PHYSICAL_ADDRESS((cyg_uint32)&priv->rbd[i+1]);       
       priv->rbd[i].addr        = CYGARC_PHYSICAL_ADDRESS((cyg_uint32)&priv->rb[i]);
       priv->rbd[i].buf_len     = OMAP_EMAC_RX_BUFF_SIZE;
       priv->rbd[i].buf_off     = 0;
       priv->rbd[i].pkt_len     = 0;
       priv->rbd[i].flags       = CYGHWR_HAL_L1XX_EMAC_BD_OWNER;
   }

   // Complete the ring by pointing last BD back to first
   priv->rbd[i-1].next          = (bd_t *)CYGARC_PHYSICAL_ADDRESS((cyg_uint32)&priv->rbd[0]);
}

//======================================================================
// Transmit buffer handling
//
// Initialize the transmit buffer descriptors into a free list.

static void 
omap_tb_init(omap_eth_priv_t *priv)
{
   int i;
   debug2_printf("\n");
   for (i = 0 ; i < CYGNUM_DEVS_ETH_ARM_OMAP_TX_BUFS; i++)
   {
       priv->tbd[i].next        = (bd_t *)CYGARC_PHYSICAL_ADDRESS((cyg_uint32)&priv->tbd[i+1]);       
       
       priv->tbd[i].addr        = 0;
       priv->tbd[i].buf_len     = 0;
       priv->tbd[i].buf_off     = 0;
       priv->tbd[i].pkt_len     = 0;
       priv->tbd[i].flags       = 0;
   }

   // Zero next of last TBD
   priv->tbd[i].next            = NULL;
}

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

static void
omap_disable_rx(omap_eth_priv_t *priv)
{
   HAL_WRITE_UINT32(priv->emac + CYGHWR_HAL_L1XX_EMAC_RXCONTROL, 0);   
}

static void
omap_disable_tx(omap_eth_priv_t *priv)
{
   HAL_WRITE_UINT32(priv->emac + CYGHWR_HAL_L1XX_EMAC_TXCONTROL, 0);   
}

static void
omap_enable_rx(omap_eth_priv_t *priv)
{
   HAL_WRITE_UINT32(priv->emac + CYGHWR_HAL_L1XX_EMAC_RXCONTROL, 1);   
}

static void
omap_enable_tx(omap_eth_priv_t *priv)
{
   HAL_WRITE_UINT32(priv->emac + CYGHWR_HAL_L1XX_EMAC_TXCONTROL, 1);   
}

static void 
omap_enable(omap_eth_priv_t *priv)
{
   debug2_printf("\n");
   omap_enable_tx(priv);
   omap_enable_rx(priv);
}

static void 
omap_disable(omap_eth_priv_t *priv)
{
   debug2_printf("\n");
   omap_disable_tx(priv);
   omap_disable_rx(priv);
}

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

static void
omap_cfg_pins(omap_eth_priv_t *priv)
{
    int i;

    CYGHWR_HAL_L1XX_POWER_ENABLE( priv->power );

    debug2_printf("Configure pins\n");

    for( i = 0; i < priv->pin_num; i++ )
    {
        CYGHWR_HAL_L1XX_PINMUX_SET( priv->pins[i] );
    }

    // Platform setup, enable MDIO buffer
#ifdef CYGHWR_HAL_OMAP_BUFF_OE_SET
    debug2_printf("Set BUFF_OE\n");
    CYGHWR_HAL_OMAP_BUFF_OE_SET();
#endif
    
    // Clear RMII_SEL in CFGCHIP[3]
    {
        cyg_uint32 cfgchip3;
        debug2_printf("Clear RMII_SEL\n");
        HAL_READ_UINT32( CYGHWR_HAL_L1XX_SYSCFG0+CYGHWR_HAL_L1XX_SYSCFG0_CFGCHIP3, cfgchip3 );
        cfgchip3 &= ~CYGHWR_HAL_L1XX_SYSCFG0_CFGCHIP3_RMII_SEL;
        HAL_WRITE_UINT32( CYGHWR_HAL_L1XX_SYSCFG0+CYGHWR_HAL_L1XX_SYSCFG0_CFGCHIP3, cfgchip3 );        
    }
}

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

static void
omap_set_mac(omap_eth_priv_t * priv, cyg_uint8 * enaddr)
{
    cyg_uint32 macaddrhi, macaddrlo;
    int i;

    macaddrhi = (enaddr[0]<<0) | (enaddr[1]<<8) |(enaddr[2]<<16) |(enaddr[3]<<24);
    macaddrlo = (enaddr[4]<<0) | (enaddr[5]<<8);
    
    HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACINDEX, 0 );
    HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACADDRHI, macaddrhi );
    HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACADDRLO,
                      macaddrlo|CYGHWR_HAL_L1XX_EMAC_MACADDRLO_VALID|CYGHWR_HAL_L1XX_EMAC_MACADDRLO_MATCHFILT );

    for( i = 1; i < 8; i++ )
    {
        HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACINDEX, i );
        HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACADDRLO, macaddrlo );
    }

    HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACSRCADDRHI, macaddrhi );    
    HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACSRCADDRLO, macaddrlo );
}

//======================================================================
// PHY control
//
// Extract the negotiated speed and duplex setting from PHY and
// install in MAC.

static cyg_bool omap_phy_init( omap_eth_priv_t *priv)
{
   unsigned short phy_state = 0;
   cyg_uint32 maccontrol;

   // 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;

   HAL_READ_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACCONTROL, maccontrol );

   maccontrol &= ~(CYGHWR_HAL_L1XX_EMAC_MACCONTROL_FULLDUPLEX|CYGHWR_HAL_L1XX_EMAC_MACCONTROL_RMIISPEED);
   
   if( phy_state & ETH_PHY_STAT_100MB )
   {
       debug2_printf("set 100MB\n");
       maccontrol |= CYGHWR_HAL_L1XX_EMAC_MACCONTROL_RMIISPEED;
   }

   if( phy_state & ETH_PHY_STAT_FDX )
   {
       debug2_printf("set full duplex\n");       
       maccontrol |= CYGHWR_HAL_L1XX_EMAC_MACCONTROL_FULLDUPLEX;
   }

   HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACCONTROL, maccontrol );

   debug2_printf("maccontrol %08x\n", maccontrol);
   
   return true;
}

//======================================================================
// 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
omap_eth_init(struct cyg_netdevtab_entry *tab)
{
   struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance;
   omap_eth_priv_t *priv = (omap_eth_priv_t *)sc->driver_private;
   CYG_ADDRESS emac = priv->emac;
   CYG_ADDRESS cm = priv->cm;   
   bool esa_ok = false;
   unsigned char enaddr[6] = { CYGDAT_DEVS_ETH_ARM_OMAP_MACADDR };
   int i;
   cyg_uint32 maccontrol;
   cyg_uint32 userphysel0;
   
   debug1_printf("Initialising @ %x\n",priv->emac);

   priv->tx_busy = 0;
   priv->rbd_idx = 0;

   priv->rbd = (bd_t*)CYGARC_UNCACHED_ADDRESS(priv->ram);
   priv->tbd = (bd_t*)CYGARC_UNCACHED_ADDRESS(priv->rbd+CYGNUM_DEVS_ETH_ARM_OMAP_RX_BUFS);
   priv->rb  = (rb_t*)CYGARC_UNCACHED_ADDRESS(&priv->rbb[0]);

   omap_cfg_pins( priv );

   // Do a soft reset of the EMAC and wait for it to complete.
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_SOFTRESET, 1 );
   for(;;)
   {
       cyg_uint32 sr;
       HAL_READ_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_SOFTRESET, sr );
       if( sr == 0 )
           break;
   }

   // Disable all interrupts in CM:
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C0RXTHRESHEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C0RXEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C0TXEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C0MISCEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C1RXTHRESHEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C1RXEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C1TXEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C1MISCEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C2RXTHRESHEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C2RXEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C2TXEN, 0 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C2MISCEN, 0 );


#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
   // If we are building an interrupt enabled version, install the
   // interrupt handlers.
   
   debug1_printf("Installing Interrupts on TX IRQ %d\n",
		 priv->tx_vector);
   cyg_drv_interrupt_create(priv->tx_vector,
                            CYGNUM_DEVS_ETH_ARM_OMAP_INTRPRI,
                            (cyg_addrword_t)sc,
                            omap_eth_isr,
                            eth_drv_dsr,
                            &priv->tx_intr_handle,
                            &priv->tx_intr);

   cyg_drv_interrupt_attach(priv->tx_intr_handle);
   cyg_drv_interrupt_unmask(priv->tx_vector);

   debug1_printf("Installing Interrupts on RX IRQ %d\n",
		 priv->rx_vector);
   cyg_drv_interrupt_create(priv->rx_vector,
                            CYGNUM_DEVS_ETH_ARM_OMAP_INTRPRI,
                            (cyg_addrword_t)sc,
                            omap_eth_isr,
                            eth_drv_dsr,
                            &priv->rx_intr_handle,
                            &priv->rx_intr);

   cyg_drv_interrupt_attach(priv->rx_intr_handle);
   cyg_drv_interrupt_unmask(priv->rx_vector);

   debug1_printf("Installing Interrupts on PHY IRQ %d\n",
		 priv->phy_vector);
   cyg_drv_interrupt_create(priv->phy_vector,
                            CYGNUM_DEVS_ETH_ARM_OMAP_INTRPRI,
                            (cyg_addrword_t)sc,
                            omap_eth_isr,
                            eth_drv_dsr,
                            &priv->phy_intr_handle,
                            &priv->phy_intr);

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

   
   priv->tx_masked = false;
   priv->rx_masked = false;
   priv->phy_masked = false;
#endif

#ifdef CYGHWR_DEVS_ETH_ARM_OMAP_GET_ESA

   // Get MAC address from RedBoot configuration variables
   CYGHWR_DEVS_ETH_ARM_OMAP_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("Warning! ESA unknown\n");
   }
   debug1_printf("ESA: %02x:%02x:%02x:%02x:%02x:%02x\n",
                 enaddr[0],enaddr[1],enaddr[2],
                 enaddr[3],enaddr[4],enaddr[5]);

   // Give the EMAC its address
   omap_set_mac(priv, enaddr);

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

   // Init MAC from PHY
   if( !omap_phy_init( priv ) )
       return false;
   
   // Clear descriptor header pointers

   for( i = 0; i < 8; i++ )
   {
       HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_TXHDP(i), 0 );
       HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_RXHDP(i), 0 );
   }
   
   // Clear multicast address hashes
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_MACHASH1, 0 );
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_MACHASH2, 0 );

   // Set rx buffer offset and max length
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_RXBUFFEROFFSET, 0 );   
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_RXMAXLEN, OMAP_EMAC_RX_BUFF_SIZE );  
   
   // Enable unicast on channel 0
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_RXUNICASTSET, 1 );   

   // Enable Broadcast and multicast reception for channel 0
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_RXMBPENABLE,
                     CYGHWR_HAL_L1XX_EMAC_RXMBPENABLE_RXMULTEN |
                     CYGHWR_HAL_L1XX_EMAC_RXMBPENABLE_RXBROADEN );      

   // Setup the receiver buffers and descriptors, and tell the EMAC
   // where the to find the descriptors
   omap_rb_init(priv);
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_RXHDP(0), (CYG_ADDRWORD)priv->rbd);
   
   // Setup the transmit descriptors
   omap_tb_init(priv);
   
   // Enable Rx and Tx channel interrupts. Only use channel 0 for
   // reception, but use all channels for Transmission.
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_RXINTMASKSET, 1 );   
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_TXINTMASKSET, 0xFF );   

    // Enable LINK state change interrupt in MDIO module
   HAL_READ_UINT32( priv->mdio+CYGHWR_HAL_L1XX_EMAC_MDIO_USERPHYSEL0, userphysel0 );
   userphysel0 |= CYGHWR_HAL_L1XX_EMAC_MDIO_USERPHYSEL_LINKINT;
   HAL_WRITE_UINT32( priv->mdio+CYGHWR_HAL_L1XX_EMAC_MDIO_USERPHYSEL0, userphysel0 );
   
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED   
   // Enable interrupts in CM module.
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C0RXEN, 1 );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C0TXEN, 0xFF );
   HAL_WRITE_UINT32( cm+CYGHWR_HAL_L1XX_EMAC_CM_C0MISCEN, 0x02 );    
#endif
   
   // Set GMIIEN bit
   HAL_READ_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACCONTROL, maccontrol );
   maccontrol |= CYGHWR_HAL_L1XX_EMAC_MACCONTROL_GMIIEN;
   HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACCONTROL, maccontrol );
   
   // Initialize the upper layer driver
   _eth_drv_init(sc,enaddr);

   return (true);
}

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

static void 
omap_eth_stop(struct eth_drv_sc *sc)
{
   omap_eth_priv_t *priv = (omap_eth_priv_t *)sc->driver_private;
   
   debug2_printf("\n");
   omap_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
omap_eth_start(struct eth_drv_sc *sc, unsigned char *enaddr, int flags)
{
   omap_eth_priv_t *priv = (omap_eth_priv_t *)sc->driver_private;
   
   debug2_printf("\n");
   // Enable the receiver and transmitter
   omap_enable(priv);

}

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

static int
omap_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)
        {
            omap_eth_stop(sc);

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

            omap_set_mac((omap_eth_priv_t *)sc->driver_private,enaddr);
            omap_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:
    {
        omap_eth_priv_t *priv = (omap_eth_priv_t *)sc->driver_private;
        cyg_uint32 rxmbp;
        
        omap_disable_rx(priv);
        
        // Set all bits in hash table and enable multicast in config.
        HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACHASH1, 0xffffffff );
        HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACHASH2, 0xffffffff );
        HAL_READ_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_RXMBPENABLE, rxmbp );
        rxmbp |= CYGHWR_HAL_L1XX_EMAC_RXMBPENABLE_RXMULTEN;
        HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_RXMBPENABLE, rxmbp );        

        omap_enable_rx(priv);        
        return 0;
    }
#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 use all 8 channels, but only one packet at a time from
// each. tx_busy counts how many channels are busy, so if it is <8, we
// can transmit.

static int
omap_eth_can_send(struct eth_drv_sc *sc)
{
   omap_eth_priv_t *priv = (omap_eth_priv_t *)sc->driver_private;

   debug2_printf("tx_busy %d\n", priv->tx_busy );
   
   return priv->tx_busy<8;
}

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

static void
omap_eth_send(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, 
              int total_len, unsigned long key)
{
   omap_eth_priv_t *priv = (omap_eth_priv_t *)sc->driver_private;
   cyg_uint32 emac = priv->emac;
   int chan;
   bd_t *tbd_head, *tbd_tail, *tbd;
   int i;
   cyg_uint16 pkt_len = 0;
   
   debug2_printf("sglen %d len %d key %08x\n", sg_len, total_len, key);

   tbd_head = NULL;
   tbd_tail = NULL;

   for( i = 0; i < sg_len; i++ )
   {
       // Get a BD off free list
       tbd = priv->tbd;

       debug2_printf("sg: %08x %d\n", sg_list[i].buf, sg_list[i].len );
       //debug2_dump_buf( sg_list[i].buf, sg_list[i].len );
       
       if( tbd == NULL )
       {
           // Run out of BDs, free the current BD chain and return.
           while( tbd_head != NULL )
           {
               tbd = tbd_head;
               tbd_head = tbd->next;
               tbd->next = priv->tbd;
               priv->tbd = tbd;
           }

           // Pretend to the upper layers that it was sent.
           _eth_drv_tx_done(sc, key, 0);
           return;
       }

       debug2_printf("tbd %08x\n", tbd );
       
       // Unlink BD from list, add to end of current chain.
       priv->tbd = tbd->next;
       if( tbd_head == NULL ) tbd_head = tbd;
       if( tbd_tail == NULL ) tbd_tail = tbd;
       else
           tbd_tail->next = tbd, tbd_tail = tbd;

       // Fill in this BD with SG buffer
       tbd->next        = NULL;
       tbd->addr        = CYGARC_PHYSICAL_ADDRESS(sg_list[i].buf);
       tbd->buf_len     = sg_list[i].len;
       tbd->buf_off     = 0;
       tbd->pkt_len     = 0;
       tbd->flags       = CYGHWR_HAL_L1XX_EMAC_BD_OWNER;

       pkt_len          += sg_list[i].len;
   }

   // Ensure packet is at least 60 bytes, the minimum packet size. If
   // not we extend the last buffer to transmit some extra. This
   // should be benign.
   if( pkt_len < 60 )
   {
       tbd_tail->buf_len += (60-pkt_len);
       pkt_len = 60;
   }

   // Set packet length, start and end of packet bits.
   tbd_head->flags |= CYGHWR_HAL_L1XX_EMAC_BD_SOP;
   tbd_head->pkt_len = pkt_len;
   tbd_tail->flags |= CYGHWR_HAL_L1XX_EMAC_BD_EOP;

   // Look for a free channel, we know there must be one.
   for( chan = 0; chan < 8; chan++ )
       if( priv->tx_key[chan] == 0 )
           break;

   debug2_printf("tx channel %d\n", chan );
   
   // Record that the transmission is running.
   priv->tx_busy++;
   priv->tx_key[chan] = key;
   priv->tx_tbd[chan] = tbd_head;

   // Ensure all data is out in memory for the DMA.
   HAL_DCACHE_SYNC();
   
   // Start the transmission by writing the channel's head pointer
   HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_TXHDP(chan), tbd_head );
}

//======================================================================
// EMAC ISR.
//
// This is attached to both the Tx and Rx vectors.

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

    cyg_drv_interrupt_mask(vector);
    
    debug2_printf("vector %d\n", vector);

    if( vector == priv->tx_vector )
        priv->tx_masked = true;
    else if( vector == priv->rx_vector )
        priv->rx_masked = true;
    else
        priv->phy_masked = true;
    
    return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR);  // Run the DSR
}
#endif

//======================================================================
// Deliver function.
//
// This is called from the net stack to handle ethernet delivery. We
// check for transmission and reception completion and call up into
// net stack as appropriate.

static void 
omap_eth_deliver(struct eth_drv_sc *sc)
{
    omap_eth_priv_t *priv = (omap_eth_priv_t *)sc->driver_private;
    CYG_ADDRESS emac = priv->emac;

    // Look for completed transmits
    {
        cyg_uint32 stat;
        int i;
        
        HAL_READ_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_TXINTSTATRAW, stat );

        if( stat )
        {
            // Scan for completed channels
            for( i = 0; i < 8; i++ )
                if( stat & (1<<i) )
                {
                    unsigned long key = priv->tx_key[i];
                    bd_t *tbd_head = priv->tx_tbd[i];
                    bd_t *tbd;

                    debug2_printf("tbd %08x\n", tbd_head);
                
                    // Release the buffer descriptors
                    while( tbd_head )
                    {
                        tbd         = tbd_head;
                        tbd_head    = tbd->next;
                        tbd->next   = priv->tbd;
                        priv->tbd   = tbd;
                    }

                    // Clear interrupt by writing address of last BD
                    // to channel completion register.
                    HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_TXCP(i), tbd );

                    // Mark channel free
                    priv->tx_tbd[i] = NULL;
                    priv->tx_key[i] = 0;
                    priv->tx_busy--;

                    debug2_printf("tx done: %08x\n", key );

                    // Inform upper layers
                    _eth_drv_tx_done(sc, key, 0);
                }
        }
    }
    
    // Look for completed receives
    {
        cyg_uint32 idx = priv->rbd_idx;

        while( (priv->rbd[idx].flags & CYGHWR_HAL_L1XX_EMAC_BD_OWNER) == 0 )
        {
            debug2_printf("rbd[%d] %d %04x\n", idx, priv->rbd[idx].pkt_len, priv->rbd[idx].flags );

            if( (priv->rbd[idx].flags & CYGHWR_HAL_L1XX_EMAC_BD_ERRORS) == 0 )
            {
                // Inform higher layers of completed packet

                priv->rbd_idx = idx;
                _eth_drv_recv(sc, priv->rbd[idx].pkt_len );
            }
            else
            {
                // Packet error, just drop it.
            }

            // Clear interrupt by writing address of BD to channel
            // completion register.
            HAL_WRITE_UINT32( emac+CYGHWR_HAL_L1XX_EMAC_RXCP(0), &priv->rbd[idx] );
            
            // Reset the descriptor and give it back to EMAC
            priv->rbd[idx].buf_len = OMAP_EMAC_RX_BUFF_SIZE;
            priv->rbd[idx].pkt_len = 0;
            priv->rbd[idx].flags = CYGHWR_HAL_L1XX_EMAC_BD_OWNER;

            // Advance to next BD
            idx++;
            if( idx == CYGNUM_DEVS_ETH_ARM_OMAP_RX_BUFS ) idx = 0;
        }

        priv->rbd_idx = idx;
    }

    // Look for PHY Link state change
    {
        cyg_uint32 linkint;

        HAL_READ_UINT32( priv->mdio+CYGHWR_HAL_L1XX_EMAC_MDIO_LINKINTRAW, linkint );

        if( linkint & CYGHWR_HAL_L1XX_EMAC_MDIO_LINKINT(0) )
        {
            // Rerun PHY init sequence to reconnect with PHY
            omap_init_phy();
               
            omap_phy_init( priv );

            HAL_WRITE_UINT32( priv->mdio+CYGHWR_HAL_L1XX_EMAC_MDIO_LINKINTRAW,
                              CYGHWR_HAL_L1XX_EMAC_MDIO_LINKINT(0) );
        }
    }
    
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED

    if( priv->tx_masked )
    {
        // Acknowledge and unmask Tx interrupt.
        cyg_drv_interrupt_acknowledge(priv->tx_vector);
        HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACEOIVECTOR,
                          CYGHWR_HAL_L1XX_EMAC_MACEOIVECTOR_TX );
        priv->tx_masked = false;
        cyg_drv_interrupt_unmask(priv->tx_vector);
    }

    if( priv->rx_masked )
    {
        // Acknowledge and unmask Rx interrupt.        
        cyg_drv_interrupt_acknowledge(priv->rx_vector);
        HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACEOIVECTOR,
                          CYGHWR_HAL_L1XX_EMAC_MACEOIVECTOR_RX );        
        priv->rx_masked = false;
        cyg_drv_interrupt_unmask(priv->rx_vector);
    }

    if( priv->phy_masked )
    {
        // Acknowledge and unmask PHY interrupt.        
        cyg_drv_interrupt_acknowledge(priv->phy_vector);

        HAL_WRITE_UINT32( priv->mdio+CYGHWR_HAL_L1XX_EMAC_MDIO_LINKINTMASKED,
                          CYGHWR_HAL_L1XX_EMAC_MDIO_LINKINT(0) );
    
        HAL_WRITE_UINT32( priv->emac+CYGHWR_HAL_L1XX_EMAC_MACEOIVECTOR,
                          CYGHWR_HAL_L1XX_EMAC_MACEOIVECTOR_MISC );
        
        priv->phy_masked = false;
        cyg_drv_interrupt_unmask(priv->phy_vector);
    }

#endif
   
}

//======================================================================
// Callback from stack
//
// Called from net stack to copy data from Rx buffer.

static void
omap_eth_recv(struct eth_drv_sc *sc,
              struct eth_drv_sg *sg_list,
              int sg_len)
{
   omap_eth_priv_t *priv = (omap_eth_priv_t *)sc->driver_private;
//   CYG_ADDRESS emac = priv->emac;
   cyg_uint8 *buf;
   int i;

   if( sg_list == NULL || sg_len == 0 )
       return;

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

   debug2_printf("sg_len %d rbd_idx %d\n", sg_len, priv->rbd_idx);
   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 
omap_eth_poll(struct eth_drv_sc *sc)
{
   /* Service the buffers */
   omap_eth_deliver(sc);
}

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

static int
omap_eth_int_vector(struct eth_drv_sc *sc)
{
    omap_eth_priv_t *priv = (omap_eth_priv_t *)sc->driver_private;
    
    return priv->rx_vector;
}

//======================================================================
// Instantiate device private data

static cyg_uint32 omap_eth_pins[] =
{
    CYGHWR_HAL_L1XX_EMAC_TXCLK,
    CYGHWR_HAL_L1XX_EMAC_TXD0,
    CYGHWR_HAL_L1XX_EMAC_TXD1,
    CYGHWR_HAL_L1XX_EMAC_TXD2,
    CYGHWR_HAL_L1XX_EMAC_TXD3,
    CYGHWR_HAL_L1XX_EMAC_TXEN,
    CYGHWR_HAL_L1XX_EMAC_TXCOL,
    CYGHWR_HAL_L1XX_EMAC_TXCRS,
    CYGHWR_HAL_L1XX_EMAC_RXCLK,
    CYGHWR_HAL_L1XX_EMAC_RXD0,
    CYGHWR_HAL_L1XX_EMAC_RXD1,
    CYGHWR_HAL_L1XX_EMAC_RXD2,
    CYGHWR_HAL_L1XX_EMAC_RXD3,
    CYGHWR_HAL_L1XX_EMAC_RXDV,
    CYGHWR_HAL_L1XX_EMAC_RXER,
    CYGHWR_HAL_L1XX_EMAC_MDIO_CLK,
    CYGHWR_HAL_L1XX_EMAC_MDIO_DATA,    
};

omap_eth_priv_t omap_priv_data =
{
   .tx_vector   = CYGNUM_HAL_INTERRUPT_EMAC_C0TX,
   .rx_vector   = CYGNUM_HAL_INTERRUPT_EMAC_C0RX,
   .phy_vector = CYGNUM_HAL_INTERRUPT_EMAC_C0MISC,
   
   .cm          = CYGHWR_HAL_L1XX_EMAC_CMREG,
   .ram         = CYGHWR_HAL_L1XX_EMAC_CMRAM,
   .emac        = CYGHWR_HAL_L1XX_EMAC_CR,
   .mdio        = CYGHWR_HAL_L1XX_EMAC_MDIO,

   .power       = CYGHWR_HAL_L1XX_EMAC_POWER,
   
   .pins        = omap_eth_pins,
   .pin_num     = sizeof(omap_eth_pins)/sizeof(omap_eth_pins[0]),

   .phy = &omap_phy
};

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

ETH_DRV_SC(omap_sc,
           &omap_priv_data,       // Driver specific data
           "eth0",                // Name for this interface
           omap_eth_start,
           omap_eth_stop,
           omap_eth_control,
           omap_eth_can_send,
           omap_eth_send,
           omap_eth_recv,
           omap_eth_deliver,
           omap_eth_poll,
           omap_eth_int_vector);

NETDEVTAB_ENTRY(omap_netdev,
                "omap",
                omap_eth_init,
                &omap_sc);

//======================================================================
// EOF if_omap.c
