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

#if 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);
}
#endif


int gp_ack(GPctrl, GPreg, ioaddr)
{
volatile int ack;

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

  outb(GPreg    , ioaddr + GPio);	//data lo, clock lo
	udelay(1);
  outb(GPreg | 2, ioaddr + GPio);	//data lo, clock hi
	udelay(1);
	ack = inb(ioaddr + GPio);
  outb(GPreg    , ioaddr + GPio);	//data lo, clock lo
	// make GP0 and 1 outputs
  outb(GPctrl , ioaddr + GPioCtrl);

	if (ack & 0x01)
	{
		printk("Ack error!\n");
	        return(1);
	}

    return(0);
}


void gp_write_byte(data, GPreg, ioaddr)
{
int	i;

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

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

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

	// set SCSI GPIO 0 and 1 as inputs
	GPctrl = inb(ioaddr + 0x47);
	GPctrl |= 3;
	outb(GPctrl, ioaddr+0x47);

	// 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 , ioaddr + GPioCtrl);
	
	//-----------------------------------------------------------------
	// drive both high
  outb(GPreg| 3, ioaddr + GPio);
	udelay(2);

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

	//write A0 command to the chip, followed by ACK clock
	//=================================================================
	data = 0xA0;
	gp_write_byte(data, GPreg, ioaddr);
	data = gp_ack(GPctrl, GPreg, ioaddr);

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

	data = location;
	gp_write_byte(data, GPreg, ioaddr);
	data = gp_ack(GPctrl, GPreg, ioaddr);
	//-----------------------------------------------------------------

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

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


	//write A1 command to the chip, followed by ACK clock
	//=================================================================
	data = 0xA1;
	gp_write_byte(data, GPreg, ioaddr);
	data = gp_ack(GPctrl, GPreg, ioaddr);

	//-----------------------------------------------------------------
	//read 8 bits of data
	// make GP0 input, GP1 still output (clock)
  outb(GPctrl | 1, ioaddr + GPioCtrl);
	data = 0;

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

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

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

  outb(GPctrl | 3, ioaddr + GPioCtrl);

	return(data);
}

static int read_eeprom(long ioaddr, int location)
{
	int bogus_cnt = 100000;
	volatile unsigned char status;
	volatile unsigned char data;

        outb(location, ioaddr + EEAddr);
        /* issue a read command */
	outb(0x30 | ((location >> 8) & 7), ioaddr + EECtrl);
	while (((status=inb(ioaddr + EEStatus)) & 0x80)  && --bogus_cnt > 0)
		;

	data = inb(ioaddr + EERead);

	if (status)
	    printk ("Suspect read problem: address 0x%02X, data 0x%02X, status 0x%02X.\n",
		location, data, status);
	return data;
}

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

        outb(1,        ioaddr + EEFeature);
        outb(location, ioaddr + EEAddr);
        outb(value,    ioaddr + EEWrite);
        /* issue a write command */
	outb(0x20 | ((location >> 8) & 7), ioaddr + EECtrl);
	while ((inb(ioaddr + EEStatus) & 0x80)  && --bogus_cnt > 0)
            ;
        // poll for ready
        do {
            outb(0x10, ioaddr + EECtrl);
            while ((inb(ioaddr + EEStatus) & 0x80) && --bogus_cnt > 0);
        } while ((inb(ioaddr + EEStatus) & 0x81) && bogus_cnt > 0);
	return;
}

/* 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[128] =         /* 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[12];


        ioaddr = BASE_ADDR;    // io mapped PCI memory address 0x500
        if (inw(ioaddr + ChipRev) != 0x701) {
            printk(KERN_INFO "The YellowFin (83c885) device does not seem to be installed.\n"
                   KERN_INFO "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(KERN_INFO "This YellowFin (8xc885) board has already been initialized.\n"
                   KERN_INFO "To force a new config, uninstall this module and reinstall with\n"
                   KERN_INFO "         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;


	retry = 10;
write_loop:

#if 0
        printk("write_eeprom: ");

        for (i = 0; i < 11; i++)
	{
		printk("%02X ",vnc_eeprom[i]);
		write_eeprom(ioaddr, i, vnc_eeprom[i]);
	}
        printk("\n");
#endif

        printk("ws_read_eeprom: ");

        for (i = 0; i < 11; i++)
        {
		vnc_verify[i] = ws_read_eeprom(ioaddr, i);
		printk("%02X ",vnc_verify[i]);
//		delay20ms();
        }
        printk("\n");

#if 1

        printk("   read_eeprom: ");

        for (i = 0; i < 11; i++)
        {
		vnc_verify[i] = read_eeprom(ioaddr, i);
		printk("%02X ",vnc_verify[i]);
//		delay20ms();
        }
        printk("\n");
#endif


#if 0
        for (i = 0; i < 11; i++)
        {
		if ((vnc_verify[i] != vnc_eeprom[i]) && retry)
		{
			retry--;
			goto write_loop;
		}
	}
#endif
        printk("Done..\n");
        return (-EBUSY);
}

void
cleanup_module(void)
{
}

