1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*****************************************************************************/
3*4882a593Smuzhiyun
4*4882a593Smuzhiyun /*
5*4882a593Smuzhiyun * baycom_ser_hdx.c -- baycom ser12 halfduplex radio modem driver.
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Copyright (C) 1996-2000 Thomas Sailer (sailer@ife.ee.ethz.ch)
8*4882a593Smuzhiyun *
9*4882a593Smuzhiyun * Please note that the GPL allows you to use the driver, NOT the radio.
10*4882a593Smuzhiyun * In order to use the radio, you need a license from the communications
11*4882a593Smuzhiyun * authority of your country.
12*4882a593Smuzhiyun *
13*4882a593Smuzhiyun * Supported modems
14*4882a593Smuzhiyun *
15*4882a593Smuzhiyun * ser12: This is a very simple 1200 baud AFSK modem. The modem consists only
16*4882a593Smuzhiyun * of a modulator/demodulator chip, usually a TI TCM3105. The computer
17*4882a593Smuzhiyun * is responsible for regenerating the receiver bit clock, as well as
18*4882a593Smuzhiyun * for handling the HDLC protocol. The modem connects to a serial port,
19*4882a593Smuzhiyun * hence the name. Since the serial port is not used as an async serial
20*4882a593Smuzhiyun * port, the kernel driver for serial ports cannot be used, and this
21*4882a593Smuzhiyun * driver only supports standard serial hardware (8250, 16450, 16550A)
22*4882a593Smuzhiyun *
23*4882a593Smuzhiyun * Command line options (insmod command line)
24*4882a593Smuzhiyun *
25*4882a593Smuzhiyun * mode ser12 hardware DCD
26*4882a593Smuzhiyun * ser12* software DCD
27*4882a593Smuzhiyun * ser12@ hardware/software DCD, i.e. no explicit DCD signal but hardware
28*4882a593Smuzhiyun * mutes audio input to the modem
29*4882a593Smuzhiyun * ser12+ hardware DCD, inverted signal at DCD pin
30*4882a593Smuzhiyun * iobase base address of the port; common values are 0x3f8, 0x2f8, 0x3e8, 0x2e8
31*4882a593Smuzhiyun * irq interrupt line of the port; common values are 4,3
32*4882a593Smuzhiyun *
33*4882a593Smuzhiyun * History:
34*4882a593Smuzhiyun * 0.1 26.06.1996 Adapted from baycom.c and made network driver interface
35*4882a593Smuzhiyun * 18.10.1996 Changed to new user space access routines (copy_{to,from}_user)
36*4882a593Smuzhiyun * 0.3 26.04.1997 init code/data tagged
37*4882a593Smuzhiyun * 0.4 08.07.1997 alternative ser12 decoding algorithm (uses delta CTS ints)
38*4882a593Smuzhiyun * 0.5 11.11.1997 ser12/par96 split into separate files
39*4882a593Smuzhiyun * 0.6 14.04.1998 cleanups
40*4882a593Smuzhiyun * 0.7 03.08.1999 adapt to Linus' new __setup/__initcall
41*4882a593Smuzhiyun * 0.8 10.08.1999 use module_init/module_exit
42*4882a593Smuzhiyun * 0.9 12.02.2000 adapted to softnet driver interface
43*4882a593Smuzhiyun * 0.10 03.07.2000 fix interface name handling
44*4882a593Smuzhiyun */
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun /*****************************************************************************/
47*4882a593Smuzhiyun
48*4882a593Smuzhiyun #include <linux/capability.h>
49*4882a593Smuzhiyun #include <linux/module.h>
50*4882a593Smuzhiyun #include <linux/ioport.h>
51*4882a593Smuzhiyun #include <linux/string.h>
52*4882a593Smuzhiyun #include <linux/init.h>
53*4882a593Smuzhiyun #include <linux/interrupt.h>
54*4882a593Smuzhiyun #include <linux/uaccess.h>
55*4882a593Smuzhiyun #include <asm/io.h>
56*4882a593Smuzhiyun #include <linux/hdlcdrv.h>
57*4882a593Smuzhiyun #include <linux/baycom.h>
58*4882a593Smuzhiyun #include <linux/jiffies.h>
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun #define BAYCOM_DEBUG
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun static const char bc_drvname[] = "baycom_ser_hdx";
67*4882a593Smuzhiyun static const char bc_drvinfo[] = KERN_INFO "baycom_ser_hdx: (C) 1996-2000 Thomas Sailer, HB9JNX/AE4WA\n"
68*4882a593Smuzhiyun "baycom_ser_hdx: version 0.10\n";
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun #define NR_PORTS 4
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun static struct net_device *baycom_device[NR_PORTS];
75*4882a593Smuzhiyun
76*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun #define RBR(iobase) (iobase+0)
79*4882a593Smuzhiyun #define THR(iobase) (iobase+0)
80*4882a593Smuzhiyun #define IER(iobase) (iobase+1)
81*4882a593Smuzhiyun #define IIR(iobase) (iobase+2)
82*4882a593Smuzhiyun #define FCR(iobase) (iobase+2)
83*4882a593Smuzhiyun #define LCR(iobase) (iobase+3)
84*4882a593Smuzhiyun #define MCR(iobase) (iobase+4)
85*4882a593Smuzhiyun #define LSR(iobase) (iobase+5)
86*4882a593Smuzhiyun #define MSR(iobase) (iobase+6)
87*4882a593Smuzhiyun #define SCR(iobase) (iobase+7)
88*4882a593Smuzhiyun #define DLL(iobase) (iobase+0)
89*4882a593Smuzhiyun #define DLM(iobase) (iobase+1)
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun #define SER12_EXTENT 8
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun /* ---------------------------------------------------------------------- */
94*4882a593Smuzhiyun /*
95*4882a593Smuzhiyun * Information that need to be kept for each board.
96*4882a593Smuzhiyun */
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun struct baycom_state {
99*4882a593Smuzhiyun struct hdlcdrv_state hdrv;
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun int opt_dcd;
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun struct modem_state {
104*4882a593Smuzhiyun short arb_divider;
105*4882a593Smuzhiyun unsigned char flags;
106*4882a593Smuzhiyun unsigned int shreg;
107*4882a593Smuzhiyun struct modem_state_ser12 {
108*4882a593Smuzhiyun unsigned char tx_bit;
109*4882a593Smuzhiyun int dcd_sum0, dcd_sum1, dcd_sum2;
110*4882a593Smuzhiyun unsigned char last_sample;
111*4882a593Smuzhiyun unsigned char last_rxbit;
112*4882a593Smuzhiyun unsigned int dcd_shreg;
113*4882a593Smuzhiyun unsigned int dcd_time;
114*4882a593Smuzhiyun unsigned int bit_pll;
115*4882a593Smuzhiyun unsigned char interm_sample;
116*4882a593Smuzhiyun } ser12;
117*4882a593Smuzhiyun } modem;
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun #ifdef BAYCOM_DEBUG
120*4882a593Smuzhiyun struct debug_vals {
121*4882a593Smuzhiyun unsigned long last_jiffies;
122*4882a593Smuzhiyun unsigned cur_intcnt;
123*4882a593Smuzhiyun unsigned last_intcnt;
124*4882a593Smuzhiyun int cur_pllcorr;
125*4882a593Smuzhiyun int last_pllcorr;
126*4882a593Smuzhiyun } debug_vals;
127*4882a593Smuzhiyun #endif /* BAYCOM_DEBUG */
128*4882a593Smuzhiyun };
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
131*4882a593Smuzhiyun
baycom_int_freq(struct baycom_state * bc)132*4882a593Smuzhiyun static inline void baycom_int_freq(struct baycom_state *bc)
133*4882a593Smuzhiyun {
134*4882a593Smuzhiyun #ifdef BAYCOM_DEBUG
135*4882a593Smuzhiyun unsigned long cur_jiffies = jiffies;
136*4882a593Smuzhiyun /*
137*4882a593Smuzhiyun * measure the interrupt frequency
138*4882a593Smuzhiyun */
139*4882a593Smuzhiyun bc->debug_vals.cur_intcnt++;
140*4882a593Smuzhiyun if (time_after_eq(cur_jiffies, bc->debug_vals.last_jiffies + HZ)) {
141*4882a593Smuzhiyun bc->debug_vals.last_jiffies = cur_jiffies;
142*4882a593Smuzhiyun bc->debug_vals.last_intcnt = bc->debug_vals.cur_intcnt;
143*4882a593Smuzhiyun bc->debug_vals.cur_intcnt = 0;
144*4882a593Smuzhiyun bc->debug_vals.last_pllcorr = bc->debug_vals.cur_pllcorr;
145*4882a593Smuzhiyun bc->debug_vals.cur_pllcorr = 0;
146*4882a593Smuzhiyun }
147*4882a593Smuzhiyun #endif /* BAYCOM_DEBUG */
148*4882a593Smuzhiyun }
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
151*4882a593Smuzhiyun /*
152*4882a593Smuzhiyun * ===================== SER12 specific routines =========================
153*4882a593Smuzhiyun */
154*4882a593Smuzhiyun
ser12_set_divisor(struct net_device * dev,unsigned char divisor)155*4882a593Smuzhiyun static inline void ser12_set_divisor(struct net_device *dev,
156*4882a593Smuzhiyun unsigned char divisor)
157*4882a593Smuzhiyun {
158*4882a593Smuzhiyun outb(0x81, LCR(dev->base_addr)); /* DLAB = 1 */
159*4882a593Smuzhiyun outb(divisor, DLL(dev->base_addr));
160*4882a593Smuzhiyun outb(0, DLM(dev->base_addr));
161*4882a593Smuzhiyun outb(0x01, LCR(dev->base_addr)); /* word length = 6 */
162*4882a593Smuzhiyun /*
163*4882a593Smuzhiyun * make sure the next interrupt is generated;
164*4882a593Smuzhiyun * 0 must be used to power the modem; the modem draws its
165*4882a593Smuzhiyun * power from the TxD line
166*4882a593Smuzhiyun */
167*4882a593Smuzhiyun outb(0x00, THR(dev->base_addr));
168*4882a593Smuzhiyun /*
169*4882a593Smuzhiyun * it is important not to set the divider while transmitting;
170*4882a593Smuzhiyun * this reportedly makes some UARTs generating interrupts
171*4882a593Smuzhiyun * in the hundredthousands per second region
172*4882a593Smuzhiyun * Reported by: Ignacio.Arenaza@studi.epfl.ch (Ignacio Arenaza Nuno)
173*4882a593Smuzhiyun */
174*4882a593Smuzhiyun }
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
177*4882a593Smuzhiyun
178*4882a593Smuzhiyun /*
179*4882a593Smuzhiyun * must call the TX arbitrator every 10ms
180*4882a593Smuzhiyun */
181*4882a593Smuzhiyun #define SER12_ARB_DIVIDER(bc) (bc->opt_dcd ? 24 : 36)
182*4882a593Smuzhiyun
183*4882a593Smuzhiyun #define SER12_DCD_INTERVAL(bc) (bc->opt_dcd ? 12 : 240)
184*4882a593Smuzhiyun
ser12_tx(struct net_device * dev,struct baycom_state * bc)185*4882a593Smuzhiyun static inline void ser12_tx(struct net_device *dev, struct baycom_state *bc)
186*4882a593Smuzhiyun {
187*4882a593Smuzhiyun /* one interrupt per channel bit */
188*4882a593Smuzhiyun ser12_set_divisor(dev, 12);
189*4882a593Smuzhiyun /*
190*4882a593Smuzhiyun * first output the last bit (!) then call HDLC transmitter,
191*4882a593Smuzhiyun * since this may take quite long
192*4882a593Smuzhiyun */
193*4882a593Smuzhiyun outb(0x0e | (!!bc->modem.ser12.tx_bit), MCR(dev->base_addr));
194*4882a593Smuzhiyun if (bc->modem.shreg <= 1)
195*4882a593Smuzhiyun bc->modem.shreg = 0x10000 | hdlcdrv_getbits(&bc->hdrv);
196*4882a593Smuzhiyun bc->modem.ser12.tx_bit = !(bc->modem.ser12.tx_bit ^
197*4882a593Smuzhiyun (bc->modem.shreg & 1));
198*4882a593Smuzhiyun bc->modem.shreg >>= 1;
199*4882a593Smuzhiyun }
200*4882a593Smuzhiyun
201*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
202*4882a593Smuzhiyun
ser12_rx(struct net_device * dev,struct baycom_state * bc)203*4882a593Smuzhiyun static inline void ser12_rx(struct net_device *dev, struct baycom_state *bc)
204*4882a593Smuzhiyun {
205*4882a593Smuzhiyun unsigned char cur_s;
206*4882a593Smuzhiyun /*
207*4882a593Smuzhiyun * do demodulator
208*4882a593Smuzhiyun */
209*4882a593Smuzhiyun cur_s = inb(MSR(dev->base_addr)) & 0x10; /* the CTS line */
210*4882a593Smuzhiyun hdlcdrv_channelbit(&bc->hdrv, cur_s);
211*4882a593Smuzhiyun bc->modem.ser12.dcd_shreg = (bc->modem.ser12.dcd_shreg << 1) |
212*4882a593Smuzhiyun (cur_s != bc->modem.ser12.last_sample);
213*4882a593Smuzhiyun bc->modem.ser12.last_sample = cur_s;
214*4882a593Smuzhiyun if(bc->modem.ser12.dcd_shreg & 1) {
215*4882a593Smuzhiyun if (!bc->opt_dcd) {
216*4882a593Smuzhiyun unsigned int dcdspos, dcdsneg;
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun dcdspos = dcdsneg = 0;
219*4882a593Smuzhiyun dcdspos += ((bc->modem.ser12.dcd_shreg >> 1) & 1);
220*4882a593Smuzhiyun if (!(bc->modem.ser12.dcd_shreg & 0x7ffffffe))
221*4882a593Smuzhiyun dcdspos += 2;
222*4882a593Smuzhiyun dcdsneg += ((bc->modem.ser12.dcd_shreg >> 2) & 1);
223*4882a593Smuzhiyun dcdsneg += ((bc->modem.ser12.dcd_shreg >> 3) & 1);
224*4882a593Smuzhiyun dcdsneg += ((bc->modem.ser12.dcd_shreg >> 4) & 1);
225*4882a593Smuzhiyun
226*4882a593Smuzhiyun bc->modem.ser12.dcd_sum0 += 16*dcdspos - dcdsneg;
227*4882a593Smuzhiyun } else
228*4882a593Smuzhiyun bc->modem.ser12.dcd_sum0--;
229*4882a593Smuzhiyun }
230*4882a593Smuzhiyun if(!bc->modem.ser12.dcd_time) {
231*4882a593Smuzhiyun hdlcdrv_setdcd(&bc->hdrv, (bc->modem.ser12.dcd_sum0 +
232*4882a593Smuzhiyun bc->modem.ser12.dcd_sum1 +
233*4882a593Smuzhiyun bc->modem.ser12.dcd_sum2) < 0);
234*4882a593Smuzhiyun bc->modem.ser12.dcd_sum2 = bc->modem.ser12.dcd_sum1;
235*4882a593Smuzhiyun bc->modem.ser12.dcd_sum1 = bc->modem.ser12.dcd_sum0;
236*4882a593Smuzhiyun /* offset to ensure DCD off on silent input */
237*4882a593Smuzhiyun bc->modem.ser12.dcd_sum0 = 2;
238*4882a593Smuzhiyun bc->modem.ser12.dcd_time = SER12_DCD_INTERVAL(bc);
239*4882a593Smuzhiyun }
240*4882a593Smuzhiyun bc->modem.ser12.dcd_time--;
241*4882a593Smuzhiyun if (!bc->opt_dcd) {
242*4882a593Smuzhiyun /*
243*4882a593Smuzhiyun * PLL code for the improved software DCD algorithm
244*4882a593Smuzhiyun */
245*4882a593Smuzhiyun if (bc->modem.ser12.interm_sample) {
246*4882a593Smuzhiyun /*
247*4882a593Smuzhiyun * intermediate sample; set timing correction to normal
248*4882a593Smuzhiyun */
249*4882a593Smuzhiyun ser12_set_divisor(dev, 4);
250*4882a593Smuzhiyun } else {
251*4882a593Smuzhiyun /*
252*4882a593Smuzhiyun * do PLL correction and call HDLC receiver
253*4882a593Smuzhiyun */
254*4882a593Smuzhiyun switch (bc->modem.ser12.dcd_shreg & 7) {
255*4882a593Smuzhiyun case 1: /* transition too late */
256*4882a593Smuzhiyun ser12_set_divisor(dev, 5);
257*4882a593Smuzhiyun #ifdef BAYCOM_DEBUG
258*4882a593Smuzhiyun bc->debug_vals.cur_pllcorr++;
259*4882a593Smuzhiyun #endif /* BAYCOM_DEBUG */
260*4882a593Smuzhiyun break;
261*4882a593Smuzhiyun case 4: /* transition too early */
262*4882a593Smuzhiyun ser12_set_divisor(dev, 3);
263*4882a593Smuzhiyun #ifdef BAYCOM_DEBUG
264*4882a593Smuzhiyun bc->debug_vals.cur_pllcorr--;
265*4882a593Smuzhiyun #endif /* BAYCOM_DEBUG */
266*4882a593Smuzhiyun break;
267*4882a593Smuzhiyun default:
268*4882a593Smuzhiyun ser12_set_divisor(dev, 4);
269*4882a593Smuzhiyun break;
270*4882a593Smuzhiyun }
271*4882a593Smuzhiyun bc->modem.shreg >>= 1;
272*4882a593Smuzhiyun if (bc->modem.ser12.last_sample ==
273*4882a593Smuzhiyun bc->modem.ser12.last_rxbit)
274*4882a593Smuzhiyun bc->modem.shreg |= 0x10000;
275*4882a593Smuzhiyun bc->modem.ser12.last_rxbit =
276*4882a593Smuzhiyun bc->modem.ser12.last_sample;
277*4882a593Smuzhiyun }
278*4882a593Smuzhiyun if (++bc->modem.ser12.interm_sample >= 3)
279*4882a593Smuzhiyun bc->modem.ser12.interm_sample = 0;
280*4882a593Smuzhiyun /*
281*4882a593Smuzhiyun * DCD stuff
282*4882a593Smuzhiyun */
283*4882a593Smuzhiyun if (bc->modem.ser12.dcd_shreg & 1) {
284*4882a593Smuzhiyun unsigned int dcdspos, dcdsneg;
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun dcdspos = dcdsneg = 0;
287*4882a593Smuzhiyun dcdspos += ((bc->modem.ser12.dcd_shreg >> 1) & 1);
288*4882a593Smuzhiyun dcdspos += (!(bc->modem.ser12.dcd_shreg & 0x7ffffffe))
289*4882a593Smuzhiyun << 1;
290*4882a593Smuzhiyun dcdsneg += ((bc->modem.ser12.dcd_shreg >> 2) & 1);
291*4882a593Smuzhiyun dcdsneg += ((bc->modem.ser12.dcd_shreg >> 3) & 1);
292*4882a593Smuzhiyun dcdsneg += ((bc->modem.ser12.dcd_shreg >> 4) & 1);
293*4882a593Smuzhiyun
294*4882a593Smuzhiyun bc->modem.ser12.dcd_sum0 += 16*dcdspos - dcdsneg;
295*4882a593Smuzhiyun }
296*4882a593Smuzhiyun } else {
297*4882a593Smuzhiyun /*
298*4882a593Smuzhiyun * PLL algorithm for the hardware squelch DCD algorithm
299*4882a593Smuzhiyun */
300*4882a593Smuzhiyun if (bc->modem.ser12.interm_sample) {
301*4882a593Smuzhiyun /*
302*4882a593Smuzhiyun * intermediate sample; set timing correction to normal
303*4882a593Smuzhiyun */
304*4882a593Smuzhiyun ser12_set_divisor(dev, 6);
305*4882a593Smuzhiyun } else {
306*4882a593Smuzhiyun /*
307*4882a593Smuzhiyun * do PLL correction and call HDLC receiver
308*4882a593Smuzhiyun */
309*4882a593Smuzhiyun switch (bc->modem.ser12.dcd_shreg & 3) {
310*4882a593Smuzhiyun case 1: /* transition too late */
311*4882a593Smuzhiyun ser12_set_divisor(dev, 7);
312*4882a593Smuzhiyun #ifdef BAYCOM_DEBUG
313*4882a593Smuzhiyun bc->debug_vals.cur_pllcorr++;
314*4882a593Smuzhiyun #endif /* BAYCOM_DEBUG */
315*4882a593Smuzhiyun break;
316*4882a593Smuzhiyun case 2: /* transition too early */
317*4882a593Smuzhiyun ser12_set_divisor(dev, 5);
318*4882a593Smuzhiyun #ifdef BAYCOM_DEBUG
319*4882a593Smuzhiyun bc->debug_vals.cur_pllcorr--;
320*4882a593Smuzhiyun #endif /* BAYCOM_DEBUG */
321*4882a593Smuzhiyun break;
322*4882a593Smuzhiyun default:
323*4882a593Smuzhiyun ser12_set_divisor(dev, 6);
324*4882a593Smuzhiyun break;
325*4882a593Smuzhiyun }
326*4882a593Smuzhiyun bc->modem.shreg >>= 1;
327*4882a593Smuzhiyun if (bc->modem.ser12.last_sample ==
328*4882a593Smuzhiyun bc->modem.ser12.last_rxbit)
329*4882a593Smuzhiyun bc->modem.shreg |= 0x10000;
330*4882a593Smuzhiyun bc->modem.ser12.last_rxbit =
331*4882a593Smuzhiyun bc->modem.ser12.last_sample;
332*4882a593Smuzhiyun }
333*4882a593Smuzhiyun bc->modem.ser12.interm_sample = !bc->modem.ser12.interm_sample;
334*4882a593Smuzhiyun /*
335*4882a593Smuzhiyun * DCD stuff
336*4882a593Smuzhiyun */
337*4882a593Smuzhiyun bc->modem.ser12.dcd_sum0 -= (bc->modem.ser12.dcd_shreg & 1);
338*4882a593Smuzhiyun }
339*4882a593Smuzhiyun outb(0x0d, MCR(dev->base_addr)); /* transmitter off */
340*4882a593Smuzhiyun if (bc->modem.shreg & 1) {
341*4882a593Smuzhiyun hdlcdrv_putbits(&bc->hdrv, bc->modem.shreg >> 1);
342*4882a593Smuzhiyun bc->modem.shreg = 0x10000;
343*4882a593Smuzhiyun }
344*4882a593Smuzhiyun if(!bc->modem.ser12.dcd_time) {
345*4882a593Smuzhiyun if (bc->opt_dcd & 1)
346*4882a593Smuzhiyun hdlcdrv_setdcd(&bc->hdrv, !((inb(MSR(dev->base_addr)) ^ bc->opt_dcd) & 0x80));
347*4882a593Smuzhiyun else
348*4882a593Smuzhiyun hdlcdrv_setdcd(&bc->hdrv, (bc->modem.ser12.dcd_sum0 +
349*4882a593Smuzhiyun bc->modem.ser12.dcd_sum1 +
350*4882a593Smuzhiyun bc->modem.ser12.dcd_sum2) < 0);
351*4882a593Smuzhiyun bc->modem.ser12.dcd_sum2 = bc->modem.ser12.dcd_sum1;
352*4882a593Smuzhiyun bc->modem.ser12.dcd_sum1 = bc->modem.ser12.dcd_sum0;
353*4882a593Smuzhiyun /* offset to ensure DCD off on silent input */
354*4882a593Smuzhiyun bc->modem.ser12.dcd_sum0 = 2;
355*4882a593Smuzhiyun bc->modem.ser12.dcd_time = SER12_DCD_INTERVAL(bc);
356*4882a593Smuzhiyun }
357*4882a593Smuzhiyun bc->modem.ser12.dcd_time--;
358*4882a593Smuzhiyun }
359*4882a593Smuzhiyun
360*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
361*4882a593Smuzhiyun
ser12_interrupt(int irq,void * dev_id)362*4882a593Smuzhiyun static irqreturn_t ser12_interrupt(int irq, void *dev_id)
363*4882a593Smuzhiyun {
364*4882a593Smuzhiyun struct net_device *dev = (struct net_device *)dev_id;
365*4882a593Smuzhiyun struct baycom_state *bc = netdev_priv(dev);
366*4882a593Smuzhiyun unsigned char iir;
367*4882a593Smuzhiyun
368*4882a593Smuzhiyun if (!dev || !bc || bc->hdrv.magic != HDLCDRV_MAGIC)
369*4882a593Smuzhiyun return IRQ_NONE;
370*4882a593Smuzhiyun /* fast way out */
371*4882a593Smuzhiyun if ((iir = inb(IIR(dev->base_addr))) & 1)
372*4882a593Smuzhiyun return IRQ_NONE;
373*4882a593Smuzhiyun baycom_int_freq(bc);
374*4882a593Smuzhiyun do {
375*4882a593Smuzhiyun switch (iir & 6) {
376*4882a593Smuzhiyun case 6:
377*4882a593Smuzhiyun inb(LSR(dev->base_addr));
378*4882a593Smuzhiyun break;
379*4882a593Smuzhiyun
380*4882a593Smuzhiyun case 4:
381*4882a593Smuzhiyun inb(RBR(dev->base_addr));
382*4882a593Smuzhiyun break;
383*4882a593Smuzhiyun
384*4882a593Smuzhiyun case 2:
385*4882a593Smuzhiyun /*
386*4882a593Smuzhiyun * check if transmitter active
387*4882a593Smuzhiyun */
388*4882a593Smuzhiyun if (hdlcdrv_ptt(&bc->hdrv))
389*4882a593Smuzhiyun ser12_tx(dev, bc);
390*4882a593Smuzhiyun else {
391*4882a593Smuzhiyun ser12_rx(dev, bc);
392*4882a593Smuzhiyun bc->modem.arb_divider--;
393*4882a593Smuzhiyun }
394*4882a593Smuzhiyun outb(0x00, THR(dev->base_addr));
395*4882a593Smuzhiyun break;
396*4882a593Smuzhiyun
397*4882a593Smuzhiyun default:
398*4882a593Smuzhiyun inb(MSR(dev->base_addr));
399*4882a593Smuzhiyun break;
400*4882a593Smuzhiyun }
401*4882a593Smuzhiyun iir = inb(IIR(dev->base_addr));
402*4882a593Smuzhiyun } while (!(iir & 1));
403*4882a593Smuzhiyun if (bc->modem.arb_divider <= 0) {
404*4882a593Smuzhiyun bc->modem.arb_divider = SER12_ARB_DIVIDER(bc);
405*4882a593Smuzhiyun local_irq_enable();
406*4882a593Smuzhiyun hdlcdrv_arbitrate(dev, &bc->hdrv);
407*4882a593Smuzhiyun }
408*4882a593Smuzhiyun local_irq_enable();
409*4882a593Smuzhiyun hdlcdrv_transmitter(dev, &bc->hdrv);
410*4882a593Smuzhiyun hdlcdrv_receiver(dev, &bc->hdrv);
411*4882a593Smuzhiyun local_irq_disable();
412*4882a593Smuzhiyun return IRQ_HANDLED;
413*4882a593Smuzhiyun }
414*4882a593Smuzhiyun
415*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
416*4882a593Smuzhiyun
417*4882a593Smuzhiyun enum uart { c_uart_unknown, c_uart_8250,
418*4882a593Smuzhiyun c_uart_16450, c_uart_16550, c_uart_16550A};
419*4882a593Smuzhiyun static const char *uart_str[] = {
420*4882a593Smuzhiyun "unknown", "8250", "16450", "16550", "16550A"
421*4882a593Smuzhiyun };
422*4882a593Smuzhiyun
ser12_check_uart(unsigned int iobase)423*4882a593Smuzhiyun static enum uart ser12_check_uart(unsigned int iobase)
424*4882a593Smuzhiyun {
425*4882a593Smuzhiyun unsigned char b1,b2,b3;
426*4882a593Smuzhiyun enum uart u;
427*4882a593Smuzhiyun enum uart uart_tab[] =
428*4882a593Smuzhiyun { c_uart_16450, c_uart_unknown, c_uart_16550, c_uart_16550A };
429*4882a593Smuzhiyun
430*4882a593Smuzhiyun b1 = inb(MCR(iobase));
431*4882a593Smuzhiyun outb(b1 | 0x10, MCR(iobase)); /* loopback mode */
432*4882a593Smuzhiyun b2 = inb(MSR(iobase));
433*4882a593Smuzhiyun outb(0x1a, MCR(iobase));
434*4882a593Smuzhiyun b3 = inb(MSR(iobase)) & 0xf0;
435*4882a593Smuzhiyun outb(b1, MCR(iobase)); /* restore old values */
436*4882a593Smuzhiyun outb(b2, MSR(iobase));
437*4882a593Smuzhiyun if (b3 != 0x90)
438*4882a593Smuzhiyun return c_uart_unknown;
439*4882a593Smuzhiyun inb(RBR(iobase));
440*4882a593Smuzhiyun inb(RBR(iobase));
441*4882a593Smuzhiyun outb(0x01, FCR(iobase)); /* enable FIFOs */
442*4882a593Smuzhiyun u = uart_tab[(inb(IIR(iobase)) >> 6) & 3];
443*4882a593Smuzhiyun if (u == c_uart_16450) {
444*4882a593Smuzhiyun outb(0x5a, SCR(iobase));
445*4882a593Smuzhiyun b1 = inb(SCR(iobase));
446*4882a593Smuzhiyun outb(0xa5, SCR(iobase));
447*4882a593Smuzhiyun b2 = inb(SCR(iobase));
448*4882a593Smuzhiyun if ((b1 != 0x5a) || (b2 != 0xa5))
449*4882a593Smuzhiyun u = c_uart_8250;
450*4882a593Smuzhiyun }
451*4882a593Smuzhiyun return u;
452*4882a593Smuzhiyun }
453*4882a593Smuzhiyun
454*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
455*4882a593Smuzhiyun
ser12_open(struct net_device * dev)456*4882a593Smuzhiyun static int ser12_open(struct net_device *dev)
457*4882a593Smuzhiyun {
458*4882a593Smuzhiyun struct baycom_state *bc = netdev_priv(dev);
459*4882a593Smuzhiyun enum uart u;
460*4882a593Smuzhiyun
461*4882a593Smuzhiyun if (!dev || !bc)
462*4882a593Smuzhiyun return -ENXIO;
463*4882a593Smuzhiyun if (!dev->base_addr || dev->base_addr > 0x1000-SER12_EXTENT ||
464*4882a593Smuzhiyun dev->irq < 2 || dev->irq > 15)
465*4882a593Smuzhiyun return -ENXIO;
466*4882a593Smuzhiyun if (!request_region(dev->base_addr, SER12_EXTENT, "baycom_ser12"))
467*4882a593Smuzhiyun return -EACCES;
468*4882a593Smuzhiyun memset(&bc->modem, 0, sizeof(bc->modem));
469*4882a593Smuzhiyun bc->hdrv.par.bitrate = 1200;
470*4882a593Smuzhiyun if ((u = ser12_check_uart(dev->base_addr)) == c_uart_unknown) {
471*4882a593Smuzhiyun release_region(dev->base_addr, SER12_EXTENT);
472*4882a593Smuzhiyun return -EIO;
473*4882a593Smuzhiyun }
474*4882a593Smuzhiyun outb(0, FCR(dev->base_addr)); /* disable FIFOs */
475*4882a593Smuzhiyun outb(0x0d, MCR(dev->base_addr));
476*4882a593Smuzhiyun outb(0, IER(dev->base_addr));
477*4882a593Smuzhiyun if (request_irq(dev->irq, ser12_interrupt, IRQF_SHARED,
478*4882a593Smuzhiyun "baycom_ser12", dev)) {
479*4882a593Smuzhiyun release_region(dev->base_addr, SER12_EXTENT);
480*4882a593Smuzhiyun return -EBUSY;
481*4882a593Smuzhiyun }
482*4882a593Smuzhiyun /*
483*4882a593Smuzhiyun * enable transmitter empty interrupt
484*4882a593Smuzhiyun */
485*4882a593Smuzhiyun outb(2, IER(dev->base_addr));
486*4882a593Smuzhiyun /*
487*4882a593Smuzhiyun * set the SIO to 6 Bits/character and 19200 or 28800 baud, so that
488*4882a593Smuzhiyun * we get exactly (hopefully) 2 or 3 interrupts per radio symbol,
489*4882a593Smuzhiyun * depending on the usage of the software DCD routine
490*4882a593Smuzhiyun */
491*4882a593Smuzhiyun ser12_set_divisor(dev, bc->opt_dcd ? 6 : 4);
492*4882a593Smuzhiyun printk(KERN_INFO "%s: ser12 at iobase 0x%lx irq %u uart %s\n",
493*4882a593Smuzhiyun bc_drvname, dev->base_addr, dev->irq, uart_str[u]);
494*4882a593Smuzhiyun return 0;
495*4882a593Smuzhiyun }
496*4882a593Smuzhiyun
497*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
498*4882a593Smuzhiyun
ser12_close(struct net_device * dev)499*4882a593Smuzhiyun static int ser12_close(struct net_device *dev)
500*4882a593Smuzhiyun {
501*4882a593Smuzhiyun struct baycom_state *bc = netdev_priv(dev);
502*4882a593Smuzhiyun
503*4882a593Smuzhiyun if (!dev || !bc)
504*4882a593Smuzhiyun return -EINVAL;
505*4882a593Smuzhiyun /*
506*4882a593Smuzhiyun * disable interrupts
507*4882a593Smuzhiyun */
508*4882a593Smuzhiyun outb(0, IER(dev->base_addr));
509*4882a593Smuzhiyun outb(1, MCR(dev->base_addr));
510*4882a593Smuzhiyun free_irq(dev->irq, dev);
511*4882a593Smuzhiyun release_region(dev->base_addr, SER12_EXTENT);
512*4882a593Smuzhiyun printk(KERN_INFO "%s: close ser12 at iobase 0x%lx irq %u\n",
513*4882a593Smuzhiyun bc_drvname, dev->base_addr, dev->irq);
514*4882a593Smuzhiyun return 0;
515*4882a593Smuzhiyun }
516*4882a593Smuzhiyun
517*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
518*4882a593Smuzhiyun /*
519*4882a593Smuzhiyun * ===================== hdlcdrv driver interface =========================
520*4882a593Smuzhiyun */
521*4882a593Smuzhiyun
522*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
523*4882a593Smuzhiyun
524*4882a593Smuzhiyun static int baycom_ioctl(struct net_device *dev, struct ifreq *ifr,
525*4882a593Smuzhiyun struct hdlcdrv_ioctl *hi, int cmd);
526*4882a593Smuzhiyun
527*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
528*4882a593Smuzhiyun
529*4882a593Smuzhiyun static const struct hdlcdrv_ops ser12_ops = {
530*4882a593Smuzhiyun .drvname = bc_drvname,
531*4882a593Smuzhiyun .drvinfo = bc_drvinfo,
532*4882a593Smuzhiyun .open = ser12_open,
533*4882a593Smuzhiyun .close = ser12_close,
534*4882a593Smuzhiyun .ioctl = baycom_ioctl,
535*4882a593Smuzhiyun };
536*4882a593Smuzhiyun
537*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
538*4882a593Smuzhiyun
baycom_setmode(struct baycom_state * bc,const char * modestr)539*4882a593Smuzhiyun static int baycom_setmode(struct baycom_state *bc, const char *modestr)
540*4882a593Smuzhiyun {
541*4882a593Smuzhiyun if (strchr(modestr, '*'))
542*4882a593Smuzhiyun bc->opt_dcd = 0;
543*4882a593Smuzhiyun else if (strchr(modestr, '+'))
544*4882a593Smuzhiyun bc->opt_dcd = -1;
545*4882a593Smuzhiyun else if (strchr(modestr, '@'))
546*4882a593Smuzhiyun bc->opt_dcd = -2;
547*4882a593Smuzhiyun else
548*4882a593Smuzhiyun bc->opt_dcd = 1;
549*4882a593Smuzhiyun return 0;
550*4882a593Smuzhiyun }
551*4882a593Smuzhiyun
552*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
553*4882a593Smuzhiyun
baycom_ioctl(struct net_device * dev,struct ifreq * ifr,struct hdlcdrv_ioctl * hi,int cmd)554*4882a593Smuzhiyun static int baycom_ioctl(struct net_device *dev, struct ifreq *ifr,
555*4882a593Smuzhiyun struct hdlcdrv_ioctl *hi, int cmd)
556*4882a593Smuzhiyun {
557*4882a593Smuzhiyun struct baycom_state *bc;
558*4882a593Smuzhiyun struct baycom_ioctl bi;
559*4882a593Smuzhiyun
560*4882a593Smuzhiyun if (!dev)
561*4882a593Smuzhiyun return -EINVAL;
562*4882a593Smuzhiyun
563*4882a593Smuzhiyun bc = netdev_priv(dev);
564*4882a593Smuzhiyun BUG_ON(bc->hdrv.magic != HDLCDRV_MAGIC);
565*4882a593Smuzhiyun
566*4882a593Smuzhiyun if (cmd != SIOCDEVPRIVATE)
567*4882a593Smuzhiyun return -ENOIOCTLCMD;
568*4882a593Smuzhiyun switch (hi->cmd) {
569*4882a593Smuzhiyun default:
570*4882a593Smuzhiyun break;
571*4882a593Smuzhiyun
572*4882a593Smuzhiyun case HDLCDRVCTL_GETMODE:
573*4882a593Smuzhiyun strcpy(hi->data.modename, "ser12");
574*4882a593Smuzhiyun if (bc->opt_dcd <= 0)
575*4882a593Smuzhiyun strcat(hi->data.modename, (!bc->opt_dcd) ? "*" : (bc->opt_dcd == -2) ? "@" : "+");
576*4882a593Smuzhiyun if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl)))
577*4882a593Smuzhiyun return -EFAULT;
578*4882a593Smuzhiyun return 0;
579*4882a593Smuzhiyun
580*4882a593Smuzhiyun case HDLCDRVCTL_SETMODE:
581*4882a593Smuzhiyun if (netif_running(dev) || !capable(CAP_NET_ADMIN))
582*4882a593Smuzhiyun return -EACCES;
583*4882a593Smuzhiyun hi->data.modename[sizeof(hi->data.modename)-1] = '\0';
584*4882a593Smuzhiyun return baycom_setmode(bc, hi->data.modename);
585*4882a593Smuzhiyun
586*4882a593Smuzhiyun case HDLCDRVCTL_MODELIST:
587*4882a593Smuzhiyun strcpy(hi->data.modename, "ser12");
588*4882a593Smuzhiyun if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl)))
589*4882a593Smuzhiyun return -EFAULT;
590*4882a593Smuzhiyun return 0;
591*4882a593Smuzhiyun
592*4882a593Smuzhiyun case HDLCDRVCTL_MODEMPARMASK:
593*4882a593Smuzhiyun return HDLCDRV_PARMASK_IOBASE | HDLCDRV_PARMASK_IRQ;
594*4882a593Smuzhiyun
595*4882a593Smuzhiyun }
596*4882a593Smuzhiyun
597*4882a593Smuzhiyun if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi)))
598*4882a593Smuzhiyun return -EFAULT;
599*4882a593Smuzhiyun switch (bi.cmd) {
600*4882a593Smuzhiyun default:
601*4882a593Smuzhiyun return -ENOIOCTLCMD;
602*4882a593Smuzhiyun
603*4882a593Smuzhiyun #ifdef BAYCOM_DEBUG
604*4882a593Smuzhiyun case BAYCOMCTL_GETDEBUG:
605*4882a593Smuzhiyun bi.data.dbg.debug1 = bc->hdrv.ptt_keyed;
606*4882a593Smuzhiyun bi.data.dbg.debug2 = bc->debug_vals.last_intcnt;
607*4882a593Smuzhiyun bi.data.dbg.debug3 = bc->debug_vals.last_pllcorr;
608*4882a593Smuzhiyun break;
609*4882a593Smuzhiyun #endif /* BAYCOM_DEBUG */
610*4882a593Smuzhiyun
611*4882a593Smuzhiyun }
612*4882a593Smuzhiyun if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi)))
613*4882a593Smuzhiyun return -EFAULT;
614*4882a593Smuzhiyun return 0;
615*4882a593Smuzhiyun
616*4882a593Smuzhiyun }
617*4882a593Smuzhiyun
618*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
619*4882a593Smuzhiyun
620*4882a593Smuzhiyun /*
621*4882a593Smuzhiyun * command line settable parameters
622*4882a593Smuzhiyun */
623*4882a593Smuzhiyun static char *mode[NR_PORTS] = { "ser12*", };
624*4882a593Smuzhiyun static int iobase[NR_PORTS] = { 0x3f8, };
625*4882a593Smuzhiyun static int irq[NR_PORTS] = { 4, };
626*4882a593Smuzhiyun
627*4882a593Smuzhiyun module_param_array(mode, charp, NULL, 0);
628*4882a593Smuzhiyun MODULE_PARM_DESC(mode, "baycom operating mode; * for software DCD");
629*4882a593Smuzhiyun module_param_hw_array(iobase, int, ioport, NULL, 0);
630*4882a593Smuzhiyun MODULE_PARM_DESC(iobase, "baycom io base address");
631*4882a593Smuzhiyun module_param_hw_array(irq, int, irq, NULL, 0);
632*4882a593Smuzhiyun MODULE_PARM_DESC(irq, "baycom irq number");
633*4882a593Smuzhiyun
634*4882a593Smuzhiyun MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu");
635*4882a593Smuzhiyun MODULE_DESCRIPTION("Baycom ser12 half duplex amateur radio modem driver");
636*4882a593Smuzhiyun MODULE_LICENSE("GPL");
637*4882a593Smuzhiyun
638*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
639*4882a593Smuzhiyun
init_baycomserhdx(void)640*4882a593Smuzhiyun static int __init init_baycomserhdx(void)
641*4882a593Smuzhiyun {
642*4882a593Smuzhiyun int i, found = 0;
643*4882a593Smuzhiyun char set_hw = 1;
644*4882a593Smuzhiyun
645*4882a593Smuzhiyun printk(bc_drvinfo);
646*4882a593Smuzhiyun /*
647*4882a593Smuzhiyun * register net devices
648*4882a593Smuzhiyun */
649*4882a593Smuzhiyun for (i = 0; i < NR_PORTS; i++) {
650*4882a593Smuzhiyun struct net_device *dev;
651*4882a593Smuzhiyun struct baycom_state *bc;
652*4882a593Smuzhiyun char ifname[IFNAMSIZ];
653*4882a593Smuzhiyun
654*4882a593Smuzhiyun sprintf(ifname, "bcsh%d", i);
655*4882a593Smuzhiyun
656*4882a593Smuzhiyun if (!mode[i])
657*4882a593Smuzhiyun set_hw = 0;
658*4882a593Smuzhiyun if (!set_hw)
659*4882a593Smuzhiyun iobase[i] = irq[i] = 0;
660*4882a593Smuzhiyun
661*4882a593Smuzhiyun dev = hdlcdrv_register(&ser12_ops,
662*4882a593Smuzhiyun sizeof(struct baycom_state),
663*4882a593Smuzhiyun ifname, iobase[i], irq[i], 0);
664*4882a593Smuzhiyun if (IS_ERR(dev))
665*4882a593Smuzhiyun break;
666*4882a593Smuzhiyun
667*4882a593Smuzhiyun bc = netdev_priv(dev);
668*4882a593Smuzhiyun if (set_hw && baycom_setmode(bc, mode[i]))
669*4882a593Smuzhiyun set_hw = 0;
670*4882a593Smuzhiyun found++;
671*4882a593Smuzhiyun baycom_device[i] = dev;
672*4882a593Smuzhiyun }
673*4882a593Smuzhiyun
674*4882a593Smuzhiyun if (!found)
675*4882a593Smuzhiyun return -ENXIO;
676*4882a593Smuzhiyun return 0;
677*4882a593Smuzhiyun }
678*4882a593Smuzhiyun
cleanup_baycomserhdx(void)679*4882a593Smuzhiyun static void __exit cleanup_baycomserhdx(void)
680*4882a593Smuzhiyun {
681*4882a593Smuzhiyun int i;
682*4882a593Smuzhiyun
683*4882a593Smuzhiyun for(i = 0; i < NR_PORTS; i++) {
684*4882a593Smuzhiyun struct net_device *dev = baycom_device[i];
685*4882a593Smuzhiyun
686*4882a593Smuzhiyun if (dev)
687*4882a593Smuzhiyun hdlcdrv_unregister(dev);
688*4882a593Smuzhiyun }
689*4882a593Smuzhiyun }
690*4882a593Smuzhiyun
691*4882a593Smuzhiyun module_init(init_baycomserhdx);
692*4882a593Smuzhiyun module_exit(cleanup_baycomserhdx);
693*4882a593Smuzhiyun
694*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
695*4882a593Smuzhiyun
696*4882a593Smuzhiyun #ifndef MODULE
697*4882a593Smuzhiyun
698*4882a593Smuzhiyun /*
699*4882a593Smuzhiyun * format: baycom_ser_hdx=io,irq,mode
700*4882a593Smuzhiyun * mode: ser12 hardware DCD
701*4882a593Smuzhiyun * ser12* software DCD
702*4882a593Smuzhiyun * ser12@ hardware/software DCD, i.e. no explicit DCD signal but hardware
703*4882a593Smuzhiyun * mutes audio input to the modem
704*4882a593Smuzhiyun * ser12+ hardware DCD, inverted signal at DCD pin
705*4882a593Smuzhiyun */
706*4882a593Smuzhiyun
baycom_ser_hdx_setup(char * str)707*4882a593Smuzhiyun static int __init baycom_ser_hdx_setup(char *str)
708*4882a593Smuzhiyun {
709*4882a593Smuzhiyun static unsigned nr_dev;
710*4882a593Smuzhiyun int ints[3];
711*4882a593Smuzhiyun
712*4882a593Smuzhiyun if (nr_dev >= NR_PORTS)
713*4882a593Smuzhiyun return 0;
714*4882a593Smuzhiyun str = get_options(str, 3, ints);
715*4882a593Smuzhiyun if (ints[0] < 2)
716*4882a593Smuzhiyun return 0;
717*4882a593Smuzhiyun mode[nr_dev] = str;
718*4882a593Smuzhiyun iobase[nr_dev] = ints[1];
719*4882a593Smuzhiyun irq[nr_dev] = ints[2];
720*4882a593Smuzhiyun nr_dev++;
721*4882a593Smuzhiyun return 1;
722*4882a593Smuzhiyun }
723*4882a593Smuzhiyun
724*4882a593Smuzhiyun __setup("baycom_ser_hdx=", baycom_ser_hdx_setup);
725*4882a593Smuzhiyun
726*4882a593Smuzhiyun #endif /* MODULE */
727*4882a593Smuzhiyun /* --------------------------------------------------------------------- */
728