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

#include <redboot.h>
#include <cyg/io/pci.h>
#include "test_menu.h"

extern int diag_ishex(char theChar);
extern int diag_hex2dec(char hex);

#define READMEM8(   _reg_, _val_ ) ((CYG_BYTE)(_val_) = *((volatile CYG_BYTE *)(_reg_)))
#define WRITEMEM8(  _reg_, _val_ ) (*((volatile CYG_BYTE *)(_reg_)) = (CYG_BYTE)(_val_))
#define READMEM16(  _reg_, _val_ ) ((CYG_WORD16)(_val_) = *((volatile CYG_WORD16 *)(_reg_)))
#define WRITEMEM16( _reg_, _val_ ) (*((volatile CYG_WORD16 *)(_reg_)) = (CYG_WORD16)(_val_))
#define READMEM32(  _reg_, _val_ ) ((CYG_WORD32)(_val_) = *((volatile CYG_WORD32 *)(_reg_)))
#define WRITEMEM32( _reg_, _val_ ) (*((volatile CYG_WORD32 *)(_reg_)) = (CYG_WORD32)(_val_))
#define READMEM64(  _reg_, _val_ ) ((CYG_WORD64)(_val_) = *((volatile CYG_WORD64 *)(_reg_)))
#define WRITEMEM64( _reg_, _val_ ) (*((volatile CYG_WORD64 *)(_reg_)) = (CYG_WORD64)(_val_))

#define OUTL( _v_, _a_ ) WRITEMEM32( _a_, _v_ )

static inline cyg_uint32 INL(cyg_uint32 io_address)
 {   cyg_uint32 _t_; READMEM32( io_address, _t_ ); return _t_;   }

// ------------------------------------------------------------------------
//
// Serial EEPROM access  - much like the other Intel ethernet
//

#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		(0x10|EE_CS)

#define EE_DELAY() do { int z; for ( z = 0; z < 10000; z++ ) ; } while (0)

#if 0
# define EE_PRINTF diag_printf
# define EE_STUFF "%4s | %4s | %4s | %4s [%08x]\n",     \
    (l & EE_SHIFT_CLK) ? "CLK"  : "clk",                      \
    (l & EE_CS) ? "eeCS" : "--",                       \
    (l & EE_DATA_WRITE) ? "eeDW" : "---",                      \
    (l & EE_DATA_READ) ? "eeDR" : "---",                      \
    l & 0xfffff
#else
# define EE_PRINTF( foo )
# define EE_STUFF
#endif

#define I82544_EECD     0x00010

static inline void
ee_select( int ioaddr )
{
    cyg_uint32 l;
    l = EE_ENB;
    OUTL( l, ioaddr + I82544_EECD );
    EE_DELAY();
    l |= EE_CS;
    OUTL( l, ioaddr + I82544_EECD );
    l = INL( ioaddr + I82544_EECD );
    EE_PRINTF( "ee_select       : " EE_STUFF  );
    EE_DELAY();
}

static inline void
ee_deselect( int ioaddr )
{
    cyg_uint32 l;
    l = EE_ENB;
    OUTL( l, ioaddr + I82544_EECD );
    EE_PRINTF( "ee_deselect 1   : " EE_STUFF  );
    EE_DELAY();
    EE_DELAY();
    EE_DELAY();
    l = EE_ENB & ~EE_CS;
    OUTL( l, ioaddr + I82544_EECD );
    l = INL( ioaddr + I82544_EECD );
    EE_PRINTF( "ee_deselect 2   : " EE_STUFF  );
    EE_DELAY();
    EE_DELAY();
    EE_DELAY();
}

static inline void
ee_clock_up( int ioaddr )
{
    cyg_uint32 l;
    l = INL( ioaddr + I82544_EECD );
    l |= EE_SHIFT_CLK;
    OUTL( l, ioaddr + I82544_EECD );
    EE_DELAY();
    l = INL( ioaddr + I82544_EECD );
    EE_PRINTF( "ee_clock_up     : " EE_STUFF  );
    EE_DELAY();
}

static inline void
ee_clock_down( int ioaddr )
{
    cyg_uint32 l;
    l = INL( ioaddr + I82544_EECD );
    l &=~ EE_SHIFT_CLK;
    OUTL( l, ioaddr + I82544_EECD );
    EE_DELAY();
    l = INL( ioaddr + I82544_EECD );
    EE_PRINTF( "ee_clock_down   : " EE_STUFF  );
    EE_DELAY();
}

static inline int
ee_read_data_bit( int ioaddr )
{
    cyg_uint32 l;
    l = INL( ioaddr + I82544_EECD );
    EE_PRINTF( "ee_read_data    : " EE_STUFF  );
    return EE_DATA_READ == (EE_DATA_READ & l);
}

