/* 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 daqctl, use the appropriate cross compiler and run the
 * command:
 *
 *   gcc -Wall -O -mcpu=arm9 -o daqctl daqctl.c
 *
 * On uclibc based initrd's, the following additional gcc options are
 * necessary: -Wl,--rpath,/slib -Wl,-dynamic-linker,/slib/ld-uClibc.so.0
 */

const char copyright[] = "Copyright (c) Technologic Systems - " __DATE__ ;

#define _GNU_SOURCE

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

int opt_irq = 30, opt_dumpcsv = 0, opt_dumpbin = 0;
unsigned int opt_server = 0, opt_speed_set = 0;
int opt_period_us = 1000, opt_single = 0, opt_stats = 0;
unsigned int opt_gain2x_set = 0, opt_chan_set = 0;
unsigned int opt_flags_set = 0, opt_flags = 0;
int opt_chan = 0xffff, opt_gain2x = 0;
char *opt_connect = NULL, *opt_pwm = NULL;
/* adcbuf_get and adcbuf_put are in units of bytes, not unsigned shorts */
static unsigned int adc7558_next, adcbuf_get, adcbuf_put, adc7558_started;
static unsigned short adcbuf[8192];
static volatile unsigned long long *sbus_shm;

static int do_client(void); 

static void reservemem(void) {
#ifdef __linux__
	char dummy[32768];
	int i, pgsize;
	FILE *maps;

	pgsize = getpagesize();
	mlockall(MCL_CURRENT|MCL_FUTURE);
	for (i = 0; i < sizeof(dummy); i += 4096) {
		dummy[i] = 0;
	}

	maps = fopen("/proc/self/maps", "r"); 
	if (maps == NULL) {
		perror("/proc/self/maps");
		exit(1);
	}
	while (!feof(maps)) {
		size_t s, e, x;
		char perms[16];
		int r = fscanf(maps, "%zx-%zx %s %*x %zx:%*x %*d",
		  &s, &e, perms, &x);
		if (r == EOF) break;
		assert (r == 4);

		while ((r = fgetc(maps)) != '\n') {
			if (r == EOF) break;
		}
		assert(s <= e && (s & 0xfff) == 0);
		if (perms[0] == 'r' && perms[3] == 'p' && x != 1)
		  while (s < e) {
			volatile unsigned char *ptr = (unsigned char *)s;
			unsigned char d;
			d = *ptr;
			if (perms[1] == 'w') *ptr = d;
			s += pgsize;
		}
	}
#endif
}


static int semid = -1;
static int sbuslocked = 0;
static volatile unsigned int *cvspiregs = NULL;
static volatile unsigned int *cvgpioregs = NULL;
static int memwindow_adr, orig_memwindow_adr;
static int last_gpio_adr = -1;
static void sbuslock(void) {
	int r;
	struct sembuf sop;
	if (semid == -1) {
		key_t semkey;
		semkey = 0x75000000;
		semid = semget(semkey, 1, IPC_CREAT|IPC_EXCL|0777);
		if (semid != -1) {
			sop.sem_num = 0;
			sop.sem_op = 1;
			sop.sem_flg = 0;
			r = semop(semid, &sop, 1);
			assert (r != -1);
		} else semid = semget(semkey, 1, 0777);
		assert (semid != -1);
	}
	sop.sem_num = 0;
	sop.sem_op = -1;
	sop.sem_flg = SEM_UNDO;
	r = TEMP_FAILURE_RETRY(semop(semid, &sop, 1));
	assert (r == 0);
	sbuslocked = 1;
	cvgpioregs[0] = (last_gpio_adr<<15|1<<17|1<<3);
	memwindow_adr = orig_memwindow_adr = -1;
}


static void poke16(unsigned int adr, unsigned short dat) {
#ifdef __arm__
	unsigned int dummy = 0, d;
	d = dat;
	if (last_gpio_adr != adr >> 5) {
		last_gpio_adr = adr >> 5;
		cvgpioregs[0] = (cvgpioregs[0] & ~(0x3<<15))|((adr>>5)<<15);
	}
	adr &= 0x1f;

	asm volatile (
		"mov %0, %1, lsl #18\n"
		"orr %0, %0, #0x800000\n"
		"orr %0, %0, %2, lsl #3\n"
		"3: ldr r1, [%3, #0x64]\n"
		"cmp r1, #0x0\n"
		"bne 3b\n"
		"2: str %0, [%3, #0x50]\n"
		"1: ldr r1, [%3, #0x64]\n"
		"cmp r1, #0x0\n"
		"beq 1b\n"
		"ldr %0, [%3, #0x58]\n"
		"ands r1, %0, #0x1\n"
		"moveq %0, #0x0\n"
		"beq 3b\n"
		: "+r"(dummy) : "r"(adr), "r"(d), "r"(cvspiregs) : "r1","cc"
	);
#endif
}


static unsigned short peek16(unsigned int adr) {
#ifdef __arm__
	unsigned short ret = 0;
	if (last_gpio_adr != adr >> 5) {
		last_gpio_adr = adr >> 5;
		cvgpioregs[0] = (cvgpioregs[0] & ~(0x3<<15))|((adr>>5)<<15);
	}
	adr &= 0x1f;

	asm volatile (
		"mov %0, %1, lsl #18\n"
		"2: str %0, [%2, #0x50]\n"
		"1: ldr r1, [%2, #0x64]\n"
		"cmp r1, #0x0\n"
		"beq 1b\n"
		"ldr %0, [%2, #0x58]\n"
		"ands r1, %0, #0x10000\n"
		"bicne %0, %0, #0xff0000\n"
		"moveq %0, #0x0\n"
		"beq 2b\n" 
		: "+r"(ret) : "r"(adr), "r"(cvspiregs) : "r1", "cc"
	);

	return ret;
#else
	return 0;
#endif
}


