/*
 #
 # Original File From
 # ------------------
 #	http://csel.cs.colorado.edu/~boggs/pbus/pbus.c
 #
 * pbus.c -- Parallel port bus device drivers for Linux 
 *           Written by Adam Boggs, 4/95
 */


/* NOTE: Not all code is included in this file.  This is the main 
 * driver code.  I only printed this out to avoid printing the
 * entire kernel sources.  If you're interested in how any of this
 * works, you're welcome to read the kernel source yourself!
 */


#include <linux/config.h>
#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#endif



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

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/sem.h>
#include <linux/delay.h>

/* To be used for anything that creates an array indexed by device */
#define MAX_DEVICES 8

/* default priority for a task.  ie. gets 5 bytes written per cycle */
#define DEFAULT_PRI 5

#define PBIOCT_INIT_PPI 1
#define PBIOCT_LOCK_DEV 2


/* addresses to pass to ppi_write() to access different ports on the chip */

#define PPI_PORTA   0x00
#define PPI_PORTB   0x01
#define PPI_PORTC   0x02
#define PPI_CONTROL 0x03

/* inverts proper bits so all pp control lines are active high */

#define CTRL(x) ((x) ^ 0x0B)


/* global variables */


/* semaphore to prevent programs from competing */
struct semaphore sem = MUTEX; /* create semaphore for MUTually EXclusive (?) devices.  */

/* bit locks for devices */
static unsigned char pb_lock = 0x00;

/* should include kernel identification string in module. */
static char kernel_version[] = UTS_RELEASE;

/* this way we can tune it via insmod pbus major=30 */
static int pb_major = PBUS_MAJOR;

/* again, these are now modifyable by command line options to insmod or the kernel */

static unsigned int pp_base = 0x278;
static unsigned int pp_data;
static unsigned int pp_status;
static unsigned int pp_control;


/* this is the dynamic priority table.  While it is initialized to
 * give all processes equal priority, it can be modified with ioctl()
 * or in the future maybe dynamically depending on the estimated load
 * of the process.
 */

static unsigned int pb_pri[MAX_DEVICES];




/* function prototyes */

long pb_init(long kmem_start);

static int pb_write(struct inode *inode, struct file *file, char *buf, int count);
static int pb_open(struct inode *inode, struct file *file);
static int pb_close(struct inode *inode, struct file *file);
static int pb_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
static int pb_read(struct inode *inode, struct file *fp, char *buf, int count);
static unsigned char ppi_read(unsigned char addr);
static void ppi_init(void);

static void ppi_bit_twiddle(unsigned char bit_num, unsigned char value);
static void ppi_write(unsigned char addr, unsigned char data);

int init_module(void);
void cleanup_module(void);

static struct file_operations pb_fops = {
   NULL,               /* pb_lseek           */
   pb_read,
   pb_write,
   NULL,               /* readdir()         */
   NULL,               /* select ()(future) */
   pb_ioctl,           /* ioctl()           */
   NULL,               /* mmap()            */
   pb_open,
   pb_close,
   NULL,               /* fsync()                 */
   NULL,               /* fasync()                */
   NULL,               /* check_media_change()    */
   NULL                /* revalidate()            */ 
};


long pb_init(long kmem_start)
{
   int i;

   if (register_chrdev(pb_major, "pb", &pb_fops)) {
      printk("Unable to get major number %d for parallel bus.\n", pb_major);
      return kmem_start;
   }
   pp_data = (pp_base);
   pp_status = (pp_base + 1);
   pp_control = (pp_base + 2);

   for (i = 0; i < MAX_DEVICES; i++)
      pb_pri[i] = DEFAULT_PRI;
   ppi_init();

   printk("\n\nParallel Port Bus initialized at port 0x%X.\n\n", pp_base);

   return kmem_start;
}


static void ppi_init(void)
{
   ppi_write(PPI_CONTROL, 0x90); /* send control word to ppi        */
   ppi_write(PPI_PORTB, 0x00);  /* Clear port b and c...            */
   ppi_write(PPI_PORTC, 0xC0);
}

static int pb_open(struct inode *inode, struct file *file)
{
   MOD_INC_USE_COUNT; /* to prevent driver from being unloaded when in use */
   return 0;
}

