/* a tiny device driver to fill the eeprom on
 * the NetWinder yellowfin board
 */
#include <linux/config.h>
#ifdef MODULE
#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/module.h>
#include <linux/version.h>
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#endif

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/ptrace.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/malloc.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <asm/bitops.h>
#include <asm/io.h>
#include <asm/dma.h>

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>

#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x10338
#ifdef MODULE
#if !defined(CONFIG_MODVERSIONS) && !defined(__NO_VERSION__)
char kernel_version[] = UTS_RELEASE;
#endif
#else
#undef MOD_INC_USE_COUNT
#define MOD_INC_USE_COUNT
#undef MOD_DEC_USE_COUNT
#define MOD_DEC_USE_COUNT
#endif
#endif /* 1.3.38 */

#if (LINUX_VERSION_CODE >= 0x10344)
#define NEW_MULTICAST
#include <linux/delay.h>
#endif

#define BASE_ADDR  0x500
/* Offsets to the hardware */
enum yellowfin_offsets {
	TxCtrl=0x00, TxStatus=0x04, TxPtr=0x0C,
	TxIntrSel=0x10, TxBranchSel=0x14, TxWaitSel=0x18,
	RxCtrl=0x40, RxStatus=0x44, RxPtr=0x4C,
	RxIntrSel=0x50, RxBranchSel=0x54, RxWaitSel=0x58,
	EventStatus=0x80, IntrEnb=0x82, IntrClear=0x84, IntrStatus=0x86,
        ChipRev=0x8C, DMACtrl=0x90, GPio=0x9e, GPioCtrl=0x9f,
        Cnfg=0xA0, FrameGap0=0xA2, FrameGap1=0xA4,
	MII_Cmd=0xA6, MII_Addr=0xA8, MII_Wr_Data=0xAA, MII_Rd_Data=0xAC,
	MII_Status=0xAE,
	RxDepth=0xB8, FlowCtrl=0xBC,
	AddrMode=0xD0, StnAddr=0xD2, HashTbl=0xD8, FIFOcfg=0xF8,
	EEStatus=0xF0, EECtrl=0xF1, EEAddr=0xF2, EERead=0xF3, EEWrite=0xF4,
	EEFeature=0xF5,
};

extern unsigned int board_id;
extern unsigned int serial_num;
int force = 0;

void delay_wait(int ticks)
{
    /* wait ticks*10 ms */
    current->state = TASK_INTERRUPTIBLE;
    current->timeout = jiffies + ticks;
    schedule();
}

void	delay20ms(void)
{
    //wait good 20 ms
    delay_wait(3);
}

/*
GPIO0 - serial data
GPIO1 - serial clock
*/

