#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>

#include "phlib.h"
#include "phdriver.h"

/*
This file 'phlib.c' contains a collection of functions that makes 'ioctl' calls
to the device driver to perform hardware related functions. 
The structure and the array of structures defined below is used for sending
and receiving data from the driver. The structure 'timeval', that has second
and microsecond valiables for storing the time information, is used in almost
all the calls.

IMPORTANT: Programs calling 'phlib' MUST include the file 'phlib.h'
*/


int fd;
drvdata ddata[MAXPOINTS];	// defined in phdriver.h


boolean open_phoenix (void)
{
/* 
For this call to succeed the driver should be loaded earlier using 'insmod' 
command and the device file '/dev/phoenix' should exist. 
The script 'loading.sh' creates this file.
*/

  fd = open ("/dev/phoenix", O_RDWR);
  if (fd < 0)
    {
      fprintf (stderr, "Phoenix driver open failed\n");
      exit (1);
    }
  return TRUE;
}


int
read_inputs (void)
{
/*
Returns a value from 0 to 15 depends on the voltage levels present at the
four digital input pins of the phoenix box. Returns 255 on error
*/
  int dat;

  if (ioctl (fd, DIGIN, &dat) < 0)
    {
      fprintf (stderr, "DIGIN failed\n");
      return 255;		// error return
    }
  return dat & 15;
}


boolean
write_outputs (int dat)
{
/* Writes a value from 0 to 255 to the eight digital outputs of phoenix
*/
  if (ioctl (fd, DIGOUT, &dat) < 0)
    {
      fprintf (stderr, "DIGOUT failed\n");
      return FALSE;
    }
  return TRUE;
}

boolean
set_dac (int dat)		/* input 0 to 255 */
{
/*
Sets the output of the programmable voltage source. It is an 8 bit DAC that
takes 0 to 255 input and sets output from -5V to +5V.
*/
  if (ioctl (fd, SETDAC, &dat) < 0)
  	{
      fprintf (stderr, "SETDAC failed\n");
      return FALSE;
    }

  return TRUE;
}


boolean
set_voltage (double val)		/* input from -5000 to 5000 mV */
{
/*
Sets the output of the programmable voltage source. It is an 8 bit DAC that
takes 0 to 255 input and sets output from -5V to +5V. This function
is called with argument from -5000 to + 5000 millivolts.
*/
  static const double m = 10000.0 / 255;
  int dat;

  if ((val < -5000.0) || (val > 5000.0))
    return FALSE;
  dat = (int) (128.0 + val / m);
  if (ioctl (fd, SETDAC, &dat) < 0)
    {
      fprintf (stderr, "SETDAC failed\n");
      return FALSE;
    }
  return TRUE;
}

boolean
write_motor (int dat)
{

  if (ioctl (fd, SETMOTOR, &dat) < 0)
    {
      fprintf (stderr, "SETMOTOR failed\n");
      return FALSE;
    }
  return TRUE;
}


boolean
rotate_motor (int nsteps, int dir)
{
  if (dir)
    {
      if (ioctl (fd, MOTORCW, &nsteps) < 0)
	{
	  fprintf (stderr, "MOTORCW failed\n");
	  return FALSE;
	}
    }
  else
    {
      if (ioctl (fd, MOTORCCW, &nsteps) < 0)
	{
	  fprintf (stderr, "MOTORCCW failed\n");
	  return FALSE;
	}
    }
  return TRUE;
}


boolean
select_adc (int chan)
{
/*
Four analog inputs connected the ADC through Sample and Hold circuits.
Any value between 0 to 3 selects corresponding channel during the next 
digitization. 
Adding 4 to the channel number will prevent the Sampling during the next 
digitization and only converts the existing voltage on the HOLD capacitor. 
This is useful for simultanious digitization of multiple channels.
This feature is used internally by the driver and library functions are
provided for multiple channel digitizations.
*/


  if (chan > 7)
    return FALSE;

  if (ioctl (fd, SELECTADC, &chan) < 0)
    {
      fprintf (stderr, "SELECTADC failed\n");
      return FALSE;
    }
  return TRUE;
}

