/*****************************************************************************
 *
 * setpciscc.c
 * Configuration tool for PCISCC-4 driver pciscc4.c .
 *
 * Info:	http://www.afthd.tu-darmstadt.de/~dg1kjd/pciscc4
 *
 * Authors:	(c) 1999  Jens David <dg1kjd@afthd.tu-darmstadt.de>
 *
 * CVS:		$Id: setpciscc.c,v 1.1 2000/08/18 17:38:18 dg1kjd Exp $
 *
 *
 *****************************************************************************
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 *****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <config.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#ifdef __GLIBC__
#include <netinet/if_ether.h>
#else
#include <linux/if_ether.h>
#endif
#include <linux/pciscc4.h>
#include "getargs.h"
#include "setpciscc.h"

/*---------------------------------------------------------------------------*/
/* global variables */

static char version[]="$Id: setpciscc.c,v 1.1 2000/08/18 17:38:18 dg1kjd Exp $";
static char *progname;
static int fd_socket = 0;
static char *ifname = "dscc0";
static struct ifreq ifr;

/*---------------------------------------------------------------------------*/

/* open network socket for communication with device driver */
static void open_socket()
{
	if ((fd_socket = socket(PF_INET, SOCK_PACKET, htons(ETH_P_AX25))) < 0) {
		perror("socket");
		terminate(1);
        }
	strcpy(ifr.ifr_name, ifname);
	if (ioctl(fd_socket, SIOCGIFFLAGS, &ifr) < 0) {
		perror("SIOCGIFFLAGS");
		terminate(1);
	}
	return;
}

/*---------------------------------------------------------------------------*/

/* set channel LEDs */
static void set_leds(int argc, char **argv)
{
	unsigned long led = 0;
	char opts[] = "led&";

	if (getargs(&argc, (char *const **) &argv, opts, getnum, &led) < 0) {
		usage(1);
		terminate(1);
	}
	if (argc > 0) {
		usage(1);
		terminate(1);
	}
	open_socket();
	ifr.ifr_data = (caddr_t) led;
	if (ioctl(fd_socket, SIOCPCISCCSLED, &ifr) < 0) {
		perror("SIOCPCISCCSLED");
		terminate(1);
	}
	return;
}

/*---------------------------------------------------------------------------*/

/* display device configuration from cfg */
static void print_device(struct devcfg_t *cfg)
{
	char *code_string[] = {"(NONE)", "NRZ", "NRZI", "FM0", "FM1", "MANCH"};
	char *clock_string[] = {"(NONE)", "DF9IC", "G3RUH", "TCM3105", "HS"};
	char *duplex_string[] = {"HALF", "FULL", "FULLPTT"};
	char *txddrive_string[] = {"(NONE)", "TP", "OD"};
	char *txdmode_string[] = {"(NONE)", "SOFT", "HARD"};
	long l;

	printf("code=%s ", code_string[cfg->coding]);
	printf("clk=%s ", clock_string[cfg->clockmode]);
	printf("duplex=%s ", duplex_string[cfg->duplex]);
	printf("pll=%s ", (cfg->dpll & CFG_DPLL_PS) ? "FAST" : "SLOW");
	printf("brg=%u ", (cfg->brate_n+1)*(1 << cfg->brate_m));
	printf("txclk=%s ", (cfg->clkout & CFG_TXTXCLK) ? "on" : "off");
	printf("rtsclk=%s ", (cfg->clkout & CFG_TXRTS) ? "on" : "off");
	printf("dinv=%s ", (cfg->datainv) ? "on" : "off");
	printf("txdata=%s ", txddrive_string[cfg->txddrive]);
	printf("cdinv=%s ", (cfg->cdinv) ? "on" : "off");
	printf("loop=%s ", (cfg->testloop) ? "on" : "off");
	printf("txdmode=%s ", txdmode_string[cfg->txdelmode]);
	printf("txd=%u ", cfg->txdelval);
	printf("txt=%u ", cfg->txtailval);
	printf("slot=%u ", cfg->slottime);
	printf("pers=%u ", cfg->persist);
	printf("shflg=%s ", (cfg->sharedflg) ? "on" : "off");
	printf("crc=%s ", (cfg->crcmode & CFG_CRCMODE_CRC32) ? "CRC-32" : "CRC-16");
	printf("crcinit=%s ", (cfg->crcmode & CFG_CRCMODE_RESET_0000) ? "0000" : "FFFF");
	printf("crcrx=%s ", (cfg->crcmode & CFG_CRCMODE_RXCD) ? "off" : "on");
	printf("crctx=%s ", (cfg->crcmode & CFG_CRCMODE_TXNOCRC) ? "off" : "on");
	l=cfg->preamble;
	printf("pamb=0x%02lx ", l);
	printf("prep=%u ", cfg->preamb_rpt);
	printf("oneins=%s ", (cfg->hdlcext & CFG_HDLCEXT_ONEINS) ? "on" : "off");
	printf("fill=%s ", (cfg->hdlcext & CFG_HDLCEXT_ONEFILL) ? "ONE" : "FLAGS");
	printf("\n");
	return;
}

/*---------------------------------------------------------------------------*/

/* display/change device parameters */
static void set_device(int argc, char **argv)
{
	struct devcfg_t cfg_old, cfg_new;
	char opts[] = "code*,clk*,duplex*,pll*,brg&,txclk&,rtsclk&,dinv&,txdata*,cdinv&,loop&,txdmode*,txd&,txt&,slot&,pers&,shflg&,crc*,crcinit*,crcrx&,crctx&,pamb&,prep&,oneins&,fill*";
	char *code_str		= NULL;
	char *clk_str		= NULL;
	char *duplex_str	= NULL;
	char *pll_str		= NULL;
	int brg_int		= -1;
	int txclk_bool		= -1;
	int rtsclk_bool		= -1;
	int dinv_bool		= -1;
	char *txdata_str	= NULL;
	int cdinv_bool		= -1;
	int loop_bool		= -1;
	char *txdmode_str	= NULL;
	int txd_int		= -1;
	int txt_int		= -1;
	int slot_int		= -1;
	int pers_int		= -1;
	int shflg_bool		= -1;
	char *crc_str		= NULL;
	char *crcinit_str	= NULL;
	int crcrx_bool		= -1;
	int crctx_bool		= -1;
	int pamb_int		= -1;
	int prep_int		= -1;
	int oneins_bool		= -1;
	char *fill_str		= NULL;
	int brg_best_m;
	int brg_best_n;
	int brg_best;
	int brg_tmp;
	int brg_tmp_n;
	int i;

	open_socket();
	ifr.ifr_data = (caddr_t) &cfg_old;
	if (ioctl(fd_socket, SIOCPCISCCGDCFG, &ifr) < 0) {
		perror("SIOCPCISCCGDCFG");
		terminate(1);
	}
	printf("Device parameters of %s:\n", ifname);
	printf("Old: ");
	print_device(&cfg_old);
	memcpy((unsigned char *) &cfg_new, (unsigned char *) &cfg_old, sizeof(struct devcfg_t));
	if (getargs(&argc, (char *const **) &argv, opts,
	  &code_str,
	  &clk_str,
	  &duplex_str,
	  &pll_str,
	  getnum, &brg_int,
	  getbool, &txclk_bool,
	  getbool, &rtsclk_bool,
	  getbool, &dinv_bool,
	  &txdata_str,
	  getbool, &cdinv_bool,
	  getbool, &loop_bool,
	  &txdmode_str,
	  getnum, &txd_int,
	  getnum, &txt_int,
	  getnum, &slot_int,
	  getnum, &pers_int,
	  getbool, &shflg_bool,
	  &crc_str,
	  &crcinit_str,
	  getbool, &crcrx_bool,
	  getbool, &crctx_bool,
	  getnum, &pamb_int,
	  getnum, &prep_int,
	  getbool, &oneins_bool,
	  &fill_str) < 0) {
		usage(1);
		terminate(1);
	}
	if (argc > 0) {
		usage(1);
		terminate(1);
	}
	if (code_str) {
		if (!strcasecmp(code_str, "nrz")) {
			cfg_new.coding = CFG_CHCODE_NRZ;
		} else if (!strcasecmp(code_str, "nrzi")) {
			cfg_new.coding = CFG_CHCODE_NRZI;
		} else if (!strcasecmp(code_str, "fm0")) {
			cfg_new.coding = CFG_CHCODE_FM0;
		} else if (!strcasecmp(code_str, "fm1")) {
			cfg_new.coding = CFG_CHCODE_FM1;
		} else if (!strcasecmp(code_str, "manch")) {
			cfg_new.coding = CFG_CHCODE_MANCH;
		} else {
			usage(1);
			printf("%s: unknown channel coding type: %s.\n", progname, code_str);
			terminate(1);
		}
	}
	if (clk_str) {
		if (!strcasecmp(clk_str, "df9ic")) {
			cfg_new.clockmode = CFG_CM_DF9IC;
		} else if (!strcasecmp(clk_str, "g3ruh")) {
			cfg_new.clockmode = CFG_CM_G3RUH;
		} else if (!strcasecmp(clk_str, "tcm3105")) {
			cfg_new.clockmode = CFG_CM_TCM3105;
		} else if (!strcasecmp(clk_str, "hs")) {
			cfg_new.clockmode = CFG_CM_HS;
		} else {
			usage(1);
			printf("%s: unknown clockmode: %s.\n", progname, clk_str);
			terminate(1);
		}
	}
	if (duplex_str) {
		if (!strcasecmp(duplex_str, "full")) {
			cfg_new.duplex = CFG_DUPLEX_FULL;
		} else if (!strcasecmp(duplex_str, "fullptt")) {
			cfg_new.duplex = CFG_DUPLEX_FULLPTT;
		} else if (!strcasecmp(duplex_str, "half")) {
			cfg_new.duplex = CFG_DUPLEX_HALF;
		} else {
			usage(1);
			printf("%s: unknown duplex mode: %s.\n", progname, duplex_str);
			terminate(1);
		}
	}
	if (pll_str) {
		if (!strcasecmp(pll_str, "slow")) {
			cfg_new.dpll = 0;
		} else if (!strcasecmp(pll_str, "fast")) {
			cfg_new.dpll = CFG_DPLL_PS;
		} else {
			usage(1);
			printf("%s: unknown pll mode: %s.\n", progname, pll_str);
			terminate(1);
		}
	}
	if (brg_int >= 0) {
		if (brg_int > 2097152) {
			usage(1);
			printf("%s: illegal baud rate generator ratio: %d.\n", progname, brg_int);
			terminate(1);
		}
		/* k = (n+1) * 2^m, with k given by user */
		brg_best = brg_best_n = brg_best_m = brg_tmp_n = 0;
		for (i=0; i<16; i++) {
			brg_tmp_n = (brg_int/(1<<i))-1;
			if (brg_tmp_n > 63 || brg_tmp_n < 0) continue;
			brg_tmp = (brg_tmp_n+1)*(1<<i);
			if (abs(brg_best-brg_int) < abs(brg_tmp-brg_int)) continue;
			brg_best = brg_tmp;
			brg_best_n = brg_tmp_n;
			brg_best_m = i;
		}
		cfg_new.brate_m = brg_best_m;
		cfg_new.brate_n = brg_best_n;
	}
	if (txclk_bool >= 0) {
		cfg_new.clkout = txclk_bool ? cfg_new.clkout | CFG_TXTXCLK : cfg_new.clkout & ~CFG_TXTXCLK;
	}
	if (rtsclk_bool >= 0) {
		cfg_new.clkout = txclk_bool ? cfg_new.clkout | CFG_TXRTS : cfg_new.clkout & ~CFG_TXRTS;
	}
	if (dinv_bool >= 0) {
		cfg_new.datainv = !!dinv_bool;
	}
	if (txdata_str) {
		if (!strcasecmp(txdata_str, "tp")) {
			cfg_new.txddrive = CFG_TXDDRIVE_TP;
		} else if (!strcasecmp(txdata_str, "od")) {
			cfg_new.txddrive = CFG_TXDDRIVE_OD;
		} else {
			usage(1);
			printf("%s: unknown driver type: %s.\n", progname, txdata_str);
			terminate(1);
		}
	}
	if (cdinv_bool >= 0) {
		cfg_new.cdinv = !!cdinv_bool;
	}
	if (loop_bool >= 0) {
		cfg_new.testloop = !!loop_bool;
	}
	if (txdmode_str) {
		if (!strcasecmp(txdmode_str, "hard")) {
			cfg_new.txdelmode = CFG_TXDEL_HARD;
		} else if (!strcasecmp(txdmode_str, "soft")) {
			cfg_new.txdelmode = CFG_TXDEL_SOFT;
		} else {
			usage(1);
			printf("%s: unknown txdelay mode: %s.\n", progname, txdmode_str);
			terminate(1);
		}
	}
	if (txd_int >= 0) {
		cfg_new.txdelval = txd_int;
	}
	if (txt_int >= 0) {
		cfg_new.txtailval = txt_int;
	}
	if (slot_int >= 0) {
		cfg_new.slottime = slot_int;
	}
	if (pers_int >= 0) {
		cfg_new.persist = pers_int;
	}
	if (shflg_bool >= 0) {
		cfg_new.sharedflg = !!shflg_bool;
	}
	if (crc_str) {
		if (!strcasecmp(crc_str, "crc-16") || !strcasecmp(crc_str, "crc16")) {
			cfg_new.crcmode &= ~CFG_CRCMODE_CRC32;
		} else if (!strcasecmp(crc_str, "crc-32") || !strcasecmp(crc_str, "crc32")) {
			cfg_new.crcmode |= CFG_CRCMODE_CRC32;
		} else {
			usage(1);
			printf("%s: unknown crc mode: %s.\n", progname, crc_str);
			terminate(1);
		}
	}
	if (crcinit_str) {
		if (!strcasecmp(crcinit_str, "ffff") || !strcasecmp(crcinit_str, "0xffff")) {
			cfg_new.crcmode &= ~CFG_CRCMODE_RESET_0000;
		} else if (!strcasecmp(crcinit_str, "0000") || !strcasecmp(crcinit_str, "0x0000")) {
			cfg_new.crcmode |= CFG_CRCMODE_RESET_0000;
		} else {
			usage(1);
			printf("%s: crc reset value %s not supported.\n", progname, crcinit_str);
			terminate(1);
		}
	}
	if (crcrx_bool >= 0) {
		cfg_new.crcmode = (crcrx_bool ? cfg_new.crcmode & ~CFG_CRCMODE_RXCD : cfg_new.crcmode | CFG_CRCMODE_RXCD);
	}
	if (crctx_bool >= 0) {
		cfg_new.crcmode = (crctx_bool ? cfg_new.crcmode & ~CFG_CRCMODE_TXNOCRC : cfg_new.crcmode | CFG_CRCMODE_TXNOCRC);
	}
	if (pamb_int >= 0) {
		if (pamb_int<0 || pamb_int>255) {
			usage(1);
			printf("%s: illegal preamble: %d.\n", progname, pamb_int);
			terminate(1);
		}
		cfg_new.preamble = pamb_int;
	}
	if (prep_int >= 0) {
		if (prep_int!=0 && prep_int!=2 && prep_int!=4 && prep_int!=8) {
			usage(1);
			printf("%s: illegal number of preamble repetitions: %u.\n", progname, prep_int);
			terminate(1);
		}
		cfg_new.preamb_rpt = prep_int;
	}
	if (oneins_bool >= 0) {
		if (oneins_bool) {
			cfg_new.hdlcext |= CFG_HDLCEXT_ONEINS;
		} else {
			cfg_new.hdlcext &= ~CFG_HDLCEXT_ONEINS;
		}
	}
	if (fill_str) {
		if (!strcasecmp(fill_str, "one") || !strcasecmp(fill_str, "ones")) {
			cfg_new.hdlcext |= CFG_HDLCEXT_ONEFILL;
		} else if (!strcasecmp(fill_str, "flag") || !strcasecmp(fill_str, "flags")) {
			cfg_new.hdlcext &= ~CFG_HDLCEXT_ONEFILL;
		} else {
			usage(1);
			printf("%s: unknown interframe-fill mode: %s.\n", progname, fill_str);
			terminate(1);
		}
	}
	if (!memcmp((unsigned char *) &cfg_old, (unsigned char *) &cfg_new, sizeof(struct devcfg_t))) {
		return;
	}
	ifr.ifr_data = (caddr_t) &cfg_new;
	if (ioctl(fd_socket, SIOCPCISCCSDCFG, &ifr) < 0) {
		perror("SIOCPCISCCSDCFG");
		terminate(1);
	}
	ifr.ifr_data = (caddr_t) &cfg_new;
	if (ioctl(fd_socket, SIOCPCISCCGDCFG, &ifr) < 0) {
		perror("SIOCPCISCCGDCFG");
		terminate(1);
	}
	printf("New: ");
	print_device(&cfg_new);
	return;
}

/*---------------------------------------------------------------------------*/

/* display global configuration from cfg */
static void print_global(struct chipcfg_t *cfg)
{
	printf("rxbuf=%u ", cfg->rxbufcnt);
	printf("txbuf=%u ", cfg->txbufcnt);
	printf("iqlen=%u ", cfg->iqlen);
	printf("pchan=%d ", cfg->prichan);
	printf("rfifo=%u ", cfg->mfifo_rx_t);
	printf("lbi=0x%08lx ", cfg->lbimode);
	printf("oscp=%s\n", cfg->oscpwr ? "on" : "off");
	printf("\n");
	return;
}

/*---------------------------------------------------------------------------*/

/* display/change controllers global parameters */
static void set_global(int argc, char **argv)
{
	struct chipcfg_t cfg_old, cfg_new;
	char opts[] = "rxbuf&,txbuf&,iqlen&,pchan&,rfifo&,lbi&,oscp&";

	open_socket();
	ifr.ifr_data = (caddr_t) &cfg_old;
	if (ioctl(fd_socket, SIOCPCISCCGCCFG, &ifr) < 0) {
		perror("SIOCPCISCCGCCFG");
		terminate(1);
	}
	printf("Global parameters for controller of %s:\n", ifname);
	printf("Old: ");
	print_global(&cfg_old);
	memcpy((unsigned char *) &cfg_new, (unsigned char *) &cfg_old, sizeof(struct chipcfg_t));
	if (getargs(&argc, (char *const **) &argv, opts,
		getnum, &cfg_new.rxbufcnt,
		getnum, &cfg_new.txbufcnt,
		getnum, &cfg_new.iqlen,
		getnum, &cfg_new.prichan,
		getnum, &cfg_new.mfifo_rx_t,
		getnum, &cfg_new.lbimode,
		getbool, &cfg_new.oscpwr)
	< 0) {
		usage(1);
		terminate(1);
	}
	if (argc > 0) {
		usage(1);
		terminate(1);
	}
	if (!memcmp((unsigned char *) &cfg_old, (unsigned char *) &cfg_new, sizeof(struct chipcfg_t))) {
		return;
	}
	ifr.ifr_data = (caddr_t) &cfg_new;
	if (ioctl(fd_socket, SIOCPCISCCSCCFG, &ifr) < 0) {
		perror("SIOCPCISCCSCCFG");
		terminate(1);
	}
	ifr.ifr_data = (caddr_t) &cfg_new;
	if (ioctl(fd_socket, SIOCPCISCCGCCFG, &ifr) < 0) {
		perror("SIOCPCISCCGCCFG");
		terminate(1);
	}
	printf("New: ");
	print_global(&cfg_new);
	return;
}

