/* PCI-E ECAT Service Module

   Author: Golden Wang

   This program 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.

   This program 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 this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */

/* File level history (record changes for this file here.)

   v 0.1.5 24 Aug 2018 by Golden Wang
     Add ioctl cmd "IXECAT_HOST_B0" to read/write Host's Bar0.

   v 0.1.4 13 Aug 2018 by Golden Wang
     Add the code to resolve the error INT from Firmware(v 1.4).

   v 0.1.3 9 Aug 2018 by Golden Wang
     Adding the code to clear the flag "DPRAM_f" of RDC's ISR register when 
     the DPRAM INT bit(page 0x7ff) is low in the function "irq_handler".

   v 0.1.2 28 Jun 2018 by Golden Wang
     removing timer's code and modify "ixecat_generic_open" function for the CPLD's bug 
     that host's interrupt can't work correctly.

   v 0.1.1 30 May 2018 by Golden Wang
     support DPRAM/EMG INT and EMG signal.

   v 0.1.0 17 Jan 2018 by Golden Wang
     support SN2401, EEPROM(Spertan6 Flash) DPRAM/EMG INT and EMG signal.

   v 0.0.0 20 Dec 2016 by Golden Wang
     create, blah blah... */

/* Mandatory */
#include <linux/kernel.h>		/* ta, kernel work */
#include <linux/module.h>		/* is a module */
#include <linux/delay.h>
#include <linux/uaccess.h>
#include "ixecat.h"

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#include <linux/cdev.h>
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
        /* Deal with CONFIG_MODVERSIONS that is defined in
         * /usr/include/linux/config.h (config.h is included by module.h) */
        #if CONFIG_MODVERSIONS==1
        #define MODVERSIONS
        #include <linux/modversions.h>
        #endif

        /* for compatibility with future version of Linux */
        #include <linux/wrapper.h>
#endif
/* Additional */
#include <linux/fs.h>

/* irq service */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,0)
#include <linux/sched/signal.h>
#endif
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <asm/irq.h>

/* use I/O ports */
#include <asm/io.h>
#include <linux/ioport.h>

/* Local matter */
#include "_pciecat.h"

static int r3600_cpu = 0;
module_param(r3600_cpu, int, 0);
MODULE_PARM_DESC(r3600_cpu, "R3600 ECAT Master Card");

ixecat_devinfo_t *dev;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
int ixecat_minor[256] = {0};
#endif

#ifdef MODULE_LICENSE
MODULE_AUTHOR("Golden Wang <service@icpdas.com>");
MODULE_DESCRIPTION
	("ICPDAS PCI-series driver, PCI-E ECAT service module");
MODULE_LICENSE(ICPDAS_LICENSE);
#endif

// EXPORT_NO_SYMBOLS; /* do not export symbols */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
EXPORT_SYMBOL_GPL(ixecat_generic_ioctl);
EXPORT_SYMBOL_GPL(ixecat_generic_release);
EXPORT_SYMBOL_GPL(ixecat_generic_open);
#else
EXPORT_SYMBOL_NOVERS(ixecat_generic_ioctl);
EXPORT_SYMBOL_NOVERS(ixecat_generic_release);
EXPORT_SYMBOL_NOVERS(ixecat_generic_open);
#endif

#define SRAM_TIMER_DELAY (HZ/200)	//5ms

//XILINX SP6 Series DUAL Port RAM CRC(High Byte)
unsigned char CRC16Hi[] = {
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};

