/*
 
  2001-oct
  modified once again to change it back to ISA ... retaining 2.4 compatibility
  
  "Humanistic Licence Provider (HLP)"

  Device Driver for parallel port card reader interface,
  based on ISA card reader device driver of Taneem Ahmed (taneem@eyetap.org),
  which was based on ISA card reader device driver of Ben Konrath (ben@eyetap),
  based on Prof. Mann's (mann@eyetap.org) ECE 385 course project,
  http://eyetap.org/ece385/lab9, fall of 2000.
  quick hack with everything in the top half; really should be split apart
  for expandability (e.g. later data parse functions into bottom half so
  something like cat /dev/paraseat0 | letters_a-z | welcome_to_seatsale
  becomes meaningful...

  typical connections with phone wire: 
  Convention of Prof. Mann: red = +5v
                            green = clock
                            blue = data
                            black = ground 
  ----------------------------------------------------------------------

ben@eyetap.org (google search), the following parallel ports are supported:
   - SIIG Cyber Parallel PCI (both versions)
   - SIIG Cyber Parallel Dual PCI (both versions)
   - SIIG Cyber 2P1S PCI
   - SIIG Cyber I/O PCI (both versions)
   - SIIG Cyber 2S1P PCI (both versions)
   - Lava Parallel PCI
   - Lava Dual Parallel PCI
   - Lava 2SP PCI
   - LavaPort Plus
this allows use on pci only system, e.g. with pci parallel port cards.

A good reference for interrupts is the chapter on interrupts in the second
edition of the Oreilly book "writing device drivers".
--yacine@eyetap.org 

*/
#include <linux/module.h>
#include <linux/malloc.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/delay.h>

#include <asm/io.h>
#include <asm/uaccess.h>

#define DEFAULT_HLP_BASE	0x240
//default lava parallel port is ECF8 not 278
//#define DEFAULT_HLP_BASE	0xECF8
//#define DEFAULT_HLP_BASE	0x278
#define DEFAULT_HLP_IRQ1		11
#define DEFAULT_HLP_IRQ2                7

static short hlp_in_use;
static short hlp_got_data;
static char hlp_name[10] = "isaseat";

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
  static wait_queue_head_t hlp_irq_waitQ1;
  static wait_queue_head_t hlp_irq_waitQ2;
#else
  static struct wait_queue* hlp_irq_waitQ1;
  static struct wait_queue* hlp_irq_waitQ2;
#endif

const char * id_keys[] = 
{
  "9876325842"
};

unsigned char alpha[] = 
{
  ' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', 
  '*', '+', ',', '-', '.', '/', '0', '1', '2', '3',
  '4', '5', '6', '7', '8', '9', ':', ';', '<', '=',
  '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
  'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
  'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[',
  '\\',']', '^', '_' 
};
unsigned char bcd[] = 
{
  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  ':', ';', '<', '=', '>', '?'
};

#define TRACK1_PROMPT "Track 1: "
#define TRACK2_PROMPT "Track 2: "

static int hlp_major = 0;
static int hlp_irq1 = 0;
static int hlp_irq2 = 0;
static int hlp_base  = 0; 

int startTrack1, endTrack1;
int startTrack2, endTrack2;
int startRead;
static int bitpos1 = 0;
static int bitpos2 = 0;
static int parity1 = 0;
static int parity2 = 0;
static unsigned char bits1 = 0;
static unsigned char bits2 = 0;

#define BUFSIZE 1000
static unsigned char bitbuf1[BUFSIZE];
static unsigned char bitbuf2[BUFSIZE];
static unsigned char charbuf1[BUFSIZE];
static unsigned char charbuf2[BUFSIZE];
int charcount1;
int charcount2;
volatile int head1;
volatile int head2;
volatile int tail1;
volatile int tail2;
void processbit1(unsigned long);
void processbit2(unsigned long);
int irq_received1, irq_received2;
DECLARE_TASKLET(reader_tasklet1, processbit1, 0);
DECLARE_TASKLET(reader_tasklet2, processbit2, 0);