/*---------------------------------------------------------------------------*/

/* do local bus transaction */
static void do_lbi(int argc, char **argv)
{
	long address_int = -1;
	unsigned long data_int = 0;
	int read_bool = -1;
	int write_bool = -1;
	char opts[] = "r,w,addr&,data&";
	struct lbi_xfer lbi;

	open_socket();
	if (getargs(&argc, (char *const **) &argv, opts,
		&read_bool,
		&write_bool,
		getnum, &address_int,
		getnum, &data_int)
	  < 0) {
		usage(1);
		terminate(1);
	}
	if (argc > 0) {
		usage(1);
		terminate(1);
	}
	if ((!read_bool && !write_bool) || (read_bool && write_bool)) {
		usage(1);
		printf("%s: illegal access mode.\n", progname);
		terminate(1);
	}
	if (address_int<0 || address_int>65535) {
		usage(1);
		printf("%s: illegal address.\n", progname);
		terminate(1);
	}
	if (read_bool) lbi.mode = LBI_READ; else lbi.mode = LBI_WRITE;
	lbi.addr = address_int;
	if (write_bool) lbi.data = data_int;
	ifr.ifr_data = (caddr_t) &lbi;
	if (ioctl(fd_socket, SIOCPCISCCLBI, &ifr) < 0) {
		perror("SIOCPCISCCLBI");
		terminate(1);
	}
	if (read_bool) printf("LBI read data: 0x%08lx.\n", (signed long) lbi.data);
	return;
}

