/*
 *	seriali.c
 *   seriali driver with read interrupt enabled	
 */

#include <linux/module.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/serial_core.h>
#include <linux/serial_reg.h>
#include <linux/smp_lock.h>
#include <linux/device.h>
#include <linux/serial.h> /* for serial_state and serial_icounter_struct */
#include <linux/delay.h>
#include <linux/fs.h>

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

#include "seriali.h"

static short msglen;
static unsigned char kbuf[BUF_LEN];
static unsigned char c;
int rate=1843200/16/BAUDRATE;


/*  	interrupt handler	*/

static irqreturn_t
irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
  printk(KERN_ALERT "I just enter the interrupt routine\n");
  inb(BASEPORT+UART_IIR);
  c = inb(BASEPORT+UART_RX); /* read one byte from the UART */
    
    flag = 1;
    wake_up_interruptible(&queue_read);

  return(IRQ_HANDLED);
}

/***** Open/read/write/close functions follow *************************/

/*	read routine 	*/
static ssize_t 
read_seriali(struct file *zfile, char *buf, size_t length, 
	     loff_t *offset){
	
  int n,len;

    printk(KERN_ALERT "I am going to sleep\n");
    wait_event_interruptible(queue_read,flag!=0);
    flag = 0;

    local_irq_disable();

    put_user(kbuf[n],buf+n);  /* transfer one byte to the user buffer */ 


    local_irq_enable();

  return(len);
}

/*	write routine 	*/
static ssize_t 
write_seriali(struct file *zfile, const char *buf, size_t length,
	      loff_t *offset) {
  int n, len;
  char c;


    get_user(c, buf+n);  /* transfer one byte from the user buffer  */

    
    outb(c,BASEPORT+UART_TX);  /* write one byte to the UART   */


  return(len);
}


/*	open routine	*/
static int 
open_seriali(struct inode *zinode, struct file *zfile) 
{
  int result;

  msglen=0;
  result = request_irq(IRQ, irq_handler, SA_INTERRUPT, "seriali", NULL); 

  printk(KERN_ALERT "I am in the open routine\n");
  switch (result) {
  case -EBUSY:
    printk(KERN_ALERT "servo: IRQ %d busy\n",3);
    return -EBUSY;
  case -EINVAL:
    printk(KERN_ALERT "servo: Bad irq number or handler\n");
    return -EINVAL;
  default:
    break;
  }

  /* Disable interrupts while we setup the port */

  local_irq_disable();
	
  /* set the baud rate		*/
  outb(UART_LCR_DLAB, BASEPORT+UART_LCR); 
  outb(rate & 0xff ,BASEPORT+UART_DLL);
  outb(rate >> 8 , BASEPORT+UART_DLM);

  /*	8N1		*/
  outb(UART_LCR_WLEN8, BASEPORT+UART_LCR);

  /*      enable modem control lines	*/
  outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2
       , BASEPORT+UART_MCR);

  /*	enable interrupt	*/
  outb(UART_IER_RDI, BASEPORT+UART_IER);

  /*	clear receiving register	*/
  inb(BASEPORT+UART_RX);

  /*	re-enable interrupts	*/
  local_irq_enable();
  return 0;
}

static int 
release_seriali(struct inode *zinode, struct file *zfile) {
  local_irq_disable();
  free_irq(IRQ,NULL); 
  local_irq_enable();
  return(0);
}

/* ---------------------  Kernel Module Initializations ------------- */

static struct class *seriali_class;

static struct file_operations seriali_fops = {
  .read=  read_seriali,
  .write=  write_seriali,
  .open= open_seriali,
  .release= release_seriali,
};

static int __init start_module(void)
{
  int n,err;
  
  n = register_chrdev(MODULE_MAJOR, "seriali", &seriali_fops);
  if(n < 0) {
    printk(KERN_ALERT "unable to get major for seriali device\n");
    return(-EIO);
  }
  
  seriali_class = class_create(THIS_MODULE, "seriali");
  
  class_device_create( seriali_class, 
                       NULL, 
                       MKDEV(MODULE_MAJOR,0), NULL, "mydev" );
  
  if ( IS_ERR(seriali_class) ) {
    err = PTR_ERR(seriali_class);
    class_destroy(seriali_class);
    return(err);
  }
  
  return(0);
}

static
void __exit stop_module(void)
{
  int err;
  class_device_destroy(seriali_class, MKDEV(MODULE_MAJOR, 0));
  class_destroy(seriali_class);
  err = unregister_chrdev(MODULE_MAJOR,"seriali");
  printk("<1>Module seriali removed.\n");
}

module_init(start_module);
module_exit(stop_module);

MODULE_LICENSE("GPL");
MODULE_ALIAS_CHARDEV_MAJOR(MODULE_MAJOR);