int read_adc (double *ts)		/* returns  0 to 255 */
{
  if (ioctl (fd, READADC, ddata) < 0)
    {
      fprintf (stderr, "READADC failed.\n");
      return -1;
    }
    
  *ts = (double) ddata[0].t.tv_sec + (double) ddata[0].t.tv_usec / 1.0e6;
  return ddata[0].adval[0] & 0xff;
}

double
zero_to_5000 (void)		/* returns  0 to 5000 mV */
{
  static const double m = 5000.0 / 255;
  double ts;
      
  return m * read_adc(&ts);
}

double
minus5000_to_5000 (void)		/* returns  -5000 to 5000 mV */
{
/*
The ADC takes only 0 to 5V. To digitize bipolar signals, -5V to 5V range, they
are connected to the ADC input through the (x+5)/2 amplifier. In that case
use this function that will return a a value from -5000 to 5000 millivolts.
*/

  static const double m = 10000.0 / 255;
  double ts;
  
  return m * read_adc(&ts) - 5000.0;
}

double
minus5000_to_5000TS (double *ts)	/* returns  -5000 to 5000 mV */
{
/*
The ADC takes only 0 to 5V. To digitize bipolar signals, -5V to 5V range, they
are connected to the ADC input through the (x+5)/2 amplifier. In that case
use this function that will return a a value from -5000 to 5000 millivolts.
ts will return the timestamp.
*/

  static const double m = 10000.0 / 255;
  return m * read_adc(ts) - 5000.0;
}


boolean read_block
  (int npoints, int delay, boolean bipolar, double *tstamp, double *adval)
{
/*
Does a specified number of digitizations from the channel selected before
npoints : number of digitizations.(upto MAXPOINTS=1024)
delay   : time interval between two digitizations. minimum is around 115 usec
bipolar	: If TRUE adval elements will be from -5000 to 5000. Else 0 to 5000 mV
tstamp  : array of type double to return the time stamps
adval   : arrayof type double to return adc output
*/
  int k, *ip;
  double strt;

  if (npoints > MAXPOINTS)
    {
    fprintf(stderr, "Number of samples limited to %d\n", MAXPOINTS);
    return FALSE;
    }
  ip = (int *) ddata;
  *ip++ = npoints;
  *ip = delay;
  if (ioctl (fd, READBLOCK, ddata) < 0)
    {
      fprintf (stderr, "READBLOCK failed\n");
      return FALSE;
    }

  strt = (double) ddata[0].t.tv_sec + (double) ddata[0].t.tv_usec / 1.0e6;

  for (k = 0; k < npoints; ++k)
    {
      *tstamp++ = (double) ddata[k].t.tv_sec +
	(double) ddata[k].t.tv_usec / 1.0e6 - strt;

      if (bipolar)
	{
	  double m = 10000.0 / 255;
	  *adval++ = m * (ddata[k].adval[0] & 0xff) - 5000;
	}
      else
	{
	  double m = 5000.0 / 255;
	  *adval++ = m * (ddata[k].adval[0] & 0xff);
	}
    }
  return TRUE;
}


boolean multi_read_block (int npoints, int nchan, int delay,
			double *t, int *a, int *b, int *c, int *d)
{
/*
Does a specified number of digitizations.
npoints : number of digitizations.(upto MAXPOINTS=1024)
nchan	: number of channels to read (1 to 4). Starts with zero only
delay   : time interval between two digitizations. minimum is around 115 usec
t	: double array for timestamp
a,b,c,d : int arrays for data (fills only the first nchan ones)
*/

  int k, *ip;
  double strt;

  if (npoints > MAXPOINTS)
    return FALSE;

  ip = (int *) ddata;
  *ip++ = npoints;
  *ip++ = nchan;
  *ip = delay;
  if (ioctl (fd, MULTIREADBLOCK, ddata) < 0)
    {
      fprintf (stderr, "MULTIREADBLOCK failed\n");
      return FALSE;
    }

  strt = (double) ddata[0].t.tv_sec + (double) ddata[0].t.tv_usec / 1.0e6;

  for (k = 0; k < npoints; ++k)
    {
      t[k] = (double) ddata[k].t.tv_sec + 
      		(double) ddata[k].t.tv_usec / 1.0e6 - strt;

      a[k] = ddata[k].adval[0];
      if(nchan == 1) continue;
      b[k] = ddata[k].adval[1];
      if(nchan == 2) continue;
      c[k] = ddata[k].adval[2];
      if(nchan == 3) continue;
      d[k] = ddata[k].adval[3];
    }
  return TRUE;
}

boolean trig_read_block (int pin, int npoints, int deadtime, int delay,
	boolean trig_pol, double *tstamp, int *adval)
{
/*
Similar to above but digitization is triggered by a falling edge.
pin	: Digital input pin to wait for the falling edge (0 to 3)
npoints : number of digitizations.(upto MAXPOINTS=1024)
deadtime: time interval from the Falling Edge to the first digitization.
delay	: Time interval between samples
bipolar	: If TRUE adval elements will be from -5000 to 5000. Else 0 to 5000 mV

tstamp  : array of type double to return the time stamps
adval   : arrayof type double to return adc output
*/
  int k, *ip;
  double strt;

  if (npoints > MAXPOINTS)
    return FALSE;

  ip = (int *) ddata;
  *ip++ = pin;
  *ip++ = npoints;
  *ip++ = deadtime;
  *ip++ = delay;
  *ip = trig_pol;
  
  if (ioctl (fd, TRIGREADBLOCK, ddata) < 0)
    {
      fprintf (stderr, "TRIGREADBLOCK failed\n");
      return FALSE;
    }

  strt = (double) ddata[0].t.tv_sec + (double) ddata[0].t.tv_usec / 1.0e6;

  for (k = 0; k < npoints; ++k)
    {
    *tstamp++ = (double) ddata[k].t.tv_sec +
    		(double) ddata[k].t.tv_usec / 1.0e6 - strt;
    *adval++ = ddata[k].adval[0] & 0xff;
    }
  return TRUE;
}


//------------------Time difference measurement functions--------------------
double timer(int cmd, int pin1, int pin2)	// internal helper function
{
  double t0, t1;
  unsigned int *ip = (unsigned int *) ddata;

  *ip++ = pin1;
  *ip = pin2;
  if (ioctl (fd, cmd, ddata) < 0)
    {
      fprintf(stderr, "Timer call %d failed on %d to %d\n",cmd, pin1, pin2);
      return -1.0;
    }
  t0 = (double) ddata[0].t.tv_sec * 1.0e6 + (double) ddata[0].t.tv_usec;
  t1 = (double) ddata[1].t.tv_sec * 1.0e6 + (double) ddata[1].t.tv_usec;
  return t1 - t0;
}

double period (int bit)		// returns negative value on error
{
/* Measures the time interval for 10 cycles of the input waveform to
calculate the Period.
*/

  unsigned int *ip = (unsigned int *) ddata;
  double t0, t1;

  *ip++ = bit;
  *ip = 10;		// measure for 10 cycles
  
  if (ioctl (fd, PERIOD, ddata) < 0)	// returns 2 time stamps
    {
      fprintf(stderr, "PERIOD failed\n");
      return -1.0;
    }
  t0 = (double) ddata[0].t.tv_sec * 1.0e6 + (double) ddata[0].t.tv_usec;
  t1 = (double) ddata[1].t.tv_sec * 1.0e6 + (double) ddata[1].t.tv_usec;

//printf("%f %f %f\n", t0,t1, t1-t0);

return (t1 - t0) / 10.0;
}


double r2rtime (int pin1, int pin2)
{
/* 
Returns the time interval between a rising edge on pin1 to a rising edge
on pin2 in microseconds. If same pin is specified it will return 
the interval between two consecutive rising edges.
Returns -1 on error.
*/

return timer(R2RTIME, pin1,pin2);
}


double r2ftime (int pin1, int pin2)
{
/* 
measures the time interval between a rising edge on pin1 to a falling edge
on pin2. If same pin is specified it will return the 'high time' of the 
TTL pulse fed to that pin. Also used for testing the accuracy of the system.
*/

return timer(R2FTIME, pin1,pin2);
}


double f2rtime (int pin1, int pin2)
{
/* 
measures the time interval between a falling edge on pin1 to a rising edge
on pin2. If same pin is specified it will return the 'low time' of the TTL
pulse fed to that pin.
*/

return timer(F2RTIME, pin1,pin2);
}

double f2ftime (int pin1, int pin2)
{
/* 
Returns the time interval between a falling edge on pin1 to a falling edge
on pin2 in microseconds. If same pin is specified it will return 
the time between two consecutive falling edges
*/

return timer(F2FTIME, pin1,pin2);
}


double set2rtime (int pin1, int pin2)
{
/* 
Returns the time interval from setting an output bit to the rising edge 
on an input pin. 
pin1 value: 0 to 7 means the Digital output pins. 8 to 11 => stepper motor 
outputs A,B,C and D
pin2 : Input pins 0 to 4
*/

return timer(SET2RTIME, pin1,pin2);
}


double set2ftime (int pin1, int pin2)
{
/* 
Returns the time interval from setting an output bit to the rising edge 
on an input pin. 
pin1 value: 0 to 7 means the Digital output pins. 8 to 11 => stepper motor 
outputs A,B,C and D
pin2 : Input pins 0 to 4
*/

return timer(SET2FTIME, pin1,pin2);
}


double clr2rtime (int pin1, int pin2)
{
/* 
Returns the time interval from clearing an output bit to the rising edge 
on an input pin. 
pin1 value: 0 to 7 means the Digital output pins. 8 to 11 => stepper motor 
outputs A,B,C and D
pin2 : Input pins 0 to 4
*/

return timer(CLR2RTIME, pin1,pin2);
}


double clr2ftime (int pin1, int pin2)
{
/* 
Returns the time interval from clearing an output bit to the falling edge 
on an input pin. 
pin1 value: 0 to 7 means the Digital output pins. 8 to 11 => stepper motor 
outputs A,B,C and D
pin2 : Input pins 0 to 4
*/

return timer(CLR2FTIME, pin1,pin2);
}

double pulse2rtime (int pin1, int pin2, int width, int deadtime, int pol)
{
/*
Generates a single pulse on pin1 and wait for a rising edge on pin2.
pin1: 0 to 7 for digital outputs, 8 to 11 for stepper motor outputs
pin2: 0 to 3
width: width of the pulse, Assumes the output was zero when called
deadtime: Starts waiting only after elapsing deadtime.
*/
  double t0, t1;
  unsigned int *ip = (unsigned int *) ddata;

  *ip++ = pin1;
  *ip++ = pin2;
  *ip++ = width;
  *ip++ = deadtime;
  *ip = pol;

  if (ioctl (fd, PULSE2RTIME, ddata) < 0)
    {
      fprintf (stderr, "ECHOTIME failed %f\n", -1.0);
      return -1.0;
    }
  t0 = (double) ddata[0].t.tv_sec * 1.0e6 + (double) ddata[0].t.tv_usec;
  t1 = (double) ddata[1].t.tv_sec * 1.0e6 + (double) ddata[1].t.tv_usec;
//  *mid = t0 + (t1-t0)/2.0;
  return t1 - t0;
}

double pulse2ftime (int pin1, int pin2, int width, int deadtime, int pol)
{
/*
Generates a single pulse on pin1 and wait for a Falling edge on pin2.
pin1: 0 to 7 for digital outputs, 8 to 11 for stepper motor outputs
pin2: 0 to 3
width: width of the pulse, Assumes the output was zero when called
deadtime: Starts waiting only after elapsing deadtime.
*/
  double t0, t1;
  unsigned int *ip = (unsigned int *) ddata;

  *ip++ = pin1;
  *ip++ = pin2;
  *ip++ = width;
  *ip++ = deadtime;
  *ip = pol;
  if (ioctl (fd, PULSE2FTIME, ddata) < 0)
    {
      fprintf (stderr, "ECHOTIME failed %f\n", -1.0);
      return -1.0;
    }
  t0 = (double) ddata[0].t.tv_sec * 1.0e6 + (double) ddata[0].t.tv_usec;
  t1 = (double) ddata[1].t.tv_sec * 1.0e6 + (double) ddata[1].t.tv_usec;
//  *mid = t0 + (t1-t0)/2.0;
  return t1 - t0;
}

//----------------Absolute Time marking finctions------------------------

