/* AM7910 / AM7911 modem chip emulation and signal processing * * (C) 2019 by Andreas Eversberg * All Rights Reserved * * 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 3 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, see . */ /* * Not implemented: * - auto answer * - Bell 202 5 bps back channel * - equalizer */ /* * Implementation is done according to the AM7910/AM7911 datasheet. This is * only a (short and bad) summary, so read the datasheet!!! * * DTR state: * When DTR is off, state is clamped to INIT state. * When DTR becomes on, RX and TX state machines begin to run. * When DTR becomes off, state machines change to INIT state immediately. * * (B)RTS state: * When (B)RTS becomes on, data transmission is enabled. * Then (B)CTS becomes on, when the timer (B)RCON is complete. * This means that data transmission is now allowed by upper layer. * When (B)RTS becomes off, data transmission is disabled. * Then (B)CTS becomes off, when the timer (B)RCOFF is complete. * * (B)CD state: * When carrier is detected, timer (B)CDON is started. * When carrier is stable and timer is complete, (B)CD becomes on and data * reception is enabled. * When carrier is lost, timer (B)CDOFF is started. * When carrier timer is complete, (B)CD becomes off and data reception is * disabled. * While transmitting half duplex mode, (B)CD will be blocked to prevent * carrier detection from loopback of audio signal. * * (B)TD data: * When transmitting, data is requested from upper layer and forwarded into * modulator. * When not transmitting, this data is blocked, meaning that '1' (MARK) is * transmitted into the modulator, regardless of the upper layer data. * STO (soft turn off) and/or silence is sent after transmission is over. * * (B)RD data: * When data reception is not blocked, data is received from the demodulator * and forwarded towards upper layer. * While receiving in half duplex mode, (B)RD is blocked, meaning that '1' * (MARK) is forwarded toward upper layer, regardless of the data from the * demodulator. * Squelch (mute receive audio) is used to prevent noise when turning off * half duplex transmission. * * Audio level is based on milliwatts (at 600 Ohms), which is a value of 1. */ #include #include #include #include #include #include #include "../libdebug/debug.h" #include "../libtimer/timer.h" #include "../libsample/sample.h" #include "../libfsk/fsk.h" #include "am791x.h" #define db2level(db) pow(10, (double)(db) / 20.0) #define level2db(level) (20 * log10(level)) /* levels used (related to dBm0 (1 mW)) */ #define TX_LEVEL -3 /* according to datasheet (at 600 Ohms) */ #define RX_CD_ON_7910 -40.5 /* according to datasheet (at 600 Ohms) */ #define RX_CD_ON_7911 -42.0 /* according to datasheet (at 600 Ohms) */ #define RX_CD_OFF_7910 -45.0 /* according to datasheet (at 600 Ohms) */ #define RX_CD_OFF_7911 -47.5 /* according to datasheet (at 600 Ohms) */ #define RX_QUALITY 0.1 /* FIXME: minimum quality */ #define BIT_ADJUST 0.5 /* must be 0.5 to completely sync each bit */ /* frequencies used */ /* standard f0, f1 (tx) f0, f1 (rx) f0, f1 (back tx+rx) */ #define BELL_103_ORIG 1070, 1270, 2025, 2225, 0, 0, #define BELL_103_ANS 2025, 2225, 1070, 1270, 0, 0, #define BELL_103_ORIG_L 1070, 1270, 1070, 1270, 0, 0, #define BELL_103_ANS_L 2025, 2225, 2025, 2225, 0, 0, #define CCITT_V21_ORIG 1180, 980, 1850, 1650, 0, 0, #define CCITT_V21_ANS 1850, 1650, 1180, 980, 0, 0, #define CCITT_V21_ORG_L 1180, 980, 1180, 980, 0, 0, #define CCITT_V21_ANS_L 1850, 1650, 1850, 1650, 0, 0, #define CCITT_V23_M1 1700, 1300, 1700, 1300, 0, 0, #define CCITT_V23_M1B 1700, 1300, 1700, 1300, 450, 390, #define CCITT_V23_M2 2100, 1300, 2100, 1300, 0, 0, #define CCITT_V23_M2B 2100, 1300, 2100, 1300, 450, 390, #define BELL_202 2200, 1200, 2200, 1200, 0, 0, #define BELL_202B 2200, 1200, 2200, 1200, 487, 387, #define CCITT_V23_BACK 0, 0, 0, 0, 450, 390, #define BELL_202_BACK 0, 0, 0, 0, 487, 387, #define RESERVED 0, 0, 0, 0, 0, 0, /* timings used */ /* timer/std 7910 7911 */ #define T_RCON_B103 0.2083, 0.025, #define T_RCOFF_B103 0.0004, 0.00052, #define T_BRCON_B103 NAN, NAN, #define T_BRCOFF_B103 NAN, NAN, #define T_CDON_B103 0.0291, 0.010, #define T_CDOFF_B103 0.021, 0.007, #define T_BCDON_B103 NAN, NAN, #define T_BCDOFF_B103 NAN, NAN, #define T_AT_B103 1.9, 1.9, #define T_SIL1_B103 1.3, 2.0, #define T_SIL2_B103 NAN, NAN, #define T_SQ_B103 NAN, NAN, #define T_STO_B103 NAN, NAN, #define T_B103 \ T_RCON_B103 T_RCOFF_B103 T_BRCON_B103 T_BRCOFF_B103 \ T_CDON_B103 T_CDOFF_B103 T_BCDON_B103 T_BCDOFF_B103 \ T_AT_B103 T_SIL1_B103 T_SIL2_B103 T_SQ_B103 T_STO_B103 /* timer/std 7910 7911 */ #define T_RCON_V21 0.400, 0.025, #define T_RCOFF_V21 0.0004, 0.00052, #define T_BRCON_V21 NAN, NAN, #define T_BRCOFF_V21 NAN, NAN, #define T_CDON_V21 0.301, 0.010, #define T_CDOFF_V21 0.021, 0.007, #define T_BCDON_V21 NAN, NAN, #define T_BCDOFF_V21 NAN, NAN, #define T_AT_V21 3.0, 3.0, #define T_SIL1_V21 1.9, 2.0, #define T_SIL2_V21 NAN, 0.075, #define T_SQ_V21 NAN, NAN, #define T_STO_V21 NAN, NAN, #define T_V21 \ T_RCON_V21 T_RCOFF_V21 T_BRCON_V21 T_BRCOFF_V21 \ T_CDON_V21 T_CDOFF_V21 T_BCDON_V21 T_BCDOFF_V21 \ T_AT_V21 T_SIL1_V21 T_SIL2_V21 T_SQ_V21 T_STO_V21 /* timer/std 7910 7911 */ #define T_RCON_V23 0.2083, 0.008, #define T_RCOFF_V23 0.0004, 0.00052, #define T_BRCON_V23 0.0823, 0.0823, #define T_BRCOFF_V23 0.0004, 0.0005, #define T_CDON_V23 0.011, 0.003, #define T_CDOFF_V23 0.0035, 0.002, #define T_BCDON_V23 0.017, 0.018, #define T_BCDOFF_V23 0.021, 0.022, #define T_AT_V23 3.0, 3.0, #define T_SIL1_V23 1.9, 2.0, #define T_SIL2_V23 NAN, 0.075, #define T_SQ_V23 0.1563, 0.009, #define T_STO_V23 NAN, 0.008, #define T_V23 \ T_RCON_V23 T_RCOFF_V23 T_BRCON_V23 T_BRCOFF_V23 \ T_CDON_V23 T_CDOFF_V23 T_BCDON_V23 T_BCDOFF_V23 \ T_AT_V23 T_SIL1_V23 T_SIL2_V23 T_SQ_V23 T_STO_V23 /* timer/std 7910 7911 */ #define T_RCON_B202 0.1833, 0.008, #define T_RCOFF_B202 0.0004, 0.00052, #define T_BRCON_B202 0.0823, 0.0823, #define T_BRCOFF_B202 0.0004, 0.0005, #define T_CDON_B202 0.018, 0.003, #define T_CDOFF_B202 0.012, 0.002, #define T_BCDON_B202 0.017, 0.018, #define T_BCDOFF_B202 0.021, 0.022, #define T_AT_B202 1.9, 1.9, #define T_SIL1_B202 1.3, 2.0, #define T_SIL2_B202 NAN, NAN, #define T_SQ_B202 0.1563, 0.009, #define T_STO_B202 0.024, 0.008, #define T_B202 \ T_RCON_B202 T_RCOFF_B202 T_BRCON_B202 T_BRCOFF_B202 \ T_CDON_B202 T_CDOFF_B202 T_BCDON_B202 T_BCDOFF_B202 \ T_AT_B202 T_SIL1_B202 T_SIL2_B202 T_SQ_B202 T_STO_B202 #define T_RES \ NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, \ NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, \ NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, /* mode definition */ static struct am791x_mode { int sup_7910, sup_7911; /* supported */ int f0_tx, f1_tx; /* frequencies */ int f0_rx, f1_rx; int f0_back, f1_back; double t_rcon_7910, t_rcon_7911; /* timers */ double t_rcoff_7910, t_rcoff_7911; double t_brcon_7910, t_brcon_7911; double t_brcoff_7910, t_brcoff_7911; double t_cdon_7910, t_cdon_7911; double t_cdoff_7910, t_cdoff_7911; double t_bcdon_7910, t_bcdon_7911; double t_bcdoff_7910, t_bcdoff_7911; double t_at_7910, t_at_7911; double t_sil1_7910, t_sil1_7911; double t_sil2_7910, t_sil2_7911; double t_sq_7910, t_sq_7911; double t_sto_7910, t_sto_7911; int fullduplex; /* duplex */ int loopback_main, loopback_back; /* loopback */ int equalizer, sto; /* equalizer, soft turn off */ int bell_202; /* is BELL 202 mode */ double max_baud; /* maximum baud rate */ const char *description; /* description */ } am791x_modes[32] = { /*sup frequencies timers duplex loop EQ,STO BELL202 maxBAUD description */ /* normal modes */ { 1, 1, BELL_103_ORIG T_B103 1, 0, 0, 0, 0, 0, 300, "Bell 103 originate (300 bps full-duplex)" }, { 1, 1, BELL_103_ANS T_B103 1, 0, 0, 0, 0, 1, 300, "Bell 103 answer (300 bps full-duplex)" }, { 1, 1, BELL_202 T_B202 0, 0, 0, 0, 0, 1, 1200, "Bell 202 (1200 bps half-duplex)" }, { 1, 1, BELL_202 T_B202 0, 0, 0, 1, 0, 1, 1200, "Bell 202 with equalizer (1200 bps half-duplex)" }, { 1, 1, CCITT_V21_ORIG T_V21 1, 0, 0, 0, 0, 0, 300, "CCITT V.21 originate (300 bps full-duplex)" }, { 1, 1, CCITT_V21_ANS T_V21 1, 0, 0, 0, 0, 0, 300, "CCITT V.21 answer (300 bps full-duplex)" }, { 1, 1, CCITT_V23_M2 T_V23 0, 0, 0, 0, 0, 0, 1200, "CCITT V.23 mode 2 (1200 bps half-duplex)" }, { 1, 1, CCITT_V23_M2 T_V23 0, 0, 0, 1, 0, 0, 1200, "CCITT V.23 mode 2 with equalizer (1200 bps half-duplex)" }, { 1, 1, CCITT_V23_M1B T_V23 0, 0, 0, 0, 0, 0, 1200, "CCITT V.23 mode 1 (600/75 bps half-duplex)" }, { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, { 0, 1, BELL_202B T_B202 0, 0, 0, 0, 0, 1, 1200, "Bell 202 (1200/150 bps half-duplex)" }, { 0, 1, BELL_202B T_B202 0, 0, 0, 1, 0, 1, 1200, "Bell 202 with equalizer (1200/150 bps half-duplex)" }, { 0, 1, CCITT_V23_M1B T_V23 0, 0, 0, 0, 1, 0, 1200, "CCITT V.23 mode 1 with soft turn-off (600/75 bps half-duplex)" }, { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, { 0, 1, CCITT_V23_M2B T_V23 0, 0, 0, 0, 1, 0, 1200, "CCITT V.23 mode 2 with soft turn-off (1200/75 bps half-duplex)" }, { 0, 1, CCITT_V23_M2B T_V23 0, 0, 0, 1, 1, 0, 1200, "CCITT V.23 mode 2 with soft turn-off and equalizer (1200/75 bps half-duplex)" }, /* loopback modes */ { 1, 1, BELL_103_ORIG_L T_B103 0, 1, 0, 0, 0, 0, 300, "Bell 103 orig loopback (300 bps)" }, { 1, 1, BELL_103_ANS_L T_B103 0, 1, 0, 0, 0, 0, 300, "Bell 103 answer loopback (300 bps)" }, { 1, 1, BELL_202 T_B202 0, 1, 0, 0, 0, 1, 1200, "Bell 202 main loopback (1200 bps)" }, { 1, 1, BELL_202 T_B202 0, 1, 0, 1, 0, 1, 1200, "Bell 202 main loopback with equalizer (1200 bps)" }, { 1, 1, CCITT_V21_ORG_L T_V21 0, 1, 0, 0, 0, 0, 300, "CCITT V.21 originate loopback (300 bps)" }, { 1, 1, CCITT_V21_ANS_L T_V21 0, 1, 0, 0, 0, 0, 300, "CCITT V.21 answer loopback (300 bps)" }, { 1, 1, CCITT_V23_M2 T_V23 0, 1, 0, 0, 0, 0, 1200, "CCITT V.23 mode 2 main loopback (1200 bps)" }, { 1, 1, CCITT_V23_M2 T_V23 0, 1, 0, 1, 0, 0, 1200, "CCITT V.23 mode 2 main loopback with equalizer (1200 bps)" }, { 1, 1, CCITT_V23_M1 T_V23 0, 1, 0, 0, 0, 0, 1200, "CCITT V.23 mode 1 main loopback (600 bps)" }, { 1, 1, CCITT_V23_BACK T_V23 0, 0, 1, 0, 0, 0, 150, "CCITT V.23 back loopback (75/150 bps)" }, { 0, 1, BELL_202_BACK T_B202 0, 0, 1, 0, 0, 1, 150, "Bell 202 back loopback (150 bps)" }, { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, { 0, 0, RESERVED T_RES 0, 0, 0, 0, 0, 0, 0, "Reserved" }, }; const char *am791x_state_names[] = { "INIT", "RCON", "CDON", "DATA", "RCOFF", "CDOFF", "STO_OFF", "SQ_OFF", "BRCON", "BCDON", "BDATA", "BRCOFF", "BCDOFF", }; /* list all modes */ void am791x_list_mc(enum am791x_type type) { int i; const char *description; for (i = 0; i < 32; i++) { if ((!type && am791x_modes[i].sup_7910) || (type && am791x_modes[i].sup_7911)) description = am791x_modes[i].description; else description = am791x_modes[31].description; printf("mc %d: %s\n", i, description); } } /* init STO signal */ void init_sto(am791x_t *am791x) { am791x->sto_phaseshift65536 = 900 / (double)am791x->samplerate * 65536.0; } /* transmit STO signal, use phase from FSK modulator, to avoid phase jumps */ int send_sto(am791x_t *am791x, sample_t *sample, int length) { fsk_mod_t *fsk = &am791x->fsk_tx; int count = 0; double phase, phaseshift; phase = fsk->tx_phase65536; /* modulate STO */ phaseshift = am791x->sto_phaseshift65536; while (count < length && fsk->tx_bitpos < 1.0) { sample[count++] = fsk->sin_tab[(uint16_t)phase]; phase += phaseshift; if (phase >= 65536.0) phase -= 65536.0; } fsk->tx_phase65536 = phase; return count; } /* send audio from FSK modulator */ void am791x_send(am791x_t *am791x, sample_t *samples, int length) { if (am791x->tx_sto) send_sto(am791x, samples, length); else { if (am791x->f0_tx) { /* if filter set (even if tx_silence, we want to request bits from upper layer) */ fsk_mod_send(&am791x->fsk_tx, samples, length, 0); } if (!am791x->f0_tx || am791x->tx_silence) { /* if filter not set, or silence */ memset(samples, 0, length * sizeof(*samples)); } } } /* receive audio and feed into FSK demodulator */ void am791x_receive(am791x_t *am791x, sample_t *samples, int length) { if (!am791x->f0_rx) { /* no mode set */ memset(samples, 0, length * sizeof(*samples)); return; } if (am791x->squelch) { /* handle squelch, but then demod... */ memset(samples, 0, length * sizeof(*samples)); } if (am791x->f0_rx) { /* handle RX audio */ fsk_demod_receive(&am791x->fsk_rx, samples, length); } } /* provide bit to FSK modulator */ static int fsk_send_bit(void *inst) { am791x_t *am791x = (am791x_t *)inst; int bit, bbit; bit = am791x->td_cb(am791x->inst); bbit = am791x->btd_cb(am791x->inst); /* main channel returns TD */ if (!am791x->block_td) { #ifdef HEAVY_DEBUG PDEBUG(DDSP, DEBUG_DEBUG, "Modulating bit '%d' for MAIN channel\n", bit); #endif return bit; } /* back channel returns BTD */ if (!am791x->block_btd) { #ifdef HEAVY_DEBUG PDEBUG(DDSP, DEBUG_DEBUG, "Modulating bit '%d' for BACK channel\n", bbit); #endif return bbit; } #ifdef HEAVY_DEBUG PDEBUG(DDSP, DEBUG_DEBUG, "Modulating bit '1', because TD & BTD is ignored\n"); #endif return 1; } static void handle_rx_state(am791x_t *am791x); /* get bit from FSK demodulator */ static void fsk_receive_bit(void *inst, int bit, double quality, double level) { am791x_t *am791x = (am791x_t *)inst; int *block, *cd; #ifdef HEAVY_DEBUG PDEBUG(DDSP, DEBUG_DEBUG, "Demodulated bit '%d' (level = %.0f dBm, quality = %%%.0f)\n", bit, level2db(level), quality * 100.0); #endif if (!am791x->rx_back_channel) { block = &am791x->block_cd; cd = &am791x->cd; } else { block = &am791x->block_bcd; cd = &am791x->bcd; } /* detection/loss of carrier */ if (*block && *cd) { *cd = 0; handle_rx_state(am791x); } else if (!(*block) && !(*cd) && level > am791x->cd_on && quality >= RX_QUALITY) { PDEBUG(DDSP, DEBUG_DEBUG, "Good quality (level = %.0f dBm, quality = %%%.0f)\n", level2db(level), quality * 100.0); *cd = 1; handle_rx_state(am791x); } else if (*cd && (level < am791x->cd_off || quality < RX_QUALITY)) { PDEBUG(DDSP, DEBUG_DEBUG, "Bad quality (level = %.0f dBm, quality = %%%.0f)\n", level2db(level), quality * 100.0); *cd = 0; handle_rx_state(am791x); } /* assume 1 if no carrier */ if (!(*cd)) { bit = 1; quality = 0; level = 0; } /* main channel forwards bit to RD */ if (!am791x->block_rd) { #ifdef HEAVY_DEBUG PDEBUG(DDSP, DEBUG_DEBUG, " -> Forwarding bit '%d' to MAIN channel\n", bit); #endif am791x->rd_cb(am791x->inst, bit, quality * 100.0, level2db(level)); } else { #ifdef HEAVY_DEBUG PDEBUG(DDSP, DEBUG_DEBUG, " -> Forwarding bit '1' to MAIN channel, because RD is set to MARK\n"); #endif am791x->rd_cb(am791x->inst, 1, NAN, NAN); } /* main channel forwards bit to RD */ if (!am791x->block_brd) { #ifdef HEAVY_DEBUG PDEBUG(DDSP, DEBUG_DEBUG, " -> Forwarding bit '%d' to BACK channel\n", bit); #endif am791x->brd_cb(am791x->inst, bit, quality * 100.0, level2db(level)); } else { #ifdef HEAVY_DEBUG PDEBUG(DDSP, DEBUG_DEBUG, " -> Forwarding bit '1' to BACK channel, because BRD is set to MARK\n"); #endif am791x->brd_cb(am791x->inst, 1, NAN, NAN); } } /* setup FSK */ static void set_filters(am791x_t *am791x) { const char *name_tx = NULL, *name_rx = NULL; int f0_tx = -1, f1_tx = -1; int f0_rx = -1, f1_rx = -1; uint8_t mc = am791x->mc; /* not supported */ if (!((am791x->type) ? am791x_modes[mc].sup_7911 : am791x_modes[mc].sup_7910)) { f0_tx = 0; f1_tx = 0; f0_rx = 0; f1_rx = 0; } else switch (am791x->tx_state) { case AM791X_STATE_INIT: /* when RTS and BRTS are not asserted */ f0_tx = 0; // TX !! f1_tx = 0; name_tx = "MAIN"; if (am791x->loopback_back) { /* loopback (BACK): always listens to back channel */ f0_rx = am791x_modes[mc].f0_back; // RX !! f1_rx = am791x_modes[mc].f1_back; name_rx = "BACK"; am791x->rx_back_channel = 1; } else { /* listen to main channel's RX frequencies */ f0_rx = am791x_modes[mc].f0_rx; // RX !! f1_rx = am791x_modes[mc].f1_rx; name_rx = "MAIN"; am791x->rx_back_channel = 0; } break; case AM791X_STATE_RCON: /* when asserting RTS */ f0_tx = am791x_modes[mc].f0_tx; f1_tx = am791x_modes[mc].f1_tx; name_tx = "MAIN"; if (!am791x->loopback_main && am791x_modes[mc].f0_back && am791x_modes[mc].f1_back) { /* switch receiver to back channel, (if not in loopback mode) */ f0_rx = am791x_modes[mc].f0_back; f1_rx = am791x_modes[mc].f1_back; name_rx = "BACK"; am791x->rx_back_channel = 1; } break; case AM791X_STATE_BRCON: /* when asserting BRTS */ f0_tx = am791x_modes[mc].f0_back; f1_tx = am791x_modes[mc].f1_back; name_tx = "BACK"; break; default: /* keep current frequencies in other state */ return; } /* transmitter not used anymore or has changed */ if (f0_tx >= 0 && am791x->f0_tx && (f0_tx != am791x->f0_tx || f1_tx != am791x->f1_tx)) { /* disable transmitter */ fsk_mod_cleanup(&am791x->fsk_tx); am791x->f0_tx = 0; am791x->f1_tx = 0; } /* transmitter used */ if (f0_tx > 0 && am791x->f0_tx == 0) { PDEBUG(DDSP, DEBUG_DEBUG, "Setting modulator to %s channel's frequencies (F0 = %d, F1 = %d), baudrate %.0f\n", name_tx, f0_tx, f1_tx, am791x->tx_baud); if (fsk_mod_init(&am791x->fsk_tx, am791x, fsk_send_bit, am791x->samplerate, am791x->tx_baud, (double)f0_tx, (double)f1_tx, am791x->tx_level, 0, 1) < 0) PDEBUG(DDSP, DEBUG_ERROR, "FSK RX init failed!\n"); else { am791x->f0_tx = f0_tx; am791x->f1_tx = f1_tx; } } /* receiver has changed */ if (f0_rx >= 0 && am791x->f0_rx && (f0_rx != am791x->f0_rx || f1_rx != am791x->f1_rx)) { /* disable receiver */ fsk_demod_cleanup(&am791x->fsk_rx); am791x->f0_rx = 0; am791x->f1_rx = 0; } /* receiver used */ if (f0_rx > 0 && am791x->f0_rx == 0) { PDEBUG(DDSP, DEBUG_DEBUG, "Setting demodulator to %s channel's frequencies (F0 = %d, F1 = %d), baudrate %.0f\n", name_rx, f0_rx, f1_rx, am791x->rx_baud); if (fsk_demod_init(&am791x->fsk_rx, am791x, fsk_receive_bit, am791x->samplerate, am791x->rx_baud, (double)f0_rx, (double)f1_rx, BIT_ADJUST) < 0) PDEBUG(DDSP, DEBUG_ERROR, "FSK RX init failed!\n"); else { am791x->f0_rx = f0_rx; am791x->f1_rx = f1_rx; } } } /* new state */ static void new_tx_state(am791x_t *am791x, enum am791x_st state) { if (am791x->tx_state != state) PDEBUG(DAM791X, DEBUG_DEBUG, "Change TX state %s -> %s\n", am791x_state_names[am791x->tx_state], am791x_state_names[state]); am791x->tx_state = state; } static void new_rx_state(am791x_t *am791x, enum am791x_st state) { if (am791x->rx_state != state) PDEBUG(DAM791X, DEBUG_DEBUG, "Change RX state %s -> %s\n", am791x_state_names[am791x->rx_state], am791x_state_names[state]); am791x->rx_state = state; } /* new flags */ static void set_flag(int *flag_p, int value, const char *name) { if (*flag_p != value) { PDEBUG(DAM791X, DEBUG_DEBUG, " -> %s\n", name); *flag_p = value; } } /* * state machine according to datasheet */ static void go_main_channel_tx(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Enable transmitter on main channel\n"); /* only block RD, if not full duplex and not 4-wire (loopback mode) */ if (!am791x->fullduplex && !am791x->loopback_main) { set_flag(&am791x->block_rd, 1, "RD = MARK"); set_flag(&am791x->block_cd, 1, "SET CD HIGH"); } /* activate TD now and set CTS timer (RCON) */ set_flag(&am791x->block_td, 0, "TD RELEASED"); set_flag(&am791x->tx_silence, 0, "RESET SILENCE"); timer_start(&am791x->tx_timer, am791x->t_rcon); new_tx_state(am791x, AM791X_STATE_RCON); set_filters(am791x); /* check CD to be blocked */ if (!am791x->fullduplex && !am791x->loopback_main) { if (am791x->line_cd) { /* send CD off */ am791x->cd_cb(am791x->inst, 0); } } } static void rcon_release_rts(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "RTS was released\n"); set_flag(&am791x->block_td, 1, "TD IGNORED"); set_flag(&am791x->tx_silence, 1, "SET SILENCE"); set_flag(&am791x->block_cd, 0, "RELEASE CD"); new_tx_state(am791x, AM791X_STATE_INIT); set_filters(am791x); /* check CD to be released */ if (am791x->line_cd) { /* send CD on */ am791x->cd_cb(am791x->inst, 1); } } static void rcon_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Transmission started\n"); new_tx_state(am791x, AM791X_STATE_DATA); /* CTS on */ am791x->cts_cb(am791x->inst, 1); } static void tx_data_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "RTS was released\n"); new_tx_state(am791x, AM791X_STATE_RCOFF); set_flag(&am791x->block_td, 1, "TD IGNORED"); if (am791x->sto) { set_flag(&am791x->tx_sto, 1, "start STO"); } else { set_flag(&am791x->tx_silence, 1, "SET SILENCE (if not STO)"); } if (!am791x->fullduplex) { set_flag(&am791x->squelch, 1, "SET SQUELCH (ON)"); } timer_start(&am791x->tx_timer, am791x->t_rcoff); } static void rcoff_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Transmission over\n"); /* CTS off */ am791x->cts_cb(am791x->inst, 0); if (am791x->fullduplex) { new_tx_state(am791x, AM791X_STATE_INIT); set_filters(am791x); return; } if (!am791x->sto) { timer_start(&am791x->tx_timer, am791x->t_sq - am791x->t_rcoff); new_tx_state(am791x, AM791X_STATE_SQ_OFF); return; } timer_start(&am791x->tx_timer, am791x->t_sto - am791x->t_rcoff); new_tx_state(am791x, AM791X_STATE_STO_OFF); } static void sq_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Squelch over\n"); set_flag(&am791x->block_cd, 0, "CD RELEASED"); new_tx_state(am791x, AM791X_STATE_INIT); set_filters(am791x); /* SET SQUELCH OFF */ set_flag(&am791x->squelch, 0, "SET SQUELCH OFF"); if (am791x->line_cd) { /* send CD on */ am791x->cd_cb(am791x->inst, 1); } } static void sto_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "STO over\n"); set_flag(&am791x->tx_sto, 0, "stop STO"); timer_start(&am791x->tx_timer, am791x->t_sq - am791x->t_sto); new_tx_state(am791x, AM791X_STATE_SQ_OFF); } static void go_back_channel_tx(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Enable transmitter on back channel\n"); if (!am791x->loopback_back) { set_flag(&am791x->block_brd, 1, "BRD = MARK"); set_flag(&am791x->block_bcd, 1, "SET BCD HIGH"); } /* activate BTD now and set BCTS timer (BRCON) */ set_flag(&am791x->block_btd, 0, "BTD RELEASED"); set_flag(&am791x->tx_silence, 0, "RESET SILENCE"); timer_start(&am791x->tx_timer, am791x->t_brcon); new_tx_state(am791x, AM791X_STATE_BRCON); set_filters(am791x); /* check BCD to be blocked */ if (!am791x->loopback_back) { if (am791x->line_bcd) { /* send BCD off */ am791x->bcd_cb(am791x->inst, 0); } } } static void brcon_release_brts(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "BRTS was released\n"); set_flag(&am791x->tx_silence, 1, "SET SILENCE"); new_tx_state(am791x, AM791X_STATE_INIT); set_filters(am791x); } static void brcon_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Transmission started\n"); new_tx_state(am791x, AM791X_STATE_BDATA); /* BCTS on */ am791x->bcts_cb(am791x->inst, 1); } static void tx_bdata_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "BRTS was released\n"); set_flag(&am791x->block_btd, 1, "BTD IGNORED"); set_flag(&am791x->tx_silence, 1, "SET SILENCE"); timer_start(&am791x->tx_timer, am791x->t_brcoff); } static void brcoff_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Transmission over\n"); /* BCTS off */ am791x->bcts_cb(am791x->inst, 0); set_flag(&am791x->block_bcd, 0, "BCD RELEASED"); new_tx_state(am791x, AM791X_STATE_INIT); set_filters(am791x); /* check BCD to be released */ if (am791x->line_bcd) { /* send BCD on */ am791x->bcd_cb(am791x->inst, 1); } } static void handle_tx_state(am791x_t *am791x) { switch (am791x->tx_state) { /* depending on the states we change to "main TX " or to "back TX" state */ case AM791X_STATE_INIT: /* select TX on main or back channel, according to states */ if (am791x->line_brts) { if (am791x->loopback_back) { go_back_channel_tx(am791x); break; } if (!am791x->line_rts && !am791x->loopback_main && !am791x->fullduplex) { go_back_channel_tx(am791x); break; } } if (!am791x->loopback_back && am791x->line_rts) { go_main_channel_tx(am791x); break; } break; /* all main channel states ... */ case AM791X_STATE_RCON: if (!am791x->line_rts) { rcon_release_rts(am791x); break; } if (!timer_running(&am791x->tx_timer)) { rcon_done(am791x); break; } break; case AM791X_STATE_DATA: if (!am791x->line_rts) { tx_data_done(am791x); break; } break; case AM791X_STATE_RCOFF: if (!timer_running(&am791x->tx_timer)) { rcoff_done(am791x); break; } break; case AM791X_STATE_STO_OFF: if (!timer_running(&am791x->tx_timer)) { sto_done(am791x); break; } break; case AM791X_STATE_SQ_OFF: if (!timer_running(&am791x->tx_timer)) { sq_done(am791x); break; } break; /* all back channel states */ case AM791X_STATE_BRCON: if (!am791x->line_brts) { brcon_release_brts(am791x); break; } if (!timer_running(&am791x->tx_timer)) { brcon_done(am791x); break; } break; case AM791X_STATE_BDATA: if (!am791x->line_brts) { tx_bdata_done(am791x); break; } break; case AM791X_STATE_BRCOFF: if (!timer_running(&am791x->tx_timer)) { brcoff_done(am791x); break; } break; default: PDEBUG(DAM791X, DEBUG_ERROR, "State %s not handled!\n", am791x_state_names[am791x->rx_state]); } } static void go_main_channel_rx(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Enable receiver on main channel\n"); timer_start(&am791x->rx_timer, am791x->t_cdon); new_rx_state(am791x, AM791X_STATE_CDON); } static void cdon_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Reception started\n"); set_flag(&am791x->block_rd, 0, "RD RELEASED"); new_rx_state(am791x, AM791X_STATE_DATA); set_flag(&am791x->line_cd, 1, "set CD"); /* check CD not blocked */ if (!am791x->block_cd) { /* send CD on */ am791x->cd_cb(am791x->inst, 1); } } static void cdon_no_cd(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier is gone\n"); timer_stop(&am791x->rx_timer); new_rx_state(am791x, AM791X_STATE_INIT); } static void rx_data_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier lost\n"); timer_start(&am791x->rx_timer, am791x->t_cdoff); new_rx_state(am791x, AM791X_STATE_CDOFF); } static void cdoff_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Reception finished\n"); set_flag(&am791x->block_rd, 1, "RD = MARK"); new_rx_state(am791x, AM791X_STATE_INIT); set_flag(&am791x->line_cd, 0, "release CD"); /* check CD not blocked */ if (!am791x->block_cd) { /* send CD off */ am791x->cd_cb(am791x->inst, 0); } } static void cdoff_cd(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier recovered\n"); timer_stop(&am791x->rx_timer); new_rx_state(am791x, AM791X_STATE_DATA); } static void go_back_channel_rx(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Enable receiver on back channel\n"); timer_start(&am791x->rx_timer, am791x->t_bcdon); new_rx_state(am791x, AM791X_STATE_BCDON); } static void bcdon_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier was detected\n"); set_flag(&am791x->block_brd, 0, "BRD RELEASED"); new_rx_state(am791x, AM791X_STATE_BDATA); set_flag(&am791x->line_bcd, 1, "set BCD"); /* check BCD not blocked */ if (!am791x->block_bcd) { /* send BCD on */ am791x->bcd_cb(am791x->inst, 1); } } static void bcdon_no_cd(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier is gone\n"); timer_stop(&am791x->rx_timer); new_rx_state(am791x, AM791X_STATE_INIT); } static void rx_bdata_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier lost\n"); timer_start(&am791x->rx_timer, am791x->t_bcdoff); new_rx_state(am791x, AM791X_STATE_BCDOFF); } static void bcdoff_done(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Reception finished\n"); if (!am791x->bell_202) set_flag(&am791x->block_brd, 1, "BRD = MARK"); new_rx_state(am791x, AM791X_STATE_INIT); set_flag(&am791x->line_bcd, 0, "release BCD"); /* check BCD not blocked */ if (!am791x->block_bcd) { /* send BCD off */ am791x->bcd_cb(am791x->inst, 0); } } static void bcdoff_cd(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Carrier recovered\n"); timer_stop(&am791x->rx_timer); new_rx_state(am791x, AM791X_STATE_BDATA); } static void handle_rx_state(am791x_t *am791x) { switch (am791x->rx_state) { case AM791X_STATE_INIT: /* select RX on main or back channel, according to states */ if (!am791x->loopback_back) { if (am791x->cd) { go_main_channel_rx(am791x); break; } if (!am791x->loopback_main && !am791x->fullduplex && am791x->bcd) { go_back_channel_rx(am791x); break; } } else { if (am791x->bcd) { go_back_channel_rx(am791x); break; } } break; /* all main channel states ... */ case AM791X_STATE_CDON: if (!timer_running(&am791x->rx_timer)) { cdon_done(am791x); break; } if (!am791x->cd) { cdon_no_cd(am791x); break; } break; case AM791X_STATE_DATA: if (!am791x->cd) { rx_data_done(am791x); break; } break; case AM791X_STATE_CDOFF: if (!timer_running(&am791x->rx_timer)) { cdoff_done(am791x); break; } if (am791x->cd) { cdoff_cd(am791x); break; } break; /* all back channel states ... */ case AM791X_STATE_BCDON: if (!timer_running(&am791x->rx_timer)) { bcdon_done(am791x); break; } if (!am791x->bcd) { bcdon_no_cd(am791x); break; } break; case AM791X_STATE_BDATA: if (!am791x->bcd) { rx_bdata_done(am791x); break; } break; case AM791X_STATE_BCDOFF: if (!timer_running(&am791x->rx_timer)) { bcdoff_done(am791x); break; } if (am791x->bcd) { bcdoff_cd(am791x); break; } break; default: PDEBUG(DAM791X, DEBUG_ERROR, "State %s not handled!\n", am791x_state_names[am791x->rx_state]); } } /* handle both (rx and tx) states */ static void handle_state(am791x_t *am791x) { /* DTR blocks all */ if (!am791x->line_dtr) { /* do reset of all states, if not in INIT state */ if (am791x->tx_state != AM791X_STATE_INIT || am791x->rx_state != AM791X_STATE_INIT) am791x_reset(am791x); return; } /* handle states if DTR is on */ handle_tx_state(am791x); handle_rx_state(am791x); } /* timeout events */ static void tx_timeout(struct timer *timer) { am791x_t *am791x = (am791x_t *)timer->priv; handle_tx_state(am791x); } static void rx_timeout(struct timer *timer) { am791x_t *am791x = (am791x_t *)timer->priv; handle_rx_state(am791x); } /* init routine, must be called before anything else */ int am791x_init(am791x_t *am791x, void *inst, enum am791x_type type, uint8_t mc, int samplerate, double tx_baud, double rx_baud, void (*cts)(void *inst, int cts), void (*bcts)(void *inst, int cts), void (*cd)(void *inst, int cd), void (*bcd)(void *inst, int cd), int (*td)(void *inst), int (*btd)(void *inst), void (*rd)(void *inst, int bit, double quality, double level), void (*brd)(void *inst, int bit, double quality, double level)) { memset(am791x, 0, sizeof(*am791x)); /* init timers */ timer_init(&am791x->tx_timer, tx_timeout, am791x); timer_init(&am791x->rx_timer, rx_timeout, am791x); PDEBUG(DAM791X, DEBUG_DEBUG, "Initializing instance of AM791%d:\n", type); am791x->inst = inst; am791x->type = type; am791x->samplerate = samplerate; am791x->cts_cb = cts; am791x->bcts_cb = bcts; am791x->cd_cb = cd; am791x->bcd_cb = bcd; am791x->td_cb = td; am791x->btd_cb = btd; am791x->rd_cb = rd; am791x->brd_cb = brd; /* levels */ am791x->tx_level = db2level(TX_LEVEL); am791x->cd_on = db2level((am791x->type) ? RX_CD_ON_7911 : RX_CD_ON_7910); am791x->cd_off = db2level((am791x->type) ? RX_CD_OFF_7911 : RX_CD_OFF_7910); init_sto(am791x); /* set initial mode and reset */ am791x_mc(am791x, mc, samplerate, tx_baud, rx_baud); return 0; } /* exit routine, must be called when exit */ void am791x_exit(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_DEBUG, "Exit instance of AM791%d:\n", am791x->type); /* bring to reset state, be sure to clean FSK processes */ am791x_reset(am791x); timer_exit(&am791x->tx_timer); timer_exit(&am791x->rx_timer); } /* get some default baud rate for each mode, before IOCTL sets it (if it sets it) */ double am791x_max_baud(uint8_t mc) { if (am791x_modes[mc].max_baud) return am791x_modes[mc].max_baud; else return 300; /* useless modem. just report something to the caller */ } /* change mode on the fly, may be called any time by upper layer */ int am791x_mc(am791x_t *am791x, uint8_t mc, int samplerate, double tx_baud, double rx_baud) { int rc = 0; /* prevent out of range value */ if (mc >= 32) mc = 0; if (!((am791x->type) ? am791x_modes[mc].sup_7911 : am791x_modes[mc].sup_7910)) rc = -EINVAL; PDEBUG(DAM791X, DEBUG_INFO, "Setting mode %d: %s\n", mc, am791x_modes[mc].description); PDEBUG(DAM791X, DEBUG_DEBUG, " -> Baud rate: %.1f/%.1f\n", rx_baud, tx_baud); am791x->mc = mc; am791x->samplerate = samplerate; am791x->tx_baud = tx_baud; am791x->rx_baud = rx_baud; am791x->t_rcon = (am791x->type) ? am791x_modes[mc].t_rcon_7911 : am791x_modes[mc].t_rcon_7910; am791x->t_rcoff = (am791x->type) ? am791x_modes[mc].t_rcoff_7911 : am791x_modes[mc].t_rcoff_7910; am791x->t_brcon = (am791x->type) ? am791x_modes[mc].t_brcon_7911 : am791x_modes[mc].t_brcon_7910; am791x->t_brcoff = (am791x->type) ? am791x_modes[mc].t_brcoff_7911 : am791x_modes[mc].t_brcoff_7910; am791x->t_cdon = (am791x->type) ? am791x_modes[mc].t_cdon_7911 : am791x_modes[mc].t_cdon_7910; am791x->t_cdoff = (am791x->type) ? am791x_modes[mc].t_cdoff_7911 : am791x_modes[mc].t_cdoff_7910; am791x->t_bcdon = (am791x->type) ? am791x_modes[mc].t_bcdon_7911 : am791x_modes[mc].t_bcdon_7910; am791x->t_bcdoff = (am791x->type) ? am791x_modes[mc].t_bcdoff_7911 : am791x_modes[mc].t_bcdoff_7910; am791x->t_at = (am791x->type) ? am791x_modes[mc].t_at_7911 : am791x_modes[mc].t_at_7910; am791x->t_sil1 = (am791x->type) ? am791x_modes[mc].t_sil1_7911 : am791x_modes[mc].t_sil1_7910; am791x->t_sil2 = (am791x->type) ? am791x_modes[mc].t_sil2_7911 : am791x_modes[mc].t_sil2_7910; am791x->t_sq = (am791x->type) ? am791x_modes[mc].t_sq_7911 : am791x_modes[mc].t_sq_7910; am791x->t_sto = (am791x->type) ? am791x_modes[mc].t_sto_7911 : am791x_modes[mc].t_sto_7910; am791x->fullduplex = am791x_modes[mc].fullduplex; am791x->loopback_main = am791x_modes[mc].loopback_main; am791x->loopback_back = am791x_modes[mc].loopback_back; am791x->equalizer = am791x_modes[mc].equalizer; am791x->sto = am791x_modes[mc].sto; am791x->bell_202 = am791x_modes[mc].bell_202; /* changing mode causes a reset */ am791x_reset(am791x); /* Return error on invalid mode. The emualtion still works, but no TX/RX possible. */ return rc; } /* reset at any time, may be called any time by upper layer */ void am791x_reset(am791x_t *am791x) { PDEBUG(DAM791X, DEBUG_INFO, "Reset!\n"); timer_stop(&am791x->tx_timer); timer_stop(&am791x->rx_timer); if (am791x->f0_tx) { fsk_mod_cleanup(&am791x->fsk_tx); am791x->f0_tx = 0; am791x->f1_tx = 0; } if (am791x->f0_rx) { fsk_demod_cleanup(&am791x->fsk_rx); am791x->f0_rx = 0; am791x->f1_rx = 0; } /* initial states */ am791x->tx_state = AM791X_STATE_INIT; am791x->rx_state = AM791X_STATE_INIT; am791x->tx_silence = 1; /* state machine implies that silence is sent */ am791x->tx_sto = 0; /* state machine implies that STO is not sent */ am791x->block_td = 1; /* state machine implies that TD is MARK (1) */ am791x->block_rd = 1; /* state machine implies that RD is ignored */ am791x->line_cd = 0; am791x->block_cd = 0; /* state machine implies that CD is released */ am791x->block_btd = 1; /* state machine implies that BTD is MARK (1) */ am791x->block_brd = 1; /* state machine implies that BTD is ignored */ am791x->line_bcd = 0; am791x->block_bcd = 0; /* state machine implies that BCD is released */ am791x->squelch = 0; /* state machine implies that squelch is off */ /* set filters, if DTR is on */ if (am791x->line_dtr) set_filters(am791x); handle_state(am791x); } /* change input lines */ void am791x_dtr(am791x_t *am791x, int dtr) { PDEBUG(DAM791X, DEBUG_DEBUG, "Terminal is%s ready!\n", (dtr) ? "" : " not"); /* set filters, if DTR becomes on */ if (!am791x->line_dtr && dtr) { am791x->line_dtr = dtr; set_filters(am791x); } else am791x->line_dtr = dtr; handle_state(am791x); } void am791x_rts(am791x_t *am791x, int rts) { PDEBUG(DAM791X, DEBUG_DEBUG, "Terminal %s RTS.\n", (rts) ? "sets" : "clears"); am791x->line_rts = rts; handle_state(am791x); } void am791x_brts(am791x_t *am791x, int rts) { PDEBUG(DAM791X, DEBUG_DEBUG, "Terminal %s BRTS.\n", (rts) ? "sets" : "clears"); am791x->line_brts = rts; handle_state(am791x); } void am791x_ring(am791x_t *am791x, int ring) { PDEBUG(DAM791X, DEBUG_DEBUG, "Terminal %s RING.\n", (ring) ? "sets" : "clears"); am791x->line_ring = ring; handle_state(am791x); }