static int pb_close(struct inode *inode, struct file *file)
{
   MOD_DEC_USE_COUNT;
   return 0;
}

static int pb_ioctl(struct inode *inode, struct file *file, 
                                 unsigned int cmd, unsigned long arg)
{
   switch (cmd) {
      /* INIT_PPI initializes ppi in case of lockup or bad state */
      case PBIOCT_INIT_PPI:   ppi_init();
                              break;
      /* This will lock the bus so only one device can use it */
      case PBIOCT_LOCK_DEV:   break; 
   }
   return 0;
}


static int pb_write(struct inode *inode, struct file *file, char *buf, int count)
{

   unsigned int device_num = ((MINOR(inode->i_rdev) >> 3) & 0x0007);
   unsigned int control_val = MINOR(inode->i_rdev) & 0x0007;
   unsigned int buf_index = 0;
   unsigned int idx_count;



#ifdef DEBUG
   printk("Got to pb_write, count = %d, device = %1x:%1x, pb_lock = %d\n", count, device_num, control_val, pb_lock);
#endif


   while (pb_lock & (1 << device_num));
   /* set lock bit once device gains control */
   pb_lock |= (1 << device_num);

   while (buf_index < count) {

#ifdef DEBUG
      printk("Downing semaphore.\n");
#endif
      down(&sem); /* will go to sleep if sem already down and wakeup at next up() */
#ifdef DEBUG
      printk("past down().\n");
#endif


      /* we should really load data into a kernel buffer since get_fs_byte() */
      /* may cause a page fault, and thus will screw up the cli()/sti() pair */

      cli();     /* to stop scheduling */

      /* select device to output to by putting the address of the device */
      /* (3 bits) on C0, C1, and C2                                      */
      ppi_bit_twiddle(0, (device_num >> 0) & 0x01);
      ppi_bit_twiddle(1, (device_num >> 1) & 0x01);
      ppi_bit_twiddle(2, (device_num >> 2) & 0x01);

      ppi_bit_twiddle(3, (control_val >> 0) & 0x01);
      ppi_bit_twiddle(4, (control_val >> 1) & 0x01);
      ppi_bit_twiddle(5, (control_val >> 2) & 0x01);

      for (idx_count = 0; ((idx_count < pb_pri[device_num]) && (buf_index < count)); idx_count++) {
         
         ppi_write(PPI_PORTB, get_fs_byte(buf + buf_index++));

         /* And then toggle the write line to complete the bus cycle */

         ppi_bit_twiddle(7, 1);
         ppi_bit_twiddle(7, 0);
         ppi_bit_twiddle(7, 1);

      }

      /* clear lock when done with buffer but before we wakeup */
      if (buf_index == count)  {
#ifdef DEBUG
         printk("clearing lock on device %d old_lock = %d  ", device_num, pb_lock);
#endif

         pb_lock -= (1 << device_num);

#ifdef DEBUG
         printk("new_lock = %d.\n", pb_lock);
#endif
      }
      sti();
      up(&sem);
      schedule();
   } 


#ifdef DEBUG
   printk("returning from pb_write()");
#endif

   return count;
}


/*
 * Surprise! This looks almost exactly like pb_write, except
 * it toggles the read line and deals with reading the data.
 */

