/* phoenix.c -- Phoenix driver for 2.4.x & 2.6.x kernels 25-Feb-2005 BPA 
To compile under 2.4.x kernels
gcc -Wall -O2 -c -DMODULE -D__KERNEL__ -I/usr/src/linux/include -o ph.o ph.c
Note: '/usr/src/linux' should have kernel source for compilation
last modified on 19-JUL-05
*/

// Uncommend the line below, for using parallel port interrupt IRQ7

#define USE_IRQ

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/times.h>
#include <asm/uaccess.h>	/* for put_user */
#include <asm/io.h>		/* for I/O */

#include "phdriver.h"		// Has info to be shared with user programs

MODULE_LICENSE ("GPL");
MODULE_AUTHOR ("Ajith Kumar, Nuclear Science Centre");

#define	DATA	0x378		/* printer port I/O addresses */
#define STAT	0x379
#define CTL	0x37A

#define	ADCLO	0		// Phoenix Hardware address information
#define	ADCHI	1		// Offset for different functions
#define	ADADR	2
#define	ADSTRT	3
#define	DAC	4
#define	SM	5
#define	DOUT	6
#define	DIN	7
#define ENBIT	8		/* to avoid decoder spikes */
#define ADEOC	128		/* connected to S7 of status byte */

#define	ERR_BREAK {error=TRUE; break;}	// Just to save some typing

u8 waitForHigh (int b);		// Internal function prototypes
u8 waitForLow (int b);
u8 markRising (int b, int index);
u8 markFalling (int b, int index);
void outPort (u8 data, u8 port);
void pulseOut (int pin, int th, int tl, int np);
void invPulseOut (int pin, int th, int tl, int np);
u8 timedReadADC (int index);
void rotateMotor (int nsteps, int dir);
void user_delay(u32 del);

int motorDelay = 10;		// 10 msecs for Stepper Motor
u8  ADCtimeout = FALSE;
int ADCdelay	= 120;		// Conversion time for ADC0809 chip	
static int maxWait = 2000000;	// nearly 2 seconds timeout
struct timeval begin;		// used by user_delay() and ADC READBLOCK

drvdata *data;			// storage to be allocated dynamically

#ifdef USE_IRQ

#define	IRQBIT		0x40
#define	IRQENBIT	0x10
static int irq = 7;
static int irq_count = 0;


#define	NCHAN		4096
static	int	waiting_for_irq;
static	int	hist[NCHAN];
void readadc(void);

irqreturn_t ppisr (int irqnum, void *device, struct pt_regs *regs)
{
if(irqnum != 7)
	return IRQ_NONE;

if(!waiting_for_irq)
	{
	printk("unexpected Interrupt from %d\n",irq);
	return IRQ_HANDLED;
	}

readadc();

outb(7, DATA);		// clear busy flag 
outb(0, DATA);

return IRQ_HANDLED;
}

#endif


static int
device_ioctl (struct inode *inode, struct file *file,
	      unsigned int ioctl_num, unsigned long ioctl_param)
{
/*
IOCTL calls from user programs land here. Arguments:
'ioctl_num' is the command send by the user.
'ioctl_param' is a pointer to some memory block in user space.
Driver gets/puts data from there according to the requirement.
*/
  static  u8 dataval, ctlval;		// current data & ctlval on the PCB
  u32 *up = (void *) ioctl_param;	// our pointer to user space
  u8 error, tmp8, chan;
  u32 tmpi, k, buf[4];
  
  error = FALSE;

  switch (ioctl_num)
    {
    case READ_HIST:
	if(copy_to_user(up, hist, sizeof(hist)))
		return -EFAULT;
	break;

    case CLEAR_HIST:
	for(k=0; k < NCHAN; ++k) hist[k] = 0;	// Clear Histogram
	break;

    case START_HIST:
	outb(7, DATA);		/* clear busy flag */
	outb(0, DATA);
	waiting_for_irq = 1;
	outb(IRQENBIT, CTL);
//	printk("START: IRQ enabled...\n");
       	break;

    case STOP_HIST:
	outb(0, CTL);
	waiting_for_irq = 0;	
//	printk("STOP: IRQ disabled...%d\n",sizeof(hist));		
       	break;

    case OUTDATA:
    	copy_from_user (&tmp8, up, sizeof (u8));
    	dataval = tmp8;
    	outb (tmp8, DATA);
    	break;

    case OUTCTL:
    	copy_from_user (&tmp8, up, sizeof (u8));
    	tmp8 ^= 0x0b;		// Parallel port inverts C0, C1 and C3 inside
    	ctlval = tmp8;
    	outb (tmp8, CTL);
	break;

    case INSTAT:
    	tmp8 = inb(STAT);
    	copy_to_user (up, &tmp8, sizeof (u8));
	break;

    case PULSEDATA:
    	copy_from_user (&tmp8, up, sizeof (u8));
	tmp8 ^= dataval;
    	for(k = 0; k < 1024; ++k)
    		{
    		outb(tmp8, DATA);
    		outb(dataval, DATA);
		}
	break;

    case PULSECTL:
    	copy_from_user (&tmp8, up, sizeof (u8));
	tmp8 ^= ctlval;
    	for(k = 0; k < 1024; ++k)
    		{
    		outb(tmp8, CTL);
    		outb(ctlval, CTL);
		}
	break;


#ifdef USE_IRQ
    case ENBIRQ:
    	irq_count = 0;
    	outb(DIN, CTL);
		outb(ENBIT+DIN, CTL);
		outb(ENBIT+DIN+IRQENBIT, CTL);
	printk("IRQ Enabled\n");
	break; 

    case IRQCOUNT:
        copy_to_user (up, &irq_count, sizeof (u32));
    	outb(DIN, CTL);
    	printk("IRQ Disabled. Count =  %d\n", irq_count);
	break;    
#endif

    case TIMESTAMP:
      do_gettimeofday (&data[0].t);
      copy_to_user (up, data, sizeof (drvdata));
	break;    

    case DIGIN:
// Reads the 4 bit digital input word
      outb (DIN, CTL);
      outb (ENBIT + DIN, CTL);
      tmp8 = (inb (STAT) >> 3) & 15;
      outb (DIN, CTL);
      copy_to_user (up, &tmp8, sizeof (u8));
      break;

    case DIGOUT:
// Writes a value, 0 to 255, to the digital output
      copy_from_user (&tmp8, up, sizeof (u8));
      outPort(tmp8, DOUT);
      break;

    case SETDAC:
// Accepts a number , 0 to 255, and sets the DAC to that value.
      copy_from_user (&tmp8, up, sizeof (u8));
      outb (tmp8, DATA);	/* DAC goes from -5 to 5 for 0 to 255 */
      outb (DAC, CTL);
      outb (ENBIT + DAC, CTL);
      outb (DAC, CTL);
      break;

    case MOTORCW:		// Rotate motor clockwise by 'tmpi' steps
      copy_from_user (&tmpi, up, sizeof (u32));
      rotateMotor (tmpi, 1);
      break;

    case MOTORCCW:		// counter clockwise
      copy_from_user (&tmpi, up, sizeof (u32));
      rotateMotor (tmpi, 0);
      break;

    case SETMOTOR:		// Set any desired value (0 to 15)
      copy_from_user (&tmp8, up, sizeof (u8));
      outPort (tmp8, SM);
      break;

    case SELECTADC:
//       Select the ADC channel to be used. (0 to 7)
      copy_from_user (&tmp8, up, sizeof (u8));
      if (tmp8 > 7)
	ERR_BREAK;
      outb (tmp8, DATA);
      outb (ADADR, CTL);
      outb (ENBIT + ADADR, CTL);
      outb (ADADR, CTL);
      break;

    case READADC:		// Retuns ADC value with time stamp
      ADCtimeout = FALSE;
	data[0].adval[0] = timedReadADC (0) & 0xff;
      if (ADCtimeout)
	ERR_BREAK;
      copy_to_user (up, data, sizeof (drvdata));
      break;

    case READBLOCK:
/*
Digitize a waveform from the channel kept selected before  and returns 
the values in data[k].adval[0] and time stamps in data[k].t
Arguments:
1. Number of samples
2. Delay between samples in usecs
*/
      copy_from_user (buf, up, 2 * sizeof (u32));
      if ((buf[0] > MAXPOINTS) || (buf[1] > MAXDELAY*1000) )
	ERR_BREAK;

	if(buf[1] < ADCdelay) 
		buf[1] = ADCdelay;
	 
      ADCtimeout = FALSE;
      for (k = 0; k < buf[0]; ++k)
	{
	  if(!k)
		do_gettimeofday (&begin);	// mark starttime
	  else
	  	user_delay(k * buf[1]);
	  			
	  data[k].adval[0] = timedReadADC (k) & 0xff;
	  if (ADCtimeout)
	    ERR_BREAK;
	}
 
      if (error)
	break;
      copy_to_user (up, data, buf[0] * sizeof (drvdata));
      break;

    case MULTIREADBLOCK:
/*
Digitizes waveforms on multiple channels sampled simultaneously, starting with
channel zero. Arguments:
1. Number of samples
2. Number of channels to be read (1 to 4)
3. Delay between samples
*/
      copy_from_user (buf, up, 3 * sizeof (u32));
      if ((buf[0] > MAXPOINTS) || (buf[1] > 4) || (buf[2] > MAXDELAY))
	ERR_BREAK;
      ADCtimeout = FALSE;

      for (k = 0; k < buf[0]; ++k)
	{
	  if(!k)
		do_gettimeofday (&begin);	// mark starttime
	  else
	  	user_delay(k * buf[2]);
	  	
	  for (chan = 0; chan < buf[1]; ++chan)
	    {
	      if (chan == 0)
	      	outb (0, DATA);	// Channel 0 with fresh sampling
	      else
	      	outb (chan + 4, DATA);	// rest with the stored charge
	      outb (ADADR, CTL);
	      outb (ENBIT + ADADR, CTL);	// Select Channel
	      outb (ADADR, CTL);

	      data[k].adval[chan] = timedReadADC (k);
	      if (ADCtimeout)
	      	ERR_BREAK;	// get out of the inner for loop
	    }
	  if (error)
	    break;		// get out of the outer for loop
	}

      if (error)
	break;
      copy_to_user (up, data, buf[0] * sizeof (drvdata));
      break;

    case TRIGREADBLOCK:
/*
Digitize a block after detecting a Falling Edge on a Digital Input Pin
Arguments:
1. Digital Input pin number to which trigger is applied
2. Number of points to be digitized
3. Deadtime introduced from trigger to first digitization.
4. Delay between samples afterwards.
5. trigger polarity (rising edge or falling edge)
*/
      copy_from_user (buf, up, 5 * sizeof (u32));

      if ((buf[0] > 3) || (buf[1] > MAXPOINTS) || (buf[2] > MAXDELAY)
      	|| ( buf[3] > MAXDELAY) || (buf[4] > 1) )
		ERR_BREAK;
      if(buf[4] == 0)		// Rising Edge Triggering
	{	
      if (!waitForLow(buf[0]))
      	ERR_BREAK;
      if (!markRising (buf[0], 0))
      	ERR_BREAK;
      	}
      else
	{	
      if (!waitForHigh (buf[0]))
      	ERR_BREAK;
      if (!markFalling (buf[0], 0))
      	ERR_BREAK;
      	}
      
      udelay (buf[2]);		// deadtime before starting digitization
      ADCtimeout = FALSE;

      for (k = 0; k < buf[1]; ++k)  	// store ADC data and time stamps
	{
	  if(!k)
		do_gettimeofday (&begin);	// mark starttime
	  else
	  	user_delay(k * buf[3]);	// buf[3] is delay between samples

	  data[k].adval[0] = timedReadADC (k);
	  if (ADCtimeout)
	    ERR_BREAK;
	}

      if (error)
	break;
      copy_to_user (up, data, buf[1] * sizeof (drvdata));  //buf[1] = npoints
      break;
	
    case PERIOD:
/* Returns the times for specified number of rising edges on an input pin
data[0] has the fist timestamp and data[1] has the last stamp */

      copy_from_user (buf, up, 2 * sizeof (u32));
      if ((buf[0] > 3) || (buf[1] > MAXPOINTS))
	ERR_BREAK;

      if (!waitForLow (buf[0]))
	ERR_BREAK;
      if (!markRising (buf[0], 0))	// timestamp first edge to data[0]
         ERR_BREAK;

      for (k = 0; k < buf[1]; ++k)
	{
	  if (!waitForLow (buf[0]))
	    ERR_BREAK;
	  if (!markRising (buf[0], 1))	// timestamp edges to data[1]
	    ERR_BREAK;
	}

      if (error)
	break;
      copy_to_user (up, data, 2 * sizeof (drvdata));
      break;


    case R2RTIME:
/*
Measures the time between two rising edges on digital inputs pins.
Arguments:
1. Pin number to wait for raising edge
2. Pin number to wait for the falling edge
values range from 0 to 3.
*/
      copy_from_user (buf, up, 2 * sizeof (u32));
      if ((buf[0] > 3) || (buf[1] > 3) )
	ERR_BREAK;
      // Pin should be LOW first to give a rising edge
      if (!waitForLow (buf[0]))
	ERR_BREAK;
      if (!markRising (buf[0], 0))
	ERR_BREAK;
	
      if (!waitForLow (buf[1]))
	ERR_BREAK;
      if (!markRising (buf[1], 1))
	ERR_BREAK;
      copy_to_user (up, data, 2 * sizeof (drvdata));	// copy two timestamps
      break;

    case R2FTIME:
/*
Measures the time from rising edge to a falling edge on digital inputs pins.
Arguments:
1. Pin number to wait for raising edge
2. Pin number to wait for the falling edge
values range from 0 to 3 and they could be the same
*/
      copy_from_user (buf, up, 2 * sizeof (u32));

      if ((buf[0] > 3) || (buf[1] > 3))
	ERR_BREAK;
      // Pin should be LOW first to give a rising edge
      if (!waitForLow (buf[0]))
	ERR_BREAK;
      if (!markRising (buf[0], 0))
	ERR_BREAK;
      if (!markFalling (buf[1], 1))
	ERR_BREAK;
      copy_to_user (up, data, 2 * sizeof (drvdata));	// copy two timestamps
      break;

    case F2RTIME:
/*
Measures the time from falling edge to a rising edge on digital inputs pins.
Arguments:
1. Pin number to wait for falling edge
2. Pin number to wait for the rising edge
values range from 0 to 3 and they could be the same
*/
      copy_from_user (buf, up, 2 * sizeof (u32));
      if ((buf[0] > 3) || (buf[1] > 3))
	ERR_BREAK;
      // Pin should be HIGH first to give a falling edge
      if (!waitForHigh (buf[0]))
	ERR_BREAK;
      if (!markFalling (buf[0], 0))
	ERR_BREAK;
      if (!markRising (buf[1], 1))
	ERR_BREAK;
      copy_to_user (up, data, 2 * sizeof (drvdata));	// copy two timestamps
      break;


    case F2FTIME:
/*
Measures the time from falling edge to a falling edge on digital inputs pins.
Arguments:
1. Pin number to wait for falling edge
2. Pin number to wait for the rising edge
values range from 0 to 3.
*/
      copy_from_user (buf, up, 2 * sizeof (u32));
      if ((buf[0] > 3) || (buf[1] > 3) )
	ERR_BREAK;
      // Pin should be HIGH first to give a falling edge
      if (!waitForHigh (buf[0]))
	ERR_BREAK;
      if (!markFalling (buf[0], 0))
	ERR_BREAK;
	
      if (!waitForHigh (buf[1]))
	ERR_BREAK;
      if (!markFalling (buf[1], 1))
	ERR_BREAK;
      copy_to_user (up, data, 2 * sizeof (drvdata));	// copy two timestamps
      break;

    case SET2RTIME:
/* 
Returns time between setting an output to the rising edge of an input
Arguments:
1. Outut Pin number. Digital outputs(0 to 7). 8 to 11 for SM outputs 0 to 3.
2. Input Pin (0 to 3)
*/
      copy_from_user (buf, up, 2 * sizeof (u32));
      if ((buf[0] > 11) || (buf[1] > 3))
	ERR_BREAK;

      do_gettimeofday (&data[0].t);	// mark start time
      if (buf[0] < 8)		// On a digital output pin
	outPort (1 << buf[0], DOUT);
      else
	outPort (1 << (buf[0] - 8), SM);
      if (!markRising (buf[1], 1))
	ERR_BREAK;

      copy_to_user (up, data, 2 * sizeof (drvdata));	// return 2 timestamps
      break;

    case SET2FTIME:
/* 
Returns time between setting an output to the Falling edge of an input
Arguments:
1. Outut Pin number. Digital outputs(0 to 7). 8 to 11 for SM outputs 0 to 3.
2. Input Pin (0 to 3)
*/
      copy_from_user (buf, up, 2 * sizeof (u32));
      if ((buf[0] > 11) || (buf[1] > 3))
	ERR_BREAK;

      do_gettimeofday (&data[0].t);	// mark start time
      if (buf[0] < 8)		// On a digital output pin
	outPort (1 << buf[0], DOUT);
      else
	outPort (1 << (buf[0] - 8), SM);
      if (!markFalling (buf[1], 1))
	ERR_BREAK;

      copy_to_user (up, data, 2 * sizeof (drvdata));	// return 2 timestamps
      break;

    case CLR2RTIME:
/* 
Returns time between clearing an output bit to the Falling edge of an input
Arguments:
1. Outut Pin number. Digital outputs(0 to 7). 8 to 11 for SM outputs 0 to 3.
2. Input Pin (0 to 3)
*/
      copy_from_user (buf, up, 2 * sizeof (u32));
      if ((buf[0] > 11) || (buf[1] > 3))
	ERR_BREAK;

      do_gettimeofday (&data[0].t);	// mark start time
      if (buf[0] < 8)			// On a digital output pin
	outPort ( ~(1 << buf[0]), DOUT);
      else
	outPort ( ~( 1 << (buf[0] - 8) ), SM);
      if (!markRising (buf[1], 1))
	ERR_BREAK;

      copy_to_user (up, data, 2 * sizeof (drvdata));	// return 2 timestamps
      break;


    case CLR2FTIME:
/* 
Returns time between clearing an output bit to the Falling edge of an input
Arguments:
1. Outut Pin number. Digital outputs(0 to 7). 8 to 11 for SM outputs 0 to 3.
2. Input Pin (0 to 3)
*/
      copy_from_user (buf, up, 2 * sizeof (u32));
      if ((buf[0] > 11) || (buf[1] > 3))
	ERR_BREAK;

      do_gettimeofday (&data[0].t);	// mark start time
      if (buf[0] < 8)		// On a digital output pin
	outPort ( ~(1 << buf[0]), DOUT);
      else
	outPort ( ~( 1 << (buf[0] - 8) ), SM);
      if (!markFalling (buf[1], 1))
	ERR_BREAK;

      copy_to_user (up, data, 2 * sizeof (drvdata));	// return 2 timestamps
      break;


   case PULSE2RTIME:
/* 
Generate single pulse on any digital output pins or stepper motor outputs and
then waits for rising edge on any of the Input lines
Arguments:
1. Pin1: From 0 to 7 means digital output and 8 to 11 means SM outputs 0 to 3.
2. Pin2: From 0 to 3
3. Pulse Width in microseconds (upto 1000 usecs)
4. Deadtime after sending the pulse and looking for the response
5. Polarity of pulse
*/
      copy_from_user (buf, up, 5 * sizeof (u32));
      if ((buf[0] > 11) || (buf[1] > 3) || (buf[2] > MAXHP) || 
      	 (buf[3] > MAXDELAY) || (buf[4] > 1) )
			ERR_BREAK;

      do_gettimeofday (&data[0].t);	// mark start time
      if(buf[4] == 0)			// TTL HIGH TRUE
	      pulseOut (buf[0], buf[2], 0, 1);	
      else				// TTL LOW TRUE
	      invPulseOut (buf[0], buf[2], 0, 1);

      udelay(buf[3]);		// Dead time.
      
     if (!markRising (buf[1], 1))
	ERR_BREAK;

      copy_to_user (up, data, 2 * sizeof (drvdata));	// return 2 timestamps
      break;

   case PULSE2FTIME:
/* 
Generate single pulse on any digital output pins or stepper motor outputs and
then waits for Falling edge on any of the Input lines
Arguments:
1. Pin1: From 0 to 7 means digital output and 8 to 11 means SM outputs 0 to 3.
2. Pin2: From 0 to 3
3. Pulse Width in microseconds (upto 1000 usecs)
4. Deadtime after sending the pulse and looking for the response
5. Polarity of pulse
*/
      copy_from_user (buf, up, 5 * sizeof (u32));
      if ((buf[0] > 11) || (buf[1] > 3) || (buf[2] > MAXHP) || 
      	 (buf[3] > MAXDELAY) || (buf[4] > 1) )
			ERR_BREAK;

      do_gettimeofday (&data[0].t);	// mark start time
      if(buf[4] == 0)			// TTL HIGH TRUE
	      pulseOut (buf[0], buf[2], 0, 1);	
      else				// TTL LOW TRUE
	      invPulseOut (buf[0], buf[2], 0, 1);

      udelay(buf[3]);		// Dead time.
      
     if (!markFalling (buf[1], 1))
	ERR_BREAK;

      copy_to_user (up, data, 2 * sizeof (drvdata));	// return 2 timestamps
      break;


    case PULSEOUT:
/* 
Generates pulses on any digital output pins or stepper motor outputs.
Arguments:
1. Pin number. 0 to 7 means digital output and 8 to 11 means SM outputs 0 to 3.
2. HITIME in microseconds (upto MAXHP usecs)
3. LOWTIME in microseconds (upto MAXHP usecs)
4. Number of pulses to be generated.
5. Polarity. 0 for positive TTL ( _|^|_ ) and 1 for negative TTL ( |_| )
*/
      copy_from_user (buf, up, 5 * sizeof (u32));
      if ((buf[0] > 11) || (buf[1] > MAXHP) ||
	  (buf[2] > MAXHP) || (buf[3] > MAXPULSES) || (buf[4] > 1) )
	     ERR_BREAK;
      do_gettimeofday (&data[0].t);	// mark start time
      if(buf[4] == 0)
		pulseOut (buf[0], buf[1], buf[2], buf[3]);
	  else
		invPulseOut (buf[0], buf[1], buf[2], buf[3]);
    
      do_gettimeofday (&data[1].t);	// mark end time

      copy_to_user (up, data, 2 * sizeof (drvdata));	// return 2 timestamps
      break;


    case SETMAXWAIT:
/*
	The time measurement calls wait inside the driver and freezes
the system for that period. It is essential to provide maximum limits for
waiting. The default is around 1000000 microseconds ( 1 second). User program
can change this to any value between 5 msecs & 5 secs.
*/
      copy_from_user (buf, up, sizeof (u32));
      if ((buf[0] < 5000) || (buf[0] > 5000000))	// 5 msec to 5 sec
	ERR_BREAK;
      maxWait = buf[0];
      break;

    default:
      ERR_BREAK;
    }

  if (error)
    return -EIO;
  return 0;
}


//---------------------------------------------------------------------------

int init_module (void);
void cleanup_module (void);
static int device_open (struct inode *, struct file *);
static int device_release (struct inode *, struct file *);

#define DEVICE_NAME "phoenix"	/* Dev name as it appears in /proc/devices   */
static int Major;		/* Major number assigned to our device driver */
static int Device_Open = 0;	/* Is device open */


static struct file_operations fops = {
  .open = device_open,
  .ioctl = device_ioctl,
  .release = device_release
};

int
init_module (void)
{
  Major = register_chrdev (0, DEVICE_NAME, &fops);

  if (Major < 0)
    {
      printk ("Registering the character device failed with %d\n", Major);
      return Major;
    }

//data = (drvdata *) kmalloc(MAXPOINTS*sizeof(drvdata), GFP_KERNEL);

data = (drvdata *) vmalloc(MAXPOINTS*sizeof(drvdata));

if(!data)
    {
      printk ("Phoenix: memory allocation failed.\n");
      unregister_chrdev (Major, DEVICE_NAME);
      return -1;
    }

#ifdef USE_IRQ
  {
  int status;
  outb(0, CTL);		// Clear Interrupt BIT of parallel port
  status = request_irq (irq, (void *) ppisr, SA_INTERRUPT, DEVICE_NAME, NULL);
  if (status < 0)
    {
      printk ("IRQ number allocation failed. %d\n",status);
      unregister_chrdev (Major, DEVICE_NAME);
      return status;
    }
  }  
#endif

  return 0;
}


