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

/*
 *	configadapt.c  --  Baycom USB modem utility.
 *
 *	Copyright (C) 1999-2001
 *          Thomas Sailer (t.sailer@alumni.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.
 *
 *
 *  History:
 *   0.1  26.05.99  Created
 *
 */

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

#define _GNU_SOURCE
#include <sys/types.h>
//#ifdef HAVE_SYS_IOCTL_H
//#include <sys/ioctl.h>
//#endif
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <math.h>

#include "baycomusb.h"
#include "usbdrv.h"
#include "fpga/fsk_fpga.h"
#include "fpga/fsk_firmware.h"
#include "fpga/afsk_firmware.h"
#include "fpga/sample_firmware.h"
#include "firmware/reset2firmware.h"
#include "firmware/dl2firmware.h"
#include "firmware/bscanfirmware.h"
#include "firmware/fskfirmware.h"
#include "firmware/afskfirmware.h"
#include "firmware/audiofirmware.h"

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

static void printconfig(const struct configentry *cfg)
{
	lprintf(1, "configuration for serial \"%s\"\n"
		"  adapter: mode %s, fclk %u Hz, bitrate %u/%u, loopback %u, filtmode %u gain %u\n"
		"  channel access: txdelay %ums, slottime %ums, ppersistence %u txtail %ums, %sduplex\n"
		"  interface: ifname %s, hwaddr %s, ipaddr %s, netmask %s, broadcast %s\n"
		"  modem disconnect: output 0x%02x, direction 0x%02x\n"
		"  t7f: control 0x%02x\n",
		cfg->serial,
		(cfg->adapter.mode == MODE_FSK) ? "fsk" :
		(cfg->adapter.mode == MODE_EXTERNAL) ? "external" :
		(cfg->adapter.mode == MODE_AFSK) ? "afsk" :
		(cfg->adapter.mode == MODE_AUDIO) ? "audio" :
		(cfg->adapter.mode == MODE_BSCAN) ? "bscan" : "off", 
		cfg->adapter.fclk, cfg->adapter.bitraterx, cfg->adapter.bitratetx, 
		cfg->adapter.loopback, cfg->adapter.filtmode, cfg->adapter.gain,
		cfg->chaccess.txdelay, cfg->chaccess.slottime, cfg->chaccess.ppersistence,
		cfg->chaccess.txtail, cfg->chaccess.fullduplex ? "full" : "half",
		cfg->ifcfg.ifname, cfg->ifcfg.hwaddr, cfg->ifcfg.ipaddr, cfg->ifcfg.netmask, cfg->ifcfg.broadcast,
		cfg->mdisc.output, cfg->mdisc.direction, cfg->t7f.ctrl);
}

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

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;
                }
}

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

static int writecpucs(struct usbdevice *dev, unsigned char buf)
{
        int r;

        r = usb_control_msg(dev, 0x40, 0xa0, 0x7F92, 0, 1, &buf, 5000);
        if (r != 1) {
                lprintf(2, "ezusb: writecpucs(0x%02x) failed %d\n", buf, r);
                return -1;
        }
        return 0;
}

static int loadmem(struct usbdevice *dev, const unsigned char *fw, unsigned int len)
{
        unsigned addr, sz;
        int r;

        if (writecpucs(dev, 1))
                return -1;
        for (addr = 0; addr < len; addr += 64) {
                sz = len - addr;
                if (sz > 64)
                        sz = 64;
                r = usb_control_msg(dev, 0x40, 0xa0, addr, 0, sz, (void *)(fw+addr), 5000);
                if (r != sz) {
                        lprintf(2, "ezusb: code download(sz=%d,addr=0x%04x) returned %d\n", sz, addr, r);
                        return -1;
                }
        }
        return 0;
}

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

