/* parda2.c: A simple linux user space program for the simple DA-converter
            as a function generator via the Parallel Port and 8 330 Ohm resitors
            between every data pin and the output (= 3 1/2 -bit DAC, short-circuit-proof).
            This is the continuation of parda.c but with a second DA-converter at
            control pins (4x 330 Ohm).

Compile with optimisation (-O2).

Parameter: I/O-Address (usually 888 for standard parallel port),
           Byte of the data register (pin pattern on output),
           duration of a high/low period at the data pins in µs,
           Byte of the control register (pin pattern on output),
           duration of a high/low period at the control pins in µs.
           Time in µs between two samples.

Example (for root):
./parda2 888 255 20000 15 169000 0
(106 RPM, 175 PPM)

The bit patter of the output determins the amplitude: 8 bits (255) means 100 %; 0 means
0 %, 4 bits (e. g. 15) means 50 % etc. (at the 8 data pins).

Measured frequency with Linux version 2.6.8-24.11-smp (SuSE 9.2) and the onboard port:
delay     frequency/Hz (onboard port)
10000000  0.05
1000000   0.4988
248000    2.00
100000    4.9
10000     41,6
1000      166
1 ... 100 249,9
0         499,9
0 without udelay approx. 500 kHz

The same was measured with a parallel port PCI card.

For higher frequencies you should use a kernel module which should have a limit of
approx. 100 kHz instead of 500 Hz.

Todo:
Check of the parameters + help, a function for triangle wave output with the
output sequence 0 <delay> 1 <delay> 3 <delay> 7 <delay> 15 <delay> 31 <delay> 63
<delay> 127 <delay> 255 <delay> 254 <delay> 252 <delay> 248 <delay> 240 <delay> 224
<delay> 192 <delay> 128 <delay> 0 ..., a function for sine wave output, ...,
get address from /proc/bus/pci from first device with vendor id 0x9710 and device id 0x9805.
Change usleep -> select (more portable).
Two edge triggered frequency measurement.
RTAI Version.

Rolf Freitag May 2005, rolf.freitag at web.de

 */

#include <stdio.h>
#include <sys/io.h>                               /* or asm/io.h; for inb, ioperm, iopl  ... */
#include <stdlib.h>                               /* atoi */
#include <unistd.h>                               // getuid(), usleep
#include <errno.h>                                // EPERM
#include <iso646.h>
#include <signal.h>                               // signals
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

static int value2;                                // bit pattern for the control pins (0 = off, 15 = all high)
static int delay2;                                // duration of a pulse/leap in µs for the control output
static int delay3;                                // duration of a sample
static int base;                                  // base address of the parallel port

// time(NULL) with microsecond resolution
inline signed long long int
get_time ()
{
  struct timeval ti;
  struct timezone tzp;

  gettimeofday (&ti, &tzp);
  return (ti.tv_usec + 1000000 * ((long long int) ti.tv_sec));
}


void
sig_handler_main (const int sig)                  // signal handler for main
{
  if ((SIGINT == sig) or (SIGILL == sig) or (SIGKILL == sig) or (SIGSEGV == sig) or (SIGTERM == sig))
  {
    (void) printf ("Signal %d, program exiting... \r\n", sig);
    exit (-sig);
  }
  iopl (0);                                       /* no I/O permission */
  return;
}


void
sig_handler0 (const int sig)                      // signal handler for the control pin thread
{
  static int retval;

  retval = -sig;
  if (SIGUSR1 == sig)
  {
    pthread_exit ((void *) &retval);
  }
  if ((SIGINT == sig) or (SIGILL == sig) or (SIGKILL == sig) or (SIGSEGV == sig) or (SIGTERM == sig))
  {
    (void) printf ("Signal %d, program exiting... \r\n", sig);
    exit (retval);
  }
  return;
}


// control pins thread
void *
func_control (void *threadid)
{
  int i;
  static int retval;                              // Without static the returned retval would always be zero.

  retval = 0;
  for (i = 0; i <= 0xff; i++)
    signal (i, sig_handler0);
  // control port at base+2: only bits 0-3 are used, bits 0, 1, and 3 (sum 1+2+8=11) are inverted
  value2 xor_eq 11;                               // inversion of bits 0, 1 and 3
  value2 and_eq 0xf;                              // bit mask for bits 0-3, this also sets irq enable to off and the data pins to output
  // printf("toggling thread started, output pattern %x\n", value2);
  // loop for rectangle pulses from the control pins
  if (delay2 > 0)
    for (;;)                                      // endless loop with < 1 % CPU load
  {
    outb (value2, base + 2);
    usleep (delay2);
    outb (11, base + 2);                          // set all controll pins to low
    usleep (delay2);
  }
  else
    for (;;)                                      // approx. 200 kHz Hz
  {
    outb (value2, base + 2);
    outb (11, base);                              // set all controll pins to low
  }
  pthread_exit ((void *) &retval);
}