/*---------------------------------------------------------------------------*/

/* display channel status */
static void get_status(int argc, char **argv)
{
	unsigned long l;

	open_socket();
	if (argc > 0) {
		usage(1);
		terminate(1);
	}
	if (ioctl(fd_socket, SIOCPCISCCGDSTAT, &ifr) < 0) {
		perror("SIOCPCISCCGDSTAT");
		terminate(1);
	}
	l = (unsigned long) ifr.ifr_data;
	printf("PLL: %-6s  Line: %-8s  DCD: %-8s  CTS: %-8s  RTS: %-8s\n",
		(l & STATUS_DPLA) ? "unlock" : "lock",
		(l & STATUS_RLI) ? "inactive" : "active",
		(l & STATUS_CD) ? "active" : "inactive",
		(l & STATUS_CTS) ? "active" : "inactive",
		(l & STATUS_RTS) ? "active" : "inactive");
	printf("TX-State: ");
	switch (l & 15) {
	case TX_RESET:
		printf("RESET\n");
		break;
	case TX_IDLE:
		printf("IDLE\n");
		break;
	case TX_DELAY:
		printf("TXDELAY\n");
		break;
	case TX_XMIT:
		printf("TRANSMIT\n");
		break;
	case TX_TAIL:
		printf("TXTAIL\n");
		break;
	case TX_PROBE:
		printf("PROBE\n");
		break;
	case TX_CAL:
		printf("CALIBRATE\n");
		break;
	}
	return;
}

/*---------------------------------------------------------------------------*/

/* initiate calibration */
static void do_cal(int argc, char **argv)
{
	char opts[] = "time&";
	long time_num = -1;
	char *p = argv[0];

	if (argc != 1) {
		printf("%s: time (in bit-times) or 0 (for calibrate off) must be specified.\n", progname);
		terminate(1);
	}
	open_socket();
	time_num = strtol(argv[0], &p, 0);
	if (p == argv[0]) {
		/* long command line format with "time=" */
		if (getargs(&argc, (char *const **) &argv, opts, getnum, &time_num) < 0) {
			usage(1);
			terminate(1);
		}
		if (argc > 0) {
			usage(1);
			terminate(1);
		}
	}
	if (time_num == -1) {
		usage(1);
		printf("%s: time (in bits) must be in reasonable range or 0 to stop ongoing calibration.\n", progname);
		terminate(1);
	}
	ifr.ifr_data = (caddr_t) time_num;
	if (ioctl(fd_socket, SIOCPCISCCDCAL, &ifr) < 0) {
		perror("SIOCPCISCCDCAL");
		terminate(1);
	}
	if (time_num) printf("Calibrating for %ld bit times.\n", time_num);
		else printf("Calibrate off.\n");
	return;
}

/*---------------------------------------------------------------------------*/

