/* parledtest.c

 Triangle demo output with 14 s cycle length for 12+1 LEDs on the parallel output.

 Compile with optimisation (-O2).

Rolf Freitag 2005

 */

#include <stdio.h>
#include <errno.h>                                // error codes
#include <sys/io.h>                               // or <asm/io.h>
#include <stdlib.h>                               // exit, atoi
#include <unistd.h>                               // getuid(), usleep
#include <iso646.h>
#include <stdint.h>

union foo
{
  uint32_t u32_;
  uint8_t u8_[4];
};

// case with break because 97 % of the cases do have a preceding break
#   define CASE break;case

// default with break because 97 % of the defaults do have a preceding break
#   define DEFAULT break;default

// set the bits 0 ... bit(n-1) in integral variable x, no checks
// Version for 4 Bit variable to avoid compiler and lint warnings.
#   define mc_SET_BITS_LSB4(x, n) switch (n){ \
  case 0: \
    x=0; \
    CASE 1: \
    x=1; \
    CASE 2: \
    x=3; \
    CASE 3: \
    x=7; \
    CASE 4: \
    x=0xf; \
    DEFAULT: \
    break; \
  }

// set the bits 0 ... bit(n-1) in integral variable x, no checks
// Version for 8 Bit variable to avoid compiler and lint warnings.
#   define mc_SET_BITS_LSB8(x, n) switch (n){ \
  case 0: \
    x=0; \
    CASE 1: \
    x=1; \
    CASE 2: \
    x=3; \
    CASE 3: \
    x=7; \
    CASE 4: \
    x=0xf; \
    CASE 5: \
    x=0x1f; \
    CASE 6: \
    x= 0x3f; \
    CASE 7: \
    x=0x7f; \
    CASE 8: \
    x=0xff; \
    DEFAULT: \
    break; \
  }

// set the bits MSB ... MSB-(n-1) in integral 4-bit variable x, no checks
#   define mc_SET_BITS_MSB4(x, n) switch (n){ \
  case 0: \
    x=0; \
    CASE 1: \
    x=0x8; \
    CASE 2: \
    x=0xc; \
    CASE 3: \
    x=0xe; \
    CASE 4: \
    x=0xf; \
    DEFAULT: \
    break; \
  }

// set the bits MSB ... MSB-(n-1) in integral 8-bit variable x, no checks
#   define mc_SET_BITS_MSB8(x, n) switch (n){ \
  case 0: \
    x=0; \
    CASE 1: \
    x=0x80; \
    CASE 2: \
    x=0xc0; \
    CASE 3: \
    x=0xe0; \
    CASE 4: \
    x=0xf0; \
    CASE 5: \
    x=0xf8; \
    CASE 6: \
    x= 0xfc; \
    CASE 7: \
    x=0xfe; \
    CASE 8: \
    x=0xff; \
    DEFAULT: \
    break; \
  }

int
main (int argc, char *argv[])
{
  unsigned int b=0;                               // parallel port base (e. g. 0x3bc, 0x378=888 or 0x278=632), a wrong value can cause serious damage
  int i;                                          // local counter
  unsigned char uc=0;
  union foo u_foo;

  if (2 != argc)
  {
    printf ("Usage: %s <parallel port base>\n", argv[0]);
    exit (-1);
  }
  b = strtol (argv[1], (char **) NULL, 0);
  if (geteuid () != 0)
  {
    printf ("\a\n\nError: $EUID==%d!=0 (you are not a superuser).\n\n", getuid ());
    exit (-EPERM);
  }
  iopl (3);                                       // Allows access to all I/O-Ports. ioperm doesen't work above the 0x3ff-Limit e. g. at PCI-Cards
  outb (0xb, b + 2);                              // set write mode for the parallel port
  u_foo.u8_[2] = 0xb;
  // start with 0, set BIT0...BIT7 of the data port for 1 s, set PIN0...PIN3 of the control port
  for (;;)
  {
    // up part 1 ... 12
    //    u_foo.u32_ = u32_initial_value;
    for (i = 0; i <= 12; i++)                     // count up loop
    {
      if (i <= 8)                                 // data port
      {
        mc_SET_BITS_LSB8 (uc, i);
        u_foo.u8_[0] = uc;
      }
      else                                        // controll port
      {
        mc_SET_BITS_LSB4 (uc, i - 8);
        // inversions for the crontroll register
        uc xor_eq 0xb;
        u_foo.u8_[2] = uc;
      }
      outl (u_foo.u32_, b);
      sleep (1);
    }
    // down part 11 ... 0
    for (i = 11; i >= 0; i--)                     // count down loop
    {
      if (i >= 4)                                 // data port
      {
        mc_SET_BITS_MSB8 (uc, i - 4);
        //        mc_SET_BITS_LSB8 (uc, i);
        u_foo.u8_[0] = uc;
      }
      else                                        // controll port
      {
        mc_SET_BITS_MSB4 (uc, i);
        // inversions for the crontroll register
        uc xor_eq 0xb;
        u_foo.u8_[2] = uc;
      }
      outl (u_foo.u32_, b);
      sleep (1);                                  // wait one second
    }
  }
  iopl (0);                                       /* release region */
  exit (0);
}