//XILINX SP6 Series DUAL Port RAM CRC(Low Byte)
unsigned char CRC16Lo[] = {
	0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2,
	0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04,
	0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
	0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8,
	0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
	0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
	0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6,
	0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10,
	0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
	0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
	0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE,
	0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
	0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA,
	0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
	0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
	0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0,
	0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62,
	0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
	0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE,
	0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
	0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
	0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C,
	0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76,
	0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
	0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
	0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54,
	0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
	0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
	0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A,
	0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
	0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86,
	0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
struct task_struct *find_task_by_pid(pid_t nr, struct pid_namespace *ns)
{
	return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
}
#endif

struct timer_list isr_timer;

#if 0
static void dpram_check(unsigned long arg)
{
	ixecat_devinfo_t *dp;
	int isr_reg = 0, dpram_isr_reg = 0, tmp_isr_reg = 0;

	dp = (ixecat_devinfo_t *) isr_timer.data;

	if(!r3600_cpu)
	{
		isr_reg = ioread32(dp->ioaddr[0] + _HOST_ISR) & _HOST_ISR_MASK;

		if(isr_reg & ISR_ENABLE)
		{
			//Host read DPRAM(Bar2)'s addr DPRAM_INT_FROM_CPU
			dpram_isr_reg = ioread32(dp->ioaddr[2] + DPRAM_INT_FROM_CPU) & _SRAM_MASK;
		
			if(dpram_isr_reg & 0x1)
			{
				/* User's signal condition */
				if (dp->sig.sid)
				{
					send_sig(dp->sig.sid + DPRAM_SIGNAL, dp->sig.task, 1);
				}
		
				/* Host clear INT(bar2 + DPRAM_INT_FROM_CPU) from CPU*/
				iowrite32((dpram_isr_reg & _HOST_INT_CLR_MASK), dp->ioaddr[2] + DPRAM_INT_FROM_CPU);
			}
		}
	}
	else
	{
		isr_reg = ioread32(dp->ioaddr[1] + _CPU_ISR) & _CPU_ISR_MASK;
		tmp_isr_reg = isr_reg;

		if(isr_reg & (DPRAM_INT | EMG_INT))
		{
			if(isr_reg & DPRAM_INT)
			{
				dpram_isr_reg = ioread32(dp->ioaddr[0] + DPRAM_INT_FROM_HOST) & _SRAM_MASK;

				if(dpram_isr_reg & 0x1)
				{
					/* User's signal condition */
					if (dp->sig.sid)
					{
						send_sig(dp->sig.sid + DPRAM_SIGNAL, dp->sig.task, 1);
					}
				
					/* CPU clear INT(bar0 + DPRAM_INT_FROM_HOST) from Host*/
					iowrite32(dpram_isr_reg & _CPU_INT_CLR_MASK, dp->ioaddr[0] + DPRAM_INT_FROM_HOST);
					/* set "En_RAM" = 0 */
					tmp_isr_reg = tmp_isr_reg & ~DPRAM_INT_ENABLE;
				}
			}
		
			if(isr_reg & EMG_INT)
			{
				/* User's signal condition */
				if (dp->sig.sid)
				{
					send_sig(dp->sig.sid + EMG_SIGNAL, dp->sig.task, 1);
				}
			
				/* set "En_EMG" = 0 */
				tmp_isr_reg = tmp_isr_reg & ~EMG_INT_ENABLE;	
			}

			/* clear ISR register */
			iowrite32(tmp_isr_reg, dp->ioaddr[1] + _CPU_ISR);
	
			/* set the orignal value of "En_EMG" and "En_RAM" */
			iowrite32(isr_reg, dp->ioaddr[1] + _CPU_ISR);
		}	
	}

	isr_timer.expires = jiffies + SRAM_TIMER_DELAY;
	add_timer(&isr_timer);
}

static void isr_timer_init(ixecat_devinfo_t * dp)
{
	/* init Timer */
	init_timer(&isr_timer);

	/* define timer function */
	isr_timer.function = dpram_check;

	/* define timer function's data */
	isr_timer.data = ((unsigned long) dp);

	/* define timer delay */
	isr_timer.expires = jiffies + SRAM_TIMER_DELAY;

	/* startup timer*/
	add_timer(&isr_timer);
}
#endif

int set_signal(ixecat_signal_t * sig, ixecat_devinfo_t * dp)
{
	/* set user signal for interrupt
	 *
         * Arguments:
         *   sig      user signal
         *   di       current device
         *
         * Returned:  SUCCESS or FAILURE */

	if (sig->sid) {
		/* has signal id, set */
		if (sig->pid) {
			/* user gives his process id */
			dp->sig.sid = sig->sid;
			dp->sig.pid = sig->pid;
			/* user gives his process id */
			#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
			dp->sig.task = find_task_by_pid(sig->pid,&init_pid_ns);

			#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
			dp->sig.task = find_task_by_pid_type_ns(PIDTYPE_PID,sig->pid,&init_pid_ns);
			#else
			dp->sig.task = find_task_by_pid(sig->pid);
			#endif
		} else if (sig->task) {
			/* user gives his task structure */
			dp->sig.sid = sig->sid;
			dp->sig.task = sig->task;
		} else {
			/* wrong! */
			return FAILURE;
		}
	} else {
		/* no signal id, clear */
		dp->sig.sid = 0;
		dp->sig.pid = 0;
		dp->sig.task = 0;
		dp->sig.is = 0;
		dp->sig.edge = 0;
		dp->sig.bedge = 0;
	}

	return SUCCESS;
}

#ifdef BOTTOM_HELF
void irq_handler_bh(void *data)
{
	/* bottom helf of irq handler
	 *
         * Arguments:
         *   data     data pointer as <linux/tqueue.h> defined
         *
         * Returned:  none */

	ixecat_devinfo_t *dp;

	dp = (ixecat_devinfo_t *) data;
        /* reserved */
}
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
	irqreturn_t irq_handler(int irq, void *dev_id)
#else
	#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)
	#else
	void irq_handler(int irq, void *dev_id, struct pt_regs *regs)
	#endif
#endif
{
	/* Arguments:  as <linux/sched.h> defined
	 *
	 * Returned:  none */

	ixecat_devinfo_t *dp;
	int isr_reg = 0, dpram_isr_reg = 0, tmp_isr_reg = 0;

#ifdef BOTTOM_HELF
	static struct tq_struct task = { NULL, 0, irq_handler_bh, NULL };

	task.data = dev_id;
#endif                                                  /* BOTTOM_HELF */

	dp = (ixecat_devinfo_t *) dev_id;

	if(!r3600_cpu)
	{
		isr_reg = ioread32(dp->ioaddr[0] + _HOST_ISR) & _HOST_ISR_MASK;

		if(!(isr_reg & ISR_ENABLE))
			return IRQ_NONE;

		//Host read DPRAM(Bar2)'s addr 0x1ff8(= 0x7fe * 4)
		dpram_isr_reg = ioread32(dp->ioaddr[2] + DPRAM_INT_FROM_CPU) & _SRAM_MASK;
		//printk("XXX %s ... DPRAM(0x1ff8) = 0x%x\n",__FUNCTION__, dpram_isr_reg);
		
#if 0
		if(!(dpram_isr_reg & 0x1))
			return IRQ_HANDLED;
#endif
		if(dpram_isr_reg & 0x1)
		{
			/* Host clear INT(bar2 + 0x1ff8) from CPU*/
			iowrite32((dpram_isr_reg & _HOST_INT_CLR_MASK), dp->ioaddr[2] + DPRAM_INT_FROM_CPU);
			/* User's signal condition */
			if (dp->sig.sid)
				send_sig(dp->sig.sid + DPRAM_SIGNAL, dp->sig.task, 1);
		}
	}
	else
	{
		isr_reg = ioread32(dp->ioaddr[1] + _CPU_ISR) & _CPU_ISR_MASK;
		tmp_isr_reg = isr_reg;

		if(!(isr_reg & (DPRAM_INT | EMG_INT)))
			return IRQ_NONE;

		if(isr_reg & EMG_INT)
		{
			/* User's signal condition */
			if (dp->sig.sid)
			{
				send_sig(dp->sig.sid + EMG_SIGNAL, dp->sig.task, 1);
			}
			
			tmp_isr_reg = tmp_isr_reg & ~EMG_INT_ENABLE;	//set "En_EMG" = 0
		}

		if(isr_reg & DPRAM_INT)
		{
			dpram_isr_reg = ioread32(dp->ioaddr[0] + DPRAM_INT_FROM_HOST) & _SRAM_MASK;

			if(dpram_isr_reg & 0x1)
			{
				/* User's signal condition */
				if (dp->sig.sid)
				{
					send_sig(dp->sig.sid + DPRAM_SIGNAL, dp->sig.task, 1);
				}
	
				/* CPU clear INT(bar0 + DPRAM_INT_FROM_HOST) from Host*/
				iowrite32(dpram_isr_reg & _CPU_INT_CLR_MASK, dp->ioaddr[0] + DPRAM_INT_FROM_HOST);
			}

			tmp_isr_reg = tmp_isr_reg & ~DPRAM_INT_ENABLE;	//set "En_RAM" = 0
		}
		
		/* clear ISR register */
		iowrite32(tmp_isr_reg, dp->ioaddr[1] + _CPU_ISR);
	
		/* set the orignal value of "En_EMG" and "En_RAM" */
		iowrite32(isr_reg, dp->ioaddr[1] + _CPU_ISR);
	}

#ifdef BOTTOM_HELF
	/* Scheduale bottom half to run */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
	queue_task(&task, &tq_immediate);
#else
	queue_task_irq(&task, &tq_immediate);
#endif
	mark_bh(IMMEDIATE_BH);
#endif                                                  /* BOTTOM_HELF */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	return IRQ_HANDLED;
#endif
}

int _write_eep(ixecat_eep_t * eep, void __iomem *base[])
{
	unsigned int wIndex;
	unsigned char uBufCRC_L;
	unsigned char uBufCRC_H;
	unsigned char uIndexCRC;
	unsigned char uReserve;
	unsigned int dwFlashData;
	unsigned int dwTemp;
	unsigned int dwCMDCount;
	unsigned int dwCMD;
	unsigned int dwCMDData;
	unsigned int dwTimeout;

	uReserve = 0;
	eep->uBLOCK = 0;
        eep->wCount = 256;       //128*2

	/* DPRAM_XSP6_Write */
	//Command
	//Bit 27:31 0xE=Write 0xF=Read
	//Bit 00:07 0x1=Calibration Page 0x2=Factory Calibration Page
	dwCMD = CMD_WRITE|(eep->uBLOCK+1);
	dwCMDCount = 0;
	dwCMDData = eep->wCount-1;
	dwCMDData = (dwCMDData<<24)&0xFFFFFFFF;

	//Write DATA to Flash
	for (wIndex = 0; wIndex < (eep->wCount/4); wIndex++)
	{
		dwFlashData = eep->uVal[wIndex*4+3];
		dwFlashData <<=8;
		dwFlashData += eep->uVal[wIndex*4+2];
		dwFlashData <<=8;
		dwFlashData += eep->uVal[wIndex*4+1];
		dwFlashData <<=8;
		dwFlashData += eep->uVal[wIndex*4+0];

		iowrite32(dwFlashData & _EEP_MASK, base[0] + _EEP + 0x500 + wIndex * 4);
	}

	//Create CRC
	uBufCRC_H = uBufCRC_L = 0xFF;
	for(wIndex = 0;wIndex < eep->wCount; wIndex++)
	{
		uIndexCRC = uBufCRC_L ^ eep->uVal[wIndex];
		uBufCRC_L = uBufCRC_H ^ CRC16Hi[uIndexCRC];
		uBufCRC_H = CRC16Lo[uIndexCRC];
	}

	dwTemp = uBufCRC_H;
	dwTemp <<=8;
	dwTemp +=uBufCRC_L;

	dwCMDData +=dwTemp;

        //Write to Flash
	iowrite32(dwCMDCount, base[0] + _EEP + REG_COUNT);
	iowrite32(dwCMD & _EEP_MASK, base[0] + _EEP + REG_COMMAND);
	iowrite32(dwCMDData, base[0] + _EEP + REG_DATA);
	iowrite32(STA_COMREADY, base[0] + _EEP + REG_STATUS_HOST);

        dwTimeout = 0;
	mdelay(100);

	while(1)
	{
		dwTemp = ioread32(base[0] + _EEP + +REG_STATUS_DEVICE) & _EEP_MASK;

		if ( dwTemp == STA_COMVALID)    // Command Valid
		{
			iowrite32(STA_COMBUSY, base[0] + _EEP + REG_STATUS_HOST);
			break;

		}
		else if ((dwTemp != STA_BUSY)&&(dwTemp != STA_PCIECHK))
		{	// Command Invalid

                        //Clear Command
			// Command is not ready
			iowrite32(STA_COMBUSY, base[0] + _EEP + REG_STATUS_HOST);
			// PCIe check finished
			iowrite32(STA_PCIECHK, base[0] + _EEP + REG_STATUS_DEVICE);

                        switch(dwTemp)
                        {
                                case STA_ERRCRC://Check CRC Error
                                        return FAILURE;
                                case STA_OVERADDRESS://Over EEP/Flash Address
                                        return FAILURE;
                                case STA_ERRCOM://Command Error
                                        return FAILURE;
                                case STA_ERRFLASHCTL://Control Flash Fail
                                        return FAILURE;
                                default://Unknown Error
                                        return FAILURE;
                        }
                }

                dwTimeout++;

                if(dwTimeout > 100000)
                {
			//Clear Command
			// Command is not ready
			iowrite32(STA_COMBUSY, base[0] + _EEP + REG_STATUS_HOST);
			// PCIe check finished
			iowrite32(STA_PCIECHK, base[0] + _EEP + REG_STATUS_DEVICE);

                        return FAILURE;
                }
        }

	//Clear Command
	// Command is not ready
	iowrite32(STA_COMBUSY, base[0] + _EEP + REG_STATUS_HOST);
	// PCIe check finished
	iowrite32(STA_PCIECHK, base[0] + _EEP + REG_STATUS_DEVICE);

        //Stop Pacer
	iowrite32(0, base[0] + 0x14);

	return SUCCESS;
}

int _write_reg(ixecat_reg_t * reg, void __iomem *base[])
{
	/* Write to register
	 *
	 * Arguments:
	 *   reg      pointer to a structure ixecat_reg for register
	 *   base     base address for current device
	 *
	 * Returned:  SUCCESS or FAILURE */

	switch (reg->id) {
	case IXECAT_ISR:
		if(!r3600_cpu)
		{
			/* Host clear INT(bar2 + DPRAM_INT_FROM_CPU) from CPU*/
			if((ioread32(base[2] + DPRAM_INT_FROM_CPU) & _SRAM_MASK) & 0x1)
			{
				iowrite32(((ioread32(base[2] + DPRAM_INT_FROM_CPU) & _SRAM_MASK) & _HOST_INT_CLR_MASK), base[2] + DPRAM_INT_FROM_CPU);
			}

			/* Host side enable/disable ISR */
			iowrite32(reg->value & _HOST_ISR_MASK, base[0] + _HOST_ISR);
		}
		else
		{
			/* CPU clear INT(bar0 + DPRAM_INT_FROM_HOST) from Host*/
			if((ioread32(base[0] + DPRAM_INT_FROM_HOST) & _SRAM_MASK) & 0x1)
			{
				iowrite32(((ioread32(base[0] + DPRAM_INT_FROM_HOST) & _SRAM_MASK) & _CPU_INT_CLR_MASK), base[0] + DPRAM_INT_FROM_HOST);
			}

			/* CPU side enable ISR */
			iowrite32(reg->value & _CPU_ISR_MASK, base[1] + _CPU_ISR);
		}
		break;

	case IXECAT_SRAM1:
		if(reg->sram_off < 0 || reg->sram_off > SRAM_ECAT_PAGES)
		{
			return FAILURE;
		}

		if(r3600_cpu)
		{
			iowrite32(reg->value & _SRAM_MASK, base[0] + (reg->sram_off * 4));
		}
		else
		{
			iowrite32(reg->value & _SRAM_MASK, base[2] + (reg->sram_off * 4));
		}
		break;

	case IXECAT_INT_TRIG:
		reg->value = reg->value | 0x01;

		if(r3600_cpu)
		{
			iowrite32(reg->value & _SRAM_MASK, base[0] + DPRAM_INT_FROM_CPU);
		}
		else
		{
			iowrite32(reg->value & _SRAM_MASK, base[2] + DPRAM_INT_FROM_HOST);
		}
		break;
	case IXECAT_HOST_B0:
		if(r3600_cpu)
		{
			printk("IOCTL CMD IXECAT_HOST_B9 only support for Host side.\n");
			return FAILURE;
		}
		else
		{
			iowrite32(reg->value & _32BITS_MASK, base[0] + (reg->reg_off & _HOST_B0_OFF_MASK));
		}
		break;

	default:
		return FAILURE;
	}

	return SUCCESS;
}

int _read_eep(ixecat_eep_t * eep, void __iomem * base[])
{
	/* Read EEPROM from Bar 0
	 *
	 * Arguments:
	 *   eep      pointer to structure ixecat_eep
	 *   base     base address for current device
	 *
	 * Returned:  SUCCESS or FAILURE */

	unsigned int wIndex;
	unsigned char uBufCRC_L;
	unsigned char uBufCRC_H;
	unsigned char uIndexCRC;
        unsigned char uBufData;
	unsigned int dwFlashData;
	unsigned int dwTemp;
	unsigned int dwCMDCount;
	unsigned int dwCMD;
	unsigned int dwCMDData;
	unsigned int dwTimeout;

	eep->uBLOCK = 0;
        eep->wCount = 256;       //128*2

	/* DPRAM_XSP6_Read */
	//Command
	//Bit 27:31 0xE=Write 0xF=Read
	//Bit 00:07 0x1=Calibration Page 0x2=Factory Calibration Page
	dwCMD = CMD_READ|(eep->uBLOCK+1);
	dwCMDCount = 0;
	dwCMDData = eep->wCount-1;
	dwCMDData = (dwCMDData<<24)&0xFFFFFFFF;

        //Write to Flash
	iowrite32(dwCMDCount, base[0] + _EEP + REG_COUNT);
	iowrite32(dwCMD & _EEP_MASK, base[0] + _EEP + REG_COMMAND);
	iowrite32(dwCMDData, base[0] + _EEP + REG_DATA);
	iowrite32(STA_COMREADY, base[0] + _EEP + REG_STATUS_HOST);

	dwTimeout = 0;
	//need > 15ms to read flash
	mdelay(100);

	while(1)
	{
                //Read Command Status
		dwTemp = ioread32(base[0] + _EEP + +REG_STATUS_DEVICE) & _EEP_MASK;

		if ( dwTemp == STA_COMVALID )   // Command Valid
		{
			//Command isn't ready
			iowrite32(STA_COMBUSY, base[0] + _EEP + REG_STATUS_HOST);
			break;
		}
		else if ( (dwTemp != STA_BUSY) && (dwTemp != STA_PCIECHK) )
		{// Command Invalid

                        //Clear Command
			// Command is not ready
			iowrite32(STA_COMBUSY, base[0] + _EEP + REG_STATUS_HOST);
			// PCIe check finished
			iowrite32(STA_PCIECHK, base[0] + _EEP + REG_STATUS_DEVICE);

                        switch(dwTemp)
                        {
                                case STA_ERRCRC://Check CRC Error
                                        return FAILURE;
                                case STA_OVERADDRESS://Over EEP/Flash Address
                                        return FAILURE;
                                case STA_ERRCOM://Command Error
                                        return FAILURE;
                                case STA_ERRFLASHCTL://Control Flash Fail
                                        return FAILURE;
                                default://Unknown Error
                                        return FAILURE;
                        }
                }


		dwTimeout++;
                
		if (dwTimeout > 100000)
		{
			//Clear Command
			// Command is not ready
			iowrite32(STA_COMBUSY, base[0] + _EEP + REG_STATUS_HOST);
			// PCIe check finished
			iowrite32(STA_PCIECHK, base[0] + _EEP + REG_STATUS_DEVICE);

			return FAILURE;
		}
	}

	// Read Flash Data
	uBufCRC_L= uBufCRC_H = 0xFF;
        
	for(wIndex = 0;wIndex < eep->wCount; wIndex++)
	{
		dwFlashData = ioread32(base[0] + _EEP + 0x600 + wIndex) & _EEP_MASK;

		eep->uVal[wIndex+0] = dwFlashData & 0xFF;
		eep->uVal[wIndex+1] = dwFlashData>>8 & 0xFF;
		eep->uVal[wIndex+2] = dwFlashData>>16 & 0xFF;
		eep->uVal[wIndex+3] = dwFlashData>>24 & 0xFF;

		uBufData = dwFlashData & 0xFF;
		uIndexCRC = uBufCRC_L ^ uBufData;
		uBufCRC_L = uBufCRC_H ^ CRC16Hi[uIndexCRC];
		uBufCRC_H = CRC16Lo[uIndexCRC];

		if ( ++wIndex > (eep->wCount-1) )    
			break;

		uBufData = ( dwFlashData >> 8 ) & 0xFF;

		uIndexCRC = uBufCRC_L ^ uBufData;
		uBufCRC_L = uBufCRC_H ^ CRC16Hi[uIndexCRC];
		uBufCRC_H = CRC16Lo[uIndexCRC];

		if ( ++wIndex > (eep->wCount-1) )    
			break;

		uBufData = ( dwFlashData >> 16 ) & 0xFF;

		uIndexCRC = uBufCRC_L ^ uBufData;
		uBufCRC_L = uBufCRC_H ^ CRC16Hi[uIndexCRC];
		uBufCRC_H = CRC16Lo[uIndexCRC];
                
		if ( ++wIndex > (eep->wCount-1) )    
			break;

		uBufData = ( dwFlashData >> 24 ) & 0xFF;

		uIndexCRC = uBufCRC_L ^ uBufData;
		uBufCRC_L = uBufCRC_H ^ CRC16Hi[uIndexCRC];
		uBufCRC_H = CRC16Lo[uIndexCRC];
	}

	//Clear Command
	// Command is not ready
	iowrite32(STA_COMBUSY, base[0] + _EEP + REG_STATUS_HOST);
	// PCIe check finished
	iowrite32(STA_PCIECHK, base[0] + _EEP + REG_STATUS_DEVICE);

	return SUCCESS;
}

int _read_reg(ixecat_reg_t * reg, void __iomem * base[])
{
	/* Read from register
	 *
	 * Arguments:
	 *   reg      pointer to structure ixecat_reg for register
	 *   base     base address for current device
	 *
	 * Returned:  SUCCESS or FAILURE */

	switch (reg->id) {
	case IXECAT_GPIO:
		if(!r3600_cpu)
		{
			reg->value = ioread32(base[0] + _GPIO) & _GPIO_MASK;
		}
		else
		{
			//CPU(R3600) side don't support.
			return FAILURE;
		}
		
		break;

	case IXECAT_ISR:
		if(!r3600_cpu)
		{
			reg->value = ioread32(base[0] + _HOST_ISR) & _HOST_ISR_MASK;
		}
		else
		{
			reg->value = ioread32(base[1] + _CPU_ISR) & _CPU_ISR_MASK;
		}
		break;

	case IXECAT_SRAM1:
		if(reg->sram_off < 0 || reg->sram_off > SRAM_ECAT_PAGES)
		{
			return FAILURE;
		}

		if(r3600_cpu)
		{
			reg->value = ioread32(base[0] + (reg->sram_off * 4)) & _SRAM_MASK;
		}
		else
		{
			reg->value = ioread32(base[2] + (reg->sram_off * 4)) & _SRAM_MASK;
		}

		break;

	case IXECAT_HOST_B0:
		if(r3600_cpu)
		{
			printk("IOCTL CMD 'IXECAT_HOST_B9' only support for Host side.\n");
			return FAILURE;
		}
		else
		{
			reg->value = ioread32(base[0] + (reg->reg_off & _HOST_B0_OFF_MASK)) & _32BITS_MASK;
		}
		break;

	default:
		return FAILURE;
	}

	return SUCCESS;
}

ixecat_devinfo_t *_align_minor(int minor)
{
	/* align to device by minor number */

	ixecat_devinfo_t *dp;
	for (dp = dev; dp && dp->no != minor; dp = dp->next_f);
	return dp;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
long ixecat_generic_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param)
#else
int ixecat_generic_ioctl(struct inode *inode, struct file *file, unsigned int ioctl_num, unsigned long ioctl_param)
#endif
{
	/* (export)
	 *
	 * This function is called by ixecat.o whenever a process tries
	 * to do and IO control on IXECAT device file
	 *
	 * Arguments: read <linux/fs.h> for (*ioctl) of struct file_operations
	 *
	 * Returned:  SUCCESS or FAILED */

	ixecat_devinfo_t *dp;
	ixecat_reg_t reg;
	ixecat_signal_t sig_data;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
        int minor;
        if(file->private_data)
        {
                minor = *((int *)file->private_data);
        }
        else
        {
                return -EINVAL;
        }

        dp = _align_minor(minor);
#else
        dp = _align_minor(MINOR(inode->i_rdev));
#endif

	if (!dp || !dp->open) return -EINVAL;

	switch (ioctl_num) {
	case IXECAT_GET_INFO:
		ixecat_copy_devinfo((ixecat_devinfo_t *) ioctl_param, dp);
		break;

	case IXECAT_READ_REG:
		if (copy_from_user(&reg, (void __user *)ioctl_param, sizeof(reg)))
			return -EFAULT;

		if (_read_reg(&reg, dp->ioaddr))
			return FAILURE;

		if (copy_to_user((void __user *)ioctl_param, &reg, sizeof(reg)))
			return -EFAULT;
		break;

	case IXECAT_WRITE_REG:
		if (copy_from_user(&reg, (void __user *)ioctl_param, sizeof(reg)))
			return -EFAULT;
		if (_write_reg(&reg, dp->ioaddr))
			return FAILURE;
		break;

	case IXECAT_SET_SIG:
		if (copy_from_user(&sig_data, (void __user *)ioctl_param, sizeof(sig_data)))
			return -EFAULT;
		if (set_signal(&sig_data, dp))
			return FAILURE;
                break;

#if 0
        case IXECAT_IRQ_ENABLE:
		if(!r3600_cpu)
		{
			//Host(x86 PC) side don't support.
			return FAILURE;
		}
		else
		{
	                _enable_irq(dp->ioaddr);	
		}
                break;

        case IXECAT_IRQ_DISABLE:
		if(!r3600_cpu)
		{
			//Host(x86 PC) side don't support.
			return FAILURE;
		}
		else
		{
			_disable_irq(dp->ioaddr);
		}
                break;
#endif
	case IXECAT_READ_EEP:
		if(!r3600_cpu)
		{
			//only support on PC Host Side(Windows/Linux)
			if (_read_eep((ixecat_eep_t *) ioctl_param, dp->ioaddr))
				return FAILURE;
		}
		else
		{
			//CPU(R3600) side don't support.
			return FAILURE;
		}

		break;

	case IXECAT_WRITE_EEP:
		if(!r3600_cpu)
		{
			//only support on PC Host Side(Windows/Linux)
			if (_write_eep((ixecat_eep_t *) ioctl_param, dp->ioaddr))
				return FAILURE;
		}
		else
		{
			//CPU(R3600) side don't support.
			return FAILURE;
		}

		break;

	default:
		return -EINVAL;
	}
	return SUCCESS;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
int ixecat_generic_release(struct inode *inode, struct file *file)
#else
void ixecat_generic_release(struct inode *inode, struct file *file)
#endif
{
	/* (export)
	 *
	 * This function is called by ixecat.o whenever a process attempts to
	 * closes the device file. It doesn't have a return value in kernel
	 * version 2.0.x because it can't fail (you must always be able to
	 * close a device).  In version 2.2.x it is allowed to fail.
	 *
	 * Arguments: read <linux/fs.h> for (*release) of struct file_operations
	 *
	 * Returned:  none */

	int minor;
	ixecat_devinfo_t *dp;
	ixecat_signal_t sig;

	minor = MINOR(inode->i_rdev);
	dp = _align_minor(minor);
	
	if (dp) 
	{
		if(file->private_data)
                {
                        file->private_data = NULL;
                        kfree(file->private_data);
                }

		dp->open = 0;

		if(r3600_cpu)
		{
			/* clear isr_timer */
			//del_timer_sync(&isr_timer);

			/* CPU clear interrupt from Host */
			iowrite32((ioread32(dp->ioaddr[0] + DPRAM_INT_FROM_HOST) & _CPU_INT_CLR_MASK), dp->ioaddr[0] + DPRAM_INT_FROM_HOST);

			/* CPU disable board interrupt */
			iowrite32(0, dp->ioaddr[1] + _CPU_ISR);

			/* clear signal setting */
			sig.sid = 0;
			set_signal(&sig, dp);
			free_irq(dp->irq, dp);

			/* Bar0 for DPRAM on R3600 side */
			release_mem_region(dp->base[0], dp->range[0]);
			iounmap(dp->ioaddr[0]);

			/* Bar1 for EMG signal on R3600 side */
			release_mem_region(dp->base[1], dp->range[1]);
			iounmap(dp->ioaddr[1]);
		}
		else
		{
			/* Host clear isr_timer */
			//del_timer_sync(&isr_timer);

			/* Host clear interrupt from CPU */
			iowrite32((ioread32(dp->ioaddr[2] + DPRAM_INT_FROM_CPU) & _HOST_INT_CLR_MASK), dp->ioaddr[2] + DPRAM_INT_FROM_CPU);

			/* Host disable board interrupt */
			iowrite32(0, dp->ioaddr[0] + _HOST_ISR);

			/* clear signal setting */
			sig.sid = 0;
			set_signal(&sig, dp);
			free_irq(dp->irq, dp);

			/* release Bar0/Bar2 for PC Host Side(Windows/Linux) */
			release_mem_region(dp->base[0], dp->range[0]);
			iounmap(dp->ioaddr[0]);
			release_mem_region(dp->base[2], dp->range[2]);
			iounmap(dp->ioaddr[2]);
		}

# if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
                MOD_DEC_USE_COUNT;
#endif
	}

# if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
	return 0;
# endif
}

static struct file_operations fops = {
# if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        owner:THIS_MODULE,
#endif
	open:ixecat_generic_open,
	release:ixecat_generic_release,

# if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
	unlocked_ioctl:ixecat_generic_ioctl,
#else
	ioctl:ixecat_generic_ioctl,
#endif
};

int ixecat_generic_open(struct inode *inode, struct file *file)
{
	/* (export)
	 *
	 * This function is called by ixecat.o whenever a process attempts to
	 * open the device file of PCI-1602
	 *
	 * Arguments: read <linux/fs.h> for (*open) of struct file_operations
	 *
	 * Returned:  none */

	int minor;
	ixecat_devinfo_t *dp;
	ixecat_signal_t sig;

	minor = MINOR(inode->i_rdev);
	dp = _align_minor(minor);

	if (!dp) return -EINVAL;

	++(dp->open);
	if (dp->open > 1) {
		--(dp->open);
		return -EBUSY;
		/* if still opened by someone, get out */
	}

	/* request io region */
	if(r3600_cpu)
	{
		/* Bar0 for DPRAM on CPU side */
		request_mem_region(dp->base[0], dp->range[0], R3600_MODULE_NAME);
		dp->ioaddr[0] = ioremap(dp->base[0], dp->range[0]);
		
		/* Bar1 for EMG signal on CPU side*/
		request_mem_region(dp->base[1], dp->range[1], R3600_MODULE_NAME);
		dp->ioaddr[1] = ioremap(dp->base[1], dp->range[1]);

		/* clear signal setting */
		sig.sid = 0;
		set_signal(&sig, dp);

		/* disable the interrupt of CPU side*/
		iowrite32(0, dp->ioaddr[1] + _CPU_ISR);
		
		/* CPU clear the interrupt from Host */
		udelay(10);
		iowrite32((ioread32(dp->ioaddr[0] + DPRAM_INT_FROM_HOST) & _CPU_INT_CLR_MASK), dp->ioaddr[0] + DPRAM_INT_FROM_HOST);

		/* install ISR */
		if (request_irq(dp->irq, irq_handler, SA_SHIRQ, dp->name, dp)) {
			--(dp->open);
                	return -EBUSY;
		}
	
		/* CPU side don't create isr_timer to avoid the error that INT stop */
		//isr_timer_init(dp);
	}
	else
	{
		/* Using Bar0 to control or update Spertan6 on Host Side(Windows/Linux) */
		request_mem_region(dp->base[0], dp->range[0], GENERIC_MODULE_NAME);
		dp->ioaddr[0] = ioremap(dp->base[0], dp->range[0]);

		/* Using Bar2 to R/W DPRAM on PC Host Side(Windows/Linux) */
		request_mem_region(dp->base[2], dp->range[2], GENERIC_MODULE_NAME);
		dp->ioaddr[2] = ioremap(dp->base[2], dp->range[2]);
		
		/* clear signal setting */
		sig.sid = 0;
		set_signal(&sig, dp);

		/* disable the interrupt of HOST side*/
		iowrite32(1, dp->ioaddr[0] + _HOST_ISR);
		iowrite32(0, dp->ioaddr[0] + _HOST_ISR);

		/* Host clear the interrupt from CPU */
		udelay(10);
		iowrite32((ioread32(dp->ioaddr[2] + DPRAM_INT_FROM_CPU) & _HOST_INT_CLR_MASK), dp->ioaddr[2] + DPRAM_INT_FROM_CPU);

		/* install ISR */
		if (request_irq(dp->irq, irq_handler, SA_SHIRQ, dp->name, dp)) {
			--(dp->open);
                	return -EBUSY;
		}

		/* disable the interrupt of HOST side*/
		iowrite32(1, dp->ioaddr[0] + _HOST_ISR);
		iowrite32(0, dp->ioaddr[0] + _HOST_ISR);
		/* Host side must create isr_timer to avoid the error that INT stop */
		//isr_timer_init(dp);
	}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
        MOD_INC_USE_COUNT;
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
        ixecat_minor[minor - 1] = minor;
        file->private_data = &ixecat_minor[minor - 1];
#endif

	return SUCCESS;
}

void cleanup_module()
{
	/* cleanup this module */

	ixecat_devinfo_t *dp;


	if(r3600_cpu)
		KMSG("%s ", R3600_MODULE_NAME);
	else
		
		KMSG("%s ", GENERIC_MODULE_NAME);

	for (dp = dev; dp; dp = dp->next_f) {
//		_reset_dev(dp);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
          cdev_del(dp->cdev);
#else
          dp->fops = 0;
#endif
		/* remove file operations */
		printk(".");
	}
	printk(" has been removed.\n");
}

int init_module()
{
	/* initialize this module
	 *
	 * Arguments: none
	 *
	 * Returned:
	 * integer 0 for ok, otherwise failed (module can't be load) */

	ixecat_devinfo_t *dp;

	if(r3600_cpu)
		KMSG("Linux Driver %s(ver %s, build at %s) for CPU side", R3600_MODULE_NAME, IXECAT_DRIVER_VERSION, __DATE__);
	else
		KMSG("Linux Driver %s(ver %s, build at %s) for Host side", GENERIC_MODULE_NAME, IXECAT_DRIVER_VERSION, __DATE__);

	/* align to first PCI-E ECAT in ixecat list */
	for (dev = ixecat_dev; dev && (dev->id != PCI_ECAT_R3600) && (dev->id != PCI_ECAT_GENERIC); dev = dev->next);

	if (!dev) {
		printk("fail!\n");
		return FAILURE;
	}

	/* initiate for each device (card) in family */
	for (dp = dev; dp; dp = dp->next_f) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
                dp->cdev->ops = &fops;
#else
                dp->fops = &fops;
#endif
		//_reset_dev(dp);
		printk(".");
	}

	printk(" ready.\n");

	return SUCCESS;
}
