/* VERSION 0.1 - $Id: cortex.c,v 1.10 1995/08/30 03:50:28 cullen Exp $*/

/*
 *   Copyright (C) 1995 Cullen Jennings 
 *
 *   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 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define __KERNEL__
#define MODULE

#include <linux/module.h>
#include <linux/version.h>

#include <linux/types.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/fcntl.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/config.h>
#include <linux/malloc.h>

#include <asm/io.h>

char    kernel_version [] = UTS_RELEASE;

int cortex_major = 0;
int cortex_debug = 0;
int cortex_port = 0x230;
int cortex_mem  = 0xD0000;

#define IFDEBUG( L ) if ( (L) <= cortex_debug ) 
#define inp  inb_p
#define outp outb_p

static unsigned char port0val;
static unsigned char port1val;

typedef struct 
{
   int type;
   int offset;
} GrabInfo;


/*******************************************************************/
/*                    Hardware Manipulation Stuff                  */

#define BIT_DISP_RAM  0x0010
#define BIT_VERT_BLK  0x0008
#define BIT_ACQ_REQ   0x0008
#define BIT_ACQ_ACK   0x0020
#define BIT_SIGNATURE 0x003F

#define BIT_LUT_LOAD  0x0400
#define BIT_DISP_BLK  0x0800
#define BIT_AB_SEL    0x1000
#define BIT_RAM_ENB   0x2000
#define BIT_PORT1     0x3F00

#define BIT_ALL       0xFFFF

/* helper functions */

static int  getBit( int i)
{
   int ret;
   ret=inp(cortex_port);
   ret &= i;
   return ret;
}

static void setBit( int i )
{
   int lowBit = i & 0xFF;
   int hiBit = (i>>8) & 0xFF;
   
   if ( lowBit )
   {
      port0val |= lowBit;
      outp( port0val , cortex_port+0);
      IFDEBUG(999) printk("set port0=%d\n",port0val);
   }

   if ( hiBit )
   {
      port1val |= hiBit;
      outp( port1val , cortex_port+1);
      IFDEBUG(999) printk("set port1=%d\n",port1val);
   }
}

static void clrBit( int i )
{
   int lowBit = i & 0xFF;
   int hiBit = (i>>8) & 0xFF;
   
   if ( lowBit )
   {
      port0val &= (~lowBit);
      outp( port0val , cortex_port+0);
      IFDEBUG(999) printk("clr port0=%d\n",port0val);
   }

   if ( hiBit )
   {
      port1val &= (~hiBit);
      outp( port1val , cortex_port+1);
      IFDEBUG(999) printk("clr port1=%d\n",port1val);
   }
}

static void waitvb(void)
{
   long maxTime=10; /* time in jiffies aprox (1/30 second) */
   long startTime;
   
   startTime = jiffies;
   
   while ( getBit( BIT_VERT_BLK ) )
   {
      if ( jiffies >= startTime+maxTime ) 
      {
	 printk("cortex timed out waiting for vertical blank fall\n");
	 return;
      }
   }

   while ( !getBit( BIT_VERT_BLK ) )
   {
      if ( jiffies >= startTime+maxTime ) 
      {
	 printk("cortex timed out waiting for vertical blank rise\n");
	 return;
      }
   }
   
   IFDEBUG(2) printk("did waitvb\n");
}

/* main functions */

static int haveVideo(void)
{
   int ret=0;
   
   IFDEBUG(2) printk("cortex haveVideo\n");

   waitvb();
   setBit( BIT_ACQ_REQ );
   if ( getBit( BIT_ACQ_ACK )  ) ret=1;
   clrBit( BIT_ACQ_REQ );

   IFDEBUG(1) printk("cortex haveVideo return %d\n",ret);
     
   IFDEBUG(666) return 1;

   return ret;
}

static void memOn( void )
{
   IFDEBUG(3) printk("cortex memOn\n");
   setBit( BIT_RAM_ENB );
}

static void grab(void)
{
   int start;
   int end;
   
   IFDEBUG(2) printk("cortex grab\n");
   
   start = jiffies;
   
   clrBit( BIT_LUT_LOAD | BIT_DISP_RAM );
   setBit( BIT_ACQ_REQ );
   waitvb();
   waitvb();
   clrBit( BIT_ACQ_REQ );

   end = jiffies;
   
   IFDEBUG(1) printk("cortex grab took %d jiffies\n",end-start);
}
 
static void memOff(void)
{
   IFDEBUG(2) printk("cortex memOff\n");
   clrBit( BIT_RAM_ENB );
}
  
static int haveBord(void)
{
   int ret=0;
   
   IFDEBUG(2) printk("cortex haveBord\n");

   IFDEBUG(3) printk("port0 = 0x%x\n",inp(cortex_port+0));
   IFDEBUG(3) printk("port1 = 0x%x\n",inp(cortex_port+1));
   IFDEBUG(3) printk("port2 = 0x%x\n",inp(cortex_port+2));
   IFDEBUG(3) printk("port3 = 0x%x\n",inp(cortex_port+3));
   
   if ( getBit(BIT_SIGNATURE) < BIT_SIGNATURE ) ret=1;
   
   IFDEBUG(1) printk("cortex haveBord return = %d \n",ret);

   IFDEBUG(666) return 1;

   return ret;
}


