
#include <linux/init.h>     /*for init and cleanup*/
#include <linux/module.h>
#include <linux/config.h>


#include <linux/kernel.h>   /* printk() */
#include <linux/sched.h>
#include <linux/fs.h>       /* everything... */
#include <linux/errno.h>    /* error codes */
#include <linux/types.h>    /* size_t */
#include <linux/timer.h>

#ifdef UCSIMM
#   include<asm/MC68EZ328.h>
#endif


#ifdef PCPARPORT
#   include <linux/ioport.h>   /*for pc parport control*/
#endif

#include "sysdep.h"



#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif

#ifdef LINUX_20
#  error "This module will not run with linux-2.0" 
#endif 


#define TRUE 1
#define FALSE 0
#define KPQUEUESIZE 16 

#ifdef PCPARPORT
#   define KP_PORT_READ         (inb(base+1))
#   define KP_PORT_WRITE(A)     (outb((A),base))
#endif

#ifdef UCSIMM
#   define KP_PORT_READ         (PDDATA)
#   define KP_PORT_WRITE(A)     ((PDDATA) = (A))
#endif


typedef int KP_QUEUE_TYPE;


static int keypad_major = 0;
MODULE_PARM(keypad_major, "i");   /* let the user choose the major number */
MODULE_AUTHOR("John Jarvis - jarv@verizon.net");


/*
 * ssize_t = signed size type
 * loff_t = long offset type (usually 64 bits wide)
 */

static int addkpQueue (KP_QUEUE_TYPE val);
static int deletekpQueue (KP_QUEUE_TYPE *pval);

int keypad_open(struct inode *inode, struct file *filp);
int keypad_release(struct inode *inode, struct file *filp);
size_t keypad_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);


#ifdef PCPARPORT
static unsigned long base = 0x378;
MODULE_PARM(base, "l");  /*override the parport base address*/
#endif



/*circular buffer*/
KP_QUEUE_TYPE kpQueue[KPQUEUESIZE];
unsigned int kp_queue_head = 0;
unsigned int kp_queue_tail = 0;




/* WAIT PROCESS QUEUE FOR BLOCKING I/O */
DECLARE_WAIT_QUEUE_HEAD(kp_queue);
/**/


static int kpkeystate = 0;

static struct timer_list poll_timer;


struct file_operations keypad_fops = {
    read:       keypad_read,
    open:       keypad_open,
    release:    keypad_release,
    owner:      THIS_MODULE,
};

int keypad_open(struct inode *inode, struct file *filp)
{
    printk("keypad_open()\n");
    MOD_INC_USE_COUNT;
    return(0);
}

int keypad_release(struct inode *inode, struct file *filp)
{
    MOD_DEC_USE_COUNT;
    return 0;
}

size_t keypad_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
    int popd_key,cnt;
    int retval = count;
    //char key;
    char userbuf[KPQUEUESIZE] = {0}; 

    if (kp_queue_head == kp_queue_tail)    //if our circular buffer is empty we block until a keypress
           interruptible_sleep_on(&kp_queue);  
   

/* 
 * This loop pops keys off our circular FIFO buffer 
 *      and adds them to a local buffer which later gets copied to userspace.  
 * This will not grab more than the user requests (count).  
 * If the user requests more than what is in our FIFO buffer than it will break out
 *      early and return what we have.
 *
 */
    for (cnt = 0; cnt < count; cnt++) { 
        if (deletekpQueue(&popd_key) == -1) break;
        sprintf(userbuf, "%s%c",userbuf,popd_key); 
    }
#ifdef DEBUG
    printk("before copy_to_user\n");
#endif
    copy_to_user(buf, userbuf, cnt);
#ifdef DEBUG
    printk("after copy_to_user\n");
#endif

    retval = cnt;


    return retval; 
}


/*
 * Below we have our simple keypad matrix scan
 * When it detects a keypress it pushs the ascii code of the key on our
 * circular buffer.
 *
 */


