//==========================================================================
//
//      if_iq80310.c
//
//	Ethernet drivers
//	Intel IQ80310 and PRO/100+ platform specific support
//
//==========================================================================
//####COPYRIGHTBEGIN####
//                                                                          
// -------------------------------------------                              
// The contents of this file are subject to the Red Hat eCos Public License 
// Version 1.1 (the "License"); you may not use this file except in         
// compliance with the License.  You may obtain a copy of the License at    
// http://www.redhat.com/                                                   
//                                                                          
// Software distributed under the License is distributed on an "AS IS"      
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the 
// License for the specific language governing rights and limitations under 
// the License.                                                             
//                                                                          
// The Original Code is eCos - Embedded Configurable Operating System,      
// released September 30, 1998.                                             
//                                                                          
// The Initial Developer of the Original Code is Red Hat.                   
// Portions created by Red Hat are                                          
// Copyright (C) 1998, 1999, 2000 Red Hat, Inc.                             
// All Rights Reserved.                                                     
// -------------------------------------------                              
//                                                                          
//####COPYRIGHTEND####
//####BSDCOPYRIGHTBEGIN####
//
// -------------------------------------------
//
// Portions of this software may have been derived from OpenBSD or other sources,
// and are covered by the appropriate copyright disclaimers included herein.
//
// -------------------------------------------
//
//####BSDCOPYRIGHTEND####
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    hmt, gthomas
// Contributors: Ron Spence, Pacific Softworks; msalter
// Date:         2000-02-01
// Purpose:      
// Description:  hardware driver for 82559 Intel PRO/100+ ethernet on
//               a Intel XScale IQ80310 development board
//              
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/system.h>
#include <pkgconf/devs_eth_arm_iq80310.h>
#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/hal_io.h>
#include <cyg/hal/hal_cache.h>
#include <cyg/infra/diag.h>
#include <cyg/hal/drv_api.h>
#include <netdev.h>
#include <eth_drv.h>

#ifdef CYGPKG_NET
#include <pkgconf/net.h>
#include <net/if.h>  /* Needed for struct ifnet */
#else
#include <cyg/hal/hal_if.h>
#define diag_printf printf
#endif

#ifdef CYGPKG_IO_PCI
#include <cyg/io/pci.h>
// So we can check the validity of the PCI window against the MLTs opinion,
// and thereby what the malloc heap consumes willy-nilly:
#include CYGHWR_MEMORY_LAYOUT_H
#else
#error "Need PCI package here"
#endif

// Exported statistics and the like
#include <cyg/devs/eth/iq80310_info.h>
#include <eth_drv_stats.h>

// ------------------------------------------------------------------------

#ifdef CYGDBG_DEVS_ETH_ARM_IQ80310_CHATTER
#define notDEBUG_82559 // This one prints stuff as packets come and go
#define DEBUG          // Startup printing mainly
#define DEBUG_EE       // Some EEPROM specific retries &c
#endif

#define os_printf diag_printf
#define db_printf diag_printf

// ------------------------------------------------------------------------
// I/O access macros as inlines for type safety

static inline void OUTB(cyg_uint8 value, cyg_uint32 io_address)
{   *((volatile cyg_uint8 *)io_address) = value;    }

static inline void OUTW(cyg_uint16 value, cyg_uint32 io_address)
{   *((volatile cyg_uint16 *)io_address) = value;   }

static inline void OUTL(cyg_uint32 value, cyg_uint32 io_address)
{   *((volatile cyg_uint32 *)io_address) = value;   }

static inline cyg_uint8 INB(cyg_uint32 io_address)
{   return *((volatile cyg_uint8 *)io_address);     }

static inline cyg_uint16 INW(cyg_uint32 io_address)
{   return *((volatile cyg_uint16 *)io_address);    }

static inline cyg_uint32 INL(cyg_uint32 io_address)
{   return *((volatile cyg_uint32 *)io_address);    }

// Bus masters can get to all of SDRAM using direct mapping.
#define VIRT_TO_BUS( _x_ ) ((cyg_uint32)(_x_))
#define BUS_TO_VIRT( _x_ ) ((cyg_uint32)(_x_))

// ------------------------------------------------------------------------
//                                                                      
//                   82559 REGISTER OFFSETS (I/O SPACE)                 
//                                                                      
// ------------------------------------------------------------------------
#define SCBStatus       0               // Rx/Command Unit command and status.
#define SCBCmd          2               // Rx/Command Unit command and status.
#define SCBPointer      4               // General purpose pointer.
#define SCBPort         8               // Misc. commands and operands.
#define SCBflash        12              // Flash memory control.
#define SCBeeprom       14              // EEPROM memory control.
#define SCBCtrlMDI      16              // MDI interface control.
#define SCBEarlyRx      20              // Early receive byte count.
#define SCBGenControl   28              // 82559 General Control Register
#define SCBGenStatus    29              // 82559 General Status register


// ------------------------------------------------------------------------
//
//               82559 SCB STATUS WORD DEFNITIONS
//
// ------------------------------------------------------------------------
#define SCB_STATUS_CX   0x8000          // CU finished command (transmit)
#define SCB_STATUS_FR   0x4000          // frame received
#define SCB_STATUS_CNA  0x2000          // CU left active state
#define SCB_STATUS_RNR  0x1000          // receiver left ready state
#define SCB_STATUS_MDI  0x0800          // MDI read/write cycle done
#define SCB_STATUS_SWI  0x0400          // software generated interrupt
#define SCB_STATUS_FCP  0x0100          // flow control pause interrupt

#define SCB_INTACK_MASK 0xFD00          // all the above

#define SCB_INTACK_TX (SCB_STATUS_CX | SCB_STATUS_CNA)
#define SCB_INTACK_RX (SCB_STATUS_FR | SCB_STATUS_RNR)

// ------------------------------------------------------------------------
//
//               82559 PORT INTERFACE COMMANDS
//
// ------------------------------------------------------------------------
#define I82559_RESET            0x00000000 // software reset
#define I82559_SELFTEST         0x00000001 // 82559 selftest command
#define I82559_SELECTIVE_RESET  0x00000002
#define I82559_DUMP             0x00000003
#define I82559_DUMP_WAKEUP      0x00000007


// ------------------------------------------------------------------------
//
//                   82559 EEPROM INTERFACE
//
// ------------------------------------------------------------------------
//  EEPROM_Ctrl bits.
#define EE_SHIFT_CLK	0x01            // EEPROM shift clock.
#define EE_CS		0x02            // EEPROM chip select.
#define EE_DATA_WRITE	0x04            // EEPROM chip data in.
#define EE_DATA_READ	0x08            // EEPROM chip data out.
#define EE_ENB		(0x4800 | EE_CS)

// Delay between EEPROM clock transitions.
#define eeprom_delay(usec)		udelay(usec);

// The EEPROM commands include the always-set leading bit.
#define EE_WRITE_CMD(a)     (5 << (a))
#define EE_READ_CMD(a)	    (6 << (a))
#define EE_ERASE_CMD(a)	    (7 << (a))
#define EE_WRITE_EN_CMD(a)  (19 << ((a)-2))
#define EE_WRITE_DIS_CMD(a) (16 << ((a)-2))
#define EE_ERASE_ALL_CMD(a) (18 << ((a)-2))

#define EE_TOP_CMD_BIT(a)      ((a)+2) // Counts down to zero
#define EE_TOP_DATA_BIT        (15)    // Counts down to zero

#define EEPROM_ENABLE_DELAY (10) // Delay at chip select

#define EEPROM_SK_DELAY  (2) // Delay between clock edges *and* data
                             // read or transition; 3 of these per bit.
#define EEPROM_DONE_DELAY (100) // Delay when all done


// ------------------------------------------------------------------------
//
//               SYSTEM CONTROL BLOCK COMMANDS
//
// ------------------------------------------------------------------------
// CU COMMANDS
#define CU_NOP          0x0000
#define	CU_START        0x0010
#define	CU_RESUME       0x0020
#define	CU_STATSADDR    0x0040          // Load Dump Statistics ctrs addr
#define	CU_SHOWSTATS    0x0050          // Dump statistics counters.
#define	CU_ADDR_LOAD    0x0060          // Base address to add to CU commands
#define	CU_DUMPSTATS    0x0070          // Dump then reset stats counters.

// RUC COMMANDS
#define RUC_NOP         0x0000
#define	RUC_START       0x0001
#define	RUC_RESUME      0x0002
#define RUC_ABORT       0x0004
#define	RUC_ADDR_LOAD   0x0006          // (seems not to clear on acceptance)
#define RUC_RESUMENR    0x0007

#define SCB_M	        0x0100          // 0 = enable interrupt, 1 = disable
#define SCB_SI          0x0200          // 1 - cause device to interrupt

#define CU_STATUS_MASK  0x00C0
#define RU_STATUS_MASK  0x003C

#define RU_STATUS_IDLE  (0<<2)
#define RU_STATUS_SUS   (1<<2)
#define RU_STATUS_NORES (2<<2)
#define RU_STATUS_READY (4<<2)
#define RU_STATUS_NO_RBDS_SUS   ((1<<2)|(8<<2))
#define RU_STATUS_NO_RBDS_NORES ((2<<2)|(8<<2))
#define RU_STATUS_NO_RBDS_READY ((4<<2)|(8<<2))


#define MAX_MEM_RESERVED_IOCTL 1000

// ------------------------------------------------------------------------
//
//               RECEIVE FRAME DESCRIPTORS
//
// ------------------------------------------------------------------------
typedef struct rfd {
    volatile union {
        cyg_uint32 u32_status;         // result of receive operation
        cyg_uint16 u16_status[2];
    } u_status;
    volatile cyg_uint32 link;           // offset from RU base to next RFD
    volatile cyg_uint32 rdb_address;    // pointer to Rx data buffer
    volatile cyg_uint32 count:14,       // number of bytes received +
        f:1,                            //   + EOF & F flags
        eof:1,
            size:16;                    // size of the data buffer
    volatile cyg_uint8 buffer[0];       // data buffer (simple mode)
} RFD;

// The status is split into two shorts to get atomic access to the EL bit;
// the upper word is not written by the device, so we can just hit it,
// leaving the lower word (which the device updates) alone.  Otherwise
// there's a race condition between software moving the end-of-list (EL)
// bit round and the device writing into the previous slot.

#define rxstatus    u_status.u32_status
#define rxstatus_hi u_status.u16_status[1]
#define rxstatus_lo u_status.u16_status[0]

#define RFD_STATUS_EL   0x80000000      // 1=last RFD in RFA
#define RFD_STATUS_S    0x40000000      // 1=suspend RU after receiving frame
#define RFD_STATUS_H    0x00100000      // 1=RFD is a header RFD
#define RFD_STATUS_SF   0x00080000      // 0=simplified, 1=flexible mode
#define RFD_STATUS_C    0x00008000      // completion of received frame
#define RFD_STATUS_OK   0x00002000      // frame received with no errors

#define RFD_STATUS_HI_EL   0x8000       // 1=last RFD in RFA
#define RFD_STATUS_HI_S    0x4000       // 1=suspend RU after receiving frame
#define RFD_STATUS_HI_H    0x0010       // 1=RFD is a header RFD
#define RFD_STATUS_HI_SF   0x0008       // 0=simplified, 1=flexible mode

#define RFD_STATUS_LO_C    0x8000       // completion of received frame
#define RFD_STATUS_LO_OK   0x2000       // frame received with no errors

#define RFD_RX_CRC          0x00000800  // crc error
#define RFD_RX_ALIGNMENT    0x00000400  // alignment error
#define RFD_RX_RESOURCE     0x00000200  // out of space, no resources
#define RFD_RX_DMA_OVER     0x00000100  // DMA overrun
#define RFD_RX_SHORT        0x00000080  // short frame error
#define RFD_RX_LENGTH       0x00000020  //
#define RFD_RX_ERROR        0x00000010  // receive error
#define RFD_RX_NO_ADR_MATCH 0x00000004  // no address match
#define RFD_RX_IA_MATCH     0x00000002  // individual address does not match
#define RFD_RX_TCO          0x00000001  // TCO indication


typedef struct rbd {
    volatile cyg_uint32 count:14,       // bytes used in buffer
        f:1,                            // buffer has been used (filled)
        eof:1;                          // last receive buffer in frame
    volatile cyg_uint32 next_rbd;       // next RBD (RU base relative)
    volatile cyg_uint32 buffer_address; // address of receive data buffer
    volatile cyg_uint32 size:15,        // size of the associated buffer
        el:1;                           // buffer of this RBD is last
} RBD;


// ------------------------------------------------------------------------
//
//               TRANSMIT FRAME DESCRIPTORS
//
// ------------------------------------------------------------------------
typedef struct txcb {
    volatile cyg_uint32 txstatus:16,      // result of transmit operation
        command:16;                     // transmit command
    volatile cyg_uint32 link;           // offset from RU base to next RFD
    volatile cyg_uint32 tbd_address;    // pointer to Rx data buffer
    volatile cyg_uint32 count:15,       // number of bytes in transmit buffer
        eof:1,
        tx_threshold:8,
        tbd_number:8;
    volatile cyg_uint8 buffer[0];       // data buffer (simple mode)
} TxCB;


#define TxCB_CMD_TRANSMIT   0x0004      // transmit command
#define TxCB_CMD_SF         0x0008      // 0=simplified, 1=flexible mode
#define TxCB_CMD_NC         0x0010      // 0=CRC insert by controller
#define TxCB_CMD_I          0x2000      // generate interrupt on completion
#define TxCB_CMD_S          0x4000      // suspend on completion
#define TxCB_CMD_EL         0x8000      // last command block in CBL


// ------------------------------------------------------------------------
//
//                   STRUCTURES ADDED FOR PROMISCUOUS MODE
//
// ------------------------------------------------------------------------
typedef struct {
    cyg_uint32 cb_status_word:13,
        cb_ok:1,
        cb_dc:1,
        cb_complete:1,
        cb_cmd:3,
        cb_cmd_word:10,
        cb_int:1,
        cb_suspend:1,
        cb_el:1;
    cyg_uint32 cb_link_offset;
} CB_STRUCT;


typedef struct {
    CB_STRUCT cb_entry;
    cyg_uint8 config_bytes[24];
} CONFIG_CMD_STRUCT;

// ------------------------------------------------------------------------
//
//                       STATISTICAL COUNTER STRUCTURE
//
// ------------------------------------------------------------------------
#ifdef KEEP_STATISTICS
STATISTICS statistics[2];
I82559_COUNTERS i82559_counters[2];
#endif // KEEP_STATISTICS

// ------------------------------------------------------------------------
//
//                      DEVICES AND PACKET QUEUES
//
// ------------------------------------------------------------------------

#define MAX_RX_PACKET_SIZE  1536        // maximum Rx packet size
#define MAX_TX_PACKET_SIZE  1536        // maximum Tx packet size


// This is encapsulated here so that a change to > 2 interfaces can
// easily be accommodated.

#define IF_BAD_82559( _p_ )                                     \
    CYG_ASSERT( (&i82559[0] == (_p_)) || (&i82559[1] == (_p_)), \
                "Bad pointer-to-i82559" );                      \
    if ( (&i82559[0] != (_p_)) && (&i82559[1] != (_p_)) )

// ------------------------------------------------------------------------
// Instantiate the interfaces that we have:

// number of interfaces
#define MAX_82559 1

I82559 i82559[MAX_82559];               // i82559 device info. structure

// eth0

ETH_DRV_SC(iq80310_sc0,
           &i82559[0],                  // Driver specific data
           "eth0",                      // Name for this interface
           i82559_start,
           i82559_stop,
           i82559_ioctl,
           i82559_can_send,
           i82559_send,
           i82559_recv,
           i82559_deliver,
           i82559_poll,
           i82559_int_vector
    );

NETDEVTAB_ENTRY(iq80310_netdev0, 
                "iq80310-0", 
                iq80310_i82559_init, 
                &iq80310_sc0);

#if (MAX_82559 > 1)

// eth1

ETH_DRV_SC(iq80310_sc1,
           &i82559[1],                  // Driver specific data
           "eth1",                      // Name for this interface
           i82559_start,
           i82559_stop,
           i82559_ioctl,
           i82559_can_send,
           i82559_send,
           i82559_recv,
           i82559_deliver,
           i82559_poll,
           i82559_int_vector
    );

NETDEVTAB_ENTRY(iq80310_netdev1, 
                "iq80310-1", 
                iq80310_i82559_init, 
                &iq80310_sc1);

#else
int iq80310_netdev1 = -1; // for asserts about valid addresses
int iq80310_sc1 = -1;
#endif // eth1 is included

// This is in a macro so that if more devices arrive it can easily be changed
#define CHECK_NDP_SC_LINK() CYG_MACRO_START                                \
    CYG_ASSERT( ((void *)ndp == (void *)&iq80310_netdev0) ||               \
                ((void *)ndp == (void *)&iq80310_netdev1), "Bad ndp" );    \
    CYG_ASSERT( ((void *)sc == (void *)&iq80310_sc0) ||                    \
                ((void *)sc == (void *)&iq80310_sc1), "Bad sc" );          \
    CYG_ASSERT( (void *)p_i82559 == sc->driver_private, "sc pointer bad" );\
CYG_MACRO_END

// ------------------------------------------------------------------------
//
// Managing the memory that is windowed onto the PCI bus
//
// ------------------------------------------------------------------------

static cyg_uint32 i82559_heap_size;
static cyg_uint8 *i82559_heap_base;
static cyg_uint8 *i82559_heap_free;

static void *mem_reserved_ioctl = (void*)0;
// uncacheable memory reserved for ioctl calls

// ------------------------------------------------------------------------
//
//                       FUNCTION PROTOTYPES
//
// ------------------------------------------------------------------------

static int pci_init_find_82559s(void);

static void i82559_reset(struct i82559* p_i82559);

static void InitRxRing(struct i82559* p_i82559);
static void ResetRxRing(struct i82559* p_i82559);
static void InitTxRing(struct i82559* p_i82559);
static void ResetTxRing(struct i82559* p_i82559);

#ifdef CYGPKG_DEVS_ETH_ARM_IQ80310_WRITE_EEPROM
static void program_eeprom(cyg_uint32 , cyg_uint32 , cyg_uint8 * );
#endif
#ifdef CYGPKG_NET
static int eth_set_promiscuous_mode(struct i82559* p_i82559);
#endif

// debugging/logging only:
void dump_txcb(TxCB *p_txcb);
void DisplayStatistics(void);
void update_statistics(struct i82559* p_i82559);
void dump_rfd(RFD *p_rfd, int anyway );
void dump_all_rfds( int intf );
void dump_packet(cyg_uint8 *p_buffer, int length);

// ------------------------------------------------------------------------
// utilities
// ------------------------------------------------------------------------

static // inline
void wait_for_cmd_done(long scb_ioaddr)
{
    register int CSRstatus;
    register int wait = 0x100000;
    do CSRstatus = INB(scb_ioaddr + SCBCmd) ;
    while( CSRstatus && --wait >= 0);
    CYG_ASSERT( wait > 0, "wait_for_cmd_done" );
}

// Short circuit the drv_interrupt_XXX API once we are started:

static inline int Mask82559Interrupt(struct i82559* p_i82559)
{
    int cpu_intr;
    int old;
    int mask = 1 << (CYGNUM_HAL_INTERRUPT_ETHERNET - CYGNUM_HAL_INTERRUPT_TIMER);

    HAL_DISABLE_INTERRUPTS( cpu_intr );

    old = *X3MASK_REG;
    *X3MASK_REG = old | mask;

    HAL_RESTORE_INTERRUPTS( cpu_intr );

    return old & mask;
}


static inline void UnMask82559Interrupt(struct i82559* p_i82559, int old)
{
    int cpu_intr;
    int mask = 1 << (CYGNUM_HAL_INTERRUPT_ETHERNET - CYGNUM_HAL_INTERRUPT_TIMER);

    // We must only unmask (enable) if it was unmasked before,
    // according to the bit in old.
    HAL_DISABLE_INTERRUPTS( cpu_intr );
    *X3MASK_REG = (*X3MASK_REG & ~mask) | old;
    HAL_RESTORE_INTERRUPTS( cpu_intr );
}

#ifdef CYGDBG_USE_ASSERTS // an indication of a debug build
static int acknowledge82559interrupt_compensating = 0;
#endif

static void Acknowledge82559Interrupt(struct i82559* p_i82559)
{
    int sources, mask;
    cyg_uint32 ioaddr;
    cyg_uint16 status;
    int loops = 64;

    // XScale does nothing in interrupt_acknowledge, so we can comment
    // this all out.  So this routine really boils down to
    //   "wait for the device to stop interrupting before we unmask"
    // The acknowledge call is left in place, for documentary purposes.

    cyg_drv_interrupt_acknowledge(p_i82559->vector);

    // It appears that some time can be taken before the interrupt source
    // *really* quietens down... this is ugly, but effective.
    // Without it, we get "Spurious Interrupt!" failures.
    ioaddr = p_i82559->io_address; // get I/O address for 82559

    mask = (1 << (CYGNUM_HAL_INTERRUPT_ETHERNET - CYGNUM_HAL_INTERRUPT_TIMER));
    sources = *X3ISR_REG;

    status = INW(ioaddr + SCBStatus);
    while ( ((0 != (sources & mask)) || (0 != (status & SCB_INTACK_MASK)))
            && --loops >= 0) {
        OUTW( status & SCB_INTACK_MASK, ioaddr + SCBStatus);
        cyg_drv_interrupt_acknowledge(p_i82559->vector);
#ifdef CYGDBG_USE_ASSERTS
        acknowledge82559interrupt_compensating++; // verify this is executed
#endif
	sources = *X3ISR_REG;
        status = INW(ioaddr + SCBStatus);
    }
    CYG_ASSERT( loops >= 0, "Acknowledge82559Interrupt" );
}


static void udelay(int delay)
{
  int i;
  // the loop is going to take 3 ticks.  At 600 MHz, to give uS, multiply
  // by 600/3 = 200. No volatile is needed on i; gcc recognizes delay
  // loops and does NOT elide them.
  for ( i = 200 * delay; i ; i--)
    ;
}

// ------------------------------------------------------------------------
// Memory management
//
// Simply carve off from the front of the PCI mapped window into real memory

static void *pciwindow_mem_alloc(int size)
{
    void *p_memory;
    int _size = size;

    CYG_ASSERT(
        (CYGHWR_HAL_ARM_IQ80310_PCI_MEM_MAP_BASE <= (int)i82559_heap_free)
        &&
        ((CYGHWR_HAL_ARM_IQ80310_PCI_MEM_MAP_BASE + 
          CYGHWR_HAL_ARM_IQ80310_PCI_MEM_MAP_SIZE) > (int)i82559_heap_free)
        &&
        (0 < i82559_heap_size)
        &&
        (CYGHWR_HAL_ARM_IQ80310_PCI_MEM_MAP_SIZE >= i82559_heap_size)
        &&
        (CYGHWR_HAL_ARM_IQ80310_PCI_MEM_MAP_BASE == (int)i82559_heap_base),
        "Heap variables corrupted" );

    p_memory = (void *)0;
    size = (size + 3) & ~3;
    if ( (i82559_heap_free+size) < (i82559_heap_base+i82559_heap_size) ) {
        cyg_uint32 *p;
        p_memory = (void *)i82559_heap_free;
        i82559_heap_free += size;
        for ( p = (cyg_uint32 *)p_memory; _size > 0; _size -= 4 )
            *p++ = 0;
    }

    return p_memory;
}



