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