static int displaymem(struct usbdevice *dev)
{
        unsigned char buf[16];
        unsigned addr, u;
        int r;

        printf("EZUSB Memory contents:\n");
        for (addr = 0; addr < 0x2000; addr += 16) {
                r = usb_control_msg(dev, 0xc0, 0xa0, addr, 0, 16, buf, 5000);
                if (r != 16) {
                        lprintf(2, "ezusb: code upload(sz=%d,addr=0x%04x) returned %d\n", 16, addr, r);
                        return -1;
                }
                printf("%04x:", addr);
                for (u = 0; u < 16; u++)
                        printf(" %02x", buf[u]);
                printf("\n");
        }
        r = usb_control_msg(dev, 0xc0, 0xa0, 0x7F92, 0, 1, buf, 5000);
        if (r != 1) {
                lprintf(2, "ezusb: readcpucs returned %d\n", r);
                return -1;
        }
        printf("CPUCS: %02x\n", buf[0]);
        return 0;
}

static int displaycpucs(struct usbdevice *dev)
{
        unsigned char buf;
        int r;

        r = usb_control_msg(dev, 0xc0, 0xa0, 0x7F92, 0, 1, &buf, 5000);
        if (r != 1) {
                lprintf(2, "ezusb: readcpucs returned %d\n", r);
                return -1;
        }
        printf("CPUCS: %02x\n", buf);
        return 0;
}

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

int adapter_reset(struct usbdevice *dev)
{
        /* first download the loader firmware to the AnchorChips core */
        if (loadmem(dev, reset2firmware, sizeof(reset2firmware)) ||
            /* start AnchorChips core */
            writecpucs(dev, 0))
                return -1;
        return 0;
}

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

struct anfwstat {
	unsigned char errcode;
	unsigned char errval;
	unsigned count;
};

static int anchor_fw_stat(struct usbdevice *dev, struct anfwstat *st)
{
 	unsigned char buf[4];
	int r;

	r = usb_control_msg(dev, 0xc0, 0xb1, 0, 0, 4, buf, 5000);
	if (r != 4) {
		lprintf(2, "usb_control_msg(0xb1) error %s\n", strerror(errno));
		return -1;
	}
	lprintf(4, "ezusb: anchorstat: errcode 0x%02x  errval 0x%02x  cfgcntr 0x%04x\n",
		buf[0], buf[1], buf[2] | (buf[3] << 8));
	st->errcode = buf[0];
	st->errval = buf[1];
	st->count = buf[2] | (buf[3] << 8);
	return 0;
}

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

static unsigned int prepare_fsk(struct usbdevice *dev, struct configentry *cfg, const char *serial, unsigned char *fpga, unsigned char *fw)
{
        float fcoeff[32];
        unsigned filtrom[16];
        unsigned dividerrx, dividertx;
        unsigned int i;

	/* copy FPGA firmware */
        memcpy(fpga, fsk_firmware, FPGA_CONFIGSIZE);
	memcpy(fw, fskfirmware, sizeof(fskfirmware));
	strncpy(&fw[0xf0], serial, 0x10);
	fw[0xe3] = cfg->adapter.mode == MODE_EXTERNAL;
	fw[0xe4] = cfg->adapter.pttmute;
        /* calculate the divider */
        dividerrx = (cfg->adapter.fclk + 8 * cfg->adapter.bitraterx) / (16 * cfg->adapter.bitraterx);
        if (dividerrx < 1)
                dividerrx = 1;
        if (dividerrx > 1024)
                dividerrx = 1024;
        cfg->adapter.bitraterx = (cfg->adapter.fclk + 8 * dividerrx) / (16 * dividerrx);
        dividertx = (cfg->adapter.fclk + 8 * cfg->adapter.bitratetx) / (16 * cfg->adapter.bitratetx);
        if (dividertx < 1)
                dividertx = 1;
        if (dividertx > 1024)
                dividertx = 1024;
        cfg->adapter.bitratetx = (cfg->adapter.fclk + 8 * dividertx) / (16 * dividertx);
	fw[0xe0] = cfg->adapter.bitratetx;
	fw[0xe1] = cfg->adapter.bitratetx >> 8;
	fw[0xe2] = cfg->adapter.bitratetx >> 16;
	fw[0xe3] = cfg->adapter.bitraterx;
	fw[0xe4] = cfg->adapter.bitraterx >> 8;
	fw[0xe5] = cfg->adapter.bitraterx >> 16;
	/* patch configuration ROM's */
#if 0
        if (cfg->adapter.intclk)
                writeconfigrom(fpga, fsk_roms+FSK_ROM_CLKSELROM, 0);
#endif
	writeconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMRX0, 1 << ((dividerrx-1) & 0xf));
	writeconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMRX1, 1 << (((dividerrx-1) >> 4) & 0xf));
	writeconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMRX2, 0x1000 << (((dividerrx-1) >> 8) & 0x3));
	writeconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMTX0, 1 << ((dividertx-1) & 0xf));
	writeconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMTX1, 1 << (((dividertx-1) >> 4) & 0xf));
	writeconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMTX2, 0x1000 << (((dividertx-1) >> 8) & 0x3));
	if (cfg->adapter.mode == MODE_EXTERNAL) {
                writeconfigrom(fpga, fsk_roms+FSK_ROM_SWROMID, 0xcccc);
                writeconfigrom(fpga, fsk_roms+FSK_ROM_SWROMIC, 0x0c0c); /* RxC: rising edge */
                writeconfigrom(fpga, fsk_roms+FSK_ROM_SWROMOC, 0x3030); /* TxC: falling edge */
                /* switch off scrambler and differential */
                writeconfigrom(fpga, fsk_roms+FSK_ROM_SWROMOE, 0xaaaa);
                writeconfigrom(fpga, fsk_roms+FSK_ROM_SWROMOS, 0xaaaa);
                writeconfigrom(fpga, fsk_roms+FSK_ROM_SWROMIE, 0xaaaa);
                writeconfigrom(fpga, fsk_roms+FSK_ROM_SWROMIS, 0xaaaa);
	} else {
		switch (cfg->adapter.loopback) {
		case 1:
			writeconfigrom(fpga, fsk_roms+FSK_ROM_SWROMID, 0xf0f0);
			writeconfigrom(fpga, fsk_roms+FSK_ROM_SWROMIC, 0xff00); /* modem output clock */
			break;
			
		case 2:
			writeconfigrom(fpga, fsk_roms+FSK_ROM_MDMINROM, 0xcccc);
			break;
		}
        }
        if (cfg->adapter.filtmode == 1) {
                filt_rom(filt_comp, filtrom);
                for (i = 0; i < 16; i++)
                        writeconfigrom(fpga, fsk_roms+FSK_ROM_CROM0_R_0+16*i, filtrom[i]);
        } else if (cfg->adapter.filtmode == 2) {
                filt_genideal(fcoeff);
                filt_rom(fcoeff, filtrom);
                for (i = 0; i < 16; i++)
                        writeconfigrom(fpga, fsk_roms+FSK_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(fpga, fsk_roms+FSK_ROM_CROM0_R_0),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM0_R_1),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM0_R_2),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM0_R_3),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM0_R_4),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM0_R_5),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM0_R_6),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM0_R_7),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM1_R_0),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM1_R_1),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM1_R_2),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM1_R_3),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM1_R_4),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM1_R_5),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM1_R_6),
                readconfigrom(fpga, fsk_roms+FSK_ROM_CROM1_R_7));
#if 0
        lprintf(2, "config: ClkSel: %04x  PTT mute: %04x\n", 
                readconfigrom(fpga, fsk_roms+FSK_ROM_CLKSELROM),
                readconfigrom(fpga, fsk_roms+FSK_ROM_PTTMUTEROM));
        lprintf(2, "config: DCD: %04x\n", 
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMDCD));
#endif
        lprintf(2, "config: DivRx: %04x %04x %04x\n", 
                readconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMRX0),
                readconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMRX1),
                readconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMRX2));
        lprintf(2, "config: DivTx: %04x %04x %04x\n", 
                readconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMTX0),
                readconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMTX1),
                readconfigrom(fpga, fsk_roms+FSK_ROM_DIVROMTX2));
        lprintf(2, "config: IClk: %04x ID: %04x IE: %04x IS: %04x\n",
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMIC),
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMID),
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMIE),
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMIS));
        lprintf(2, "config: OClk: %04x OE: %04x OS: %04x\n", 
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMOC),
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMOE),
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMOS));
        lprintf(2, "config: MDISC: RxC: %04x TxC: %04x TxD: %04x\n", 
                readconfigrom(fpga, fsk_roms+FSK_ROM_MDISCROMRXC),
                readconfigrom(fpga, fsk_roms+FSK_ROM_MDISCROMTXC),
                readconfigrom(fpga, fsk_roms+FSK_ROM_MDISCROMTXD));
        lprintf(2, "config: Modem: in: %04x\n", 
                readconfigrom(fpga, fsk_roms+FSK_ROM_MDMINROM));
	return sizeof(fskfirmware);
}

static unsigned int prepare_afsk(struct usbdevice *dev, struct configentry *cfg, const char *serial, unsigned char *fpga, unsigned char *fw)
{
        cfg->adapter.bitraterx = cfg->adapter.bitratetx = 1200;
	/* copy FPGA firmware */
        memcpy(fpga, afsk_firmware, FPGA_CONFIGSIZE);
	memcpy(fw, afskfirmware, sizeof(afskfirmware));
	strncpy(&fw[0xf0], serial, 0x10);
	fw[0xe0] = cfg->adapter.bitratetx;
	fw[0xe1] = cfg->adapter.bitratetx >> 8;
	fw[0xe2] = cfg->adapter.bitratetx >> 16;
	fw[0xe3] = cfg->adapter.bitraterx;
	fw[0xe4] = cfg->adapter.bitraterx >> 8;
	fw[0xe5] = cfg->adapter.bitraterx >> 16;
	fw[0xe6] = 0;
	fw[0xe7] = cfg->adapter.pttmute;
        /* patch configuration ROM's */
	/* only swromie/swromoe could be patched, for differential decoding */
        /* print configuration ROM's (mainly a debugging action) */
        lprintf(2, "config: IE: %04x OE: %04x\n",
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMIE),
                readconfigrom(fpga, fsk_roms+FSK_ROM_SWROMOE));
	return sizeof(afskfirmware);
}

static unsigned int prepare_audio(struct usbdevice *dev, struct configentry *cfg, const char *serial, unsigned char *fpga, unsigned char *fw)
{
        unsigned divider;
        unsigned int i;

	/* copy FPGA firmware */
        memcpy(fpga, sample_firmware, FPGA_CONFIGSIZE);
	memcpy(fw, audiofirmware, sizeof(audiofirmware));
	strncpy(&fw[0xf0], serial, 0x10);
	/* calculate the true bitrate (aka samplerate) */
	cfg->adapter.samplerate = (cfg->adapter.samplerate + 500) / 1000 * 1000;
	if (cfg->adapter.samplerate < 8000 && cfg->adapter.samplerate != 2000 && cfg->adapter.samplerate != 3000)
                cfg->adapter.samplerate = 8000;
        if (cfg->adapter.samplerate > 24000)
                cfg->adapter.samplerate = 24000;
	divider = (24000000 + cfg->adapter.samplerate/2) / cfg->adapter.samplerate;
	fw[0xe0] = cfg->adapter.samplerate / 1000;
        /* calculate the gain */
        i = (cfg->adapter.gain << 24) / divider;
        if (i > 65535)
                i = 65535;
        if (i < 1)
                i = 1;
        /* patch configuration ROM's */
        writeconfigrom(fpga, sample_roms+SAMPLE_ROM_MULROM, i);
        /* print configuration ROM's (mainly a debugging action) */
        lprintf(2, "config: InputGain: %04x\n",
                readconfigrom(fpga, sample_roms+SAMPLE_ROM_MULROM));
	return sizeof(audiofirmware);
}

