/* Copyright 2011, Unpublished Work of Technologic Systems
 * All Rights Reserved.
 *
 * THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL,
 * PROPRIETARY AND TRADE SECRET INFORMATION OF TECHNOLOGIC SYSTEMS.
 * ACCESS TO THIS WORK IS RESTRICTED TO (I) TECHNOLOGIC SYSTEMS 
 * EMPLOYEES WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE
 * OF THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN TECHNOLOGIC
 * SYSTEMS WHO HAVE ENTERED INTO APPROPRIATE LICENSE AGREEMENTS.  NO
 * PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED, COPIED, 
 * DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED, CONDENSED, 
 * EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, TRANSFORMED, ADAPTED
 * IN ANY FORM OR BY ANY MEANS, MANUAL, MECHANICAL, CHEMICAL, 
 * ELECTRICAL, ELECTRONIC, OPTICAL, BIOLOGICAL, OR OTHERWISE WITHOUT
 * THE PRIOR WRITTEN PERMISSION AND CONSENT OF TECHNOLOGIC SYSTEMS.
 * ANY USE OR EXPLOITATION OF THIS WORK WITHOUT THE PRIOR WRITTEN
 * CONSENT OF TECHNOLOGIC SYSTEMS COULD SUBJECT THE PERPETRATOR TO
 * CRIMINAL AND CIVIL LIABILITY.
 */

/* To compile the libdaqctl testbench sample code, use the appropriate
 * cross compiler and run the command:
 *
 *   gcc -Wall -O -DTESTBENCH -o libdaqctl-tb libdaqctl.c
 *
 * On uclibc based initrd's, the following additional gcc options are
 * necessary: -Wl,--rpath,/slib -Wl,-dynamic-linker,/slib/ld-uClibc.so.0
 */

#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sched.h>
#include <getopt.h>
#include <termios.h>
#include <ctype.h>
#include <limits.h>

#ifndef TEMP_FAILURE_RETRY
# define TEMP_FAILURE_RETRY(expression) \
  (__extension__                                                              \
    ({ long int __result;                                                     \
       do __result = (long int) (expression);                                 \
       while (__result == -1L && errno == EINTR);                             \
       __result; }))
#endif

/* If hostspec and args are both NULL, connect to the daqctl --server running
 * on localhost and use default settings */
int daqctl_connect(char *hostspec, char *args) {
	struct sockaddr_in sa;
	struct hostent *he;
	int sk, r, x, tos = IPTOS_LOWDELAY;
	char *p;
	char buf[1024];
	char cmd[1024];
	struct linger lx;

	/* hostspec must be in form hostname:port */
	if (hostspec) strncpy(buf, hostspec, sizeof(buf));
	else strcpy(buf, "127.0.0.1");
	buf[sizeof(buf) - 1] = 0;

	if (args) {
		/* daqctl should be in your $PATH! TODO: check for it! */
		snprintf(cmd, sizeof(cmd),
		  "daqctl --connect=%s %s >/dev/null 2>&1", buf, args);
		r = system(cmd);
		assert(r == 0);
	}
	p = buf;
	while (*p != ':' && *p != 0) p++;
	if (*p == 0) p = "7700";
	else {
		*p = 0;
		p++;
	}

	sa.sin_family = AF_INET;
	he = gethostbyname(buf);
	assert(he != NULL);
	memcpy(&sa.sin_addr, he->h_addr_list[0], he->h_length);
	sa.sin_port = htons(strtoul(p, NULL, 0));
	assert(sa.sin_port != 0);
	sk = socket(PF_INET, SOCK_STREAM, 0);
	assert(sk != -1);
	lx.l_onoff = 1;
	lx.l_linger = 15;
	setsockopt(sk, SOL_SOCKET, SO_LINGER, &lx, sizeof(lx));
	r = connect(sk, (struct sockaddr *)&sa, sizeof(sa));
	setsockopt(sk, IPPROTO_IP, IP_TOS, &tos, 4);
	x = 1;
	setsockopt(sk, IPPROTO_TCP, TCP_NODELAY, &x, 4);

	if (r) {
		close(sk);
		return -1;
	} else return sk;
}


/* This takes a PWMSPEC as documented in daqctl --help */
int daqctl_pwmspec(int sk, char *spec) {
	char buf[64];
	int l, r;

	l = snprintf(buf, sizeof(buf), "O%s", spec) + 1;
	r = TEMP_FAILURE_RETRY(write(sk, buf, l));
	if (r != l) return -1; 
	else return 0;
}


/* Max frequency = 131071 hz,  Max duty = 100% (0xfff) */
#define PWM_DUTY_PCT(x)	((0xfff * (x) / 100 ) << 1)
#define PWM_DUTY(x) ((x) << 1)
#define PWM_FREQ(x) ((x) << 13)
#define PWM_FREQ_IS_SAMPLERATE (1<<30)
#define PWM_FREQ_NOT_SAMPLERATE (3<<30)
int daqctl_setoutput(int sk, int pin, unsigned int val) {
	char buf[64], l, r;

	if (val == 0) l = sprintf(buf, "O%d:0", pin);
	else if (val == 1) l = sprintf(buf, "O%d:0xfff", pin);
	else {
		int duty, freq;

		duty = (val & 0x1ffe) >> 1;
		val >>= 13;
	
		freq = val & 0x1ffff; 
		val >>= 17;

		if (freq) l = sprintf(buf, "O%d:0x%x@%dhz", pin, duty, freq);
		else if (val) 
		  l = sprintf(buf, "O%d:0x%x@#%d", pin, duty, val >> 1);
		else l = sprintf(buf, "O%d:0x%x", pin, duty);
	}
	l++;

	r = TEMP_FAILURE_RETRY(write(sk, buf, l));
	if (r != l) return -1;
	else return 0;
}


/* This function is designed to talk to the VFD core inside the PWM core
 * of the whole DAQ core for the TS-7558.  This function abstracts away
 * needing to correctly write all of the registers to get 3phase PWM output.
 * 
 * This function should only be used when the VFD core is active, otherwise
 * it will end up writing incorrect data to normal PWM registers.  There is
 * unfortunately no way to determine if the VFD core is installed and active,
 * use this code wisely.
 *
 * sk - Socket buffer received by daqctl_connect that was called prior
 * mHz - Desired speed in milliHertz, max 127141mHz
 * amp - Amplitude to use, 0x0 to 0xfff
 * reverse - 1 = reverse phases B and C to reverse motor direction, 0 = normal
 *
 * Returns 0 on success, -1 on failure.  This return value is passed down from
 * daqctl_setoutput.
 */
int daqctl_setvfd(int sk, unsigned long mHz, unsigned int amp,
  int reverse) {
	const int step = 155220;
	unsigned long freq;
	int ret = 0;

	if(mHz > 127141) mHz = 127141;

	freq = ((mHz*10000) / step);
	if(freq & 0x1000) {
		freq |= 1<<30;
		freq &= ~0x1000;
	}
	else freq |= 1<<29;
	ret |= daqctl_setoutput(sk, 6, freq<<1);

	amp &= 0xfff;
	if(reverse) amp |= 1<<30;
	else amp |= 1<<29;
	ret |= daqctl_setoutput(sk, 5, amp<<1);
	
	return ret;
}


/* This function requests the server send whatever samples it has up to now */
int daqctl_flushreq(int sk) {
	static char *buf = "O";
	int r;

	r = TEMP_FAILURE_RETRY(write(sk, buf, 2));
	if (r != 2) return -1;
	else return 0;
}


struct daqctl {
	/* The below data members are only safe to read in a callback OR when
	 * daqctl_process() is NOT running. (e.g. not safe to read in a signal
	 * handler or in another thread) 
	 *
	 * last_16bit[8] contains the result of a DSP low-pass IIR filter that
	 * uses multiple readings to attempt to interpolate an extra 4 bits.
	 */
	int fd;
	unsigned long long nsamples;
	long long last[16];
	unsigned short last_16bit[8];
	unsigned long long counter[4];
	long long quad[2];

	/* Callback functions: 
	 *   posedge - called when a posedge is detected on a digital input
	 *   negedge - called when a negedge is detected on a digital input
	 *   analog - called for every acquired analog sample channel
	 *   digital - called on every acquired DIO sampling
	 *   digital_change - called whenever a DIO input change is detected
	 */
	void (*posedge)(struct daqctl *, int);
	void (*negedge)(struct daqctl *, int);
	void (*analog)(struct daqctl *, int, int);
	void (*digital)(struct daqctl *, long long);
	void (*digital_change)(struct daqctl *, long long, long long);

	/* Private -- new instances should all start zeroed-out */
	int seq;
	unsigned int chans;
	unsigned int rem;
	unsigned short b[8];
	int bidx;
};


/* Returns number of 16-bit values processed */
int daqctl_process(struct daqctl *d) {
	unsigned short samplebuf[65536];
	unsigned char *sbuf = (unsigned char *)samplebuf;
	int n, i, j;
	long long llval, x;

	if (d->rem) {
		sbuf[0] = d->rem & 0xff;
		n = TEMP_FAILURE_RETRY(
		  read(d->fd, &sbuf[1], sizeof(samplebuf) - 1));
		if (n <= 0) return n;
		d->rem = 0;
		n++;
	} else {
		n = TEMP_FAILURE_RETRY(
		  read(d->fd, samplebuf, sizeof(samplebuf)));
		if (n <= 0) return n;
	}
	
	if (n & 1) d->rem = 0x100|sbuf[n - 1];
	n >>= 1;

	/* Process samples */
	for (i = 0; i < n; i++) {
		int chan = samplebuf[i] >> 12;
		int val = samplebuf[i] & 0xfff;
		d->chans |= (1<<(chan + 16));
		switch (chan) {
		/* analog channels */
		case 0: case 1: case 2: case 3: 
		case 4: case 5: case 6: case 7:
			d->last_16bit[chan] = (d->last_16bit[chan] - 
			  (d->last_16bit[chan] >> 4)) + val;
			d->last[chan] = val;
			if (d->analog) d->analog(d, chan, val);
			break;
		/* quadrature channels */
		case 8: case 9:
			d->last[chan] = val;
			d->quad[chan - 8] += (short)(val << 4) >> 4;
			break;
		/* digital input */
		case 10:
			d->b[d->bidx++ & 0x7] = val;
			d->bidx &= 0x7;
			if (!(val & (1<<11))) {
				llval = 0;
				for (j = 0; j < 6 && j < d->bidx; j++) 
				  llval |= (d->b[j] & 0x7ffLL)<<(11 * j);	
				d->bidx = 0;
				x = d->last[10];
				d->last[10] = llval;
				for (j = 0; j < 62; j++) 
				  if (((llval ^ x) >> j) & 0x1) {
					if (((llval >> j) & 0x1) && d->posedge) 
					  d->posedge(d, j);
					else if (d->negedge) d->negedge(d, j);
				}
				if (d->digital) d->digital(d, llval);
	
				/* Only call digital_change() if not sampling 
				 * chan #15 (digital in glitch detect chan) */
				if (d->digital_change && x != llval && 
				  !(d->chans & (1<<15))) 
				    d->digital_change(d, x, x ^ llval);
			} 
			break;
		/* posedge counter channels */
		case 11: case 12: case 13: case 14:
			d->last[chan] = val;
			d->counter[chan - 11] += val;
			break;
		case 15:
			/* XXX: If external trigger is used, this isn't
			 * sampling digital change anymore. */
			d->b[d->bidx++ & 0x7] = val;
			d->bidx &= 0x7;
			if (!(val & (1<<11))) {
				llval = 0;
				for (j = 0; j < 6 && j < d->bidx; j++) 
				  llval |= (d->b[j] & 0x7ffLL)<<(11 * j);	
				d->bidx = 0;
				d->last[15] = llval;
				if (!(d->chans & (1<<26))) d->last[10] = -1;
				if (d->digital_change && llval) 
				  d->digital_change(d, d->last[10], llval);
			}
		}
		if (d->seq >= chan) {
			d->nsamples++;
			d->chans >>= 16;
		}
		d->seq = chan;
	}

	return n;
};

#ifdef TESTBENCH
void negedge(struct daqctl *dq, int pin) {
	printf("%d: negedge on pin %d\n", (int)dq->nsamples, pin);
}
void posedge(struct daqctl *dq, int pin) {
	printf("%d: posedge on pin %d\n", (int)dq->nsamples, pin);
}
void analog(struct daqctl *dq, int chan, int val) {
	printf("%d: analog val %x (16bit: %x) on chan %d\n", 
	  (int)dq->nsamples, val, dq->last_16bit[chan], chan);
}
void digital(struct daqctl *dq, long long val) {
	printf("%d: digital val %llx\n", (int)dq->nsamples, val);
}
void digital_change(struct daqctl *dq, long long val, long long chg) {
	printf("%d: digital change %llx\n", (int)dq->nsamples, chg);
}
static int alarmed;
void alarmsig(int x) { alarmed = 1; };

/* Sample client app */
int main(void) {
	int sk;
	struct daqctl dq;
	int n, o = 0; 

	/* The following daqctl_connect() call says to connect to the daqctl 
	 * server running on on localhost, returning a file descriptor for 
	 * reading samples, and:
	 *
	 *   - Sample the 4 external ADC inputs AD1-AD4 (chans 4-7).  Also 
	 *     include sampling of the 8 digital inputs IN1-IN8 (chan 10) and
	 *     edge counters (chans 11-14) 
	 *     [--chans=4-7,10-14]
	 *
	 *   - Turn on 2x gain setting (0-5V range instead of 0-10V) on 
	 *     channel 4 (external AD1) [--gain2x=4]
	 *
	 *   - Turn on the 4-20ma current-loop circuitry on external AD3 and
	 *     AD4 [--cloop=2-3]
	 *
	 *   - Sample each of the selected channels at a 5Khz sample rate.
	 *     [--speed=5khz]
	 *
	 *   - Force an IRQ and resultant sample pipe flush after a sample
	 *     is recorded and any of the digital inputs (IN1-IN8) changed.  
	 *     When this IRQ occurs, the applications read() will immediately
	 *     unblock and return.  The last entry in the returned sample buf
	 *     should contain the sample taken at the time the IRQ was tripped.
	 *     [--irq-on-any-change]
	 *
	 *   - If the digitals are repeatedly changing, do not interrupt on
	 *     every sample, instead interrupt at most every 4 samples. 
	 *     [--max-irq-rate=4]
	 *
	 *   - Even if the digital inputs are not changing, keep the interrupt
	 *     rate at around 500Hz such that samples returned from a
	 *     read() are no older than 2ms (period of 500HZ) 
	 *     [--min-irq-rate=500hz]
	 *
	 *   - Turn all 7 PWM channels (0-6) off (0% duty) (OUT1-OUT4 external 
	 *     and pins 23, 21, and 25 on the JTAG header) and set default PWM
	 *     frequency to 100hz [--pwm=0-6:0%@100hz]
	 */
	sk = daqctl_connect("127.0.0.1", 
	  "--chan=4-7,10-14 --gain2x=4 --cloop=2-3 --speed=5khz"
	  " --irq-on-any-change --max-irq-rate=4 --min-irq-rate=500hz"
	  " --pwm=0-6:0%@100hz");
	assert(sk != -1);
	
	bzero(&dq, sizeof(dq));
	dq.fd = sk;
	dq.negedge = negedge;
	dq.posedge = posedge;
	dq.analog = analog;
	dq.digital = digital;
	dq.digital_change = digital_change;
	alarm(10);
	signal(SIGALRM, alarmsig);

	for (;!alarmed;) {
		n = daqctl_process(&dq);
		
		if (n <= 0) {
			fprintf(stderr, "EOF!\n");
			break;
		}

		/* Set PWM output #3 (OUT3) according to last sample of 
		 * chan #4 (AD1) */
		daqctl_setoutput(sk, 3, PWM_DUTY(dq.last[4]));

		/* Set PWM output #0 according DIO input #0 */
		if ((dq.last[10] & 0x1) != o) {
			o = dq.last[10] & 0x1;
			if (o) daqctl_setoutput(sk, 0, 1); /* 100% */
			else daqctl_setoutput(sk, 0, 0); /* 0% */
		}

		/* If counter for input #0 is greater than 10, set output
		 * 1 and reset counter.  If counter for input #1 is greater
		 * than 20, reset both counters and clear output 1 */
		if (dq.counter[0] > 10) { /* read and reset counter #0 */
			dq.counter[0] = 0;
			daqctl_setoutput(sk, 1, 1);
		}
		if (dq.counter[1] > 20) { 
			dq.counter[0] = dq.counter[1] = 0;
			daqctl_setoutput(sk, 1, 0);
		}
	}

	close(sk);
	printf("nsamples: %lld in 10 seconds\n", dq.nsamples);
	return 0;
}
#endif
