/* opcd_test - Low-Level test program for OpenPCD
 * (C) 2006 by Harald Welte <laforge@gnumonks.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 
 *  as published by the Free Software Foundation
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define _GNU_SOURCE
#include <getopt.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

#include <usb.h>

#include <openpcd.h>
#include "opcd_usb.h"

static int get_number(const char *optarg, unsigned int min,
		      unsigned int max, unsigned int *num)
{
	char *endptr;
	unsigned long nbr = strtoul(optarg, &endptr, 0);
	//fprintf(stderr, "trying to read `%s' as number\n", optarg);

	if (nbr == 0 && optarg == endptr)
		return -EINVAL;

	if (nbr < min || nbr > max)
		return -ERANGE;

	*num = nbr;
	return 0;
}

static void print_welcome(void)
{
	printf("opcd_test - OpenPCD Test and Debug Program\n"
	       "(C) 2006 by Harald Welte <laforge@gnumonks.org>\n\n");
}
static void print_help(void)
{
	printf( "\t-l\t--led-set\tled {0,1}\n"
		"\t-w\t--reg-write\treg value\n"
		"\t-r\t--reg-read\treg\n"

		"\t-W\t--fifo-write\thex\n"
		"\t-R\t--fifo-read\thex\n"

		"\t-s\t--set-bits\treg\tmask\n"
		"\t-c\t--clear-bits\treg\tmask\n"

		"\t-u\t--usb-perf\txfer_size\n"
		);
}


static struct option opts[] = {
	{ "led-set", 1, 0, 'l' },
	{ "reg-write", 1, 0, 'w' },
	{ "reg-read", 1, 0, 'r' },
	{ "fifo-write", 1, 0, 'W' },
	{ "fifo-read", 1, 0, 'R' },
	{ "set-bits", 1, 0, 's' },
	{ "clear-bits", 1, 0, 'c' },
	{ "usb-perf", 1, 0, 'u' },
	{ "adc-read", 0, 0, 'a' },
	{ "adc-loop", 0, 0, 'A' },
	{ "ssc-read", 0, 0, 'S' },
	{ "loop", 0, 0, 'L' },
	{ "serial-number", 0, 0, 'n' },
	{ "help", 0, 0, 'h'},
};	

int main(int argc, char **argv)
{
	struct opcd_handle *od;
	int c, outfd, retlen;
	char *data;
	static char buf[8192];
	int buf_len = sizeof(buf);

	print_welcome();

	if (!strcmp(argv[0], "./opicc_test"))
		od = opcd_init(1);
	else
		od = opcd_init(0);

	while (1) {
		int option_index = 0;

		c = getopt_long(argc, argv, "l:r:w:R:W:s:c:h?u:aASLn", opts,
				&option_index);

		if (c == -1)
			break;

		switch (c) {
			unsigned int i,j;
		case 'l':
			if (get_number(optarg, 1, 2, &i) < 0)
				exit(2);
			if (get_number(argv[optind], 0, 1, &j) < 0)
				exit(2);
			printf("setting LED %d to %s\n", i, j ? "on" : "off");
			opcd_send_command(od, OPENPCD_CMD_SET_LED, i, j, 0, NULL);
			break;
		case 'r':
			if (get_number(optarg, 0x00, OPENPCD_REG_MAX, &i) < 0)
				exit(2);
			printf("reading register 0x%02x: ", i);
			opcd_send_command(od, OPENPCD_CMD_READ_REG, i, 0, 0, NULL);
			opcd_recv_reply(od, buf, buf_len);
			break;
		case 'w':
			if (get_number(optarg, 0x00, OPENPCD_REG_MAX, &i) < 0) {
				fprintf(stderr, "can't read register\n");
				exit(2);
			}
			if (get_number(argv[optind], 0x00, 0xff, &j) < 0) {
				fprintf(stderr, "can't read value\n");
				exit(2);
			}
			fprintf(stdout, "setting register 0x%02x to 0x%02x\n", i, j);
			opcd_send_command(od, OPENPCD_CMD_WRITE_REG, i, j, 0, NULL);
			break;
		case 'R':
			if (get_number(optarg, 0x00, OPENPCD_REG_MAX, &i) < 0)
				exit(2);
			opcd_send_command(od, OPENPCD_CMD_READ_FIFO, 0, i, 0, NULL);
			opcd_recv_reply(od, buf, buf_len);
			break;
		case 'W':
			fprintf(stderr, "FIFO write not implemented yet\n");
			break;
		case 's':
			if (get_number(optarg, 0x00, OPENPCD_REG_MAX, &i) < 0)
				exit(2);
			if (get_number(argv[optind], 0x00, 0xff, &j) < 0)
				exit(2);
			opcd_send_command(od, OPENPCD_CMD_REG_BITS_SET, i, j, 0, NULL);
			break;
		case 'c':
			if (get_number(optarg, 0x00, OPENPCD_REG_MAX, &i) < 0)
				exit(2);
			if (get_number(argv[optind], 0x00, 0xff, &j) < 0)
				exit(2);
			opcd_send_command(od, OPENPCD_CMD_REG_BITS_CLEAR, i, j, 0, NULL);
			break;
		case 'u':
			if (get_number(optarg, 1, 255, &i) < 0)
				exit(2);
			opcd_usbperf(od, i);
			break;
		case 'a':
			opcd_send_command(od, OPENPCD_CMD_ADC_READ, 0, 1, 0, NULL);
			opcd_recv_reply(od, buf, buf_len);
			/* FIXME: interpret and print ADC result */
			break;
		case 'A':
			while (1) {
				opcd_send_command(od, OPENPCD_CMD_ADC_READ, 0, 1, 0, NULL);
				opcd_recv_reply(od, buf, buf_len);
				/* FIXME: interpret and print ADC result */
			}
			break;
		case 'S':
			opcd_send_command(od, OPENPCD_CMD_SSC_READ, 0, 1, 0, NULL);
			opcd_recv_reply(od, buf, buf_len);
			/* FIXME: interpret and print SSC result */
			break;
		case 'L':
			outfd = open("/tmp/opcd_samples",
				     O_CREAT|O_WRONLY|O_APPEND, 0664);
			if (outfd < 0)
				exit(2);
			while (1) {
				data = buf + sizeof(struct openpcd_hdr);
				retlen = opcd_recv_reply(od, buf, buf_len);
				if (retlen < 0)
					break;
				printf("DATA: %s\n", opcd_hexdump(data, retlen-4));
#if 1
				write(outfd, data, retlen-4);
				fsync(outfd);
#endif
			}
			close(outfd);
			break;
		case 'h':
		case '?':
			print_help();
			exit(0);
			break;
		case 'n':
			opcd_send_command(od, OPENPCD_CMD_GET_SERIAL, 0, 1, 4,
					  NULL);
			retlen = opcd_recv_reply(od, buf,
						 sizeof(struct openpcd_hdr)+4);
			if (retlen > 0) {
				data = buf + sizeof(struct openpcd_hdr);
				printf("SERIAL: %s\n", opcd_hexdump(data, retlen-4));
			} else
				printf("ERROR: %d, %s\n", retlen, usb_strerror());
			break;
		default:
			fprintf(stderr, "unknown key `%c'\n", c);
			print_help();
			exit(2);
			break;
		}
	}

	sleep(1);

	exit(0);
}