static inline
void peek16_stream(unsigned int adr, unsigned short *dat, unsigned int len) {
#ifdef __arm__
	unsigned int dummy = 0;
	if (last_gpio_adr != adr >> 5) {
		last_gpio_adr = adr >> 5;
		cvgpioregs[0] = (cvgpioregs[0] & ~(0x3<<15))|((adr>>5)<<15);
	}
	adr &= 0x1f;

	asm volatile(
		"mov %0, #0x0\n"
		"str %0, [%4, #0x4c]\n"
		"mov %1, %1, lsl #18\n"
		"orr %1, %1, #(1<<15)\n"
		"2: str %1, [%4, #0x50]\n"
		"1: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 1b\n"
		"ldr %0, [%4, #0x58]\n"
		"tst %0, #0x10000\n"
		"beq 2b\n"
		"\n"
		"3:\n"
		"strh %0, [%3], #0x2\n"
		"mov %0, #0x80000001\n"
		"orr %0, %0, #0xc00\n"
		"str %0, [%4, #0x40]\n"
		"ldr %0, [%4, #0x40]\n" /* XXX */
		"str %1, [%4, #0x50]\n"
		"str %1, [%4, #0x50]\n"
		"sub %2, %2, #0x4\n"
		"4: str %1, [%4, #0x50]\n"
		"5: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 5b\n"
		"ldr %0, [%4, #0x58]\n"
		"subs %2, %2, #0x1\n"
		"strh %0, [%3], #0x2\n"
		"bne 4b\n"
		"\n"
		"mov %0, #0x4\n"
		"str %0, [%4, #0x4c]\n"
		"mov %1, #0x0\n"
		"str %1, [%4, #0x50]\n"
		"6: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 6b\n"
		"ldr %0, [%4, #0x58]\n"
		"strh %0, [%3], #0x2\n"
		"\n"
		"7: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 7b\n"
		"ldr %0, [%4, #0x58]\n"
		"strh %0, [%3], #0x2\n"
		"\n"
		"8: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 8b\n"
		"ldr %0, [%4, #0x58]\n"
		"strh %0, [%3], #0x2\n"
		"\n"
		"mov %0, #0x80000002\n"
		"orr %0, %0, #0xc00\n"
		"str %0, [%4, #0x40]\n"
		: "+r"(dummy), "+r"(adr), "+r"(len), "+r"(dat)
		: "r"(cvspiregs)
		: "cc", "memory"
	);
#endif
}


static void sbusunlock(void) {
	struct sembuf sop = { 0, 1, SEM_UNDO};
	int r;
	if (!sbuslocked) return;
	if (memwindow_adr != orig_memwindow_adr) 
	  poke16(0x3c, orig_memwindow_adr);
	r = semop(semid, &sop, 1);
	assert (r == 0);
	sbuslocked = 0;
}


int hup = 0;
static void hupsig(int x) {
	hup = 1;
}


static void adcmemread(int start, int len, unsigned short *buf) {
	int adr = 0x2000 + (start * 2);

	if (memwindow_adr == -1) orig_memwindow_adr = 
	  memwindow_adr = peek16(0x3c);
	if (memwindow_adr != adr) poke16(0x3c, adr);
	if (len > 7) {
		peek16_stream(0x3e, buf, len);
	} else while (len--) {
		*buf = peek16(0x3e);
		buf++;
	}
	memwindow_adr = adr + (len * 2);
}

#define ADCPOKE(x, y)	poke16((x) + 0x50, (y))
#define ADCPEEK(x)	peek16((x) + 0x50)

static unsigned int is_cavium(void) __attribute__((pure));
static unsigned int is_cavium(void) {
#ifdef __linux__
	FILE *cpuinfo;
	char buf[4096];
	static unsigned int checked = 0;
	static unsigned int iscavium = 0;

	if (!checked) {
		cpuinfo = fopen("/proc/cpuinfo", "r");
		if (cpuinfo == NULL) {
			perror("/proc/cpuinfo");
			exit(4);
		}
		bzero(buf, 4096);
		fread(buf, 1, 4095, cpuinfo);
		fclose(cpuinfo);
		if (strstr(buf, "FA526")) iscavium = 1;
		checked = 1;
	}
	return iscavium;
#else
	return 0;
#endif
}


static void adc7558_irq(void) {
	int next = -1;
	unsigned short x, y, min;

	if (!adc7558_started) return;

	if (is_cavium()) sbuslock();
	if (!hup) do {
		if (next == -1) {
			next = ADCPEEK(0x2);
			if (next & 0x4000) sbus_shm[1]++;
			next &= 0xfff;
		}
		min = x = (next - adc7558_next) & 0xfff; /* tot remaining */
		y = 0x1000 - adc7558_next; /* contig space in hardware buf */
		if (x > y) min = y;
		x = (0x4000 - adcbuf_put) >> 1; /* contig spc in swbuf */
		if (min > x) min = x;

		/* check total remaining sw buf space */
		if (adcbuf_get == adcbuf_put) y = 0x2000;
		else y = ((adcbuf_get - adcbuf_put) & 0x3fff) >> 1;
		y--;
		if (min > y) min = y;
		if (min == 0) break;

		adcmemread(adc7558_next, min, &adcbuf[adcbuf_put/2]);
		adc7558_next = (adc7558_next + min) & 0xfff;
		adcbuf_put = (adcbuf_put + min*2) & 0x3fff;
	} while (adc7558_next != next);
	if (is_cavium()) sbusunlock();
}


static void adc7558_stop(void) {
	if (!adc7558_started) return;
	if (is_cavium()) sbuslock();
	ADCPOKE(0x2, 0); /* Disable sampling */	

	while(ADCPEEK(0x2) & 0x8000); /* Wait to be done */

	if (is_cavium()) sbusunlock();
	adc7558_started = 0;
	adcbuf_get = adcbuf_put = 0;
}