void keypad_poll(void)
{

    del_timer(&poll_timer);

    KP_PORT_WRITE(0xE);
    wmb(); 


        if (!(KP_PORT_READ & 0x10))  {
            if (!(kpkeystate & 0x200)) addkpQueue(57); //printk("keypress 9 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x200; 
        }

        else kpkeystate = kpkeystate & 0xDFF; 
        
        
        if (!(KP_PORT_READ  & 0x20))  {
            if (!(kpkeystate & 0x100)) addkpQueue(56); //printk("keypress 8 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x100; 
        }

        else kpkeystate = kpkeystate & 0xEFF; 
        
        
        
        if (!(KP_PORT_READ & 0x40 ))  {
            if (!(kpkeystate & 0x080)) addkpQueue(55); //printk("keypress 7 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x080; 
        }

        else kpkeystate = kpkeystate & 0xF7F; 
        
    rmb();

    
    KP_PORT_WRITE(0xD); 
    wmb(); 

    
         if (!(KP_PORT_READ & 0x10))  {
            if (!(kpkeystate & 0x040)) addkpQueue(54); //printk("keypress 6 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x040; 
        }
        
        else kpkeystate = kpkeystate & 0xFBF; 


        if (!(KP_PORT_READ & 0x20))  {
            if (!(kpkeystate & 0x020)) addkpQueue(53); //printk("keypress 5 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x020; 
        }

        else kpkeystate = kpkeystate & 0xFDF;


        if (!(KP_PORT_READ & 0x40))  {
            if (!(kpkeystate & 0x010)) addkpQueue(52); //printk("keypress 4 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x010;
        }

        else kpkeystate = kpkeystate & 0xFEF; 


    rmb();

    
    KP_PORT_WRITE(0xB);
    wmb(); 
    
    
        if (!(KP_PORT_READ & 0x10))  {
            if (!(kpkeystate & 0x008)) addkpQueue(51); //printk("keypress 3 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x008; 
        }

        else kpkeystate = kpkeystate & 0xFF7; 
        
        
        if (!(KP_PORT_READ  & 0x20))  {
            if (!(kpkeystate & 0x004)) addkpQueue(50); //printk("keypress 2 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x004; 
        }

        else kpkeystate = kpkeystate & 0xFFB; 
        
        
        
        if (!(KP_PORT_READ & 0x40 ))  {
            if (!(kpkeystate & 0x002)) addkpQueue(49); //printk("keypress 1 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x002; 
        }

        else kpkeystate = kpkeystate & 0xFFD; 
        
    rmb();



    KP_PORT_WRITE(0x7);
    wmb(); 
 
        if (!(KP_PORT_READ & 0x10))  {
            if (!(kpkeystate & 0x400)) addkpQueue(42); //printk("keypress * - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x400; 
        }

        else kpkeystate = kpkeystate & 0xBFF; 
        
        
        if (!(KP_PORT_READ  & 0x20))  {
            if (!(kpkeystate & 0x001)) addkpQueue(48); //printk("keypress 0 - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x001; 
        }

        else kpkeystate = kpkeystate & 0xFFE; 
        
        
        
        if (!(KP_PORT_READ & 0x40 ))  {
            if (!(kpkeystate & 0x800)) addkpQueue(35); //printk("keypress # - %ld\n",jiffies);
            kpkeystate = kpkeystate | 0x800; 
        }

        else kpkeystate = kpkeystate & 0x7FF; 
        
    rmb();

    
    
    
    poll_timer.expires = jiffies + 1;
    add_timer(&poll_timer);


}

static int addkpQueue(KP_QUEUE_TYPE val)
{
    int i;

    i = (kp_queue_tail + 1) % KPQUEUESIZE;
    if (i != kp_queue_head)  {
        kpQueue[i] = val;
        kp_queue_tail = i;
        wake_up_interruptible(&kp_queue);  // a button was pushed! wake up!
        return 0;
    }
    return -1;

}

static int deletekpQueue(KP_QUEUE_TYPE *pval)
{

    if (kp_queue_tail != kp_queue_head)  {
        kp_queue_head = (kp_queue_head + 1) % KPQUEUESIZE;
        *pval = kpQueue[kp_queue_head];
        return 0;
    }
    return -1;
}



static int my_init(void) 
{
    int result;
    result = register_chrdev(keypad_major, "keypad", &keypad_fops);

    if (result < 0)  {
        printk(KERN_WARNING "keypad: unable to get major %d\n", keypad_major);
        return result;
    }

    if (keypad_major == 0)
        keypad_major = result;


#ifdef UCSIMM
      PDSEL = 0x7f;  //select PD0-PD6 for I/O
      PDDIR = 0x0f;  //select PD0-PD3 for output
#endif




#ifdef PCPARPORT 
    /* attempt to allocate the I/O region for the PC parport  */
    result = check_region(base,1);
    if (result) {
        printk(KERN_INFO "keypad: can't get I/O address 0x%ld\n",base);
        return result;
    }
    request_region(base,1,"keypad");

    /* init the port to zero */
    outb(0,base);
    wmb();  /*write memory barrier*/
#endif


    /* set up kernel timer */
    poll_timer.expires = jiffies + 1;
    poll_timer.function = keypad_poll;
    add_timer(&poll_timer);
    
    printk("<1>keypad Module Loaded\n");
    return 0;
}


static void my_cleanup(void) 
{

#ifdef PCPARPORT
    /* release I/O region for parport */ 
    release_region(base,1);
#endif

    /* remove our kernel timer */
    del_timer(&poll_timer);
    unregister_chrdev(keypad_major, "keypad");
    printk("<1>keypad Module Unloaded\n");

}

/*<1> is the priority of the message*/


module_init(my_init);
module_exit(my_cleanup);


    