MODULE_PARM(hlp_base,"i");
MODULE_PARM(hlp_irq1,"i");
MODULE_PARM(hlp_irq2,"i");

/*
** This is the actual interrupt handler.  There is a problem
** in having the interrupt handler calling wake_up() to wake
** up whoever is waiting on the waitqueue as there may be no one
** missed.  A far better implementation will be to register a
** tasklet (bottom half) and call tasklet_schedule() to run
** the bottom half to read in (and optionally process) the
** data from the port.
**
** A simple bottom half tasklet may just assemble the characters
** (either 5-bit track 2, or 7-bit track 1) from the card reader
** and place them into a buffer ready for a user space application
** to read.
**
** A more complex bottom half tasklet may perform more processing
** on the complete card data.  I.E. when the tasklet recognizes the
** END_SENTINAL character from the card, it can compare the card
** number with an internal list of valid card numbers and perform
** some processing such as turn on an LED for a second (to simulate
** unlocking a door).
*/

/*
** Name
**	hlp_handler1  --  Interrupt handler for track 1
**
** Synopsis
**	DEFAULT_HLP_IRQ1
**
** Description
**	This is the interrupt handler for the interrupts received
**	from track one of the card reader.  This function reads the
**	data bit from the card reader and saves it in it bit buffer
**	'bitbuf1'.
**
*/
void hlp_handler1(int irq, void *dev_id, struct pt_regs *regs) {
//  printk("interrupt service routine woke up\n");

//  hlp_got_data = 1;
  /*
  ** Read and save the bit in the circular bit buffer
  ** then increment the head to point to the next available
  ** location.
  */
  if (head1 != (tail1+BUFSIZE-1)%BUFSIZE)
  {
    bitbuf1[head1] = ((~(inb(hlp_base))) & 0x01);
    head1 = (head1 + 1) % BUFSIZE;
  }
  /*
  ** Don't process the data bits here, simply schedule a
  ** 'bottom handler' to read the bits from the circular
  ** bit buffer and perform the necessary processing
  */
  tasklet_schedule(&reader_tasklet1);
}

void hlp_handler2(int irq, void *dev_id, struct pt_regs *regs) {
//  printk("interrupt service routine 2 woke up\n");

//  hlp_got_data = 1;
  if (head2 != (tail2+BUFSIZE-1)%BUFSIZE)
  {
    bitbuf2[head2] = ((~(inb(hlp_base))) & 0x02)>>1;
    head2 = (head2 + 1) % BUFSIZE;
  }
  tasklet_schedule(&reader_tasklet2);
}

void dumpBin(int b)
{
  int i=0;
  for (i=0; i<=6; i++)
  {
    printk("%1d", (b&0x40)>>6);
    b = b<<1;
  }
}

void turnOnLed()
{
  outb(0, DEFAULT_HLP_BASE);
  mdelay(100);
  outb(0xFF, DEFAULT_HLP_BASE);
}

/*
** Name
**	processbit1
**
** Synopsis
**
** Description
**	This is the tasklet that processes bits from track 1 of the
**	card reader.
*/
void processbit1(unsigned long data)
{
  while (tail1 != head1)
  {
    bits1 = (bits1 >> 1) | ((bitbuf1[tail1] & 1) << 6);
    bitpos1++;
    parity1 += bitbuf1[1] & 1;
    if (!startTrack1)
    {
//dumpBin(bits1);
      if (bits1 == 69)
      {
//printk("Starting to read track1!\n");
        irq_received1 = 1;
        charbuf1[charcount1++] = alpha[bits1&0x3F];
        startTrack1 = 1;
        bitpos1 = 0;
        bits1 = 0;
        parity1 = 0;
      }
    }
    else
    {
//printk("%1d", bitbuf1[tail1]);
      if (bitpos1 == 7)
      {
//printk(" ");
/*
        if (!(parity1 & 1))
        {
          strncpy(charbuf1 + charcount1, "<error>", 7);
          charcount1 += 7;
          startTrack1 = 0;
          endTrack1 = 1;
        }
*/
        charbuf1[charcount1++] = alpha[bits1&0x3F];
        if (bits1 == 31)
        {
//printk("Ending track1!\n");
          startTrack1 = 0;
          endTrack1 = 1;
        }
        bits1 = 0;
        bitpos1 = 0;
        parity1 = 0;
      }
    }

    tail1 = (tail1 + 1) % BUFSIZE;
    if (endTrack1)
    {
      bits1 = 0;
      bitpos1 = 0;
      wake_up(&hlp_irq_waitQ1);
      break;
    }
  }

  if (!startRead)
    charcount1 = 0;
}