// ------------------------------------------------------------------------
//
//                       GET EEPROM SIZE
//
// ------------------------------------------------------------------------
static int get_eeprom_size(long ioaddr)
{
    unsigned short retval = 0;
    int ee_addr = ioaddr + SCBeeprom;
    int i, addrbits;

    // Should already be not-selected, but anyway:
    OUTW(EE_ENB & ~EE_CS, ee_addr);
    eeprom_delay(EEPROM_ENABLE_DELAY);
    OUTW(EE_ENB, ee_addr);
    eeprom_delay(EEPROM_ENABLE_DELAY);
    
    // Shift the read command bits out.
    for (i = 2; i >= 0; i--) {
        short dataval = (6 & (1 << i)) ? EE_DATA_WRITE : 0;
        OUTW(EE_ENB | dataval               , ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        OUTW(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        OUTW(EE_ENB | dataval               , ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
    }
    // Now clock out address zero, looking for the dummy 0 data bit
    for ( i = 1; i <= 12; i++ ) {
        OUTW(EE_ENB               , ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        OUTW(EE_ENB | EE_SHIFT_CLK, ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        OUTW(EE_ENB               , ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        retval = INW(ee_addr) & EE_DATA_READ;
        if ( 0 == retval )
            break; // The dummy zero est arrive'
    }

#ifdef DEBUG_EE
    os_printf( "eeprom data bits %d (ioaddr %x)\n", i, ee_addr );
#endif
    if ( 6 != i && 8 != i ) {
#ifdef DEBUG_EE
        os_printf( "*****EEPROM data bits not 6 or 8*****\n" );
#endif
        i = 6;
    }
    addrbits = i;

    // clear the dataval, leave the clock low to read in the data regardless
    OUTW(EE_ENB, ee_addr);
    eeprom_delay(1);
    
    retval = INW(ee_addr);
    if ( (EE_DATA_READ & retval) != 0 ) {
#ifdef DEBUG_EE
        os_printf( "Size EEPROM: Dummy data bit not 0, reg %x\n" , retval );
#endif
    }
    eeprom_delay(1);
    
    for (i = EE_TOP_DATA_BIT; i >= 0; i--) {
        OUTW(EE_ENB | EE_SHIFT_CLK, ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        retval = INW(ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        OUTW(EE_ENB, ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
    }
    
    // Terminate the EEPROM access.
    OUTW(EE_ENB & ~EE_CS, ee_addr);
    eeprom_delay(EEPROM_DONE_DELAY);
    
    return addrbits;
}


// ------------------------------------------------------------------------
//
//                       READ EEPROM
//
// ------------------------------------------------------------------------
static int read_eeprom(long ioaddr, int location, int addr_len)
{
    unsigned short retval = 0;
    int ee_addr = ioaddr + SCBeeprom;
    int read_cmd = location | EE_READ_CMD(addr_len);
    int i, tries = 10;

 try_again:
    // Should already be not-selected, but anyway:
    OUTW(EE_ENB & ~EE_CS, ee_addr);
    eeprom_delay(EEPROM_ENABLE_DELAY);
    OUTW(EE_ENB, ee_addr);
    eeprom_delay(EEPROM_ENABLE_DELAY);
    
    // Shift the read command bits out, changing only one bit per time.
    for (i = EE_TOP_CMD_BIT(addr_len); i >= 0; i--) {
        short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
        OUTW(EE_ENB | dataval               , ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        OUTW(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        OUTW(EE_ENB | dataval               , ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
    }

    // clear the dataval, leave the clock low
    OUTW(EE_ENB, ee_addr);
    eeprom_delay(1);
    
    retval = INW(ee_addr);
    // This should show a zero in the data read bit to confirm that the
    // address transfer is compelete.  If not, go to the start and try
    // again!
    if ( (0 != (retval & EE_DATA_READ)) && (tries-- > 0) ) {
    // Terminate the EEPROM access.
        OUTW(EE_ENB & ~EE_CS, ee_addr);
        eeprom_delay(EEPROM_DONE_DELAY);
#ifdef DEBUG_EE
        os_printf( "Warning: Retrying EEPROM read word %d, address %x, try %d\n",
                   location,  ee_addr, tries+1 );
#endif
        goto try_again;
    }

    // This fires with one device on one of the customer boards!
    // (but is OK on all other h/w.  Worrying huh.)
    if ( (EE_DATA_READ & retval) != 0 ) {
#ifdef DEBUG_EE
        os_printf( "Read EEPROM: Dummy data bit not 0, reg %x\n" , retval );
#endif
    }
    eeprom_delay(1);
    retval = 0;

    for (i = EE_TOP_DATA_BIT; i >= 0; i--) {
        OUTW(EE_ENB | EE_SHIFT_CLK, ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
        retval = (retval << 1) | ((INW(ee_addr) & EE_DATA_READ) ? 1 : 0);
        eeprom_delay(EEPROM_SK_DELAY);
        OUTW(EE_ENB, ee_addr);
        eeprom_delay(EEPROM_SK_DELAY);
    }
    
    // Terminate the EEPROM access.
    OUTW(EE_ENB & ~EE_CS, ee_addr);
    eeprom_delay(EEPROM_DONE_DELAY);
    
    return retval;
}

// ------------------------------------------------------------------------
//
//                       MDI
//
// ------------------------------------------------------------------------

#define SPEED_NOLINK	0
#define SPEED_10M		10
#define SPEED_100M		100

// MDI Control Register
typedef union
{
    struct
    {
    	cyg_uint32 data   : 16;	// data to write or data read
    	cyg_uint32 regAdd  : 5;	// PHY register address
    	cyg_uint32 phyAdd  : 5;	// PHY address
    	cyg_uint32 op      : 2;	// opcode, 1 for MDI write, 2 for MDI read
    	cyg_uint32 ready   : 1;	// 1 = operation complete
    	cyg_uint32 intEnab : 1;	// 1 = interrupt at end of cycle
    	cyg_uint32 rsrv    : 2;	// reserved
    } bits;
    cyg_uint32 word;
} MDICTL;

static cyg_uint16 readMDI (long ioaddr, cyg_uint8 phyAdd, cyg_uint8 regAdd)
{
    int mdi_addr = ioaddr + SCBCtrlMDI;
    MDICTL mdiCtrl;
    int num_ms = 0; 

    // prepare for the MDI operation
    mdiCtrl.bits.ready = MDI_NOT_READY;
    mdiCtrl.bits.intEnab = MDI_POLLED;	// no interrupts
    mdiCtrl.bits.op = MDI_READ_OP;
    mdiCtrl.bits.phyAdd = phyAdd & 0x1f;
    mdiCtrl.bits.regAdd = regAdd & 0x1f;

    // start the operation
    OUTW(mdiCtrl.word, mdi_addr);

    // delay a bit
    udelay (1000);

    // poll for completion */
    mdiCtrl.word = INW(mdi_addr);

    while ((mdiCtrl.bits.ready == MDI_NOT_READY) && (num_ms != 2000)) { // wait max 2secs
	mdiCtrl.word = INW(mdi_addr);
	udelay(1000);
	num_ms++;
    }	
	
    if (num_ms >= 2000)	{
	CYG_FAIL ("readMDI Timeout!\n");
	return -1;
    }
    else 
	return (cyg_uint16)mdiCtrl.bits.data;
}


static void writeMDI (long ioaddr, cyg_uint8 phyAdd, cyg_uint8 regAdd, cyg_uint16 data)
{
    int mdi_addr = ioaddr + SCBCtrlMDI;
    register MDICTL mdiCtrl;
    int num_ms = 0;

    // prepare for the MDI operation
    mdiCtrl.bits.ready = MDI_NOT_READY;
    mdiCtrl.bits.intEnab = MDI_POLLED;  // no interrupts
    mdiCtrl.bits.op = MDI_WRITE_OP;
    mdiCtrl.bits.phyAdd = phyAdd & 0x1f;
    mdiCtrl.bits.regAdd = regAdd & 0x1f;
    mdiCtrl.bits.data = data & 0xffff;

    // start the operation
    OUTW(mdiCtrl.word, mdi_addr);

    // delay a bit
    udelay(1000);

    // poll for completion
    mdiCtrl.word = INW(mdi_addr);

    while ((mdiCtrl.bits.ready == MDI_NOT_READY) && (num_ms != 2000)) {
	mdiCtrl.word = INW(mdi_addr);
	udelay(1000);
	num_ms++;
    }	
    if (num_ms >= 2000)
	CYG_FAIL ("writeMDI Timeout!\n");
 
    return;
}

static int initPHY (long ioaddr, cyg_uint32 device_type)
{
    cyg_uint16 temp_reg;
    cyg_uint8 revision;

    // strip off revision and phy. id information
    revision = (cyg_uint8)(device_type & REVISION_MASK);
    device_type &= ~REVISION_MASK;

    switch (device_type) {
      case ICS1890_PHY_ID: 
	temp_reg = readMDI(ioaddr, MDI_DEFAULT_PHY_ADDR, MDI_PHY_CTRL); // get ready for loopback setting
	writeMDI(ioaddr, MDI_DEFAULT_PHY_ADDR, MDI_PHY_CTRL, temp_reg);
	break;
		
      case DP83840_PHY_ID:  // set the Intel-specified "must set" bits
	temp_reg = readMDI(ioaddr, MDI_DEFAULT_PHY_ADDR, DP83840_PCR_REG);
	temp_reg |= (PCR_TXREADY_SEL | PCR_FCONNECT);
	writeMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, DP83840_PCR_REG, temp_reg);

	// get ready for loopback setting
	temp_reg = readMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, DP83840_LOOPBACK_REG);
	temp_reg &= CLEAR_LOOP_BITS;
	writeMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, DP83840_LOOPBACK_REG, temp_reg);
	break;

      case I82553_PHY_ID:
      case I82553_REVAB_PHY_ID:
      case I82555_PHY_ID:
	break;

      default:
	return 1;
	break;
  }

  return 0;
}





// ------------------------------------------------------------------------
//
//                NETWORK INTERFACE INITIALIZATION
//
//  Function : Init82559
//
//  Description :
//       This routine resets, configures, and initializes the chip.
//       It also clears the ethernet statistics structure, and selects
//       which statistics are supported by this driver.
//
// ------------------------------------------------------------------------
static bool
iq80310_i82559_init(struct cyg_netdevtab_entry * ndp)
{
    static int initialized = 0; // only probe PCI et al *once*

    struct eth_drv_sc *sc;
    cyg_uint32 selftest;
    volatile cyg_uint32 *p_selftest;
    cyg_uint32 ioaddr;
    cyg_uint16 checksum;
    int count;
    int i, ints;
    int addr_length;
    cyg_uint8 mac_address[6];
    struct i82559 *p_i82559;

#ifdef DEBUG
    db_printf("iq80310_i82559_init\n");
#endif

    sc = (struct eth_drv_sc *)(ndp->device_instance);
    p_i82559 = (struct i82559 *)(sc->driver_private);

    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "Bad device private pointer %x\n", sc->driver_private );
#endif
        return 0;
    }

    CHECK_NDP_SC_LINK();

    if ( 0 == initialized++ ) {
        // then this is the first time ever:
        if ( ! pci_init_find_82559s() ) {
#ifdef DEBUG
            os_printf( "pci_init_find_82559s failed" );
#endif
            return 0;
        }
    }

    if ( ! p_i82559->found ) // no device on PCI bus
        return (0);

    ioaddr = p_i82559->io_address; // get I/O address for 82559

#ifdef DEBUG
    os_printf("Init82559 %d @ %x\n82559 Self Test\n",
              p_i82559->index, (int)ndp);
#endif

    ints = Mask82559Interrupt(p_i82559);

    wait_for_cmd_done(ioaddr); // make sure no command operating

    i82559_reset(p_i82559);

    // Perform a system self-test. (get enough mem to round address)
    if ( (selftest = (cyg_uint32)pciwindow_mem_alloc(32) ) == 0)
        return (0);
    p_selftest = (cyg_uint32 *) ((selftest + 15) & ~0xf);

    p_selftest[0] = p_selftest[1] = -1;
        
    OUTL( (VIRT_TO_BUS(p_selftest)) | I82559_SELFTEST, ioaddr + SCBPort);
    count = 0x1FFFFF;                // Timeout for self-test.
    do {
        udelay(10);
    } while ( (p_selftest[1] == -1)  &&  (--count >= 0) );

    Acknowledge82559Interrupt(p_i82559);
    UnMask82559Interrupt(p_i82559, ints );
    
    if (count < 0) {
        // Test timed out.
#ifdef DEBUG
        os_printf("Self test failed\n");
#endif
        return (0);
    }
#ifdef DEBUG
    os_printf("  General self-test: %s.\n"
              "  serial sub-system self-test: %s.\n"
              "  Internal registers self-test: %s.\n"
              "  ROM checksum self-test: %s (%08X).\n",
              p_selftest[1] & 0x1000 ? "failed" : "passed",
              p_selftest[1] & 0x0020 ? "failed" : "passed",
              p_selftest[1] & 0x0008 ? "failed" : "passed",
              p_selftest[1] & 0x0004 ? "failed" : "passed",
              p_selftest[0]);
#endif

    // read eeprom and get 82559's mac address
    addr_length = get_eeprom_size(ioaddr);
    // (this is the length of the *EEPROM*s address, not MAC address)

    for (checksum = 0, i = 0, count = 0; count < 64; count++) {
        cyg_uint16 value;
        // read word from eeprom
        value = read_eeprom(ioaddr, count, addr_length);
#ifdef DEBUG_EE
        // os_printf( "%2d: %04x\n", count, value );
#endif
        checksum += value;
        if (count < 3) {
            mac_address[i++] = value & 0xFF;
            mac_address[i++] = (value >> 8) & 0xFF;
        }
    }

    // If the EEPROM checksum is wrong, the MAC address read from the
    // EEPROM is probably wrong as well. In that case, we don't set
    // mac_addr_ok, but continue the initialization. If then somebody calls
    // i82559_start without calling eth_set_mac_address() first, we refuse
    // to bring up the interface, because running with an invalid MAC
    // address is not a very brilliant idea.

#if 0
    if ((checksum & 0xFFFF) != 0xBABA)  {
        // selftest verified checksum, verify again
#ifdef DEBUG_EE
        os_printf( "Warning: Invalid EEPROM checksum %04X for device %d\n",
                   checksum, p_i82559->index);
#endif
    } else
#endif
    {
        p_i82559->mac_addr_ok = 1;
#ifdef DEBUG_EE
        os_printf("Valid EEPROM checksum\n");
#endif
    }
#ifdef DEBUG
    os_printf("MAC Address = %02X %02X %02X %02X %02X %02X\n",
              mac_address[0], mac_address[1], mac_address[2], mac_address[3],
              mac_address[4], mac_address[5]);
#endif

    // record the MAC address in the device structure
    p_i82559->mac_address[0] = mac_address[0];
    p_i82559->mac_address[1] = mac_address[1];
    p_i82559->mac_address[2] = mac_address[2];
    p_i82559->mac_address[3] = mac_address[3];
    p_i82559->mac_address[4] = mac_address[4];
    p_i82559->mac_address[5] = mac_address[5];
    
    // and record the net dev pointer
    p_i82559->ndp = (void *)ndp;
    
    InitRxRing(p_i82559);
    InitTxRing(p_i82559);

    // Initialize upper level driver
    if ( p_i82559->mac_addr_ok )
        (sc->funs->eth_drv->init)(sc, &(p_i82559->mac_address[0]) );
    else
        (sc->funs->eth_drv->init)(sc, 0 );

    return (1);
}

// ------------------------------------------------------------------------
//
//  Function : i82559_start
//
// ------------------------------------------------------------------------
static void i82559_start( struct eth_drv_sc *sc,
                          unsigned char *enaddr, int flags )
{
    struct i82559 *p_i82559;
    cyg_uint32 ioaddr, phy_id;
    cyg_uint16 phy_addr_reg, temp1, temp2;
    int broadcom_flag = false;
    int link_speed = SPEED_NOLINK;
#ifdef KEEP_STATISTICS
    void *p_statistics;
#endif
#ifdef CYGPKG_NET
    struct ifnet *ifp = &sc->sc_arpcom.ac_if;
#endif

    p_i82559 = (struct i82559 *)sc->driver_private;
    
    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "i82559_start: Bad device pointer %x\n", p_i82559 );
#endif
        return;
    }

    if ( ! p_i82559->mac_addr_ok ) {
#ifdef DEBUG
        os_printf("i82559_start %d: invalid MAC address, "
                  "can't bring up interface\n",
                  p_i82559->index );
#endif
        return;
    }

    if ( p_i82559->active )
        i82559_stop( sc );

    ioaddr = p_i82559->io_address; // get 82559's I/O address

    phy_id = readMDI(ioaddr ,MDI_DEFAULT_PHY_ADDR, MDI_PHY_ID_1) << 16;
    phy_id |= readMDI(ioaddr ,MDI_DEFAULT_PHY_ADDR, MDI_PHY_ID_2);

    if ((phy_id & 0xfffffff0) == I82555_PHY_ID)	{
#ifdef DEBUG
	os_printf ("Intel 82555/558 PHY detected...\n");
#endif
	// dummy read for reliable status
	(void)readMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, MDI_PHY_STAT);
    
	temp1 = readMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, MDI_PHY_STAT);

	phy_addr_reg = readMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, I82555_STATCTRL_REG);

	if (temp1 & MDI_STAT_LINK) // speed only valid with good LNK
	    link_speed = (phy_addr_reg & I82555_100_MBPS) ? SPEED_100M : SPEED_10M;
#ifdef DEBUG
	else
	    os_printf ("Connect Speed is NOT VALID\n");
#endif
    }

    if ((phy_id & 0xfffffff0) == ICS1890_PHY_ID) {
#ifdef DEBUG
	os_printf ("Integrated Circuit Systems ICS1890 PHY detected...\n");
	os_printf ("Revision = %c\n", 'A' + (phy_id & REVISION_MASK));
#endif
	// dummy read for reliable status
	(void)readMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, ICS1890_QUICKPOLL_REG);
	temp1 = readMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, ICS1890_QUICKPOLL_REG);

	if (temp1 & QUICK_LINK_VALID) // speed only valid with good LNK
	    link_speed = (temp1 & QUICK_100_MBPS) ? SPEED_100M : SPEED_10M;
#ifdef DEBUG
	else
	    os_printf ("Connect Speed is NOT VALID\n");
#endif
    }

    if ((phy_id & 0xfffffff0) == DP83840_PHY_ID) {
#ifdef DEBUG
	os_printf ("National DP83840 PHY detected...\n");
	os_printf ("Revision = %c\n", 'A' + (phy_id & REVISION_MASK));
#endif

	// dummy read for reliable status
	(void)readMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, MDI_PHY_STAT);
	temp1 = readMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, MDI_PHY_STAT);

	phy_addr_reg = readMDI (ioaddr ,MDI_DEFAULT_PHY_ADDR, DP83840_PHY_ADDR_REG);

	if (temp1 & MDI_STAT_LINK) // speed only valid with good LNK
	    link_speed = (phy_addr_reg & PHY_ADDR_SPEED_10_MBPS) ? SPEED_10M : SPEED_100M;
#ifdef DEBUG
	else
	    os_printf ("Connect Speed is NOT VALID\n");
#endif
    }

    if ((phy_id & 0xfffffff0) == I82553_PHY_ID)	{
#ifdef DEBUG
	os_printf ("Intel 82553 PHY detected...\n");
	os_printf ("Revision = %c\n", 'A' + (phy_id & REVISION_MASK));
#endif
	broadcom_flag = true;
    }

    if (phy_id == I82553_REVAB_PHY_ID) {
#ifdef DEBUG
	os_printf ("Intel 82553 PHY detected...\n");
	os_printf ("Revision = B\n");
#endif
	broadcom_flag = true;
    }

    if (broadcom_flag == true) {
	temp2 = readMDI (ioaddr, MDI_DEFAULT_PHY_ADDR, I82553_PHY_EXT_REG0);

	// dummy read for reliable status
	(void)readMDI (ioaddr ,MDI_DEFAULT_PHY_ADDR, MDI_PHY_STAT);
	temp1 = readMDI (ioaddr ,MDI_DEFAULT_PHY_ADDR, MDI_PHY_STAT);

	if (temp1 & MDI_STAT_LINK) { // speed only valid with good LNK
	    link_speed = (temp2 & EXT_REG0_100_MBPS) ? SPEED_100M : SPEED_10M;
#ifdef DEBUG
	} else {
	    os_printf ("Connect Speed is NOT VALID\n");
#endif
	}
    }

