brintos

brintos / linux-shallow public Read only

0
0
Text · 19.9 KiB · 5d1ab48 Raw
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