/* kick transmitter */
static void do_kick(int argc, char **argv)
{
	open_socket();
	if (ioctl(fd_socket, SIOCPCISCCKICKTX, &ifr) < 0) {
		perror("SIOCPCISCCKICKTX");
		terminate(1);
	}
        printf("TX of interface %s has been kicked.\n", ifname);
	return;
}

/*---------------------------------------------------------------------------*/

/* display command usage */
static void usage(int error)
{
	if (error) {
		printf("%s: illegal syntax.\n", progname);
	}
	printf("%s\n", version);
	printf("(C) 1999 Jens David, DG1KJD <dg1kjd@afthd.tu-darmstadt.de>\n");
	printf("This tool can be used to configure the PCISCC-4 driver. The driver module\n");
	printf("must have been loaded and for most operations all interfaces must be down.\n");
	printf("syntax:\n");
	printf("%s [-i iface] -g [parms...]	get/set global parameters\n", progname);
	printf("	rxbuf=<val>	number of RX descriptors and buffers\n");
	printf("	txbuf=<val>	number of TX descriptors\n");
	printf("	iqlen=<val>	interrupt queue len (multiple of 32!)\n");
	printf("	pchan=<0..3>	DMA priority channel, -1 for none\n");
	printf("	rfifo=<val>	RX main FIFO DMA threshold (dwords)\n");
	printf("	lbi=<val>	Local Bus Interface mode\n");
	printf("	oscp=<0|1>	on-board oscillator power\n");
	printf("\n");
	printf("%s [-i iface] -d [parms...]	get/set device specific parameters\n", progname);
	printf("	code=<type>	channel coding NRZ, NRZI, FM0, FM1, MANCH\n");
	printf("	clk=<type>	clock mode: DF9IC, G3RUH, TCM3105, HS\n");
	printf("	duplex=<type>	duplex type: HALF, FULL, FULLPTT\n");
	printf("	pll=<FAST|SLOW>	DPLL 180 deg phase shift enable\n");
	printf("	brg=<val>	baud rate generator division value\n");
	printf("	txclk=<0|1>	enable TX clock _OUTPUT_ on TxClk. (Use with care!)\n");
	printf("	rtsclk=<0|1>	enable TX clock _OUTPUT_ on RTS. (Use with care!)\n");
	printf("	dinv=<0|1>	data inversion with NRZ\n");
	printf("	txdata=<type>	TXD driver: TP=totem pole, OD=open drain (!)\n");
	printf("	cdinv=<0|1>	invert carrier detect line\n");
	printf("	loop=<0|1>	enable on chip test loopback\n");
	printf("	txdmode=<type>	TX-delay mode HARD: RTS/CTS-controlled, SOFT: timer\n");
	printf("	txd=<val>	TX-delay value if type SOFT in bits\n");
	printf("	txt=<val>	TX-tail in bits\n");
	printf("	shflg=<0|1>	Use shared flags on TX? (RX not influenced)\n");
	printf("	crc=<type>	CRC-32, CRC-16 (default)\n");
	printf("	crcinit=<type>	FFFF (default), 0000\n");
	printf("	crcrx=<0|1>	check CRC on RX?\n");
	printf("	crctx=<0|1>	append CRC on TX?\n");
	printf("	pamb=<val>	preamble byte\n");
	printf("	prep=<val>	preamble repetitions 0=off\n");
	printf("	oneins=<0|1>	HDLC extension: one insertion after 7 zeros\n");
	printf("	fill=<type>	interframe fill: FLAGS, ONE\n");
	printf("\n");
	printf("%s [-i iface] -l [parms...]	set LEDs\n", progname);
	printf("	led=<0..3>	Channel-LEDs: 0=off 1=red 2=green 3=both\n");
	printf("\n");
	printf("%s [-i iface] -b [parms...]	local bus transaction\n", progname);
	printf("	-r / -w		read/write mode\n");
	printf("	addr=<val>	address\n");
	printf("	data=<val>	data when in write mode\n");
	printf("This will crash your system if LBI is misconfigured.\n");
	printf("\n");
	printf("%s [-i iface] -s		get device status\n", progname);
	printf("\n");
	printf("%s [-i iface] -c [parms...]	calibrate (send flags)\n", progname);
	printf("	time=<val>	time in bits, 0=off\n");
	printf("\n");
	printf("%s [-i iface] -k 	        kick TX\n", progname);
	printf("\n");
	printf("%s -h				Print this help text\n", progname);
	printf("See manual page for details.\n");
	return;
}