void
cleanup_module (void)
{
  int ret;
  vfree(data);

#ifdef USE_IRQ
  free_irq (irq, NULL);	
#endif

  ret = unregister_chrdev (Major, DEVICE_NAME);
  if (ret < 0)
    printk ("Error in unregister_phoenix: %d\n", ret);
}

static int
device_open (struct inode *inode, struct file *file)
{
  if (Device_Open)		// We may remove this..
    {
      printk ("Device Busy....\n");
      return -EBUSY;
    }
  Device_Open++;

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
  try_module_get (THIS_MODULE);
#endif

return 0;
}


static int
device_release (struct inode *inode, struct file *file)
{
  Device_Open--;		/* We're now ready for our next caller */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
  module_put (THIS_MODULE);
#endif
  return 0;
}


//-------------------------------------------------------------------------

u8
timedReadADC (int k)		/* returns  0 to 255. */
{
  u8 lo, hi, dat;
  volatile int timer = 50;

  outb (ADSTRT, CTL);
  outb (ENBIT + ADSTRT, CTL);	// sample mode
  do_gettimeofday (&data[k].t);	// mark time
  udelay (1);
  outb (ADSTRT, CTL);		// Start Conversion

  udelay (100);			// better not disturb the PCB for 100 microseconds
  while (timer)
    {
      if (~inb (STAT) & ADEOC)
	break;			// EOC bit inverted by Pport
      --timer;
    }

  if (!timer)
    {
      ADCtimeout = TRUE;	// set error flag
      return 0;
    }

  outb (ADCLO, CTL);
  outb (ENBIT + ADCLO, CTL);
  lo = (inb (STAT) >> 3) & 15;
  outb (ADCLO, CTL);

  outb (ADCHI, CTL);
  outb (ENBIT + ADCHI, CTL);
  hi = inb (STAT) >> 3;
  outb (ADCHI, CTL);

  dat = (hi << 4) | lo;
  return dat;
}