static int ws_read_eeprom(long ioaddr, int location)
{
volatile unsigned char GPctrl;
volatile unsigned char GPreg;


	// get current setting
	GPctrl = inb(ioaddr + GPioCtrl);
	GPctrl&= 0xFC;	//clear GP0 and GP1 bits
	GPreg	 = inb(ioaddr + GPio);
	GPreg &= 0xFC;	//clear GP0 and GP1 bits

	// make GP0 and 1 outputs
  outb(GPctrl | 3, ioaddr + GPioCtrl);
	
	//-----------------------------------------------------------------
	// drive both high
  outb(GPio | 3, ioaddr + GPio);
	udelay(2);

	//start sequence from clock and data hi: data low, followed by clock 
	udelay(2);
  outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
	udelay(2);
  outb(GPio    , ioaddr + GPio);	//data lo, clock lo

	//write A0 command to the chip, followed by ACK clock
	//=================================================================
	data = 0xA0;
	for (i = 7; i>=0; i--)
	{
		udelay(1);
		if (data & (1<<i))	//bit set?
		{
		  outb(GPio | 1, ioaddr + GPio);	//data hi, clock lo
			udelay(1);
		  outb(GPio | 3, ioaddr + GPio);	//data hi, clock hi
			udelay(2);
		  outb(GPio | 1, ioaddr + GPio);	//data hi, clock lo
		}
		else
		{
		  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
			udelay(1);
		  outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
			udelay(2);
		  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
		}
		udelay(1);
	}
	udelay(1);

	//-----------------------------------------------------------------
	//8 bits clocked - read acknowledge
	// make GP0 input, GP1 still output (clock)
  outb(GPctrl | 2, ioaddr + GPioCtrl);

  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
	udelay(1);
  outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
	udelay(1);
	ack = inb(ioaddr + GPio);
  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
	if (ack & 0x01)
	{
		printk("Ack1 error!\n");
	}
	// make GP0 and 1 outputs
  outb(GPctrl | 3, ioaddr + GPioCtrl);
	//-----------------------------------------------------------------

	data = location;

	for (i = 7; i>=0; i--)
	{
		udelay(1);

		if (data & (1<<i))	//bit set?
		{
		  outb(GPio | 1, ioaddr + GPio);	//data hi, clock lo
			udelay(1);
		  outb(GPio | 3, ioaddr + GPio);	//data hi, clock hi
			udelay(2);
		  outb(GPio | 1, ioaddr + GPio);	//data hi, clock lo
		}
		else
		{
		  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
			udelay(1);
		  outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
			udelay(2);
		  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
		}
		udelay(1);
	}
	udelay(1);

	//-----------------------------------------------------------------
	//8 bits clocked - read acknowledge
	// make GP0 input, GP1 still output (clock)
  outb(GPctrl | 2, ioaddr + GPioCtrl);

  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
	udelay(1);
  outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
	udelay(1);
	ack = inb(ioaddr + GPio);
  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
	if (ack & 0x01)
	{
		printk("Ack2 error!\n");
	}

	// make GP0 and 1 outputs
  outb(GPctrl | 3, ioaddr + GPioCtrl);
	//-----------------------------------------------------------------


	//=================================================================
	
	// drive both high
  outb(GPio | 3, ioaddr + GPio);
	udelay(2);

	//start sequence from clock and data hi: data low, followed by clock 
	udelay(2);
  outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
	udelay(2);
  outb(GPio    , ioaddr + GPio);	//data lo, clock lo


	//write A1 command to the chip, followed by ACK clock
	//=================================================================
	data = 0xA1;
	for (i = 7; i>=0; i--)
	{
		udelay(1);
		if (data & (1<<i))	//bit set?
		{
		  outb(GPio | 1, ioaddr + GPio);	//data hi, clock lo
			udelay(1);
		  outb(GPio | 3, ioaddr + GPio);	//data hi, clock hi
			udelay(2);
		  outb(GPio | 1, ioaddr + GPio);	//data hi, clock lo
		}
		else
		{
		  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
			udelay(1);
		  outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
			udelay(2);
		  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
		}
		udelay(1);
	}
	udelay(1);

	//-----------------------------------------------------------------
	//8 bits clocked - read acknowledge
	// make GP0 input, GP1 still output (clock)
  outb(GPctrl | 2, ioaddr + GPioCtrl);

  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
	udelay(1);
  outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
	udelay(1);
	ack = inb(ioaddr + GPio);
  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
	if (ack & 0x01)
	{
		printk("Ack3 error!\n");
	}


	//-----------------------------------------------------------------
	//read 8 bits of data
	data = 0;

	for (i = 7; i>=0; i--)
	{
		udelay(1);
		outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
		ack = inb(ioaddr + GPio);
		ack &= 0x01;
		ack <<= i;
		data |= ack;
		udelay(1);
	  outb(GPio    , ioaddr + GPio);	//data lo, clock lo
		udelay(2);
	}

	// make GP0 and 1 outputs
  outb(GPctrl | 3, ioaddr + GPioCtrl);

	//=================================================================
	// generate stop condition
	udelay(2);
  outb(GPio | 2, ioaddr + GPio);	//data lo, clock hi
	udelay(2);
  outb(GPio | 3, ioaddr + GPio);	//data hi, clock hi


	return(data);
}


static int read_eeprom(long ioaddr, int location)
{
	int bogus_cnt = 100000;
	volatile int c;
	
        outb((0xFF & location), ioaddr + EEAddr);

        /* issue a read command */
	outb((0x30 | ((location >> 8) & 7)), ioaddr + EECtrl);

	while (((c = inb(ioaddr + EEStatus)) & 0x80) && (bogus_cnt > 0))
	{
	    bogus_cnt--;
	}
	if (!bogus_cnt || c)
	{
	    printk("8xc885 eeprom read error: status 0x%02X.\n",c);
	    return(-1);
	}
	else
	    return(inb(ioaddr + EERead) & 0xFF);
}


