📄 baycom_ser_hdx.c
字号:
/*****************************************************************************//* * baycom_ser_hdx.c -- baycom ser12 halfduplex radio modem driver. * * Copyright (C) 1996-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. * * * Supported modems * * ser12: This is a very simple 1200 baud AFSK modem. The modem consists only * of a modulator/demodulator chip, usually a TI TCM3105. The computer * is responsible for regenerating the receiver bit clock, as well as * for handling the HDLC protocol. The modem connects to a serial port, * hence the name. Since the serial port is not used as an async serial * port, the kernel driver for serial ports cannot be used, and this * driver only supports standard serial hardware (8250, 16450, 16550A) * * * Command line options (insmod command line) * * mode ser12 hardware DCD * ser12* software DCD * ser12@ hardware/software DCD, i.e. no explicit DCD signal but hardware * mutes audio input to the modem * ser12+ hardware DCD, inverted signal at DCD pin * iobase base address of the port; common values are 0x3f8, 0x2f8, 0x3e8, 0x2e8 * irq interrupt line of the port; common values are 4,3 * * * History: * 0.1 26.06.1996 Adapted from baycom.c and made network driver interface * 18.10.1996 Changed to new user space access routines (copy_{to,from}_user) * 0.3 26.04.1997 init code/data tagged * 0.4 08.07.1997 alternative ser12 decoding algorithm (uses delta CTS ints) * 0.5 11.11.1997 ser12/par96 split into separate files * 0.6 14.04.1998 cleanups * 0.7 03.08.1999 adapt to Linus' new __setup/__initcall * 0.8 10.08.1999 use module_init/module_exit * 0.9 12.02.2000 adapted to softnet driver interface * 0.10 03.07.2000 fix interface name handling *//*****************************************************************************/#include <linux/version.h>#include <linux/module.h>#include <linux/ioport.h>#include <linux/string.h>#include <linux/init.h>#include <asm/uaccess.h>#include <asm/io.h>#include <linux/hdlcdrv.h>#include <linux/baycom.h>/* --------------------------------------------------------------------- */#define BAYCOM_DEBUG/* --------------------------------------------------------------------- */static const char bc_drvname[] = "baycom_ser_hdx";static const char bc_drvinfo[] = KERN_INFO "baycom_ser_hdx: (C) 1996-2000 Thomas Sailer, HB9JNX/AE4WA\n"KERN_INFO "baycom_ser_hdx: version 0.10 compiled " __TIME__ " " __DATE__ "\n";/* --------------------------------------------------------------------- */#define NR_PORTS 4static struct net_device baycom_device[NR_PORTS];/* --------------------------------------------------------------------- */#define RBR(iobase) (iobase+0)#define THR(iobase) (iobase+0)#define IER(iobase) (iobase+1)#define IIR(iobase) (iobase+2)#define FCR(iobase) (iobase+2)#define LCR(iobase) (iobase+3)#define MCR(iobase) (iobase+4)#define LSR(iobase) (iobase+5)#define MSR(iobase) (iobase+6)#define SCR(iobase) (iobase+7)#define DLL(iobase) (iobase+0)#define DLM(iobase) (iobase+1)#define SER12_EXTENT 8/* ---------------------------------------------------------------------- *//* * Information that need to be kept for each board. */struct baycom_state { struct hdlcdrv_state hdrv; int opt_dcd; struct modem_state { short arb_divider; unsigned char flags; unsigned int shreg; struct modem_state_ser12 { unsigned char tx_bit; int dcd_sum0, dcd_sum1, dcd_sum2; unsigned char last_sample; unsigned char last_rxbit; unsigned int dcd_shreg; unsigned int dcd_time; unsigned int bit_pll; unsigned char interm_sample; } ser12; } modem;#ifdef BAYCOM_DEBUG struct debug_vals { unsigned long last_jiffies; unsigned cur_intcnt; unsigned last_intcnt; int cur_pllcorr; int last_pllcorr; } debug_vals;#endif /* BAYCOM_DEBUG */};/* --------------------------------------------------------------------- */static void inline baycom_int_freq(struct baycom_state *bc){#ifdef BAYCOM_DEBUG unsigned long cur_jiffies = jiffies; /* * measure the interrupt frequency */ bc->debug_vals.cur_intcnt++; if ((cur_jiffies - bc->debug_vals.last_jiffies) >= HZ) { bc->debug_vals.last_jiffies = cur_jiffies; bc->debug_vals.last_intcnt = bc->debug_vals.cur_intcnt; bc->debug_vals.cur_intcnt = 0; bc->debug_vals.last_pllcorr = bc->debug_vals.cur_pllcorr; bc->debug_vals.cur_pllcorr = 0; }#endif /* BAYCOM_DEBUG */}/* --------------------------------------------------------------------- *//* * ===================== SER12 specific routines ========================= */static void inline ser12_set_divisor(struct net_device *dev, unsigned char divisor){ outb(0x81, LCR(dev->base_addr)); /* DLAB = 1 */ outb(divisor, DLL(dev->base_addr)); outb(0, DLM(dev->base_addr)); outb(0x01, LCR(dev->base_addr)); /* word length = 6 */ /* * make sure the next interrupt is generated; * 0 must be used to power the modem; the modem draws its * power from the TxD line */ outb(0x00, THR(dev->base_addr)); /* * it is important not to set the divider while transmitting; * this reportedly makes some UARTs generating interrupts * in the hundredthousands per second region * Reported by: Ignacio.Arenaza@studi.epfl.ch (Ignacio Arenaza Nuno) */}/* --------------------------------------------------------------------- *//* * must call the TX arbitrator every 10ms */#define SER12_ARB_DIVIDER(bc) (bc->opt_dcd ? 24 : 36) #define SER12_DCD_INTERVAL(bc) (bc->opt_dcd ? 12 : 240)static inline void ser12_tx(struct net_device *dev, struct baycom_state *bc){ /* one interrupt per channel bit */ ser12_set_divisor(dev, 12); /* * first output the last bit (!) then call HDLC transmitter, * since this may take quite long */ outb(0x0e | (!!bc->modem.ser12.tx_bit), MCR(dev->base_addr)); if (bc->modem.shreg <= 1) bc->modem.shreg = 0x10000 | hdlcdrv_getbits(&bc->hdrv); bc->modem.ser12.tx_bit = !(bc->modem.ser12.tx_bit ^ (bc->modem.shreg & 1)); bc->modem.shreg >>= 1;}/* --------------------------------------------------------------------- */static inline void ser12_rx(struct net_device *dev, struct baycom_state *bc){ unsigned char cur_s; /* * do demodulator */ cur_s = inb(MSR(dev->base_addr)) & 0x10; /* the CTS line */ hdlcdrv_channelbit(&bc->hdrv, cur_s); bc->modem.ser12.dcd_shreg = (bc->modem.ser12.dcd_shreg << 1) | (cur_s != bc->modem.ser12.last_sample); bc->modem.ser12.last_sample = cur_s; if(bc->modem.ser12.dcd_shreg & 1) { if (!bc->opt_dcd) { unsigned int dcdspos, dcdsneg; dcdspos = dcdsneg = 0; dcdspos += ((bc->modem.ser12.dcd_shreg >> 1) & 1); if (!(bc->modem.ser12.dcd_shreg & 0x7ffffffe)) dcdspos += 2; dcdsneg += ((bc->modem.ser12.dcd_shreg >> 2) & 1); dcdsneg += ((bc->modem.ser12.dcd_shreg >> 3) & 1); dcdsneg += ((bc->modem.ser12.dcd_shreg >> 4) & 1); bc->modem.ser12.dcd_sum0 += 16*dcdspos - dcdsneg; } else bc->modem.ser12.dcd_sum0--; } if(!bc->modem.ser12.dcd_time) { hdlcdrv_setdcd(&bc->hdrv, (bc->modem.ser12.dcd_sum0 + bc->modem.ser12.dcd_sum1 + bc->modem.ser12.dcd_sum2) < 0); bc->modem.ser12.dcd_sum2 = bc->modem.ser12.dcd_sum1; bc->modem.ser12.dcd_sum1 = bc->modem.ser12.dcd_sum0; /* offset to ensure DCD off on silent input */ bc->modem.ser12.dcd_sum0 = 2; bc->modem.ser12.dcd_time = SER12_DCD_INTERVAL(bc); } bc->modem.ser12.dcd_time--; if (!bc->opt_dcd) { /* * PLL code for the improved software DCD algorithm */ if (bc->modem.ser12.interm_sample) { /* * intermediate sample; set timing correction to normal */ ser12_set_divisor(dev, 4); } else { /* * do PLL correction and call HDLC receiver */ switch (bc->modem.ser12.dcd_shreg & 7) { case 1: /* transition too late */ ser12_set_divisor(dev, 5);#ifdef BAYCOM_DEBUG bc->debug_vals.cur_pllcorr++;#endif /* BAYCOM_DEBUG */ break; case 4: /* transition too early */ ser12_set_divisor(dev, 3);#ifdef BAYCOM_DEBUG bc->debug_vals.cur_pllcorr--;#endif /* BAYCOM_DEBUG */ break; default: ser12_set_divisor(dev, 4); break; } bc->modem.shreg >>= 1; if (bc->modem.ser12.last_sample == bc->modem.ser12.last_rxbit) bc->modem.shreg |= 0x10000; bc->modem.ser12.last_rxbit = bc->modem.ser12.last_sample; } if (++bc->modem.ser12.interm_sample >= 3) bc->modem.ser12.interm_sample = 0; /* * DCD stuff */ if (bc->modem.ser12.dcd_shreg & 1) { unsigned int dcdspos, dcdsneg; dcdspos = dcdsneg = 0; dcdspos += ((bc->modem.ser12.dcd_shreg >> 1) & 1); dcdspos += (!(bc->modem.ser12.dcd_shreg & 0x7ffffffe)) << 1; dcdsneg += ((bc->modem.ser12.dcd_shreg >> 2) & 1); dcdsneg += ((bc->modem.ser12.dcd_shreg >> 3) & 1); dcdsneg += ((bc->modem.ser12.dcd_shreg >> 4) & 1); bc->modem.ser12.dcd_sum0 += 16*dcdspos - dcdsneg; } } else { /* * PLL algorithm for the hardware squelch DCD algorithm */ if (bc->modem.ser12.interm_sample) { /* * intermediate sample; set timing correction to normal */ ser12_set_divisor(dev, 6); } else { /* * do PLL correction and call HDLC receiver */ switch (bc->modem.ser12.dcd_shreg & 3) { case 1: /* transition too late */ ser12_set_divisor(dev, 7);#ifdef BAYCOM_DEBUG bc->debug_vals.cur_pllcorr++;#endif /* BAYCOM_DEBUG */ break; case 2: /* transition too early */ ser12_set_divisor(dev, 5);#ifdef BAYCOM_DEBUG bc->debug_vals.cur_pllcorr--;#endif /* BAYCOM_DEBUG */ break; default: ser12_set_divisor(dev, 6); break; } bc->modem.shreg >>= 1; if (bc->modem.ser12.last_sample == bc->modem.ser12.last_rxbit) bc->modem.shreg |= 0x10000; bc->modem.ser12.last_rxbit = bc->modem.ser12.last_sample; } bc->modem.ser12.interm_sample = !bc->modem.ser12.interm_sample; /* * DCD stuff */ bc->modem.ser12.dcd_sum0 -= (bc->modem.ser12.dcd_shreg & 1); } outb(0x0d, MCR(dev->base_addr)); /* transmitter off */ if (bc->modem.shreg & 1) { hdlcdrv_putbits(&bc->hdrv, bc->modem.shreg >> 1); bc->modem.shreg = 0x10000; } if(!bc->modem.ser12.dcd_time) { if (bc->opt_dcd & 1) hdlcdrv_setdcd(&bc->hdrv, !((inb(MSR(dev->base_addr)) ^ bc->opt_dcd) & 0x80)); else hdlcdrv_setdcd(&bc->hdrv, (bc->modem.ser12.dcd_sum0 + bc->modem.ser12.dcd_sum1 + bc->modem.ser12.dcd_sum2) < 0); bc->modem.ser12.dcd_sum2 = bc->modem.ser12.dcd_sum1; bc->modem.ser12.dcd_sum1 = bc->modem.ser12.dcd_sum0; /* offset to ensure DCD off on silent input */ bc->modem.ser12.dcd_sum0 = 2; bc->modem.ser12.dcd_time = SER12_DCD_INTERVAL(bc); } bc->modem.ser12.dcd_time--;}/* --------------------------------------------------------------------- */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -