/*****************************************************************************/

/*
 *      configfpga.c  --  FPGA configuration routines.
 *
 *      Copyright (C) 1998-2000  Thomas Sailer (sailer@ife.ee.ethz.ch)
 *
 *      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.
 *
 *  Please note that the GPL allows you to use the driver, NOT the radio.
 *  In order to use the radio, you need a license from the communications
 *  authority of your country.
 *
 */

/*****************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <math.h>
#include <unistd.h>
#include <stdarg.h>

#ifdef HAVE_SYS_PERM_H
#include <sys/perm.h>
#endif

#include "parport.h"
#include "fpga.h"
#include "util.h"

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

#include "epp_fpga.h"
#include "epp_firmware.h"
#include "eppchk_firmware.h"
#include "ecp_firmware.h"
#include "eppafsk_firmware.h"
#include "eppsamp_firmware.h"
#include "sppafsk_firmware.h"

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

/* LPT data register */
#define LPTDATA_SHIFT_TDI     0
#define LPTDATA_SHIFT_TMS     2
#define LPTDATA_TDI           (1<<LPTDATA_SHIFT_TDI)
#define LPTDATA_TCK           0x02
#define LPTDATA_TMS           (1<<LPTDATA_SHIFT_TMS)
#define LPTDATA_INITBIAS      0x80

/* Xilinx 4k JTAG instructions */
#define XC4K_IRLENGTH   3
#define XC4K_EXTEST     0
#define XC4K_PRELOAD    1
#define XC4K_CONFIGURE  5
#define XC4K_BYPASS     7

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

static void jtag_shiftout(unsigned num, unsigned tdi, unsigned tms, unsigned char eppdata)
{
	unsigned char v;

	eppdata &= ~(LPTDATA_TDI | LPTDATA_TMS | LPTDATA_TCK);
	v = eppdata | ((tdi << LPTDATA_SHIFT_TDI) & LPTDATA_TDI) | ((tms << LPTDATA_SHIFT_TMS) & LPTDATA_TMS);
	parport_write_data(v);
	while (num > 0) {
		parport_write_data(v | LPTDATA_TCK);
		tdi >>= 1;
		tms >>= 1;
		v = eppdata | ((tdi << LPTDATA_SHIFT_TDI) & LPTDATA_TDI) |
			((tms << LPTDATA_SHIFT_TMS) & LPTDATA_TMS);
		parport_write_data(v);
		num--;
	}
}

static unsigned jtag_shift(unsigned num, unsigned tdi, unsigned tms, unsigned char eppdata)
{
	unsigned char v;
	unsigned in = 0, mask = 2;

	eppdata &= ~(LPTDATA_TDI | LPTDATA_TMS | LPTDATA_TCK);
	v = eppdata | ((tdi << LPTDATA_SHIFT_TDI) & LPTDATA_TDI) |
		((tms << LPTDATA_SHIFT_TMS) & LPTDATA_TMS);
	parport_write_data(v);
	if (parport_read_status() & LPTSTAT_NINTR)
		in |= 1;
	while (num > 0) {
		parport_write_data(v | LPTDATA_TCK);
		tdi >>= 1;
		tms >>= 1;
		v = eppdata | ((tdi << LPTDATA_SHIFT_TDI) & LPTDATA_TDI) |
			((tms << LPTDATA_SHIFT_TMS) & LPTDATA_TMS);
		parport_write_data(v);
		num--;
		if (parport_read_status() & LPTSTAT_NINTR)
			in |= mask;
		mask <<= 1;
	}
	return in;
}

extern inline void jtag_reset_tap(void)
{
	jtag_shiftout(5, 0, ~0, 0);
}

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

/*
 * this routine expects the target TAP to be in RUNTEST/IDLE
 * state and leaves it there at exit 
 */

void boundary(unsigned int blen, const unsigned char *in, unsigned char *out, unsigned char eppdata)
{
	
	jtag_shiftout(3, 0, 1, eppdata);
	while (blen > 8) {
		*out++ = jtag_shift(8, *in++, 0, eppdata);
		blen -= 8;
	}
	*out = jtag_shift(blen, *in, 1 << (blen-1), eppdata);
	jtag_shiftout(2, 0, 1, eppdata);
}

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

/*
 * Modem detection routines (assume the LPT port is there)
 */

static int detect_modem(void)
{
	unsigned int i;
	unsigned char val;
	unsigned char bd[FPGA_BOUNDSIZE];

	/* pulse PROGRAM low */
	parport_write_control(LPTCTRL_ADDRSTB);
	parport_write_data(LPTDATA_TMS);
	usleep(10);
	parport_write_control(LPTCTRL_PROGRAM);
	/* check if EPP NINIT is low */
	val = parport_read_status();
	if (val & LPTSTAT_WAIT) {
		lprintf(0, "cannot hold down INIT\n");
		return -1;
	}
	if (val & LPTSTAT_DONE) {
		lprintf(0, "DONE high\n");
		return -1;
	}
	/* FPGA held in configuration state due to INITBIAS low */
	jtag_reset_tap(); /* force TAP controller into reset state */
	/* check instruction register length */
	jtag_shiftout(5, 0, 6, 0);   /* enter Shift-IR state */
	jtag_shiftout(32, 0, 0, 0);  /* assume max. 32bit IR */
	i = jtag_shift(32, 1, 0, 0); /* shift in a single bit */
	if (hweight32(i) != 1) {
		lprintf(0, "unable to detect JTAG IR (val %#06x)\n", i);
		return -1;
	}
	if (ffs(i) != XC4K_IRLENGTH+1) {
		lprintf(0, "size of JTAG IR not %d bits (val %#06x)\n", XC4K_IRLENGTH, i);
		return -1;
	}
	/* shift in PRELOAD insn, goto SHIFTDR state */
	jtag_shiftout(XC4K_IRLENGTH+4, XC4K_PRELOAD, 7 << (XC4K_IRLENGTH-1), 0); 
	for (i = 0; i < 1000; i++) {
		parport_write_data(0);
		parport_write_data(LPTDATA_TCK);
	}
	parport_write_data(LPTDATA_TDI);
	parport_write_data(LPTDATA_TDI | LPTDATA_TCK);
	i = 1;
	parport_write_data(0);
	while (!(parport_read_status() & LPTSTAT_NINTR)) {
		if (i >= 1000) {
			lprintf(0, "JTAG DR too long\n");
			return -1;
		}
		parport_write_data(LPTDATA_TCK);
		parport_write_data(0);
		i++;
	}
	if (i != FPGA_BOUND) {
		lprintf(0, "size of JTAG DR not %d bits (val %d)\n", FPGA_BOUND, i);
		return -1;
	}
	for (i = 0; i < FPGA_BOUND-1; i++) {
		val = ((fpga_safebound[i >> 3] >> (i & 3)) & 1) << LPTDATA_SHIFT_TDI;
		parport_write_data(val);
		parport_write_data(val | LPTDATA_TCK);
	}
	/* i = data->h.h.boundarylength-1; */
	val = (((fpga_safebound[(FPGA_BOUND-1) >> 3] >> ((FPGA_BOUND-1) & 3)) & 1)
	       << LPTDATA_SHIFT_TDI) | LPTDATA_TMS;
	parport_write_data(val);
	parport_write_data(val | LPTDATA_TCK);
	/* shift in EXTEST insn, goto TESTRESET state */
	jtag_shiftout(7 + XC4K_IRLENGTH, XC4K_EXTEST << 5, 7 | (0x30 << XC4K_IRLENGTH), 0);  
	boundary(FPGA_BOUND, fpga_safebound, bd, 0);
	lprintf(1, "Modem jumpers: %c %c%c%c%c %c%c%c%c %c%c%c%c\n",
		'0' + readboundary(bd, FPGA_PIN_RAMA0_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA1_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA2_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA3_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA4_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA5_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA6_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA7_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA8_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA9_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA10_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA11_I),
		'0' + readboundary(bd, FPGA_PIN_RAMA12_I));
	return 0;
}

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

void printconfig(const struct adapter_config *cfg)
{
	lprintf(1, "configuration: %sclk,%smodem,fclk=%d,bitrate=%d%s,%sextstat,%spttmute,filter=%d,gain=%d\n", 
		cfg->intclk ? "int" : "ext",
		cfg->extmodem ? "ext" : "int", cfg->fclk, cfg->bitrate,
		cfg->loopback ? ",loopback" : "", cfg->extstat ? "" : "no", cfg->pttmute ? "" : "no",
		cfg->filtmode, cfg->gain);
}

void parseconfig(struct adapter_config *cfg, const char *modestr)
{
	const char *cp;
	/* temporary hack to ease the transition to the new format */
	unsigned hasbitrate = 0, divider = 0;

	/* meaningful frequencies/bit rates */
	if (!cfg->fclk)
		cfg->fclk = 19666600;
	if (!cfg->bitrate)
		cfg->bitrate = 9600;
	if (cfg->fclk < 1000000)
		cfg->fclk = 1000000;
	if (cfg->fclk > 25000000)
		cfg->fclk = 25000000;
	if (cfg->bitrate < 4096)
		cfg->bitrate = 4096;
	if (cfg->bitrate > 1500000)
		cfg->bitrate = 1500000;
	/* command line parsing */
	if (strstr(modestr, "intclk"))
		cfg->intclk = 1;
	if (strstr(modestr, "extclk"))
		cfg->intclk = 0;
	if (strstr(modestr, "intmodem"))
		cfg->extmodem = 0;
	if (strstr(modestr, "extmodem"))
		cfg->extmodem = 1;
	if (strstr(modestr, "noloopback"))
		cfg->loopback = 0;
	else if (strstr(modestr, "loopback"))
		cfg->loopback = 1;
	if (strstr(modestr, "noextstat"))
		cfg->extstat = 0;
	else if (strstr(modestr, "extstat"))
		cfg->extstat = 1;
	if (strstr(modestr, "nopttmute"))
		cfg->pttmute = 0;
	else if (strstr(modestr, "pttmute"))
		cfg->pttmute = 1;
	if ((cp = strstr(modestr, "fclk="))) {
		cfg->fclk = strtoul(cp+5, NULL, 0);
		if (cfg->fclk < 1000000)
			cfg->fclk = 1000000;
		if (cfg->fclk > 25000000)
			cfg->fclk = 25000000;
	}
	if ((cp = strstr(modestr, "bitrate="))) {
		cfg->bitrate = strtoul(cp+8, NULL, 0);
		if (cfg->bitrate < 4096)
			cfg->bitrate = 4096;
		if (cfg->bitrate > 1500000)
			cfg->bitrate = 1500000;
		hasbitrate = 1;
	}
	if ((cp = strstr(modestr, "filter="))) {
		cfg->filtmode = strtoul(cp+7, NULL, 0);
		if (cfg->filtmode > 2)
			cfg->filtmode = 2;
	}
	if ((cp = strstr(modestr, "gain="))) {
		cfg->gain = strtoul(cp+5, NULL, 0);
		if (cfg->gain < 1)
			cfg->gain = 1;
		if (cfg->gain > 32)
			cfg->gain = 32;
	}
	if ((cp = strstr(modestr, "divider="))) {
		divider = strtoul(cp+8, NULL, 0);
		if (divider < 1)
			divider = 1;
		if (divider > 1024)
			divider = 1024;
	}
	/* set bitrate if there was a divider= but no bitrate= */
	if (!hasbitrate && divider) {
		cfg->bitrate = (cfg->fclk + 8 * divider) / (16 * divider);
		if (cfg->bitrate < 4096)
			cfg->bitrate = 4096;
		if (cfg->bitrate > 1500000)
			cfg->bitrate = 1500000;
	}		
}

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

#if 0
static void calc_crc(const unsigned char *data, unsigned int frame)
{
	unsigned int ptr = frame * FPGA_FRAMELEN + 41, i, creg = ~0, creg2 = creg;
	unsigned char b1[8], b2[8], b3[8], *s1 = b1, *s2 = b2, *s3 = b3;

	for (i = 0; i < FPGA_FRAMELEN-5; i++, ptr++) {
		creg <<= 1;
		creg |= ((data[ptr >> 3] >> ((~ptr) & 7)) ^ (creg >> 16) ^ (creg >> 1) ^ (creg >> 14)) & 1;
		creg2 <<= 1;
		if (((data[ptr >> 3] >> ((~ptr) & 7)) ^ (creg2 >> 16)) & 1)
			creg2 ^= (1 << 0) | (1 << 2) | (1 << 15);
	}
	for (i = 0; i < 4; i++, ptr++) {
		*s1++ = '0' + ((data[ptr >> 3]>> ((~ptr) & 7)) & 1);
		creg <<= 1;
		creg |= ((creg >> 16) ^ (creg >> 1) ^ (creg >> 14)) & 1;
		*s2++ = '0' + ((creg >> 16) & 1);
		creg2 <<= 1;
		if (creg2 & (1 << 16))
			creg2 ^= (1 << 0) | (1 << 2) | (1 << 15);
		*s3++ = '0' + ((creg2 >> 16) & 1);
	}
	*s1 = *s2 = *s3 = 0;
	printf("Frame %3d CRC file %s calc %s %s  ptr %5d\n", frame, b1, b2, b3, ptr);
}
#endif

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

static int configure(const unsigned char *data, unsigned cfgsz)
{
	struct timeval time1, time2;
	unsigned i, j, dt;
	unsigned char val;
	unsigned char lpstat;
	unsigned char cfgbuf[16];
	unsigned char *bp;
	
#if 0
	for (i = 0; i < EPP_FPGANUMFRAMES; i++)
		calc_crc(cfgdata, i);
#endif
	parport_write_data(0);
	/* pulse PROGRAM low */
	parport_write_control(LPTCTRL_ADDRSTB);
	usleep(100);
	parport_write_control(LPTCTRL_PROGRAM);
	if (parport_read_status() & LPTSTAT_WAIT) {
		lprintf(0, "FPGA configuration error (INIT prematurely high)\n");
		return -1;
	}
	jtag_shiftout(12 + XC4K_IRLENGTH, XC4K_CONFIGURE << 10, 
		      0xdf | (0x600 << XC4K_IRLENGTH), 0);
	/* we are now in RUNTEST/IDLE state */
	/* set initbias high; poll for init going high */
	parport_write_data(LPTDATA_INITBIAS);
	gettime(&time1);
	while (!(parport_read_status() & LPTSTAT_WAIT)) {
		gettime(&time2);
		if (((time2.tv_sec - time1.tv_sec) * 1000000UL + 
		     (time2.tv_usec - time1.tv_usec)) >= 500000) {
			lprintf(0, "during configure: INIT stuck low\n");
			return -1;
		}
	}
	gettime(&time2);
	lprintf(2, "Init was down for %ld us\n", (time2.tv_sec - time1.tv_sec) * 1000000UL +
	       time2.tv_usec - time1.tv_usec);
	jtag_shiftout(3, ~0, 1, LPTDATA_INITBIAS); /* advance to SHIFT-DR state */
	for (i = 0; i < cfgsz; i++) {
		dt = data[i];
		bp = cfgbuf;
		for (j = 0; j < 8; j++, dt >>= 1) {
			val = LPTDATA_INITBIAS | ((dt << LPTDATA_SHIFT_TDI) & LPTDATA_TDI);
			*bp++ = val;
			*bp++ = val | LPTDATA_TCK;
		}
		if (parport_fpgaconfig_write(cfgbuf, 16) != 16) {
			lprintf(0, "FPGA configuration write error\n");
			return -1;
		}
		lpstat = parport_read_status();
		if (lpstat & LPTSTAT_DONE) {
			if (i >= cfgsz-1)
				break;
			lprintf(0, "FPGA configuration error (DONE prematurely high)\n");
			return -1;
		}
		if (!(lpstat & LPTSTAT_WAIT)) {
			lprintf(0, "FPGA configuration error (INIT down)\n");
			return -1;
		}
	}
	for (i = 0; ; ) {
		lpstat = parport_read_status();
		if (lpstat & LPTSTAT_DONE) 
			break;
		if (!(lpstat & LPTSTAT_WAIT)) {
			lprintf(0, "FPGA configuration error (INIT down) (2)!!\n");
			return -1;
		}
		parport_write_data(LPTDATA_INITBIAS | LPTDATA_TDI);
		parport_write_data(LPTDATA_INITBIAS | LPTDATA_TDI | LPTDATA_TCK);
		if ((++i) > 1000) {
			lprintf(0, "DONE stuck low\n");
			return -1;
		}
	}
	/* generate a few additional clock cycles to ensure proper startup */
	for (i = 0; i < 16; i++) {
		parport_write_data(LPTDATA_INITBIAS | LPTDATA_TDI);
		parport_write_data(LPTDATA_INITBIAS | LPTDATA_TDI | LPTDATA_TCK);
	}
	lprintf(1, "FPGA configuration completed successfully\n");
	return 0;
}

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

static unsigned int readconfigrom(const unsigned char *firmware, const unsigned long rom[16])
{
	unsigned ret = 0, mask = 1, i, j;

	for (i = 0; i < 16; i++, mask <<= 1) {
		j = rom[i];
		if (!(firmware[j >> 3] & (1 << (j & 7))))
			ret |= mask;
	}
	return ret & 0xffff;
}

/* warning: if used, don't use CRC bitstream, there's currently no code to recalc the CRC */
static void writeconfigrom(unsigned char *firmware, const unsigned long rom[16], unsigned val)
{
	unsigned mask = 1, i, j;

	for (i = 0; i < 16; i++, mask <<= 1) {
		j = rom[i];
		if (val & mask) 
			firmware[j >> 3] &= ~(1 << (j & 7));
		else
			firmware[j >> 3] |= 1 << (j & 7);
	}
}

/* ---------------------------------------------------------------------- */
/*
 * Filter calc routines
 */

static const float filt_comp[32] = {
   -0.0000,   -0.0071,   -0.0144,   -0.0160,
   -0.0028,    0.0279,    0.0617,    0.0683,
    0.0208,   -0.0765,   -0.1732,   -0.1877,
   -0.0525,    0.2365,    0.5993,    0.9000,
    1.0122,    0.8857,    0.5741,    0.2060,
   -0.0825,   -0.2130,   -0.1919,   -0.0888,
    0.0138,    0.0649,    0.0606,    0.0281,
   -0.0021,   -0.0152,   -0.0139,   -0.0070
};

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

#define HAMMING(x) (0.54-0.46*cos(2*M_PI*(x)));

extern inline float hamming(float x)
{
        return 0.54-0.46*cos(2*M_PI*x);
}

extern inline float sinc(float x)
{
        if (x == 0)
                return 1;
        x *= M_PI;
        return sin(x)/x;
}

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

#if 0
static void filt_printfcoeff(const float coeff[32])
{
	int i;

	lprintf(3, "filter coefficients:");
	for (i = 0; i < 32; i++) {
		if (!(i & 7))
			lprintf(3, "\n  ");
		lprintf(3, "%6.2f", coeff[i]);
	}
	lprintf(3, '\n');
}

static void filt_printicoeff(const int coeff[32])
{
	int i;

	lprintf(3, "filter coefficients:");
	for (i = 0; i < 32; i++) {
		if (!(i & 7))
			lprintf(3, "\n  ");
		lprintf(3, "%5i", coeff[i]);
	}
	lprintf(3, '\n');
}
#endif

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

static void filt_genideal(float coeff[32])
{
	int i;

	for (i = 0; i < 32; i++)
		coeff[i] = hamming(i / 31.0) * sinc((i - 15.5) / 4.0);
}

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

static void filt_rom(const float coeff[32], unsigned rom[16])
{
	float max = 0.000001, s;
	int ocoeff[32];
	int i, j;

	/* scale filter values */
	for(i = 0; i < 4; i++) {
		for(s = 0, j = i; j < 32; j += 4)
			s += fabs(coeff[j]);
		if (s > max)
			max = s;
	}
	s = 126.0 / max;
	for(i = 0; i < 32; i++)
		ocoeff[i] = s * coeff[i];
	/* convert to internal ROM representation */
	memset(rom, 0, sizeof(rom[0])*16);
	for (i = 0; i < 8; i++)
		for (j = 0; j < 16; j++) {
			if ((1 << i) & ocoeff[j])
				rom[i] |= 1 << (((j + 1) & 3) | (j & 12));
			if ((1 << i) & ocoeff[j+16])
				rom[i+8] |= 1 << j;
		}
}

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

int adapter_start_no_firmware(void)
{
	if (FPGA_CONFIGSIZE != sizeof(epp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppchk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(ecp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppafsk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppsamp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(sppafsk_firmware))
		abort();
	if (detect_modem())
		return -3;
	return 0;
}

int adapter_start_eppchk(void)
{
	if (FPGA_CONFIGSIZE != sizeof(epp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppchk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(ecp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppafsk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppsamp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(sppafsk_firmware))
		abort();
	if (detect_modem())
		return -3;
	if (configure(eppchk_firmware, FPGA_CONFIGSIZE)) {
		reset_modem();
		return -4;
	}
	parport_write_control(LPTCTRL_PROGRAM);
	return 0;
}

int adapter_start_epp(struct adapter_config *cfg)
{
	unsigned char cfgdata[FPGA_CONFIGSIZE];
	float fcoeff[32];
	unsigned filtrom[16];
	int i;
	unsigned char null = 0;
	unsigned divider;

	if (FPGA_CONFIGSIZE != sizeof(epp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppchk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(ecp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppafsk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppsamp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(sppafsk_firmware))
		abort();
	if (detect_modem())
		return -3;
	/* calculate the divider */
	divider = (cfg->fclk + 8 * cfg->bitrate) / (16 * cfg->bitrate);
	if (divider < 1)
		divider = 1;
	if (divider > 1024)
		divider = 1024;
	cfg->bitrate = (cfg->fclk + 8 * divider) / (16 * divider);
	/* print configuration */
	printconfig(cfg);
	memcpy(cfgdata, epp_firmware, FPGA_CONFIGSIZE);
	/* patch configuration ROM's */
	if (cfg->intclk)
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_CLKSELROM, 0);
	writeconfigrom(cfgdata, epp_roms+EPP_ROM_DIVROM0, 1 << ((divider-1) & 0xf));
	writeconfigrom(cfgdata, epp_roms+EPP_ROM_DIVROM1, 1 << (((divider-1) >> 4) & 0xf));
	writeconfigrom(cfgdata, epp_roms+EPP_ROM_DIVROM2, 0x1000 << (((divider-1) >> 8) & 0x3));
	if (!cfg->extstat) {
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_STIROM, 0);
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_STOROM, 0);
	}
	if (cfg->loopback) {
		writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMID, 0xf0f0);
		writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMIC, 0xff00); /* modem output clock */
	} else if (cfg->extmodem) {
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMID, 0xcccc);
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMIC, 0x0c0c); /* RxC: rising edge */
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMOC, 0x3030); /* TxC: falling edge */
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMDCD, 0x3333); /* DCD: inverted */
		/* switch off scrambler and differential */
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMOE, 0xaaaa);
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMOS, 0xaaaa);
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMIE, 0xaaaa);
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMIS, 0xaaaa);
        }
        if (!cfg->pttmute)
                writeconfigrom(cfgdata, epp_roms+EPP_ROM_PTTMUTEROM, 0xffff); 
	if (cfg->filtmode == 1) {
		filt_rom(filt_comp, filtrom);
		for (i = 0; i < 16; i++)
			writeconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_0+16*i, filtrom[i]);
	} else if (cfg->filtmode == 2) {
		filt_genideal(fcoeff);
		filt_rom(fcoeff, filtrom);
		for (i = 0; i < 16; i++)
			writeconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_0+16*i, filtrom[i]);
	}
	/* print configuration ROM's (mainly a debugging action) */
	lprintf(2, "config: coeff: %04x %04x %04x %04x %04x %04x %04x %04x\n"
		"config: coeff: %04x %04x %04x %04x %04x %04x %04x %04x\n",
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_0),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_1),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_2),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_3),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_4),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_5),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_6),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM0_R_7),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM1_R_0),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM1_R_1),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM1_R_2),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM1_R_3),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM1_R_4),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM1_R_5),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM1_R_6),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CROM1_R_7));
	lprintf(2, "config: ClkSel: %04x  PTT mute: %04x\n", 
		readconfigrom(cfgdata, epp_roms+EPP_ROM_CLKSELROM),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_PTTMUTEROM));
	lprintf(2, "config: Div: %04x %04x %04x\n", 
		readconfigrom(cfgdata, epp_roms+EPP_ROM_DIVROM0),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_DIVROM1),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_DIVROM2));
	lprintf(2, "config: DCD: %04x\n", 
		readconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMDCD));
	lprintf(2, "config: IClk: %04x ID: %04x IE: %04x IS: %04x\n",
		readconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMIC),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMID),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMIE),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMIS));
	lprintf(2, "config: OClk: %04x OE: %04x OS: %04x\n", 
		readconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMOC),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMOE),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_SWROMOS));
	lprintf(2, "config: StI: %04x StO: %04x\n", 
		readconfigrom(cfgdata, epp_roms+EPP_ROM_STIROM),
		readconfigrom(cfgdata, epp_roms+EPP_ROM_STOROM));
	/* finally download code to FPGA */
	if (configure(cfgdata, FPGA_CONFIGSIZE)) {
		reset_modem();
		return -4;
	}
	parport_write_control(LPTCTRL_PROGRAM);
	if (parport_epp_write_addr(&null, 1) != 1) { /* reset modem */
		lprintf(0, "EPP timeout\n");
		return -1;
	}
	return 0;
}

int adapter_start_ecp(struct adapter_config *cfg)
{
	unsigned char cfgdata[FPGA_CONFIGSIZE];
	float fcoeff[32];
	unsigned filtrom[16];
	int i;
	unsigned divider;

	if (FPGA_CONFIGSIZE != sizeof(epp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppchk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(ecp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppafsk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppsamp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(sppafsk_firmware))
		abort();
	if (detect_modem())
		return -3;
	/* calculate the divider */
	divider = (cfg->fclk + 8 * cfg->bitrate) / (16 * cfg->bitrate);
	if (divider < 1)
		divider = 1;
	if (divider > 1024)
		divider = 1024;
	cfg->bitrate = (cfg->fclk + 8 * divider) / (16 * divider);
	/* print configuration */
	printconfig(cfg);
	memcpy(cfgdata, ecp_firmware, FPGA_CONFIGSIZE);
	/* patch configuration ROM's */
	if (cfg->intclk)
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_CLKSELROM, 0);
	writeconfigrom(cfgdata, ecp_roms+ECP_ROM_DIVROM0, 1 << ((divider-1) & 0xf));
	writeconfigrom(cfgdata, ecp_roms+ECP_ROM_DIVROM1, 1 << (((divider-1) >> 4) & 0xf));
	writeconfigrom(cfgdata, ecp_roms+ECP_ROM_DIVROM2, 0x1000 << (((divider-1) >> 8) & 0x3));
	if (cfg->loopback) {
		writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMID, 0xf0f0);
		writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMIC, 0xff00); /* modem output clock */
	} else if (cfg->extmodem) {
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMID, 0xcccc);
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMIC, 0x0c0c); /* RxC: rising edge */
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMOC, 0x3030); /* TxC: falling edge */
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMDCD, 0x3333); /* DCD: inverted */
		/* switch off scrambler and differential */
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMOE, 0xaaaa);
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMOS, 0xaaaa);
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMIE, 0xaaaa);
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMIS, 0xaaaa);
        }
        if (!cfg->pttmute)
                writeconfigrom(cfgdata, ecp_roms+ECP_ROM_PTTMUTEROM, 0xffff); 
	if (cfg->filtmode == 1) {
		filt_rom(filt_comp, filtrom);
		for (i = 0; i < 16; i++)
			writeconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_0+16*i, filtrom[i]);
	} else if (cfg->filtmode == 2) {
		filt_genideal(fcoeff);
		filt_rom(fcoeff, filtrom);
		for (i = 0; i < 16; i++)
			writeconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_0+16*i, filtrom[i]);
	}
	/* print configuration ROM's (mainly a debugging action) */
	lprintf(2, "config: coeff: %04x %04x %04x %04x %04x %04x %04x %04x\n"
		"config: coeff: %04x %04x %04x %04x %04x %04x %04x %04x\n",
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_0),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_1),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_2),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_3),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_4),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_5),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_6),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM0_R_7),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM1_R_0),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM1_R_1),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM1_R_2),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM1_R_3),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM1_R_4),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM1_R_5),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM1_R_6),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CROM1_R_7));
	lprintf(2, "config: ClkSel: %04x  PTT mute: %04x\n", 
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_CLKSELROM),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_PTTMUTEROM));
	lprintf(2, "config: Div: %04x %04x %04x\n", 
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_DIVROM0),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_DIVROM1),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_DIVROM2));
	lprintf(2, "config: DCD: %04x\n", 
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMDCD));
	lprintf(2, "config: IClk: %04x ID: %04x IE: %04x IS: %04x\n",
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMIC),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMID),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMIE),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMIS));
	lprintf(2, "config: OClk: %04x OE: %04x OS: %04x\n", 
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMOC),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMOE),
		readconfigrom(cfgdata, ecp_roms+ECP_ROM_SWROMOS));
	/* finally download code to FPGA */
	if (configure(cfgdata, FPGA_CONFIGSIZE)) {
		reset_modem();
		return -4;
	}
	return 0;
}

int adapter_start_eppafsk(struct adapter_config *cfg)
{
	unsigned char cfgdata[FPGA_CONFIGSIZE];
	unsigned char null = 0;
	unsigned divider;

	if (FPGA_CONFIGSIZE != sizeof(epp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppchk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(ecp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppafsk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppsamp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(sppafsk_firmware))
		abort();
	if (detect_modem())
		return -3;
	/* calculate the divider */
	divider = (cfg->fclk + 32 * cfg->bitrate) / (64 * cfg->bitrate);
	if (divider < 1)
		divider = 1;
	if (divider > 1024)
		divider = 1024;
	cfg->bitrate = (cfg->fclk + 32 * divider) / (64 * divider);
	/* print configuration */
	printconfig(cfg);
	memcpy(cfgdata, eppafsk_firmware, FPGA_CONFIGSIZE);
	/* patch configuration ROM's */
	if (cfg->intclk)
                writeconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_CLKSELROM, 0);
        if (!cfg->pttmute)
                writeconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_PTTMUTEROM, 0xffff); 
	writeconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_DIVROM0, 1 << ((divider-1) & 0xf));
	writeconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_DIVROM1, 1 << (((divider-1) >> 4) & 0xf));
	writeconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_DIVROM2, 0x1000 << (((divider-1) >> 8) & 0x3));
	/* print configuration ROM's (mainly a debugging action) */
	lprintf(2, "config: ClkSel: %04x  PTT mute: %04x\n", 
		readconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_CLKSELROM),
		readconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_PTTMUTEROM));
	lprintf(2, "config: Div: %04x %04x %04x\n", 
		readconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_DIVROM0),
		readconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_DIVROM1),
		readconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_DIVROM2));
	lprintf(2, "config: IE: %04x OE: %04x\n",
		readconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_SWROMIE),
		readconfigrom(cfgdata, eppafsk_roms+EPPAFSK_ROM_SWROMOE));
	/* finally download code to FPGA */
	if (configure(cfgdata, FPGA_CONFIGSIZE)) {
		reset_modem();
		return -4;
	}
	parport_write_control(LPTCTRL_PROGRAM);
	if (parport_epp_write_addr(&null, 1) != 1) {  /* reset modem */
		lprintf(0, "EPP timeout\n");
		return -1;
	}
	return 0;
}

int adapter_start_sppafsk(struct adapter_config *cfg)
{
	unsigned char cfgdata[FPGA_CONFIGSIZE];
	unsigned divider;

	if (FPGA_CONFIGSIZE != sizeof(epp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppchk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(ecp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppafsk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppsamp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(sppafsk_firmware))
		abort();
	if (detect_modem())
		return -3;
	/* calculate the divider */
	divider = (cfg->fclk + 32 * cfg->bitrate) / (64 * cfg->bitrate);
	if (divider < 1)
		divider = 1;
	if (divider > 1024)
		divider = 1024;
	cfg->bitrate = (cfg->fclk + 32 * divider) / (64 * divider);
	/* print configuration */
	printconfig(cfg);
	memcpy(cfgdata, sppafsk_firmware, FPGA_CONFIGSIZE);
	/* patch configuration ROM's */
	if (cfg->intclk)
                writeconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_CLKSELROM, 0);
        if (!cfg->pttmute)
                writeconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_PTTMUTEROM, 0xffff); 
	writeconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_DIVROM0, 1 << ((divider-1) & 0xf));
	writeconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_DIVROM1, 1 << (((divider-1) >> 4) & 0xf));
	writeconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_DIVROM2, 0x1000 << (((divider-1) >> 8) & 0x3));
	/* print configuration ROM's (mainly a debugging action) */
	lprintf(2, "config: ClkSel: %04x  PTT mute: %04x\n", 
		readconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_CLKSELROM),
		readconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_PTTMUTEROM));
	lprintf(2, "config: Div: %04x %04x %04x\n", 
		readconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_DIVROM0),
		readconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_DIVROM1),
		readconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_DIVROM2));
	lprintf(2, "config: IE: %04x OE: %04x\n",
		readconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_SWROMIE),
		readconfigrom(cfgdata, sppafsk_roms+SPPAFSK_ROM_SWROMOE));
	/* finally download code to FPGA */
	if (configure(cfgdata, FPGA_CONFIGSIZE)) {
		reset_modem();
		return -4;
	}
	parport_write_control(LPTCTRL_PROGRAM);
	sppafsk_wrcmd(0x00);
	return 0;
}

int adapter_start_eppsamp(struct adapter_config *cfg)
{
	unsigned char cfgdata[FPGA_CONFIGSIZE];
	int i;
	unsigned char buf;
	unsigned divider;

	if (FPGA_CONFIGSIZE != sizeof(epp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppchk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(ecp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppafsk_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(eppsamp_firmware) || 
	    FPGA_CONFIGSIZE != sizeof(sppafsk_firmware))
		abort();
	if (detect_modem())
		return -3;
	/* calculate the divider */
	divider = (cfg->fclk + cfg->bitrate / 2) / cfg->bitrate;
	if (divider < 1)
		divider = 1;
	if (divider > 4096)
		divider = 4096;
	cfg->bitrate = (cfg->fclk + divider / 2) / divider;
	/* print configuration */
	printconfig(cfg);
	memcpy(cfgdata, eppsamp_firmware, FPGA_CONFIGSIZE);
	/* patch configuration ROM's */
	if (cfg->intclk)
                writeconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_CLKSELROM, 0);
	writeconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_DIVROM0, 1 << ((divider-1) & 0xf));
	writeconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_DIVROM1, 1 << (((divider-1) >> 4) & 0xf));
	writeconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_DIVROM2, 1 << (((divider-1) >> 8) & 0xf));
	/* calculate the gain */
	i = (cfg->gain << 24) / divider;
	if (i > 65535)
		i = 65535;
	if (i < 1)
		i = 1;
	writeconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_MULROM, i);
       	/* print configuration ROM's (mainly a debugging action) */
	lprintf(2, "config: ClkSel: %04x  InputGain: %04x\n", 
		readconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_CLKSELROM),
		readconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_MULROM));
	lprintf(2, "config: Div: %04x %04x %04x\n", 
		readconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_DIVROM0),
		readconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_DIVROM1),
		readconfigrom(cfgdata, eppsamp_roms+EPPSAMP_ROM_DIVROM2));
	/* finally download code to FPGA */
	if (configure(cfgdata, FPGA_CONFIGSIZE)) {
		reset_modem();
		return -4;
	}
	parport_write_control(LPTCTRL_PROGRAM);
	/* set Modem Disconnect Data Output register to 0x1f */
	buf = 6;
	if (parport_epp_write_addr(&buf, 1) != 1)
		goto epperr;
	buf = 0x1f;
	if (parport_epp_write_data(&buf, 1) != 1)
		goto epperr;
	/* set Modem Disconnect Tristate register to 0x17 */
	buf = 6;
	if (parport_epp_write_addr(&buf, 1) != 1)
		goto epperr;
	buf = 0x17;
	if (parport_epp_write_data(&buf, 1) != 1)
		goto epperr;
	/* reset modem */
	buf = 0x1f;
	if (parport_epp_write_addr(&buf, 1) != 1)
		goto epperr;
	return 0;

epperr:
	lprintf(0, "EPP timeout\n");
	return -1;	
}

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