//==========================================================================
//
//      dev/if_at91rm9200.c
//
//      Ethernet device driver for Atmel AT91RM9200 controller
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007 Free Software Foundation, Inc.
// Copyright (C) 2002, 2003, 2004, 2005, 2007 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):    eCosCentric
// Contributors: eCosCentric
// Date:         2003-08-15
// Purpose:      
// Description:  hardware driver for AT91RM9200
//              
//
//####DESCRIPTIONEND####
//
//==========================================================================

// Ethernet device driver for AT91RM9200

#include <pkgconf/system.h>
#include <pkgconf/devs_eth_arm_at91rm9200.h>
#include <pkgconf/io_eth_drivers.h>

#ifdef CYGPKG_NET
#include <pkgconf/net.h>
#endif

#include <cyg/infra/cyg_type.h>
#include <cyg/infra/diag.h>

#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_cache.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/drv_api.h>
#include <cyg/hal/hal_if.h>
#include <cyg/hal/hal_io.h>
#include <cyg/hal/at91rm9200.h>

#include <cyg/io/eth/netdev.h>
#include <cyg/io/eth/eth_drv.h>
#include <cyg/io/eth_phy.h>

//
// PHY access functions
//
static void at91rm9200_eth_phy_init(void);
static void at91rm9200_eth_phy_put_reg(int reg, int phy, unsigned short data);
static bool at91rm9200_eth_phy_get_reg(int reg, int phy, unsigned short *val);

//
// Receive Buffer Descriptor
//
typedef struct bd {
    unsigned long address;    
    unsigned long status;
} bd_t;

//
// Address bits
//
#define BD_WRAP    0x00000002
#define BD_BUSY    0x00000001

//
// Private information kept about interface
//
struct at91rm9200_eth_info {
    // These fields should be defined by the implementation
    int                 int_vector;
    char               *esa_key;        // RedBoot 'key' for device ESA
    unsigned char       enaddr[6];
    int                 rxnum;          // Number of Rx buffers
    unsigned char      *rxbuf;          // Rx buffer space
    bd_t               *rxbd_table;     // Rx buffer headers
    int                 txnum;          // Number of Tx buffers
    unsigned char      *txbuf;          // Tx buffer space
    bd_t               *txbd_table;     // Tx buffer headers
    eth_phy_access_t   *phy;            // Routines to access PHY
    // The following fields are maintained by the driver
    volatile bd_t  *txbd, *rxbd;     // Next Tx,Rx descriptor to use
    volatile bd_t  *tbase, *rbase;   // First Tx,Rx descriptor
    volatile bd_t  *tnext, *rnext;   // Next descriptor to check for interrupt
    int                 txsize, rxsize;  // Length of individual buffers
    int                 txactive;        // Count of active Tx buffers
    unsigned long       txkey[CYGNUM_DEVS_ETH_ARM_AT91RM9200_TxNUM];
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    unsigned long       ints;            // Mask of interrupts in progress
#endif
};
#include CYGDAT_DEVS_AT91RM9200_ETH_INL

#define DEBUG 0

#ifdef CYGPKG_REDBOOT
static void os_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 );
}
#else
#ifdef CYGPKG_NET_FORCE_SERIAL_CONSOLE
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 );
}
#else
#define os_printf diag_printf
#endif
#endif

#if DEBUG
#define db_printf os_printf
#else
#define db_printf(fmt, ...)
#endif

// For fetching the ESA from RedBoot
#include <cyg/hal/hal_if.h>
#ifndef CONFIG_ESA
#define CONFIG_ESA 6
#endif

static void at91rm9200_eth_int(struct eth_drv_sc *data);

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
static cyg_interrupt at91rm9200_emac_interrupt;
static cyg_handle_t  at91rm9200_emac_interrupt_handle;