static int write_eeprom(int ioaddr, int location, int value)
{
	int bogus_cnt = 100000;
	volatile int c;

        outb(1,                 ioaddr + EEFeature); //enable writing
        outb((location & 0xFF), ioaddr + EEAddr);
        outb((value & 0xFF),    ioaddr + EEWrite);
        /* issue a write command */
	outb(0x20 | ((location >> 8) & 7), ioaddr + EECtrl);
	
	while (((c = inb(ioaddr + EEStatus)) & 0x80) && (bogus_cnt > 0))
	{
	    bogus_cnt--;
        }
	if (!bogus_cnt || c)
	{
	    printk("8xc885 eeprom write error: status 0x%02X.\n",c);
	    return(-1);
	}

        // poll for ready
#if 0
        do
	{
            outb(0x10, ioaddr + EECtrl);	//sequential read
	    while (((c = inb(ioaddr + EEStatus)) & 0x80) && (bogus_cnt > 0))
	    {
		bogus_cnt--;
	    }

        } while ((c & 0x01) && (bogus_cnt > 0));

	if (!bogus_cnt || c)
	{
	    printk("8xc885 eeprom write timeout: status 0x%02X.\n",c);
	    return(-1);
	}
#endif
	return (0);
}

/* the yellowfin chip only needs a simple add */
static unsigned char
calc_checksum(unsigned char *eeprom)
{
        int crc = 0x55;           /* NB really 32 bits, int32 or __s32.  */
        int i;

        for (i = 0; i < 10; i++)        /* Note: loc. 63 is the CRC. */
            crc += eeprom[i];
        return (unsigned char) (-(signed char)(crc & 0xff));
}

int
init_module(void)
{

        int i;
        unsigned short sum = 0;
        int ioaddr;
	int retry;

        unsigned char vnc_eeprom[16] =         /* Serial EEPROM contents. */
        {
            0x4E,0x57,          /* sub vendor ID 'NW' */
            0x59,0x46,          /* subsystem ID 'YF'*/
            0,0,0,0,0,0,0,0,0,0,0,0    /* 12 reserved */
        };
        unsigned char vnc_verify[16];
        
        ioaddr = BASE_ADDR;    // io mapped PCI memory address 0x500
        if (inw(ioaddr + ChipRev) != 0x701) {
            printk("The YellowFin (83c885) device does not seem to be installed.\n"
                   "No upgrade done\n");
            return 0;
        }
        // set the two eeprom control bits to outputs
        //outb(inb(ioaddr + GPioCtrl) & 0xFC, ioaddr + GPioCtrl);
#if 0
        i = (read_eeprom(ioaddr, 5) << 8) + read_eeprom(ioaddr, 6);
        if (i==0x1057 && force==0) {
            printk("This YellowFin (8xc885) board has already been initialized.\n"
                   "To force a new config, uninstall this module and reinstall with\n"
                   "         insmod yellow_upgd.o force=1\n");
            return 0;
        }
#endif
            
        printk("Upgrading board rev. %X serial #: %d (0x%x)...\n",board_id,
               serial_num,serial_num);

        vnc_eeprom[4] = 0;
        vnc_eeprom[5] = 0x10;
        vnc_eeprom[6] = 0x57;
        vnc_eeprom[7] = 0xC0;
        vnc_eeprom[8] = serial_num >> 8;
        vnc_eeprom[9] = serial_num;
        i = calc_checksum(vnc_eeprom);
        vnc_eeprom[10] = i & 0xFF;


        for (i = 0; i < 11; i++)
	{
		vnc_verify[i] = ~vnc_eeprom[i];
		retry = 100;
		while (retry & vnc_verify[i] != vnc_eeprom[i])
		{
			write_eeprom(ioaddr, i, vnc_eeprom[i]);
			delay20ms();
			vnc_verify[i] = read_eeprom(ioaddr, i);
		}

		if (!retry)
		{
		    printk("Verify error!\n");
		    return(i+1);
		}
	}

	// now program the same stuff directly in the StationAddress regs
	// that way we do not need to wait till reset for chip to read it in
	for (i = 0; i < 6; i++)
	{
		outb(vnc_eeprom[4+i], ioaddr + StnAddr + i);
	}

        printk("Done OK..\n");
	return(0);
}

void
cleanup_module(void)
{
}

