/************************************************************************
 *  Author :	Vlad Korolev   <vkorol1@umbc.edu>
 *
 *  First Version:  Apr , 1999
 *
 *  BUGS
 *
 * ************************************************************************/

#define __KERNEL__
#define MODULE

#include <linux/module.h>
#include <linux/config.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/malloc.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/vt_kern.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>


/*************************************************
 * 	Data Types			        *
 ************************************************/

typedef struct 		/* Each dev file corresponds to Channel */
{
    int Busy;		/* Open Status 	 	*/
    int Delay;		/* Delay in usecs	*/
    int Gain;		/* Output Gain		*/

    int Faults;		/* Number of Faults	*/
    int Sent;		/* Bytes  sent		*/
} Channel;

typedef struct
{
   int  Good;
   int  Clock;		/* 1 - 32 Mhz  0 - 8Mhz */

   int  Sent;
   int  Faults;

   int  Cntl0;

   Channel Chan [8];

} IPack;

typedef struct
{
  unsigned long * plx9600;
  unsigned char * altera;
  int             irq;
  
  int   major;

  int   AutoInt;
  int   BusErr;
  int   CurPack;

  IPack  IP [4];

} Board;

/* Minor decoding */
#define ChanFromN(n)   ( n & 0xf )
#define IPFromN(n)     ( n >> 4 )

/* Hardware access */

/* Altera Registers */
#define  CNTL0   *(Dev.altera+0x500)
#define  CNTL1   *(Dev.altera+0x600)
#define  CNTL2   *(Dev.altera+0x700)

/* PLX9060 registers */
#define  ICSR    	*(Dev.plx9600+0x68/4)	/* Int. Control Stat Register*/
#define  PCUI    	*(Dev.plx9600+0x6c/4)	/* Prom Command User Init    */

/* Industry Pack registers */
#define  SETPACK(n)	(Dev.CurPack = n )
#define  IOSPACE(n)     *(Dev.altera+0x1000+(Dev.CurPack*0x1000)+n)
#define  IDSPACE(n)     *(Dev.altera+(0x1000+(Dev.CurPack*0x1000)+0x100+n))
#define  INTSPACE(n)    *(Dev.altera+0x1000+Dev.CurPack*0x1000+0x200+n)
#define  IOSPACEW(n)    *(unsigned short*)(Dev.altera+0x1000+(Dev.CurPack*0x1000)+n)



#define  MAXIP	 4
#define  MAXCHAN 8

static Board Dev =
	{
            NULL,		/* PLX9600 */
	    NULL,		/* Altera  */
	    0,			/* IRQ     */

	    0,			/* Major   */

	    0,			/* AutoInt flag  */
	    0,			/* BusErr  flag  */

	    0,			/* Current Pack for Accounting */
	    
            {
	       {0,0,0,0,0,
		 {
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},   	
	       	  {0,0,0,0,0}, 	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
		 }
	       },
	       {0,0,0,0,0,
		 {
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0}	   	
		 }
	       },
	       {0,0,0,0,0,
		 {
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0}	   	
		 }
	       },
	       {0,0,0,0,0,
		 {
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0},	   	
	       	  {0,0,0,0,0}	   	
		 }
	       }
            }
	};



static void Sleep ( int ticks ); 
static int Memset ( char * ptr , int c , int size );


/*
    Implement 
         Open / Close

         ioctl ()

	 read / write

*/




static int pci40_open ( struct inode * inode , struct file * filp )
{
   int minor = inode->i_rdev & 0xff;

   if ( !Dev.IP[IPFromN(minor)].Good ) return -ENODEV;

   /* Clear the text of mode is WR_ONLY */

   if ( Dev.IP[IPFromN(minor)].Chan[ChanFromN(minor)].Busy )
	   	return -EBUSY;

   Dev.IP[IPFromN(minor)].Chan[ChanFromN(minor)].Busy = 1;

   filp->private_data = (void*)((IPFromN  (minor) << 4   ) | 
		                (ChanFromN(minor) &  0xf ));
   

   MOD_INC_USE_COUNT;

   return 0;
} 


static int pci40_release ( struct inode * inode , struct file * filp )
{

   int minor = inode->i_rdev & 0xff;

   Dev.IP[IPFromN(minor)].Chan[ChanFromN(minor)].Busy = 0;

   MOD_DEC_USE_COUNT;

   return 0;
}

/* Write operation (Unipolar,Raw, )  */

static ssize_t pci40_write ( struct file * filp , const char * buf , 
			     size_t count, loff_t *ppos )	
{ 
	 int i,p;
	 static int j = 0x1000;
	 static short data;

	 int offSet  =  0x1000;
	     offSet +=  0x1000*(((int)filp->private_data)/0x10);
	     offSet +=  0x0002*((int)filp->private_data)&0x0f;

	if ( count < 2 ) return -EINVAL;

	get_user (  data , (int*)buf );

	   
	{
	  writew ( data & 0x1fff , Dev.altera + offSet ); 
	  writew ( 0x000f , Dev.altera + 0x4000 + 0x22 ); 

        }
         return count;
}

