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

/*
 *      common.c  --  Common routines needed by driver and clients.
 *
 *      Copyright (C) 2000-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  19.09.2000  Created
 *   0.2  09.03.2001  Now requiring T7F firmware V1.44
 *
 */

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

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

#define _REENTRANT

#include "trx.h"

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

void addrxfifo(struct trx_thread_state *ctrl, const unsigned char *rp, unsigned int rlen)
{
        struct rxuartfifo *fifo = &ctrl->rfifo;
	unsigned int sz, ptr;
	
        while (rlen > 0) {
		ptr = fifo->wr % sizeof(fifo->buf);
		sz = sizeof(fifo->buf) - ptr;
		if (sz > rlen)
			sz = rlen;
		memcpy(&fifo->buf[ptr], rp, sz);
                rp += sz;
                rlen -= sz;
                fifo->wr += sz;
	}
}

unsigned int getrxfifo(struct trx_thread_state *ctrl, unsigned int *ptr, unsigned char *p, unsigned int len)
{
        struct rxuartfifo *fifo = &ctrl->rfifo;
	unsigned int sz, pt, pt2, cnt = 0;
	signed int diff;
	
	pt = fifo->wr - *ptr;
	if (pt > sizeof(fifo->buf))
		pt = sizeof(fifo->buf);
	pt = fifo->wr - pt;
        while (len > 0) {
		diff = fifo->wr - pt;
		if (diff <= 0)
			break;
		pt2 = pt % sizeof(fifo->buf);
		sz = sizeof(fifo->buf) - pt2;
		if (sz > len)
			sz = len;
		if (sz > diff)
			sz = diff;
		memcpy(p, &fifo->buf[pt2], sz);
                p += sz;
                len -= sz;
                pt += sz;
		cnt += sz;
	}
	*ptr = pt;
	return cnt;
}

int addtxfifo(struct trx_thread_state *ctrl, const unsigned char *tp, unsigned int tlen)
{
        struct txuartfifo *fifo = &ctrl->tfifo;
        unsigned int len;

	len = (sizeof(fifo->buf) - 1 + fifo->rd - fifo->wr) % sizeof(fifo->buf);
	if (tlen > len)
		return -len;
	while (tlen > 0) {
		len = tlen;
		if (fifo->wr + len > sizeof(fifo->buf))
			len = sizeof(fifo->buf) - fifo->wr;
		memcpy(&fifo->buf[fifo->wr], tp, len);
		fifo->wr = (fifo->wr + len) % sizeof(fifo->buf);
		tp += len;
		tlen -= len;
	}
	return 0;
}

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

int checkreload_adapt(struct trx_thread_state *servant, struct trxapi_baycomusb_adapter_config *cfg)
{
	struct trxapi_baycomusb_adapter_config *cfgo = &servant->cfg.adapt;

	if (cfgo->mode != cfg->mode || cfgo->fclk != cfg->fclk)
		return 1;
	if (cfg->mode == trxapi_baycomusb_mode_fsk ||
	    cfg->mode == trxapi_baycomusb_mode_external ||
	    cfg->mode == trxapi_baycomusb_mode_afsk) {
		if (cfgo->bitraterx != cfg->bitraterx ||
		    cfgo->bitratetx != cfg->bitratetx ||
		    cfgo->loopback != cfg->loopback ||
		    cfgo->pttmute != cfg->pttmute ||
		    cfgo->filtmode != cfg->filtmode)
			return 1;
	} else if (cfg->mode == trxapi_baycomusb_mode_audio) {
		if (cfgo->samplerate != cfg->samplerate ||
		    cfgo->gain != cfg->gain ||
		    strcmp(cfgo->audiodevin, cfg->audiodevin) ||
		    strcmp(cfgo->audiodevout, cfg->audiodevout))
			return 1;
	}
	return 0;
}

int checkreload_mdisc(struct trx_thread_state *servant, struct trxapi_modem_disconnect_config *cfg)
{
	struct trxapi_modem_disconnect_config *cfgo = &servant->cfg.mdisc;

	if (cfgo->rxc != cfg->rxc)
		return 1;
	if (cfgo->txc != cfg->txc)
		return 1;
	if (cfgo->txd != cfg->txd)
		return 1;
	return 0;
}

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

static int setfreq(struct trx_thread_state *state)
{
	unsigned int rxf, txf, fifoptr;
	unsigned char buff[16];
	unsigned char buf[256];
	int r;

	acquire_trxdata_mutex(state);
	fifoptr = state->rfifo.wr;
	rxf = state->newrxdiv;
	txf = state->newtxdiv;
	snprintf(buff, sizeof(buff), "\rC?%03d%03d\r", txf, rxf);
	if (!(state->flags & FLG_T7FERROR) && (state->flags & FLG_MANUALPTT)) {
		state->flags |= FLG_SETPTT | FLG_INHIBITSETPTT | FLG_CLRPTT;
		release_trxdata_mutex(state);
		for (r = 0; r < 20; r++) {
			if (polldevice(state))
				return -1;
			if (!state->ptt)
				break;
		}
		acquire_trxdata_mutex(state);
	}
	addtxfifo(state, buff, 10);
	release_trxdata_mutex(state);
	for (;;) {
		r = getuartline(state, &fifoptr, buf, sizeof(buf), 5000);
		if (r <= 0) {
			acquire_trxdata_mutex(state);
			state->flags |= FLG_SETFREQ | FLG_T7FERROR;
			state->flags &= ~FLG_INHIBITSETPTT;
			release_trxdata_mutex(state);
			return r;
		}
		if (!memcmp(buf, buff+1, 8)) {
			acquire_trxdata_mutex(state);
			state->t7fport = 0;
			state->flags |= FLG_SETT7FPORT | FLG_SAVECONFIG;
			state->flags &= ~(FLG_T7FERROR | FLG_INHIBITSETPTT);
			state->rxdiv = rxf;
			state->txdiv = txf;
			release_trxdata_mutex(state);
			return 0;
		}
	}
}

static int checktransceiver(struct trx_thread_state *state)
{
	unsigned int fifoptr;
	unsigned char buff[16];
	unsigned char buf[256];
	unsigned char *cp;
	int r;

	acquire_trxdata_mutex(state);
	fifoptr = state->rfifo.wr;
	snprintf(buff, sizeof(buff), "\rV\r");
	addtxfifo(state, buff, 10);
	release_trxdata_mutex(state);
	for (;;) {
		r = getuartline(state, &fifoptr, buf, sizeof(buf), 2500);
		if (r < 0)
			return r;
		if (!r)
			return -1;
		if (buf[0] == 'V') {
			cp = buf + 1;
			while (*cp != 0 && *cp != '\r' && *cp != '\n')
				cp++;
			*cp = 0;
			lprintf(2, "T7F Firmware Version: %s\n", buf+1);
			return 0;
		}
	}
}

static void mainloop(struct trx_thread_state *state)
{
	int r;

	while (!check_quit()) {
		acquire_trxdata_mutex(state);
		if (state->flags & FLG_SAVECONFIG) {
		        state->flags &= ~FLG_SAVECONFIG;
			config_savedevice(state);
			release_trxdata_mutex(state);
			config_save();
			continue;
		}
		if (state->flags & FLG_RELOADMODEM) {
			state->flags &= ~FLG_RELOADMODEM;
			state->flags |= FLG_MODEMERROR | FLG_T7FERROR;
			release_trxdata_mutex(state);
			netif_close(state);
                        state->rssi = 0;
                        state->dcd = state->ptt = 0;
			/* need to reset the adapter first to get descriptors with bulk pipes for FPGA download */
			r = adapter_reset(state->usbdev);
#if 0
			if (r) {
				sleep(1);
				r = adapter_reset(state->usbdev);
			}
#endif
			if (r) {
				lprintf(1, "Error resetting adapter: %d\n", r);
				return;
			}
			r = reopen_usbdev(state);
			if (r) {
				lprintf(1, "Error reopening USB device: %d\n", r);
				return;
			}
			r = adapter_finalinit(state);
			if (r) {
				lprintf(1, "Error during final initialization of adapter: %d\n", r);
				return;
			}
			r = netif_open(state);
			if (r) {
#if defined(WIN32) || defined(USERMODEDRV)
#define STR ""
#else
#define STR " (baycom_usb.o loaded?)"
#endif
				lprintf(1, "Error during network interface opening" STR ": %d\n", r);
#undef STR
				return;
			}
			acquire_trxdata_mutex(state);
			state->flags &= ~FLG_MODEMERROR;
			state->flags |= FLG_RECONFINTF | FLG_RECONFCHACC | FLG_SETMDISCDIR | FLG_SETMDISCOUT | FLG_SETT7FPORT | FLG_SETFREQ | FLG_SAVECONFIG;
			release_trxdata_mutex(state);
			r = checktransceiver(state);
			if (r)
				r = checktransceiver(state);
			if (r) {
				state->flags |= FLG_T7FERROR;
				lprintf(1, "Error while checking the T7F firmware\n");
			}
			continue;
		}
		if (state->flags & FLG_SETFREQ) {
			state->flags &= ~FLG_SETFREQ;
			release_trxdata_mutex(state);
			/* do it twice since T7F apparently doesn't get it the first time... */
			if (setfreq(state))
				return;
			if (setfreq(state))
				return;
			continue;
		}
		release_trxdata_mutex(state);
		if (polldevice(state))
			return;
	}
}

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

void trx_thread(struct trx_thread_state *state)
{
	trxapi_frequency_t rx, tx;

	config_loaddevice(state);
	rx = state->cfg.freq.rx;
	tx = state->cfg.freq.tx;
	if (rx < 430000000 || rx > 440000000)
		rx = 433350000;
	if (tx < 430000000 || tx > 440000000)
		tx = 433350000;
	state->newrxdiv = (rx-430000000+12500/2)/12500;
	state->newtxdiv = (tx-430000000+12500/2)/12500;
	init_object(state);
	init_shmem_object(state);
	mainloop(state);
	destroy_shmem_object(state);
	destroy_object(state);
}

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