void processbit2(unsigned long data)
{
  int i;
  int match = 0;

  while (tail2 != head2)
  {
//printk("%1d", bitbuf2[tail2]);
    bits2 = (bits2 >> 1) | ((bitbuf2[tail2] & 1) << 4);
    bitpos2++;
    parity2 += bitbuf2[tail2] & 1;
    if (!startTrack2)
    {
//dumpBin(bits2);
      if (bits2 == 11)
      {
        irq_received2 = 1;
//printk("Starting to read track2!\n");
        charbuf2[charcount2++] = bcd[bits2&0xF];
        startTrack2 = 1;
        bitpos2 = 0;
        bits2 = 0;
        parity2 = 0;
      }
    }
    else
    {
//printk("%1d", bitbuf2[tail2]);
      if (bitpos2 == 5)
      {
//printk(" ");
/*
        if (!(parity2 & 1))
        {
          strncpy(charbuf2 + charcount2, "<error>", 7);
          charcount2 += 7;
          startTrack2 = 0;
          endTrack2 = 1;
        }
*/
        charbuf2[charcount2++] = bcd[bits2&0xF];
        if (bits2 == 31)
        {
//printk("Ending track2!\n");
          startTrack2 = 0;
          endTrack2 = 1;
        }
        bits2 = 0;
        bitpos2 = 0;
        parity2 = 0;
      }
    }

    tail2 = (tail2 + 1) % BUFSIZE;
    if (endTrack2)
    {
      bits2 = 0;
      bitpos2 = 0;
      wake_up(&hlp_irq_waitQ2);
      break;
    }
  }

  for (i=0; i<sizeof(id_keys)/sizeof(const char *); i++)
  {
    charbuf2[charcount2] = '\0';
    if (strstr(charbuf2, id_keys[i]) != NULL)
    {
      match = 1;
      break;
    }
  }

  if (match)
    turnOnLed();

  if (!startRead)
  {
//printk("zeroing charcount2\n");
    charcount2 = 0;
  }
}