#ifdef KEEP_STATISTICS
#ifdef CYGDBG_DEVS_ETH_ARM_IQ80310_KEEP_82559_STATISTICS
    p_i82559->p_statistics =
        p_statistics = pciwindow_mem_alloc(sizeof(I82559_COUNTERS));
    memset(p_statistics, 0xFFFFFFFF, sizeof(I82559_COUNTERS));
    wait_for_cmd_done(ioaddr); // make sure no command operating
                                        // set statistics dump address
    OUTL(VIRT_TO_BUS(p_statistics), ioaddr + SCBPointer);
    OUTW(SCB_M | CU_STATSADDR, ioaddr + SCBCmd);

    wait_for_cmd_done(ioaddr); // make sure no command operating
    OUTW(SCB_M | CU_DUMPSTATS, ioaddr + SCBCmd); // start register dump
#endif
#endif

    // Set the base address
    wait_for_cmd_done(ioaddr);
    OUTL(0, ioaddr + SCBPointer);       // load ru base address = 0
    OUTW(SCB_M | RUC_ADDR_LOAD, ioaddr + SCBCmd);
    udelay( 1000 );                     // load pointer to Rx Ring
    OUTL(VIRT_TO_BUS(p_i82559->rx_ring[0]), ioaddr + SCBPointer);
    OUTW(RUC_START, ioaddr + SCBCmd);
    
    p_i82559->active = 1;

    initPHY(ioaddr, phy_id);

#ifdef CYGPKG_NET
    if (( 0
#ifdef ETH_DRV_FLAGS_PROMISC_MODE
         != (flags & ETH_DRV_FLAGS_PROMISC_MODE)
#endif
        ) || (ifp->if_flags & IFF_PROMISC)
        ) {
        eth_set_promiscuous_mode(p_i82559);
    }
#endif
#ifdef DEBUG
    {
        int status = i82559_status( sc );
        os_printf("i82559_start %d flg %x Link = %s, %s Mbps, %s Duplex\n",
                  p_i82559->index,
                  *(int *)p_i82559,
                  status & GEN_STATUS_LINK ? "Up" : "Down",
                  status & GEN_STATUS_100MBPS ?  "100" : "10",
                  status & GEN_STATUS_FDX ? "Full" : "Half");
    }
#endif
}

// ------------------------------------------------------------------------
//
//  Function : i82559_status
//
// ------------------------------------------------------------------------
int i82559_status( struct eth_drv_sc *sc )
{
    int status;
    struct i82559 *p_i82559;
    cyg_uint32 ioaddr;
    p_i82559 = (struct i82559 *)sc->driver_private;
    
    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "i82559_status: Bad device pointer %x\n", p_i82559 );
#endif
        return 0;
    }

    ioaddr = p_i82559->io_address; // get 82559's I/O address

    status = INB(ioaddr + SCBGenStatus);

    return status;
}

// ------------------------------------------------------------------------
//
//  Function : BringDown82559
//
// ------------------------------------------------------------------------

static void i82559_stop( struct eth_drv_sc *sc )
{
    struct i82559 *p_i82559;

    p_i82559 = (struct i82559 *)sc->driver_private;

    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "i82559_stop: Bad device pointer %x\n", p_i82559 );
#endif
        return;
    }
   
#ifdef DEBUG
    os_printf("i82559_stop %d flg %x\n", p_i82559->index, *(int *)p_i82559 );
#endif

    p_i82559->active = 0;               // stop people tormenting it
    i82559_reset(p_i82559);             // that should stop it

    ResetRxRing( p_i82559 );
    ResetTxRing( p_i82559 );
}


// ------------------------------------------------------------------------
//
//  Function : InitRxRing
//
// ------------------------------------------------------------------------
static void InitRxRing(struct i82559* p_i82559)
{
    int i;
    RFD *rfd;
    RFD *p_rfd = 0;
#ifdef DEBUG_82559
    os_printf("InitRxRing %d\n", p_i82559->index);
#endif
    for ( i = 0; i < MAX_RX_DESCRIPTORS; i++ ) {
        rfd = (RFD *)pciwindow_mem_alloc(sizeof(RFD) + MAX_RX_PACKET_SIZE);
        p_i82559->rx_ring[i] = rfd;
        if ( i )
            p_rfd->link = VIRT_TO_BUS(rfd);
        p_rfd = (RFD *)rfd;
    }
    // link last RFD to first:
    p_rfd->link = VIRT_TO_BUS(p_i82559->rx_ring[0]);

    ResetRxRing( p_i82559 );
}

// ------------------------------------------------------------------------
//
//  Function : ResetRxRing
//
// ------------------------------------------------------------------------
static void ResetRxRing(struct i82559* p_i82559)
{
    RFD *p_rfd;
    int i;
#ifdef DEBUG_82559
    os_printf("ResetRxRing %d\n", p_i82559->index);
#endif
    for ( i = 0; i < MAX_RX_DESCRIPTORS; i++ ) {
        p_rfd = p_i82559->rx_ring[i];
        CYG_ASSERT( (cyg_uint8 *)p_rfd >= i82559_heap_base, "rfd under" );
        CYG_ASSERT( (cyg_uint8 *)p_rfd <  i82559_heap_free, "rfd over" );
        CYG_ASSERT( p_i82559->rx_ring[
                 ( i ? (i-1) : (MAX_RX_DESCRIPTORS-1) )
            ]->link == VIRT_TO_BUS(p_rfd), "rfd linked list broken" );
        p_rfd->rxstatus = 0;
        p_rfd->count = 0;
        p_rfd->f = 0;
        p_rfd->eof = 0;
        p_rfd->rdb_address = 0xFFFFFFFF;
        p_rfd->size = MAX_RX_PACKET_SIZE;
    }
    p_i82559->next_rx_descriptor = 0;
    // And set an end-of-list marker in the previous one.
    p_rfd->rxstatus = RFD_STATUS_EL;
}

// ------------------------------------------------------------------------
//
//  Function : PacketRxReady        (Called from delivery thread)
//
// ------------------------------------------------------------------------
static void PacketRxReady(struct i82559* p_i82559)
{
    RFD *p_rfd;
    int next_descriptor;
    int length, ints;
    struct cyg_netdevtab_entry *ndp;
    struct eth_drv_sc *sc;
    cyg_uint32 ioaddr;
    cyg_uint16 status;

    ndp = (struct cyg_netdevtab_entry *)(p_i82559->ndp);
    sc = (struct eth_drv_sc *)(ndp->device_instance);

    CHECK_NDP_SC_LINK();

    ioaddr = p_i82559->io_address;

    next_descriptor = p_i82559->next_rx_descriptor;
    p_rfd = p_i82559->rx_ring[next_descriptor];

    CYG_ASSERT( (cyg_uint8 *)p_rfd >= i82559_heap_base, "rfd under" );
    CYG_ASSERT( (cyg_uint8 *)p_rfd <  i82559_heap_free, "rfd over" );

    while ( p_rfd->rxstatus & RFD_STATUS_C ) {
        p_rfd->rxstatus_hi |= RFD_STATUS_HI_EL;
        length = p_rfd->count;

#ifdef DEBUG_82559
        os_printf( "Device %d (eth%d), rx descriptor %d:\n", 
                   p_i82559->index, p_i82559->index, next_descriptor );
//        dump_rfd( p_rfd, 1 );
#endif

        p_i82559->next_rx_descriptor = next_descriptor;
        // Check for bogusly short packets; can happen in promisc mode:
        // Asserted against and checked by upper layer driver.
#ifdef CYGPKG_NET
        if ( length > sizeof( struct ether_header ) )
            // then it is acceptable; offer the data to the network stack
#endif
        (sc->funs->eth_drv->recv)( sc, length );

        p_rfd->count = 0;
        p_rfd->f = 0;
        p_rfd->eof = 0;
        p_rfd->rxstatus_lo = 0;

        // The just-emptied slot is now ready for re-use and already marked EL;
        // we can now remove the EL marker from the previous one.
        if ( 0 == next_descriptor )
            p_rfd = p_i82559->rx_ring[ MAX_RX_DESCRIPTORS-1 ];
        else
            p_rfd = p_i82559->rx_ring[ next_descriptor-1 ];
        // The previous one: check it *was* marked before clearing.
        CYG_ASSERT( p_rfd->rxstatus_hi & RFD_STATUS_HI_EL, "No prev EL" );
        p_rfd->rxstatus_hi = 0; // that word is not written by the device.

#ifdef KEEP_STATISTICS
        statistics[p_i82559->index].rx_deliver++;
#endif
        if (++next_descriptor >= MAX_RX_DESCRIPTORS)
            next_descriptor = 0;
        p_rfd = p_i82559->rx_ring[next_descriptor];

        CYG_ASSERT( (cyg_uint8 *)p_rfd >= i82559_heap_base, "rfd under" );
        CYG_ASSERT( (cyg_uint8 *)p_rfd <  i82559_heap_free, "rfd over" );
    }

    // See if the RU has gone idle (usually because of out of resource
    // condition) and restart it if needs be.
    ints = Mask82559Interrupt(p_i82559);
    status = INW(ioaddr + SCBStatus);
    if ( RU_STATUS_READY != (status & RU_STATUS_MASK) ) {
        // Acknowledge the RX INT sources
        OUTW( SCB_INTACK_RX, ioaddr + SCBStatus);
        // (see pages 6-10 & 6-90)

#ifdef KEEP_STATISTICS
        statistics[p_i82559->index].rx_restart++;
#endif
        // There's an end-of-list marker out there somewhere...
        // So mop it up; it takes a little time but this is infrequent.
        ResetRxRing( p_i82559 );  
        next_descriptor = 0;        // re-initialize next desc.
        // wait for SCB command complete
        wait_for_cmd_done(ioaddr);
        // load pointer to Rx Ring
        OUTL(VIRT_TO_BUS(p_i82559->rx_ring[0]),
             ioaddr + SCBPointer);
        OUTW(RUC_START, ioaddr + SCBCmd);
        Acknowledge82559Interrupt(p_i82559);
    }
    UnMask82559Interrupt(p_i82559, ints);

    p_i82559->next_rx_descriptor = next_descriptor;
}

// and the callback function

static void i82559_recv( struct eth_drv_sc *sc,
                         struct eth_drv_sg *sg_list, int sg_len )
{
    struct i82559 *p_i82559;
    RFD *p_rfd;
    int next_descriptor;
    int total_len;
    struct eth_drv_sg *last_sg;
    volatile cyg_uint8 *from_p;

    p_i82559 = (struct i82559 *)sc->driver_private;
    
    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "i82559_recv: Bad device pointer %x\n", p_i82559 );
#endif
        return;
    }

    next_descriptor = p_i82559->next_rx_descriptor;
    p_rfd = p_i82559->rx_ring[next_descriptor];
    
    CYG_ASSERT( (cyg_uint8 *)p_rfd >= i82559_heap_base, "rfd under" );
    CYG_ASSERT( (cyg_uint8 *)p_rfd <  i82559_heap_free, "rfd over" );

    CYG_ASSERT( p_rfd->rxstatus & RFD_STATUS_C, "No complete frame" );
    CYG_ASSERT( p_rfd->rxstatus & RFD_STATUS_EL, "No marked frame" );

    CYG_ASSERT( p_rfd->rxstatus_lo & RFD_STATUS_LO_C, "No complete frame 2" );
    CYG_ASSERT( p_rfd->rxstatus_hi & RFD_STATUS_HI_EL, "No marked frame 2" );
    
    if ( 0 == (p_rfd->rxstatus & RFD_STATUS_C) )
        return;
        
    total_len = p_rfd->count;
    