static void clearPorts(void)
{
   IFDEBUG(3) printk("cortex clearPorts\n");
   clrBit( BIT_ALL );

   if ( (cortex_mem == 0xB0000) || (cortex_mem == 0xE0000) )
   {
      setBit( BIT_AB_SEL );
   }
}

static int verifyMem(void)
{
   int i;
   int chg;
   int ret=0;
   int size=16;
   char buf[16];
   char *data;

   data = (char*)cortex_mem;
   
   IFDEBUG(2) printk("cortex verifyMem\n");

   clearPorts();
   memOn();
   
   for( i=0; i<size; i++ )
   {
      buf[i]=data[i];
   }

   for( i=0; i<size; i++ )
   {
      if ( buf[i] != data[i] ) 
      {
	 IFDEBUG(1) printk("cortex verifyMem fail - memory changed\n");
 	 goto verify_done;
      }
   }

   memOff();

   chg=0;
   for( i=0; i<size; i++ )
   {
      if ( buf[i] != data[i] ) chg=1;
   }
   if ( !chg )
   {
      IFDEBUG(1) printk("cortex verifyMem fail - memory did not change\n");
      goto verify_done;
   }

   ret=1;
   
 verify_done:
   memOff();
      
   IFDEBUG(1) printk("cortex verifyMem return %d\n",ret);

   IFDEBUG(666) return 1;
   return ret;
}

static void loadLut(void)
{
   int i;
   
   IFDEBUG(2) printk("cortex loadLut\n");
   waitvb();
   setBit( BIT_DISP_BLK|BIT_LUT_LOAD );
   
   for ( i=0; i<256; i++)
   {
      outp( i , cortex_port+2);
      outp( i , cortex_port+3);
   }
   
   clrBit( BIT_LUT_LOAD );
   clrBit( BIT_DISP_BLK );
}

/*******************************************************************/
/*                    Character Device STUFF                       */


static int lseek (struct inode *inode, struct file *filp,
		      off_t ofs, int origin)
{
   GrabInfo* ginfo;
   
   IFDEBUG(1) printk("\ncortex lseek:\n");
   IFDEBUG(1) printk("cortex ofs=%ld origin=%d\n",ofs,origin);
 
   ginfo = (GrabInfo*)filp->private_data;
 
   switch (origin)
   {
   case 0:
      /* SEEK_SET */
      ginfo->offset = ofs;
      break;
   case 1:
      /* SEEK_CUR */
      ginfo->offset += ofs;
      break;
   case 2:
      /* SEEK_END */
   default:   
      return  -EINVAL;
   }
 
   if ( (ginfo->offset<0) || ( ginfo->offset > 512*486))
   {
      ginfo->offset = 0;
      return -ESPIPE;
   }

   IFDEBUG(1) printk("cortex origin set to %d\n",ginfo->offset);
   
   return ginfo->offset;
}

static int open (struct inode *inode, struct file *filp)
{
   GrabInfo* ginfo;

   IFDEBUG(1) printk("\ncortex open:\n");
   IFDEBUG(1) printk("cortex open - dev minor=%d\n", MINOR(inode->i_rdev) );
   IFDEBUG(1) printk("cortex open - dev major=%d\n", MAJOR(inode->i_rdev) );

   filp->private_data=kmalloc(sizeof(GrabInfo),GFP_KERNEL);
   if (!(filp->private_data)) return -ENOMEM;

   ginfo = (GrabInfo*)filp->private_data;
 
   ginfo->type =  MINOR(inode->i_rdev);
   ginfo->offset = 0;
   
   IFDEBUG(1000)
   {
      printk("cortex - emulating card - open\n");
      return 0;
   }

   if ( !haveVideo() )
   {
      printk("cortex device open: no video input\n");
      return -ENODEV;
   }
   
   IFDEBUG(2) printk("about to try grab\n");
   grab();
   
   IFDEBUG(1) printk("cortex open OK\n" );
   return 0;
}

static  void close (struct inode *inode, struct file *filp) 
{
   IFDEBUG(1) printk("\ncortex close:\n");

   if ( filp->private_data ) kfree( filp->private_data );
   filp->private_data = NULL;
}