/*---------------------------------------------------------------------------*/

/* gracefully terminate program */
static void terminate(int error)
{
	if (fd_socket) {
		/* close socket if open */
		close(fd_socket);
		fd_socket = 0;
	}
	exit(error);
	return;		/* never reached */
}

/*---------------------------------------------------------------------------*/

/* main program */
int main(int argc, char **argv)
{
	int argcnt;
	enum mode_t mode = mode_none;
	
	progname=argv[0];
	if (argc == 1) {
		usage(0);
		terminate(1);
	}
	for (argcnt=1; argcnt<argc; argcnt++)
	{
		if (!strcmp(argv[argcnt], "-i")) {
			if (argc < argcnt+2) {
				usage(1);
				terminate(1);
			}
			ifname = argv[++argcnt];
		} else if (!strcmp(argv[argcnt], "-g")) {
			mode = mode_global;
			argcnt++;
			break;
		} else if (!strcmp(argv[argcnt], "-d")) {
			mode = mode_device;
			argcnt++;
			break;
		} else if (!strcmp(argv[argcnt], "-l")) {
			mode = mode_led;
			argcnt++;
			break;
		} else if (!strcmp(argv[argcnt], "-b")) {
			mode = mode_lbi;
			argcnt++;
			break;
		} else if (!strcmp(argv[argcnt], "-s")) {
			mode = mode_status;
			argcnt++;
			break;
		} else if (!strcmp(argv[argcnt], "-c")) {
			mode = mode_cal;
			argcnt++;
			break;
		} else if (!strcmp(argv[argcnt], "-k")) {
			mode = mode_kick;
			argcnt++;
			break;
		} else if (!strcmp(argv[argcnt], "-h")) {
			usage(0);
			terminate(0);
			break;
		}
	}
	switch (mode) {
	case mode_global:
		set_global(argc-argcnt, &argv[argcnt]);
		break;
	case mode_device:
		set_device(argc-argcnt, &argv[argcnt]);
		break;
	case mode_led:
		set_leds(argc-argcnt, &argv[argcnt]);
		break;
	case mode_lbi:
		do_lbi(argc-argcnt, &argv[argcnt]);
		break;
	case mode_status:
		get_status(argc-argcnt, &argv[argcnt]);
		break;
	case mode_cal:
		do_cal(argc-argcnt, &argv[argcnt]);
		break;
	case mode_kick:
	        do_kick(argc-argcnt, &argv[argcnt]);
		break;
	default:
		usage(1);
		terminate(1);
		break;
	}
	terminate(0);
	return 0;
}