void
rotateMotor (int nsteps, int dir)
{
  static u8 pos = 0;
  static u8 seq[4] = { 12, 6, 3, 9 };
  int i;

  for (i = 0; i < nsteps; ++i)
    {
      if (dir)
	if (pos == 3)
	  pos = 0;
	else
	  ++pos;
      else if (pos == 0)
	pos = 3;
      else
	--pos;

      outb (seq[pos], DATA);
      outb (SM, CTL);
      outb (SM + ENBIT, CTL);
      outb (SM, CTL);
      outb (0, CTL);
      mdelay (motorDelay);
    }
}

u8
waitForHigh (int b)
{
  u8 mask = 1 << (b + 3);	// Printer Status port data is from S3 to S7
volatile int timer = 0;

  outb (DIN, CTL);
  outb (ENBIT + DIN, CTL);
  while (timer++ < maxWait)
    if (inb (STAT) & mask)
      break;
  outb (DIN, CTL);

//printk("WFH: timer %d\n", timer);
  if (timer >= maxWait)
    return FALSE;
  return TRUE;
}

u8
waitForLow (int b)
{
  u8 mask = 1 << (b + 3);	// Printer Status port data is from S3 to S7
  volatile int timer = 0;

  outb (DIN, CTL);
  outb (ENBIT + DIN, CTL);
  while (timer++ < maxWait)
    if (~inb (STAT) & mask)
      break;
  outb (DIN, CTL);

//printk("WFL: timer %d\n", timer);

  if (timer >= maxWait)
    return FALSE;
  return TRUE;
}

u8
markRising (int b, int index)
{
  u8 mask = 1 << (b + 3);	// Printer Status port data is from S3 to S7
  volatile int timer = 0;

  outb (DIN, CTL);
  outb (ENBIT + DIN, CTL);
  while (timer++ < maxWait)
    if (inb (STAT) & mask)
      break;
  do_gettimeofday (&data[index].t);	// Store the time stamp
  outb (DIN, CTL);

//  printk("MR: timer %d\n", timer);
  if (timer >= maxWait)
    return FALSE;
  return TRUE;
}

u8
markFalling (int b, int index)
{
  u8 mask = 1 << (b + 3);	// Printer Status port data is from S3 to S7
  volatile int timer = 0;

  outb (DIN, CTL);
  outb (ENBIT + DIN, CTL);
  while (timer++ < maxWait)
    if (~inb (STAT) & mask)
      break;
  do_gettimeofday (&data[index].t);	// Store the time stamp
  outb (DIN, CTL);
  
//printk("MF: timer %d\n", timer);

  if (timer >= maxWait)
    return FALSE;
  return TRUE;
}

void
pulseOut (int pin, int th, int tl, int np)
{
  int k;
  u8 port;

  if (pin < 8)
   	port = DOUT;
  else
      	port = SM;

  for (k = 0; k < np; ++k)
    {
      outb (1 << pin, DATA);
      outb (ENBIT + port, CTL);
      outb (port, CTL);
      if (th > 3)		// 3 usecs goes for the 'outb' instruction
	udelay (th - 3);

      outb (0, DATA);
      outb (ENBIT + port, CTL);
      outb (port, CTL);
      if (tl > 3)
	udelay (tl - 3);
    }
}