#ifdef DEBUG_82559
    os_printf("Rx %d %x (status %x): %d sg's, %d bytes\n",
              p_i82559->index, (int)p_i82559, p_rfd->rxstatus, sg_len, total_len);
#endif

    // Copy the data to the network stack
    from_p = &p_rfd->buffer[0];

    // check we have memory to copy into; we would be called even if
    // caller was out of memory in order to maintain our state.
    if ( 0 == sg_len || 0 == sg_list )
        return; // caller was out of mbufs

    CYG_ASSERT( 0 < sg_len, "sg_len underflow" );
    CYG_ASSERT( MAX_ETH_DRV_SG >= sg_len, "sg_len overflow" );

    for ( last_sg = &sg_list[sg_len]; sg_list < last_sg; sg_list++ ) {
        cyg_uint8 *to_p;
        int l;
            
        to_p = (cyg_uint8 *)(sg_list->buf);
        l = sg_list->len;

        CYG_ASSERT( 0 <= l, "sg length -ve" );

        if ( 0 >= l || 0 == to_p )
            return; // caller was out of mbufs

        if ( l > total_len )
            l = total_len;

        memcpy( to_p, (unsigned char *)from_p, l );
        from_p += l;
        total_len -= l;
    }

    CYG_ASSERT( 0 == total_len, "total_len mismatch in rx" );
    CYG_ASSERT( last_sg == sg_list, "sg count mismatch in rx" );
    CYG_ASSERT( &p_rfd->buffer[0] < from_p, "from_p wild in rx" );
    CYG_ASSERT( &p_rfd->buffer[0] + MAX_RX_PACKET_SIZE >= from_p,
                "from_p overflow in rx" );
}    


// ------------------------------------------------------------------------
//
//  Function : InitTxRing
//
// ------------------------------------------------------------------------
static void InitTxRing(struct i82559* p_i82559)
{
    int i;
    cyg_uint32 ioaddr;

#ifdef DEBUG_82559
    os_printf("InitTxRing %d\n", p_i82559->index);
#endif
    ioaddr = p_i82559->io_address;
    for ( i = 0; i < MAX_TX_DESCRIPTORS; i++) {
        p_i82559->tx_ring[i] = (TxCB *)pciwindow_mem_alloc(
            sizeof(TxCB) + MAX_TX_PACKET_SIZE);
    }

    ResetTxRing(p_i82559);
}

// ------------------------------------------------------------------------
//
//  Function : ResetTxRing
//
// ------------------------------------------------------------------------
static void ResetTxRing(struct i82559* p_i82559)
{
    int i;
    cyg_uint32 ioaddr;

#ifdef DEBUG_82559
    os_printf("ResetTxRing %d\n", p_i82559->index);
#endif
    ioaddr = p_i82559->io_address;
    p_i82559->tx_descriptor_add =
        p_i82559->tx_descriptor_active = 
        p_i82559->tx_descriptor_remove = 0;
    p_i82559->tx_in_progress =
        p_i82559->tx_queue_full = 0;

    for ( i = 0; i < MAX_TX_DESCRIPTORS; i++) {
        TxCB *p_txcb = p_i82559->tx_ring[i];
        CYG_ASSERT( (cyg_uint8 *)p_txcb >= i82559_heap_base, "txcb under" );
        CYG_ASSERT( (cyg_uint8 *)p_txcb <  i82559_heap_free, "txcb over" );

        p_txcb->txstatus = 0;
        p_txcb->command = 0;
        p_txcb->link = VIRT_TO_BUS((cyg_uint32)p_txcb);
        p_txcb->tbd_address = 0xFFFFFFFF;
        p_txcb->tbd_number = 0;
        p_txcb->tx_threshold = 16;
        p_txcb->eof = 1;
        p_txcb->count = 0;
        p_i82559->tx_keys[i] = 0;
    }
    
    wait_for_cmd_done(ioaddr);
    OUTL(0, ioaddr + SCBPointer);
    OUTW(SCB_M | CU_ADDR_LOAD, ioaddr + SCBCmd);
}

// ------------------------------------------------------------------------
//
//  Function : TxMachine          (Called from FG & ISR)
//
// This steps the Tx Machine onto the next record if necessary - allowing
// for missed interrupts, and so on.
// ------------------------------------------------------------------------

static void TxMachine(struct i82559* p_i82559)
{
    int tx_descriptor_active;
    cyg_uint32 ioaddr;

    tx_descriptor_active = p_i82559->tx_descriptor_active;
    ioaddr = p_i82559->io_address;  
    
    // See if the CU is idle when we think it isn't; this is the only place
    // tx_descriptor_active is advanced. (Also recovers from a dropped intr)
    if ( p_i82559->tx_in_progress ) {
        cyg_uint16 status;
        status = INW(ioaddr + SCBStatus);
        if ( 0 == (status & CU_STATUS_MASK) ) {
            // It is idle.  So ack the TX interrupts
            OUTW( SCB_INTACK_TX, ioaddr + SCBStatus);
            // (see pages 6-10 & 6-90)

            // and step on to the next queued tx.
            p_i82559->tx_in_progress = 0;
            if ( ++tx_descriptor_active >= MAX_TX_DESCRIPTORS )
                tx_descriptor_active = 0;
            p_i82559->tx_descriptor_active = tx_descriptor_active;
        }
    }

    // is the CU idle, and there a next tx to set going?
    if ( ( ! p_i82559->tx_in_progress )
         && p_i82559->tx_descriptor_add != tx_descriptor_active ) {
        TxCB *p_txcb;
        p_txcb = p_i82559->tx_ring[tx_descriptor_active];
        CYG_ASSERT( (cyg_uint8 *)p_txcb >= i82559_heap_base, "txcb under" );
        CYG_ASSERT( (cyg_uint8 *)p_txcb <  i82559_heap_free, "txcb over" );
#ifdef DEBUG_82559
        os_printf("Tx %d %x: Starting Engines, KEY %x\n",
                  p_i82559->index, (int)p_i82559, key );
#endif
        // make sure no command operating
        wait_for_cmd_done(ioaddr); 
        // start Tx operation
        OUTL(VIRT_TO_BUS(p_txcb), ioaddr + SCBPointer);
        OUTW(CU_START, ioaddr + SCBCmd);
        p_i82559->tx_in_progress = 1;
    }
}

// ------------------------------------------------------------------------
//
//  Function : TxDone          (Called from delivery thread)
//
// This returns Tx's from the Tx Machine to the stack (ie. reports
// completion) - allowing for missed interrupts, and so on.
// ------------------------------------------------------------------------

static void TxDone(struct i82559* p_i82559)
{
    struct cyg_netdevtab_entry *ndp;
    struct eth_drv_sc *sc;
    int tx_descriptor_remove = p_i82559->tx_descriptor_remove;

    ndp = (struct cyg_netdevtab_entry *)(p_i82559->ndp);
    sc = (struct eth_drv_sc *)(ndp->device_instance);

    CHECK_NDP_SC_LINK();
    
    // "Done" txen are from here to active, OR 
    // the remove one if the queue is full AND its status is nonzero:
    while (  (tx_descriptor_remove != p_i82559->tx_descriptor_active) ||
             ( p_i82559->tx_queue_full &&
              (0 != p_i82559->tx_ring[ tx_descriptor_remove ]->txstatus) ) ) {
        unsigned long key = p_i82559->tx_keys[ tx_descriptor_remove ];
#ifdef DEBUG_82559
        os_printf("TxDone %d %x: KEY %x\n",
                  p_i82559->index, (int)p_i82559, key );
#endif
        (sc->funs->eth_drv->tx_done)( sc, key, 1 /* status */ );
        
        if ( ++tx_descriptor_remove >= MAX_TX_DESCRIPTORS )
            tx_descriptor_remove = 0;
        p_i82559->tx_descriptor_remove = tx_descriptor_remove;
        p_i82559->tx_queue_full = 0;
    }
}


// ------------------------------------------------------------------------
//
//  Function : i82559_can_send
//
// ------------------------------------------------------------------------

static int 
i82559_can_send(struct eth_drv_sc *sc)
{
    struct i82559 *p_i82559;
    int ints;

    p_i82559 = (struct i82559 *)sc->driver_private;

    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "i82559_send: Bad device pointer %x\n", p_i82559 );
#endif
        return 0;
    }
    
    // Advance TxMachine atomically
    ints = Mask82559Interrupt(p_i82559);
    TxMachine(p_i82559);
    Acknowledge82559Interrupt(p_i82559); // This can eat an Rx interrupt, so
    PacketRxReady(p_i82559);
    UnMask82559Interrupt(p_i82559,ints);

    return ! p_i82559->tx_queue_full;
}

// ------------------------------------------------------------------------
//
//  Function : i82559_send
//
// ------------------------------------------------------------------------

static void 
i82559_send(struct eth_drv_sc *sc,
            struct eth_drv_sg *sg_list, int sg_len, int total_len,
            unsigned long key)
{
    struct i82559 *p_i82559;
    int tx_descriptor_add, ints;
    TxCB *p_txcb;
    cyg_uint32 ioaddr;

    p_i82559 = (struct i82559 *)sc->driver_private;

    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "i82559_send: Bad device pointer %x\n", p_i82559 );
#endif
        return;
    }

#ifdef DEBUG_82559
    os_printf("Tx %d %x: %d sg's, %d bytes, KEY %x\n",
              p_i82559->index, (int)p_i82559, sg_len, total_len, key );
#endif

    if ( ! p_i82559->active )
        return;                         // device inactive, no return
#ifdef KEEP_STATISTICS
    statistics[p_i82559->index].tx_count++;
#endif
    ioaddr = p_i82559->io_address;      // get device I/O address

    if ( p_i82559->tx_queue_full ) {
#ifdef KEEP_STATISTICS
        statistics[p_i82559->index].tx_dropped++;
#endif
#ifdef DEBUG_82559
        os_printf( "i82559_send: Queue full, device %x, key %x\n",
                   p_i82559, key );
#endif
    }
    else {
        struct eth_drv_sg *last_sg;
        volatile cyg_uint8 *to_p;

        tx_descriptor_add = p_i82559->tx_descriptor_add;

        p_i82559->tx_keys[tx_descriptor_add] = key;

        p_txcb = p_i82559->tx_ring[tx_descriptor_add];

        CYG_ASSERT( (cyg_uint8 *)p_txcb >= i82559_heap_base, "txcb under" );
        CYG_ASSERT( (cyg_uint8 *)p_txcb <  i82559_heap_free, "txcb over" );

        p_txcb->txstatus = 0;
        p_txcb->command = TxCB_CMD_TRANSMIT | TxCB_CMD_S
                                | TxCB_CMD_I | TxCB_CMD_EL;
        p_txcb->link = VIRT_TO_BUS((cyg_uint32)p_txcb);
        p_txcb->tbd_address = 0xFFFFFFFF;
        p_txcb->tbd_number = 0;
        p_txcb->tx_threshold = 16;
        p_txcb->eof = 1;
        p_txcb->count = total_len;

        // Copy from the sglist into the txcb
        to_p = &p_txcb->buffer[0];

        CYG_ASSERT( 0 < sg_len, "sg_len underflow" );
        CYG_ASSERT( MAX_ETH_DRV_SG >= sg_len, "sg_len overflow" );

        for ( last_sg = &sg_list[sg_len]; sg_list < last_sg; sg_list++ ) {
            cyg_uint8 *from_p;
            int l;
            
            from_p = (cyg_uint8 *)(sg_list->buf);
            l = sg_list->len;

            if ( l > total_len )
                l = total_len;

            memcpy( (unsigned char *)to_p, from_p, l );
            to_p += l;
            total_len -= l;

            if ( 0 > total_len ) 
                break; // Should exit via sg_last normally
        }

        CYG_ASSERT( 0 == total_len, "length mismatch in tx" );
        CYG_ASSERT( last_sg == sg_list, "sg count mismatch in tx" );
        CYG_ASSERT( &p_txcb->buffer[0] < to_p, "to_p wild in tx" );
        CYG_ASSERT( &p_txcb->buffer[0] + MAX_TX_PACKET_SIZE >= to_p,
                    "to_p overflow in tx" );
  
        // Next descriptor
        if ( ++tx_descriptor_add >= MAX_TX_DESCRIPTORS)
            tx_descriptor_add = 0;
        p_i82559->tx_descriptor_add = tx_descriptor_add;

        // From this instant, interrupts can advance the world and start,
        // even complete, this tx request...

        if ( p_i82559->tx_descriptor_remove == tx_descriptor_add )
            p_i82559->tx_queue_full = 1;
    }

    // Try advancing the Tx Machine regardless

    // no more interrupts until started
    ints = Mask82559Interrupt(p_i82559);

    // Check that either:
    //     tx is already active, there is other stuff queued,
    // OR  this tx just added is the current active one
    // OR  this tx just added is already complete
    CYG_ASSERT(
        // The machine is busy:
        (p_i82559->tx_in_progress == 1) ||
        // or: The machine is idle and this just added is the next one
        (((p_i82559->tx_descriptor_add-1) == p_i82559->tx_descriptor_active)
         || ((0 == p_i82559->tx_descriptor_add) &&
             ((MAX_TX_DESCRIPTORS-1) == p_i82559->tx_descriptor_active))) ||
        // or: This tx is already complete
        (p_i82559->tx_descriptor_add == p_i82559->tx_descriptor_active),
                "Active/add mismatch" );

    // Advance TxMachine atomically
    TxMachine(p_i82559);
    Acknowledge82559Interrupt(p_i82559); // This can eat an Rx interrupt, so
    PacketRxReady(p_i82559);
    UnMask82559Interrupt(p_i82559, ints);
}