static inline void
ee_write_data_bit( int ioaddr, int databit )
{
    cyg_uint32 l;
    l = INL( ioaddr + I82544_EECD );
    if ( databit )
        l |= EE_DATA_WRITE;
    else
        l &= ~EE_DATA_WRITE;
    OUTL( l, ioaddr + I82544_EECD );
    l = INL( ioaddr + I82544_EECD );
    EE_PRINTF( "ee_write_data   : " EE_STUFF  );
    EE_DELAY();
}

// Pass ioaddr around "invisibly"
#define EE_SELECT()              ee_select(ioaddr)
#define EE_DESELECT()            ee_deselect(ioaddr)
#define EE_CLOCK_UP()            ee_clock_up(ioaddr)
#define EE_CLOCK_DOWN()          ee_clock_down(ioaddr)
#define EE_READ_DATA_BIT()       ee_read_data_bit(ioaddr)
#define EE_WRITE_DATA_BIT( _d_ ) ee_write_data_bit(ioaddr,(_d_))

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

static int
read_eeprom_word( cyg_uint32 ioaddr, int addrbits, int address )
{
    int i, tmp;

    // Should already be not-selected, but anyway:
    EE_SELECT();

    // Shift the read command bits out.
    for (i = 3; i >= 0; i--) { // Doc says to shift out a zero then:
        tmp = (6 & (1 << i)) ? 1 : 0; // "6" is the "read" command.
        EE_WRITE_DATA_BIT(tmp);
        EE_CLOCK_UP();
        EE_CLOCK_DOWN();
    }

    // Now clock out address
    for ( i = addrbits - 1; i >= 0 ; i-- ) {
        tmp = (address & (1<<i)) ? 1 : 0;
        EE_WRITE_DATA_BIT(tmp);
        EE_CLOCK_UP();
        tmp = EE_READ_DATA_BIT();
        EE_CLOCK_DOWN();

//        CYG_ASSERT( (0 == tmp) == (0 == i), "Looking for zero handshake bit" );
    }

    // read in the data
    tmp = 0;
    for (i = 15; i >= 0; i--) {
        EE_CLOCK_UP();
        if ( EE_READ_DATA_BIT() )
            tmp |= (1<<i);
        EE_CLOCK_DOWN();
    }

    // Terminate the EEPROM access.
    EE_DESELECT();
 
#ifdef DEBUG_EE
//    diag_printf( "eeprom address %4x: data %4x\n", address, tmp );
#endif

    return tmp;
}

// ------------------------------------------------------------------------
static void
write_eeprom_word( cyg_uint32 ioaddr, int addrbits, int address, cyg_uint16 value)
{
    int i, tmp;
    
    // Should already be not-selected, but anyway:
    EE_SELECT();

    // Shift the write command bits out.
    for (i = 3; i >= 0; i--) {          // Doc says to shift out a zero then:
        tmp = (5 & (1 << i)) ? 1 : 0;   // "5" is the "write" command.
        EE_WRITE_DATA_BIT(tmp);
        EE_CLOCK_UP();
        EE_CLOCK_DOWN();
    }

    // Now clock out address
    for ( i = addrbits - 1; i >= 0 ; i-- ) {
        tmp = (address & (1<<i)) ? 1 : 0;
        EE_WRITE_DATA_BIT(tmp);
        EE_CLOCK_UP();
        tmp = EE_READ_DATA_BIT();
        EE_CLOCK_DOWN();
    }

    
    // write the data
    for (i = 15; i >= 0; i--) {
        tmp = (value & (1 << i)) ? 1 : 0;
        EE_WRITE_DATA_BIT(tmp);
        EE_CLOCK_UP();
        EE_CLOCK_DOWN();
    }

    // Terminate the EEPROM access.
    EE_DESELECT();

    CYGACC_CALL_IF_DELAY_US((cyg_int32)15*1000);
}

// ------------------------------------------------------------------------
static int
write_enable_eeprom(cyg_uint32 ioaddr, int addrbits)
{
    int i, tmp;

    // Should already be not-selected, but anyway:
    EE_SELECT();

    // Shift the EWEN command bits out.
    for (i = 5; i >= 0; i--) {           // Doc says to shift out a zero then:
        tmp = (0x13 & (1 << i)) ? 1 : 0; // "0x13" is the "EWEN" command.
        EE_WRITE_DATA_BIT(tmp);
        EE_CLOCK_UP();
        EE_CLOCK_DOWN();
    }

    // Shift the rest of the address bits out. (ignored)
    // Two bits were used for the EWEN command above.
    tmp = 0;
    for (i = (addrbits-3); i >= 0; i--) {
        EE_WRITE_DATA_BIT(tmp);
        EE_CLOCK_UP();
        EE_CLOCK_DOWN();
    }

    // Terminate the EEPROM access.
    EE_DESELECT();
}