// status pins thread
void *
func_status (void *threadid)
{
  int i, lastbit = 0, i_last_output_time = 0, sum0 = 0, sum1 = 0;
  static int retval;                              // Without static the returned retval would always be zero.
  // double sum = 0.5;  // sum for pwm
                                                  //  average cyclus time in µs
  long long int counter, lli_last_counter = 0, last_time = 0, act_time = 0, lli_last_pulses = 0, lli_pulses = 0, lli_cyclt = 0;

  retval = 0;
  for (i = 0; i <= 0xff; i++)
    signal (i, sig_handler0);
  for (counter = 1;; counter++)                   // read forever
  {
    // evaluate only pin 15, bit 3 for statistical PWM estimation
    i = inb (base + 1);
    if (0 == counter)
      lastbit = i bitand 0x08;
    if (i bitand 0x08)                            // high
    {
      if (0 == lastbit)                           // rising edge detected, next periode
      {
        act_time = get_time (NULL);
        if (last_time)                            // after the start
        {
          lli_cyclt += act_time - last_time;      // cycle time
          lli_pulses++;                           // valid pulse
        }
        last_time = act_time;
      }
      sum1++;
      lastbit = 1;
    }
    else
    {
      lastbit = 0;
      sum0++;
    }
    if ((i = time (NULL)) - i_last_output_time)   // every second
    {
      if (i_last_output_time)
      {
        printf ("Input at Pin 13: Duty cycle: %f +- %f, Frequency: %f Hz, Sample frequency: %lld Hz.\n",
          sum1 / ((double) (sum0 + sum1)), 1. / (sum1 ? sum1 : sum0),
          1000000 * (lli_pulses - lli_last_pulses) / ((double) lli_cyclt), counter - lli_last_counter);
        //printf ("cycletime: %lld, counts: %lld \n", lli_cyclt, lli_pulses - lli_last_pulses);
      }
      else
      {
        i_last_output_time = i;
        while (i_last_output_time == time (NULL));// go to the beginning of the next second
        i = time (NULL);
      }
      i_last_output_time = i;
      lli_last_counter = counter;
      lli_last_pulses = lli_pulses;
      lli_cyclt = 0;
      sum0 = 0;
      sum1 = 0;
    }
    if (delay3 > 0)                               // without waiting: approx. 200 kHz, 99 % CPU load
      usleep (delay3);
  }
  pthread_exit ((void *) &retval);
}


int
main (int argc, char *argv[])                     /* Caution: A wrong parameter value can cause serious damage! */
{
  int value;                                      // bit pattern for the data pins (0 = off, 255 = all high)
  int delay;                                      // duration of a pulse/leap in µs
  static pthread_attr_t attr0, attr1;
  // static void *statusp;         // for pthread_return
  static pthread_t thread0, thread1;
  static int i, id0, id1, i_ret;

  if (7 != argc)
  {
    printf ("Usage: %s <parallel port base> <pin pattern for output> <duration of a high/low period at the data pins in µs>\n", argv[0]);
    printf ("<pit pattern for the control register for output> <duration of a high/low period at the control pins in µs>\n");
    printf ("<Time in µs between two samples>\n");
    exit (-1);
  }
  base = strtol (argv[1],  (char **)NULL, 0);
  value = strtol (argv[2],  (char **)NULL, 0);
  delay = strtol (argv[3], (char **) NULL, 0);
  value2 = strtol (argv[4],  (char **)NULL, 0);
  delay2 = strtol (argv[5],  (char **)NULL, 0);
  delay3 = strtol (argv[6],  (char **)NULL, 0);
  if (geteuid () != 0)
  {
    printf ("\a\n\nError: $EUID==%d!=0 (you are not a superuser, port access would be denied from the kernel), exiting.\n\n", getuid ());
    exit (-EPERM);
  }
  for (i = 0; i <= 0xff; i++)
    signal (i, sig_handler_main);

  iopl (3);                                       /* unlimited I/O access permission, nessesary above the 0x3ff limit e. g. at 0x9800=38912 */
  outb (0x0b, base + 2);                          /* write mode */

  if ((i_ret = pthread_create (&thread0, &attr0, func_control, (void *) &id0)))
  {
    printf ("ERROR; return code from pthread_create(...) is %d\n", i_ret);
    perror ("pthread_create(...)\n");
    exit (-1);
  }
  if ((i_ret = pthread_create (&thread1, &attr1, func_status, (void *) &id1)))
  {
    printf ("ERROR; return code from pthread_create(...) is %d\n", i_ret);
    perror ("pthread_create(...)\n");
    exit (-1);
  }
  // loop for rectangle pulses from the data pins. Write the byte direct to the data port.
  if (delay > 0)
    for (;;)                                      // endless loop with < 1 % CPU load
  {
    outb (value, base);
    usleep (delay);
    outb (0, base);
    usleep (delay);
  }
  else
    for (;;)                                      // endless loop with < 99 % CPU load at approx. 200 kHz
  {
    outb (value, base);
    outb (0, base);
  }
  //pthread_kill (thread0, SIGUSR1);
  //(void) pthread_join (thread0, &statusp);
  iopl (0);                                       /* no I/O permission */
  exit (i_ret);
};