// ------------------------------------------------------------------------
//
//  Function : i82559_reset
//
// ------------------------------------------------------------------------
static void i82559_reset(struct i82559* p_i82559)
{
    cyg_uint32 ioaddr;
    int count;

    ioaddr = p_i82559->io_address;
    // make sure no command operating
    wait_for_cmd_done(ioaddr);   
 
    OUTL(I82559_SELECTIVE_RESET, ioaddr + SCBPort);
  
    for (count = 10 ; count-- ; ) {
        udelay(1000);
    }

    OUTL(I82559_RESET, ioaddr + SCBPort);
    
    for (count = 10 ; count-- ; ) {
      udelay(1000);
    }
}


// ------------------------------------------------------------------------
//
//                       INTERRUPT HANDLERS
//
// ------------------------------------------------------------------------

static cyg_uint32 eth_isr(cyg_vector_t vector, cyg_addrword_t data)
{
    struct i82559* p_i82559 = (struct i82559 *)data;
    cyg_uint16 status;
    cyg_uint32 ioaddr;

    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "i82559_isr: Bad device pointer %x\n", p_i82559 );
#endif
        return 0;
    }

    ioaddr = p_i82559->io_address;
    status = INW(ioaddr + SCBStatus);
    // Acknowledge all INT sources that were active
    OUTW( status & SCB_INTACK_MASK, ioaddr + SCBStatus);
    // (see pages 6-10 & 6-90)

#ifdef KEEP_STATISTICS
    statistics[p_i82559->index].interrupts++;

    // receiver left ready state ?
    if ( status & SCB_STATUS_RNR )
        statistics[p_i82559->index].rx_resource++;

    // frame receive interrupt ?
    if ( status & SCB_STATUS_FR )
        statistics[p_i82559->index].rx_count++;

    // transmit interrupt ?
    if ( status & SCB_STATUS_CX )
        statistics[p_i82559->index].tx_complete++;
#endif

    // Advance the Tx Machine regardless
    TxMachine(p_i82559);

    // it should have settled down now...
    Acknowledge82559Interrupt(p_i82559);

    return CYG_ISR_CALL_DSR;        // schedule DSR
}


// ------------------------------------------------------------------------

static 
void eth_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    struct i82559* p_i82559 = (struct i82559 *)data;
    struct cyg_netdevtab_entry *ndp =
        (struct cyg_netdevtab_entry *)(p_i82559->ndp);
    struct eth_drv_sc *sc = (struct eth_drv_sc *)(ndp->device_instance);

    // but here, it must be a *sc:
    eth_drv_dsr( vector, count, (cyg_addrword_t)sc );
}

// ------------------------------------------------------------------------
// This is called from the function below (used to be uni-DSR)
static inline void
uni_deliver(struct i82559* p_i82559)
{
    // First pass any rx data up the stack
    PacketRxReady(p_i82559);

    // Then scan for completed Txen and inform the stack
    TxDone(p_i82559);
}


// ------------------------------------------------------------------------
void i82559_deliver(struct eth_drv_sc *sc)
{
    struct i82559* p_i82559;

    p_i82559 = &i82559[0];
    if ( p_i82559->active )
	uni_deliver( p_i82559 );
}

// ------------------------------------------------------------------------
// Device table entry to operate the chip in a polled mode.
// Only diddle the interface we were asked to!

void i82559_poll(struct eth_drv_sc *sc)
{
    struct i82559 *p_i82559;
    int ints;
    p_i82559 = (struct i82559 *)sc->driver_private;
    
    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "i82559_poll: Bad device pointer %x\n", p_i82559 );
#endif
        return;
    }

    // Do these atomically
    ints = Mask82559Interrupt(p_i82559);

    // As it happens, this driver always requests the DSR to be called:
    (void)eth_isr( CYGNUM_HAL_INTERRUPT_ETHERNET, (cyg_addrword_t)p_i82559 );

    // (no harm in calling this ints-off also, when polled)
    uni_deliver( p_i82559 );

    Acknowledge82559Interrupt(p_i82559);
    UnMask82559Interrupt(p_i82559, ints);
}

// ------------------------------------------------------------------------
// Determine interrupt vector used by a device - for attaching GDB stubs
// packet handler.
int
i82559_int_vector(struct eth_drv_sc *sc)
{
    struct i82559 *p_i82559;
    p_i82559 = (struct i82559 *)sc->driver_private;
    return (p_i82559->vector);
}

#if 0
int
i82559_int_op( struct eth_drv_sc *sc, int mask)
{
    struct i82559 *p_i82559;
    p_i82559 = (struct i82559 *)sc->driver_private;

    if ( 1 == mask )
        return Mask82559Interrupt( p_i82559 );

    if ( 0 == mask )
        UnMask82559Interrupt( p_i82559, 0x0fffffff ); // enable all

    return 0;
}
#endif
    

	// form a second level small page entry
#define SL_SMPAGE_ENTRY(base,ap3,ap2,ap1,ap0,c,b) \
	( ((base) << 12) | ((ap3) << 10) | ((ap2) << 8) | ((ap1) << 6) |\
	      ((ap0) << 4) | ((c) << 3) | ((b) << 2) | 2)

// ------------------------------------------------------------------------
//
//  Function : pci_init_find_82559s
//
// This is called exactly once at the start of time to:
//  o setup some uncached memory for bus mastering
//  o scan the PCI bus for objects
//  o record them in the device table
//  o acquire all the info needed for the driver to access them
//  o instantiate interrupts for them
//  o attach those interrupts appropriately
// ------------------------------------------------------------------------
static int
pci_init_find_82559s( void )
{
    cyg_pci_device_id devid;
    cyg_pci_device dev_info;
    cyg_uint16 cmd;
    int device_index;
    extern char cyg_io_iq80310_i82559_shmem[];

#ifdef DEBUG
    db_printf("pci_init_find_82559s()\n");
#endif

    // allocate memory to be used in ioctls later
    if (mem_reserved_ioctl != (void*)0) {
#ifdef DEBUG
        db_printf("pci_init_find_82559s() called > once\n");
#endif
        return 0;
    }

    // First initialize the heap in PCI window'd memory
    i82559_heap_size = 16*1024;  // match actual size in if_shmem.S
    i82559_heap_base = cyg_io_iq80310_i82559_shmem;

    // only first 1MB of board uses 4K page table
    if (cyg_io_iq80310_i82559_shmem > (char *)0xa0100000) {
#ifdef DEBUG
        db_printf("Can't get shared mem\n");
#endif
	return 0;
    }

    {
	// page table for 1st 1M of RAM
	unsigned *ram_mmutab = (unsigned *)0xA0008400; 
	unsigned base = ((unsigned)i82559_heap_base) / 4096;
	unsigned offset = ((unsigned)i82559_heap_base - 0xa0000000) / 4096;
	int i;

	for (i = 0; i < (i82559_heap_size/4096); i++)
	    ram_mmutab[offset+i] = SL_SMPAGE_ENTRY(base+i, 3, 3, 3, 3, 0, 0);

	HAL_DCACHE_SYNC();
	asm volatile ("mcr p15, 0, %0, c7, c10, 4;\n"	// drain WB
		      "mcr p15, 0, %0, c8, c7, 0;\n"	// flush TLBs
		      : : "r"(i) );
    }

    i82559_heap_free = i82559_heap_base;

    mem_reserved_ioctl = pciwindow_mem_alloc(MAX_MEM_RESERVED_IOCTL);

    cyg_pci_init();

#ifdef DEBUG
    db_printf("Finished cyg_pci_init();\n");
#endif

    devid = CYG_PCI_NULL_DEVID;

    for (device_index = 0; device_index < MAX_82559; device_index++) {
        struct i82559 *p_i82559 = &i82559[device_index];
        p_i82559->index = device_index;

        if (cyg_pci_find_device(0x8086, 0x1209, &devid) ) {
#ifdef DEBUG
            db_printf("eth%d = 82559\n", device_index);
#endif
            cyg_pci_get_device_info(devid, &dev_info);

            if (cyg_pci_translate_interrupt(&dev_info, &p_i82559->vector)) {
#ifdef DEBUG
                db_printf(" Wired to HAL vector %d\n", p_i82559->vector);
#endif
#ifndef CYGPKG_REDBOOT
                cyg_drv_interrupt_create(
                    p_i82559->vector,
                    0,                  // Priority - unused
                    (CYG_ADDRWORD)p_i82559, // Data item passed to ISR & DSR
                    eth_isr,            // ISR
                    eth_dsr,            // DSR
                    &p_i82559->interrupt_handle, // handle to intr obj
                    &p_i82559->interrupt_object ); // space for int obj

                cyg_drv_interrupt_attach(p_i82559->interrupt_handle);
#endif

                // Don't unmask the interrupt yet, that could get us into a
                // race.

            } else {
                p_i82559->vector=0;
#ifdef DEBUG
                db_printf(" Does not generate interrupts.\n");
#endif
            }

            if (cyg_pci_configure_device(&dev_info)) {
#ifdef DEBUG
                int i;
                db_printf("Found device on bus %d, devfn 0x%02x:\n",
                          CYG_PCI_DEV_GET_BUS(devid),
                          CYG_PCI_DEV_GET_DEVFN(devid));

                if (dev_info.command & CYG_PCI_CFG_COMMAND_ACTIVE) {
                    db_printf(" Note that board is active. Probed"
                              " sizes invalid.!\n");
                }
                db_printf(" Vendor    0x%04x", dev_info.vendor);
                db_printf("\n Device    0x%04x", dev_info.device);
                db_printf("\n Command   0x%04x, Status 0x%04x\n",
                          dev_info.command, dev_info.status);
                
                db_printf(" Class/Rev 0x%08x", dev_info.class_rev);
                db_printf("\n Header 0x%02x\n", dev_info.header_type);

                db_printf(" SubVendor 0x%04x, Sub ID 0x%04x\n",
                          dev_info.header.normal.sub_vendor, 
                          dev_info.header.normal.sub_id);

                for(i = 0; i < CYG_PCI_MAX_BAR; i++) {
                    db_printf(" BAR[%d]    0x%08x /", i, dev_info.base_address[i]);
                    db_printf(" probed size 0x%08x / CPU addr 0x%08x\n",
                              dev_info.base_size[i], dev_info.base_map[i]);
                }
                db_printf(" eth%d configured\n", device_index);
#endif
                p_i82559->found = 1;
                p_i82559->active = 0;
                p_i82559->devid = devid;
                p_i82559->memory_address = dev_info.base_map[0];
                p_i82559->io_address = dev_info.base_map[1];
#ifdef DEBUG
                db_printf(" memory address = 0x%08x\n", dev_info.base_map[0]);
                db_printf(" I/O address = 0x%08x\n", dev_info.base_map[1]);
#endif

                // Don't use cyg_pci_set_device_info since it clears
                // some of the fields we want to print out below.
                cyg_pci_read_config_uint16(dev_info.devid, CYG_PCI_CFG_COMMAND, &cmd);
                cmd |= CYG_PCI_CFG_COMMAND_IO // enable I/O space
                    | CYG_PCI_CFG_COMMAND_MEMORY // enable memory space
                    | CYG_PCI_CFG_COMMAND_MASTER; // enable bus master
                cyg_pci_write_config_uint16(dev_info.devid, CYG_PCI_CFG_COMMAND, cmd);

                // Now the PCI part of the device is configured, reset it. This 
                // should make it safe to enable the interrupt
                i82559_reset(p_i82559);

                if (p_i82559->vector != 0) {
                    cyg_drv_interrupt_acknowledge(p_i82559->vector);
#ifndef CYGPKG_REDBOOT
                    cyg_drv_interrupt_unmask(p_i82559->vector);
#endif
                }
#ifdef DEBUG
                db_printf(" **** Device enabled for I/O and Memory and Bus Master\n");
#endif
            }
            else {
                p_i82559->found = 0;
                p_i82559->active = 0;
#ifdef DEBUG
                db_printf("Failed to configure device %d\n",device_index);
#endif
            }
        }
        else {
            p_i82559->found = 0;
            p_i82559->active = 0;
#ifdef DEBUG
            db_printf("eth%d not found\n", device_index);
#endif
        }
    }

    // Now a delay to ensure the hardware has "come up" before you try to
    // use it.  Yes, really, the full 2 seconds.  It's only really
    // necessary if DEBUG is off - otherwise all that printout wastes
    // enough time.  No kidding.
    udelay( 2000000 );
    return 1;
}

#ifdef CYGPKG_NET
// ------------------------------------------------------------------------
//
//  Function : eth_set_promiscuous_mode
//
//  Return : 0 = It worked.
//           non0 = It failed.
// ------------------------------------------------------------------------

static int eth_set_promiscuous_mode(struct i82559* p_i82559)
{
    cyg_uint32  ioaddr;
    volatile CONFIG_CMD_STRUCT *ccs;

    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "eth_set_promiscuos_mode: Bad device pointer %x\n",
                   p_i82559 );