static
void adc7558_start(int chanspec, int gain2xspec, int period_us, int flags) {
	int interest_mask = 0, r, exttrig;
	adc7558_stop();

	if (flags & (1<<20)) interest_mask |= 0xffe0;
	if (flags & (1<<21)) interest_mask |= 0x3;
	if (flags & (1<<22)) interest_mask |= 0x4;
	if (flags & (1<<23)) interest_mask |= 0x18;
	exttrig = period_us >> 24;
	period_us &= 0xffffff;
	if (is_cavium()) sbuslock();
	ADCPOKE(0x0, chanspec);
	ADCPOKE(0x6, gain2xspec|((flags & 0xf)<<8)|(exttrig<<12));
	r = ADCPEEK(0x8);
	if (interest_mask) {
		r &= ~interest_mask;
		r |= (flags >> 4) & 0xffff;
		ADCPOKE(0x8, r);
	}
	adc7558_next = ADCPEEK(0x2) & 0xfff;
	adcbuf_get = adcbuf_put = 0;
	if (period_us > 32768) ADCPOKE(0x4, 0x8000|(period_us / 1000 - 1));
	else ADCPOKE(0x4, period_us - 1);
	if (r & 0x4) ADCPOKE(0x2, 0xa000);
	else ADCPOKE(0x2, 0x8000); /* start sampling */
	if (is_cavium()) sbusunlock();
	adc7558_started = 1;
}


/* Writes as much of adcbuf as the file descriptor will take.  Returns 1
 * if theres samples yet to send, or 0 if adcbuf is empty, or -1 on 
 * socket error. */
static int adc7558_writefd(int fd, fd_set *fds) {
	unsigned short tot, y, cts;
	char *c = (char *)adcbuf;
	int r;

	assert(fds != NULL);

	if (adcbuf_get == adcbuf_put || fd == -1) { 
		if (fd >= 0) FD_CLR(fd, fds);
		return 0;
	} 

	/* Total remaining to write */
	cts = tot = (adcbuf_put - adcbuf_get) & 0x3fff;
	y = 0x4000 - adcbuf_get; /* contig spc in sw buf */
	if (y < cts) cts = y;
	
writemore:
	r = write(fd, c + adcbuf_get, cts);
	if (r == -1) {
		if (errno != EINTR && errno != EAGAIN) return -1;
		FD_SET(fd, fds);
		return 1;
	} else if (r == tot) {
		adcbuf_get = adcbuf_put = 0;
		FD_CLR(fd, fds);
		return 0;
	} else if (r == cts) {
		cts = tot - cts;
		adcbuf_get = 0;
		goto writemore;
	} else {
		adcbuf_get += r;
		FD_SET(fd, fds);
		return 1;
	}
}


static
int server_running(void) {
	struct sockaddr_in sa;
	int sk, r;

	/* Currently, we just check if we can bind to port 7700 */
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = INADDR_ANY;
	sa.sin_port = htons(7700);
	sk = socket(PF_INET, SOCK_STREAM, 0);
	assert(sk != -1);
	r = bind(sk, (struct sockaddr *)&sa, sizeof(sa));
	close(sk);
	return (r != 0);
}

/* Takes a string for speed/period in the forms of:
 *   "1000"
 *   "1000khz" 
 *   "2.5khz"
 *   "10ms"
 *   "5us"
 *   "100hz"
 *   "1.3s"
 *   ".7khz"
 * 
 * ... and returns period in units of microseconds
 */
static
int parse_period_us(char *spec) {
	int n = 0, f, d = 0;
	int state = 0;
	char *b = spec, *x;

	for (;;) switch(state) {
	case 0: /* building a number in n */
		if (*b == '.') state = 2;
		else if (!isdigit(*b)) return -1;
		else {
			n = strtoul(b, &b, 10);
			state++;
		}
		break;
	case 1:
		if (*b == '.') state++;
		else if (*b == 'h' || *b == 'H' || *b == 0) { /* hz */
			return 1000000000 / (n * 1000 + d);
		} else if (*b == 'k' || *b == 'K') { /* Khz */
			return 1000000 / (n * 1000 + d);
		} else if (*b == 'u' || *b == 'U') { /* us */
			return n;
		} else if (*b == 'm' || *b == 'M') { /* ms */
			return n * 1000 + d;
		} else if (*b == 's' || *b == 'S') { /* seconds */
			return n * 1000000 + d * 1000;
		}
		b++;
		break;
	case 2: /* Parse fraction */
		if (!isdigit(*b)) return -1;
		x = b;
		d = strtoul(b, &b, 10);
		for (f = 1; x != b; x++) f *= 10;
		d = d * 1000 / f; /* d contains thousandths */
		state = 1;
		break;
	}
}


/* Takes a string in the form "0,3,5,8-11,15-10" and returns a bitmap
 * or -1 if theres a parse error.  Can also be a hex number starting with
 * 0x in which case the number is parsed with strtoul() */
static
int parse_chanspec(const char *spec) {
	int bitmap = 0;
	int state = 0;
	int c, n = 0, rangestart = -1;

	if (spec && spec[0] == '0' && spec[1] == 'x') 
	  return strtoul(spec, NULL, 0);
	else if (spec && strcmp("none", spec) == 0)
	  return 0;
	
nextchar:
	c = *spec++;
	if (!isdigit(c) && state == 0) return -1;
	else if (isspace(c) && state == 0) {
		/* skip whitespace */
	} else if (state == 0) {
		n = c - '0';
		state = 1;
	} else if (c == 0 || c == ',') {
		if (rangestart == -1) bitmap |= (1<<n);
		else if (rangestart <= n) bitmap |=
		  ((1<<(n + 1)) - 1) ^ ((1<<rangestart) - 1);
		else bitmap |= ((1<<(rangestart + 1)) - 1) ^ ((1<<n) - 1);
		rangestart = -1;
		state = 0;
	} else if (isdigit(c) && state == 1) {
		n = (n * 10) + c - '0'; 
		if (n > 15) return -1;
		state = 2;
	} else if (isdigit(c) && state == 2) {
		return -1; /* can't have 3 digits in a row */
	} else if (c == '-') {
		rangestart = n;
		state = 0;
	} else return -1;

	if (c) goto nextchar;
	else return bitmap;

}


/* This function should parse strings like such:
 *   "0:33%"  -- Set duty to 33%, preserving frequency
 *   "0:33%@100" -- Set duty to 33%, frequency at 100Hz
 *   "0:33%@1Khz" -- ditto, but 1Khz
 *   "0:0x1230@100" -- 0x123 is the 12-bit duty pct (0x123/0x4096)
 *   "0:2048" -- ditto, but in decimal, preserves freq
 *   "0:50%@EXT0" -- 50% duty, uses external trig #0
 *   "0:33.333%@100" -- should parse decimal also
 *   "0:50%@100us" -- and period specs rather than freq
 *   "0-3:50%" -- can take multiple channels
 *   "0,2,5-6:50%" -- e.g.
 */
static int parse_pwm(char *buf) {
	static int trigsel = 0xff; /* Default to all use internal freq */
	int state = 0;
	int chanspec;
	unsigned int n = 0;
	unsigned int pt_n = 0;
	unsigned int pt_d = 1;
	unsigned int tmp, f = 0, i, l = 0;
	char *b = buf;

	while (*b != ':') {
		b++;
		if (*b == 0) return -1;
	}
	*b = 0;
	chanspec = parse_chanspec(buf);
	if (chanspec == -1) return -1;
	b++;
	
	for (;;) switch(state) {
	case 0: /* building a number in n */
		if (!isdigit(*b)) return -1;
		n = strtoul(b, &b, 0);
		state++;
		break;
	case 1: /* is it %, @, or . */
		if (*b == '%') {
			n = ((0xfff * n * pt_d) + pt_n) / (100 * pt_d);
		} else if (*b == '.' && pt_d == 1) {
			state = 2; /* build up decimal portion */
		} else if (*b == 0) {
			n &= 0xfff;
			goto write_n_preserve_trigsel;
		} else if (*b == '@') state = 5;
		else return -1;
		b++;
		break;
	case 2: /* Look at up to 3 digits past decimal point */
		if (!isdigit(*b)) return -1;
		pt_n = *b - '0';
		pt_d = 10;
		b++;
		if (!isdigit(*b)) state = 1;
		else {
			state++;
			pt_n = (*b - '0') + (pt_n * 10);
			pt_d = 100;
			b++;
		}
		break;
	case 3:
		if (!isdigit(*b)) state = 1;
		else {
			state++;
			pt_n = (*b - '0') + (pt_n * 10);
			pt_d = 1000;
			b++;
		}
		break;
	case 4: /* consume/ignore any remaining digits */
		while (isdigit(*b)) b++;
		if (*b != '%') return -1;
		state = 1;
		break;
	case 5: /* Parse whatever's after the '@' */
		if (isdigit(*b) || *b == '.') {
			tmp = parse_period_us(b);
			if (tmp == -1) return -1;

			n |= (1<<12);
			if (tmp <= 4096) f = (0x7<<13)|(tmp - 1);
			else if (tmp <= 409600) f = (0xf<<12)|(tmp / 100 - 1);
			else f = 0xffff;
			goto write_f;
		} else if (strcmp(b, "EXT0") == 0 || strcmp(b, "#0") == 0) {
			goto write_n;
		} else if (strcmp(b, "EXT1") == 0 || strcmp(b, "#1") == 0) {
			n |= (1<<12);
			goto write_n;
		} else return -1;
		break;
	}

write_f:
	if (is_cavium() && !sbuslocked) {
		sbuslock();
		l = 1;
	}
	ADCPOKE(0xc, f);
write_n:
	if (is_cavium() && !sbuslocked && !l) {
		sbuslock();
		l = 1;
	}
	for (i = 0; i < 7; i++) if (chanspec & (1<<i)) {
		ADCPOKE(0xc, (i<<13)|n);
	}
	trigsel &= ~chanspec;
	if (n & (1<<12)) trigsel |= chanspec;
	if (is_cavium() && l) sbusunlock();
	return 0;

write_n_preserve_trigsel:
	if (!sbuslocked) {
		sbuslock();
		l = 1;
	}
	for (i = 0; i < 7; i++) if (chanspec & (1<<i)) {
		if (trigsel & (1<<i)) tmp = 1; else tmp = 0;
		ADCPOKE(0xc, (i<<13)|(tmp<<12)|n);
	}
	if (l) sbusunlock();
	return 0;
}


static int parseO(char *buf, int len) {
	int i;
	int origlen = len;
	char *b;

parse_another:
	b = buf;
	/* Find terminating NULL? */
	for (i = 0; i < len; i++) if (buf[i] == 0) break;

	if (i != len) { /* Yep, found a 0, ok to parse it... */
		if (buf[0] == 'O') parse_pwm(buf + 1);

		/* Shift leftovers left to the beginning of buf */
		while (i != (len - 1)) *b++ = buf[++i];
		len = b - buf;
		if (len) goto parse_another;
	}
	
	return (origlen - len);
}


static void printsample(unsigned int s) {
	int x = s & 0xfff;
	int chan = s >> 12;
	int uv;

	if (opt_gain2x & (1<<chan)) uv = 1650000ULL * x / 4096; 
	else uv = 3300000ULL * x / 4096;
	printf("chan%d_value=%d\n", chan, x);
	printf("chan%d_microvolts=%d\n", chan, uv);
}


static int printsingle(const unsigned char *buf, int len) {
	static int lsb = 0;
	static int pos = -1;
	unsigned int d;
	
	if (len == 0) return 0;

	if (lsb) {
		d = lsb & 0xff;
		d |= *buf++ << 8;
		if (pos >= (int)(d >> 12)) return 1;
		pos = (d >> 12);
		lsb = 0;
		printsample(d);
		len--;
	}

	while (len >= 2) {
		d = *buf++;
		d |= *buf++ << 8;
		if (pos >= (int)(d >> 12)) return 1;
		pos = (d >> 12);
		printsample(d);
		len -= 2;
	}

	if (len) lsb = 0x8000U|*buf;
	return 0;
}


static void writecsv(const unsigned char *buf, int len) {
	static int lsb = 0;
	static int pos = 0;
	unsigned int d, i;

	if (len == 0) return;

	if (lsb) {
		d = lsb & 0xff;
		d |= *buf++ << 8;
		if (pos >= (d >> 12)) {
			fputc('\n', stdout);
			pos = 0;
		} 
		for (i = pos; i != (d >> 12); i++) fputc(',', stdout);
		pos = i;
		lsb = 0;
		printf("%d", d & 0xfff);
		len--;
	}

	while (len >= 2) {
		d = *buf++;
		d |= *buf++ << 8;
		if (pos >= (d >> 12)) {
			fputc('\n', stdout);
			pos = 0;
		} 
		for (i = pos; i != (d >> 12); i++) fputc(',', stdout);
		pos = i;
		printf("%d", d & 0xfff);
		len -= 2;
	}

	if (len) lsb = 0x8000U|*buf;
}


static const char *usagehelp =
  "Usage: %s [OPTION] ...\n"
  "Technologic Systems ADC-7558 core userspace driver utility.\n"
  "Example: %s --server --speed=2.5khz --chan=0-3,5,7 --gain2x=5\n"
  "\n"
  "  -i, --irq=N            Use IRQ N as ADC IRQ (30)\n"
  "  -q, --stats            Print server daemon stats\n"
  "  -s, --speed=FREQ       Use FREQ as default sample frequency (1000)\n"
  "  -c, --chan=CHANS       Sample only channels CHANS (0-15)\n"
  "  -x, --exttrig=PIN      Override --speed and use external trigger instead\n"
  "  -g, --gain2x=CHANS     Use high-gain setting on CHANS\n"
  "  -l, --cloop=CHANS      Enable 4-20 ma current loops on CHANS\n"
  "  -d, --server           Daemonize and run as server\n"
  "  -I, --bind=IPADDR      Bind server to IPADDR\n"
  "  -p, --connect=HOST     Connect to remote daqctl service at HOST\n" 
  "  -P, --dumpcsv          Output acquired samples as CSV text\n"
  "  -b, --dumpbin          Output acquired samples as raw binary\n"
  "  -1, --single           Print one sample's values\n"
  "  -D, --pwm=PWMSPEC      Sets PWM outputs according to PWMSPEC\n"
  "  -h, --help             This help\n"
  "\n"
  "  --irq-on-change=PIN    Flush when digital input PIN (0-7) changes\n"
  "  --irq-on-glitch=PIN    Flush when digital input PIN (0-7) glitches\n"
  "  --irq-on-high=PIN      Flush while digital input PIN (0-7) is high\n"
  "  --irq-on-low=PIN       Flush while digital input PIN (0-7) is low\n"
  "  --irq-on-quadrature    Flush when a quadrature counter changes value\n"
  "  --irq-on-quad-dir      Flush when quadrature changes direction\n"
  "  --irq-on-counter       Flush when a monitored edge counter changes\n"
  "  --irq-on-any-change    Flush when any digital input changes\n"
  "  --max-irq-rate=NSAM    IRQ at most every NSAM (1,4,16,64) samples\n"
  "  --max-irq-rate-always  Always interrupt at max rate\n" 
  "  --min-irq-rate=HZ      Minimum IRQ rate HZ (1000,500,100,0)\n"
  "  --min-irq-rate-always  Always interrupt at min rate\n"
  "\n"
  "When run as a server, default is to listen at TCP port 7700\n"
  "\n"
  "The --dumpbin option will send 16-bit binary samples to stdout\n"
  "where the 4 MSBs are the channel number and the 12 LSBs is the\n"
  "sample value.  This is the same raw format as the port 7700 TCP\n"
  "socket interface.\n"
  "\n"
  "The PWMSPEC format is CHANS:DUTY%%[@FREQ].  Examples: \"0-3:57%%@100hz\",\n"
  "\"1,3:33.33%%@20ms\", \"1:0x800\" (literal 12-bit), or \"5:5%%@1.81khz\"\n"; 

void usage(char **argv) { fprintf(stderr, usagehelp, argv[0], argv[0]); }