static struct file_operations greensp_ops = {
        NULL,           /* seek    */
        NULL,           /* read    */
        pci40_write,    /* write   */
        NULL,           /* readdir */
        NULL,           /* poll    */
        NULL,           /* ioctl   */
        NULL,           /* mmap    */
        pci40_open,	/* open    */
        NULL,           /* flush   */
        pci40_release,  /* release */
	NULL,
	NULL,
	NULL,
	NULL
};

#define NR_DEVICE 2

/***************
 * Interrupt Handler
 *
 * In the present configuration the only possible cause of interrupt
 * is BUS timeout, so the only thing we can do is to increase 
 * the fault count, and set the fault flag 
 * ******************/
static void int_handler ( int xw , void * xx , struct pt_regs *  xy )
{ 
   int i;	 
   int  x;
   unsigned long flags;

   save_flags(flags);			/* Save flags */
   cli();


   if ( CNTL2 & 0x40 ) { Dev.BusErr  = 1;  }
   if ( CNTL0 & 0x80 ) Dev.AutoInt = 1;

  /* Reset the cpu Line */
  CNTL0  = 0; ICSR = 0x0031b00 ; CNTL0 |= 0x70; 
  
  restore_flags(flags);		/* Restore CPU flags */

}

int init_module ( void ) 
{ 
  /* Look up PCI Bios */
  struct pci_dev *pcidev = NULL;
  int index = 0 ;

  printk ("<1>PCI40 + FastDAC16 Driver \n" );

  if (!pci_present())   /* No PCI bus in this machine! */
          return -ENODEV;


  if ( pcidev = pci_find_device( 0x124b, 0x0040, pcidev))
  { 
      unsigned long a;	   
      int i, GoodPacks;

        Dev.altera  = (char*) ioremap(pcidev->base_address[2], 0x14000 );
	Dev.plx9600 = (long*) ioremap(pcidev->base_address[0], 0x01000 );
	Dev.irq	    =  pcidev->irq;
	request_irq (  Dev.irq , int_handler , SA_INTERRUPT , "pci40" , NULL );

	/* Reset the board */
//	 PCUI |= 0x40000000 ; Sleep ( 1 ); PCUI &= ~ 0x40000000; Sleep ( 1 );
//	 PCUI |= 0x20000000 ; Sleep ( 1 ); PCUI &= ~ 0x20000000; Sleep ( 1 );

	/* Set interrupt handling */
        ICSR = 0x0031b00 ; CNTL0 |= 0x70; 
	
	/* Test the interrupts */

	printk ( "   Testing Interrupts....." );
	CNTL0 |= 0xF0; udelay ( 100 );
	
	if (  Dev.AutoInt == 1 ) 
		{ Dev.AutoInt = 0; printk ( "Passed\n" ); }
	else
		{ Dev.AutoInt = 0; printk ( "Failed\n" ); }

	GoodPacks = 0;
	for ( i = 0 ; i < 4 ; i++ )
	{
             int x;
	     SETPACK(i);
	     Dev.BusErr = 0;  x = IDSPACE(0); Sleep ( x );
	     if ( Dev.BusErr != 0 ) { Dev.BusErr = 0; }
	                      else  { Dev.IP[i].Good = 1; Dev.BusErr = 0;
			      	      GoodPacks++; }
	}

	if ( GoodPacks != 0 )
        {
	    printk ( "Packs with Good ID ......." );
	    for ( i = 0 ; i < 4 ; i ++ )
	        if ( Dev.IP[i].Good ) printk ( "%c" , 'A'+i );
	    printk ( "\n" );
	}

	GoodPacks = 0;
	for ( i = 0 ; i < 4 ; i++ )
	{
             int x;
	     SETPACK(i);
	     Dev.BusErr = 0;  IOSPACEW(0x20) = 0x0100;  Sleep ( x );
	     if ( Dev.BusErr != 0 ) { Dev.BusErr = 0; }
	                      else  { Dev.BusErr = 0;
			      	      Dev.IP[i].Good = 1;
			      	      GoodPacks++; }
	}
	if ( GoodPacks != 0 )
        {
	    printk ( "Packs with Good IO ......." );
	    for ( i = 0 ; i < 4 ; i ++ )
	        if ( Dev.IP[i].Good ) printk ( "%c" ,  'A'+i );
	    printk ( "\n" );
	}

  }

  printk ( "Altera %p  \n" , Dev.altera ); 

	  writew ( 0xf01 , Dev.altera + 0x4000 + 0x20 ); 
	  udelay ( 20 );


  Dev.major = register_chrdev ( 0 , "pci40" , &greensp_ops );
  return 0; 
}


void cleanup_module ( void ) 
{
   
   if ( Dev.major != 0 )
     unregister_chrdev ( Dev.major , "pci40" );

   iounmap ( Dev.plx9600 );
   iounmap ( Dev.altera  );

   free_irq ( Dev.irq  , NULL );

   
}

static int Memset ( char * ptr , int c , int size )
{ int i; for ( i = 0 ; i < size ; i++ ) ptr[i] = c; }

static void Sleep ( int ticks ) { int i; for ( i = 0 ; i < 100 ; i++ ) 
					udelay( 500 ); } 