static void doRead( int reqStart, int reqSize , char* buf , int* count)
{
   int page;
   int loc;
   
   *count = 0;
   if ( reqStart >= 486*512 ) return;
   if ( reqStart+reqSize >= 486*512 ) reqSize = 486*512-reqStart;
   
   page = (reqStart>>16)&0x03;
   loc = reqStart & 0xFFFF;
   if ( reqSize+loc >= 0x10000 ) reqSize =  0x10000 - loc;
  
   IFDEBUG(2) printk("in doRead page=%0x\n",page);
   IFDEBUG(2) printk("in doRead loc =%0x\n",loc);
   IFDEBUG(2) printk("in doRead size=%0x\n",reqSize);

   setBit( page );
   
   IFDEBUG(3) printk("about to try mem on \n");
   memOn();

   *count = reqSize;
   IFDEBUG(2) printk("about to try mem copy of %d bytes\n",reqSize);
   memcpy_tofs(buf,(void*)(cortex_mem+loc),reqSize);

   IFDEBUG(3) printk("about to try mem off\n");
   memOff();

   clrBit( 0x03 );
}

static  int read (struct inode *inode, struct file *filp,
		     char *buf, int count)
{
   GrabInfo* ginfo=NULL;
   int ret=0;
   int did;
   char* bufp;
   
   IFDEBUG(1) printk("\ncortex read: request %d bytes\n",count);
   
   ginfo = (GrabInfo*)filp->private_data;
   IFDEBUG(5) printk(" type = %d \n", ginfo->type); 
   IFDEBUG(5) printk(" offset = %d \n", ginfo->offset); 
   
   if ( ginfo->type == 1 )
   {
      /* write out a pgm header */
      char data[] = "P5\n512 486\n255\n";
      char* bufp;
      int len = 15;
      
      len -= ginfo->offset;
      bufp = data;
      bufp += ginfo->offset;
      if ( count < len ) len=count;
      
      memcpy_tofs(buf,bufp,len);
      
      ginfo->offset += len;
      
      if (ginfo->offset>=15)
      {
	 /* done header */
	 ginfo->type = 0;
	 ginfo->offset = 0;
      }
      
      return len;
   }
 
   did = 1;
   ret = 0;
   bufp = buf;
   
   while ( (count>0) && (did>0) )
   {
      doRead( ginfo->offset , count , bufp , &did );
   
      ret += did;
      ginfo->offset += did;
      count -= did;
      bufp += did;
   }

   return ret;
}

static  int write (struct inode *inode, struct file *filp,
		      char *ostr, int count)
{
   IFDEBUG(1) printk("\ncortex write: try write %d bytes\n",count);
   return 0;
}

static  int ioctl(struct inode *inode, struct file * filp,
		     unsigned int cmd, unsigned long arg)
{
   IFDEBUG(1) printk("\ncortex ioctl: try %d\n",cmd);

   return -ENOIOCTLCMD;
}

/*****************************************************************/
/*             MODULE STUFF                                      */

static struct file_operations fops = {
   lseek,
   read,
   write,
   NULL,			/* readdir */  
   NULL,			/* select */   /* this lacks */
   ioctl,
   NULL,			/* mmap */
   open,
   close
};

int init_module (void) 
{
   int ret;

   IFDEBUG( 3 ) printk("cortex: init module \n");

   printk("cortex: init port=0x%x mem=0x%x debug=%d\n",
	  cortex_port,cortex_mem,cortex_debug);
   
   ret = register_chrdev (cortex_major, "cortex", &fops);
   if (ret <0)
   {
      printk("cortex init_module: register device failed ret=%d\n",ret);
      goto error_module;
   }
   if (cortex_major==0) cortex_major=ret;
   
   if ( (ret=check_region(cortex_port,8)) )
   {
      printk("cortex init_module: port %x in use\n",cortex_port);
      ret = 0; 
      goto error_device;
   }

   request_region(cortex_port,8,"cortex");

   /* check address sanity */
   if (   (cortex_mem != 0xA0000) && (cortex_mem != 0xB0000) 
       && (cortex_mem != 0xD0000) && (cortex_mem != 0xE0000) )
   {
      printk("cortex init_module: cortex_mem address %x not valid",cortex_mem);
      printk("must be one of 0x[ABDE]0000\n");
      ret=0;
      goto error_ports;
   }
   
   IFDEBUG(1000)
   {
      printk("cortex - emulating card - driver loaded\n");
      return 0;
   }

   /* module set up - now set up the card */
   if ( !haveBord() )
   {
      printk("cortex init_module: can't find frame grabber card\n");
      ret=0;
      goto error_ports;
   }

   clearPorts();

   if ( !verifyMem() )
   {
      printk("cortex init_module: can't find card memory");
      ret=0;
      goto error_ports;
   }

   loadLut();
   
   /* all read to go */
   printk("cortex device driver loaded\n");
   return 0;

 error_ports:
   release_region(cortex_port,8);

 error_device:
   unregister_chrdev(cortex_major,"cortex");

 error_module:
   printk("cortex device driver load failed\n");

   return (ret)?ret:-EINVAL;
}


void cleanup_module (void) 
{
   IFDEBUG(1) printk("cortex: cleanup module\n");

   release_region(cortex_port,8);
   
   unregister_chrdev(cortex_major,"cortex");
}