int main(int argc, char **argv) {
	struct sockaddr_in sa;
	struct in_addr opt_bind = { INADDR_ANY };
	int sk = -1, maxfd = 0;
	fd_set rfds, wfds;
	struct timeval tv;
	char fbuf[32], rbuf[32];
	int x, r, c, i = 0, devmem, remfd = -1, irqfd = -1, irqpoll = 0;
	int rbufidx = 0, interest_reg = 0, interest_mask = 0;
	int got_O = 0, eof = 0;
	static int irq_on = 0;
	unsigned int ftmp;
	key_t shmkey;
	int shmid;

	static struct option long_options[] = {
	  { "irq-on-change", 1, &irq_on, 1},
	  { "irq-on-glitch", 1, &irq_on, 2},
	  { "irq-on-high", 1, &irq_on, 3},
	  { "irq-on-low", 1, &irq_on, 4},
	  { "irq-on-quadrature", 0, &irq_on, 5},
	  { "irq-on-quad-dir", 0, &irq_on, 6},
	  { "irq-on-counter", 0, &irq_on, 7},
	  { "irq-on-any-change", 0, &irq_on, 8},
	  { "max-irq-rate", 1, &irq_on, 9},
	  { "min-irq-rate", 1, &irq_on, 10},
	  { "min-irq-rate-always", 0, &irq_on, 11},
	  { "max-irq-rate-always", 0, &irq_on, 12},
	  { "pwm", 1, 0, 'D'},
	  { "exttrig", 1, 0, 'x' },
	  { "bind", 1, 0, 'I' },
	  { "irq", 1, 0, 'i' },
	  { "speed", 1, 0, 's' },
	  { "chan", 1, 0, 'c' },
	  { "gain2x", 1, 0, 'g' },
	  { "cloop", 1, 0, 'l' },
	  { "server", 0, 0, 'd' },
	  { "connect", 1, 0, 'p' },
	  { "dumpcsv", 0, 0, 'P' },
	  { "dumpbin", 0, 0, 'b' },
	  { "single", 0, 0, '1' },
	  { "stats", 0, 0, 'q' },
	  { "help", 0, 0, 'h' },
	  { 0, 0, 0, 0 }
	};

	while((c = getopt_long(argc, argv, "I:i:s:c:g:dp:Ph1bl:x:D:q",
	  long_options, NULL)) != -1) {
		switch (c) {
		case 0:
			if (optarg) i = strtoul(optarg, NULL, 0);
			if (irq_on < 5 && (interest_reg & (1<<11))) {
				fprintf(stderr, "Impossible --irq-on args\n");
				return 3;
			}
			if (irq_on == 1) interest_reg |= (1<<11)|((i&0x7)<<5);
			else if (irq_on == 2) 
			  interest_reg |= (1<<11)|(1<<9)|((i&0x7)<<5);
			else if (irq_on == 3) 
			  interest_reg |= (1<<11)|(2<<9)|((i&0x7)<<5);
			else if (irq_on == 4) 
			  interest_reg |= (1<<11)|(3<<9)|((i&0x7)<<5);
			else if (irq_on == 5) interest_reg |= 1<<12;
			else if (irq_on == 6) interest_reg |= 1<<13;
			else if (irq_on == 7) interest_reg |= 1<<14;
			else if (irq_on == 8) interest_reg |= 1<<15;
			else if (irq_on == 9) {
				interest_mask |= 0x7<<2;
				if (i > 16) interest_reg |= 3<<3;
				else if (i > 4) interest_reg |= 2<<3;
				else if (i > 1) interest_reg |= 1<<3;
			} else if (irq_on == 10) {
				interest_mask |= 0x3;
				if (i > 500) interest_reg |= 1;
				else if (i > 100) interest_reg |= 2;
				else if (i > 0) interest_reg |= 3;
			} else if (irq_on == 11) {
				interest_mask |= 0xf800;
				interest_reg &= 0x7ff;
			} else if (irq_on == 12) {
				interest_reg |= 1<<2;
				interest_mask |= 1<<2;
			}
			if (irq_on <= 8) interest_mask |= 0xffe0;
				
			break;
		case 'q':
			opt_stats = 1;
			break;
		case 'D':
			opt_pwm = strdup(optarg);
			break;
		case 'x':
			i = strtoul(optarg, NULL, 0);
			if (i > 7) {
				fprintf(stderr, "--exttrig opt must be 0-7\n");
				return 3;
			}
			opt_speed_set = 1;
			opt_period_us &= ~0xff000000;
			opt_period_us |= (0x8|i)<<24;
			break;
		case 'I':
			i = inet_aton(optarg, &opt_bind);
			if (i == 0) {
				fprintf(stderr, "Bad arg: %s\n", optarg);
				return 3;
			}
			break;
		case 'p':
			opt_connect = strdup(optarg);
			break;
		case 'd':
			opt_server = 1;
			break;
		case 'i':
			if (strcasestr(optarg, "hz")) irqpoll = 1;
			opt_irq = strtoul(optarg, NULL, 0);
			break;
		case 's':
			ftmp = parse_period_us(optarg);
			if (ftmp == -1) {
				fprintf(stderr, "Bad arg: %s\n", optarg);
				return 3;
			}
			opt_period_us &= ~0xffffff;
			opt_period_us |= ftmp;
			opt_speed_set = 1;
			break;
		case 'l':
			opt_flags = parse_chanspec(optarg);
			if (opt_flags == -1 || opt_flags > 0xff) {
				fprintf(stderr, "Bad arg: %s\n", optarg);
				return 3;
			}
			/* Make is so args of chans 4-7 == 0-3 */	
			opt_flags = (opt_flags >> 4)|(opt_flags & 0xf);
			/* Enable gain2x automatically when current-loop on */
			opt_gain2x |= opt_flags << 4;
			opt_flags_set = opt_gain2x_set = 1;
			break;
		case 'g':
			x = parse_chanspec(optarg);
			if (x == -1 || x > 0xff) {
				fprintf(stderr, "Bad arg: %s\n", optarg);
				return 3;
			}
			opt_gain2x |= x;
			opt_gain2x_set = 1;
			break;
		case 'c':
			opt_chan = parse_chanspec(optarg);
			if (opt_chan <= 0) {
				fprintf(stderr, "Bad arg: %s\n", optarg);
				return 3;
			}
			opt_chan_set = 1;
			break;
		case 'b':
			opt_dumpbin = 1;
			break;
		case 'P':
			opt_dumpcsv = 1;
			break;
		case '1':
			opt_single = 1;
			break;
		default:
			usage(argv);
			return 1;
		}
	}

	if ((opt_single + opt_dumpcsv + opt_dumpbin) > 1) {
		fprintf(stderr, "Can't use multiple output options\n");
		usage(argv);
		return 1;
	}
	
	if (opt_speed_set) {
		fprintf(stderr, "period_us=%d\n", opt_period_us & 0xffffff);
		if (opt_period_us & (1<<27))
		  fprintf(stderr, "ext_trig=%d\n", (opt_period_us>>24) & 0x7);
	}

	if (interest_mask) {
		opt_flags_set = 1;
		opt_flags |= interest_reg<<4;
		/* --irq-on-* options */
		if (interest_mask & (1<<15)) opt_flags |= 1<<20;
		/* --min-irq-rate */
		if (interest_mask & 1) opt_flags |= 1<<21;
		/* --max-irq-rate-alwayss */
		if (interest_mask & (1<<2)) opt_flags |= 1<<22;
		/* --max-irq-rate */
		if (interest_mask & (1<<3)) opt_flags |= 1<<23;
	}

	if (opt_connect) return do_client();
	else if (!opt_connect && server_running()) {
		opt_connect = strdup("127.0.0.1");
		return do_client();
	}

	shmkey = 0x75000000;
	shmid = shmget(shmkey, 0x1000, IPC_CREAT|SHM_R|SHM_W);
	assert(shmid != -1);
	sbus_shm = shmat(shmid, NULL, 0);
	assert (sbus_shm != (void *)-1);
	sbus_shm += 8*32;
	if (getpriority(PRIO_PROCESS, sbus_shm[0]) == -1 &&
	  errno == ESRCH) sbus_shm[0] = 0;

	if (opt_stats) {
		fprintf(stderr, "daqctl_server_pid=%lld\n", sbus_shm[0]);
		fprintf(stderr, "daqctl_overflows=%lld\n", sbus_shm[1]);
		fprintf(stderr, "daqctl_wakeups=%lld\n", sbus_shm[2]);
		return 0;
	}

	if (is_cavium()) reservemem();

	FD_ZERO(&rfds);
	FD_ZERO(&wfds);
	if (!irqpoll) {
		snprintf(fbuf, sizeof(fbuf), "/proc/irq/%d/irq", opt_irq);
		irqpoll = 99999;
	} else {
		fbuf[0] = 0;
		irqpoll = (1000000 / opt_irq) - 1;
	}
	devmem = open("/dev/mem", O_RDWR|O_SYNC);
	if (devmem == -1) {
		perror("/dev/mem");
		return 3;
	}

	if (is_cavium()) {
		cvspiregs = (unsigned int *) mmap(0, 4096,
		  PROT_READ | PROT_WRITE, MAP_SHARED, devmem, 0x71000000);
		cvgpioregs = (unsigned int *) mmap(0, 4096,
		  PROT_READ | PROT_WRITE, MAP_SHARED, devmem, 0x7c000000);

		sbuslock();
		cvgpioregs[0] = (1<<15|1<<17|1<<3);
		cvspiregs[0x64 / 4] = 0x0; /* RX iRQ threahold 0 */
		cvspiregs[0x40 / 4] = 0x80000c02; /* 24-bit mode, no byte swap */
		cvspiregs[0x60 / 4] = 0x0; /* 0 clock inter-transfer delay */
		cvspiregs[0x6c / 4] = 0x0; /* disable interrupts */
		cvspiregs[0x4c / 4] = 0x4; /* deassert CS# */
		for (i = 0; i < 8; i++) cvspiregs[0x58 / 4];
		sbusunlock();
	} else assert(0); /* Unsupported server platform (non-Cavium) */

	if (opt_pwm) {
		r = parse_pwm(opt_pwm);
		if (r == -1) {
			fprintf(stderr, "PWMSPEC parse error\n");
			return 1;
		}
	}

	adc7558_stop();
	if (opt_server) {
		/* create TCP server socket filedescriptor 'sk' */
		sa.sin_family = AF_INET;
		sa.sin_addr.s_addr = opt_bind.s_addr;
		sa.sin_port = htons(7700);
		maxfd = sk = socket(PF_INET, SOCK_STREAM, 0);
		setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &x, 4);
		assert(sk != -1);
		r = bind(sk, (struct sockaddr *)&sa, sizeof(sa));
		if (r) {
			perror("bind");
			return 1;
		}
		r = listen(sk, 1);
		assert(r != -1);
		r = fcntl(sk, F_SETFL, O_NONBLOCK);
		assert(r != -1);
		FD_SET(sk, &rfds);
		daemon(0, 1);
		sbus_shm = shmat(shmid, NULL, 0);
		assert (sbus_shm != (void *)-1);
		sbus_shm += 8*32;
	} else if (opt_dumpbin || opt_dumpcsv || opt_single) {
		adc7558_start(opt_chan, opt_gain2x, opt_period_us, opt_flags);
	} else return 0;
	sbus_shm[0] = getpid();