static int pb_read(struct inode *inode, struct file *fp, char *buf, int count)
{

   unsigned int device_num = ((MINOR(inode->i_rdev) >> 3) & 0x0007);
   unsigned int control_val = MINOR(inode->i_rdev) & 0x0007;
   unsigned int buf_index = 0;
   unsigned int idx_count;

#ifdef DEBUG
   printk("Got to pb_read, count = %d, minor = %d, device_num = %1x:%1x\n", count, MINOR(inode->i_rdev), device_num, control_val);
#endif

   /* wait for lock to free up */
   while (pb_lock & (1 << device_num));

   /* set lock bit once device gains control */
   pb_lock |= (1 << device_num);

   while (buf_index < count) {

      down(&sem); /* will go to sleep if sem already down and wakeup at next up() */

      /* set lock bit once device gains control */
      pb_lock |= (1 << device_num);

      /* we should really load data into a kernel buffer since get_fs_byte() */
      /* may cause a page fault, and thus will screw up the cli()/sti() pair */

      cli();     /* to stop scheduling */

      /* select device to output to by putting the address of the device */
      /* (3 bits) on C0, C1, and C2                                      */
      ppi_bit_twiddle(0, (device_num >> 0) & 0x01);
      ppi_bit_twiddle(1, (device_num >> 1) & 0x01);
      ppi_bit_twiddle(2, (device_num >> 2) & 0x01);

      ppi_bit_twiddle(3, (control_val >> 0) & 0x01);
      ppi_bit_twiddle(4, (control_val >> 1) & 0x01);
      ppi_bit_twiddle(5, (control_val >> 2) & 0x01);

      for (idx_count = 0; ((idx_count < pb_pri[device_num]) && (buf_index < count)); idx_count++) {
         
         /* And then toggle the write line to complete the bus cycle */

         ppi_bit_twiddle(6, 1); /* Idle state */
         ppi_bit_twiddle(6, 0); /* lower read line */

      /* this should really read 8 bits off the data lines instead
       * of only 4 bits off the status, but I don't have a
       * bidirectional parallel port!! 
       */
         put_fs_byte(inb(pp_status), buf + buf_index++);

         ppi_bit_twiddle(6, 1); /* raise read line */

      }

      /* clear lock when done with buffer but before we wakeup */
      if (buf_index == count)  {
#ifdef DEBUG
         printk("clearing lock on device %d old_lock = %d  ", device_num, pb_lock);
#endif

         pb_lock -= (1 << device_num);

#ifdef DEBUG
         printk("new_lock = %d.\n", pb_lock);
#endif
      }
      sti();
      up(&sem);
      schedule();
   } 

#ifdef DEBUG
   printk("returning %d bytes from pb_read()", count);
#endif

   return count;
}



static void ppi_bit_twiddle(unsigned char bit_num, unsigned char value)
{
   /* send bit twiddle command word to the ppi.  bit 0 is the value */
   /* bits 1-3 are the bitnum, and everything else is 0 */

   unsigned char val =  ((((bit_num&0x07)<<1) | (value ? 1 : 0)) & 0x7F);
   ppi_write(PPI_CONTROL, val);
}



static void ppi_write(unsigned char addr, unsigned char data)
{
   /* bit fields of output are [ XX XX XX XX  A1  A0  RD/  WR/  ]   */
   unsigned char output = ((addr << 2)  | 0x03);


   outb(data, pp_data);

   outb(CTRL(0x0F), pp_control);            /* idle state */
   outb(CTRL(output), pp_control);          /* load  */
   outb(CTRL(output & 0xFE), pp_control);   /* lower write bit      */
   outb(CTRL(output), pp_control);          /* raise write bit      */
   outb(CTRL(0x0F), pp_control);            /* idle state                      */
}


static unsigned char ppi_read(unsigned char addr) 
{
   /* bit fields of output are [ XX XX DIR INT  A1  A0  RD/  WR/  ]   */
   unsigned char output = ((addr << 2)  | 0x23);
   unsigned char data;


   outb(CTRL(0x2F), pp_control);            /* tri-state parallel port*/
   outb(CTRL(output), pp_control);          /* load  */
   outb(CTRL(output & 0xFD), pp_control);   /* lower read bit      */

   data = inb(pp_data);                     /* read in the data     */

   outb(CTRL(output), pp_control);          /* raise write bit      */
   outb(CTRL(0x0F), pp_control);            /* idle state           */

   return data;
}



/* Kernel module support */

int init_module(void)
{
   if (register_chrdev(pb_major, "pb", &pb_fops)) {
      printk("Unable to get major number %d for parallel bus.\n", pb_major);
      return -EIO;
   }

   pp_data = (pp_base);
   pp_status = (pp_base + 1);
   pp_control = (pp_base + 2);

   ppi_init();

   printk("\n\nParallel Port Bus initialized at port 0x%X.\n\n", pp_base);

   return 0;
}

void cleanup_module(void)
{
   if (MOD_IN_USE)
      printk("pb: device busy, remove delayed.\n");

   if (unregister_chrdev(pb_major, "pb") != 0)
      printk("pb: cleanup module failed!\n");

}