// ------------------------------------------------------------------------
static void
program_eeprom(cyg_uint32 ioaddr, int addrbits, cyg_uint16 *data)
{
    cyg_uint32 i;
    cyg_uint16 checksum = 0;

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

    for (i = 0; i < 63; i++, data++) {
	checksum += *data;
	write_eeprom_word(ioaddr, addrbits, i, *data);
    }
    checksum = 0xBABA - checksum;
    write_eeprom_word(ioaddr, addrbits, i, checksum);
}

// Get MAC address from user.
// Acceptable formats are:
//   nn.nn.nn.nn.nn.nn
//   nn:nn:nn:nn:nn:nn
//   nn-nn-nn-nn-nn-nn
//   nn nn nn nn nn nn
//   nnnnnnnnnnnn
static void
get_mac_address(char *addr_buf)
{
    char input[40], mac[6], *p;
    int got, i;

    do {
	got = 0;
	diag_printf ("\nCurrent MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
		     addr_buf[0], addr_buf[1], addr_buf[2],
		     addr_buf[3], addr_buf[4], addr_buf[5]);
	diag_printf ("Enter desired MAC address: ");
	while (_rb_gets(input, sizeof(input), 0) != _GETS_OK)
	    ;

	p = input;
	while (*p && *p == ' ');

	if (p[0] == '\0' || p[1] == '\0')
	    continue;

	for (; got < 6; got++) {
	    if (!diag_ishex(p[0]) || !diag_ishex(p[1]))
		break;
	    mac[got] = (diag_hex2dec(p[0]) * 16) + diag_hex2dec(p[1]);
	    p += 2;
	    if (*p == '.' || *p == ':' || *p == ' ' || *p == '-')
		p++;
	}
    } while (got != 6);

    for (i = 0; i < 6; i++)
	*addr_buf++ = mac[i];
}


static cyg_uint16 eeprom_defaults[] = {
/* halfword addresses! */
/*  0: */  0x0000,  0x0000,  0x0000,  0x0000,
/*  4: */  0x0000,  0x0000,  0x0000,  0x0000,
/*  8: */  0x0000,  0x0000,  0x4e0b,  0x1008,
/*  C: */  0x8086,  0x1008,  0x8086,  0xf02c,
/* 10: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 14: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 18: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 1C: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 20: */  0xdfde,  0x0002,  0x1414,  0xffff,
/* 24: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 28: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 2C: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 30: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 34: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 38: */  0xffff,  0xffff,  0xffff,  0xffff,
/* 3C: */  0xffff,  0xffff,  0xffff,  0x0000,
};

/* Setup Serial EEPROM for Ethernet Configuration */
void
enet_setup (MENU_ARG arg)
{
    cyg_pci_device dev_info;
    cyg_pci_device_id devid;
    cyg_uint16 cksum;
    cyg_uint16 eeprom_data[64];
    cyg_uint32 nic_addr;
    int i, bus;

    // First, look for NIC at private and public addresses

    bus = (*ATU_PCIXSR >> 8) & 0xff;
    if (bus == 0xff)
	bus = 0;

    devid = CYG_PCI_DEV_MAKE_ID(bus, CYG_PCI_DEV_MAKE_DEVFN(__NIC_PUB, 0));
    cyg_pci_get_device_info(devid, &dev_info);

    if (dev_info.vendor != 0x8086) {
	devid = CYG_PCI_DEV_MAKE_ID(bus, CYG_PCI_DEV_MAKE_DEVFN(__NIC_PRIV, 0));
	cyg_pci_get_device_info(devid, &dev_info);
	if (dev_info.vendor != 0x8086) {
	    diag_printf("No i82544 device found\n");
	    return;
	}
    }

    nic_addr = dev_info.base_map[0];

    // Now read complete EEPROM contents
    for (i = cksum = 0; i < 64; i++) {
	eeprom_data[i] = read_eeprom_word(nic_addr, 6, i);
	cksum += eeprom_data[i];
    }

    // If not valid, initialize to defaults
    if (cksum != 0xBABA || (eeprom_data[0x0a] & 0xC000) != 0x4000) {
	diag_printf ("Current EEPROM contents invalid. Restoring defaults.\n");
	for (i = 0; i < 63; i++)
	    eeprom_data[i] = eeprom_defaults[i];
    }
	
    get_mac_address((char *)eeprom_data);

    // Now compute checksum on complete EEPROM contents
    for (i = cksum = 0; i < 63; i++)
	cksum += eeprom_data[i];

    eeprom_data[63] = 0xBABA - cksum;

    diag_printf ("Writing to the Serial EEPROM... ");
    program_eeprom(nic_addr, 6, eeprom_data);
    diag_printf ("Done\n"); 

    /* now that we have finished writing the configuration data, we must ask the
       operator to reset the board to have the configuration changes take effect.
       After the reset, the standard Enet. port diagnostics can be run on the
       board under test */
    
    diag_printf ("\n******** Reset The Board To Have Changes Take Effect ********\n");
}