//	sched.sched_priority = 25;
//	sched_setscheduler(0, SCHED_FIFO, &sched);
/* XXX: TODO signal handling */
	signal(SIGPIPE, SIG_IGN);
	signal(SIGHUP, hupsig);
	signal(SIGCHLD, SIG_IGN);
//	signal(SIGTERM, termsig);
//	signal(SIGINT, termsig);

superloop_start:
	if (!adc7558_started) tv.tv_sec = 9999; else tv.tv_sec = 0;
	if (adc7558_started && hup) { /* If we got a SIGHUP, reinitialize hardware */
		hup = 0;
		adc7558_start(opt_chan, opt_gain2x, opt_period_us, opt_flags);
	}
	if (irqfd >= 0) FD_SET(irqfd, &rfds);
	tv.tv_usec = irqpoll;
	r = select(maxfd + 1, &rfds, &wfds, NULL, &tv);
	if (r == -1 && errno == EINTR) {
		FD_ZERO(&rfds);
		FD_ZERO(&wfds);
	}

	if (remfd != -1 && FD_ISSET(remfd, &rfds)) {
		r = read(remfd, &rbuf[rbufidx], sizeof(rbuf) - rbufidx); 
		if (r <= 0 && errno != EAGAIN && errno != EINTR) {
			eof = 1;
			goto eof;
		} else rbufidx += r;

		if (rbuf[0] == 'O' || got_O) {
			got_O = 1;
			rbufidx -= parseO(rbuf, rbufidx);
			/* If we fill rbuf and can't parse any of it, eof */
			if (rbufidx == sizeof(rbuf)) {
				eof = 1;
				goto eof;
			}
		} else if (rbufidx == sizeof(rbuf) || rbuf[rbufidx - 1] == 0) {
			eof = 1;
			goto eof;
		}
		/* New ADC parameters */
	} else if (remfd != -1) FD_SET(remfd, &rfds);

	adc7558_irq();
		
	if (sk != -1) { /* shovel as much data as possible to remote socket */
		r = adc7558_writefd(remfd, &wfds);
		if (r == -1) {
			eof = 1;
			goto eof;
		}
	} else {
		if (opt_single && 
		  printsingle((unsigned char *)adcbuf, adcbuf_put)) {
			adc7558_stop();
			return 0;
		} else if (opt_dumpbin) fwrite(adcbuf, adcbuf_put, 1, stdout);
		else writecsv((unsigned char *)adcbuf, adcbuf_put);
		adcbuf_put = 0;
		if (feof(stdout) || ferror(stdout)) {
			adc7558_stop();
			return 0;
		}
	}
	sbus_shm[2]++; /* wakeups */

	if (irqfd >= 0 && FD_ISSET(irqfd, &rfds)) {
		FD_CLR(irqfd, &rfds);
		TEMP_FAILURE_RETRY(close(irqfd));
		irqfd = open(fbuf, O_RDONLY|O_SYNC|O_NONBLOCK);
	}

	if (sk == -1) goto superloop_start;