#define EMAC_INTERRUPT_HANDLER(_int_,_hdlr_)                                  \
    cyg_drv_interrupt_create(_int_,                                           \
                             0,                                               \
                             (cyg_addrword_t)sc, /*  passed to interrupt */   \
                             (cyg_ISR_t *)at91rm9200_eth_isr,                 \
                             (cyg_DSR_t *)eth_drv_dsr,                        \
                             &at91rm9200_##_hdlr_##_interrupt_handle,         \
                             &at91rm9200_##_hdlr_##_interrupt);               \
    cyg_drv_interrupt_attach(at91rm9200_##_hdlr_##_interrupt_handle);         \
    cyg_drv_interrupt_unmask(_int_);

// This ISR is called when the ethernet interrupt occurs
static int
at91rm9200_eth_isr(cyg_vector_t vector, cyg_addrword_t data, HAL_SavedRegisters *regs)
{
    //struct eth_drv_sc *sc = (struct eth_drv_sc *)data;
    cyg_drv_interrupt_mask(CYGNUM_HAL_INTERRUPT_EMAC);
    return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR);  // Run the DSR
}
#endif

// Deliver function (ex-DSR) handles the ethernet [logical] processing
static void
at91rm9200_eth_deliver(struct eth_drv_sc *sc)
{
#if defined(CYGINT_IO_ETH_INT_SUPPORT_REQUIRED)
    cyg_uint32 old_ints;
#endif
    at91rm9200_eth_int(sc);
#if defined(CYGINT_IO_ETH_INT_SUPPORT_REQUIRED)
    // Allow interrupts to happen again
    HAL_DISABLE_INTERRUPTS(old_ints);
    cyg_drv_interrupt_acknowledge(CYGNUM_HAL_INTERRUPT_EMAC);
    cyg_drv_interrupt_unmask(CYGNUM_HAL_INTERRUPT_EMAC);
    HAL_RESTORE_INTERRUPTS(old_ints);
#endif
#if defined(CYGPKG_REDBOOT)
    cyg_drv_interrupt_acknowledge(CYGNUM_HAL_INTERRUPT_EMAC);
#endif
}

//
// PHY unit access
//
static void 
at91rm9200_eth_phy_init(void)
{
    // Set up MII hardware - nothing to do on this platform
}

static void 
at91rm9200_eth_phy_put_reg(int reg, int phy, unsigned short data)
{
    unsigned long reg_val;
    
    reg_val = 0x50020000 | (phy<<23) | (reg<<18) | data;

    db_printf("PHY PUT - reg: %d, phy: %d, val: %04x [%08x]\n", reg, phy, data, reg_val);
    
    _ETH_CTL |= _ETH_CTL_MPE;

    while( (_ETH_SR & _ETH_SR_IDLE) == 0 );

    _ETH_PHY = reg_val;
    
    while( (_ETH_SR & _ETH_SR_IDLE) == 0 );

    _ETH_CTL &= ~_ETH_CTL_MPE;
}

static bool 
at91rm9200_eth_phy_get_reg(int reg, int phy, unsigned short *val)
{
    unsigned long reg_val;
    
    reg_val = 0x60020000 | (phy<<23) | (reg<<18);

    db_printf("PHY GET - reg: %d, phy: %d [%08x] = ", reg, phy, reg_val);

    _ETH_CTL |= _ETH_CTL_MPE;

    while( (_ETH_SR & _ETH_SR_IDLE) == 0 );

    _ETH_PHY = reg_val;
    
    while( (_ETH_SR & _ETH_SR_IDLE) == 0 );

    *val = _ETH_PHY & 0xFFFF;

    _ETH_CTL &= ~_ETH_CTL_MPE;
    
    db_printf("%04x\n", *val);

    return true;
}

//
// [re]Initialize the ethernet controller
//   Done separately since shutting down the device requires a 
//   full reconfiguration when re-enabling.
static bool
at91rm9200_eth_reset(struct eth_drv_sc *sc, unsigned char *enaddr, int flags)
{
    struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;
    volatile bd_t *rxbd, *RxBD, *txbd, *TxBD;
    unsigned char *RxBUF, *TxBUF;
    int i, int_state;
    unsigned long mode;
    unsigned short phy_state = 0;

    db_printf("%s.%d\n", __FUNCTION__, __LINE__);
    
    // Ignore unless device is idle/stopped
    if ((_ETH_CTL & (_ETH_CTL_RE|_ETH_CTL_TE)) != 0) {
        return true;
    }

    // Make sure interrupts are off while we mess with the device
    HAL_DISABLE_INTERRUPTS(int_state);

    // Reset EMAC controller
    _ETH_CTL &= ~(_ETH_CTL_RE|_ETH_CTL_TE);

    TxBD = qi->txbd_table;
    txbd = (bd_t *)CYGARC_UNCACHED_ADDRESS(TxBD);
    RxBD = qi->rxbd_table;
    rxbd = (bd_t *)CYGARC_UNCACHED_ADDRESS(RxBD);
    qi->tbase = qi->txbd = qi->tnext = txbd;
    qi->rbase = qi->rxbd = qi->rnext = rxbd;
    qi->txactive = 0;

    RxBUF = qi->rxbuf;
    TxBUF = qi->txbuf;

    // setup buffer descriptors
    for (i = 0;  i < qi->rxnum;  i++) {
        rxbd->address = CYGARC_PHYSICAL_ADDRESS((unsigned long)RxBUF);
        rxbd->status = 0;
        RxBUF += CYGNUM_DEVS_ETH_ARM_AT91RM9200_BUFSIZE;
        rxbd++;
    }
    rxbd--;
    rxbd->address |= BD_WRAP;  // Last buffer
    for (i = 0;  i < qi->txnum;  i++) {
        txbd->address = CYGARC_PHYSICAL_ADDRESS((unsigned long)TxBUF);
        txbd->status = 0;
        TxBUF += CYGNUM_DEVS_ETH_ARM_AT91RM9200_BUFSIZE;
        txbd++;
    }
    txbd--;
    txbd->address |= BD_WRAP;  // Last buffer

    // Set up Rx buffer queue
    _ETH_RBQ = CYGARC_PHYSICAL_ADDRESS(RxBD);

    // Set device physical address (ESA)
    _ETH_ESA1H = (enaddr[3]<<24) | (enaddr[2]<<16) | (enaddr[1]<<8) | (enaddr[0]<<0);
    _ETH_ESA1L = (enaddr[5]<<8) | (enaddr[4]<<0);

    // Operating mode
    if (!_eth_phy_init(qi->phy)) {
        return false;
    }
    phy_state = _eth_phy_state(qi->phy);
    if ((phy_state & ETH_PHY_STAT_LINK) == 0)
    {
        int i;
        os_printf("AT91RM9200 ETH: Waiting for link to come up");
        for(i = 0; i < 2; i++ ) // only wait 1 second
        {
            os_printf(".");
            phy_state = _eth_phy_state(qi->phy);
            if ((phy_state & ETH_PHY_STAT_LINK) != 0)
                break;
            CYGACC_CALL_IF_DELAY_US(500000);   // 1/2 second
        }
        os_printf("\n");
    }
    os_printf("AT91RM9200 ETH: ");
    mode = _ETH_CFG_BIG;
#ifdef ETH_PHY_USES_RMII
    mode |= _ETH_CFG_RMII;
#endif
    if ( CYGARC_HAL_ARM_ARM9_AT91RM9200_MCK >= 80*1000*1000 )
        mode |= _ETH_CFG_CLK_64;
    else if ( CYGARC_HAL_ARM_ARM9_AT91RM9200_MCK >= 40*1000*1000 )
        mode |= _ETH_CFG_CLK_32;
    else if ( CYGARC_HAL_ARM_ARM9_AT91RM9200_MCK >= 20*1000*1000 )
        mode |= _ETH_CFG_CLK_16;
    else
        mode |= _ETH_CFG_CLK_8;
    if ((phy_state & ETH_PHY_STAT_LINK) != 0) {
        if ((phy_state & ETH_PHY_STAT_100MB) != 0) {
            // Link can handle 100Mb
            mode |= _ETH_CFG_SPD;  // 100MHz 
            os_printf("100Mb");
            if ((phy_state & ETH_PHY_STAT_FDX) != 0) {
                mode |= _ETH_CFG_FD;
                os_printf("/Full Duplex");
            } 
        } else {
            // Assume 10Mb, half duplex
            os_printf("10Mb");
        }
    } else {
        os_printf("/***NO LINK***");
        return false;
    }
    os_printf("\n");
    _ETH_CFG = mode;
    
    // Reset all interrupts
    _ETH_ISR = 0x00000000;
    _ETH_IER = 0x00000FFE;

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    qi->ints = 0;
#endif

    // Enable interface
    _ETH_CTL = _ETH_CTL_RE | _ETH_CTL_TE | _ETH_CTL_CSR;

    // Restore interrupts
    HAL_RESTORE_INTERRUPTS(int_state);
    return true;
}

//
// Initialize the interface - performed at system startup
// This function must set up the interface, including arranging to
// handle interrupts, etc, so that it may be "started" cheaply later.
//

static bool 
at91rm9200_eth_init(struct cyg_netdevtab_entry *tab)
{
    struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance;
    struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;
    bool esa_ok = false;
    unsigned char enaddr[6];

    db_printf("%s.%d\n", __FUNCTION__, __LINE__);

    at91rm9200_eth_stop(sc);  // Make sure it's not running yet

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    // Set up to handle interrupts
    EMAC_INTERRUPT_HANDLER(CYGNUM_HAL_INTERRUPT_EMAC, emac);
#elif defined(CYGPKG_REDBOOT)
    // Unmask interrupt vector in RedBoot for ^C handling
    cyg_drv_interrupt_acknowledge(CYGNUM_HAL_INTERRUPT_EMAC);
    cyg_drv_interrupt_unmask(CYGNUM_HAL_INTERRUPT_EMAC);    

#endif

    qi->int_vector = CYGNUM_HAL_INTERRUPT_EMAC;
    
    // Get physical device address
#ifdef CYGPKG_REDBOOT
#ifdef CYGSEM_REDBOOT_FLASH_CONFIG
    esa_ok = flash_get_config(qi->esa_key, enaddr, CONFIG_ESA);
#else
    esa_ok = false;
#endif
#else
    esa_ok = CYGACC_CALL_IF_FLASH_CFG_OP(CYGNUM_CALL_IF_FLASH_CFG_GET,         
                                         qi->esa_key, enaddr, CONFIG_ESA);
#endif
    if (!esa_ok) {
        // Can't figure out ESA
        os_printf("AT91RM9200_ETH - Warning! ESA unknown\n");
        memcpy(&enaddr, qi->enaddr, sizeof(enaddr));
    }

    // Configure the device
    if (!at91rm9200_eth_reset(sc, enaddr, 0)) {
        return false;
    }

    // Initialize upper level driver
    (sc->funs->eth_drv->init)(sc, (unsigned char *)&enaddr);
    
    return true;
}
 
//
// This function is called to shut down the interface.
//
static void
at91rm9200_eth_stop(struct eth_drv_sc *sc)
{
    db_printf("%s.%d\n", __FUNCTION__, __LINE__);    
    _ETH_CTL &= ~(_ETH_CTL_RE|_ETH_CTL_TE);
}

//
// This function is called to "start up" the interface.  It may be called
// multiple times, even when the hardware is already running.  It will be
// called whenever something "hardware oriented" changes and should leave
// the hardware ready to send/receive packets.
//
static void
at91rm9200_eth_start(struct eth_drv_sc *sc, unsigned char *enaddr, int flags)
{
    db_printf("%s.%d\n", __FUNCTION__, __LINE__);    
    _ETH_CTL |= (_ETH_CTL_RE|_ETH_CTL_TE);
}

//
// This function is called for low level "control" operations
//
static int
at91rm9200_eth_control(struct eth_drv_sc *sc, unsigned long key,
                  void *data, int length)
{
    db_printf("%s.%d\n", __FUNCTION__, __LINE__);
    return 1;
#if 0
#ifdef ETH_DRV_SET_MC_ALL
    struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;
    volatile struct at91rm9200 *at91rm9200 = qi->at91rm9200;
#endif

    switch (key) {
    case ETH_DRV_SET_MAC_ADDRESS:
        return 0;
        break;
#ifdef ETH_DRV_SET_MC_ALL
    case ETH_DRV_SET_MC_ALL:
    case ETH_DRV_SET_MC_LIST:
        at91rm9200->RxControl &= ~RxControl_PROM;
        at91rm9200->hash[0] = 0xFFFFFFFF;
        at91rm9200->hash[1] = 0xFFFFFFFF;
        return 0;
        break;
#endif
    default:
        return 1;
        break;
    }
#endif
}

//
// 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.
//
static int
at91rm9200_eth_can_send(struct eth_drv_sc *sc)
{
    struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;

    return (qi->txactive < CYGNUM_DEVS_ETH_ARM_AT91RM9200_TxNUM);
}

//
// Dispatch a buffer to the transmiiter
//
static void
at91rm9200_eth_start_tx(volatile bd_t *bd)
{
#if 0
    // This is commented out because we do not use any of the
    // values which can be cleared (not all of them can).
    //_ETH_TSR = 0xFFFFFFFF;  // Clear current interrupt/status
#endif
    _ETH_TAR = bd->address & 0xFFFFFFFC;
    _ETH_TCR = bd->status;  // Length of data
}

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

static void 
at91rm9200_eth_send(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, 
               int total_len, unsigned long key)
{
    struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;
    volatile bd_t *txbd;
    volatile char *bp;
    int i, txindex;

    db_printf("%s.%d\n", __FUNCTION__, __LINE__);

#if 0 // Redundant - API users must always use can_send first
    // verify there is room in hardware
    if (!(_ETH_TSR & _ETH_TSR_BNQ))
    {
        diag_printf("oops\n");
        return;
    }
#endif

    // Find a free buffer
    txbd = qi->txbd;
    // Set up buffer
    // Note: I think the cache is write-through, so no need to bother with uncached access
    // FIXME: untrue! but never seems to have mattered?!
    bp = (volatile char *)CYGARC_VIRTUAL_ADDRESS((unsigned long)txbd->address & 0xFFFFFFFC);
    for (i = 0;  i < sg_len;  i++) {
        memcpy((void *)bp, (void *)sg_list[i].buf, sg_list[i].len);
        bp += sg_list[i].len;
    } 
    txbd->status = total_len;
    txindex = ((unsigned long)txbd - (unsigned long)qi->tbase) / sizeof(*txbd);
    qi->txkey[txindex] = key;
    at91rm9200_eth_start_tx(txbd);
    qi->txactive++;
    // Remember the next buffer to try
    if (txbd->address & BD_WRAP) {
        qi->txbd = qi->tbase;
    } else {
        qi->txbd = txbd+1;
    }    
}

//
// This function is called when a packet has been received.  It's job is
// to prepare to unload the packet from the hardware.  Once the length of
// the packet is known, the upper layer of the driver can be told.  When
// the upper layer is ready to unload the packet, the internal function
// 'at91rm9200_eth_recv' will be called to actually fetch it from the hardware.
//
static void
at91rm9200_eth_RxEvent(struct eth_drv_sc *sc)
{
    struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;
    volatile bd_t *rxbd;
    int i;

    db_printf("%s.%d\n", __FUNCTION__, __LINE__);    
    // Note: must use uncached access to buffer descriptors
    rxbd = (volatile bd_t *)CYGARC_UNCACHED_ADDRESS(qi->rbase);
    for (i = 0;  i < qi->rxnum;  i++, rxbd++) {
        if ((rxbd->address & BD_BUSY) != 0) {
            qi->rxbd = rxbd;  // Save for callback
            (sc->funs->eth_drv->recv)(sc, rxbd->status & 0x7FF); 
            rxbd->address &= ~BD_BUSY;
        }
    }
}

//
// This function is called as a result of the "eth_drv_recv()" call above.
// It's job is to actually fetch data for a packet from the hardware once
// memory buffers have been allocated for the packet.  Note that the buffers
// may come in pieces, using a scatter-gather list.  This allows for more
// efficient processing in the upper layers of the stack.
//
static void
at91rm9200_eth_recv(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len)
{
    struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;
    unsigned char *bp;
    int i;

    db_printf("%s.%d\n", __FUNCTION__, __LINE__);    
    bp = (unsigned char *)CYGARC_VIRTUAL_ADDRESS((unsigned long)qi->rxbd->address & 0xFFFFFFFC);
    // Note: need to use the uncached address, as the data cache could be stale
    bp = (unsigned char *)CYGARC_UNCACHED_ADDRESS(bp);
    for (i = 0;  i < sg_len;  i++) {
        if (sg_list[i].buf != 0) {
            memcpy((void *)sg_list[i].buf, bp, sg_list[i].len);
            bp += sg_list[i].len;
        }
    }
}

static void
at91rm9200_eth_TxEvent(struct eth_drv_sc *sc)
{
    struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;
    volatile bd_t *txbd;
    int key, txindex;
    // We could have transmitted one packet, with the possibility of another
    // packet already having been notified to the hardware. Or we might have
    // sent that packet as well by the time we get called here, in which case
    // there may be two packets completed. We determine what's going on from
    // the transmit state. If the transmitter is idle when we think there's
    // one packet still outstanding, then that packet must also be complete.

    cyg_bool do_second_packet = false;
    cyg_uint32 tsr = _ETH_TSR;

    // There's a possibility that when we processed the previous txevent,
    // we removed a packet for being done, but TCOM in the Interrupt Status Reg
    // had managed to get set, between the time it was tested and the time the
    // second packet was removed.
    // So we bomb out early if there's nothing to do.
    if ((qi->txactive == 0) ||
        ((qi->txactive == 1) && (0 == (tsr & _ETH_TSR_IDLE))) ||
        ((qi->txactive == 2) && (0 == (tsr & _ETH_TSR_BNQ))))
        return;

    do {
        db_printf("%s.%d\n", __FUNCTION__, __LINE__);    
        txbd = qi->tnext;
        txindex = ((unsigned long)txbd - (unsigned long)qi->tbase) / sizeof(*txbd);
        if ((key = qi->txkey[txindex]) != 0) {
            qi->txkey[txindex] = 0;
            (sc->funs->eth_drv->tx_done)(sc, key, 0);
        }
        qi->txactive -= 1;
        if ((txbd->address & BD_WRAP) != 0) {
            txbd = qi->tbase;
        } else {
            txbd++;
        }
        // Remember where we left off
        qi->tnext = (bd_t *)txbd;

        // The txbd should now be the next outstanding packet. Since
        // there could only be two packets known to the hardware,
        // and one of them has just completed, then if the transmit
        // hardware is idle, the other one is complete too, so deal with
        // it.
        if (!do_second_packet && (tsr & _ETH_TSR_IDLE) && qi->txactive)
            do_second_packet = true;
        else
            do_second_packet = false;
    } while (do_second_packet);
}

//
// Interrupt processing
//

static void          
at91rm9200_eth_int(struct eth_drv_sc *sc)
{
    //struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;
    unsigned long event, rsr, tsr;

    event = _ETH_ISR;
    if ((event & _ETH_ISR_TCOM) != 0) {
        at91rm9200_eth_TxEvent(sc);
        event &= ~(_ETH_ISR_TCOM | _ETH_ISR_TIDLE);
    }
    if ((event & _ETH_ISR_RCOM) != 0) {
        at91rm9200_eth_RxEvent(sc);
        event &= ~(_ETH_ISR_RCOM);
    }
    if (event != 0) {
        rsr = _ETH_RSR;  
        tsr = _ETH_TSR;  
        db_printf("ETH - unhandled event: 0x%x, RSR: 0x%x, TSR: 0x%x\n", event, rsr, tsr);
    }
}

//
// Interrupt vector
//
static int          
at91rm9200_eth_int_vector(struct eth_drv_sc *sc)
{
    struct at91rm9200_eth_info *qi = (struct at91rm9200_eth_info *)sc->driver_private;
    return qi->int_vector;
}
