patch-2.1.7 linux/drivers/net/soundmodem.c

Next file: linux/drivers/net/tulip.c
Previous file: linux/drivers/net/sm_tables.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.6/linux/drivers/net/soundmodem.c linux/drivers/net/soundmodem.c
@@ -0,0 +1,2120 @@
+/*****************************************************************************/
+
+/*
+ *	soundmodem.c  -- soundcard radio modem driver.
+ *
+ *	Copyright (C) 1996  Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *  Please note that the GPL allows you to use the driver, NOT the radio.
+ *  In order to use the radio, you need a license from the communications
+ *  authority of your country.
+ *
+ *
+ *  Command line options (insmod command line)
+ * 
+ *  hardware hardware type; 0=sbc, 1=wss, any other value invalid
+ *  mode     mode type; 0=1200 baud AFSK, 1=9600 baud FSK, any other
+ *           value invalid
+ *  iobase   base address of the soundcard; common values are 0x220 for sbc,
+ *           0x530 for wss
+ *  irq      interrupt number; common values are 7 or 5 for sbc, 11 for wss
+ *  dma      dma number; common values are 0 or 1
+ * 
+ *
+ *  History:
+ *   0.1  21.09.96  Started
+ *        18.10.96  Changed to new user space access routines (copy_{to,from}_user)
+ */
+
+/*****************************************************************************/
+
+#include <linux/module.h>
+
+#include <linux/ptrace.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/string.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/uaccess.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <limits.h>
+#include <linux/hdlcdrv.h>
+#include <linux/soundmodem.h>
+
+/* --------------------------------------------------------------------- */
+
+#define NR_PORTS 4
+
+#define SM_DEBUG
+
+#define ENABLE_SBC
+#define ENABLE_WSS
+#undef  ENABLE_WSSFDX
+
+#define ENABLE_AFSK1200
+#define ENABLE_FSK9600
+
+/* --------------------------------------------------------------------- */
+
+#include "sm_tables.h"
+
+/* --------------------------------------------------------------------- */
+
+static struct device sm_device[NR_PORTS];
+
+static struct {
+	int hardware, mode, iobase, irq, dma, seriobase, pariobase, midiiobase;
+} sm_ports[NR_PORTS] = { 
+{ SM_HARDWARE_INVALID, SM_MODE_INVALID, -1, 0, 0, -1, -1, -1 }, 
+};
+
+/* --------------------------------------------------------------------- */
+/* 
+ * the sbc converter's registers 
+ */
+#define DSP_RESET(iobase)        (iobase+0x6)
+#define DSP_READ_DATA(iobase)    (iobase+0xa)
+#define DSP_WRITE_DATA(iobase)   (iobase+0xc)
+#define DSP_WRITE_STATUS(iobase) (iobase+0xc)
+#define DSP_DATA_AVAIL(iobase)   (iobase+0xe)
+#define DSP_MIXER_ADDR(iobase)   (iobase+0x4)
+#define DSP_MIXER_DATA(iobase)   (iobase+0x5)
+#define SBC_EXTENT               16
+
+/* --------------------------------------------------------------------- */
+/*
+ * SBC commands
+ */
+#define SBC_OUTPUT             0x14
+#define SBC_INPUT              0x24
+#define SBC_HISPEED            0x48
+#define SBC_HI_OUTPUT          0x91 
+#define SBC_HI_INPUT           0x99 
+#define SBC_LO_OUTPUT_AUTOINIT 0x1c
+#define SBC_LO_INPUT_AUTOINIT  0x2c
+#define SBC_HI_OUTPUT_AUTOINIT 0x90 
+#define SBC_HI_INPUT_AUTOINIT  0x98
+#define SBC_IMMED_INT          0xf2
+#define SBC_GET_REVISION       0xe1
+#define ESS_GET_REVISION       0xe7
+#define SBC_SPEAKER_ON         0xd1
+#define SBC_SPEAKER_OFF        0xd3
+#define SBC_DMA_ON             0xd0
+#define SBC_DMA_OFF            0xd4
+#define SBC_SAMPLE_RATE        0x40
+#define SBC_MONO_8BIT          0xa0
+#define SBC_MONO_16BIT         0xa4
+#define SBC_STEREO_8BIT        0xa8
+#define SBC_STEREO_16BIT       0xac
+
+#define DMA_MODE_AUTOINIT      0x10
+
+/* --------------------------------------------------------------------- */
+
+#define WSS_CONFIG(iobase)       (iobase+0)
+#define WSS_STATUS(iobase)       (iobase+3)
+#define WSS_CODEC_IA(iobase)     (iobase+4)
+#define WSS_CODEC_ID(iobase)     (iobase+5)
+#define WSS_CODEC_STATUS(iobase) (iobase+6)
+#define WSS_CODEC_DATA(iobase)   (iobase+7)
+
+#define WSS_EXTENT   8
+
+/* --------------------------------------------------------------------- */
+
+#define UART_RBR(iobase) (iobase+0)
+#define UART_THR(iobase) (iobase+0)
+#define UART_IER(iobase) (iobase+1)
+#define UART_IIR(iobase) (iobase+2)
+#define UART_FCR(iobase) (iobase+2)
+#define UART_LCR(iobase) (iobase+3)
+#define UART_MCR(iobase) (iobase+4)
+#define UART_LSR(iobase) (iobase+5)
+#define UART_MSR(iobase) (iobase+6)
+#define UART_SCR(iobase) (iobase+7)
+#define UART_DLL(iobase) (iobase+0)
+#define UART_DLM(iobase) (iobase+1)
+
+#define SER_EXTENT 8
+
+#define LPT_DATA(iobase)    (iobase+0)
+#define LPT_STATUS(iobase)  (iobase+1)
+#define LPT_CONTROL(iobase) (iobase+2)
+#define LPT_IRQ_ENABLE      0x10
+
+#define LPT_EXTENT 3
+
+#define MIDI_DATA(iobase)     (iobase)
+#define MIDI_STATUS(iobase)   (iobase+1)
+#define MIDI_READ_FULL 0x80   /* attention: negative logic!! */
+#define MIDI_WRITE_EMPTY 0x40 /* attention: negative logic!! */
+
+#define MIDI_EXTENT 2
+
+/* ---------------------------------------------------------------------- */
+
+#define PARAM_TXDELAY   1
+#define PARAM_PERSIST   2
+#define PARAM_SLOTTIME  3
+#define PARAM_TXTAIL    4
+#define PARAM_FULLDUP   5
+#define PARAM_HARDWARE  6
+#define PARAM_RETURN    255
+
+#define SP_SER  1
+#define SP_PAR  2
+#define SP_MIDI 4
+
+/* ---------------------------------------------------------------------- */
+/*
+ * Information that need to be kept for each board. 
+ */
+
+struct sm_state {
+	struct hdlcdrv_state hdrv;
+
+	struct config {
+		int hardware;
+		int mode;
+	} config;
+
+	struct modem_state {
+		unsigned char revhi, revlo;
+		
+		unsigned int shreg;
+		
+		unsigned char last_sample;
+		unsigned int bit_pll;
+		unsigned int dcd_shreg;
+		int dcd_sum0, dcd_sum1, dcd_sum2;
+		unsigned int dcd_time;
+		unsigned char last_rxbit;
+		unsigned char tx_bit;
+		
+		signed char filt[9];
+		
+		unsigned long descram;
+		unsigned long scram;
+		
+		unsigned char *dmabufr;
+		unsigned char *dmabufw;
+		unsigned char dmabufidx;
+		unsigned char oldptt;
+	} modem;
+	
+#define DIAGDATALEN 64
+	struct diag_data {
+		unsigned int mode;
+		unsigned int flags;
+		volatile int ptr;
+		short data[DIAGDATALEN];
+	} diag;
+	
+#ifdef SM_DEBUG
+	struct debug_vals {
+		unsigned long last_jiffies;
+		unsigned cur_intcnt;
+		unsigned last_intcnt;
+		int cur_pllcorr;
+		int last_pllcorr;
+	} debug_vals;
+#endif /* SM_DEBUG */
+};
+
+/* --------------------------------------------------------------------- */
+
+struct modem_info {
+	struct hdlcdrv_ops hops;
+	unsigned int samplerate;
+	unsigned char sbcmix;
+	unsigned char sperbit;
+	char *mode_name; /* used for request_{region,irq,dma} */
+	/*
+	 * low level chip informations
+	 */
+	unsigned char data_fmt;
+	unsigned int dmabuflen;
+	void (*modulator)(struct sm_state *, unsigned char *, int);
+	void (*demodulator)(struct sm_state *, unsigned char *, int);
+};
+
+/* --------------------------------------------------------------------- */
+
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+#define max(a, b) (((a) > (b)) ? (a) : (b))
+
+/* --------------------------------------------------------------------- */
+
+static void inline sm_int_freq(struct sm_state *sm)
+{
+#ifdef SM_DEBUG
+	unsigned long cur_jiffies = jiffies;
+	/* 
+	 * measure the interrupt frequency
+	 */
+	sm->debug_vals.cur_intcnt++;
+	if ((cur_jiffies - sm->debug_vals.last_jiffies) >= HZ) {
+		sm->debug_vals.last_jiffies = cur_jiffies;
+		sm->debug_vals.last_intcnt = sm->debug_vals.cur_intcnt;
+		sm->debug_vals.cur_intcnt = 0;
+	}
+#endif /* SM_DEBUG */
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== port checking routines ========================
+ */
+
+static int inline check_lpt(unsigned int iobase)
+{
+	unsigned char b1,b2;
+	int i;
+
+	if (iobase <= 0 || iobase > 0x1000-LPT_EXTENT)
+		return 0;
+	if (check_region(iobase, LPT_EXTENT))
+		return 0;
+	b1 = inb(LPT_DATA(iobase));
+	b2 = inb(LPT_CONTROL(iobase));
+	outb(0xaa, LPT_DATA(iobase));
+	i = inb(LPT_DATA(iobase)) == 0xaa;
+	outb(0x55, LPT_DATA(iobase));
+	i &= inb(LPT_DATA(iobase)) == 0x55;
+	outb(0x0a, LPT_CONTROL(iobase));
+	i &= (inb(LPT_CONTROL(iobase)) & 0xf) == 0x0a;
+	outb(0x05, LPT_CONTROL(iobase));
+	i &= (inb(LPT_CONTROL(iobase)) & 0xf) == 0x05;
+	outb(b1, LPT_DATA(iobase));
+	outb(b2, LPT_CONTROL(iobase));
+	return !i;
+}
+
+/* --------------------------------------------------------------------- */
+
+enum uart { c_uart_unknown, c_uart_8250,
+	c_uart_16450, c_uart_16550, c_uart_16550A};
+static const char *uart_str[] =
+	{ "unknown", "8250", "16450", "16550", "16550A" };
+
+static enum uart inline check_uart(unsigned int iobase)
+{
+	unsigned char b1,b2,b3;
+	enum uart u;
+	enum uart uart_tab[] =
+		{ c_uart_16450, c_uart_unknown, c_uart_16550, c_uart_16550A };
+
+	if (iobase <= 0 || iobase > 0x1000-SER_EXTENT)
+		return c_uart_unknown;
+	if (check_region(iobase, SER_EXTENT))
+		return c_uart_unknown;
+	b1 = inb(UART_MCR(iobase));
+	outb(b1 | 0x10, UART_MCR(iobase));	/* loopback mode */
+	b2 = inb(UART_MSR(iobase));
+	outb(0x1a, UART_MCR(iobase));
+	b3 = inb(UART_MSR(iobase)) & 0xf0;
+	outb(b1, UART_MCR(iobase));	   /* restore old values */
+	outb(b2, UART_MSR(iobase));
+	if (b3 != 0x90) 
+		return c_uart_unknown;
+	inb(UART_RBR(iobase));
+	inb(UART_RBR(iobase));
+	outb(0x01, UART_FCR(iobase));		/* enable FIFOs */
+	u = uart_tab[(inb(UART_IIR(iobase)) >> 6) & 3];
+	if (u == c_uart_16450) {
+		outb(0x5a, UART_SCR(iobase));
+		b1 = inb(UART_SCR(iobase));
+		outb(0xa5, UART_SCR(iobase));
+		b2 = inb(UART_SCR(iobase));
+		if ((b1 != 0x5a) || (b2 != 0xa5)) 
+			u = c_uart_8250;
+	}
+	return u;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int inline check_midi(unsigned int iobase)
+{
+	unsigned long timeout;
+	unsigned long flags;
+	unsigned char b;
+
+	if (iobase <= 0 || iobase > 0x1000-MIDI_EXTENT)
+		return 0;
+	if (check_region(iobase, MIDI_EXTENT))
+		return 0;
+	timeout = jiffies + (HZ / 100);
+	while (inb(MIDI_STATUS(iobase)) & MIDI_WRITE_EMPTY)
+		if ((signed)(jiffies - timeout) > 0)
+			return 0;
+	save_flags(flags);
+	cli();
+	outb(0xff, MIDI_DATA(iobase));
+	b = inb(MIDI_STATUS(iobase));
+	restore_flags(flags);
+	if (!(b & MIDI_WRITE_EMPTY))
+		return 0;
+	while (inb(MIDI_STATUS(iobase)) & MIDI_WRITE_EMPTY)
+		if ((signed)(jiffies - timeout) > 0)
+			return 0;
+	return 1;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void inline output_status(struct sm_state *sm)
+{
+	int invert_dcd = 0;
+	int invert_ptt = 0;
+
+	int ptt = hdlcdrv_ptt(&sm->hdrv) ^ invert_ptt;
+	int dcd = (!!sm->hdrv.hdlcrx.dcd) ^ invert_dcd;
+
+	if (sm->hdrv.ptt_out.flags & SP_SER) {
+		outb(dcd | (ptt << 1), UART_MCR(sm->hdrv.ptt_out.seriobase));
+		outb(0x40 & (-ptt), UART_LCR(sm->hdrv.ptt_out.seriobase));
+	}
+	if (sm->hdrv.ptt_out.flags & SP_PAR) {
+		outb(ptt | (dcd << 1), LPT_DATA(sm->hdrv.ptt_out.pariobase));
+	}
+	if (sm->hdrv.ptt_out.flags & SP_MIDI && hdlcdrv_ptt(&sm->hdrv)) {
+		outb(0, MIDI_DATA(sm->hdrv.ptt_out.midiiobase));
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static void output_open(struct sm_state *sm)
+{
+	enum uart u = c_uart_unknown;
+
+	sm->hdrv.ptt_out.flags = 0;
+	if (sm->hdrv.ptt_out.seriobase > 0 && 
+	    sm->hdrv.ptt_out.seriobase <= 0x1000-SER_EXTENT &&
+	    ((u = check_uart(sm->hdrv.ptt_out.seriobase))) != c_uart_unknown) {
+		sm->hdrv.ptt_out.flags |= SP_SER;
+		request_region(sm->hdrv.ptt_out.seriobase, SER_EXTENT, "sm ser ptt");
+		outb(0, UART_IER(sm->hdrv.ptt_out.seriobase));
+		/* 5 bits, 1 stop, no parity, no break, Div latch access */
+		outb(0x80, UART_LCR(sm->hdrv.ptt_out.seriobase)); 
+		outb(0, UART_DLM(sm->hdrv.ptt_out.seriobase));
+		outb(1, UART_DLL(sm->hdrv.ptt_out.seriobase)); /* as fast as possible */
+		/* LCR and MCR set by output_status */
+	}
+	if (sm->hdrv.ptt_out.pariobase > 0 && 
+	    sm->hdrv.ptt_out.pariobase <= 0x1000-LPT_EXTENT &&
+	    check_lpt(sm->hdrv.ptt_out.pariobase)) {
+		sm->hdrv.ptt_out.flags |= SP_PAR;
+		request_region(sm->hdrv.ptt_out.pariobase, LPT_EXTENT, "sm par ptt");
+	}
+	if (sm->hdrv.ptt_out.midiiobase > 0 && 
+	    sm->hdrv.ptt_out.midiiobase <= 0x1000-MIDI_EXTENT &&
+	    check_midi(sm->hdrv.ptt_out.midiiobase)) {
+		sm->hdrv.ptt_out.flags |= SP_MIDI;
+		request_region(sm->hdrv.ptt_out.midiiobase, MIDI_EXTENT, 
+			       "sm midi ptt");
+	}
+	output_status(sm);
+
+	printk(KERN_INFO "sm: ptt output:");
+	if (sm->hdrv.ptt_out.flags & SP_SER)
+		printk(" serial interface at 0x%x, uart %s", sm->hdrv.ptt_out.seriobase,
+		       uart_str[u]);
+	if (sm->hdrv.ptt_out.flags & SP_PAR)
+		printk(" parallel interface at 0x%x", sm->hdrv.ptt_out.pariobase);
+	if (sm->hdrv.ptt_out.flags & SP_MIDI)
+		printk(" mpu401 (midi) interface at 0x%x", sm->hdrv.ptt_out.midiiobase);
+	if (!sm->hdrv.ptt_out.flags)
+		printk(" none");
+	printk("\n");
+}
+
+/* --------------------------------------------------------------------- */
+
+static void output_close(struct sm_state *sm)
+{
+	/* release regions used for PTT output */
+	sm->hdrv.hdlctx.ptt = sm->hdrv.hdlctx.calibrate = 0;
+	output_status(sm);
+	if (sm->hdrv.ptt_out.flags & SP_SER)
+		release_region(sm->hdrv.ptt_out.seriobase, SER_EXTENT);
+       	if (sm->hdrv.ptt_out.flags & SP_PAR) 
+		release_region(sm->hdrv.ptt_out.pariobase, LPT_EXTENT);
+       	if (sm->hdrv.ptt_out.flags & SP_MIDI) 
+		release_region(sm->hdrv.ptt_out.midiiobase, MIDI_EXTENT);
+	sm->hdrv.ptt_out.flags = 0;
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== diagnostics stuff ===============================
+ */
+
+static inline void diag_trigger(struct sm_state *sm)
+{
+	if (sm->diag.ptr < 0) 
+		if (!(sm->diag.flags & SM_DIAGFLAG_DCDGATE) || sm->hdrv.hdlcrx.dcd)
+			sm->diag.ptr = 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static inline void diag_add(struct sm_state *sm, int valinp, int valdemod)
+{
+	int val;
+
+	if ((sm->diag.mode != SM_DIAGMODE_INPUT && 
+	     sm->diag.mode != SM_DIAGMODE_DEMOD) || 
+	    sm->diag.ptr >= DIAGDATALEN || sm->diag.ptr < 0)
+		return;
+	val = (sm->diag.mode == SM_DIAGMODE_DEMOD) ? valdemod : valinp;
+	/* clip */
+	if (val > SHRT_MAX)
+		val = SHRT_MAX;
+	if (val < SHRT_MIN)
+		val = SHRT_MIN;
+	sm->diag.data[sm->diag.ptr++] = val;
+}
+
+/* --------------------------------------------------------------------- */
+
+static inline void diag_add_one(struct sm_state *sm, int val)
+{
+	if ((sm->diag.mode != SM_DIAGMODE_INPUT && 
+	     sm->diag.mode != SM_DIAGMODE_DEMOD) || 
+	    sm->diag.ptr >= DIAGDATALEN || sm->diag.ptr < 0)
+		return;
+	/* clip */
+	if (val > SHRT_MAX)
+		val = SHRT_MAX;
+	if (val < SHRT_MIN)
+		val = SHRT_MIN;
+	sm->diag.data[sm->diag.ptr++] = val;
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== modem routines 1200 baud =========================
+ */
+
+static inline unsigned int hweight32(unsigned int w)
+	__attribute__ ((unused));
+static inline unsigned int hweight16(unsigned short w)
+	__attribute__ ((unused));
+static inline unsigned int hweight8(unsigned char w)
+        __attribute__ ((unused));
+
+static inline unsigned int hweight32(unsigned int w) 
+{
+        unsigned int res = (w & 0x55555555) + ((w >> 1) & 0x55555555);
+        res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
+        res = (res & 0x0F0F0F0F) + ((res >> 4) & 0x0F0F0F0F);
+        res = (res & 0x00FF00FF) + ((res >> 8) & 0x00FF00FF);
+        return (res & 0x0000FFFF) + ((res >> 16) & 0x0000FFFF);
+}
+
+static inline unsigned int hweight16(unsigned short w)
+{
+        unsigned short res = (w & 0x5555) + ((w >> 1) & 0x5555);
+        res = (res & 0x3333) + ((res >> 2) & 0x3333);
+        res = (res & 0x0F0F) + ((res >> 4) & 0x0F0F);
+        return (res & 0x00FF) + ((res >> 8) & 0x00FF);
+}
+
+static inline unsigned int hweight8(unsigned char w)
+{
+        unsigned short res = (w & 0x55) + ((w >> 1) & 0x55);
+        res = (res & 0x33) + ((res >> 2) & 0x33);
+        return (res & 0x0F) + ((res >> 4) & 0x0F);
+}
+
+/* --------------------------------------------------------------------- */
+
+#ifdef ENABLE_AFSK1200
+
+static void modulator_1200(struct sm_state *sm, unsigned char *buf, int buflen)
+{
+	static const int dds_inc[2] = { 8192, 15019 };
+	int j, k;
+
+	for (; buflen >= 8; buflen -= 8) {
+		if (sm->modem.shreg <= 1)
+			sm->modem.shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000;
+		sm->modem.tx_bit = (sm->modem.tx_bit ^ 
+				    (!(sm->modem.shreg & 1))) & 1;
+		sm->modem.shreg >>= 1;
+		k = dds_inc[sm->modem.tx_bit & 1];
+		for (j = 0; j < 8; j++) {
+			*buf++ = sinetab[(sm->modem.bit_pll >> 10) & 0x3f];
+			sm->modem.bit_pll += k;
+		}
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static void demodulator_1200(struct sm_state *sm, unsigned char *buf, int buflen)
+{
+	static const int pll_corr[2] = { -0x1000, 0x1000 };
+	int j;
+	signed char *fp;
+	const signed char *coeffp;
+	int sum1, sum2;
+	unsigned char newsample;
+
+	for (; buflen > 0; buflen--, buf++) {
+		sm->modem.filt[8] = (*buf - 128);
+		for (sum1 = j = 0, fp = sm->modem.filt+1, coeffp = tx_lo_i; 
+		     j < 8; j++, fp++, coeffp++) {
+			sum1 += (*coeffp) * (*fp);
+			fp[-1] = fp[0];
+		}
+		sum1 >>= 7;
+		sum2 = sum1 * sum1;
+		for (sum1 = j = 0, fp = sm->modem.filt, coeffp = tx_lo_q; 
+		     j < 8; j++, fp++, coeffp++)
+			sum1 += (*coeffp) * (*fp);
+		sum1 >>= 7;
+		sum2 += sum1 * sum1;
+		for (sum1 = j = 0, fp = sm->modem.filt, coeffp = tx_hi_i; 
+		     j < 8; j++, fp++, coeffp++)
+			sum1 += (*coeffp) * (*fp);
+		sum1 >>= 7;
+		sum2 -= sum1 * sum1;
+		for (sum1 = j = 0, fp = sm->modem.filt, coeffp = tx_hi_q; 
+		     j < 8; j++, fp++, coeffp++)
+			sum1 += (*coeffp) * (*fp);
+		sum1 >>= 7;
+		sum2 -= sum1 * sum1;
+		sm->modem.dcd_shreg <<= 1;
+		sm->modem.bit_pll += 0x2000;
+		newsample = (sum2 > 0);
+		if (sm->modem.last_sample ^ newsample) {
+			sm->modem.last_sample = newsample;
+			sm->modem.dcd_shreg |= 1;
+			sm->modem.bit_pll += pll_corr
+				[sm->modem.bit_pll < 0x9000];
+			j = 4 * hweight8(sm->modem.dcd_shreg & 0x38)
+				- hweight16(sm->modem.dcd_shreg & 0x7c0);
+			sm->modem.dcd_sum0 += j;
+		}
+		hdlcdrv_channelbit(&sm->hdrv, sm->modem.last_sample);
+		if ((--sm->modem.dcd_time) <= 0) {
+			hdlcdrv_setdcd(&sm->hdrv, (sm->modem.dcd_sum0 + 
+						   sm->modem.dcd_sum1 + 
+						   sm->modem.dcd_sum2) < 0);
+			sm->modem.dcd_sum2 = sm->modem.dcd_sum1;
+			sm->modem.dcd_sum1 = sm->modem.dcd_sum0;
+			sm->modem.dcd_sum0 = 2; /* slight bias */
+			sm->modem.dcd_time = 120;
+		}
+		if (sm->modem.bit_pll >= 0x10000) {
+			sm->modem.bit_pll &= 0xffff;
+			sm->modem.shreg >>= 1;
+			sm->modem.shreg |= (!(sm->modem.last_rxbit ^
+					      sm->modem.last_sample)) << 16;
+			sm->modem.last_rxbit = sm->modem.last_sample;
+			diag_trigger(sm);
+			if (sm->modem.shreg & 1) {
+				hdlcdrv_putbits(&sm->hdrv, sm->modem.shreg >> 1);
+				sm->modem.shreg = 0x10000;
+			}
+		}
+		diag_add(sm, sm->modem.filt[7] << 8, sum2);
+	}
+}
+
+#endif /* ENABLE_AFSK1200 */
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== modem routines 9600 baud =========================
+ */
+
+#ifdef ENABLE_FSK9600
+
+#define DESCRAM_TAP1 0x20000
+#define DESCRAM_TAP2 0x01000
+#define DESCRAM_TAP3 0x00001
+
+#define DESCRAM_TAPSH1 17
+#define DESCRAM_TAPSH2 12
+#define DESCRAM_TAPSH3 0
+
+#define SCRAM_TAP1 0x20000 /* X^17 */
+#define SCRAM_TAPN 0x00021 /* X^0+X^5 */
+
+/* --------------------------------------------------------------------- */
+
+#ifdef ENABLE_SBC
+
+static void modulator_9600_4(struct sm_state *sm, unsigned char *buf, int buflen)
+{
+	int j;
+	const unsigned char *cp;
+
+	for (; buflen >= 4; buflen -= 4) {
+		if (sm->modem.shreg <= 1)
+			sm->modem.shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000;
+		sm->modem.scram = ((sm->modem.scram << 1) |
+				   (sm->modem.scram & 1));
+		sm->modem.scram ^= (!(sm->modem.shreg & 1));
+		sm->modem.shreg >>= 1;
+		if (sm->modem.scram & (SCRAM_TAP1 << 1))
+			sm->modem.scram ^= (SCRAM_TAPN << 1);
+		sm->modem.tx_bit = (sm->modem.tx_bit << 1) | 
+			(!!(sm->modem.scram & (SCRAM_TAP1 << 2)));
+		cp = tx_filter_9k6_4 + (sm->modem.tx_bit & 0xff);
+		for (j = 0; j < 4; j++) {
+			*buf++ = *cp;
+			cp += 0x100;
+		}
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static void demodulator_9600_4(struct sm_state *sm, unsigned char *buf, int buflen)
+{
+	static const int pll_corr[2] = { -0x1000, 0x1000 };
+	unsigned char curbit;
+	unsigned int descx;
+
+	for (; buflen > 0; buflen--, buf++) {
+		sm->modem.dcd_shreg <<= 1;
+		sm->modem.bit_pll += 0x4000;
+		curbit = (*buf >= 0x80);
+		if (sm->modem.last_sample ^ curbit) {
+			sm->modem.dcd_shreg |= 1;
+			sm->modem.bit_pll += pll_corr
+				[sm->modem.bit_pll < 0xa000];
+			sm->modem.dcd_sum0 += 8 * 
+				hweight8(sm->modem.dcd_shreg & 0x0c) - 
+					!!(sm->modem.dcd_shreg & 0x10);
+		}
+		sm->modem.last_sample = curbit;
+		hdlcdrv_channelbit(&sm->hdrv, sm->modem.last_sample);
+		if ((--sm->modem.dcd_time) <= 0) {
+			hdlcdrv_setdcd(&sm->hdrv, (sm->modem.dcd_sum0 + 
+						   sm->modem.dcd_sum1 + 
+						   sm->modem.dcd_sum2) < 0);
+			sm->modem.dcd_sum2 = sm->modem.dcd_sum1;
+			sm->modem.dcd_sum1 = sm->modem.dcd_sum0;
+			sm->modem.dcd_sum0 = 2; /* slight bias */
+			sm->modem.dcd_time = 240;
+		}
+		if (sm->modem.bit_pll >= 0x10000) {
+			sm->modem.bit_pll &= 0xffff;
+			sm->modem.descram = (sm->modem.descram << 1) | curbit;
+			descx = sm->modem.descram ^ (sm->modem.descram >> 1);
+			descx ^= ((descx >> DESCRAM_TAPSH1) ^
+				  (descx >> DESCRAM_TAPSH2));
+			sm->modem.shreg >>= 1;
+			sm->modem.shreg |= (!(descx & 1)) << 16;
+			if (sm->modem.shreg & 1) {
+				hdlcdrv_putbits(&sm->hdrv, sm->modem.shreg >> 1);
+				sm->modem.shreg = 0x10000;
+			}
+			diag_trigger(sm);
+		}
+		diag_add_one(sm, ((short)(*buf - 0x80)) << 8);
+	}
+}
+
+#endif /* ENABLE_SBC */
+
+/* --------------------------------------------------------------------- */
+
+#if defined(ENABLE_WSS) || defined(ENABLE_WSSFDX)
+
+static void modulator_9600_5(struct sm_state *sm, unsigned char *buf, int buflen)
+{
+	int j;
+	const unsigned char *cp;
+
+	for (; buflen >= 5; buflen -= 5) {
+		if (sm->modem.shreg <= 1)
+			sm->modem.shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000;
+		sm->modem.scram = ((sm->modem.scram << 1) |
+				   (sm->modem.scram & 1));
+		sm->modem.scram ^= (!(sm->modem.shreg & 1));
+		sm->modem.shreg >>= 1;
+		if (sm->modem.scram & (SCRAM_TAP1 << 1))
+			sm->modem.scram ^= (SCRAM_TAPN << 1);
+		sm->modem.tx_bit = (sm->modem.tx_bit << 1) | 
+			(!!(sm->modem.scram & (SCRAM_TAP1 << 2)));
+		cp = tx_filter_9k6_5 + (sm->modem.tx_bit & 0xff);
+		for (j = 0; j < 5; j++) {
+			*buf++ = *cp;
+			cp += 0x100;
+		}
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static void demodulator_9600_5(struct sm_state *sm, unsigned char *buf, int buflen)
+{
+	static const int pll_corr[2] = { -0x1000, 0x1000 };
+	unsigned char curbit;
+	unsigned int descx;
+
+	for (; buflen > 0; buflen--, buf++) {
+		sm->modem.dcd_shreg <<= 1;
+		sm->modem.bit_pll += 0x3333;
+		curbit = (*buf >= 0x80);
+		if (sm->modem.last_sample ^ curbit) {
+			sm->modem.dcd_shreg |= 1;
+			sm->modem.bit_pll += pll_corr
+				[sm->modem.bit_pll < 0x9999];
+			sm->modem.dcd_sum0 += 16 * 
+				hweight8(sm->modem.dcd_shreg & 0x0c) - 
+					hweight8(sm->modem.dcd_shreg & 0x70);
+		}
+		sm->modem.last_sample = curbit;
+		hdlcdrv_channelbit(&sm->hdrv, sm->modem.last_sample);
+		if ((--sm->modem.dcd_time) <= 0) {
+			hdlcdrv_setdcd(&sm->hdrv, (sm->modem.dcd_sum0 + 
+						   sm->modem.dcd_sum1 + 
+						   sm->modem.dcd_sum2) < 0);
+			sm->modem.dcd_sum2 = sm->modem.dcd_sum1;
+			sm->modem.dcd_sum1 = sm->modem.dcd_sum0;
+			sm->modem.dcd_sum0 = 2; /* slight bias */
+			sm->modem.dcd_time = 240;
+		}
+		if (sm->modem.bit_pll >= 0x10000) {
+			sm->modem.bit_pll &= 0xffff;
+			sm->modem.descram = (sm->modem.descram << 1) | curbit;
+			descx = sm->modem.descram ^ (sm->modem.descram >> 1);
+			descx ^= ((descx >> DESCRAM_TAPSH1) ^
+				  (descx >> DESCRAM_TAPSH2));
+			sm->modem.shreg >>= 1;
+			sm->modem.shreg |= (!(descx & 1)) << 16;
+			if (sm->modem.shreg & 1) {
+				hdlcdrv_putbits(&sm->hdrv, sm->modem.shreg >> 1);
+				sm->modem.shreg = 0x10000;
+			}
+			diag_trigger(sm);
+		}
+		diag_add_one(sm, ((short)(*buf - 0x80)) << 8);
+	}
+}
+
+#endif /* defined(ENABLE_WSS) || defined(ENABLE_WSSFDX) */
+#endif /* ENABLE_FSK9600 */
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== soundblaster specific routines ===================
+ */
+
+#ifdef ENABLE_SBC
+
+static int inline reset_dsp(struct device *dev)
+{
+	int i;
+
+	outb(1, DSP_RESET(dev->base_addr));
+	for (i = 0; i < 0x100; i++)
+		SLOW_DOWN_IO;
+	outb(0, DSP_RESET(dev->base_addr));
+	for (i = 0; i < 0xffff; i++)
+		if (inb(DSP_DATA_AVAIL(dev->base_addr)) & 0x80)
+			if (inb(DSP_READ_DATA(dev->base_addr)) == 0xaa)
+				return 1;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void inline write_dsp(struct device *dev, unsigned char data)
+{
+	int i;
+	
+	for (i = 0; i < 0xffff; i++)
+		if (!(inb(DSP_WRITE_STATUS(dev->base_addr)) & 0x80)) {
+			outb(data, DSP_WRITE_DATA(dev->base_addr));
+			return;
+		}
+}
+
+/* --------------------------------------------------------------------- */
+
+static int inline read_dsp(struct device *dev, unsigned char *data)
+{
+	int i;
+
+	if (!data)
+		return 0;
+	for (i = 0; i < 0xffff; i++) 
+		if (inb(DSP_DATA_AVAIL(dev->base_addr)) & 0x80) {
+			*data = inb(DSP_READ_DATA(dev->base_addr));
+			return 1;
+		}
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void inline sbc_int_ack(struct device *dev)
+{
+	inb(DSP_DATA_AVAIL(dev->base_addr));
+}
+
+/* --------------------------------------------------------------------- */
+
+static void setup_dma_dsp(struct device *dev, int send)
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+        unsigned long flags;
+        static const unsigned char sbcmode[2][2] = {
+        { SBC_LO_INPUT_AUTOINIT, SBC_LO_OUTPUT_AUTOINIT }, 
+	{ SBC_HI_INPUT_AUTOINIT, SBC_HI_OUTPUT_AUTOINIT }};
+        static const unsigned char dmamode[2] = 
+        { DMA_MODE_READ | DMA_MODE_AUTOINIT, 
+		  DMA_MODE_WRITE  | DMA_MODE_AUTOINIT};
+	static const unsigned char sbcskr[2] = 
+	{ SBC_SPEAKER_OFF, SBC_SPEAKER_ON };
+	unsigned long dmabufaddr = virt_to_bus(sm->modem.dmabufr);
+
+	send = !!send;
+        if (!reset_dsp(dev)) {
+                printk(KERN_ERR "sm: cannot reset sb dsp\n");
+                return;
+        }
+        if ((dmabufaddr & 0xffff) + mi->dmabuflen > 0x10000)
+                panic("sm: DMA buffer violates DMA boundary!");
+        save_flags(flags);
+        cli();
+        sbc_int_ack(dev);
+        write_dsp(dev, SBC_SAMPLE_RATE); /* set sampling rate */
+        write_dsp(dev, mi->data_fmt);
+        write_dsp(dev, sbcskr[send]); 
+        disable_dma(dev->dma);
+        clear_dma_ff(dev->dma);
+        set_dma_mode(dev->dma, dmamode[send]);
+        set_dma_addr(dev->dma, dmabufaddr);
+        set_dma_count(dev->dma, mi->dmabuflen);
+        enable_dma(dev->dma);
+        sbc_int_ack(dev);
+        write_dsp(dev, SBC_HISPEED);
+        write_dsp(dev, ((mi->dmabuflen >> 1) - 1) & 0xff);
+        write_dsp(dev, ((mi->dmabuflen >> 1) - 1) >> 8);
+        write_dsp(dev, sbcmode[mi->samplerate >= 13000][send]);
+        restore_flags(flags);
+}
+
+/* --------------------------------------------------------------------- */
+
+#if 0
+static int probe_int(struct device *dev, struct sm_state *sm)
+{
+	unsigned long irqs;
+	int irq;
+
+	irqs = probe_irq_on();
+	setup_dma_dsp(dev, virt_to_bus(sm->modem.dmabufr), 4, 256-77, 0);
+	udelay(2000);
+        irq = probe_irq_off(irqs);
+	disable_dma(dev->dma);
+	return irq;
+}
+#endif
+
+/* --------------------------------------------------------------------- */
+
+static void sbc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct device *dev = (struct device *)dev_id;
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+	unsigned char new_ptt;
+	unsigned char *buf;
+
+	if (!dev || !sm || sm->hdrv.magic != HDLCDRV_MAGIC)
+		return;
+	new_ptt = hdlcdrv_ptt(&sm->hdrv);
+ 	sbc_int_ack(dev);
+ 	buf = sm->modem.dmabufr;
+	if (sm->modem.dmabufidx)
+		buf += mi->dmabuflen/2;
+	sm->modem.dmabufidx = !sm->modem.dmabufidx;
+	if (sm->modem.oldptt != new_ptt) {
+		disable_dma(dev->dma);
+		sti();
+ 		sm->modem.dmabufidx = 0;
+		if (!new_ptt) {
+			setup_dma_dsp(dev, 0);
+			goto endint;
+		}
+		mi->demodulator(sm, buf, mi->dmabuflen/2);
+		mi->modulator(sm, sm->modem.dmabufr, mi->dmabuflen/2);
+ 		setup_dma_dsp(dev, 1);
+		mi->modulator(sm, sm->modem.dmabufr + mi->dmabuflen/2, 
+			      mi->dmabuflen/2);
+		goto endint;
+ 	}
+	sm_int_freq(sm);
+	sti();
+	/*
+	 * check if transmitter active
+	 */
+	if (new_ptt)
+		mi->modulator(sm, buf, mi->dmabuflen/2);
+	else {
+		mi->demodulator(sm, buf, mi->dmabuflen/2);
+		hdlcdrv_arbitrate(dev, &sm->hdrv);
+	}
+ endint:
+	sm->modem.oldptt = new_ptt;
+	output_status(sm);
+	hdlcdrv_transmitter(dev, &sm->hdrv);
+	hdlcdrv_receiver(dev, &sm->hdrv);
+}
+
+/* --------------------------------------------------------------------- */
+
+static int sbc_open(struct device *dev) 
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+	unsigned char revreq = (mi->samplerate >= 13000) ? 3 : 2;
+	
+	if (!dev || !sm)
+		return -ENXIO;
+	if (dev->base_addr <= 0 || dev->base_addr > 0x1000-SBC_EXTENT || 
+	    dev->irq < 2 || dev->irq > 15 || dev->dma > 3)
+		return -ENXIO;
+	if (check_region(dev->base_addr, SBC_EXTENT))
+		return -EACCES;
+	/*
+	 * check if a card is available
+	 */
+	if (!reset_dsp(dev))
+		return -ENODEV;
+	write_dsp(dev, SBC_GET_REVISION);
+	if (!read_dsp(dev, &sm->modem.revhi) || 
+	    !read_dsp(dev, &sm->modem.revlo))
+		return -ENODEV;
+	if (sm->modem.revhi < revreq) {
+		printk(KERN_ERR "sm: sbc io 0x%lx: DSP rev %d.%02d too "
+		       "old, at least %d.00 required\n", dev->base_addr,
+		       sm->modem.revhi, sm->modem.revlo, revreq);
+		return -ENODEV;
+	}
+	/*
+	 * initialize some variables
+	 */
+	if (!(sm->modem.dmabufr = kmalloc(mi->dmabuflen, GFP_KERNEL | GFP_DMA)))
+		return -ENOMEM;
+	sm->modem.shreg = sm->modem.last_sample = 0;
+	sm->modem.bit_pll = sm->modem.dcd_shreg = sm->modem.dcd_sum1 = 0;
+	sm->modem.dcd_sum2 = sm->modem.last_rxbit = sm->modem.tx_bit = 0;
+	sm->modem.dmabufidx = sm->modem.oldptt = 0;
+	sm->modem.dmabufw = NULL;
+	sm->modem.dcd_time = 120;
+	sm->modem.dcd_sum0 = 2;
+#if 0
+	if (!dev->irq) {
+		int irq = probe_int(dev, sm);
+		if (irq < 0) {
+			printk(KERN_ERR "sm: irq autoprobe failed\n");
+			kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+			return -EBUSY;
+		}
+		dev->irq = irq;
+	}
+#endif
+	if (request_dma(dev->dma, mi->mode_name)) {
+		kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+		return -EBUSY;
+	}
+	if (request_irq(dev->irq, sbc_interrupt, SA_INTERRUPT, 
+			mi->mode_name, dev)) {
+		free_dma(dev->dma);
+		kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+		return -EBUSY;
+	}
+	request_region(dev->base_addr, SBC_EXTENT, mi->mode_name);
+	setup_dma_dsp(dev, 0);
+	output_open(sm);
+	printk(KERN_INFO "sm: sbc at iobase 0x%lx irq %u dma "
+	       "%u DSP revision %u.%02u\n", dev->base_addr, dev->irq, 
+	       dev->dma, (unsigned int)sm->modem.revhi, 
+	       (unsigned int)sm->modem.revlo);
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int sbc_close(struct device *dev) 
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+
+	if (!dev || !sm)
+		return -EINVAL;
+	/*
+	 * disable interrupts
+	 */
+	disable_dma(dev->dma);
+	reset_dsp(dev);	
+	free_irq(dev->irq, dev);	
+	free_dma(dev->dma);	
+	release_region(dev->base_addr, SBC_EXTENT);
+	kfree(sm->modem.dmabufr);
+	output_close(sm);
+	printk(KERN_INFO "sm: close sbc at iobase 0x%lx irq %u dma %u\n", 
+	       dev->base_addr, dev->irq, dev->dma);
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+#endif /* ENABLE_SBC */
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== Windows Sound System specific routines ==========
+ */
+
+#if defined(ENABLE_WSS) || defined(ENABLE_WSSFDX)
+
+static void write_codec(struct device *dev, unsigned char idx,
+			unsigned char data)
+{
+	int timeout = 900000;
+
+	/* wait until codec ready */
+	while (timeout > 0 && inb(WSS_CODEC_IA(dev->base_addr)) & 0x80)
+		timeout--;
+	outb(idx, WSS_CODEC_IA(dev->base_addr));
+	outb(data, WSS_CODEC_ID(dev->base_addr));
+}
+
+
+/* --------------------------------------------------------------------- */
+
+static unsigned char read_codec(struct device *dev, unsigned char idx)
+{
+	int timeout = 900000;
+
+	/* wait until codec ready */
+	while (timeout > 0 && inb(WSS_CODEC_IA(dev->base_addr)) & 0x80)
+		timeout--;
+	outb(idx & 0xf, WSS_CODEC_IA(dev->base_addr));
+	return inb(WSS_CODEC_ID(dev->base_addr));
+}
+
+/* --------------------------------------------------------------------- */
+
+static void inline wss_ack_int(struct device *dev)
+{
+	outb(0, WSS_CODEC_STATUS(dev->base_addr));
+}
+
+/* --------------------------------------------------------------------- */
+
+static int wss_init_codec(struct device *dev, unsigned char sdc, 
+			  unsigned char src_l, unsigned char src_r, 
+			  int igain_l, int igain_r,
+			  int ogain_l, int ogain_r)
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+
+	unsigned char tmp, reg0, reg1, reg6, reg7;
+	static const signed char irqtab[16] = 
+	{ -1, -1, 0x10, -1, -1, -1, -1, 0x08, -1, 0x10, 0x18, 0x20, -1, -1,
+		  -1, -1 };
+	static const signed char dmatab[4] = { 1, 2, -1, 3 };
+	unsigned long time;
+				
+	tmp = inb(WSS_STATUS(dev->base_addr));
+	if ((tmp & 0x3f) != 0x04 && (tmp & 0x3f) != 0x00 && 
+	    (tmp & 0x3f) != 0x0f) {
+		printk(KERN_ERR "sm: WSS card not found, address 0x%lx, ID "
+		       "register 0x%02x\n", dev->base_addr, (int)tmp);
+		return -1;
+	}
+	if ((tmp & 0x80) && ((dev->dma == 0) || ((dev->irq >= 8) && 
+						 (dev->irq != 9)))) {
+		printk(KERN_ERR "sm: WSS: DMA0 and/or IRQ8..IRQ15 (except "
+		       "IRQ9) cannot be used on an 8bit card\n");
+		return -1;
+	}		
+	if (dev->irq > 15 || irqtab[dev->irq] == -1) {
+		printk(KERN_ERR "sm: WSS: invalid interrupt %d\n", 
+		       (int)dev->irq);
+		return -1;
+	}
+	if (dev->dma > 3 || dmatab[dev->dma] == -1) {
+		printk(KERN_ERR "sm: WSS: invalid dma channel %d\n", 
+		       (int)dev->dma);
+		return -1;
+	}
+	tmp = irqtab[dev->irq] | dmatab[dev->dma];
+	outb((tmp & 0x38) | 0x40, WSS_CONFIG(dev->base_addr)); /* irq probe */
+	if (!(inb(WSS_STATUS(dev->base_addr)) & 0x40)) {
+		outb(0, WSS_CONFIG(dev->base_addr));
+		printk(KERN_ERR "sm: WSS: IRQ%d is not free!\n", dev->irq);
+	}
+	outb(tmp, WSS_CONFIG(dev->base_addr));
+	/*
+	 * initialize the codec
+	 */
+	if (igain_l < 0)
+		igain_l = 0;
+	if (igain_r < 0)
+		igain_r = 0;
+	if (ogain_l > 0)
+		ogain_l = 0;
+	if (ogain_r > 0)
+		ogain_r = 0;
+	reg0 = (src_l << 6) & 0xc0;
+	reg1 = (src_r << 6) & 0xc0;
+	if (reg0 == 0x80 && igain_l >= 20) {
+		reg0 |= 0x20;
+		igain_l -= 20;
+	}
+	if (reg1 == 0x80 && igain_r >= 20) {
+		reg1 |= 0x20;
+		igain_r -= 20;
+	}
+	if (igain_l > 23)
+		igain_l = 23;
+	if (igain_r > 23)
+		igain_r = 23;
+	reg0 |= igain_l * 2 / 3;
+	reg1 |= igain_r * 2 / 3;
+	reg6 = (ogain_l < -95) ? 0x80 : (ogain_l * (-2) / 3);
+	reg7 = (ogain_r < -95) ? 0x80 : (ogain_r * (-2) / 3);
+#if 1
+	write_codec(dev, 9, 0);
+	write_codec(dev, 15, 0xaa);
+	write_codec(dev, 14, 0x55);
+	if ((read_codec(dev, 15) != 0xaa) || (read_codec(dev, 14) != 0x55)) 
+		goto codec_err;
+#endif
+	write_codec(dev, 0x48, mi->data_fmt);  /* Clock and data format register */
+	write_codec(dev, 0x49, sdc ? 0xc : 0x8); /* MCE and interface config reg */
+	/* single DMA channel, disable both DMA */
+	/* clear MCE and wait until ACI set */
+	time = jiffies + HZ/4;
+	while (!(read_codec(dev, 0x0b) & 0x20) &&
+	       ((signed)(jiffies - time) < 0));
+	/* wait until ACI cleared */
+	while ((read_codec(dev, 0x0b) & 0x20) &&
+	       ((signed)(jiffies - time) < 0));
+	if ((signed)(jiffies - time) >= 0) {
+		printk(KERN_WARNING "sm: ad1848 auto calibration timed out\n");
+		goto codec_err;
+	}
+        write_codec(dev, 0, reg0); /* left input control */
+        write_codec(dev, 1, reg1); /* right input control */
+        write_codec(dev, 2, 0x80); /* left aux#1 input control */
+        write_codec(dev, 3, 0x80); /* right aux#1 input control */
+        write_codec(dev, 4, 0x80); /* left aux#2 input control */
+        write_codec(dev, 5, 0x80); /* right aux#2 input control */
+        write_codec(dev, 6, reg6); /* left dac control */
+        write_codec(dev, 7, reg7); /* right dac control */
+        write_codec(dev, 0xa, 0x2); /* pin control register */
+        write_codec(dev, 0xd, 0x0); /* digital mix control */
+	sm->modem.revhi = inb(WSS_STATUS(dev->base_addr)) & 0x3f;
+	sm->modem.revlo = read_codec(dev, 0xc) & 0xf;
+	/*
+	 * print revisions
+	 */
+	printk(KERN_INFO "sm: WSS revision %d, CODEC revision %d\n", 
+	       (int)sm->modem.revhi, (int)sm->modem.revlo);
+	return 0;
+ codec_err:
+	outb(0, WSS_CONFIG(dev->base_addr));
+	printk(KERN_ERR "sm: no WSS soundcard found at address 0x%lx\n",
+	       dev->base_addr);
+	return -1;
+}
+
+#endif /* defined(ENABLE_WSS) || defined(ENABLE_WSSFDX) */
+
+/* --------------------------------------------------------------------- */
+
+#ifdef ENABLE_WSS
+
+static void setup_dma_wss(struct device *dev, int flg)
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+        unsigned long flags;
+        static const unsigned char codecmode[2] = { 0x0e, 0x0d };
+        static const unsigned char dmamode[2] = 
+        { DMA_MODE_READ | DMA_MODE_AUTOINIT, 
+		  DMA_MODE_WRITE  | DMA_MODE_AUTOINIT};
+	unsigned char oldcodecmode, codecdma;
+	long abrt;
+	unsigned long dmabufaddr = virt_to_bus(sm->modem.dmabufr);
+
+        if ((dmabufaddr & 0xffff) + mi->dmabuflen > 0x10000)
+                panic("sm: DMA buffer violates DMA boundary!");
+	flg = !!flg;
+        save_flags(flags);
+        cli();
+	/*
+	 * perform the final DMA sequence to disable the codec request
+	 */
+	oldcodecmode = read_codec(dev, 9);
+        write_codec(dev, 9, 0xc); /* disable codec */
+	wss_ack_int(dev);
+	if ((codecdma = read_codec(dev, 11)) & 0x10) {
+		disable_dma(dev->dma);
+		clear_dma_ff(dev->dma);
+		set_dma_mode(dev->dma, dmamode[oldcodecmode & 1]);
+		set_dma_addr(dev->dma, dmabufaddr);
+		set_dma_count(dev->dma, mi->dmabuflen);
+		enable_dma(dev->dma);
+		abrt = 0;
+		while (((codecdma = read_codec(dev, 11)) & 0x10) ||
+		       ((++abrt) >= 0x10000));
+	}
+        disable_dma(dev->dma);
+        clear_dma_ff(dev->dma);
+        set_dma_mode(dev->dma, dmamode[flg]);
+        set_dma_addr(dev->dma, dmabufaddr);
+        set_dma_count(dev->dma, mi->dmabuflen);
+        enable_dma(dev->dma);
+	write_codec(dev, 15, ((mi->dmabuflen >> 1) - 1) & 0xff);
+	write_codec(dev, 14, ((mi->dmabuflen >> 1) - 1) >> 8);
+	write_codec(dev, 9, codecmode[flg]);
+        restore_flags(flags);
+}
+
+/* --------------------------------------------------------------------- */
+
+static void wss_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct device *dev = (struct device *)dev_id;
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+	unsigned char new_ptt;
+	unsigned char *buf;
+	unsigned long flags;
+	int dmares;
+
+	if (!dev || !sm || sm->hdrv.magic != HDLCDRV_MAGIC)
+		return;
+	new_ptt = hdlcdrv_ptt(&sm->hdrv);
+	save_flags(flags);
+	cli();
+	wss_ack_int(dev);
+	disable_dma(dev->dma);
+	clear_dma_ff(dev->dma);
+	dmares = get_dma_residue(dev->dma);
+	enable_dma(dev->dma);
+	if (dmares <= 0)
+		dmares = mi->dmabuflen;
+	buf = sm->modem.dmabufr;
+	if (dmares > mi->dmabuflen/2)
+		buf += mi->dmabuflen/2;
+	if (dmares > mi->dmabuflen/2)
+		dmares -= mi->dmabuflen/2;
+#ifdef SM_DEBUG
+	if (!sm->debug_vals.last_pllcorr || 
+	    dmares < sm->debug_vals.last_pllcorr)
+		sm->debug_vals.last_pllcorr = dmares;
+#endif /* SM_DEBUG */
+	dmares--;
+	write_codec(dev, 15, dmares & 0xff);
+	write_codec(dev, 14, dmares >> 8);
+	restore_flags(flags);
+	if (sm->modem.oldptt != new_ptt) {
+		disable_dma(dev->dma);
+		sti();
+ 		sm->modem.dmabufidx = 0;
+		if (!new_ptt) {
+			setup_dma_wss(dev, 0);
+			goto endint;
+		}
+		mi->demodulator(sm, buf, mi->dmabuflen/2);
+		mi->modulator(sm, sm->modem.dmabufr, mi->dmabuflen/2);
+ 		setup_dma_wss(dev, 1);
+		mi->modulator(sm, sm->modem.dmabufr + mi->dmabuflen/2,
+			      mi->dmabuflen/2);
+		goto endint;
+ 	}
+	sm_int_freq(sm);
+	sti();
+	/*
+	 * check if transmitter active
+	 */
+	if (new_ptt)
+		mi->modulator(sm, buf, mi->dmabuflen/2);
+	else {
+		mi->demodulator(sm, buf, mi->dmabuflen/2);
+		hdlcdrv_arbitrate(dev, &sm->hdrv);
+	}
+ endint:
+	sm->modem.oldptt = new_ptt;
+	output_status(sm);
+	hdlcdrv_transmitter(dev, &sm->hdrv);
+	hdlcdrv_receiver(dev, &sm->hdrv);
+}
+
+/* --------------------------------------------------------------------- */
+
+static int wss_open(struct device *dev) 
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+
+	if (!dev || !sm)
+		return -ENXIO;
+	if (dev->base_addr <= 0 || dev->base_addr > 0x1000-WSS_EXTENT || 
+	    dev->irq < 2 || dev->irq > 15 || dev->dma > 3)
+		return -ENXIO;
+	if (check_region(dev->base_addr, WSS_EXTENT))
+		return -EACCES;
+	/*
+	 * check if a card is available
+	 */
+	if (wss_init_codec(dev, 1, 1, 1, 0, 0, -45, -45))
+		return -ENODEV;
+	/*
+	 * initialize some variables
+	 */
+	if (!(sm->modem.dmabufr = kmalloc(mi->dmabuflen, GFP_KERNEL | GFP_DMA)))
+		return -ENOMEM;
+	sm->modem.shreg = sm->modem.last_sample = 0;
+	sm->modem.bit_pll = sm->modem.dcd_shreg = sm->modem.dcd_sum1 = 0;
+	sm->modem.dcd_sum2 = sm->modem.last_rxbit = sm->modem.tx_bit = 0;
+	sm->modem.dmabufidx = sm->modem.oldptt = 0;
+	sm->modem.dmabufw = NULL;
+	sm->modem.dcd_time = 120;
+	sm->modem.dcd_sum0 = 2;
+	if (request_dma(dev->dma, mi->mode_name)) {
+		kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+		return -EBUSY;
+	}
+	if (request_irq(dev->irq, wss_interrupt, SA_INTERRUPT, 
+			mi->mode_name, dev)) {
+		free_dma(dev->dma);
+		kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+		return -EBUSY;
+	}
+	request_region(dev->base_addr, WSS_EXTENT, mi->mode_name);
+	setup_dma_wss(dev, 0);
+	output_open(sm);
+	printk(KERN_INFO "sm: wss at iobase 0x%lx irq %u dma "
+	       "%u\n", dev->base_addr, dev->irq, dev->dma);
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int wss_close(struct device *dev) 
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+
+	if (!dev || !sm)
+		return -EINVAL;
+	/*
+	 * disable interrupts
+	 */
+	disable_dma(dev->dma);
+        write_codec(dev, 9, 0xc); /* disable codec */
+	free_irq(dev->irq, dev);	
+	free_dma(dev->dma);	
+	release_region(dev->base_addr, WSS_EXTENT);
+	kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+	output_close(sm);
+	printk(KERN_INFO "sm: close wss at iobase 0x%lx irq %u"
+	       " dma %u\n", dev->base_addr, dev->irq, dev->dma);
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+#endif /* ENABLE_WSS */
+
+/* --------------------------------------------------------------------- */
+/*
+ * =========== Windows Sound System Fullduplex specific routines ==========
+ */
+
+/*
+ * This does _not_ work on my hardware
+ */
+
+#ifdef ENABLE_WSSFDX
+
+static void setup_dma_wssfdx(struct device *dev)
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+        unsigned long flags;
+	unsigned char codecdma;
+	long abrt;
+	unsigned long dmabufraddr = virt_to_bus(sm->modem.dmabufr);
+	unsigned long dmabufwaddr = virt_to_bus(sm->modem.dmabufw);
+
+        if (((dmabufraddr & 0xffff) + mi->dmabuflen > 0x10000) ||
+	    ((dmabufwaddr & 0xffff) + mi->dmabuflen > 0x10000))
+                panic("sm: DMA buffer violates DMA boundary!");
+        save_flags(flags);
+        cli();
+	/*
+	 * perform the final DMA sequence to disable the codec request
+	 */
+        write_codec(dev, 9, 0x8); /* disable codec */
+	wss_ack_int(dev);
+	if ((codecdma = read_codec(dev, 11)) & 0x10) {
+		disable_dma(dev->dma);
+		disable_dma(!dev->dma);
+		clear_dma_ff(dev->dma);
+		set_dma_mode(dev->dma, DMA_MODE_WRITE | DMA_MODE_AUTOINIT);
+		set_dma_addr(dev->dma, dmabufwaddr);
+		set_dma_count(dev->dma, mi->dmabuflen);
+		set_dma_mode(!dev->dma,  DMA_MODE_READ | DMA_MODE_AUTOINIT);
+		set_dma_addr(!dev->dma, dmabufraddr);
+		set_dma_count(!dev->dma, mi->dmabuflen);
+		enable_dma(dev->dma);
+		enable_dma(!dev->dma);
+		abrt = 0;
+		while (((codecdma = read_codec(dev, 11)) & 0x10) ||
+		       ((++abrt) >= 0x10000));
+	}
+	disable_dma(dev->dma);
+	disable_dma(!dev->dma);
+	clear_dma_ff(dev->dma);
+	set_dma_mode(dev->dma, DMA_MODE_WRITE | DMA_MODE_AUTOINIT);
+	set_dma_addr(dev->dma, dmabufwaddr);
+	set_dma_count(dev->dma, mi->dmabuflen);
+	set_dma_mode(!dev->dma,  DMA_MODE_READ | DMA_MODE_AUTOINIT);
+	set_dma_addr(!dev->dma, dmabufraddr);
+	set_dma_count(!dev->dma, mi->dmabuflen);
+	enable_dma(dev->dma);
+	enable_dma(!dev->dma);
+	write_codec(dev, 15, ((mi->dmabuflen >> 1) - 1) & 0xff);
+	write_codec(dev, 14, ((mi->dmabuflen >> 1) - 1) >> 8);
+	write_codec(dev, 9, 0x0b);
+        restore_flags(flags);
+}
+
+/* --------------------------------------------------------------------- */
+
+static int msgcnt = 10;
+
+static void wssfdx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct device *dev = (struct device *)dev_id;
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+	unsigned char new_ptt;
+	unsigned char *bufr;
+	unsigned char *bufw;
+	unsigned long flags;
+	int dmares, dmares2, i;
+
+	if (!dev || !sm || sm->hdrv.magic != HDLCDRV_MAGIC)
+		return;
+	new_ptt = hdlcdrv_ptt(&sm->hdrv);
+	save_flags(flags);
+	cli();
+	wss_ack_int(dev);
+	disable_dma(dev->dma);
+	disable_dma(!dev->dma);
+	clear_dma_ff(dev->dma);
+	dmares = get_dma_residue(dev->dma);
+	dmares2 = get_dma_residue(!dev->dma);
+	enable_dma(dev->dma);
+	enable_dma(!dev->dma);
+	if (dmares <= 0)
+		dmares = mi->dmabuflen;
+	if (dmares2 <= 0)
+		dmares2 = mi->dmabuflen;
+	bufw = sm->modem.dmabufw;
+	if (dmares > mi->dmabuflen/2)
+		bufw += mi->dmabuflen/2;
+	bufr = sm->modem.dmabufr;
+	if (dmares2 > mi->dmabuflen/2)
+		bufr += mi->dmabuflen/2;
+	if ((i = dmares) > mi->dmabuflen/2)
+		i -= mi->dmabuflen/2;
+#ifdef SM_DEBUG
+	if (!sm->debug_vals.last_pllcorr || 
+	    i < sm->debug_vals.last_pllcorr)
+		sm->debug_vals.last_pllcorr = i;
+#endif /* SM_DEBUG */
+	i--;
+	write_codec(dev, 15, i & 0xff);
+	write_codec(dev, 14, i >> 8);
+	restore_flags(flags);
+	sm_int_freq(sm);
+	sti();
+
+	if (msgcnt > 0) {
+		msgcnt--;
+		printk(KERN_DEBUG "sm: DMA residue: playback: %d "
+		       "capture: %d\n", dmares, dmares2);
+	}
+
+	/*
+	 * check if transmitter active
+	 */
+	if (new_ptt)
+		mi->modulator(sm, bufw, mi->dmabuflen/2);
+	else 
+		memset(bufw, 0x80, mi->dmabuflen/2);
+	mi->demodulator(sm, bufr, mi->dmabuflen/2);
+	hdlcdrv_arbitrate(dev, &sm->hdrv);
+	sm->modem.oldptt = new_ptt;
+	output_status(sm);
+	hdlcdrv_transmitter(dev, &sm->hdrv);
+	hdlcdrv_receiver(dev, &sm->hdrv);
+}
+
+/* --------------------------------------------------------------------- */
+
+static int wssfdx_open(struct device *dev) 
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+	struct modem_info *mi = (struct modem_info *)sm->hdrv.ops;
+
+	if (!dev || !sm)
+		return -ENXIO;
+	if (dev->base_addr <= 0 || dev->base_addr > 0x1000-WSS_EXTENT || 
+	    dev->irq < 2 || dev->irq > 15 || dev->dma > 3)
+		return -ENXIO;
+	if (check_region(dev->base_addr, WSS_EXTENT))
+		return -EACCES;
+	/*
+	 * check if a card is available
+	 */
+	if (wss_init_codec(dev, 0, 1, 1, 0, 0, -45, -45))
+		return -ENODEV;
+	/*
+	 * initialize some variables
+	 */
+	if (!(sm->modem.dmabufr = kmalloc(mi->dmabuflen, GFP_KERNEL | GFP_DMA)))
+		return -ENOMEM;
+	if (!(sm->modem.dmabufw = kmalloc(mi->dmabuflen, GFP_KERNEL | GFP_DMA))) {
+		kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+		return -ENOMEM;
+	}
+	sm->modem.shreg = sm->modem.last_sample = 0;
+	sm->modem.bit_pll = sm->modem.dcd_shreg = sm->modem.dcd_sum1 = 0;
+	sm->modem.dcd_sum2 = sm->modem.last_rxbit = sm->modem.tx_bit = 0;
+	sm->modem.dmabufidx = sm->modem.oldptt = 0;
+	sm->modem.dcd_time = 120;
+	sm->modem.dcd_sum0 = 2;
+	if (request_dma(dev->dma, mi->mode_name)) {
+		kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+		kfree_s(sm->modem.dmabufw, mi->dmabuflen);
+		return -EBUSY;
+	}
+	if (request_dma(!dev->dma, mi->mode_name)) {
+		free_dma(dev->dma);
+		kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+		kfree_s(sm->modem.dmabufw, mi->dmabuflen);
+		return -EBUSY;
+	}
+	if (request_irq(dev->irq, wssfdx_interrupt, SA_INTERRUPT, 
+			mi->mode_name, dev)) {
+		free_dma(dev->dma);
+		free_dma(!dev->dma);
+		kfree_s(sm->modem.dmabufr, mi->dmabuflen);
+		kfree_s(sm->modem.dmabufw, mi->dmabuflen);
+		return -EBUSY;
+	}
+	request_region(dev->base_addr, WSS_EXTENT, mi->mode_name);
+	setup_dma_wssfdx(dev);
+	output_open(sm);
+	printk(KERN_INFO "sm: wss fdx at iobase 0x%lx irq %u dma1 "
+	       "%u dma2 %u\n", dev->base_addr, dev->irq, dev->dma, !dev->dma);
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int wssfdx_close(struct device *dev) 
+{
+	struct sm_state *sm = (struct sm_state *)dev->priv;
+
+	if (!dev || !sm)
+		return -EINVAL;
+	/*
+	 * disable interrupts
+	 */
+	disable_dma(dev->dma);
+	disable_dma(!dev->dma);
+        write_codec(dev, 9, 0x8); /* disable codec */
+	free_irq(dev->irq, dev);	
+	free_dma(dev->dma);	
+	free_dma(!dev->dma);	
+	release_region(dev->base_addr, WSS_EXTENT);
+	kfree(sm->modem.dmabufr);
+	kfree(sm->modem.dmabufw);
+	output_close(sm);
+	printk(KERN_INFO "sm: close wss fdx at iobase 0x%lx irq %u"
+	       " dma %u\n", dev->base_addr, dev->irq, dev->dma);
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+#endif /* ENABLE_WSSFDX */
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== hdlcdrv driver interface =========================
+ */
+
+static int sm_ioctl(struct device *dev, struct ifreq *ifr, int cmd);
+
+#define SBC1200_SRATE (256-104) /* the SBC sampling rate, 256-(1E6/srate) */
+#define SBC1200_DMABUFLEN 192   /* DMA buffer duration exactly 20ms */
+
+#define SBC9600_SRATE (256-26) /* the SBC sampling rate, 256-(1E6/srate) */
+#define SBC9600_DMABUFLEN 768    /* DMA buffer duration exactly 20ms */
+
+#define WSS1200_DATAFMT 0x0e   /* 8bit unsigned PCM, Mono, XTAL1, 9.6kHz */
+#define WSS1200_DMABUFLEN 192        /* DMA buffer duration exactly 20ms */
+
+#define WSS9600_DATAFMT 0x0c    /* 8bit unsigned PCM, Mono, XTAL1, 48kHz */
+#define WSS9600_DMABUFLEN 960        /* DMA buffer duration exactly 20ms */
+
+/* --------------------------------------------------------------------- */
+
+#ifdef ENABLE_SBC
+
+static const struct modem_info sbc1200_ops = { 
+{ 1200, sbc_open, sbc_close, sm_ioctl }, 9600, 1, 8, "sbc_1200",
+SBC1200_SRATE, SBC1200_DMABUFLEN,
+modulator_1200, demodulator_1200
+};
+/* --------------------------------------------------------------------- */
+
+static const struct modem_info sbc9600_ops = {
+{ 9600, sbc_open, sbc_close, sm_ioctl }, 38400, 1, 4, "sbc_9600",
+SBC9600_SRATE, SBC9600_DMABUFLEN,
+modulator_9600_4, demodulator_9600_4
+};
+
+#endif /* ENABLE_SBC */
+
+/* --------------------------------------------------------------------- */
+
+#ifdef ENABLE_WSS
+
+static const struct modem_info wss1200_ops = {
+{ 1200, wss_open, wss_close, sm_ioctl }, 9600, 0, 8, "wss_1200",
+WSS1200_DATAFMT, WSS1200_DMABUFLEN,
+modulator_1200, demodulator_1200
+};
+
+/* --------------------------------------------------------------------- */
+
+static const struct modem_info wss9600_ops = {
+{ 9600, wss_open, wss_close, sm_ioctl }, 48000, 0, 5, "wss_9600",
+WSS9600_DATAFMT, WSS9600_DMABUFLEN,
+modulator_9600_5, demodulator_9600_5
+};
+
+#endif /* ENABLE_WSS */
+
+/* --------------------------------------------------------------------- */
+
+#ifdef ENABLE_WSSFDX
+
+static const struct modem_info wss1200fdx_ops = {
+{ 1200, wssfdx_open, wssfdx_close, sm_ioctl }, 9600, 0, 8, "wss_fdx_1200",
+WSS1200_DATAFMT, WSS1200_DMABUFLEN,
+modulator_1200, demodulator_1200
+};
+
+/* --------------------------------------------------------------------- */
+
+static const struct modem_info wss9600fdx_ops = {
+{ 9600, wssfdx_open, wssfdx_close, sm_ioctl }, 48000, 0, 5, "wss_fdx_9600",
+WSS9600_DATAFMT, WSS9600_DMABUFLEN,
+modulator_9600_5, demodulator_9600_5
+};
+
+#endif /* ENABLE_WSSFDX */
+
+/* --------------------------------------------------------------------- */
+
+static const struct modem_info dummy_ops = {
+{ 0, NULL, NULL, sm_ioctl }, 0, 0, 0, "none",
+0, 0,
+NULL, NULL
+};
+
+/* --------------------------------------------------------------------- */
+
+static const struct modem_info *ops_tab[3][2] = {
+#ifdef ENABLE_SBC
+{ &sbc1200_ops, &sbc9600_ops }, 
+#else /* ENABLE_SBC */
+{ &dummy_ops, &dummy_ops }, 
+#endif /* ENABLE_SBC */
+#ifdef ENABLE_WSS
+{ &wss1200_ops, &wss9600_ops },
+#else /* ENABLE_WSS */
+{ &dummy_ops, &dummy_ops }, 
+#endif /* ENABLE_WSS */
+#ifdef ENABLE_WSSFDX
+{ &wss1200fdx_ops, &wss9600fdx_ops }
+#else /* ENABLE_WSSFDX */
+{ &dummy_ops, &dummy_ops }
+#endif /* ENABLE_WSSFDX */
+};
+
+/* --------------------------------------------------------------------- */
+
+static int sm_ioctl(struct device *dev, struct ifreq *ifr, int cmd)
+{
+	int i;
+	struct sm_state *sm;
+	struct sm_ioctl bi;
+	unsigned long flags;
+	unsigned int newdiagmode;
+	unsigned int newdiagflags;
+	
+	if (!dev || !dev->priv || 
+	    ((struct sm_state *)dev->priv)->hdrv.magic != HDLCDRV_MAGIC) {
+		printk(KERN_ERR "sm_ioctl: invalid device struct\n");
+		return -EINVAL;
+	}
+	sm = (struct sm_state *)dev->priv;
+
+	if (cmd != SIOCDEVPRIVATE)
+		return -ENOIOCTLCMD;
+	if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi)))
+		return -EFAULT;
+
+	switch (bi.cmd) {
+	default:
+		return -ENOIOCTLCMD;
+
+	case SMCTL_GETMODEMTYPE:
+		bi.data.cfg.hardware = sm->config.hardware;
+		bi.data.cfg.mode = sm->config.mode;
+		break;
+
+	case SMCTL_SETMODEMTYPE:
+		if (!suser() || dev->start)
+			return -EACCES;
+		if (bi.data.cfg.hardware < SM_HARDWARE_INVALID ||
+		    bi.data.cfg.hardware > SM_HARDWARE_WSSFDX ||
+		    bi.data.cfg.mode < SM_MODE_INVALID ||
+		    bi.data.cfg.mode > SM_MODE_FSK9600)
+			return -EINVAL;
+		sm->config.hardware = bi.data.cfg.hardware;
+		sm->config.mode = bi.data.cfg.mode;
+		if (bi.data.cfg.hardware == SM_HARDWARE_INVALID ||
+		    bi.data.cfg.mode == SM_MODE_INVALID)
+			sm->hdrv.ops = &dummy_ops.hops;
+		else {
+			sm->hdrv.ops = &ops_tab[sm->config.hardware][sm->config.mode]->hops;
+			if (!((struct modem_info *)sm->hdrv.ops)->samplerate)
+				return -ENODEV;
+		}
+		return 0;
+
+#ifdef SM_DEBUG
+	case SMCTL_GETDEBUG:
+		bi.data.dbg.debug1 = sm->hdrv.ptt_keyed;
+		bi.data.dbg.debug2 = sm->debug_vals.last_intcnt;
+		bi.data.dbg.debug3 = sm->debug_vals.last_pllcorr;
+		break;
+#endif /* SM_DEBUG */
+
+	case SMCTL_GETMIXER:
+		i = verify_area(VERIFY_WRITE, ifr->ifr_data, sizeof(bi));
+		if (i)
+			return i;
+		i = 0;
+		bi.data.mix.sample_rate = ((struct modem_info *)sm->hdrv.ops)->samplerate;
+		bi.data.mix.bit_rate = sm->hdrv.ops->bitrate;
+		if (((struct modem_info *)sm->hdrv.ops)->sbcmix) {
+			switch (sm->modem.revhi) {
+			case 2:
+				bi.data.mix.mixer_type = SM_MIXER_CT1335;
+				break;
+			case 3:
+				bi.data.mix.mixer_type = SM_MIXER_CT1345;
+				break;
+			case 4:
+				bi.data.mix.mixer_type = SM_MIXER_CT1745;
+				break;
+			}
+			if (bi.data.mix.mixer_type != SM_MIXER_INVALID &&
+			    bi.data.mix.reg < 0x80) {
+				save_flags(flags);
+				cli();
+				outb(bi.data.mix.reg, DSP_MIXER_ADDR(dev->base_addr));
+				bi.data.mix.data = inb(DSP_MIXER_DATA(dev->base_addr));
+				restore_flags(flags);
+				i = 1;
+			}
+		} else {
+			bi.data.mix.mixer_type = SM_MIXER_AD1848;
+			if ((0x20ff >> bi.data.mix.reg) & 1) {
+				bi.data.mix.data = read_codec(dev, bi.data.mix.reg);
+				i = 1;
+			}
+		}
+		if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi)))
+			return -EFAULT;
+		return i;
+
+	case SMCTL_SETMIXER:
+		if (!suser())
+			return -EACCES;
+		if (((struct modem_info *)sm->hdrv.ops)->sbcmix) {
+			switch (sm->modem.revhi) {
+			case 2:
+				if (bi.data.mix.mixer_type != SM_MIXER_CT1335)
+					return -EINVAL;
+				break;
+			case 3:
+				if (bi.data.mix.mixer_type != SM_MIXER_CT1345)
+					return -EINVAL;
+				break;
+			case 4:
+				if (bi.data.mix.mixer_type != SM_MIXER_CT1745)
+					return -EINVAL;
+				break;
+			default:
+				return -ENODEV;
+			}
+			if (bi.data.mix.reg >= 0x80)
+				return -EACCES;
+			save_flags(flags);
+			cli();
+			outb(bi.data.mix.reg, DSP_MIXER_ADDR(dev->base_addr));
+			outb(bi.data.mix.data, DSP_MIXER_DATA(dev->base_addr));
+			restore_flags(flags);
+			return 0;
+		} else {
+			if (bi.data.mix.mixer_type != SM_MIXER_AD1848)
+				return -EINVAL;
+			if (!((0x20ff >> bi.data.mix.reg) & 1))
+				return -EACCES;
+			write_codec(dev, bi.data.mix.reg, bi.data.mix.data);
+			return 0;
+		}
+
+	case SMCTL_DIAGNOSE:
+		newdiagmode = bi.data.diag.mode;
+		newdiagflags = bi.data.diag.flags;
+		if (newdiagmode > SM_DIAGMODE_DEMOD)
+			return -EINVAL;
+		bi.data.diag.mode = sm->diag.mode;
+		bi.data.diag.flags = sm->diag.flags;
+		bi.data.diag.samplesperbit = ((struct modem_info *)sm->hdrv.ops)->sperbit;
+		if (sm->diag.mode != newdiagmode) {
+			save_flags(flags);
+			cli();
+			sm->diag.ptr = -1;
+			sm->diag.flags = newdiagflags & ~SM_DIAGFLAG_VALID;
+			sm->diag.mode = newdiagmode;
+			restore_flags(flags);
+			break;
+		}
+		if (sm->diag.ptr < 0 || sm->diag.mode == SM_DIAGMODE_OFF)
+			break;
+		if (bi.data.diag.datalen > DIAGDATALEN)
+			bi.data.diag.datalen = DIAGDATALEN;
+		if (sm->diag.ptr < bi.data.diag.datalen)
+			break;
+		i = verify_area(VERIFY_WRITE, bi.data.diag.data,
+				bi.data.diag.datalen * 
+				sizeof(short));
+		if (i)
+			return i;
+		if (copy_to_user(bi.data.diag.data, sm->diag.data, 
+				 bi.data.diag.datalen * sizeof(short)))
+			return -EFAULT;
+		bi.data.diag.flags |= SM_DIAGFLAG_VALID;
+		save_flags(flags);
+		cli();
+		sm->diag.ptr = -1;
+		sm->diag.flags = newdiagflags & ~SM_DIAGFLAG_VALID;
+		sm->diag.mode = newdiagmode;
+		restore_flags(flags);
+		break;
+
+	}
+	if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi)))
+		return -EFAULT;
+	return 0;
+
+}
+
+/* --------------------------------------------------------------------- */
+
+#ifdef MODULE
+static
+#endif /* MODULE */
+int sm_init(void)
+{
+	int i, j, found = 0;
+	char set_hw = 1;
+	struct sm_state *sm;
+	char ifname[HDLCDRV_IFNAMELEN];
+	
+	printk(KERN_INFO "sm: compiled %s %s\n", __TIME__, __DATE__);
+	/*
+	 * register net devices
+	 */
+	for (i = 0; i < NR_PORTS; i++) {
+		struct device *dev = sm_device+i;
+		sprintf(ifname, "sm%d", i);
+
+		if (sm_ports[i].hardware < SM_HARDWARE_INVALID ||
+		    sm_ports[i].hardware > SM_HARDWARE_WSSFDX ||
+		    sm_ports[i].mode < SM_MODE_INVALID ||
+		    sm_ports[i].mode > SM_MODE_FSK9600)
+			set_hw = 0;
+		if (set_hw) {
+			j = hdlcdrv_register_hdlcdrv(dev, &ops_tab[sm_ports[i].hardware]
+						     [sm_ports[i].mode]->hops, 
+						     sizeof(struct sm_state), ifname, 
+						     sm_ports[i].iobase,
+						     sm_ports[i].irq,
+						     sm_ports[i].dma);
+			if (!j) {
+				sm = (struct sm_state *)dev->priv;
+				sm->hdrv.ptt_out.seriobase = sm_ports[i].seriobase;
+				sm->hdrv.ptt_out.pariobase = sm_ports[i].pariobase;
+				sm->hdrv.ptt_out.midiiobase = sm_ports[i].midiiobase;
+			}
+		} else 
+			j = hdlcdrv_register_hdlcdrv(dev, &dummy_ops.hops, 
+						     sizeof(struct sm_state), 
+						     ifname, 0, 0, 0);
+		if (j) {
+			printk(KERN_WARNING "sm: cannot register net "
+			       "device\n");
+		} else
+			found++;
+	}
+	if (!found)
+		return -ENXIO;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+#ifdef MODULE
+
+/*
+ * command line settable parameters
+ */
+int hardware = SM_HARDWARE_INVALID;
+int mode = SM_MODE_INVALID;
+int iobase = -1;
+int irq = -1;
+int dma = -1;
+int seriobase = 0;
+int pariobase = 0;
+int midiiobase = 0;
+
+int init_module(void)
+{
+	printk(KERN_INFO "sm: v0.1 (C) 1996 Thomas Sailer HB9JNX/AE4WA\n");
+
+	if (hardware != SM_HARDWARE_INVALID) {
+		if (iobase == -1)
+			iobase = (hardware == SM_HARDWARE_SBC) ? 0x220 : 0x530;
+		if (irq == -1)
+			irq = (hardware == SM_HARDWARE_SBC) ? 5 : 11;
+		if (dma == -1)
+			dma = 1;
+	}
+	sm_ports[0].hardware = hardware;
+	sm_ports[0].mode = mode;
+	sm_ports[0].iobase = iobase;
+	sm_ports[0].irq = irq;
+	sm_ports[0].dma = dma;
+	sm_ports[0].seriobase = seriobase;
+	sm_ports[0].pariobase = pariobase;
+	sm_ports[0].midiiobase = midiiobase;
+	sm_ports[1].hardware = SM_HARDWARE_INVALID;
+
+	return sm_init();
+}
+
+/* --------------------------------------------------------------------- */
+
+void cleanup_module(void)
+{
+	int i;
+
+	printk(KERN_INFO "sm: cleanup_module called\n");
+
+	for(i = 0; i < NR_PORTS; i++) {
+		struct device *dev = sm_device+i;
+		struct sm_state *sm = (struct sm_state *)dev->priv;
+
+		if (sm) {
+			if (sm->hdrv.magic != HDLCDRV_MAGIC)
+				printk(KERN_ERR "sm: invalid magic in "
+				       "cleanup_module\n");
+			else 
+				hdlcdrv_unregister_hdlcdrv(dev);
+		}
+	}
+}
+
+#else /* MODULE */
+/* --------------------------------------------------------------------- */
+/*
+ * format: sm=hw,mode,io,irq,dma,serio,pario[,hw,mode,io,irq,dma,serio,pario]
+ * hw=0: SBC, hw=1: WSS; mode=0: AFSK1200, mode=1: FSK9600
+ */
+
+void sm_setup(char *str, int *ints)
+{
+	int i;
+
+	for (i = 0; i < NR_PORTS; i++) 
+		if (ints[0] >= 8*(i+1)) {
+			sm_ports[i].hardware = ints[8*i+1];
+			sm_ports[i].mode = ints[8*i+2];
+			sm_ports[i].iobase = ints[8*i+3];
+			sm_ports[i].irq = ints[8*i+4];
+			sm_ports[i].dma = ints[8*i+5];
+			sm_ports[i].seriobase = ints[8*i+6];
+			sm_ports[i].pariobase = ints[8*i+7];
+			sm_ports[i].midiiobase = ints[8*i+8];
+		} else
+			sm_ports[i].hardware = SM_HARDWARE_INVALID;
+
+}
+
+#endif /* MODULE */
+/* --------------------------------------------------------------------- */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov