
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/parport.h>
#include <linux/errno.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>

#define MODULE_NAME "tassiv_camera"
#define DEVICE_NAME "camera"

MODULE_AUTHOR( "Robert Creager <robert_creager@techie.com>" );
MODULE_DESCRIPTION( "TASS IV parallel port camera driver" );

#ifdef TASSIV_CAMERA_DEBUG
#   define PDEBUG(fmt, args...) printk( KERN_DEBUG MODULE_NAME ": " fmt, ## args)
#else
#   define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif

/*
 * function prototypes
 */
int tassiv_camera_init( void );
void tassiv_camera_exit( void );

void tassiv_camera_attach( struct parport * );
void tassiv_camera_detach( struct parport * );

ssize_t tassiv_camera_read( struct file *,
                            char *,
                            size_t,
                            loff_t * );
int tassiv_camera_open( struct inode *,
                        struct file * );
int tassiv_camera_release( struct inode *,
                           struct file * );

int tassiv_camera_pf( void * );
void tassiv_camera_kf( void * );
void tassiv_camera_irq_func( int,
                             void *,
                             struct pt_regs * );

/*
 * module entry and exit points
 */
module_init( tassiv_camera_init );
module_exit( tassiv_camera_exit );

/*
 * Local definitions
 */
typedef enum
   {
      CAMERA_TEST = 0,
      CAMERA_ISA = 1,
      CAMERA_ECP = 2,
   }
CAMERA_MINOR_ENUM;

/*
 * module global vars
 */
static unsigned long tassiv_camera_isa_address = 0x301;
static unsigned long tassiv_camera_isa_size = 1;
static struct parport_driver tassiv_camera_port_driver
   = { MODULE_NAME, NULL, NULL, NULL };

static devfs_handle_t tassiv_camera_devfs = NULL;

struct pardevice *tassiv_camera_device = NULL;
static void *tassiv_camera_driver_data = NULL;

struct file_operations tassiv_camera_fops = {
   owner:THIS_MODULE,
   read:tassiv_camera_read,
   open:tassiv_camera_open,
   release:tassiv_camera_release
};

static int number_pixels = 8000000;

MODULE_PARM( number_pixels, "i" );
MODULE_PARM_DESC( number_pixels, "Number of pixels in ONE (1) camera" );

static int major = 0;

MODULE_PARM( major, "i" );
MODULE_PARM_DESC( major, "Major number of device driver - auto by default" );

int
tassiv_camera_init( void )
{
   int result;

   EXPORT_NO_SYMBOLS;

   tassiv_camera_port_driver.attach = tassiv_camera_attach;
   tassiv_camera_port_driver.detach = tassiv_camera_detach;

   result = devfs_register_chrdev( major, DEVICE_NAME, &tassiv_camera_fops );
   if ( result < 0 )
      {
         printk( KERN_ERR "%s: can't get major number\n", MODULE_NAME );
         return result;
      }
   if ( major == 0 )
      {
         major = result;
      }
   tassiv_camera_devfs
      = devfs_register( NULL, DEVICE_NAME, DEVFS_FL_AUTO_DEVNUM,
                        0, 0, S_IFCHR | S_IRUGO, &tassiv_camera_fops, NULL );

   result = parport_register_driver( &tassiv_camera_port_driver );
   if ( result < 0 )
      {
         printk( KERN_ERR "%s: failed to register parallel port driver\n",
                 MODULE_NAME );
         return ( -ENODEV );
      }

   printk( KERN_INFO "%s: module installed successfully\n", MODULE_NAME );
   return ( 0 );
}

void
tassiv_camera_exit( void )
{
   parport_unregister_driver( &tassiv_camera_port_driver );
   devfs_unregister_chrdev( major, DEVICE_NAME );
   devfs_unregister( tassiv_camera_devfs );
   PDEBUG( "module removed successfully\n" );
}