static unsigned int prepare_bscan(struct usbdevice *dev, const struct configentry *cfg, const char *serial, unsigned char *fpga, unsigned char *fw)
{
	unsigned int i;

	memcpy(fw, bscanfirmware, sizeof(bscanfirmware));
	for (i = 0; i < 6; i++) {
		if (*serial)
			fw[0xf2+2*i] = *serial++;
		else
			fw[0xf2+2*i] = '_';
	}
	return sizeof(bscanfirmware);
}

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

static int fpgadownload(struct usbdevice *dev)
{
	unsigned char stat[5];
	unsigned u, ln;
	struct configentry *cfg;
	char serial[16];
	unsigned char fpga[FPGA_CONFIGSIZE], firmware[8192];
	unsigned int fwlen;
	struct anfwstat anstat;
	int i;

	/* download the loader firmware to the AnchorChips core */
	if (loadmem(dev, dl2firmware, sizeof(dl2firmware)))
		return -1;
	/* start AnchorChips core */
	if (writecpucs(dev, 0))
		return -1;
	if (usb_setconfiguration(dev, 1))
		return -1;
	/* retrieve serial number from EEPROM */
	i = usb_control_msg(dev, 0xc0, 0xb3, 0, 0, sizeof(serial)-1, serial, 5000);
	if (i < 0 || i >= sizeof(serial)) {
		lprintf(2, "usb_control_msg(0xc0,0xb3) error %s\n", strerror(errno));
		serial[0] = 0;
	} else
		serial[i] = 0;
	cfg = config_lookup(serial);
	lprintf(1, "Serial number read: %s using configuration: %s  (mode %u)\n", serial, cfg->serial, cfg->adapter.mode);
	if (!serial[0])
		strncpy(serial, "000000", sizeof(serial));
	switch (cfg->adapter.mode) {
	case MODE_FSK:
	case MODE_EXTERNAL:
		fwlen = prepare_fsk(dev, cfg, serial, fpga, firmware);
		break;

	case MODE_AFSK:
		fwlen = prepare_afsk(dev, cfg, serial, fpga, firmware);
		break;

	case MODE_AUDIO:
		fwlen = prepare_audio(dev, cfg, serial, fpga, firmware);
		break;

	case MODE_BSCAN:
		fwlen = prepare_bscan(dev, cfg, serial, fpga, firmware);
		goto downloadfw;

	default:
		return -1;
	}
	printconfig(cfg);
	/* claim interface 0 */
	if (usb_claiminterface(dev, 0)) {
		lprintf(2, "FPGAdownload: cannot claim interface 0\n");
		return -1;
	}
	/* set interface 0 altsetting 1, to enable the bulk ep's */
	if (usb_setinterface(dev, 0, 1)) {
		lprintf(2, "FPGAdownload: cannot set interface 0 altsetting 1\n");
		return -1;
	}
	if (usb_resetep(dev, 0x81)) {
		lprintf(2, "FPGAdownload: cannot reset ep1 in\n");
		//return -1;
	}
	if (usb_resetep(dev, 0x02)) {
		lprintf(2, "FPGAdownload: cannot reset ep2 out\n");
		//return -1;
	}
#if 1
	i = usb_control_msg(dev, 0xc0, 0xb1, 0, 0, 4, stat, 5000);
	if (i != 4)
		lprintf(2, "usb_control_msg(0xc0, 0xb1) failure (%d)\n", i);
	else
		lprintf(3, "FPGAdownload: stat: 0x%02x 0x%02x 0x%02x 0x%02x\n", stat[0], stat[1], stat[2], stat[3]);
#endif
	/* download FPGA firmware using bulk transfers */
	for (;;) {
#if 1
		i = usb_bulk_msg(dev, 0x81, sizeof(stat), stat, 5000);
#else
		i = usb_control_msg(dev, 0xc0, 0xb2, 0, 0, 5, stat, 5000);
#endif
		if (i != sizeof(stat)) {
			lprintf(2, "usb_bulk ep1 in failure (%d)\n", i);
#if 1
			i = usb_control_msg(dev, 0xc0, 0xb1, 0, 0, 4, stat, 5000);
			if (i != 4)
				lprintf(2, "usb_control_msg(0xc0, 0xb1) failure (%d)\n", i);
			else
				lprintf(3, "FPGAdownload: stat: 0x%02x 0x%02x 0x%02x 0x%02x\n", stat[0], stat[1], stat[2], stat[3]);
#endif
			return -1;
		}
		lprintf(3, "FPGAdownload: stat: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", stat[0], stat[1], stat[2], stat[3], stat[4]);
		if (stat[0] == 0x10)
			break;
		if (stat[0] & 0x80) {
			lprintf(2, "FPGA download error code 0x%02x\n", stat[0]);
			return -1;
		}
        }
#define MAXSZ 256
	for (u = 0; u < FPGA_CONFIGSIZE; ) {
		ln = FPGA_CONFIGSIZE - u;
		if (ln > MAXSZ)
			ln = MAXSZ;
		i = usb_bulk_msg(dev, 0x02, ln, (void *)(fpga+u), 5000);
		if (i != ln) {
			lprintf(2, "usb_bulk_msg ep2 out failure (%d,len=%u,addr=%u)\n", i, ln, u);
			return -1;
		}
		u += ln;
#if 1
		i = usb_bulk_msg(dev, 0x81, sizeof(stat), stat, 5000);
#else
		i = usb_control_msg(dev, 0xc0, 0xb2, 0, 0, 5, stat, 5000);
#endif
		if (i != sizeof(stat)) {
			lprintf(2, "usb_bulk_msg ep1 in failure (%d)\n", i);
			return -1;
		}
	}
	for (;;) {
#if 1
		i = usb_bulk_msg(dev, 0x81, sizeof(stat), stat, 5000);
#else
		i = usb_control_msg(dev, 0xc0, 0xb2, 0, 0, 5, stat, 5000);
#endif
		if (i != sizeof(stat)) {
			lprintf(2, "usb_bulk_msg ep1 in failure (%d)\n", i);
			return -1;
		}
		lprintf(3, "FPGAdownload: stat: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", stat[0], stat[1], stat[2], stat[3], stat[4]);
		if (stat[0] == 0x20)
			break;
		if (stat[0] & 0x80 || stat[0] == 0x10) {
			lprintf(2, "FPGA download error code 0x%02x\n", stat[0]);
			return -1;
		}
        }
 downloadfw:
	/* download the firmware to the AnchorChips core */
	if (loadmem(dev, firmware, fwlen))
		return -1;
	/* start AnchorChips core */
	if (writecpucs(dev, 0))
		return -1;
	if (cfg->adapter.mode != MODE_BSCAN)
		return 0;
	for (;;) {
		if (anchor_fw_stat(dev, &anstat))
			return -1;
		if (anstat.errcode == 0x20)
			break;
		if (anstat.errcode != 0)
			return -1;
	}
	return 1;
}

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

int adapter_configure(struct usbdevice *dev)
{
	int i;
	unsigned char mode;
	char serial[16];
	struct configentry *cfg;
	unsigned char buf[16];
	struct anfwstat stat;

        if (FPGA_CONFIGSIZE != sizeof(fsk_firmware) ||
	    FPGA_CONFIGSIZE != sizeof(afsk_firmware) ||
	    FPGA_CONFIGSIZE != sizeof(sample_firmware))
                abort();
	/* check status of configuration */
	i = usb_control_msg(dev, 0xc0, 0xc8, 0, 0, 1, &mode, 500);
	if (i != 1) {
		lprintf(2, "usb_control_msg(0xc0,0xc8) error %s\n", strerror(errno));
		return fpgadownload(dev);
	}
	i = usb_control_msg(dev, 0xc0, 0xc9, 0, 0, sizeof(serial)-1, serial, 500);
	if (i < 0 || i >= sizeof(serial)) {
		lprintf(2, "usb_control_msg(0xc0,0xc9) error %s\n", strerror(errno));
		serial[0] = 0;
	} else
		serial[i] = 0;
	cfg = config_lookup(serial);
	lprintf(1, "Serial number read: %s using configuration: %s\n", serial, cfg->serial);
	switch (mode) {
	case MODE_BSCAN:
		for (;;) {
			if (anchor_fw_stat(dev, &stat))
				return -1;
			if (stat.errcode == 0x20)
				break;
			if (stat.errcode != 0)
				return -1;
		}
		return 1;

	case MODE_FSK:
	case MODE_AFSK:
	case MODE_AUDIO:
		if (usb_control_msg(dev, 0xc0, 0xd4, cfg->mdisc.output, 1, 3, buf, 500) != 3 ||
		    usb_control_msg(dev, 0xc0, 0xd4, cfg->mdisc.direction, 2, 3, buf, 500) != 3 ||
		    usb_control_msg(dev, 0xc0, 0xd4, 0, 0, 3, buf, 500) != 3) {
			lprintf(2, "usb_control_msg(0xc0,0xd4) error %s\n", strerror(errno));
			return -1;
		}
		lprintf(1, "Modem disconnect port: Input 0x%02x Output 0x%02x Direction 0x%02x\n", buf[0], buf[1], buf[2]);
		/* fall through */

	case MODE_EXTERNAL:
		if (usb_control_msg(dev, 0xc0, 0xd5, cfg->t7f.ctrl, 1, 2, buf, 500) != 2 ||
		    usb_control_msg(dev, 0xc0, 0xd5, 0, 0, 2, buf, 500) != 2) {
			lprintf(2, "usb_control_msg(0xc0,0xd5) error %s\n", strerror(errno));
			return -1;
		}
		lprintf(1, "T7F port: Input 0x%02x Output 0x%02x\n", buf[0], buf[1]);
		/* claim interfaces */
		if (usb_setconfiguration(dev, 1))
			return -1;
		/* kludge now in baycom_usb driver */
		if (mode != MODE_AUDIO) {
			if (usb_claiminterface(dev, 0)) {
				lprintf(2, "usb_claiminterface(2) error %s\n", strerror(errno));
			} else {
				if (usb_setinterface(dev, 0, 1)) {
					lprintf(2, "usb_setinterface(0,1) error %s\n", strerror(errno));
					return -1;
				}
				/* kludge: the first OUT2 packet seems to arrive corrupted, so send this special "magic" packet */
				buf[0] = 0x55;
				if (usb_bulk_msg(dev, 2, 1, buf, 500) != 1) {
					lprintf(2, "usb_bulk_msg(2) error %s\n", strerror(errno));
					return -1;
				}
				usb_releaseinterface(dev, 0);
			}
		}
#if 0
		i = (mode == MODE_AUDIO) ? 4 : 0;  /* interface */
		if (usb_claiminterface(dev, 0))
			return -1;
		if (usb_setinterface(dev, i, 1))
			return -1;
#endif
		return 1;

	default:
		return -1;
	}
}

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

struct configentry *adapter_findconfig(struct usbdevice *dev)
{
	int i;
	char serial[16];
	struct configentry *cfg;

	i = usb_control_msg(dev, 0xc0, 0xc9, 0, 0, sizeof(serial)-1, serial, 500);
	if (i < 0 || i >= sizeof(serial)) {
		lprintf(2, "usb_control_msg(0xc0,0xc9) error %s\n", strerror(errno));
		serial[0] = 0;
	} else
		serial[i] = 0;
	cfg = config_lookup(serial);
	lprintf(1, "Serial number read: %s using configuration: %s\n", serial, cfg->serial);
	return cfg;
}