double mid_high_time (int pin)
{
/* 
Returns the time of occurance of a Positive TTL pulse on the specified
digital input. value returned in the number of microseconds elapsed since the
Epoh (see the man page of gettimeofday). This function is used for measuring
time intervals in the order of seconds by subtracting the values from
two calls.
*/
  double t0, t1;
  unsigned int *ip = (unsigned int *) ddata;

  *ip++ = pin;
  *ip = pin;
  if (ioctl (fd, R2FTIME, ddata) < 0)
    {
      fprintf (stderr, "R2FTIME failed\n");
      return -1.0;
    }
  t0 = (double) ddata[0].t.tv_sec * 1.0e6 + (double) ddata[0].t.tv_usec;
  t1 = (double) ddata[1].t.tv_sec * 1.0e6 + (double) ddata[1].t.tv_usec;

  return t0 + (t1 - t0) / 2.0;
}


double mid_low_time (int pin)
{
// Similar to above but looks for a negative TTL pulse.
  double t0, t1;
  unsigned int *ip = (unsigned int *) ddata;

  *ip++ = pin;
  *ip = pin;
  if (ioctl (fd, F2RTIME, ddata) < 0)
    {
//      fprintf(stderr, "F2RTIME failed\n");
      return -1.0;
    }
  t0 = (double) ddata[0].t.tv_sec * 1.0e6 + (double) ddata[0].t.tv_usec;
  t1 = (double) ddata[1].t.tv_sec * 1.0e6 + (double) ddata[1].t.tv_usec;
  return t0 + (t1 - t0) / 2.0;
}

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

double pulse_out (int pin, int high, int low, int np, int pol)
{
/*
generates a pulse on the TTL digital output or the stepper motor output.
pin  : 0 to 7 means the bits on digital output. 8 tp 11 on the Motor outputs.
high : HIGH time of the pulse in usecs
low  : LOW time of the pulse in usecs
np   : number of pulses to be generated
pol  : polarity of the pulse. 0 for +ive TTL 1 for -ive TTL
*/

  double t0, t1;
  unsigned int *ip = (unsigned int *) ddata;

  *ip++ = pin;
  *ip++ = high;
  *ip++ = low;
  *ip++ = np;
  *ip = pol;
  
  if (ioctl (fd, PULSEOUT, ddata) < 0)
    {
      fprintf (stderr, "PULSEOUT failed\n");
      return -1.0;
    }
  t0 = (double) ddata[0].t.tv_sec * 1.0e6 + (double) ddata[0].t.tv_usec;
  t1 = (double) ddata[1].t.tv_sec * 1.0e6 + (double) ddata[1].t.tv_usec;
  return t0;
}


int
set_timeout (unsigned int i)	// value from 5000 to 5000000
{
/*
Time measurement calls wait inside the kernel and freezes the system 
for that period. It is essential to provide maximum limits for waiting.
The default is around 2 seconds. 
This can be set between 5 milli second and 5 second.
*/

  if (ioctl (fd, SETMAXWAIT, &i) < 0)
    {
      fprintf(stderr, "Set Timeout failed for %d usecs\n",i);
      return 0;
    }
  return i;
}

double timestamp(void)	// returns the absolute time
{
  double t0;

  if (ioctl (fd, TIMESTAMP, ddata) < 0)
    {
      fprintf(stderr, "Timestamp: error\n");
      return -1.0;
    }
  t0 = (double) ddata[0].t.tv_sec * 1.0e6 + (double) ddata[0].t.tv_usec;
  printf("%g\n", t0);
return t0;
}

boolean start_hist(void)
{
if(ioctl(fd, START_HIST, NULL) < 0)
	return FALSE;
return TRUE;
}

boolean stop_hist(void)
{
if(ioctl(fd, STOP_HIST, NULL) < 0)
	return FALSE;
return TRUE;
}


boolean clear_hist(void)
{
if(ioctl(fd, CLEAR_HIST, NULL) < 0)
	return FALSE;
return TRUE;
}

boolean read_hist(int* hist)
{
if(ioctl(fd, READ_HIST, hist) < 0) 
	return FALSE;
return TRUE;
}

//	main for tesing purpose only

/*

main()		
{
int	counter, k, hist[4096];

open_phoenix();

start_hist();
sleep(1);
stop_hist();
read_hist(hist);


counter = 0;
for(k = 0; k < 4096; ++k) 
	if(hist[k]) 
		{
		counter += hist[k];
		printf("%d %d\n", k, hist[k]);
		}
printf("counter = %d\n", counter);

}
*/