ssize_t
tassiv_camera_read( struct file *file,
                    char *data,
                    size_t count,
                    loff_t * offset )
{
   int minor;
   int to_copy = count;
   int copied = 0;

   minor = MINOR( file->f_dentry->d_inode->i_rdev );
   PDEBUG( "read from %d major %d minor %d size\n",
           MAJOR( file->f_dentry->d_inode->i_rdev ), minor, count );

   switch ( minor )
      {
      case CAMERA_TEST:
         {
            char *local_data;
            int data_size = 1024;
            unsigned short int low_value = 0x0000;
            unsigned short int high_value = 0xFFFF;
            int x;

            local_data = kmalloc( data_size, GFP_KERNEL );
            do
               {
                  for ( x = 0; x < data_size / 4;
                        ++x, ++low_value, --high_value )
                     {
                        local_data[4 * x + 0] = ( low_value & 0xFF00 ) >> 8;
                        local_data[4 * x + 1] = low_value & 0x00FF;
                        local_data[4 * x + 2] = ( high_value & 0xFF00 ) >> 8;
                        local_data[4 * x + 3] = high_value & 0x00FF;
                     }

                  if ( to_copy >= data_size )
                     {
                        if ( 0
                             != copy_to_user( &( data[copied] ), local_data,
                                              data_size ) )
                           {
                              kfree( local_data );
                              return -EFAULT;
                           }
                        to_copy -= data_size;
                        copied += data_size;
                     }
                  schedule(  );
               }
            while ( to_copy > data_size );

            if ( to_copy > 0 )
               {
                  for ( x = 0; x < to_copy / 4;
                        ++x, ++low_value, --high_value )
                     {
                        local_data[4 * x + 0] = ( low_value & 0xFF00 ) >> 8;
                        local_data[4 * x + 1] = low_value & 0x00FF;
                        local_data[4 * x + 2] = ( high_value & 0xFF00 ) >> 8;
                        local_data[4 * x + 3] = high_value & 0x00FF;
                     }
                  if ( 0 !=
                       copy_to_user( &( data[copied] ), local_data,
                                     to_copy ) )
                     {
                        kfree( local_data );
                        return -EFAULT;
                     }
                  copied += to_copy;
                  to_copy -= to_copy;
               }
            kfree( local_data );
         }
         break;

      case CAMERA_ISA:
         {
            char *local_data;
            int data_size = 1024;

            local_data = kmalloc( data_size, GFP_KERNEL );

            while ( to_copy >= data_size )
               {
                  insb( 0x300, local_data, data_size );
                  if ( 0 !=
                       copy_to_user( &( data[copied] ), local_data,
                                     data_size ) )
                     {
                        kfree( local_data );
                        return -EFAULT;
                     }
                  copied += data_size;
                  to_copy -= data_size;
                  schedule(  );
               }

            if ( to_copy > 0 )
               {
                  insb( 0x300, local_data, to_copy );
                  if ( 0 !=
                       copy_to_user( &( data[copied] ), local_data,
                                     to_copy ) )
                     {
                        kfree( local_data );
                        return -EFAULT;
                     }
                  copied += to_copy;
                  to_copy -= to_copy;
               }
         }
         break;

      case 2:                  // Parallel port
         {
            char *local_data;
            int data_size = 1024;
            struct parport *port;

            port = ( struct parport * ) tassiv_camera_driver_data;

            local_data = kmalloc( data_size, GFP_KERNEL );

            while ( to_copy >= data_size )
               {
                  if ( data_size !=
                       parport_read( port, local_data, data_size ) )
                     {
                        kfree( local_data );
                        return -ENODATA;
                     }
                  if ( 0 !=
                       copy_to_user( &( data[copied] ), local_data,
                                     data_size ) )
                     {
                        kfree( local_data );
                        return -EFAULT;
                     }
                  to_copy -= data_size;
                  copied += data_size;
               }

            if ( to_copy > 0 )
               {
                  if ( to_copy != parport_read( port, local_data, to_copy ) )
                     {
                        kfree( local_data );
                        return -ENODATA;
                     }
                  if ( 0 !=
                       copy_to_user( &( data[copied] ), local_data,
                                     to_copy ) )
                     {
                        kfree( local_data );
                        return -EFAULT;
                     }
                  to_copy -= data_size;
                  copied += data_size;
               }

            kfree( local_data );
         }
         break;

      default:
         return -ENOSYS;
         break;
      }

   return copied;
}

