/*
  "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
                            yel = clock
                            green = data
                            black = ground 
  yel sometimes goes through inverter (7404) to pin 10 of parallel port;
  green goes to pin 2 of parallel port which is data 0 (D0).
  black goes to pin 25 of parallel port (one of the grounds)
  pin9 of parallel port (d7) retracts spikes when high (e.g. outb 128)
  pin8 of parallel port (d6) activates buzzer when high (e.g. outb 64)

  to get the parallel port into input mode: outb(0x30, 0x37A)
  which is hlp_base+2.
  bit 5 of base+2 is to get parallel port into bi-directional mode
                  where bidirectional mode means input mode (usually an output)
  bit 4 of base+2 is to get parallel to enable IRQ, so raising ack (pin10)
                  induces an IRQ.
  2^5+2^4 = 0x30

  i changed data from card reader from D0 (pin2) to slct (pin13),
  so now data is read as bit4 from 379 rather than bit0 for 378.
  also i changed driver so that data looks feels and smells the same
  from userspace, so that it appears as bit0 of the read data rather than bit4.

  ----------------------------------------------------------------------
  +5v can come from ps/2 keyboard or mouse
  diagram on beyondlogic.org or our mirror is wrong, so i drew diagram
  (looking into female connection on the computer itself) here:

     CK  NC
     _|o o|_
GND  o     o  +5V
DATA  o | o  NC
        ^
not sure on these: combination of voltmeter and comparision
against the wrong diagram on beyondlogic.org --steve (mann@eyetap.org)

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.

*/
#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 <asm/io.h>
#include <asm/uaccess.h>

#define DEFAULT_HLP_BASE	0x378
//default lava parallel port is ECF8 not 278
//#define DEFAULT_HLP_BASE	0xECF8
//#define DEFAULT_HLP_BASE	0x278
#define DEFAULT_HLP_IRQ		7
//#define DEFAULT_HLP_IRQ		9
//#define DEFAULT_HLP_IRQ		5

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

static struct wait_queue* hlp_irq_waitQ;

static int hlp_major = 0;
static int hlp_irq = 0;
static int hlp_base  = 0; 

MODULE_PARM(hlp_base,"i");
MODULE_PARM(hlp_irq,"i");


void hlp_handler(int irq, void *dev_id, struct pt_regs *regs) {

  //printk("interrupt service routine woke up\n");

  hlp_got_data = 1;
  if(hlp_irq_waitQ) {
    wake_up(&hlp_irq_waitQ);
  }
}

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 retval;
  unsigned char *kbuf,*ptr;

  retval = 0;

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

  interruptible_sleep_on(&hlp_irq_waitQ);
  do {
    *ptr++ = ((inb(hlp_base+1)) & 0x10) >> 4;
    retval++;
    hlp_got_data = 0; 
    interruptible_sleep_on_timeout(&hlp_irq_waitQ,HZ/10);
  }while(hlp_got_data && (retval<count));

  copy_to_user(buf, kbuf, retval);
  kfree(kbuf);
  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 = {

  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");
    if(!hlp_irq)
      hlp_irq = DEFAULT_HLP_IRQ;
    if(!hlp_base)
      hlp_base = DEFAULT_HLP_BASE;

    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); 
    /* send data byte to io port */
    /* outb(portdata, LP_BASE); */
//  outb(0x30, hlp_base + 2); /* puts parallel port into bidirectional (by that
    outb(0x30, 0x37A); /* puts parallel port into bidirectional (by that
//    outb(0x30, 0x27A); /* puts parallel port into bidirectional (by that
                                 //we mean input) with IRQ enabled */
//    outb(0x30, 0xECFA); /* puts parallel port into bidirectional (by that
//  the above is for 378

    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_irq, hlp_handler,  SA_INTERRUPT, hlp_name, NULL);
    if (result) {
      printk(KERN_INFO "%s: failed to install irq %i handler\n",hlp_name,hlp_irq);
      unregister_chrdev(hlp_major, hlp_name);
      release_region(hlp_base,8);
      return result;
    }

    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_irq, NULL);
}