void
invPulseOut (int pin, int th, int tl, int np)
{
  int k;
  u8 port;

  if (pin < 8)
   	port = DOUT;
  else
      	port = SM;

  for (k = 0; k < np; ++k)
    {
      outb (0, DATA);
      outb (ENBIT + port, CTL);
      outb (port, CTL);
      if (th > 3)		// 3 usecs goes for the 'outb' instruction
		udelay (th - 3);

      outb (1 << pin, DATA);
      outb (ENBIT + port, CTL);
      outb (port, CTL);
      if (tl > 3)
	udelay (tl - 3);
    }
}

void
outPort (u8 data, u8 port)
{
  outb (data, DATA);
  outb (port, CTL);
  outb (ENBIT + port, CTL);
  outb (port, CTL);
}

void user_delay(u32 del)
{
struct timeval now;
u32	ds, dus;

while(1)
	{
	do_gettimeofday (&now);
	if(now.tv_usec < begin.tv_usec)
		{
		now.tv_usec += 1000000;
		now.tv_sec -= 1;
		}
	dus = now.tv_usec - begin.tv_usec;
	ds = now.tv_sec - begin.tv_sec;
	if( (ds * 1000000 + dus) > del)
		break;
	}
}

void readadc(void)
{
unsigned char	n2, n3, n4;
unsigned short 	data;

outb(1, DATA);
n2 =  inb(STAT);
n2 =  (n2 >> 3) & 0x17;
n2 |= (n2 & 16) >> 1;
n2 =  (n2 & 15);

outb(2, DATA);
n3 =  inb(STAT);
n3 =  (n3 >> 3) & 0x17;
n3 |= (n3 & 16) >> 1;
n3 =  (n3 & 15);

outb(3, DATA);
n4 =  inb(STAT);
n4 =  (n4 >> 3) & 0x17;
n4 |= (n4 & 16) >> 1;
n4 =  (n4 & 15);

data = (n4 << 8) | (n3 << 4) | n2;
++hist[data];

//printk("%x %x %x\n",data, hist[191], hist[192]);
}
