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