int
tassiv_camera_open( struct inode *inode,
                    struct file *filp )
{
   int result;
   int minor;
   struct parport *port;

   minor = MINOR( inode->i_rdev );

   switch ( minor )
      {
      case CAMERA_TEST:
         break;
      case CAMERA_ISA:
         {
            if ( check_region( tassiv_camera_isa_address,
                               tassiv_camera_isa_size ) )
               {
                  printk( KERN_ERR "%s: memory 0x%lX already in use\n",
                          MODULE_NAME, tassiv_camera_isa_address );
                  return -EBUSY;
               }
            else
               {
                  request_region( tassiv_camera_isa_address,
                                  tassiv_camera_isa_size, DEVICE_NAME );
               }
         }
         break;
      case CAMERA_ECP:
         result = parport_claim( tassiv_camera_device );
         if ( 0 != result )
            {
               printk( KERN_ERR "%s: cannot claim the parallel port\n",
                       MODULE_NAME );
               return -EBUSY;
            }
         //
         // Ok, we cannot negotiate the mode here, as the camera FIFO card is
         // not that intelligent.  So we'll fake it out...
         // May have to frob the port or something else...
//         result = parport_negotiate( (struct parport*) tassiv_camera_driver_data,
//                                                                 IEEE1284_MODE_ECP );
         result = 0;
         port = ( struct parport * ) tassiv_camera_driver_data;
         port->ieee1284.mode = IEEE1284_MODE_ECP;
         port->ieee1284.phase = IEEE1284_PH_FWD_IDLE;

         if ( -1 == result )
            {
               printk( KERN_ERR
                       "%s: peripheral is not IEEE 1284 compliant or not installed\n",
                       MODULE_NAME );
               return -ESERVERFAULT;
            }
         else if ( 1 == result )
            {
               printk( KERN_ERR "%s: peripheral rejected ECP negotiations\n",
                       MODULE_NAME );
               return -ESERVERFAULT;
            }
         break;
      default:
         break;
      }
   PDEBUG( "executed a open successfully\n" );
   return 0;
}

/*
 * As far as I can tell, the release shouldn't be called unless the open
 * succeeded, so we can always release the parport
 */
int
tassiv_camera_release( struct inode *inode,
                       struct file *filp )
{
   int minor;

   minor = MINOR( inode->i_rdev );

   switch ( minor )
      {
      case CAMERA_TEST:
         break;
      case CAMERA_ISA:
         release_region( tassiv_camera_isa_address, tassiv_camera_isa_size );
         break;
      case CAMERA_ECP:
         parport_release( tassiv_camera_device );
         break;
      default:
         break;
      }
   PDEBUG( "executed a release successfully\n" );
   return 0;
}

/********************************************************************
 *
 * parport functions here
 *
 *******************************************************************/

void
tassiv_camera_attach( struct parport *port_desc )
{
   tassiv_camera_driver_data = port_desc;
   tassiv_camera_device
      = parport_register_device( port_desc,
                                 DEVICE_NAME,
                                 tassiv_camera_pf,
                                 tassiv_camera_kf,
                                 tassiv_camera_irq_func,
                                 0, tassiv_camera_driver_data );

   if ( NULL == tassiv_camera_device )
      {
         printk( KERN_ERR "%s: cannot register driver with paraport\n",
                 MODULE_NAME );
         tassiv_camera_driver_data = NULL;
         return;
      }

   printk( KERN_INFO "%s: %s found and registered\n",
           MODULE_NAME, port_desc->name );
}

void
tassiv_camera_detach( struct parport *port_desc )
{
   if ( NULL != tassiv_camera_device )
      {
         parport_unregister_device( tassiv_camera_device );
      }

   printk( KERN_INFO "%s: %s went away\n", MODULE_NAME, port_desc->name );
}

/*
 * We'll never share the port, so for the preemptive request, always say no
 */
int
tassiv_camera_pf( void *my_data )
{
   return -1;
}

void
tassiv_camera_kf( void *my_data )
{
   PDEBUG( "got the kick\n" );
}

void
tassiv_camera_irq_func( int one,
                        void *my_data,
                        struct pt_regs *two )
{
   PDEBUG( "got the interrupt\n" );
}
