#include <linux/module.h>
#include <asm/io.h>
#include <asm/msr.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/major.h>
#include <linux/fs.h>

/* Addresses of the parallel port */
#define	PData   	0x378
#define	PStatus  	0x379
#define	PControl    	0x37a

//MEDIABOX protocol constants
#define PSTOP		0x80
#define PCLOCK		0x40
#define PDATA  		0x20
#define BUF_LEN 	80
//MEDIABOX vars
static char msg[BUF_LEN];	/* The msg the device will give when asked */
int  num		= 0;
unsigned int counter    = 0;

/* IRQ number of the parallel port */
#define IRQ 7

int verbose = 0;

/* the device driver descriptor */
static struct {
  struct semaphore   sem;
  wait_queue_head_t  wait;
  int		     data;
  unsigned long long tsc;
} foo_dev;


/* forward declarations */
static ssize_t lp_sensor_read(struct file *, char *, size_t, loff_t *);
static int lp_sensor_open(struct inode *, struct file *);
static int lp_sensor_close(struct inode *, struct file *);
static void lp_sensor_interrupt(int, void *, struct pt_regs *);

/* the methods of the character device */
static struct file_operations lp_sensor_fops = {
	owner:   THIS_MODULE,
	read:    lp_sensor_read,
	open:    lp_sensor_open,
	release: lp_sensor_close,
};

/* initializer of the character device */
int lp_sensor_init(void)
{   
    int cc; 

    cc = register_chrdev(41, "lp_sensor_card", 
                         &lp_sensor_fops); 
    if (cc < 0) {
        printk(KERN_INFO "Cannot register the lp_sensor device\n");
        return 1;
    }

    cc = request_irq(IRQ, lp_sensor_interrupt, SA_INTERRUPT,
                   "lp_sensor", NULL);                   
    if (cc < 0) {
        printk(KERN_INFO "Cannot get the irq line %d for lp_sensor device\n",
                IRQ);
	unregister_chrdev(41, "lp_sensor_card");
        return 1;
    }
            
    init_MUTEX(&foo_dev.sem);
    init_waitqueue_head(&foo_dev.wait);

    return 0;
}

/* de-initializer of the character device */
void lp_sensor_exit(void)
{
    int cc;

    free_irq(IRQ, NULL);

    cc = unregister_chrdev(41, "lp_sensor_card");
    if (cc < 0) 
        printk(KERN_INFO "Cannot unregister lp_sensor device\n");
}

/* the open method of the character device */
int lp_sensor_open(struct inode *inode, struct file *file)
{
    outb_p(0x00, PData);
    outb_p(0x00, PStatus);
    outb_p(0x24, PControl);
    outb_p(0x34, PControl);

    return 0;
}


/* the close method of the character device */
int lp_sensor_close(struct inode *inode, struct file *file)
{
    outb_p(0x0b, PControl);
    outb_p(0x00, PData);
    outb_p(0x00, PStatus);

    return 0;
}


/* the read method of the character device */
ssize_t lp_sensor_read(struct file *file, char *buf, 
                        size_t count, loff_t *ppos)
{
    wait_queue_t wait;
    unsigned long long tsc;
    
    int len = 1;
    int stop;
    int clock;
    int data;
//    int bytes_read = 0;
    

    if (down_interruptible(&foo_dev.sem))
        return -ERESTARTSYS;

    init_waitqueue_entry(&wait, current);
    add_wait_queue(&foo_dev.wait, &wait);


    rdtscll(tsc);

    /* It's crucial to declare the process TASK_INTERRUPTIBLE  !!!before!!!
       issuing the outb instructions and not after. Otherwise, if the device
       issues an interrupt right after the third outb and before 
       set_current_state(TASK_INTERRUPTIBLE), the process resumes execution
       after the interrupt handler terminates, becomes TASK_INTERRUPTIBLE and
       stops forever inside schedule because the event it waits for already
       occurred.
     */
       
    set_current_state(TASK_INTERRUPTIBLE);      
  
    outb_p(0x34,PControl);
    outb_p(0x3c,PControl);
    outb_p(0x34,PControl);

    schedule();

    set_current_state(TASK_RUNNING);
    remove_wait_queue(&foo_dev.wait, &wait);

//get bits from hardware device
    stop  = ((foo_dev.data & PSTOP)  != 0);
    clock = ((foo_dev.data & PCLOCK) != 0);
    data  = ((foo_dev.data & PDATA)  != 0);

    if (verbose)
	printk(KERN_INFO "lp_sensor bits(%d): stop:%d clock:%d data:%d\n",foo_dev.data,stop,clock,data);

//calc num, 5 byte sequance
    if( (counter < 4) && (stop == 1) )
    {
        printk(KERN_INFO "lp_sensor error: broaken pack at (%d)\n",num);
	counter = 0;
	num = 0;                
	goto CNT;
    }

    if ( (counter == 4) && (stop == 0) )
    {
        printk(KERN_INFO "lp_sensor error: broaken end pack at (%d)\n",num);
       	counter = 0;
	num = 0;
	goto CNT;
    }

    num = num + ( data << (4-counter));
    counter++;
    if(counter == 5)
    {
	if(num == 0)
        {
              	if (verbose)
			printk(KERN_INFO "lp_sensor info: RESET\n");
        }
        else if(num>30)
        {
		if (verbose)
                	printk(KERN_INFO "lp_sensor error: invalid sensor ID(%d)\n",num);
        }
        else
        {
              	sprintf(msg,"%d\n\0",num);
		for(len=0; len<strlen(msg); len++)
		{
    			if (put_user(msg[len], buf++))
       				return -EFAULT;
		}		
		if (verbose)
			printk(KERN_INFO "lp_sensor: %s\n",msg);
		msg[0] = '\0';
        }
        counter = 0;
	num = 0;
    }
    else
    {
	if (put_user('\0', buf))
        	 return -EFAULT;
    }
CNT:

    up(&foo_dev.sem);

    if (verbose)
        printk(KERN_INFO "Delay of interrupt: %llu bus cycles\n",
           foo_dev.tsc-tsc);

    return len;	
}


/* the interrupt handler */
static void lp_sensor_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
  unsigned long long t;
  unsigned flags;

  /* read the Time Stamp Counter */  
  rdtscll(t);
  foo_dev.tsc = t;
  
  /* access the device's hardware */
  save_flags(flags);
  __cli();
  outb_p(0x34,PControl);
  outb_p(0x30,PControl);
  foo_dev.data = inb_p(PStatus); 
  outb_p(0x34,PControl);
  restore_flags(flags);
  
  /* wake up the process sleeping in the device wait queue */
  wake_up_interruptible(&foo_dev.wait);
}

EXPORT_NO_SYMBOLS;
/* newer linux versions
module_init(lp_sensor_init);
module_exit(lp_sensor_exit);
*/
int init_module(void) 
{
   printk(KERN_INFO "Loading the lp_sensor driver\n");
   return lp_sensor_init();
}

void cleanup_module(void)
{
   printk(KERN_INFO "Unloading the lp_sensor device\n");
   lp_sensor_exit();
}


MODULE_AUTHOR("D.P. Bovet, M. Cesati, and V. Garofalo, modification by Antony Raijekov a.k.a Zeos");
MODULE_DESCRIPTION("LKHC driver for lp_sensor character device");
MODULE_LICENSE("GPL");
MODULE_PARM(verbose, "i");
