#define _BSD_SOURCE
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

/* Some of the concepts here taken from src/sys/i386/i386/elan-mmcr.c */

#define DURATION(a, b) ((b.tv_sec - a.tv_sec) * 1000000) + (b.tv_usec - a.tv_usec)
#define ON 1
#define OFF 0

static uint16_t *mmcr;

/* Turn pins high or low */
void led(u_int pin, int state) {
  int offset = 0;
  u_int val = 0xc34;

  if (pin > 16)
    offset = 2;

  /* Turn the pin number into a bit */
  pin = 1 << (pin & 0xf);

  if (state == 0)
    val ^= 0xc;

  /* Why do we bother dividing by 2? Not sure :( */
  mmcr[(val + offset) / 2] = pin;
}

/* usleep(3) and select(2) are unreliable. Let's spin wait instead */
void waittime(long duration) {
  struct timeval start;
  struct timeval now;
  gettimeofday(&start, NULL);

  for (gettimeofday(&now, NULL); DURATION(start, now) < duration; gettimeofday(&now, NULL)) ;
}

int main() {
  int fd;
  int state = ON;

  fd = open("/dev/elan-mmcr", O_RDWR);
  if (fd < 0)
    err(1, "/dev/elan-mmcr");
  mmcr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

  int count = 0;
  int interval = 10000; // 10ms interval, in theory...
  int duty_cycle = 0; // duty cycle is how long the 'on' state should be.
                      // Same units as interval (microseconds)

  int dir = 1;
  
  for (count = 0; ; count++) {
    duty_cycle += 100 * dir; /* duty cycle stepping of 100usecs */

    if (duty_cycle >= interval) {
      duty_cycle = interval;
      dir = -1;
    } 
    if (duty_cycle <= 0) {
      duty_cycle = 0;
      dir = 1;
    }

    state = ON;
    led(5, state); // led 5 == gpio5 == PIO1?
    led(9, state); // led 9 == error led
    waittime(interval - (interval - duty_cycle));

    state = OFF;
    led(5, state);
    led(9, state);
    waittime(interval - duty_cycle);
  }

  return 0;
}
