/* Based on material from 
 * http://www.phanderson.com/printer/lcd/lcd.html
 * http://www.eyetap.org/ece385
 *
 * Written by Gwyneth Masanga, Nick Nelson,
 * 	      Mansoor Riaz
 * 	      
 * Parallel Port Driver for 
 * 	Optrex LCD Panel DMC50037N
 * 
 * ECE385F November 2001
 */
#include <linux/module.h>
#include <linux/param.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h>     /* everything... */
#include <linux/errno.h>  /* error codes */
#include <linux/malloc.h>
#include <linux/ioport.h>

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


#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif


#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
#include <asm/uaccess.h>  /* for put_user */
#include <linux/poll.h>
#endif

static int lcd_major = 0;
static int lcd_base  = 0;  /* Check module_init */
#define   DEFAULT_LCD_BASE         0x378

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,2,0)
#define   copy_to_user  	memcpy_tofs
#define   copy_from_user        memcpy_fromfs
#endif


static void clock(int x)
{
     udelay(2500);
     outb((__u8)(x), DEFAULT_LCD_BASE);
     udelay(2500);
     outb((__u8)x|0x80, DEFAULT_LCD_BASE);
}

static void out(int x)
{
     x = x & 0x4f;
     clock(x);
}

static void out_char(char c)
{
     out(((c>>4) & 0x0f) | 0x40);  /* hi nibble */
     out((c & 0x0f) | 0x40);   /* low nibble */
}



static int lcd_open(struct inode *inode, struct file *filp){
  int minor = (MINOR(inode->i_rdev));
int y;


  MOD_INC_USE_COUNT;

     /*power on*/

     for(y=0; y<3; y++)
     {
          out(0x03);
          udelay(2500);
     }

     /*4-bit operation */
     out(0x02);

     out(0x02);
     out(0x0c);

     /*display off, cursor off, blink off*/
     out(0x00);
     out(0x08);

     /*clear screen*/
     out(0x00);
     out(0x01);

     /*increment cursor to right when writing*/
     out(0x00);
     out(0x06);

     /*cursor on and blinking*/
     out(0x00);
     out(0x0f);

  return 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static int lcd_release(struct inode *inode, struct file *filp){ 
#else 
     void lcd_release(struct inode *inode, struct file *filp){
#endif

  MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
  return 0;
#endif
}


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t lcd_write(
    struct file *filp,
    const char *buf,
    size_t count,
    loff_t *offset
){
#else
int lcd_write(
    struct inode *inode,
    struct file *filp,
    const char *buf,
    const char *buf1,
    int count
){
#endif
  int count2;
  int retval = count;
  unsigned port = lcd_base;
  unsigned char outval;
  
  unsigned char *kbuf, *ptr;
  unsigned long j;
  
  kbuf = (unsigned char *) kmalloc(count, GFP_KERNEL);
  if (!kbuf)
    return -ENOMEM;

  copy_from_user(kbuf, buf, count);
  ptr=kbuf;
       
	count2 = 0;
	while (count--){
		count2++;
		out_char (*(ptr++));
		mdelay(10);
		if (count2 == 79){
			mdelay(1200);

    /*display off, cursor off, blink off*/
     out(0x00);
     out(0x08);

     /*clear screen*/
     out(0x00);
     out(0x01);

     /*increment cursor to right when writing*/
     out(0x00);
     out(0x06);

     /*cursor on and blinking*/
     out(0x00);
     out(0x0f);


	count2=0;
		}
	}
        

  kfree(kbuf);
  return retval;

}

struct file_operations lcd_fops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
  NULL,
#endif
  NULL,          /* lcd_lseek   */
  NULL,         /*lcd_read,*/
  lcd_write,
  NULL,          /* lcd_readdir */
  NULL,       	 /* lcd_select  */
  NULL,          /* lcd_ioctl   */
  NULL,          /* lcd_mmap    */
  lcd_open,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
  NULL,   /* flush */
#endif
  lcd_release
};

#if LINUX_VERSION_CODE > 0x20118
MODULE_PARM(lcd_base, "i");
#endif

int init_module(void){

  int result;

  if (!lcd_base)    /* Check if lcd_base was set through insmod */
    lcd_base = DEFAULT_LCD_BASE;

  result = check_region(lcd_base,32);
  if (result){
    printk("lcd can't get address",lcd_base);
    return result;
  }
  request_region(lcd_base,32,"lcd");

  result = register_chrdev(lcd_major, "lcd", &lcd_fops);
  if (result < 0){
      printk("lcd2: can't get major number\n");
      release_region(lcd_base,32);
      return result;
  }
  if (lcd_major == 0) lcd_major = result; /* dynamic */ 

  printk("initializing!\n");
  
  return 0;
}

void cleanup_module(void){

  unregister_chrdev(lcd_major, "lcd");
  release_region(lcd_base,32);
}