#endif
        return -1;
    }

    ioaddr = p_i82559->io_address;  
    wait_for_cmd_done(ioaddr); 
    // load cu base address = 0 */ 
    OUTL(0, ioaddr + SCBPointer);         
    // 32 bit linear addressing used
                                        
    OUTW(SCB_M | CU_ADDR_LOAD, ioaddr + SCBCmd);
    // wait for SCB command complete
    wait_for_cmd_done(ioaddr);   
  
    ccs = (CONFIG_CMD_STRUCT *)mem_reserved_ioctl;
  
    // Check the malloc we did earlier worked
    if (ccs == (void*)0) 
        return 2; // Failed
  
    ccs->cb_entry.cb_cmd=0x2;
    ccs->cb_entry.cb_cmd_word=0x0;
    ccs->cb_entry.cb_status_word=0x0;
    ccs->cb_entry.cb_int=0;
    ccs->cb_entry.cb_suspend=1;
    ccs->cb_entry.cb_el=1;
    ccs->cb_entry.cb_complete=0;
    ccs->cb_entry.cb_link_offset=VIRT_TO_BUS((cyg_uint32)&ccs);
    
    // Default values from the Intel Manual
    ccs->config_bytes[0]=0x13;
    ccs->config_bytes[1]=0x8;
    ccs->config_bytes[2]=0x0;
    ccs->config_bytes[3]=0x0;
    ccs->config_bytes[4]=0x0;
    ccs->config_bytes[5]=0x0;
    ccs->config_bytes[6]=0xb2; // (promisc ? 0x80 : 0) | 0x32 for small stats,
    ccs->config_bytes[7]=0x0;  // \      ditto         | 0x12 for stats with PAUSE stats
    ccs->config_bytes[8]=0x0;  //  \     ditto         | 0x16 for PAUSE + TCO stats
    ccs->config_bytes[9]=0x0;
    ccs->config_bytes[10]=0x28;
    ccs->config_bytes[11]=0x0;
    ccs->config_bytes[12]=0x60;
    ccs->config_bytes[13]=0x0;          // arp
    ccs->config_bytes[14]=0x0;          // arp
    
    ccs->config_bytes[15]=0x81;         // promiscuous mode set
                                        // \ or 0x80 for normal mode.
    ccs->config_bytes[16]=0x0;
    ccs->config_bytes[17]=0x40;
    ccs->config_bytes[18]=0x72;         // Keep the Padding Enable bit
    
    // wait for SCB command complete
    wait_for_cmd_done(ioaddr);   
    
    OUTL(VIRT_TO_BUS(ccs), ioaddr + SCBPointer); 
    OUTW(SCB_M | CU_START, ioaddr + SCBCmd);    
  
    udelay(10000);

    // now check for result ...
    wait_for_cmd_done(ioaddr);   
  
    if ( (!ccs->cb_entry.cb_ok) || (!ccs->cb_entry.cb_complete) )
        return 1; // Failed

    wait_for_cmd_done(ioaddr);
    /* load pointer to Rx Ring */
    
    OUTL(VIRT_TO_BUS(p_i82559->rx_ring[0]),
         ioaddr + SCBPointer);
    OUTW(RUC_START, ioaddr + SCBCmd);

    return 0; // OK
}
#endif

// ------------------------------------------------------------------------
// We use this as a templete when writing a new MAC address into the
// eeproms. The MAC address in the first few bytes is over written
// with the correct MAC address and then the whole lot is programmed
// into the serial EEPROM. The checksum is calculated on the fly and
// sent instead of the last two bytes.
// The values are copied from the Intel EtherPro10/100+ &c devices
// in the EBSA boards.

#ifdef CYGPKG_DEVS_ETH_ARM_IQ80310_WRITE_EEPROM

#define ee00 0x00, 0x00 // shorthand

static char eeprom_burn[126] = { 
/* halfword addresses! */
/*  0: */  0x00, 0x90,   0x27, 0x8c,       0x57, 0x82,   0x03, 0x02,
/*  4: */     ee00   ,   0x01, 0x02,       0x01, 0x47,      ee00   ,
/*  8: */  0x13, 0x72,   0x06, 0x83,       0xa2, 0x40,   0x0c, 0x00,
/*  C: */  0x86, 0x80,      ee00   ,          ee00   ,      ee00   ,
/* 10: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 14: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 18: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 1C: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 20: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 24: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 28: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 2C: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 30: */  0x28, 0x01,      ee00   ,          ee00   ,      ee00   ,
/* 34: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 38: */     ee00   ,      ee00   ,          ee00   ,      ee00   ,
/* 3C: */     ee00   ,      ee00   ,          ee00   
};
#undef ee00

#endif

// ------------------------------------------------------------------------
//
//  Function : eth_set_mac_address
//
//  Return : 0 = It worked.
//           non0 = It failed.
// ------------------------------------------------------------------------
static int eth_set_mac_address(struct i82559* p_i82559, char *addr)
{
#ifdef CYGPKG_DEVS_ETH_ARM_IQ80310_WRITE_EEPROM
    int checksum, i, count;
    // (this is the length of the *EEPROM*s address, not MAC address)
    int addr_length;
#endif
    cyg_uint32  ioaddr;
    volatile CONFIG_CMD_STRUCT *ccs;
  
    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "eth_set_mac_address : Bad device pointer %x\n",
                   p_i82559 );
#endif
        return -1;
    }

    ioaddr = p_i82559->io_address;      
    
    wait_for_cmd_done(ioaddr); 

    ccs = (CONFIG_CMD_STRUCT *)mem_reserved_ioctl;
    if (ccs == (void*)0)
        return 2;

    ccs->cb_entry.cb_cmd=0x1;
    ccs->cb_entry.cb_cmd_word=0x0;
    ccs->cb_entry.cb_status_word=0x0;
    ccs->cb_entry.cb_int=0;
    ccs->cb_entry.cb_suspend=1;
    ccs->cb_entry.cb_el=1;
  
    memcpy((char *)(ccs->config_bytes),addr,6);

    ccs->config_bytes[6]=0x0;
    ccs->config_bytes[7]=0x0;
  
    ioaddr = p_i82559->io_address;  
  
    OUTL(VIRT_TO_BUS(ccs), ioaddr + SCBPointer); 
    OUTW(SCB_M | CU_START, ioaddr + SCBCmd);    
    // Next delay seems to be required, otherwise,
    // cb_ok/cb_complete won't be set later.

    udelay(1000);
    wait_for_cmd_done(ioaddr);   
  
    // now check for result ...
    if ( (!ccs->cb_entry.cb_ok) || (!ccs->cb_entry.cb_complete) )
        return 3;
  
#ifdef CYGPKG_DEVS_ETH_ARM_IQ80310_WRITE_EEPROM

    addr_length = get_eeprom_size( ioaddr );

    // now set this address in the device eeprom ....
    (void)memcpy(eeprom_burn,addr,6);

    // No idea what these were for...
    // eeprom_burn[20] &= 0xfe;   
    // eeprom_burn[20] |= p_i82559->index;   
        
    program_eeprom( ioaddr, addr_length, eeprom_burn );
   
    // update 82559 driver data structure ...
    udelay( 100000 );

    // by reading EEPROM to get the mac address back
    for (checksum = 0, i = 0, count = 0; count < 64; count++) {
        cyg_uint16 value;
        // read word from eeprom
        value = read_eeprom(ioaddr, count, addr_length);
        checksum += value;
        if (count < 3) {
            p_i82559->mac_address[i++] = value & 0xFF;
            p_i82559->mac_address[i++] = (value >> 8) & 0xFF;
        }
    }
    
#ifdef DEBUG
    os_printf("MAC Address = %02X %02X %02X %02X %02X %02X\n",
              p_i82559->mac_address[0], p_i82559->mac_address[1],
              p_i82559->mac_address[2], p_i82559->mac_address[3],
              p_i82559->mac_address[4], p_i82559->mac_address[5]);
#endif

    p_i82559->mac_addr_ok = 1;

    for ( i = 0, count = 0; i < 6; i++ )
        if ( p_i82559->mac_address[i] != addr[i] )
            count++;

    if ( count ) {
#ifdef DEBUG
        os_printf( "Warning: MAC Address read back wrong!  %d bytes differ.\n",
                   count );
#endif
        p_i82559->mac_addr_ok = 0;
    }

    // If the EEPROM checksum is wrong, the MAC address read from the
    // EEPROM is probably wrong as well. In that case, we don't set
    // mac_addr_ok.
    if ((checksum & 0xFFFF) != 0xBABA)  {
#ifdef DEBUG
        os_printf( "Warning: Invalid EEPROM checksum %04X for device %d\n",
                   checksum, p_i82559->index);
#endif
        p_i82559->mac_addr_ok = 0;
    }
#else
    p_i82559->mac_addr_ok = 1;
#endif // ! CYGPKG_DEVS_ETH_ARM_IQ80310_WRITE_EEPROM

    return p_i82559->mac_addr_ok ? 0 : 1;
}