eof:
	/* eof or new connection pending with one already active? */
	if (eof || (FD_ISSET(sk, &rfds) && remfd != -1)) {
		adc7558_stop();
		FD_CLR(remfd, &rfds);
		FD_CLR(remfd, &wfds);
		fcntl(remfd, F_SETFL, 0);
		TEMP_FAILURE_RETRY(close(remfd));
		if (irqfd >= 0) {
			FD_CLR(irqfd, &rfds);
			TEMP_FAILURE_RETRY(close(irqfd));
		}
		irqfd = remfd = -1;
		if (rbufidx) {
			char *x = rbuf;
			rbuf[rbufidx - 1] = 0;
			while (*x) switch (*x) {
			case 'C':
				opt_chan = strtoul(x+1, &x, 0);
				break;
			case 'G':
				opt_gain2x = strtoul(x+1, &x, 0);
				break;
			case 'P':
				opt_period_us = strtoul(x+1, &x, 0);
				break;
			case 'F':
				opt_flags = strtoul(x+1, &x, 0);
				break;
			default: if (isspace(*x)) x++; else *x = 0;
			}
		}
		FD_SET(sk, &rfds);
		eof = got_O = rbufidx = 0;
	}

	/* new connection requests pending accept? */
	if (FD_ISSET(sk, &rfds)) {
		int tos = IPTOS_LOWDELAY;
		int x;
		struct linger l;
		remfd = TEMP_FAILURE_RETRY(accept(sk, NULL, 0));
		if (remfd == -1) {
			FD_SET(sk, &rfds);
			goto superloop_start;
		}
		setsockopt(remfd, IPPROTO_IP, IP_TOS, &tos, 4);
		x = 1;
		setsockopt(remfd, IPPROTO_TCP, TCP_NODELAY, &x, 4);
		setsockopt(remfd, SOL_SOCKET, SO_KEEPALIVE, &x, 4);
		l.l_onoff = 1;
		l.l_linger = 0;
		setsockopt(remfd, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
		fcntl(remfd, F_SETFL, O_NONBLOCK);
		if (remfd > maxfd) maxfd = remfd;
		FD_SET(sk, &rfds);
		FD_SET(remfd, &rfds);
		hup = 0;
		adc7558_start(opt_chan, opt_gain2x, opt_period_us, opt_flags);
		got_O = rbufidx = 0;
		if (fbuf[0] != 0) 
		  irqfd = open(fbuf, O_RDONLY|O_SYNC|O_NONBLOCK);
		if (irqfd > maxfd) maxfd = irqfd;
	} else FD_SET(sk, &rfds);

	goto superloop_start;


	return 0;
}


static int do_client(void) {
	struct sockaddr_in sa;
	struct hostent *he;
	int sk, r, x, tos = IPTOS_LOWDELAY;
	int nr, nw, off;
	char *p;
	char buf[1024];
	struct linger lx;

	/* TCP socket, --connect opt must be in form hostname:port */
	p = opt_connect;
	while (*p != ':' && *p != 0) p++;
	if (*p == 0) p = "7700";
	else {
		*p = 0;
		p++;
	}

	signal(SIGPIPE, SIG_IGN);

	sa.sin_family = AF_INET;
	he = gethostbyname(opt_connect);
	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) {
		perror(opt_connect);
		return 1;
	}

	if (opt_chan_set || opt_gain2x_set || opt_speed_set || opt_flags_set) {
		char cmd[64], *c;
		c = cmd;
		if (opt_chan_set) c += sprintf(c, "C%d ", opt_chan); 
		if (opt_gain2x_set) c += sprintf(c, "G%d ", opt_gain2x); 
		if (opt_speed_set) c += sprintf(c, "P%d ", opt_period_us);
		if (opt_flags_set) c += sprintf(c, "F%d ", opt_flags);

		r = TEMP_FAILURE_RETRY(write(sk, cmd, c - cmd + 1));
		assert(r == (c - cmd + 1));
		close(sk);
		sk = socket(PF_INET, SOCK_STREAM, 0);
		r = connect(sk, (struct sockaddr *)&sa, sizeof(sa));
		setsockopt(sk, IPPROTO_IP, IP_TOS, &tos, 4);
		setsockopt(sk, IPPROTO_TCP, TCP_NODELAY, &x, 4);
	}

	if (r) {
		perror(opt_connect);
		return 1;
	}

	if (opt_pwm) {
		char p[64];
		nw = snprintf(p, sizeof(p), "O%s", opt_pwm) + 1;
		r = write(sk, p, nw);
		assert (nw == r);
	}

	if (opt_single) {
		while ((nr = read(sk, buf, sizeof(buf))) > 0) 
		  if (printsingle((unsigned char *)buf, nr)) break;
	} else if (opt_dumpbin) while ((nr= read(sk, buf, sizeof(buf))) > 0) {
		for (off = 0; nr; nr -= nw, off += nw)
		  if ((nw = write(1, buf + off, nr)) < 0) break;
	} else if (opt_dumpcsv) while ((nr = read(sk, buf, sizeof(buf))) > 0) {
		writecsv((unsigned char *)buf, nr);
		if (feof(stdout) || ferror(stdout)) break;
	}

	close(sk);
	return 0;
}