static ssize_t hlp_read (
    struct file *filp,
    char *buf,        /* The buffer to fill with data */
    size_t count,     /* The length of the buffer */
    loff_t *offset){  /* Our offset in the file */    

  int i;
  int retval;
  unsigned char *kbuf,*ptr;

  retval = 0;
  startRead = 1;
  bitpos1 = bitpos2 = 0;
  parity1 = parity2 = 0;
  bits1 = bits2 = 0;

/*
  kbuf = (unsigned char *) kmalloc(count, GFP_KERNEL);
  ptr = kbuf;
*/

  interruptible_sleep_on_timeout(&hlp_irq_waitQ1, HZ*2);
  interruptible_sleep_on_timeout(&hlp_irq_waitQ2, HZ*2);

//printk("\n");
// JJ
if (irq_received1 || irq_received2)
{
  kbuf = (unsigned char *) kmalloc(charcount1 + charcount2, GFP_KERNEL);
  strncpy(kbuf, TRACK1_PROMPT, strlen(TRACK1_PROMPT));
  retval = strlen(TRACK1_PROMPT);
  strncpy(kbuf + retval, charbuf1, charcount1);
  retval += charcount1;
  kbuf[retval++] = '\n';
  strncpy(kbuf + retval, TRACK2_PROMPT, strlen(TRACK2_PROMPT));
  retval += strlen(TRACK2_PROMPT);
  strncpy(kbuf + retval, charbuf2, charcount2);
  retval += charcount2;
  kbuf[retval++] = '\n';
  copy_to_user(buf, kbuf, retval);

  charcount1 = charcount2 = 0;
  kfree(kbuf);
  startTrack1 = startTrack2 = 0;
  endTrack1 = endTrack2 = 0;
  bitpos1 = bitpos2 = 0;
  parity1 = parity2 = 0;
  bits1 = bits2 = 0;
  irq_received1 = irq_received2 = 0;
}
else
{
  retval = 0;
}

  //startRead = 0;
  return retval;
}


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

    if(hlp_in_use)
      return -EBUSY;
    hlp_in_use = 1;

    MOD_INC_USE_COUNT;
    return 0;
}                                     


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

   hlp_in_use = 0;
   MOD_DEC_USE_COUNT;
   return 0;
 }                                                       

struct file_operations hlp_fops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
  NULL,
#endif
  NULL,
  hlp_read,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  hlp_open,
  NULL,
  hlp_release
};

int init_module(void) {

    int result;

    printk(KERN_INFO "Starting doing stuff\n");

// JJ
    startRead = 1;
    head1 = tail1 = 0;
    head2 = tail2 = 0;
    charcount1 = charcount2 = 0;
    startTrack1 = startTrack2 = 0;
    endTrack1 = endTrack2 = 0;

    if(!hlp_irq1)
      hlp_irq1 = DEFAULT_HLP_IRQ1;
    if(!hlp_irq2)
      hlp_irq2 = DEFAULT_HLP_IRQ2;
    if(!hlp_base)
      hlp_base = DEFAULT_HLP_BASE;

//    result = check_region(hlp_base, 8);
    // we want to keep 378 free and use only 379 and 37A so that spikes have 378
    result = check_region(hlp_base, 8);

    if (result) {
        printk(KERN_INFO "%s: can't get I/O address 0x%x\n", hlp_name,hlp_base);
        return result;
    }
    request_region(hlp_base, 8, hlp_name); 

    // Turn off all leds.
    outb(0xFF, DEFAULT_HLP_BASE);

    result = register_chrdev(hlp_major, hlp_name, &hlp_fops);
    if (result < 0){
      printk(KERN_INFO "%s: can't get major number\n",hlp_name);
      release_region(hlp_base,8);
      return result;
    }
     
   if (!hlp_major)
     hlp_major = result;

    /*
     * request led on irq number and install handler
     */
    result = request_irq(hlp_irq1, hlp_handler1,  SA_INTERRUPT, hlp_name, NULL);
    if (result) {
      printk(KERN_INFO "%s: failed to install irq %i handler\n",hlp_name,hlp_irq1);
      unregister_chrdev(hlp_major, hlp_name);
      release_region(hlp_base,1);
      return result;
    }

    result = request_irq(hlp_irq2, hlp_handler2,  SA_INTERRUPT, hlp_name, NULL);
    if (result) {
      printk(KERN_INFO "%s: failed to install irq %i handler\n",hlp_name,hlp_irq2);
      unregister_chrdev(hlp_major, hlp_name);
      release_region(hlp_base,1);
      return result;
    }

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0)
    init_waitqueue_head(&hlp_irq_waitQ1);
    init_waitqueue_head(&hlp_irq_waitQ2);
#endif


    printk(KERN_INFO "Done doing stuff\n");
    return 0;

}

void cleanup_module(void) {

    unregister_chrdev(hlp_major, hlp_name);
    release_region(hlp_base,8);

    free_irq(hlp_irq1, NULL);
    free_irq(hlp_irq2, NULL);
}