#ifdef CYGPKG_DEVS_ETH_ARM_IQ80310_WRITE_EEPROM
// ------------------------------------------------------------------------
static void
write_eeprom(long ioaddr, int location, int addr_len, unsigned short value)
{
    int ee_addr = ioaddr + SCBeeprom;
    int write_cmd = location | EE_WRITE_CMD(addr_len); 
    int i;
    
    OUTW(EE_ENB & ~EE_CS, ee_addr);
    eeprom_delay( 100 );
    OUTW(EE_ENB, ee_addr);
    eeprom_delay( 100 );

//    os_printf("\n write_eeprom : write_cmd : %x",write_cmd);  
//    os_printf("\n addr_len : %x  value : %x ",addr_len,value);  

    /* Shift the write command bits out. */
    for (i = (addr_len+2); i >= 0; i--) {
        short dataval = (write_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
        OUTW(EE_ENB | dataval, ee_addr);
        eeprom_delay(100);
        OUTW(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
        eeprom_delay(150);
    }
    OUTW(EE_ENB, ee_addr);
        
    for (i = 15; i >= 0; i--) {
        short dataval = (value & (1 << i)) ? EE_DATA_WRITE : 0;
        OUTW(EE_ENB | dataval, ee_addr);
        eeprom_delay(100);
        OUTW(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
        eeprom_delay(150);
    }

    /* Terminate the EEPROM access. */
    OUTW(EE_ENB & ~EE_CS, ee_addr);
    eeprom_delay(150000); // let the write take effect
}

// ------------------------------------------------------------------------
static int write_enable_eeprom(long ioaddr,  int addr_len)
{
    int ee_addr = ioaddr + SCBeeprom;
    int write_en_cmd = EE_WRITE_EN_CMD(addr_len); 
    int i;

    OUTW(EE_ENB & ~EE_CS, ee_addr);
    OUTW(EE_ENB, ee_addr);

#ifdef DEBUG_82559
    os_printf("write_en_cmd : %x",write_en_cmd);
#endif

    // Shift the wr/er enable command bits out.
    for (i = (addr_len+2); i >= 0; i--) {
	short dataval = (write_en_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
	OUTW(EE_ENB | dataval, ee_addr);
	eeprom_delay(100);
	OUTW(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
	eeprom_delay(150);
    }

    // Terminate the EEPROM access.
    OUTW(EE_ENB & ~EE_CS, ee_addr);
    eeprom_delay(EEPROM_DONE_DELAY);
}


// ------------------------------------------------------------------------
static void
program_eeprom(cyg_uint32 ioaddr, cyg_uint32 eeprom_size, cyg_uint8 *data)
{
  cyg_uint32 i;
  cyg_uint16 checksum = 0;
  cyg_uint16 value;

  // First enable erase/write operations on the eeprom.
  // This is done through the EWEN instruction.
  write_enable_eeprom( ioaddr, eeprom_size );

  for (i=0 ; i< 63 ; i++) {
    value = ((unsigned short *)data)[i];
    checksum += value;
#ifdef DEBUG_82559
    os_printf("\n i : %x ... value to be written : %x",i,value);
#endif
    write_eeprom( ioaddr, i, eeprom_size, value);
#ifdef DEBUG_82559
    os_printf("\n val read : %x ",read_eeprom(ioaddr,i,eeprom_size));
#endif
  }
  value = 0xBABA - checksum;
#ifdef DEBUG_82559
  os_printf("\n i : %x ... checksum adjustment val to be written : %x",i,value);
#endif
  write_eeprom( ioaddr, i, eeprom_size, value );
}

// ------------------------------------------------------------------------
#endif // ! CYGPKG_DEVS_ETH_ARM_IQ80310_WRITE_EEPROM


// ------------------------------------------------------------------------
//
//  Function : eth_get_mac_address
//
// ------------------------------------------------------------------------
#ifdef ETH_DRV_GET_MAC_ADDRESS
static int eth_get_mac_address(struct i82559* p_i82559, char *addr)
{
    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "eth_get_mac_address : Bad device pointer %x\n",
                   p_i82559 );
#endif
        return -1;
    }

    memcpy( addr, (char *)(&p_i82559->mac_address[0]), 6 );
    return 0;
}
#endif
// ------------------------------------------------------------------------
//
//  Function : i82559_ioctl
//
// ------------------------------------------------------------------------
static int i82559_ioctl(struct eth_drv_sc *sc, unsigned long key,
                        void *data, int data_length)
{
    struct i82559 *p_i82559;

    p_i82559 = (struct i82559 *)sc->driver_private;

    IF_BAD_82559( p_i82559 ) {
#ifdef DEBUG
        os_printf( "i82559_ioctl/control: Bad device pointer %x\n", p_i82559 );
#endif
        return -1;
    }

#ifdef DEBUG
    db_printf( "i82559_ioctl: device eth%d at %x; key is 0x%x, data at %x[%d]\n",
               p_i82559->index, p_i82559, key, data, data_length );
#endif

    switch ( key ) {

#ifdef ETH_DRV_SET_MAC_ADDRESS
    case ETH_DRV_SET_MAC_ADDRESS:
        if ( 6 != data_length )
            return -2;
        return eth_set_mac_address( p_i82559, data );
#endif

#ifdef ETH_DRV_GET_MAC_ADDRESS
    case ETH_DRV_GET_MAC_ADDRESS:
        return eth_get_mac_address( p_i82559, data );
#endif

#ifdef ETH_DRV_GET_IF_STATS_UD
    case ETH_DRV_GET_IF_STATS_UD: // UD == UPDATE
        ETH_STATS_INIT( sc );    // so UPDATE the statistics structure
#endif
        // drop through
#ifdef ETH_DRV_GET_IF_STATS
    case ETH_DRV_GET_IF_STATS:
#endif
#if defined(ETH_DRV_GET_IF_STATS) || defined (ETH_DRV_GET_IF_STATS_UD)
    {
        struct ether_drv_stats *p = (struct ether_drv_stats *)data;
        int i;
        static unsigned char my_chipset[]
            = { ETH_DEV_DOT3STATSETHERCHIPSET };

        strcpy( p->description, CYGDAT_DEVS_ETH_DESCRIPTION );
        CYG_ASSERT( 48 > strlen(p->description), "Description too long" );

        for ( i = 0; i < SNMP_CHIPSET_LEN; i++ )
            if ( 0 == (p->snmp_chipset[i] = my_chipset[i]) )
                break;

        i = i82559_status( sc );

        if ( !( i & GEN_STATUS_LINK) ) {
            p->operational = 2;         // LINK DOWN
            p->duplex = 1;              // UNKNOWN
            p->speed = 0;
        }
        else {
            p->operational = 3;            // LINK UP
            p->duplex = (i & GEN_STATUS_FDX) ? 3 : 2; // 2 = SIMPLEX, 3 = DUPLEX
            p->speed = ((i & GEN_STATUS_100MBPS) ? 100 : 10) * 1000000;
        }

#ifdef KEEP_STATISTICS
        {
            I82559_COUNTERS *pc = &i82559_counters[ p_i82559->index ];
            STATISTICS      *ps = &statistics[      p_i82559->index ];

            // Admit to it...
            p->supports_dot3        = true;

            // Those commented out are not available on this chip.

            p->tx_good              = pc->tx_good             ;
            p->tx_max_collisions    = pc->tx_max_collisions   ;
            p->tx_late_collisions   = pc->tx_late_collisions  ;
            p->tx_underrun          = pc->tx_underrun         ;
            p->tx_carrier_loss      = pc->tx_carrier_loss     ;
            p->tx_deferred          = pc->tx_deferred         ;
            //p->tx_sqetesterrors   = pc->tx_sqetesterrors    ;
            p->tx_single_collisions = pc->tx_single_collisions;
            p->tx_mult_collisions   = pc->tx_mult_collisions  ;
            p->tx_total_collisions  = pc->tx_total_collisions ;
            p->rx_good              = pc->rx_good             ;
            p->rx_crc_errors        = pc->rx_crc_errors       ;
            p->rx_align_errors      = pc->rx_align_errors     ;
            p->rx_resource_errors   = pc->rx_resource_errors  ;
            p->rx_overrun_errors    = pc->rx_overrun_errors   ;
            p->rx_collisions        = pc->rx_collisions       ;
            p->rx_short_frames      = pc->rx_short_frames     ;
            //p->rx_too_long_frames = pc->rx_too_long_frames  ;
            //p->rx_symbol_errors   = pc->rx_symbol_errors    ;
        
            p->interrupts           = ps->interrupts          ;
            p->rx_count             = ps->rx_count            ;
            p->rx_deliver           = ps->rx_deliver          ;
            p->rx_resource          = ps->rx_resource         ;
            p->rx_restart           = ps->rx_restart          ;
            p->tx_count             = ps->tx_count            ;
            p->tx_complete          = ps->tx_complete         ;
            p->tx_dropped           = ps->tx_dropped          ;
        }
#endif // KEEP_STATISTICS

        p->tx_queue_len = MAX_TX_DESCRIPTORS;

        return 0; // OK
    }
#endif

    default:
        break;
    }
    return -1;
}

// ------------------------------------------------------------------------
//
// Statistics update...
//
// ------------------------------------------------------------------------

#ifdef KEEP_STATISTICS
#ifdef CYGDBG_DEVS_ETH_ARM_IQ80310_KEEP_82559_STATISTICS
void update_statistics(struct i82559* p_i82559)
{
    I82559_COUNTERS *p_statistics;
    cyg_uint32 *p_counter;
    cyg_uint32 *p_register;
    int reg_count, ints;
    
    ints = Mask82559Interrupt(p_i82559);

    // This points to the sthared memory stats area/command block
    p_statistics = (I82559_COUNTERS *)(p_i82559->p_statistics);

    if ( (p_statistics->done & 0xFFFF) == 0xA007 ) {
        p_counter = (cyg_uint32 *)&i82559_counters[ p_i82559->index ];
        p_register = (cyg_uint32 *)p_statistics;
        for ( reg_count = 0;
              reg_count < sizeof( I82559_COUNTERS ) / sizeof( cyg_uint32 ) - 1;
              reg_count++ ) {
            *p_counter += *p_register;
            p_counter++;
            p_register++;
        }
        p_statistics->done = 0;
        // make sure no command operating
        wait_for_cmd_done(p_i82559->io_address);
        // start register dump
        OUTW(CU_DUMPSTATS, p_i82559->io_address + SCBCmd);
    }
    Acknowledge82559Interrupt(p_i82559); // This can eat an Rx interrupt, so
    PacketRxReady(p_i82559);

    UnMask82559Interrupt(p_i82559, ints);
}
#endif
#endif // KEEP_STATISTICS

// ------------------------------------------------------------------------
//
//
//           CODE FOR DEBUGGING PURPOSES ONLY
//
//
// ------------------------------------------------------------------------
void dump_txcb(TxCB *p_txcb)
{
    os_printf("TxCB @ %x\n", (int)p_txcb);
    os_printf("status = %04X ", p_txcb->txstatus);
    os_printf("command = %04X ", p_txcb->command);
    os_printf("link = %08X ", p_txcb->link);
    os_printf("tbd = %08X ", p_txcb->tbd_address);
    os_printf("count = %d ", p_txcb->count);
    os_printf("eof = %x ", p_txcb->eof);
    os_printf("threshold = %d ", p_txcb->tx_threshold);
    os_printf("tbd number = %d\n", p_txcb->tbd_number);
}

// This is intended to be the body of a THREAD that prints stuff every 10
// seconds or so:
#ifdef KEEP_STATISTICS
#ifdef DISPLAY_STATISTICS
void DisplayStatistics(void)
{
    int i;
    I82559_COUNTERS *p_statistics;
    cyg_uint32 *p_counter;
    cyg_uint32 *p_register;
    int reg_count;
    int status;
    
    while ( 1 ) {
#ifdef DISPLAY_82559_STATISTICS
        for ( i = 0; i < 2; i ++ ) {
            p_statistics = (I82559_COUNTERS *)i82559[i].p_statistics;
            if ( (p_statistics->done & 0xFFFF) == 0xA007 ) {
                p_counter = (cyg_uint32 *)&i82559_counters[i];
                p_register = (cyg_uint32 *)&p_statistics->tx_good;
                for ( reg_count = 20; reg_count != 0; reg_count--) {
                    *p_counter += *p_register;
                    p_counter++;
                    p_register++;
                }
                p_statistics->done = 0;
                // make sure no command operating
            	wait_for_cmd_done(i82559[i].io_address);
                // start register dump
                OUTW(CU_DUMPSTATS, i82559[i].io_address + SCBCmd);
            }
        }
#endif
        os_printf("\nRx\nPackets = %d  %d\n",
        statistics[0].rx_count, statistics[1].rx_count);
        os_printf("Deliver   %d  %d\n",
        statistics[0].rx_deliver, statistics[1].rx_deliver);
        os_printf("Resource  %d  %d\n",
        statistics[0].rx_resource, statistics[1].rx_resource);
        os_printf("Restart   %d  %d\n",
        statistics[0].rx_restart, statistics[1].rx_restart);

#ifdef DISPLAY_82559_STATISTICS
        os_printf("Count     %d  %d\n",
        i82559_counters[0].rx_good, i82559_counters[1].rx_good);
        os_printf("CRC       %d  %d\n",
        i82559_counters[0].rx_crc_errors, i82559_counters[1].rx_crc_errors);
        os_printf("Align     %d  %d\n",
        i82559_counters[0].rx_align_errors, i82559_counters[1].rx_align_errors);
        os_printf("Resource  %d  %d\n",
        i82559_counters[0].rx_resource_errors, i82559_counters[1].rx_resource_errors);
        os_printf("Overrun   %d  %d\n",
        i82559_counters[0].rx_overrun_errors, i82559_counters[1].rx_overrun_errors);
        os_printf("Collision %d  %d\n",
        i82559_counters[0].rx_collisions, i82559_counters[1].rx_collisions);
        os_printf("Short     %d  %d\n",
        i82559_counters[0].rx_short_frames, i82559_counters[1].rx_short_frames);
#endif
        os_printf("\nTx\nPackets = %d  %d\n",
        statistics[0].tx_count, statistics[1].tx_count);
        os_printf("Complete  %d  %d\n",
        statistics[0].tx_complete, statistics[1].tx_complete);
        os_printf("Dropped   %d  %d\n",
        statistics[0].tx_dropped, statistics[1].tx_dropped);
        os_printf("Count     %d  %d\n",
        i82559_counters[0].tx_good, i82559_counters[1].tx_good);
#ifdef DISPLAY_82559_STATISTICS
        os_printf("Collision %d  %d\n",
        i82559_counters[0].tx_max_collisions,i82559_counters[1].tx_max_collisions);
        os_printf("Late Col. %d  %d\n",
        i82559_counters[0].tx_late_collisions,i82559_counters[1].tx_late_collisions);
        os_printf("Underrun  %d  %d\n",
        i82559_counters[0].tx_underrun,i82559_counters[1].tx_underrun);
        os_printf("Carrier   %d  %d\n",
        i82559_counters[0].tx_carrier_loss,i82559_counters[1].tx_carrier_loss);
        os_printf("Deferred  %d  %d\n",
        i82559_counters[0].tx_deferred, i82559_counters[1].tx_deferred);
        os_printf("1 Col     %d  %d\n",
        i82559_counters[0].tx_single_collisions, i82559_counters[0].tx_single_collisions);
        os_printf("Mult. Col %d  %d\n",
        i82559_counters[0].tx_mult_collisions, i82559_counters[0].tx_mult_collisions);
        os_printf("Total Col %d  %d\n",
        i82559_counters[0].tx_total_collisions, i82559_counters[0].tx_total_collisions);
#endif
        status = INB(i82559[0].io_address + SCBGenStatus);
        os_printf("Interface 0 Link = %s, %s Mbps, %s Duplex\n",
            status & GEN_STATUS_LINK ? "Up" : "Down",
            status & GEN_STATUS_100MBPS ?  "100" : "10",
            status & GEN_STATUS_FDX ? "Full" : "Half");

        status = INB(i82559[1].io_address + SCBGenStatus);
        os_printf("Interface 1 Link = %s, %s Mbps, %s Duplex\n",
            status & GEN_STATUS_LINK ? "Up" : "Down",
            status & GEN_STATUS_100MBPS ?  "100" : "10",
            status & GEN_STATUS_FDX ? "Full" : "Half");

        cyg_thread_delay(1000);
    }
}
#endif // DISPLAY_STATISTICS
#endif // KEEP_STATISTICS

void dump_rfd(RFD *p_rfd, int anyway )
{
    if ( (0 != p_rfd->rxstatus) || anyway ) {
        os_printf("RFD @ %x = ", (int)p_rfd);
        os_printf("status = %x ", p_rfd->rxstatus);
        os_printf("link = %x ", p_rfd->link);
//        os_printf("rdb_address = %x ", p_rfd->rdb_address);
        os_printf("count = %x ", p_rfd->count);
        os_printf("f = %x ", p_rfd->f);
        os_printf("eof = %x ", p_rfd->eof);
        os_printf("size = %x\n", p_rfd->size);
        os_printf("[%04x %04x %04x] ",
                  *((cyg_uint16 *)(&(p_rfd->buffer[0]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[2]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[4]))) );
        os_printf("[%04x %04x %04x] %04x : ",          
                  *((cyg_uint16 *)(&(p_rfd->buffer[6]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[8]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[10]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[12]))) );
        os_printf("(%04x %04x %04x %04x) ",            
                  *((cyg_uint16 *)(&(p_rfd->buffer[14]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[16]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[18]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[20]))) );
        os_printf("[%04x %04x %04x] ",                 
                  *((cyg_uint16 *)(&(p_rfd->buffer[22]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[24]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[26]))) );
        os_printf("%d.%d.%d.%d ",
                  *((cyg_uint8  *)(&(p_rfd->buffer[28]))),
                  *((cyg_uint8  *)(&(p_rfd->buffer[29]))),
                  *((cyg_uint8  *)(&(p_rfd->buffer[30]))),
                  *((cyg_uint8  *)(&(p_rfd->buffer[31]))) );
        os_printf("[%04x %04x %04x] ",                 
                  *((cyg_uint16 *)(&(p_rfd->buffer[32]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[34]))),
                  *((cyg_uint16 *)(&(p_rfd->buffer[36]))) );
        os_printf("%d.%d.%d.%d ...\n",
                  *((cyg_uint8  *)(&(p_rfd->buffer[38]))),
                  *((cyg_uint8  *)(&(p_rfd->buffer[39]))),
                  *((cyg_uint8  *)(&(p_rfd->buffer[40]))),
                  *((cyg_uint8  *)(&(p_rfd->buffer[41]))) );
    }
}

void dump_all_rfds( int intf )
{
    struct i82559* p_i82559 = &i82559[intf];
    int i, j;
    j = p_i82559->next_rx_descriptor;
    os_printf("rx descriptors for interface %d (eth%d):\n", intf, intf );
    for ( i = 0; i < MAX_RX_DESCRIPTORS; i++ )
        dump_rfd( p_i82559->rx_ring[i], (i > (j-3) && (i <= j)) );
    os_printf("next rx descriptor = %x\n\n", j);
}


void dump_packet(cyg_uint8 *p_buffer, int length)
{
    int count;

    count = 0;
    while ( length > 0 ) {
        if ( count == 0 )
            os_printf("\n");
        count = (count + 1) & 0x0F;
        os_printf("%02X ", *p_buffer++);
        length--;
    }
    os_printf("\n");
}

// ------------------------------------------------------------------------

// EOF if_iq80310.c
