#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>

/*************************************************************

adcread -- TS-7350 utility for data acquisition from TS-ADC16
Reads data from all 16 channels.  Acquisition from 1 to 100 KHz.
Example usage:

adcread 50000 50 > datafile

will take a total of 50000 readings at 50KHz and store the raw
data in 'datafile'.  For KHz value above 100, the TS-ADC16 will
sample as fast as possible.  If there is a FIFO overflow, the
program will stop sampling and report this, otherwise it will
report success.

This code assumes JP3 is installed on the TS-ADC16.
JP1 and JP2 should be set as #defined below.

Planned improvements:
- convert to Unix utility style command line arguments
- expand to work with TS-ADC24
- adjust ISA bus speed
- add option to sample fewer channels
- add option for human readable output
- add option to filter out unwanted channel data
- add interrupt handling to reduce CPU usage
- minor performance tweaks

Compiled with:
gcc -Wall -mcpu=arm9 adcread.c -o adcread

*************************************************************/

/* PC/104 IO space for TS-7350: */
#define IO_BASE     0x600FF000
#define IO_OFFSET   0x800

/* jumper off = 0, jumper on = 1 */
#define JP1 1
#define JP2 0
#define ADC16_OFFSET (0x100 + (0x20 * JP1) + (0x40 * JP2))

#define ADC(x) (*(adc + (x)/sizeof(unsigned short)))
#define CONFIG(x) (*(config + (x)/sizeof(unsigned short)))

int main(int argc, char **argv) {

  volatile unsigned short *adc, *config;
  unsigned int pacing, samples, khz;
  unsigned short adccfg, result, ffcount;
  
  if (argc != 3) {
    fprintf(stderr, "Usage: adcread <samples> <KHz> > datafile\n");
    return 1;
  }
  
  samples = strtoul(argv[1], 0, 0);
  khz = strtoul(argv[2], 0, 0);

  int fd = open("/dev/mem", O_RDWR|O_SYNC);
  adc = (unsigned short *)mmap(0, getpagesize(),
    PROT_READ|PROT_WRITE, MAP_SHARED, fd, IO_BASE);
  
  config = adc;
    
  adc += (IO_OFFSET + ADC16_OFFSET)/sizeof(unsigned short);
  
  /* Configure PC/104 connector for PC/104: */
  CONFIG(0x98) = 0x5555;
  CONFIG(0x9a) = 0x5555;
  CONFIG(0x9c) = 0x5555;
  CONFIG(0x9e) = 0x5555;
  /* Configure ISA bus for 16 bit ARM mode: */
  CONFIG(0xd4) |= 0x0800;
  
  /* Sanity check: confirm board id: */
  if ((ADC(0) & 0xff) != 0x3e) {
    fprintf(stderr, "TS-ADC16 not found. Check jumper settings.\n");
    close(fd);
    return 1;
  }
  
  /* Initialization: */
  adccfg = 0x16e; /* sample all channels, 0-5V, no irqs */
  ADC(2) = 0; /* change this register to reset ADC16 */
  pacing = 32000/khz;
  if (pacing < 0x140) pacing = 0;
  ADC(6) = pacing & 0xffff;
  ADC(4) = pacing >> 16;
  ADC(2) = adccfg;
  fprintf(stderr, "samples=%d\n", samples);
  fprintf(stderr, "pacing=0x%x\n", pacing);
  
  fprintf(stderr, "adccfg=0x%x\n", ADC(2));
  /* Start ADC machine: */
  ADC(2) = adccfg | 1;

  while (samples > 0) {
    ffcount = ADC(8) >> 6;
//    fprintf(stderr, "ffcount=0x%x\n", ffcount);
    if ((ffcount >= samples) && (ADC(2) & 1)) {
      ADC(2) = adccfg & ~1; /* stop adc machine */
      fprintf(stderr, "Machine stopped normally.\n");
    } else if (ffcount > 0) {
      while ((samples > 0) && (ffcount > 0)) {
        /* take a sample: */
        result = ADC(0xA);
        putchar(result & 0xff);
        putchar(result >> 8);
        samples--;
        ffcount--;
      }
    } else if (!(ADC(2) & 1)) {
      ffcount = ADC(8) >> 6; /* catering to a case that is almost impossible */
      if (samples > ffcount) {
        fprintf(stderr, "ADC machine stopped prematurely due to FIFO overflow.\n");
        samples = ffcount; /* reduce total samples to overflow */
      }
    }
  }

  close(fd);
  return 0;
}

