v4.19.13 snapshot.
diff --git a/drivers/net/hamradio/6pack.c b/drivers/net/hamradio/6pack.c
new file mode 100644
index 0000000..d79a69d
--- /dev/null
+++ b/drivers/net/hamradio/6pack.c
@@ -0,0 +1,1006 @@
+/*
+ * 6pack.c	This module implements the 6pack protocol for kernel-based
+ *		devices like TTY. It interfaces between a raw TTY and the
+ *		kernel's AX.25 protocol layers.
+ *
+ * Authors:	Andreas Könsgen <ajk@comnets.uni-bremen.de>
+ *              Ralf Baechle DL5RB <ralf@linux-mips.org>
+ *
+ * Quite a lot of stuff "stolen" by Joerg Reuter from slip.c, written by
+ *
+ *		Laurence Culhane, <loz@holmes.demon.co.uk>
+ *		Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
+ */
+
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+#include <net/ax25.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/spinlock.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/semaphore.h>
+#include <linux/compat.h>
+#include <linux/refcount.h>
+
+#define SIXPACK_VERSION    "Revision: 0.3.0"
+
+/* sixpack priority commands */
+#define SIXP_SEOF		0x40	/* start and end of a 6pack frame */
+#define SIXP_TX_URUN		0x48	/* transmit overrun */
+#define SIXP_RX_ORUN		0x50	/* receive overrun */
+#define SIXP_RX_BUF_OVL		0x58	/* receive buffer overflow */
+
+#define SIXP_CHKSUM		0xFF	/* valid checksum of a 6pack frame */
+
+/* masks to get certain bits out of the status bytes sent by the TNC */
+
+#define SIXP_CMD_MASK		0xC0
+#define SIXP_CHN_MASK		0x07
+#define SIXP_PRIO_CMD_MASK	0x80
+#define SIXP_STD_CMD_MASK	0x40
+#define SIXP_PRIO_DATA_MASK	0x38
+#define SIXP_TX_MASK		0x20
+#define SIXP_RX_MASK		0x10
+#define SIXP_RX_DCD_MASK	0x18
+#define SIXP_LEDS_ON		0x78
+#define SIXP_LEDS_OFF		0x60
+#define SIXP_CON		0x08
+#define SIXP_STA		0x10
+
+#define SIXP_FOUND_TNC		0xe9
+#define SIXP_CON_ON		0x68
+#define SIXP_DCD_MASK		0x08
+#define SIXP_DAMA_OFF		0
+
+/* default level 2 parameters */
+#define SIXP_TXDELAY			(HZ/4)	/* in 1 s */
+#define SIXP_PERSIST			50	/* in 256ths */
+#define SIXP_SLOTTIME			(HZ/10)	/* in 1 s */
+#define SIXP_INIT_RESYNC_TIMEOUT	(3*HZ/2) /* in 1 s */
+#define SIXP_RESYNC_TIMEOUT		5*HZ	/* in 1 s */
+
+/* 6pack configuration. */
+#define SIXP_NRUNIT			31      /* MAX number of 6pack channels */
+#define SIXP_MTU			256	/* Default MTU */
+
+enum sixpack_flags {
+	SIXPF_ERROR,	/* Parity, etc. error	*/
+};
+
+struct sixpack {
+	/* Various fields. */
+	struct tty_struct	*tty;		/* ptr to TTY structure	*/
+	struct net_device	*dev;		/* easy for intr handling  */
+
+	/* These are pointers to the malloc()ed frame buffers. */
+	unsigned char		*rbuff;		/* receiver buffer	*/
+	int			rcount;         /* received chars counter  */
+	unsigned char		*xbuff;		/* transmitter buffer	*/
+	unsigned char		*xhead;         /* next byte to XMIT */
+	int			xleft;          /* bytes left in XMIT queue  */
+
+	unsigned char		raw_buf[4];
+	unsigned char		cooked_buf[400];
+
+	unsigned int		rx_count;
+	unsigned int		rx_count_cooked;
+
+	int			mtu;		/* Our mtu (to spot changes!) */
+	int			buffsize;       /* Max buffers sizes */
+
+	unsigned long		flags;		/* Flag values/ mode etc */
+	unsigned char		mode;		/* 6pack mode */
+
+	/* 6pack stuff */
+	unsigned char		tx_delay;
+	unsigned char		persistence;
+	unsigned char		slottime;
+	unsigned char		duplex;
+	unsigned char		led_state;
+	unsigned char		status;
+	unsigned char		status1;
+	unsigned char		status2;
+	unsigned char		tx_enable;
+	unsigned char		tnc_state;
+
+	struct timer_list	tx_t;
+	struct timer_list	resync_t;
+	refcount_t		refcnt;
+	struct semaphore	dead_sem;
+	spinlock_t		lock;
+};
+
+#define AX25_6PACK_HEADER_LEN 0
+
+static void sixpack_decode(struct sixpack *, const unsigned char[], int);
+static int encode_sixpack(unsigned char *, unsigned char *, int, unsigned char);
+
+/*
+ * Perform the persistence/slottime algorithm for CSMA access. If the
+ * persistence check was successful, write the data to the serial driver.
+ * Note that in case of DAMA operation, the data is not sent here.
+ */
+
+static void sp_xmit_on_air(struct timer_list *t)
+{
+	struct sixpack *sp = from_timer(sp, t, tx_t);
+	int actual, when = sp->slottime;
+	static unsigned char random;
+
+	random = random * 17 + 41;
+
+	if (((sp->status1 & SIXP_DCD_MASK) == 0) && (random < sp->persistence)) {
+		sp->led_state = 0x70;
+		sp->tty->ops->write(sp->tty, &sp->led_state, 1);
+		sp->tx_enable = 1;
+		actual = sp->tty->ops->write(sp->tty, sp->xbuff, sp->status2);
+		sp->xleft -= actual;
+		sp->xhead += actual;
+		sp->led_state = 0x60;
+		sp->tty->ops->write(sp->tty, &sp->led_state, 1);
+		sp->status2 = 0;
+	} else
+		mod_timer(&sp->tx_t, jiffies + ((when + 1) * HZ) / 100);
+}
+
+/* ----> 6pack timer interrupt handler and friends. <---- */
+
+/* Encapsulate one AX.25 frame and stuff into a TTY queue. */
+static void sp_encaps(struct sixpack *sp, unsigned char *icp, int len)
+{
+	unsigned char *msg, *p = icp;
+	int actual, count;
+
+	if (len > sp->mtu) {	/* sp->mtu = AX25_MTU = max. PACLEN = 256 */
+		msg = "oversized transmit packet!";
+		goto out_drop;
+	}
+
+	if (len > sp->mtu) {	/* sp->mtu = AX25_MTU = max. PACLEN = 256 */
+		msg = "oversized transmit packet!";
+		goto out_drop;
+	}
+
+	if (p[0] > 5) {
+		msg = "invalid KISS command";
+		goto out_drop;
+	}
+
+	if ((p[0] != 0) && (len > 2)) {
+		msg = "KISS control packet too long";
+		goto out_drop;
+	}
+
+	if ((p[0] == 0) && (len < 15)) {
+		msg = "bad AX.25 packet to transmit";
+		goto out_drop;
+	}
+
+	count = encode_sixpack(p, sp->xbuff, len, sp->tx_delay);
+	set_bit(TTY_DO_WRITE_WAKEUP, &sp->tty->flags);
+
+	switch (p[0]) {
+	case 1:	sp->tx_delay = p[1];
+		return;
+	case 2:	sp->persistence = p[1];
+		return;
+	case 3:	sp->slottime = p[1];
+		return;
+	case 4:	/* ignored */
+		return;
+	case 5:	sp->duplex = p[1];
+		return;
+	}
+
+	if (p[0] != 0)
+		return;
+
+	/*
+	 * In case of fullduplex or DAMA operation, we don't take care about the
+	 * state of the DCD or of any timers, as the determination of the
+	 * correct time to send is the job of the AX.25 layer. We send
+	 * immediately after data has arrived.
+	 */
+	if (sp->duplex == 1) {
+		sp->led_state = 0x70;
+		sp->tty->ops->write(sp->tty, &sp->led_state, 1);
+		sp->tx_enable = 1;
+		actual = sp->tty->ops->write(sp->tty, sp->xbuff, count);
+		sp->xleft = count - actual;
+		sp->xhead = sp->xbuff + actual;
+		sp->led_state = 0x60;
+		sp->tty->ops->write(sp->tty, &sp->led_state, 1);
+	} else {
+		sp->xleft = count;
+		sp->xhead = sp->xbuff;
+		sp->status2 = count;
+		sp_xmit_on_air(&sp->tx_t);
+	}
+
+	return;
+
+out_drop:
+	sp->dev->stats.tx_dropped++;
+	netif_start_queue(sp->dev);
+	if (net_ratelimit())
+		printk(KERN_DEBUG "%s: %s - dropped.\n", sp->dev->name, msg);
+}
+
+/* Encapsulate an IP datagram and kick it into a TTY queue. */
+
+static netdev_tx_t sp_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct sixpack *sp = netdev_priv(dev);
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return ax25_ip_xmit(skb);
+
+	spin_lock_bh(&sp->lock);
+	/* We were not busy, so we are now... :-) */
+	netif_stop_queue(dev);
+	dev->stats.tx_bytes += skb->len;
+	sp_encaps(sp, skb->data, skb->len);
+	spin_unlock_bh(&sp->lock);
+
+	dev_kfree_skb(skb);
+
+	return NETDEV_TX_OK;
+}
+
+static int sp_open_dev(struct net_device *dev)
+{
+	struct sixpack *sp = netdev_priv(dev);
+
+	if (sp->tty == NULL)
+		return -ENODEV;
+	return 0;
+}
+
+/* Close the low-level part of the 6pack channel. */
+static int sp_close(struct net_device *dev)
+{
+	struct sixpack *sp = netdev_priv(dev);
+
+	spin_lock_bh(&sp->lock);
+	if (sp->tty) {
+		/* TTY discipline is running. */
+		clear_bit(TTY_DO_WRITE_WAKEUP, &sp->tty->flags);
+	}
+	netif_stop_queue(dev);
+	spin_unlock_bh(&sp->lock);
+
+	return 0;
+}
+
+static int sp_set_mac_address(struct net_device *dev, void *addr)
+{
+	struct sockaddr_ax25 *sa = addr;
+
+	netif_tx_lock_bh(dev);
+	netif_addr_lock(dev);
+	memcpy(dev->dev_addr, &sa->sax25_call, AX25_ADDR_LEN);
+	netif_addr_unlock(dev);
+	netif_tx_unlock_bh(dev);
+
+	return 0;
+}
+
+static const struct net_device_ops sp_netdev_ops = {
+	.ndo_open		= sp_open_dev,
+	.ndo_stop		= sp_close,
+	.ndo_start_xmit		= sp_xmit,
+	.ndo_set_mac_address    = sp_set_mac_address,
+};
+
+static void sp_setup(struct net_device *dev)
+{
+	/* Finish setting up the DEVICE info. */
+	dev->netdev_ops		= &sp_netdev_ops;
+	dev->needs_free_netdev	= true;
+	dev->mtu		= SIXP_MTU;
+	dev->hard_header_len	= AX25_MAX_HEADER_LEN;
+	dev->header_ops 	= &ax25_header_ops;
+
+	dev->addr_len		= AX25_ADDR_LEN;
+	dev->type		= ARPHRD_AX25;
+	dev->tx_queue_len	= 10;
+
+	/* Only activated in AX.25 mode */
+	memcpy(dev->broadcast, &ax25_bcast, AX25_ADDR_LEN);
+	memcpy(dev->dev_addr, &ax25_defaddr, AX25_ADDR_LEN);
+
+	dev->flags		= 0;
+}
+
+/* Send one completely decapsulated IP datagram to the IP layer. */
+
+/*
+ * This is the routine that sends the received data to the kernel AX.25.
+ * 'cmd' is the KISS command. For AX.25 data, it is zero.
+ */
+
+static void sp_bump(struct sixpack *sp, char cmd)
+{
+	struct sk_buff *skb;
+	int count;
+	unsigned char *ptr;
+
+	count = sp->rcount + 1;
+
+	sp->dev->stats.rx_bytes += count;
+
+	if ((skb = dev_alloc_skb(count)) == NULL)
+		goto out_mem;
+
+	ptr = skb_put(skb, count);
+	*ptr++ = cmd;	/* KISS command */
+
+	memcpy(ptr, sp->cooked_buf + 1, count);
+	skb->protocol = ax25_type_trans(skb, sp->dev);
+	netif_rx(skb);
+	sp->dev->stats.rx_packets++;
+
+	return;
+
+out_mem:
+	sp->dev->stats.rx_dropped++;
+}
+
+
+/* ----------------------------------------------------------------------- */
+
+/*
+ * We have a potential race on dereferencing tty->disc_data, because the tty
+ * layer provides no locking at all - thus one cpu could be running
+ * sixpack_receive_buf while another calls sixpack_close, which zeroes
+ * tty->disc_data and frees the memory that sixpack_receive_buf is using.  The
+ * best way to fix this is to use a rwlock in the tty struct, but for now we
+ * use a single global rwlock for all ttys in ppp line discipline.
+ */
+static DEFINE_RWLOCK(disc_data_lock);
+                                                                                
+static struct sixpack *sp_get(struct tty_struct *tty)
+{
+	struct sixpack *sp;
+
+	read_lock(&disc_data_lock);
+	sp = tty->disc_data;
+	if (sp)
+		refcount_inc(&sp->refcnt);
+	read_unlock(&disc_data_lock);
+
+	return sp;
+}
+
+static void sp_put(struct sixpack *sp)
+{
+	if (refcount_dec_and_test(&sp->refcnt))
+		up(&sp->dead_sem);
+}
+
+/*
+ * Called by the TTY driver when there's room for more data.  If we have
+ * more packets to send, we send them here.
+ */
+static void sixpack_write_wakeup(struct tty_struct *tty)
+{
+	struct sixpack *sp = sp_get(tty);
+	int actual;
+
+	if (!sp)
+		return;
+	if (sp->xleft <= 0)  {
+		/* Now serial buffer is almost free & we can start
+		 * transmission of another packet */
+		sp->dev->stats.tx_packets++;
+		clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+		sp->tx_enable = 0;
+		netif_wake_queue(sp->dev);
+		goto out;
+	}
+
+	if (sp->tx_enable) {
+		actual = tty->ops->write(tty, sp->xhead, sp->xleft);
+		sp->xleft -= actual;
+		sp->xhead += actual;
+	}
+
+out:
+	sp_put(sp);
+}
+
+/* ----------------------------------------------------------------------- */
+
+/*
+ * Handle the 'receiver data ready' interrupt.
+ * This function is called by the tty module in the kernel when
+ * a block of 6pack data has been received, which can now be decapsulated
+ * and sent on to some IP layer for further processing.
+ */
+static void sixpack_receive_buf(struct tty_struct *tty,
+	const unsigned char *cp, char *fp, int count)
+{
+	struct sixpack *sp;
+	int count1;
+
+	if (!count)
+		return;
+
+	sp = sp_get(tty);
+	if (!sp)
+		return;
+
+	/* Read the characters out of the buffer */
+	count1 = count;
+	while (count) {
+		count--;
+		if (fp && *fp++) {
+			if (!test_and_set_bit(SIXPF_ERROR, &sp->flags))
+				sp->dev->stats.rx_errors++;
+			continue;
+		}
+	}
+	sixpack_decode(sp, cp, count1);
+
+	sp_put(sp);
+	tty_unthrottle(tty);
+}
+
+/*
+ * Try to resync the TNC. Called by the resync timer defined in
+ * decode_prio_command
+ */
+
+#define TNC_UNINITIALIZED	0
+#define TNC_UNSYNC_STARTUP	1
+#define TNC_UNSYNCED		2
+#define TNC_IN_SYNC		3
+
+static void __tnc_set_sync_state(struct sixpack *sp, int new_tnc_state)
+{
+	char *msg;
+
+	switch (new_tnc_state) {
+	default:			/* gcc oh piece-o-crap ... */
+	case TNC_UNSYNC_STARTUP:
+		msg = "Synchronizing with TNC";
+		break;
+	case TNC_UNSYNCED:
+		msg = "Lost synchronization with TNC\n";
+		break;
+	case TNC_IN_SYNC:
+		msg = "Found TNC";
+		break;
+	}
+
+	sp->tnc_state = new_tnc_state;
+	printk(KERN_INFO "%s: %s\n", sp->dev->name, msg);
+}
+
+static inline void tnc_set_sync_state(struct sixpack *sp, int new_tnc_state)
+{
+	int old_tnc_state = sp->tnc_state;
+
+	if (old_tnc_state != new_tnc_state)
+		__tnc_set_sync_state(sp, new_tnc_state);
+}
+
+static void resync_tnc(struct timer_list *t)
+{
+	struct sixpack *sp = from_timer(sp, t, resync_t);
+	static char resync_cmd = 0xe8;
+
+	/* clear any data that might have been received */
+
+	sp->rx_count = 0;
+	sp->rx_count_cooked = 0;
+
+	/* reset state machine */
+
+	sp->status = 1;
+	sp->status1 = 1;
+	sp->status2 = 0;
+
+	/* resync the TNC */
+
+	sp->led_state = 0x60;
+	sp->tty->ops->write(sp->tty, &sp->led_state, 1);
+	sp->tty->ops->write(sp->tty, &resync_cmd, 1);
+
+
+	/* Start resync timer again -- the TNC might be still absent */
+
+	del_timer(&sp->resync_t);
+	sp->resync_t.expires	= jiffies + SIXP_RESYNC_TIMEOUT;
+	add_timer(&sp->resync_t);
+}
+
+static inline int tnc_init(struct sixpack *sp)
+{
+	unsigned char inbyte = 0xe8;
+
+	tnc_set_sync_state(sp, TNC_UNSYNC_STARTUP);
+
+	sp->tty->ops->write(sp->tty, &inbyte, 1);
+
+	del_timer(&sp->resync_t);
+	sp->resync_t.expires = jiffies + SIXP_RESYNC_TIMEOUT;
+	add_timer(&sp->resync_t);
+
+	return 0;
+}
+
+/*
+ * Open the high-level part of the 6pack channel.
+ * This function is called by the TTY module when the
+ * 6pack line discipline is called for.  Because we are
+ * sure the tty line exists, we only have to link it to
+ * a free 6pcack channel...
+ */
+static int sixpack_open(struct tty_struct *tty)
+{
+	char *rbuff = NULL, *xbuff = NULL;
+	struct net_device *dev;
+	struct sixpack *sp;
+	unsigned long len;
+	int err = 0;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+	if (tty->ops->write == NULL)
+		return -EOPNOTSUPP;
+
+	dev = alloc_netdev(sizeof(struct sixpack), "sp%d", NET_NAME_UNKNOWN,
+			   sp_setup);
+	if (!dev) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	sp = netdev_priv(dev);
+	sp->dev = dev;
+
+	spin_lock_init(&sp->lock);
+	refcount_set(&sp->refcnt, 1);
+	sema_init(&sp->dead_sem, 0);
+
+	/* !!! length of the buffers. MTU is IP MTU, not PACLEN!  */
+
+	len = dev->mtu * 2;
+
+	rbuff = kmalloc(len + 4, GFP_KERNEL);
+	xbuff = kmalloc(len + 4, GFP_KERNEL);
+
+	if (rbuff == NULL || xbuff == NULL) {
+		err = -ENOBUFS;
+		goto out_free;
+	}
+
+	spin_lock_bh(&sp->lock);
+
+	sp->tty = tty;
+
+	sp->rbuff	= rbuff;
+	sp->xbuff	= xbuff;
+
+	sp->mtu		= AX25_MTU + 73;
+	sp->buffsize	= len;
+	sp->rcount	= 0;
+	sp->rx_count	= 0;
+	sp->rx_count_cooked = 0;
+	sp->xleft	= 0;
+
+	sp->flags	= 0;		/* Clear ESCAPE & ERROR flags */
+
+	sp->duplex	= 0;
+	sp->tx_delay    = SIXP_TXDELAY;
+	sp->persistence = SIXP_PERSIST;
+	sp->slottime    = SIXP_SLOTTIME;
+	sp->led_state   = 0x60;
+	sp->status      = 1;
+	sp->status1     = 1;
+	sp->status2     = 0;
+	sp->tx_enable   = 0;
+
+	netif_start_queue(dev);
+
+	timer_setup(&sp->tx_t, sp_xmit_on_air, 0);
+
+	timer_setup(&sp->resync_t, resync_tnc, 0);
+
+	spin_unlock_bh(&sp->lock);
+
+	/* Done.  We have linked the TTY line to a channel. */
+	tty->disc_data = sp;
+	tty->receive_room = 65536;
+
+	/* Now we're ready to register. */
+	err = register_netdev(dev);
+	if (err)
+		goto out_free;
+
+	tnc_init(sp);
+
+	return 0;
+
+out_free:
+	kfree(xbuff);
+	kfree(rbuff);
+
+	free_netdev(dev);
+
+out:
+	return err;
+}
+
+
+/*
+ * Close down a 6pack channel.
+ * This means flushing out any pending queues, and then restoring the
+ * TTY line discipline to what it was before it got hooked to 6pack
+ * (which usually is TTY again).
+ */
+static void sixpack_close(struct tty_struct *tty)
+{
+	struct sixpack *sp;
+
+	write_lock_bh(&disc_data_lock);
+	sp = tty->disc_data;
+	tty->disc_data = NULL;
+	write_unlock_bh(&disc_data_lock);
+	if (!sp)
+		return;
+
+	/*
+	 * We have now ensured that nobody can start using ap from now on, but
+	 * we have to wait for all existing users to finish.
+	 */
+	if (!refcount_dec_and_test(&sp->refcnt))
+		down(&sp->dead_sem);
+
+	/* We must stop the queue to avoid potentially scribbling
+	 * on the free buffers. The sp->dead_sem is not sufficient
+	 * to protect us from sp->xbuff access.
+	 */
+	netif_stop_queue(sp->dev);
+
+	del_timer_sync(&sp->tx_t);
+	del_timer_sync(&sp->resync_t);
+
+	/* Free all 6pack frame buffers. */
+	kfree(sp->rbuff);
+	kfree(sp->xbuff);
+
+	unregister_netdev(sp->dev);
+}
+
+/* Perform I/O control on an active 6pack channel. */
+static int sixpack_ioctl(struct tty_struct *tty, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	struct sixpack *sp = sp_get(tty);
+	struct net_device *dev;
+	unsigned int tmp, err;
+
+	if (!sp)
+		return -ENXIO;
+	dev = sp->dev;
+
+	switch(cmd) {
+	case SIOCGIFNAME:
+		err = copy_to_user((void __user *) arg, dev->name,
+		                   strlen(dev->name) + 1) ? -EFAULT : 0;
+		break;
+
+	case SIOCGIFENCAP:
+		err = put_user(0, (int __user *) arg);
+		break;
+
+	case SIOCSIFENCAP:
+		if (get_user(tmp, (int __user *) arg)) {
+			err = -EFAULT;
+			break;
+		}
+
+		sp->mode = tmp;
+		dev->addr_len        = AX25_ADDR_LEN;
+		dev->hard_header_len = AX25_KISS_HEADER_LEN +
+		                       AX25_MAX_HEADER_LEN + 3;
+		dev->type            = ARPHRD_AX25;
+
+		err = 0;
+		break;
+
+	 case SIOCSIFHWADDR: {
+		char addr[AX25_ADDR_LEN];
+
+		if (copy_from_user(&addr,
+		                   (void __user *) arg, AX25_ADDR_LEN)) {
+				err = -EFAULT;
+				break;
+			}
+
+			netif_tx_lock_bh(dev);
+			memcpy(dev->dev_addr, &addr, AX25_ADDR_LEN);
+			netif_tx_unlock_bh(dev);
+
+			err = 0;
+			break;
+		}
+
+	default:
+		err = tty_mode_ioctl(tty, file, cmd, arg);
+	}
+
+	sp_put(sp);
+
+	return err;
+}
+
+#ifdef CONFIG_COMPAT
+static long sixpack_compat_ioctl(struct tty_struct * tty, struct file * file,
+				unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case SIOCGIFNAME:
+	case SIOCGIFENCAP:
+	case SIOCSIFENCAP:
+	case SIOCSIFHWADDR:
+		return sixpack_ioctl(tty, file, cmd,
+				(unsigned long)compat_ptr(arg));
+	}
+
+	return -ENOIOCTLCMD;
+}
+#endif
+
+static struct tty_ldisc_ops sp_ldisc = {
+	.owner		= THIS_MODULE,
+	.magic		= TTY_LDISC_MAGIC,
+	.name		= "6pack",
+	.open		= sixpack_open,
+	.close		= sixpack_close,
+	.ioctl		= sixpack_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= sixpack_compat_ioctl,
+#endif
+	.receive_buf	= sixpack_receive_buf,
+	.write_wakeup	= sixpack_write_wakeup,
+};
+
+/* Initialize 6pack control device -- register 6pack line discipline */
+
+static const char msg_banner[]  __initconst = KERN_INFO \
+	"AX.25: 6pack driver, " SIXPACK_VERSION "\n";
+static const char msg_regfail[] __initconst = KERN_ERR  \
+	"6pack: can't register line discipline (err = %d)\n";
+
+static int __init sixpack_init_driver(void)
+{
+	int status;
+
+	printk(msg_banner);
+
+	/* Register the provided line protocol discipline */
+	if ((status = tty_register_ldisc(N_6PACK, &sp_ldisc)) != 0)
+		printk(msg_regfail, status);
+
+	return status;
+}
+
+static const char msg_unregfail[] = KERN_ERR \
+	"6pack: can't unregister line discipline (err = %d)\n";
+
+static void __exit sixpack_exit_driver(void)
+{
+	int ret;
+
+	if ((ret = tty_unregister_ldisc(N_6PACK)))
+		printk(msg_unregfail, ret);
+}
+
+/* encode an AX.25 packet into 6pack */
+
+static int encode_sixpack(unsigned char *tx_buf, unsigned char *tx_buf_raw,
+	int length, unsigned char tx_delay)
+{
+	int count = 0;
+	unsigned char checksum = 0, buf[400];
+	int raw_count = 0;
+
+	tx_buf_raw[raw_count++] = SIXP_PRIO_CMD_MASK | SIXP_TX_MASK;
+	tx_buf_raw[raw_count++] = SIXP_SEOF;
+
+	buf[0] = tx_delay;
+	for (count = 1; count < length; count++)
+		buf[count] = tx_buf[count];
+
+	for (count = 0; count < length; count++)
+		checksum += buf[count];
+	buf[length] = (unsigned char) 0xff - checksum;
+
+	for (count = 0; count <= length; count++) {
+		if ((count % 3) == 0) {
+			tx_buf_raw[raw_count++] = (buf[count] & 0x3f);
+			tx_buf_raw[raw_count] = ((buf[count] >> 2) & 0x30);
+		} else if ((count % 3) == 1) {
+			tx_buf_raw[raw_count++] |= (buf[count] & 0x0f);
+			tx_buf_raw[raw_count] =	((buf[count] >> 2) & 0x3c);
+		} else {
+			tx_buf_raw[raw_count++] |= (buf[count] & 0x03);
+			tx_buf_raw[raw_count++] = (buf[count] >> 2);
+		}
+	}
+	if ((length % 3) != 2)
+		raw_count++;
+	tx_buf_raw[raw_count++] = SIXP_SEOF;
+	return raw_count;
+}
+
+/* decode 4 sixpack-encoded bytes into 3 data bytes */
+
+static void decode_data(struct sixpack *sp, unsigned char inbyte)
+{
+	unsigned char *buf;
+
+	if (sp->rx_count != 3) {
+		sp->raw_buf[sp->rx_count++] = inbyte;
+
+		return;
+	}
+
+	buf = sp->raw_buf;
+	sp->cooked_buf[sp->rx_count_cooked++] =
+		buf[0] | ((buf[1] << 2) & 0xc0);
+	sp->cooked_buf[sp->rx_count_cooked++] =
+		(buf[1] & 0x0f) | ((buf[2] << 2) & 0xf0);
+	sp->cooked_buf[sp->rx_count_cooked++] =
+		(buf[2] & 0x03) | (inbyte << 2);
+	sp->rx_count = 0;
+}
+
+/* identify and execute a 6pack priority command byte */
+
+static void decode_prio_command(struct sixpack *sp, unsigned char cmd)
+{
+	int actual;
+
+	if ((cmd & SIXP_PRIO_DATA_MASK) != 0) {     /* idle ? */
+
+	/* RX and DCD flags can only be set in the same prio command,
+	   if the DCD flag has been set without the RX flag in the previous
+	   prio command. If DCD has not been set before, something in the
+	   transmission has gone wrong. In this case, RX and DCD are
+	   cleared in order to prevent the decode_data routine from
+	   reading further data that might be corrupt. */
+
+		if (((sp->status & SIXP_DCD_MASK) == 0) &&
+			((cmd & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK)) {
+				if (sp->status != 1)
+					printk(KERN_DEBUG "6pack: protocol violation\n");
+				else
+					sp->status = 0;
+				cmd &= ~SIXP_RX_DCD_MASK;
+		}
+		sp->status = cmd & SIXP_PRIO_DATA_MASK;
+	} else { /* output watchdog char if idle */
+		if ((sp->status2 != 0) && (sp->duplex == 1)) {
+			sp->led_state = 0x70;
+			sp->tty->ops->write(sp->tty, &sp->led_state, 1);
+			sp->tx_enable = 1;
+			actual = sp->tty->ops->write(sp->tty, sp->xbuff, sp->status2);
+			sp->xleft -= actual;
+			sp->xhead += actual;
+			sp->led_state = 0x60;
+			sp->status2 = 0;
+
+		}
+	}
+
+	/* needed to trigger the TNC watchdog */
+	sp->tty->ops->write(sp->tty, &sp->led_state, 1);
+
+        /* if the state byte has been received, the TNC is present,
+           so the resync timer can be reset. */
+
+	if (sp->tnc_state == TNC_IN_SYNC) {
+		del_timer(&sp->resync_t);
+		sp->resync_t.expires	= jiffies + SIXP_INIT_RESYNC_TIMEOUT;
+		add_timer(&sp->resync_t);
+	}
+
+	sp->status1 = cmd & SIXP_PRIO_DATA_MASK;
+}
+
+/* identify and execute a standard 6pack command byte */
+
+static void decode_std_command(struct sixpack *sp, unsigned char cmd)
+{
+	unsigned char checksum = 0, rest = 0;
+	short i;
+
+	switch (cmd & SIXP_CMD_MASK) {     /* normal command */
+	case SIXP_SEOF:
+		if ((sp->rx_count == 0) && (sp->rx_count_cooked == 0)) {
+			if ((sp->status & SIXP_RX_DCD_MASK) ==
+				SIXP_RX_DCD_MASK) {
+				sp->led_state = 0x68;
+				sp->tty->ops->write(sp->tty, &sp->led_state, 1);
+			}
+		} else {
+			sp->led_state = 0x60;
+			/* fill trailing bytes with zeroes */
+			sp->tty->ops->write(sp->tty, &sp->led_state, 1);
+			rest = sp->rx_count;
+			if (rest != 0)
+				 for (i = rest; i <= 3; i++)
+					decode_data(sp, 0);
+			if (rest == 2)
+				sp->rx_count_cooked -= 2;
+			else if (rest == 3)
+				sp->rx_count_cooked -= 1;
+			for (i = 0; i < sp->rx_count_cooked; i++)
+				checksum += sp->cooked_buf[i];
+			if (checksum != SIXP_CHKSUM) {
+				printk(KERN_DEBUG "6pack: bad checksum %2.2x\n", checksum);
+			} else {
+				sp->rcount = sp->rx_count_cooked-2;
+				sp_bump(sp, 0);
+			}
+			sp->rx_count_cooked = 0;
+		}
+		break;
+	case SIXP_TX_URUN: printk(KERN_DEBUG "6pack: TX underrun\n");
+		break;
+	case SIXP_RX_ORUN: printk(KERN_DEBUG "6pack: RX overrun\n");
+		break;
+	case SIXP_RX_BUF_OVL:
+		printk(KERN_DEBUG "6pack: RX buffer overflow\n");
+	}
+}
+
+/* decode a 6pack packet */
+
+static void
+sixpack_decode(struct sixpack *sp, const unsigned char *pre_rbuff, int count)
+{
+	unsigned char inbyte;
+	int count1;
+
+	for (count1 = 0; count1 < count; count1++) {
+		inbyte = pre_rbuff[count1];
+		if (inbyte == SIXP_FOUND_TNC) {
+			tnc_set_sync_state(sp, TNC_IN_SYNC);
+			del_timer(&sp->resync_t);
+		}
+		if ((inbyte & SIXP_PRIO_CMD_MASK) != 0)
+			decode_prio_command(sp, inbyte);
+		else if ((inbyte & SIXP_STD_CMD_MASK) != 0)
+			decode_std_command(sp, inbyte);
+		else if ((sp->status & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK)
+			decode_data(sp, inbyte);
+	}
+}
+
+MODULE_AUTHOR("Ralf Baechle DO1GRB <ralf@linux-mips.org>");
+MODULE_DESCRIPTION("6pack driver for AX.25");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_LDISC(N_6PACK);
+
+module_init(sixpack_init_driver);
+module_exit(sixpack_exit_driver);
diff --git a/drivers/net/hamradio/Kconfig b/drivers/net/hamradio/Kconfig
new file mode 100644
index 0000000..bf5e596
--- /dev/null
+++ b/drivers/net/hamradio/Kconfig
@@ -0,0 +1,193 @@
+config MKISS
+	tristate "Serial port KISS driver"
+	depends on AX25 && TTY
+	select CRC16
+	---help---
+	  KISS is a protocol used for the exchange of data between a computer
+	  and a Terminal Node Controller (a small embedded system commonly
+	  used for networking over AX.25 amateur radio connections; it
+	  connects the computer's serial port with the radio's microphone
+	  input and speaker output).
+
+	  Although KISS is less advanced than the 6pack protocol, it has
+	  the advantage that it is already supported by most modern TNCs
+	  without the need for a firmware upgrade.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called mkiss.
+
+config 6PACK
+	tristate "Serial port 6PACK driver"
+	depends on AX25 && TTY
+	---help---
+	  6pack is a transmission protocol for the data exchange between your
+	  PC and your TNC (the Terminal Node Controller acts as a kind of
+	  modem connecting your computer's serial port to your radio's
+	  microphone input and speaker output). This protocol can be used as
+	  an alternative to KISS for networking over AX.25 amateur radio
+	  connections, but it has some extended functionality.
+
+	  Note that this driver is still experimental and might cause
+	  problems. For details about the features and the usage of the
+	  driver, read <file:Documentation/networking/6pack.txt>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called 6pack.
+
+config BPQETHER
+	tristate "BPQ Ethernet driver"
+	depends on AX25
+	help
+	  AX.25 is the protocol used for computer communication over amateur
+	  radio. If you say Y here, you will be able to send and receive AX.25
+	  traffic over Ethernet (also called "BPQ AX.25"), which could be
+	  useful if some other computer on your local network has a direct
+	  amateur radio connection.
+
+config DMASCC
+	tristate "High-speed (DMA) SCC driver for AX.25"
+	depends on ISA && AX25 && BROKEN_ON_SMP && ISA_DMA_API
+	---help---
+	  This is a driver for high-speed SCC boards, i.e. those supporting
+	  DMA on one port. You usually use those boards to connect your
+	  computer to an amateur radio modem (such as the WA4DSY 56kbps
+	  modem), in order to send and receive AX.25 packet radio network
+	  traffic.
+
+	  Currently, this driver supports Ottawa PI/PI2, Paccomm/Gracilis
+	  PackeTwin, and S5SCC/DMA boards. They are detected automatically.
+	  If you have one of these cards, say Y here and read the AX25-HOWTO,
+	  available from <http://www.tldp.org/docs.html#howto>.
+
+	  This driver can operate multiple boards simultaneously. If you
+	  compile it as a module (by saying M instead of Y), it will be called
+	  dmascc. If you don't pass any parameter to the driver, all
+	  possible I/O addresses are probed. This could irritate other devices
+	  that are currently not in use. You may specify the list of addresses
+	  to be probed by "dmascc.io=addr1,addr2,..." (when compiled into the
+	  kernel image) or "io=addr1,addr2,..." (when loaded as a module). The
+	  network interfaces will be called dmascc0 and dmascc1 for the board
+	  detected first, dmascc2 and dmascc3 for the second one, and so on.
+
+	  Before you configure each interface with ifconfig, you MUST set
+	  certain parameters, such as channel access timing, clock mode, and
+	  DMA channel. This is accomplished with a small utility program,
+	  dmascc_cfg, available at
+	  <http://www.linux-ax25.org/wiki/Ax25-tools>. Please be sure to
+	  get at least version 1.27 of dmascc_cfg, as older versions will not
+	  work with the current driver.
+
+config SCC
+	tristate "Z8530 SCC driver"
+	depends on ISA && AX25 && ISA_DMA_API
+	---help---
+	  These cards are used to connect your Linux box to an amateur radio
+	  in order to communicate with other computers. If you want to use
+	  this, read <file:Documentation/networking/z8530drv.txt> and the
+	  AX25-HOWTO, available from
+	  <http://www.tldp.org/docs.html#howto>. Also make sure to say Y
+	  to "Amateur Radio AX.25 Level 2" support.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called scc.
+
+config SCC_DELAY
+	bool "additional delay for PA0HZP OptoSCC compatible boards"
+	depends on SCC
+	help
+	  Say Y here if you experience problems with the SCC driver not
+	  working properly; please read
+	  <file:Documentation/networking/z8530drv.txt> for details.
+
+	  If unsure, say N.
+
+config SCC_TRXECHO
+	bool "support for TRX that feedback the tx signal to rx"
+	depends on SCC
+	help
+	  Some transmitters feed the transmitted signal back to the receive
+	  line.  Say Y here to foil this by explicitly disabling the receiver
+	  during data transmission.
+
+	  If in doubt, say Y.
+
+config BAYCOM_SER_FDX
+	tristate "BAYCOM ser12 fullduplex driver for AX.25"
+	depends on AX25 && !S390
+	select CRC_CCITT
+	---help---
+	  This is one of two drivers for Baycom style simple amateur radio
+	  modems that connect to a serial interface. The driver supports the
+	  ser12 design in full-duplex mode. In addition, it allows the
+	  baudrate to be set between 300 and 4800 baud (however not all modems
+	  support all baudrates). This is the preferred driver. The next
+	  driver, "BAYCOM ser12 half-duplex driver for AX.25" is the old
+	  driver and still provided in case this driver does not work with
+	  your serial interface chip. To configure the driver, use the sethdlc
+	  utility available in the standard ax25 utilities package. For
+	  information on the modems, see <http://www.baycom.de/> and
+	  <file:Documentation/networking/baycom.txt>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called baycom_ser_fdx.  This is recommended.
+
+config BAYCOM_SER_HDX
+	tristate "BAYCOM ser12 halfduplex driver for AX.25"
+	depends on AX25 && !S390
+	select CRC_CCITT
+	---help---
+	  This is one of two drivers for Baycom style simple amateur radio
+	  modems that connect to a serial interface. The driver supports the
+	  ser12 design in half-duplex mode. This is the old driver.  It is
+	  still provided in case your serial interface chip does not work with
+	  the full-duplex driver. This driver is deprecated.  To configure
+	  the driver, use the sethdlc utility available in the standard ax25
+	  utilities package. For information on the modems, see
+	  <http://www.baycom.de/> and
+	  <file:Documentation/networking/baycom.txt>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called baycom_ser_hdx.  This is recommended.
+
+config BAYCOM_PAR
+	tristate "BAYCOM picpar and par96 driver for AX.25"
+	depends on PARPORT && AX25
+	select CRC_CCITT
+	---help---
+	  This is a driver for Baycom style simple amateur radio modems that
+	  connect to a parallel interface. The driver supports the picpar and
+	  par96 designs. To configure the driver, use the sethdlc utility
+	  available in the standard ax25 utilities package. For information on
+	  the modems, see <http://www.baycom.de/> and the file
+	  <file:Documentation/networking/baycom.txt>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called baycom_par.  This is recommended.
+
+config BAYCOM_EPP
+	tristate "BAYCOM epp driver for AX.25"
+	depends on PARPORT && AX25 && !64BIT
+	select CRC_CCITT
+	---help---
+	  This is a driver for Baycom style simple amateur radio modems that
+	  connect to a parallel interface. The driver supports the EPP
+	  designs. To configure the driver, use the sethdlc utility available
+	  in the standard ax25 utilities package. For information on the
+	  modems, see <http://www.baycom.de/> and the file
+	  <file:Documentation/networking/baycom.txt>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called baycom_epp.  This is recommended.
+
+config YAM
+	tristate "YAM driver for AX.25"
+	depends on AX25 && !S390
+	help
+	  The YAM is a modem for packet radio which connects to the serial
+	  port and includes some of the functions of a Terminal Node
+	  Controller. If you have one of those, say Y here.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called yam.
+
+ 
diff --git a/drivers/net/hamradio/Makefile b/drivers/net/hamradio/Makefile
new file mode 100644
index 0000000..7a1518d
--- /dev/null
+++ b/drivers/net/hamradio/Makefile
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Linux AX.25 and HFMODEM device drivers.
+#
+#
+# 19971130 	Moved the amateur radio related network drivers from 
+#		drivers/net/ to drivers/hamradio for easier maintenance.
+#               Joerg Reuter DL1BKE <jreuter@yaina.de>
+#
+# 20000806	Rewritten to use lists instead of if-statements.
+#		Christoph Hellwig <hch@infradead.org>
+#
+
+obj-$(CONFIG_DMASCC)		+= dmascc.o
+obj-$(CONFIG_SCC)		+= scc.o
+obj-$(CONFIG_MKISS)		+= mkiss.o
+obj-$(CONFIG_6PACK)		+= 6pack.o
+obj-$(CONFIG_YAM)		+= yam.o
+obj-$(CONFIG_BPQETHER)		+= bpqether.o
+obj-$(CONFIG_BAYCOM_SER_FDX)	+= baycom_ser_fdx.o	hdlcdrv.o
+obj-$(CONFIG_BAYCOM_SER_HDX)	+= baycom_ser_hdx.o	hdlcdrv.o
+obj-$(CONFIG_BAYCOM_PAR)	+= baycom_par.o		hdlcdrv.o
+obj-$(CONFIG_BAYCOM_EPP)	+= baycom_epp.o		hdlcdrv.o
diff --git a/drivers/net/hamradio/baycom_epp.c b/drivers/net/hamradio/baycom_epp.c
new file mode 100644
index 0000000..1e62d00
--- /dev/null
+++ b/drivers/net/hamradio/baycom_epp.c
@@ -0,0 +1,1338 @@
+/*****************************************************************************/
+
+/*
+ *	baycom_epp.c  -- baycom epp radio modem driver.
+ *
+ *	Copyright (C) 1998-2000
+ *          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.
+ *
+ *
+ *  History:
+ *   0.1  xx.xx.1998  Initial version by Matthias Welwarsky (dg2fef)
+ *   0.2  21.04.1998  Massive rework by Thomas Sailer
+ *                    Integrated FPGA EPP modem configuration routines
+ *   0.3  11.05.1998  Took FPGA config out and moved it into a separate program
+ *   0.4  26.07.1999  Adapted to new lowlevel parport driver interface
+ *   0.5  03.08.1999  adapt to Linus' new __setup/__initcall
+ *                    removed some pre-2.2 kernel compatibility cruft
+ *   0.6  10.08.1999  Check if parport can do SPP and is safe to access during interrupt contexts
+ *   0.7  12.02.2000  adapted to softnet driver interface
+ *
+ */
+
+/*****************************************************************************/
+
+#include <linux/crc-ccitt.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+#include <linux/fs.h>
+#include <linux/parport.h>
+#include <linux/if_arp.h>
+#include <linux/hdlcdrv.h>
+#include <linux/baycom.h>
+#include <linux/jiffies.h>
+#include <linux/random.h>
+#include <net/ax25.h> 
+#include <linux/uaccess.h>
+
+/* --------------------------------------------------------------------- */
+
+#define BAYCOM_DEBUG
+#define BAYCOM_MAGIC 19730510
+
+/* --------------------------------------------------------------------- */
+
+static const char paranoia_str[] = KERN_ERR 
+	"baycom_epp: bad magic number for hdlcdrv_state struct in routine %s\n";
+
+static const char bc_drvname[] = "baycom_epp";
+static const char bc_drvinfo[] = KERN_INFO "baycom_epp: (C) 1998-2000 Thomas Sailer, HB9JNX/AE4WA\n"
+"baycom_epp: version 0.7\n";
+
+/* --------------------------------------------------------------------- */
+
+#define NR_PORTS 4
+
+static struct net_device *baycom_device[NR_PORTS];
+
+/* --------------------------------------------------------------------- */
+
+/* EPP status register */
+#define EPP_DCDBIT      0x80
+#define EPP_PTTBIT      0x08
+#define EPP_NREF        0x01
+#define EPP_NRAEF       0x02
+#define EPP_NRHF        0x04
+#define EPP_NTHF        0x20
+#define EPP_NTAEF       0x10
+#define EPP_NTEF        EPP_PTTBIT
+
+/* EPP control register */
+#define EPP_TX_FIFO_ENABLE 0x10
+#define EPP_RX_FIFO_ENABLE 0x08
+#define EPP_MODEM_ENABLE   0x20
+#define EPP_LEDS           0xC0
+#define EPP_IRQ_ENABLE     0x10
+
+/* LPT registers */
+#define LPTREG_ECONTROL       0x402
+#define LPTREG_CONFIGB        0x401
+#define LPTREG_CONFIGA        0x400
+#define LPTREG_EPPDATA        0x004
+#define LPTREG_EPPADDR        0x003
+#define LPTREG_CONTROL        0x002
+#define LPTREG_STATUS         0x001
+#define LPTREG_DATA           0x000
+
+/* LPT control register */
+#define LPTCTRL_PROGRAM       0x04   /* 0 to reprogram */
+#define LPTCTRL_WRITE         0x01
+#define LPTCTRL_ADDRSTB       0x08
+#define LPTCTRL_DATASTB       0x02
+#define LPTCTRL_INTEN         0x10
+
+/* LPT status register */
+#define LPTSTAT_SHIFT_NINTR   6
+#define LPTSTAT_WAIT          0x80
+#define LPTSTAT_NINTR         (1<<LPTSTAT_SHIFT_NINTR)
+#define LPTSTAT_PE            0x20
+#define LPTSTAT_DONE          0x10
+#define LPTSTAT_NERROR        0x08
+#define LPTSTAT_EPPTIMEOUT    0x01
+
+/* LPT data register */
+#define LPTDATA_SHIFT_TDI     0
+#define LPTDATA_SHIFT_TMS     2
+#define LPTDATA_TDI           (1<<LPTDATA_SHIFT_TDI)
+#define LPTDATA_TCK           0x02
+#define LPTDATA_TMS           (1<<LPTDATA_SHIFT_TMS)
+#define LPTDATA_INITBIAS      0x80
+
+
+/* EPP modem config/status bits */
+#define EPP_DCDBIT            0x80
+#define EPP_PTTBIT            0x08
+#define EPP_RXEBIT            0x01
+#define EPP_RXAEBIT           0x02
+#define EPP_RXHFULL           0x04
+
+#define EPP_NTHF              0x20
+#define EPP_NTAEF             0x10
+#define EPP_NTEF              EPP_PTTBIT
+
+#define EPP_TX_FIFO_ENABLE    0x10
+#define EPP_RX_FIFO_ENABLE    0x08
+#define EPP_MODEM_ENABLE      0x20
+#define EPP_LEDS              0xC0
+#define EPP_IRQ_ENABLE        0x10
+
+/* Xilinx 4k JTAG instructions */
+#define XC4K_IRLENGTH   3
+#define XC4K_EXTEST     0
+#define XC4K_PRELOAD    1
+#define XC4K_CONFIGURE  5
+#define XC4K_BYPASS     7
+
+#define EPP_CONVENTIONAL  0
+#define EPP_FPGA          1
+#define EPP_FPGAEXTSTATUS 2
+
+#define TXBUFFER_SIZE     ((HDLCDRV_MAXFLEN*6/5)+8)
+
+/* ---------------------------------------------------------------------- */
+/*
+ * Information that need to be kept for each board.
+ */
+
+struct baycom_state {
+	int magic;
+
+        struct pardevice *pdev;
+	struct net_device *dev;
+	unsigned int work_running;
+	struct delayed_work run_work;
+	unsigned int modem;
+	unsigned int bitrate;
+	unsigned char stat;
+
+	struct {
+		unsigned int intclk;
+		unsigned int fclk;
+		unsigned int bps;
+		unsigned int extmodem;
+		unsigned int loopback;
+	} cfg;
+
+        struct hdlcdrv_channel_params ch_params;
+
+        struct {
+		unsigned int bitbuf, bitstream, numbits, state;
+		unsigned char *bufptr;
+		int bufcnt;
+		unsigned char buf[TXBUFFER_SIZE];
+        } hdlcrx;
+
+        struct {
+		int calibrate;
+                int slotcnt;
+		int flags;
+		enum { tx_idle = 0, tx_keyup, tx_data, tx_tail } state;
+		unsigned char *bufptr;
+		int bufcnt;
+		unsigned char buf[TXBUFFER_SIZE];
+        } hdlctx;
+
+	unsigned int ptt_keyed;
+	struct sk_buff *skb;  /* next transmit packet  */
+
+#ifdef BAYCOM_DEBUG
+	struct debug_vals {
+		unsigned long last_jiffies;
+		unsigned cur_intcnt;
+		unsigned last_intcnt;
+		int cur_pllcorr;
+		int last_pllcorr;
+		unsigned int mod_cycles;
+		unsigned int demod_cycles;
+	} debug_vals;
+#endif /* BAYCOM_DEBUG */
+};
+
+/* --------------------------------------------------------------------- */
+
+#define KISS_VERBOSE
+
+/* --------------------------------------------------------------------- */
+
+#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
+
+/* --------------------------------------------------------------------- */
+/*
+ * the CRC routines are stolen from WAMPES
+ * by Dieter Deyke
+ */
+
+
+/*---------------------------------------------------------------------------*/
+
+#if 0
+static inline void append_crc_ccitt(unsigned char *buffer, int len)
+{
+ 	unsigned int crc = 0xffff;
+
+	for (;len>0;len--)
+		crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buffer++) & 0xff];
+	crc ^= 0xffff;
+	*buffer++ = crc;
+	*buffer++ = crc >> 8;
+}
+#endif
+
+/*---------------------------------------------------------------------------*/
+
+static inline int check_crc_ccitt(const unsigned char *buf, int cnt)
+{
+	return (crc_ccitt(0xffff, buf, cnt) & 0xffff) == 0xf0b8;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static inline int calc_crc_ccitt(const unsigned char *buf, int cnt)
+{
+	return (crc_ccitt(0xffff, buf, cnt) ^ 0xffff) & 0xffff;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#define tenms_to_flags(bc,tenms) ((tenms * bc->bitrate) / 800)
+
+/* --------------------------------------------------------------------- */
+
+static inline void baycom_int_freq(struct baycom_state *bc)
+{
+#ifdef BAYCOM_DEBUG
+	unsigned long cur_jiffies = jiffies;
+	/*
+	 * measure the interrupt frequency
+	 */
+	bc->debug_vals.cur_intcnt++;
+	if (time_after_eq(cur_jiffies, bc->debug_vals.last_jiffies + HZ)) {
+		bc->debug_vals.last_jiffies = cur_jiffies;
+		bc->debug_vals.last_intcnt = bc->debug_vals.cur_intcnt;
+		bc->debug_vals.cur_intcnt = 0;
+		bc->debug_vals.last_pllcorr = bc->debug_vals.cur_pllcorr;
+		bc->debug_vals.cur_pllcorr = 0;
+	}
+#endif /* BAYCOM_DEBUG */
+}
+
+/* ---------------------------------------------------------------------- */
+/*
+ *    eppconfig_path should be setable  via /proc/sys.
+ */
+
+static char const eppconfig_path[] = "/usr/sbin/eppfpga";
+
+static char *envp[] = { "HOME=/", "TERM=linux", "PATH=/usr/bin:/bin", NULL };
+
+/* eppconfig: called during ifconfig up to configure the modem */
+static int eppconfig(struct baycom_state *bc)
+{
+	char modearg[256];
+	char portarg[16];
+        char *argv[] = {
+		(char *)eppconfig_path,
+		"-s",
+		"-p", portarg,
+		"-m", modearg,
+		NULL };
+
+	/* set up arguments */
+	sprintf(modearg, "%sclk,%smodem,fclk=%d,bps=%d,divider=%d%s,extstat",
+		bc->cfg.intclk ? "int" : "ext",
+		bc->cfg.extmodem ? "ext" : "int", bc->cfg.fclk, bc->cfg.bps,
+		(bc->cfg.fclk + 8 * bc->cfg.bps) / (16 * bc->cfg.bps),
+		bc->cfg.loopback ? ",loopback" : "");
+	sprintf(portarg, "%ld", bc->pdev->port->base);
+	printk(KERN_DEBUG "%s: %s -s -p %s -m %s\n", bc_drvname, eppconfig_path, portarg, modearg);
+
+	return call_usermodehelper(eppconfig_path, argv, envp, UMH_WAIT_PROC);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static inline void do_kiss_params(struct baycom_state *bc,
+				  unsigned char *data, unsigned long len)
+{
+
+#ifdef KISS_VERBOSE
+#define PKP(a,b) printk(KERN_INFO "baycomm_epp: channel params: " a "\n", b)
+#else /* KISS_VERBOSE */	      
+#define PKP(a,b) 
+#endif /* KISS_VERBOSE */	      
+
+	if (len < 2)
+		return;
+	switch(data[0]) {
+	case PARAM_TXDELAY:
+		bc->ch_params.tx_delay = data[1];
+		PKP("TX delay = %ums", 10 * bc->ch_params.tx_delay);
+		break;
+	case PARAM_PERSIST:   
+		bc->ch_params.ppersist = data[1];
+		PKP("p persistence = %u", bc->ch_params.ppersist);
+		break;
+	case PARAM_SLOTTIME:  
+		bc->ch_params.slottime = data[1];
+		PKP("slot time = %ums", bc->ch_params.slottime);
+		break;
+	case PARAM_TXTAIL:    
+		bc->ch_params.tx_tail = data[1];
+		PKP("TX tail = %ums", bc->ch_params.tx_tail);
+		break;
+	case PARAM_FULLDUP:   
+		bc->ch_params.fulldup = !!data[1];
+		PKP("%s duplex", bc->ch_params.fulldup ? "full" : "half");
+		break;
+	default:
+		break;
+	}
+#undef PKP
+}
+
+/* --------------------------------------------------------------------- */
+
+static void encode_hdlc(struct baycom_state *bc)
+{
+	struct sk_buff *skb;
+	unsigned char *wp, *bp;
+	int pkt_len;
+        unsigned bitstream, notbitstream, bitbuf, numbit, crc;
+	unsigned char crcarr[2];
+	int j;
+	
+	if (bc->hdlctx.bufcnt > 0)
+		return;
+	skb = bc->skb;
+	if (!skb)
+		return;
+	bc->skb = NULL;
+	pkt_len = skb->len-1; /* strip KISS byte */
+	wp = bc->hdlctx.buf;
+	bp = skb->data+1;
+	crc = calc_crc_ccitt(bp, pkt_len);
+	crcarr[0] = crc;
+	crcarr[1] = crc >> 8;
+	*wp++ = 0x7e;
+	bitstream = bitbuf = numbit = 0;
+	while (pkt_len > -2) {
+		bitstream >>= 8;
+		bitstream |= ((unsigned int)*bp) << 8;
+		bitbuf |= ((unsigned int)*bp) << numbit;
+		notbitstream = ~bitstream;
+		bp++;
+		pkt_len--;
+		if (!pkt_len)
+			bp = crcarr;
+		for (j = 0; j < 8; j++)
+			if (unlikely(!(notbitstream & (0x1f0 << j)))) {
+				bitstream &= ~(0x100 << j);
+ 				bitbuf = (bitbuf & (((2 << j) << numbit) - 1)) |
+					((bitbuf & ~(((2 << j) << numbit) - 1)) << 1);
+				numbit++;
+				notbitstream = ~bitstream;
+			}
+		numbit += 8;
+		while (numbit >= 8) {
+			*wp++ = bitbuf;
+			bitbuf >>= 8;
+			numbit -= 8;
+		}
+	}
+	bitbuf |= 0x7e7e << numbit;
+	numbit += 16;
+	while (numbit >= 8) {
+		*wp++ = bitbuf;
+		bitbuf >>= 8;
+		numbit -= 8;
+	}
+	bc->hdlctx.bufptr = bc->hdlctx.buf;
+	bc->hdlctx.bufcnt = wp - bc->hdlctx.buf;
+	dev_kfree_skb(skb);
+	bc->dev->stats.tx_packets++;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int transmit(struct baycom_state *bc, int cnt, unsigned char stat)
+{
+	struct parport *pp = bc->pdev->port;
+	unsigned char tmp[128];
+	int i, j;
+
+	if (bc->hdlctx.state == tx_tail && !(stat & EPP_PTTBIT))
+		bc->hdlctx.state = tx_idle;
+	if (bc->hdlctx.state == tx_idle && bc->hdlctx.calibrate <= 0) {
+		if (bc->hdlctx.bufcnt <= 0)
+			encode_hdlc(bc);
+		if (bc->hdlctx.bufcnt <= 0)
+			return 0;
+		if (!bc->ch_params.fulldup) {
+			if (!(stat & EPP_DCDBIT)) {
+				bc->hdlctx.slotcnt = bc->ch_params.slottime;
+				return 0;
+			}
+			if ((--bc->hdlctx.slotcnt) > 0)
+				return 0;
+			bc->hdlctx.slotcnt = bc->ch_params.slottime;
+			if ((prandom_u32() % 256) > bc->ch_params.ppersist)
+				return 0;
+		}
+	}
+	if (bc->hdlctx.state == tx_idle && bc->hdlctx.bufcnt > 0) {
+		bc->hdlctx.state = tx_keyup;
+		bc->hdlctx.flags = tenms_to_flags(bc, bc->ch_params.tx_delay);
+		bc->ptt_keyed++;
+	}
+	while (cnt > 0) {
+		switch (bc->hdlctx.state) {
+		case tx_keyup:
+			i = min_t(int, cnt, bc->hdlctx.flags);
+			cnt -= i;
+			bc->hdlctx.flags -= i;
+			if (bc->hdlctx.flags <= 0)
+				bc->hdlctx.state = tx_data;
+			memset(tmp, 0x7e, sizeof(tmp));
+			while (i > 0) {
+				j = (i > sizeof(tmp)) ? sizeof(tmp) : i;
+				if (j != pp->ops->epp_write_data(pp, tmp, j, 0))
+					return -1;
+				i -= j;
+			}
+			break;
+
+		case tx_data:
+			if (bc->hdlctx.bufcnt <= 0) {
+				encode_hdlc(bc);
+				if (bc->hdlctx.bufcnt <= 0) {
+					bc->hdlctx.state = tx_tail;
+					bc->hdlctx.flags = tenms_to_flags(bc, bc->ch_params.tx_tail);
+					break;
+				}
+			}
+			i = min_t(int, cnt, bc->hdlctx.bufcnt);
+			bc->hdlctx.bufcnt -= i;
+			cnt -= i;
+			if (i != pp->ops->epp_write_data(pp, bc->hdlctx.bufptr, i, 0))
+					return -1;
+			bc->hdlctx.bufptr += i;
+			break;
+			
+		case tx_tail:
+			encode_hdlc(bc);
+			if (bc->hdlctx.bufcnt > 0) {
+				bc->hdlctx.state = tx_data;
+				break;
+			}
+			i = min_t(int, cnt, bc->hdlctx.flags);
+			if (i) {
+				cnt -= i;
+				bc->hdlctx.flags -= i;
+				memset(tmp, 0x7e, sizeof(tmp));
+				while (i > 0) {
+					j = (i > sizeof(tmp)) ? sizeof(tmp) : i;
+					if (j != pp->ops->epp_write_data(pp, tmp, j, 0))
+						return -1;
+					i -= j;
+				}
+				break;
+			}
+
+		default:  /* fall through */
+			if (bc->hdlctx.calibrate <= 0)
+				return 0;
+			i = min_t(int, cnt, bc->hdlctx.calibrate);
+			cnt -= i;
+			bc->hdlctx.calibrate -= i;
+			memset(tmp, 0, sizeof(tmp));
+			while (i > 0) {
+				j = (i > sizeof(tmp)) ? sizeof(tmp) : i;
+				if (j != pp->ops->epp_write_data(pp, tmp, j, 0))
+					return -1;
+				i -= j;
+			}
+			break;
+		}
+	}
+	return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void do_rxpacket(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+	struct sk_buff *skb;
+	unsigned char *cp;
+	unsigned pktlen;
+
+	if (bc->hdlcrx.bufcnt < 4) 
+		return;
+	if (!check_crc_ccitt(bc->hdlcrx.buf, bc->hdlcrx.bufcnt)) 
+		return;
+	pktlen = bc->hdlcrx.bufcnt-2+1; /* KISS kludge */
+	if (!(skb = dev_alloc_skb(pktlen))) {
+		printk("%s: memory squeeze, dropping packet\n", dev->name);
+		dev->stats.rx_dropped++;
+		return;
+	}
+	cp = skb_put(skb, pktlen);
+	*cp++ = 0; /* KISS kludge */
+	memcpy(cp, bc->hdlcrx.buf, pktlen - 1);
+	skb->protocol = ax25_type_trans(skb, dev);
+	netif_rx(skb);
+	dev->stats.rx_packets++;
+}
+
+static int receive(struct net_device *dev, int cnt)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+	struct parport *pp = bc->pdev->port;
+        unsigned int bitbuf, notbitstream, bitstream, numbits, state;
+	unsigned char tmp[128];
+        unsigned char *cp;
+	int cnt2, ret = 0;
+	int j;
+        
+        numbits = bc->hdlcrx.numbits;
+	state = bc->hdlcrx.state;
+	bitstream = bc->hdlcrx.bitstream;
+	bitbuf = bc->hdlcrx.bitbuf;
+	while (cnt > 0) {
+		cnt2 = (cnt > sizeof(tmp)) ? sizeof(tmp) : cnt;
+		cnt -= cnt2;
+		if (cnt2 != pp->ops->epp_read_data(pp, tmp, cnt2, 0)) {
+			ret = -1;
+			break;
+		}
+		cp = tmp;
+		for (; cnt2 > 0; cnt2--, cp++) {
+			bitstream >>= 8;
+			bitstream |= (*cp) << 8;
+			bitbuf >>= 8;
+			bitbuf |= (*cp) << 8;
+			numbits += 8;
+			notbitstream = ~bitstream;
+			for (j = 0; j < 8; j++) {
+
+				/* flag or abort */
+			        if (unlikely(!(notbitstream & (0x0fc << j)))) {
+
+					/* abort received */
+					if (!(notbitstream & (0x1fc << j)))
+						state = 0;
+
+					/* flag received */
+					else if ((bitstream & (0x1fe << j)) == (0x0fc << j)) {
+						if (state)
+							do_rxpacket(dev);
+						bc->hdlcrx.bufcnt = 0;
+						bc->hdlcrx.bufptr = bc->hdlcrx.buf;
+						state = 1;
+						numbits = 7-j;
+					}
+				}
+
+				/* stuffed bit */
+				else if (unlikely((bitstream & (0x1f8 << j)) == (0xf8 << j))) {
+					numbits--;
+					bitbuf = (bitbuf & ((~0xff) << j)) | ((bitbuf & ~((~0xff) << j)) << 1);
+					}
+				}
+			while (state && numbits >= 8) {
+				if (bc->hdlcrx.bufcnt >= TXBUFFER_SIZE) {
+					state = 0;
+				} else {
+					*(bc->hdlcrx.bufptr)++ = bitbuf >> (16-numbits);
+					bc->hdlcrx.bufcnt++;
+					numbits -= 8;
+				}
+			}
+		}
+	}
+        bc->hdlcrx.numbits = numbits;
+	bc->hdlcrx.state = state;
+	bc->hdlcrx.bitstream = bitstream;
+	bc->hdlcrx.bitbuf = bitbuf;
+	return ret;
+}
+
+/* --------------------------------------------------------------------- */
+
+#ifdef __i386__
+#include <asm/msr.h>
+#define GETTICK(x)						\
+({								\
+	if (boot_cpu_has(X86_FEATURE_TSC))			\
+		x = (unsigned int)rdtsc();			\
+})
+#else /* __i386__ */
+#define GETTICK(x)
+#endif /* __i386__ */
+
+static void epp_bh(struct work_struct *work)
+{
+	struct net_device *dev;
+	struct baycom_state *bc;
+	struct parport *pp;
+	unsigned char stat;
+	unsigned char tmp[2];
+	unsigned int time1 = 0, time2 = 0, time3 = 0;
+	int cnt, cnt2;
+
+	bc = container_of(work, struct baycom_state, run_work.work);
+	dev = bc->dev;
+	if (!bc->work_running)
+		return;
+	baycom_int_freq(bc);
+	pp = bc->pdev->port;
+	/* update status */
+	if (pp->ops->epp_read_addr(pp, &stat, 1, 0) != 1)
+		goto epptimeout;
+	bc->stat = stat;
+	bc->debug_vals.last_pllcorr = stat;
+	GETTICK(time1);
+	if (bc->modem == EPP_FPGAEXTSTATUS) {
+		/* get input count */
+		tmp[0] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE|1;
+		if (pp->ops->epp_write_addr(pp, tmp, 1, 0) != 1)
+			goto epptimeout;
+		if (pp->ops->epp_read_addr(pp, tmp, 2, 0) != 2)
+			goto epptimeout;
+		cnt = tmp[0] | (tmp[1] << 8);
+		cnt &= 0x7fff;
+		/* get output count */
+		tmp[0] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE|2;
+		if (pp->ops->epp_write_addr(pp, tmp, 1, 0) != 1)
+			goto epptimeout;
+		if (pp->ops->epp_read_addr(pp, tmp, 2, 0) != 2)
+			goto epptimeout;
+		cnt2 = tmp[0] | (tmp[1] << 8);
+		cnt2 = 16384 - (cnt2 & 0x7fff);
+		/* return to normal */
+		tmp[0] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE;
+		if (pp->ops->epp_write_addr(pp, tmp, 1, 0) != 1)
+			goto epptimeout;
+		if (transmit(bc, cnt2, stat))
+			goto epptimeout;
+		GETTICK(time2);
+		if (receive(dev, cnt))
+			goto epptimeout;
+		if (pp->ops->epp_read_addr(pp, &stat, 1, 0) != 1)
+			goto epptimeout;
+		bc->stat = stat;
+	} else {
+		/* try to tx */
+		switch (stat & (EPP_NTAEF|EPP_NTHF)) {
+		case EPP_NTHF:
+			cnt = 2048 - 256;
+			break;
+		
+		case EPP_NTAEF:
+			cnt = 2048 - 1793;
+			break;
+		
+		case 0:
+			cnt = 0;
+			break;
+		
+		default:
+			cnt = 2048 - 1025;
+			break;
+		}
+		if (transmit(bc, cnt, stat))
+			goto epptimeout;
+		GETTICK(time2);
+		/* do receiver */
+		while ((stat & (EPP_NRAEF|EPP_NRHF)) != EPP_NRHF) {
+			switch (stat & (EPP_NRAEF|EPP_NRHF)) {
+			case EPP_NRAEF:
+				cnt = 1025;
+				break;
+
+			case 0:
+				cnt = 1793;
+				break;
+
+			default:
+				cnt = 256;
+				break;
+			}
+			if (receive(dev, cnt))
+				goto epptimeout;
+			if (pp->ops->epp_read_addr(pp, &stat, 1, 0) != 1)
+				goto epptimeout;
+		}
+		cnt = 0;
+		if (bc->bitrate < 50000)
+			cnt = 256;
+		else if (bc->bitrate < 100000)
+			cnt = 128;
+		while (cnt > 0 && stat & EPP_NREF) {
+			if (receive(dev, 1))
+				goto epptimeout;
+			cnt--;
+			if (pp->ops->epp_read_addr(pp, &stat, 1, 0) != 1)
+				goto epptimeout;
+		}
+	}
+	GETTICK(time3);
+#ifdef BAYCOM_DEBUG
+	bc->debug_vals.mod_cycles = time2 - time1;
+	bc->debug_vals.demod_cycles = time3 - time2;
+#endif /* BAYCOM_DEBUG */
+	schedule_delayed_work(&bc->run_work, 1);
+	if (!bc->skb)
+		netif_wake_queue(dev);
+	return;
+ epptimeout:
+	printk(KERN_ERR "%s: EPP timeout!\n", bc_drvname);
+}
+
+/* ---------------------------------------------------------------------- */
+/*
+ * ===================== network driver interface =========================
+ */
+
+static int baycom_send_packet(struct sk_buff *skb, struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return ax25_ip_xmit(skb);
+
+	if (skb->data[0] != 0) {
+		do_kiss_params(bc, skb->data, skb->len);
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+	if (bc->skb) {
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+	/* strip KISS byte */
+	if (skb->len >= HDLCDRV_MAXFLEN+1 || skb->len < 3) {
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+	netif_stop_queue(dev);
+	bc->skb = skb;
+	return NETDEV_TX_OK;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_set_mac_address(struct net_device *dev, void *addr)
+{
+	struct sockaddr *sa = (struct sockaddr *)addr;
+
+	/* addr is an AX.25 shifted ASCII mac address */
+	memcpy(dev->dev_addr, sa->sa_data, dev->addr_len); 
+	return 0;                                         
+}
+
+/* --------------------------------------------------------------------- */
+
+static void epp_wakeup(void *handle)
+{
+        struct net_device *dev = (struct net_device *)handle;
+        struct baycom_state *bc = netdev_priv(dev);
+
+        printk(KERN_DEBUG "baycom_epp: %s: why am I being woken up?\n", dev->name);
+        if (!parport_claim(bc->pdev))
+                printk(KERN_DEBUG "baycom_epp: %s: I'm broken.\n", dev->name);
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Open/initialize the board. This is called (in the current kernel)
+ * sometime after booting when the 'ifconfig' program is run.
+ *
+ * This routine should set everything up anew at each open, even
+ * registers that "should" only need to be set once at boot, so that
+ * there is non-reboot way to recover if something goes wrong.
+ */
+
+static int epp_open(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+        struct parport *pp = parport_find_base(dev->base_addr);
+	unsigned int i, j;
+	unsigned char tmp[128];
+	unsigned char stat;
+	unsigned long tstart;
+	struct pardev_cb par_cb;
+	
+        if (!pp) {
+                printk(KERN_ERR "%s: parport at 0x%lx unknown\n", bc_drvname, dev->base_addr);
+                return -ENXIO;
+        }
+#if 0
+        if (pp->irq < 0) {
+                printk(KERN_ERR "%s: parport at 0x%lx has no irq\n", bc_drvname, pp->base);
+		parport_put_port(pp);
+                return -ENXIO;
+        }
+#endif
+	if ((~pp->modes) & (PARPORT_MODE_TRISTATE | PARPORT_MODE_PCSPP | PARPORT_MODE_SAFEININT)) {
+                printk(KERN_ERR "%s: parport at 0x%lx cannot be used\n",
+		       bc_drvname, pp->base);
+		parport_put_port(pp);
+                return -EIO;
+	}
+	memset(&bc->modem, 0, sizeof(bc->modem));
+	memset(&par_cb, 0, sizeof(par_cb));
+	par_cb.wakeup = epp_wakeup;
+	par_cb.private = (void *)dev;
+	par_cb.flags = PARPORT_DEV_EXCL;
+	for (i = 0; i < NR_PORTS; i++)
+		if (baycom_device[i] == dev)
+			break;
+
+	if (i == NR_PORTS) {
+		pr_err("%s: no device found\n", bc_drvname);
+		parport_put_port(pp);
+		return -ENODEV;
+	}
+
+	bc->pdev = parport_register_dev_model(pp, dev->name, &par_cb, i);
+	parport_put_port(pp);
+        if (!bc->pdev) {
+                printk(KERN_ERR "%s: cannot register parport at 0x%lx\n", bc_drvname, pp->base);
+                return -ENXIO;
+        }
+        if (parport_claim(bc->pdev)) {
+                printk(KERN_ERR "%s: parport at 0x%lx busy\n", bc_drvname, pp->base);
+                parport_unregister_device(bc->pdev);
+                return -EBUSY;
+        }
+        dev->irq = /*pp->irq*/ 0;
+	INIT_DELAYED_WORK(&bc->run_work, epp_bh);
+	bc->work_running = 1;
+	bc->modem = EPP_CONVENTIONAL;
+	if (eppconfig(bc))
+		printk(KERN_INFO "%s: no FPGA detected, assuming conventional EPP modem\n", bc_drvname);
+	else
+		bc->modem = /*EPP_FPGA*/ EPP_FPGAEXTSTATUS;
+	parport_write_control(pp, LPTCTRL_PROGRAM); /* prepare EPP mode; we aren't using interrupts */
+	/* reset the modem */
+	tmp[0] = 0;
+	tmp[1] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE;
+	if (pp->ops->epp_write_addr(pp, tmp, 2, 0) != 2)
+		goto epptimeout;
+	/* autoprobe baud rate */
+	tstart = jiffies;
+	i = 0;
+	while (time_before(jiffies, tstart + HZ/3)) {
+		if (pp->ops->epp_read_addr(pp, &stat, 1, 0) != 1)
+			goto epptimeout;
+		if ((stat & (EPP_NRAEF|EPP_NRHF)) == EPP_NRHF) {
+			schedule();
+			continue;
+		}
+		if (pp->ops->epp_read_data(pp, tmp, 128, 0) != 128)
+			goto epptimeout;
+		if (pp->ops->epp_read_data(pp, tmp, 128, 0) != 128)
+			goto epptimeout;
+		i += 256;
+	}
+	for (j = 0; j < 256; j++) {
+		if (pp->ops->epp_read_addr(pp, &stat, 1, 0) != 1)
+			goto epptimeout;
+		if (!(stat & EPP_NREF))
+			break;
+		if (pp->ops->epp_read_data(pp, tmp, 1, 0) != 1)
+			goto epptimeout;
+		i++;
+	}
+	tstart = jiffies - tstart;
+	bc->bitrate = i * (8 * HZ) / tstart;
+	j = 1;
+	i = bc->bitrate >> 3;
+	while (j < 7 && i > 150) {
+		j++;
+		i >>= 1;
+	}
+	printk(KERN_INFO "%s: autoprobed bitrate: %d  int divider: %d  int rate: %d\n", 
+	       bc_drvname, bc->bitrate, j, bc->bitrate >> (j+2));
+	tmp[0] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE/*|j*/;
+	if (pp->ops->epp_write_addr(pp, tmp, 1, 0) != 1)
+		goto epptimeout;
+	/*
+	 * initialise hdlc variables
+	 */
+	bc->hdlcrx.state = 0;
+	bc->hdlcrx.numbits = 0;
+	bc->hdlctx.state = tx_idle;
+	bc->hdlctx.bufcnt = 0;
+	bc->hdlctx.slotcnt = bc->ch_params.slottime;
+	bc->hdlctx.calibrate = 0;
+	/* start the bottom half stuff */
+	schedule_delayed_work(&bc->run_work, 1);
+	netif_start_queue(dev);
+	return 0;
+
+ epptimeout:
+	printk(KERN_ERR "%s: epp timeout during bitrate probe\n", bc_drvname);
+	parport_write_control(pp, 0); /* reset the adapter */
+        parport_release(bc->pdev);
+        parport_unregister_device(bc->pdev);
+	return -EIO;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int epp_close(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+	struct parport *pp = bc->pdev->port;
+	unsigned char tmp[1];
+
+	bc->work_running = 0;
+	cancel_delayed_work_sync(&bc->run_work);
+	bc->stat = EPP_DCDBIT;
+	tmp[0] = 0;
+	pp->ops->epp_write_addr(pp, tmp, 1, 0);
+	parport_write_control(pp, 0); /* reset the adapter */
+        parport_release(bc->pdev);
+        parport_unregister_device(bc->pdev);
+	if (bc->skb)
+		dev_kfree_skb(bc->skb);
+	bc->skb = NULL;
+	printk(KERN_INFO "%s: close epp at iobase 0x%lx irq %u\n",
+	       bc_drvname, dev->base_addr, dev->irq);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_setmode(struct baycom_state *bc, const char *modestr)
+{
+	const char *cp;
+
+	if (strstr(modestr,"intclk"))
+		bc->cfg.intclk = 1;
+	if (strstr(modestr,"extclk"))
+		bc->cfg.intclk = 0;
+	if (strstr(modestr,"intmodem"))
+		bc->cfg.extmodem = 0;
+	if (strstr(modestr,"extmodem"))
+		bc->cfg.extmodem = 1;
+	if (strstr(modestr,"noloopback"))
+		bc->cfg.loopback = 0;
+	if (strstr(modestr,"loopback"))
+		bc->cfg.loopback = 1;
+	if ((cp = strstr(modestr,"fclk="))) {
+		bc->cfg.fclk = simple_strtoul(cp+5, NULL, 0);
+		if (bc->cfg.fclk < 1000000)
+			bc->cfg.fclk = 1000000;
+		if (bc->cfg.fclk > 25000000)
+			bc->cfg.fclk = 25000000;
+	}
+	if ((cp = strstr(modestr,"bps="))) {
+		bc->cfg.bps = simple_strtoul(cp+4, NULL, 0);
+		if (bc->cfg.bps < 1000)
+			bc->cfg.bps = 1000;
+		if (bc->cfg.bps > 1500000)
+			bc->cfg.bps = 1500000;
+	}
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+	struct hdlcdrv_ioctl hi;
+
+	if (cmd != SIOCDEVPRIVATE)
+		return -ENOIOCTLCMD;
+
+	if (copy_from_user(&hi, ifr->ifr_data, sizeof(hi)))
+		return -EFAULT;
+	switch (hi.cmd) {
+	default:
+		return -ENOIOCTLCMD;
+
+	case HDLCDRVCTL_GETCHANNELPAR:
+		hi.data.cp.tx_delay = bc->ch_params.tx_delay;
+		hi.data.cp.tx_tail = bc->ch_params.tx_tail;
+		hi.data.cp.slottime = bc->ch_params.slottime;
+		hi.data.cp.ppersist = bc->ch_params.ppersist;
+		hi.data.cp.fulldup = bc->ch_params.fulldup;
+		break;
+
+	case HDLCDRVCTL_SETCHANNELPAR:
+		if (!capable(CAP_NET_ADMIN))
+			return -EACCES;
+		bc->ch_params.tx_delay = hi.data.cp.tx_delay;
+		bc->ch_params.tx_tail = hi.data.cp.tx_tail;
+		bc->ch_params.slottime = hi.data.cp.slottime;
+		bc->ch_params.ppersist = hi.data.cp.ppersist;
+		bc->ch_params.fulldup = hi.data.cp.fulldup;
+		bc->hdlctx.slotcnt = 1;
+		return 0;
+		
+	case HDLCDRVCTL_GETMODEMPAR:
+		hi.data.mp.iobase = dev->base_addr;
+		hi.data.mp.irq = dev->irq;
+		hi.data.mp.dma = dev->dma;
+		hi.data.mp.dma2 = 0;
+		hi.data.mp.seriobase = 0;
+		hi.data.mp.pariobase = 0;
+		hi.data.mp.midiiobase = 0;
+		break;
+
+	case HDLCDRVCTL_SETMODEMPAR:
+		if ((!capable(CAP_SYS_RAWIO)) || netif_running(dev))
+			return -EACCES;
+		dev->base_addr = hi.data.mp.iobase;
+		dev->irq = /*hi.data.mp.irq*/0;
+		dev->dma = /*hi.data.mp.dma*/0;
+		return 0;	
+		
+	case HDLCDRVCTL_GETSTAT:
+		hi.data.cs.ptt = !!(bc->stat & EPP_PTTBIT);
+		hi.data.cs.dcd = !(bc->stat & EPP_DCDBIT);
+		hi.data.cs.ptt_keyed = bc->ptt_keyed;
+		hi.data.cs.tx_packets = dev->stats.tx_packets;
+		hi.data.cs.tx_errors = dev->stats.tx_errors;
+		hi.data.cs.rx_packets = dev->stats.rx_packets;
+		hi.data.cs.rx_errors = dev->stats.rx_errors;
+		break;		
+
+	case HDLCDRVCTL_OLDGETSTAT:
+		hi.data.ocs.ptt = !!(bc->stat & EPP_PTTBIT);
+		hi.data.ocs.dcd = !(bc->stat & EPP_DCDBIT);
+		hi.data.ocs.ptt_keyed = bc->ptt_keyed;
+		break;		
+
+	case HDLCDRVCTL_CALIBRATE:
+		if (!capable(CAP_SYS_RAWIO))
+			return -EACCES;
+		bc->hdlctx.calibrate = hi.data.calibrate * bc->bitrate / 8;
+		return 0;
+
+	case HDLCDRVCTL_DRIVERNAME:
+		strncpy(hi.data.drivername, "baycom_epp", sizeof(hi.data.drivername));
+		break;
+		
+	case HDLCDRVCTL_GETMODE:
+		sprintf(hi.data.modename, "%sclk,%smodem,fclk=%d,bps=%d%s", 
+			bc->cfg.intclk ? "int" : "ext",
+			bc->cfg.extmodem ? "ext" : "int", bc->cfg.fclk, bc->cfg.bps,
+			bc->cfg.loopback ? ",loopback" : "");
+		break;
+
+	case HDLCDRVCTL_SETMODE:
+		if (!capable(CAP_NET_ADMIN) || netif_running(dev))
+			return -EACCES;
+		hi.data.modename[sizeof(hi.data.modename)-1] = '\0';
+		return baycom_setmode(bc, hi.data.modename);
+
+	case HDLCDRVCTL_MODELIST:
+		strncpy(hi.data.modename, "intclk,extclk,intmodem,extmodem,divider=x",
+			sizeof(hi.data.modename));
+		break;
+
+	case HDLCDRVCTL_MODEMPARMASK:
+		return HDLCDRV_PARMASK_IOBASE;
+
+	}
+	if (copy_to_user(ifr->ifr_data, &hi, sizeof(hi)))
+		return -EFAULT;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static const struct net_device_ops baycom_netdev_ops = {
+	.ndo_open	     = epp_open,
+	.ndo_stop	     = epp_close,
+	.ndo_do_ioctl	     = baycom_ioctl,
+	.ndo_start_xmit      = baycom_send_packet,
+	.ndo_set_mac_address = baycom_set_mac_address,
+};
+
+/*
+ * Check for a network adaptor of this type, and return '0' if one exists.
+ * If dev->base_addr == 0, probe all likely locations.
+ * If dev->base_addr == 1, always return failure.
+ * If dev->base_addr == 2, allocate space for the device and return success
+ * (detachable devices only).
+ */
+static void baycom_probe(struct net_device *dev)
+{
+	const struct hdlcdrv_channel_params dflt_ch_params = { 
+		20, 2, 10, 40, 0 
+	};
+	struct baycom_state *bc;
+
+	/*
+	 * not a real probe! only initialize data structures
+	 */
+	bc = netdev_priv(dev);
+	/*
+	 * initialize the baycom_state struct
+	 */
+	bc->ch_params = dflt_ch_params;
+	bc->ptt_keyed = 0;
+
+	/*
+	 * initialize the device struct
+	 */
+
+	/* Fill in the fields of the device structure */
+	bc->skb = NULL;
+	
+	dev->netdev_ops = &baycom_netdev_ops;
+	dev->header_ops = &ax25_header_ops;
+	
+	dev->type = ARPHRD_AX25;           /* AF_AX25 device */
+	dev->hard_header_len = AX25_MAX_HEADER_LEN + AX25_BPQ_HEADER_LEN;
+	dev->mtu = AX25_DEF_PACLEN;        /* eth_mtu is the default */
+	dev->addr_len = AX25_ADDR_LEN;     /* sizeof an ax.25 address */
+	memcpy(dev->broadcast, &ax25_bcast, AX25_ADDR_LEN);
+	memcpy(dev->dev_addr, &null_ax25_address, AX25_ADDR_LEN);
+	dev->tx_queue_len = 16;
+
+	/* New style flags */
+	dev->flags = 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * command line settable parameters
+ */
+static char *mode[NR_PORTS] = { "", };
+static int iobase[NR_PORTS] = { 0x378, };
+
+module_param_array(mode, charp, NULL, 0);
+MODULE_PARM_DESC(mode, "baycom operating mode");
+module_param_hw_array(iobase, int, ioport, NULL, 0);
+MODULE_PARM_DESC(iobase, "baycom io base address");
+
+MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu");
+MODULE_DESCRIPTION("Baycom epp amateur radio modem driver");
+MODULE_LICENSE("GPL");
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_epp_par_probe(struct pardevice *par_dev)
+{
+	struct device_driver *drv = par_dev->dev.driver;
+	int len = strlen(drv->name);
+
+	if (strncmp(par_dev->name, drv->name, len))
+		return -ENODEV;
+
+	return 0;
+}
+
+static struct parport_driver baycom_epp_par_driver = {
+	.name = "bce",
+	.probe = baycom_epp_par_probe,
+	.devmodel = true,
+};
+
+static void __init baycom_epp_dev_setup(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+
+	/*
+	 * initialize part of the baycom_state struct
+	 */
+	bc->dev = dev;
+	bc->magic = BAYCOM_MAGIC;
+	bc->cfg.fclk = 19666600;
+	bc->cfg.bps = 9600;
+	/*
+	 * initialize part of the device struct
+	 */
+	baycom_probe(dev);
+}
+
+static int __init init_baycomepp(void)
+{
+	int i, found = 0, ret;
+	char set_hw = 1;
+
+	printk(bc_drvinfo);
+
+	ret = parport_register_driver(&baycom_epp_par_driver);
+	if (ret)
+		return ret;
+
+	/*
+	 * register net devices
+	 */
+	for (i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev;
+		
+		dev = alloc_netdev(sizeof(struct baycom_state), "bce%d",
+				   NET_NAME_UNKNOWN, baycom_epp_dev_setup);
+
+		if (!dev) {
+			printk(KERN_WARNING "bce%d : out of memory\n", i);
+			return found ? 0 : -ENOMEM;
+		}
+			
+		sprintf(dev->name, "bce%d", i);
+		dev->base_addr = iobase[i];
+
+		if (!mode[i])
+			set_hw = 0;
+		if (!set_hw)
+			iobase[i] = 0;
+
+		if (register_netdev(dev)) {
+			printk(KERN_WARNING "%s: cannot register net device %s\n", bc_drvname, dev->name);
+			free_netdev(dev);
+			break;
+		}
+		if (set_hw && baycom_setmode(netdev_priv(dev), mode[i]))
+			set_hw = 0;
+		baycom_device[i] = dev;
+		found++;
+	}
+
+	if (found == 0) {
+		parport_unregister_driver(&baycom_epp_par_driver);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static void __exit cleanup_baycomepp(void)
+{
+	int i;
+
+	for(i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev = baycom_device[i];
+
+		if (dev) {
+			struct baycom_state *bc = netdev_priv(dev);
+			if (bc->magic == BAYCOM_MAGIC) {
+				unregister_netdev(dev);
+				free_netdev(dev);
+			} else
+				printk(paranoia_str, "cleanup_module");
+		}
+	}
+	parport_unregister_driver(&baycom_epp_par_driver);
+}
+
+module_init(init_baycomepp);
+module_exit(cleanup_baycomepp);
+
+/* --------------------------------------------------------------------- */
+
+#ifndef MODULE
+
+/*
+ * format: baycom_epp=io,mode
+ * mode: fpga config options
+ */
+
+static int __init baycom_epp_setup(char *str)
+{
+        static unsigned __initdata nr_dev = 0;
+	int ints[2];
+
+        if (nr_dev >= NR_PORTS)
+                return 0;
+	str = get_options(str, 2, ints);
+	if (ints[0] < 1)
+		return 0;
+	mode[nr_dev] = str;
+	iobase[nr_dev] = ints[1];
+	nr_dev++;
+	return 1;
+}
+
+__setup("baycom_epp=", baycom_epp_setup);
+
+#endif /* MODULE */
+/* --------------------------------------------------------------------- */
diff --git a/drivers/net/hamradio/baycom_par.c b/drivers/net/hamradio/baycom_par.c
new file mode 100644
index 0000000..1f7ceaf
--- /dev/null
+++ b/drivers/net/hamradio/baycom_par.c
@@ -0,0 +1,615 @@
+/*****************************************************************************/
+
+/*
+ *	baycom_par.c  -- baycom par96 and picpar radio modem driver.
+ *
+ *	Copyright (C) 1996-2000  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.
+ *
+ *
+ *  Supported modems
+ *
+ *  par96:  This is a modem for 9600 baud FSK compatible to the G3RUH standard.
+ *          The modem does all the filtering and regenerates the receiver clock.
+ *          Data is transferred from and to the PC via a shift register.
+ *          The shift register is filled with 16 bits and an interrupt is
+ *          signalled. The PC then empties the shift register in a burst. This
+ *          modem connects to the parallel port, hence the name. The modem
+ *          leaves the implementation of the HDLC protocol and the scrambler
+ *          polynomial to the PC. This modem is no longer available (at least
+ *          from Baycom) and has been replaced by the PICPAR modem (see below).
+ *          You may however still build one from the schematics published in
+ *          cq-DL :-).
+ *
+ *  picpar: This is a redesign of the par96 modem by Henning Rech, DF9IC. The
+ *          modem is protocol compatible to par96, but uses only three low
+ *          power ICs and can therefore be fed from the parallel port and
+ *          does not require an additional power supply. It features
+ *          built in DCD circuitry. The driver should therefore be configured
+ *          for hardware DCD.
+ *
+ *
+ *  Command line options (insmod command line)
+ *
+ *  mode     driver mode string. Valid choices are par96 and picpar.
+ *  iobase   base address of the port; common values are 0x378, 0x278, 0x3bc
+ *
+ *
+ *  History:
+ *   0.1  26.06.1996  Adapted from baycom.c and made network driver interface
+ *        18.10.1996  Changed to new user space access routines (copy_{to,from}_user)
+ *   0.3  26.04.1997  init code/data tagged
+ *   0.4  08.07.1997  alternative ser12 decoding algorithm (uses delta CTS ints)
+ *   0.5  11.11.1997  split into separate files for ser12/par96
+ *   0.6  03.08.1999  adapt to Linus' new __setup/__initcall
+ *                    removed some pre-2.2 kernel compatibility cruft
+ *   0.7  10.08.1999  Check if parport can do SPP and is safe to access during interrupt contexts
+ *   0.8  12.02.2000  adapted to softnet driver interface
+ *                    removed direct parport access, uses parport driver methods
+ *   0.9  03.07.2000  fix interface name handling
+ */
+
+/*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.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 <linux/init.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/hdlcdrv.h>
+#include <linux/baycom.h>
+#include <linux/parport.h>
+#include <linux/bitops.h>
+#include <linux/jiffies.h>
+
+#include <linux/uaccess.h>
+
+/* --------------------------------------------------------------------- */
+
+#define BAYCOM_DEBUG
+
+/*
+ * modem options; bit mask
+ */
+#define BAYCOM_OPTIONS_SOFTDCD  1
+
+/* --------------------------------------------------------------------- */
+
+static const char bc_drvname[] = "baycom_par";
+static const char bc_drvinfo[] = KERN_INFO "baycom_par: (C) 1996-2000 Thomas Sailer, HB9JNX/AE4WA\n"
+"baycom_par: version 0.9\n";
+
+/* --------------------------------------------------------------------- */
+
+#define NR_PORTS 4
+
+static struct net_device *baycom_device[NR_PORTS];
+
+/* --------------------------------------------------------------------- */
+
+#define PAR96_BURSTBITS 16
+#define PAR96_BURST     4
+#define PAR96_PTT       2
+#define PAR96_TXBIT     1
+#define PAR96_ACK       0x40
+#define PAR96_RXBIT     0x20
+#define PAR96_DCD       0x10
+#define PAR97_POWER     0xf8
+
+/* ---------------------------------------------------------------------- */
+/*
+ * Information that need to be kept for each board.
+ */
+
+struct baycom_state {
+	struct hdlcdrv_state hdrv;
+
+	struct pardevice *pdev;
+	unsigned int options;
+
+	struct modem_state {
+		short arb_divider;
+		unsigned char flags;
+		unsigned int shreg;
+		struct modem_state_par96 {
+			int dcd_count;
+			unsigned int dcd_shreg;
+			unsigned long descram;
+			unsigned long scram;
+		} par96;
+	} modem;
+
+#ifdef BAYCOM_DEBUG
+	struct debug_vals {
+		unsigned long last_jiffies;
+		unsigned cur_intcnt;
+		unsigned last_intcnt;
+		int cur_pllcorr;
+		int last_pllcorr;
+	} debug_vals;
+#endif /* BAYCOM_DEBUG */
+};
+
+/* --------------------------------------------------------------------- */
+
+static inline void baycom_int_freq(struct baycom_state *bc)
+{
+#ifdef BAYCOM_DEBUG
+	unsigned long cur_jiffies = jiffies;
+	/*
+	 * measure the interrupt frequency
+	 */
+	bc->debug_vals.cur_intcnt++;
+	if (time_after_eq(cur_jiffies, bc->debug_vals.last_jiffies + HZ)) {
+		bc->debug_vals.last_jiffies = cur_jiffies;
+		bc->debug_vals.last_intcnt = bc->debug_vals.cur_intcnt;
+		bc->debug_vals.cur_intcnt = 0;
+		bc->debug_vals.last_pllcorr = bc->debug_vals.cur_pllcorr;
+		bc->debug_vals.cur_pllcorr = 0;
+	}
+#endif /* BAYCOM_DEBUG */
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== PAR96 specific routines =========================
+ */
+
+#define PAR96_DESCRAM_TAP1 0x20000
+#define PAR96_DESCRAM_TAP2 0x01000
+#define PAR96_DESCRAM_TAP3 0x00001
+
+#define PAR96_DESCRAM_TAPSH1 17
+#define PAR96_DESCRAM_TAPSH2 12
+#define PAR96_DESCRAM_TAPSH3 0
+
+#define PAR96_SCRAM_TAP1 0x20000 /* X^17 */
+#define PAR96_SCRAM_TAPN 0x00021 /* X^0+X^5 */
+
+/* --------------------------------------------------------------------- */
+
+static inline void par96_tx(struct net_device *dev, struct baycom_state *bc)
+{
+	int i;
+	unsigned int data = hdlcdrv_getbits(&bc->hdrv);
+	struct parport *pp = bc->pdev->port;
+
+	for(i = 0; i < PAR96_BURSTBITS; i++, data >>= 1) {
+		unsigned char val = PAR97_POWER;
+		bc->modem.par96.scram = ((bc->modem.par96.scram << 1) |
+					 (bc->modem.par96.scram & 1));
+		if (!(data & 1))
+			bc->modem.par96.scram ^= 1;
+		if (bc->modem.par96.scram & (PAR96_SCRAM_TAP1 << 1))
+			bc->modem.par96.scram ^=
+				(PAR96_SCRAM_TAPN << 1);
+		if (bc->modem.par96.scram & (PAR96_SCRAM_TAP1 << 2))
+			val |= PAR96_TXBIT;
+		pp->ops->write_data(pp, val);
+		pp->ops->write_data(pp, val | PAR96_BURST);
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static inline void par96_rx(struct net_device *dev, struct baycom_state *bc)
+{
+	int i;
+	unsigned int data, mask, mask2, descx;
+	struct parport *pp = bc->pdev->port;
+
+	/*
+	 * do receiver; differential decode and descramble on the fly
+	 */
+	for(data = i = 0; i < PAR96_BURSTBITS; i++) {
+		bc->modem.par96.descram = (bc->modem.par96.descram << 1);
+		if (pp->ops->read_status(pp) & PAR96_RXBIT)
+			bc->modem.par96.descram |= 1;
+		descx = bc->modem.par96.descram ^
+			(bc->modem.par96.descram >> 1);
+		/* now the diff decoded data is inverted in descram */
+		pp->ops->write_data(pp, PAR97_POWER | PAR96_PTT);
+		descx ^= ((descx >> PAR96_DESCRAM_TAPSH1) ^
+			  (descx >> PAR96_DESCRAM_TAPSH2));
+		data >>= 1;
+		if (!(descx & 1))
+			data |= 0x8000;
+		pp->ops->write_data(pp, PAR97_POWER | PAR96_PTT | PAR96_BURST);
+	}
+	hdlcdrv_putbits(&bc->hdrv, data);
+	/*
+	 * do DCD algorithm
+	 */
+	if (bc->options & BAYCOM_OPTIONS_SOFTDCD) {
+		bc->modem.par96.dcd_shreg = (bc->modem.par96.dcd_shreg >> 16)
+			| (data << 16);
+		/* search for flags and set the dcd counter appropriately */
+		for(mask = 0x1fe00, mask2 = 0xfc00, i = 0;
+		    i < PAR96_BURSTBITS; i++, mask <<= 1, mask2 <<= 1)
+			if ((bc->modem.par96.dcd_shreg & mask) == mask2)
+				bc->modem.par96.dcd_count = HDLCDRV_MAXFLEN+4;
+		/* check for abort/noise sequences */
+		for(mask = 0x1fe00, mask2 = 0x1fe00, i = 0;
+		    i < PAR96_BURSTBITS; i++, mask <<= 1, mask2 <<= 1)
+			if (((bc->modem.par96.dcd_shreg & mask) == mask2) &&
+			    (bc->modem.par96.dcd_count >= 0))
+				bc->modem.par96.dcd_count -= HDLCDRV_MAXFLEN-10;
+		/* decrement and set the dcd variable */
+		if (bc->modem.par96.dcd_count >= 0)
+			bc->modem.par96.dcd_count -= 2;
+		hdlcdrv_setdcd(&bc->hdrv, bc->modem.par96.dcd_count > 0);
+	} else {
+		hdlcdrv_setdcd(&bc->hdrv, !!(pp->ops->read_status(pp) & PAR96_DCD));
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static void par96_interrupt(void *dev_id)
+{
+	struct net_device *dev = dev_id;
+	struct baycom_state *bc = netdev_priv(dev);
+
+	baycom_int_freq(bc);
+	/*
+	 * check if transmitter active
+	 */
+	if (hdlcdrv_ptt(&bc->hdrv))
+		par96_tx(dev, bc);
+	else {
+		par96_rx(dev, bc);
+		if (--bc->modem.arb_divider <= 0) {
+			bc->modem.arb_divider = 6;
+			local_irq_enable();
+			hdlcdrv_arbitrate(dev, &bc->hdrv);
+		}
+	}
+	local_irq_enable();
+	hdlcdrv_transmitter(dev, &bc->hdrv);
+	hdlcdrv_receiver(dev, &bc->hdrv);
+        local_irq_disable();
+}
+
+/* --------------------------------------------------------------------- */
+
+static void par96_wakeup(void *handle)
+{
+        struct net_device *dev = (struct net_device *)handle;
+	struct baycom_state *bc = netdev_priv(dev);
+
+	printk(KERN_DEBUG "baycom_par: %s: why am I being woken up?\n", dev->name);
+	if (!parport_claim(bc->pdev))
+		printk(KERN_DEBUG "baycom_par: %s: I'm broken.\n", dev->name);
+}
+
+/* --------------------------------------------------------------------- */
+
+static int par96_open(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+	struct pardev_cb par_cb;
+	struct parport *pp;
+	int i;
+
+	if (!dev || !bc)
+		return -ENXIO;
+	pp = parport_find_base(dev->base_addr);
+	if (!pp) {
+		printk(KERN_ERR "baycom_par: parport at 0x%lx unknown\n", dev->base_addr);
+		return -ENXIO;
+	}
+	if (pp->irq < 0) {
+		printk(KERN_ERR "baycom_par: parport at 0x%lx has no irq\n", pp->base);
+		parport_put_port(pp);
+		return -ENXIO;
+	}
+	if ((~pp->modes) & (PARPORT_MODE_PCSPP | PARPORT_MODE_SAFEININT)) {
+		printk(KERN_ERR "baycom_par: parport at 0x%lx cannot be used\n", pp->base);
+		parport_put_port(pp);
+		return -ENXIO;
+	}
+	memset(&bc->modem, 0, sizeof(bc->modem));
+	bc->hdrv.par.bitrate = 9600;
+	memset(&par_cb, 0, sizeof(par_cb));
+	par_cb.wakeup = par96_wakeup;
+	par_cb.irq_func = par96_interrupt;
+	par_cb.private = (void *)dev;
+	par_cb.flags = PARPORT_DEV_EXCL;
+	for (i = 0; i < NR_PORTS; i++)
+		if (baycom_device[i] == dev)
+			break;
+
+	if (i == NR_PORTS) {
+		pr_err("%s: no device found\n", bc_drvname);
+		parport_put_port(pp);
+		return -ENODEV;
+	}
+	bc->pdev = parport_register_dev_model(pp, dev->name, &par_cb, i);
+	parport_put_port(pp);
+	if (!bc->pdev) {
+		printk(KERN_ERR "baycom_par: cannot register parport at 0x%lx\n", dev->base_addr);
+		return -ENXIO;
+	}
+	if (parport_claim(bc->pdev)) {
+		printk(KERN_ERR "baycom_par: parport at 0x%lx busy\n", pp->base);
+		parport_unregister_device(bc->pdev);
+		return -EBUSY;
+	}
+	pp = bc->pdev->port;
+	dev->irq = pp->irq;
+	pp->ops->data_forward(pp);
+        bc->hdrv.par.bitrate = 9600;
+	pp->ops->write_data(pp, PAR96_PTT | PAR97_POWER); /* switch off PTT */
+	pp->ops->enable_irq(pp);
+	printk(KERN_INFO "%s: par96 at iobase 0x%lx irq %u options 0x%x\n",
+	       bc_drvname, dev->base_addr, dev->irq, bc->options);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int par96_close(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+	struct parport *pp;
+
+	if (!dev || !bc)
+		return -EINVAL;
+	pp = bc->pdev->port;
+	/* disable interrupt */
+	pp->ops->disable_irq(pp);
+	/* switch off PTT */
+	pp->ops->write_data(pp, PAR96_PTT | PAR97_POWER);
+	parport_release(bc->pdev);
+	parport_unregister_device(bc->pdev);
+	printk(KERN_INFO "%s: close par96 at iobase 0x%lx irq %u\n",
+	       bc_drvname, dev->base_addr, dev->irq);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== hdlcdrv driver interface =========================
+ */
+
+static int baycom_ioctl(struct net_device *dev, struct ifreq *ifr,
+			struct hdlcdrv_ioctl *hi, int cmd);
+
+/* --------------------------------------------------------------------- */
+
+static const struct hdlcdrv_ops par96_ops = {
+	.drvname = bc_drvname,
+	.drvinfo = bc_drvinfo,
+	.open    = par96_open,
+	.close   = par96_close,
+	.ioctl   = baycom_ioctl
+};
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_setmode(struct baycom_state *bc, const char *modestr)
+{
+	if (!strncmp(modestr, "picpar", 6))
+		bc->options = 0;
+	else if (!strncmp(modestr, "par96", 5))
+		bc->options = BAYCOM_OPTIONS_SOFTDCD;
+	else
+		bc->options = !!strchr(modestr, '*');
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_ioctl(struct net_device *dev, struct ifreq *ifr,
+			struct hdlcdrv_ioctl *hi, int cmd)
+{
+	struct baycom_state *bc;
+	struct baycom_ioctl bi;
+
+	if (!dev)
+		return -EINVAL;
+
+	bc = netdev_priv(dev);
+	BUG_ON(bc->hdrv.magic != HDLCDRV_MAGIC);
+
+	if (cmd != SIOCDEVPRIVATE)
+		return -ENOIOCTLCMD;
+	switch (hi->cmd) {
+	default:
+		break;
+
+	case HDLCDRVCTL_GETMODE:
+		strcpy(hi->data.modename, bc->options ? "par96" : "picpar");
+		if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl)))
+			return -EFAULT;
+		return 0;
+
+	case HDLCDRVCTL_SETMODE:
+		if (netif_running(dev) || !capable(CAP_NET_ADMIN))
+			return -EACCES;
+		hi->data.modename[sizeof(hi->data.modename)-1] = '\0';
+		return baycom_setmode(bc, hi->data.modename);
+
+	case HDLCDRVCTL_MODELIST:
+		strcpy(hi->data.modename, "par96,picpar");
+		if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl)))
+			return -EFAULT;
+		return 0;
+
+	case HDLCDRVCTL_MODEMPARMASK:
+		return HDLCDRV_PARMASK_IOBASE;
+
+	}
+
+	if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi)))
+		return -EFAULT;
+	switch (bi.cmd) {
+	default:
+		return -ENOIOCTLCMD;
+
+#ifdef BAYCOM_DEBUG
+	case BAYCOMCTL_GETDEBUG:
+		bi.data.dbg.debug1 = bc->hdrv.ptt_keyed;
+		bi.data.dbg.debug2 = bc->debug_vals.last_intcnt;
+		bi.data.dbg.debug3 = bc->debug_vals.last_pllcorr;
+		break;
+#endif /* BAYCOM_DEBUG */
+
+	}
+	if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi)))
+		return -EFAULT;
+	return 0;
+
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * command line settable parameters
+ */
+static char *mode[NR_PORTS] = { "picpar", };
+static int iobase[NR_PORTS] = { 0x378, };
+
+module_param_array(mode, charp, NULL, 0);
+MODULE_PARM_DESC(mode, "baycom operating mode; eg. par96 or picpar");
+module_param_hw_array(iobase, int, ioport, NULL, 0);
+MODULE_PARM_DESC(iobase, "baycom io base address");
+
+MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu");
+MODULE_DESCRIPTION("Baycom par96 and picpar amateur radio modem driver");
+MODULE_LICENSE("GPL");
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_par_probe(struct pardevice *par_dev)
+{
+	struct device_driver *drv = par_dev->dev.driver;
+	int len = strlen(drv->name);
+
+	if (strncmp(par_dev->name, drv->name, len))
+		return -ENODEV;
+
+	return 0;
+}
+
+static struct parport_driver baycom_par_driver = {
+	.name = "bcp",
+	.probe = baycom_par_probe,
+	.devmodel = true,
+};
+
+static int __init init_baycompar(void)
+{
+	int i, found = 0, ret;
+	char set_hw = 1;
+
+	printk(bc_drvinfo);
+
+	ret = parport_register_driver(&baycom_par_driver);
+	if (ret)
+		return ret;
+
+	/*
+	 * register net devices
+	 */
+	for (i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev;
+		struct baycom_state *bc;
+		char ifname[IFNAMSIZ];
+
+		sprintf(ifname, "bcp%d", i);
+
+		if (!mode[i])
+			set_hw = 0;
+		if (!set_hw)
+			iobase[i] = 0;
+
+		dev = hdlcdrv_register(&par96_ops,
+				       sizeof(struct baycom_state),
+				       ifname, iobase[i], 0, 0);
+		if (IS_ERR(dev)) 
+			break;
+
+		bc = netdev_priv(dev);
+		if (set_hw && baycom_setmode(bc, mode[i]))
+			set_hw = 0;
+		found++;
+		baycom_device[i] = dev;
+	}
+
+	if (!found) {
+		parport_unregister_driver(&baycom_par_driver);
+		return -ENXIO;
+	}
+	return 0;
+}
+
+static void __exit cleanup_baycompar(void)
+{
+	int i;
+
+	for(i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev = baycom_device[i];
+
+		if (dev)
+			hdlcdrv_unregister(dev);
+	}
+	parport_unregister_driver(&baycom_par_driver);
+}
+
+module_init(init_baycompar);
+module_exit(cleanup_baycompar);
+
+/* --------------------------------------------------------------------- */
+
+#ifndef MODULE
+
+/*
+ * format: baycom_par=io,mode
+ * mode: par96,picpar
+ */
+
+static int __init baycom_par_setup(char *str)
+{
+        static unsigned nr_dev;
+	int ints[2];
+
+        if (nr_dev >= NR_PORTS)
+                return 0;
+        str = get_options(str, 2, ints);
+        if (ints[0] < 1)
+                return 0;
+        mode[nr_dev] = str;
+        iobase[nr_dev] = ints[1];
+	nr_dev++;
+	return 1;
+}
+
+__setup("baycom_par=", baycom_par_setup);
+
+#endif /* MODULE */
+/* --------------------------------------------------------------------- */
diff --git a/drivers/net/hamradio/baycom_ser_fdx.c b/drivers/net/hamradio/baycom_ser_fdx.c
new file mode 100644
index 0000000..190f66c
--- /dev/null
+++ b/drivers/net/hamradio/baycom_ser_fdx.c
@@ -0,0 +1,718 @@
+/*****************************************************************************/
+
+/*
+ *	baycom_ser_fdx.c  -- baycom ser12 fullduplex radio modem driver.
+ *
+ *	Copyright (C) 1996-2000  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.
+ *
+ *
+ *  Supported modems
+ *
+ *  ser12:  This is a very simple 1200 baud AFSK modem. The modem consists only
+ *          of a modulator/demodulator chip, usually a TI TCM3105. The computer
+ *          is responsible for regenerating the receiver bit clock, as well as
+ *          for handling the HDLC protocol. The modem connects to a serial port,
+ *          hence the name. Since the serial port is not used as an async serial
+ *          port, the kernel driver for serial ports cannot be used, and this
+ *          driver only supports standard serial hardware (8250, 16450, 16550A)
+ *
+ *          This modem usually draws its supply current out of the otherwise unused
+ *          TXD pin of the serial port. Thus a contiguous stream of 0x00-bytes
+ *          is transmitted to achieve a positive supply voltage.
+ *
+ *  hsk:    This is a 4800 baud FSK modem, designed for TNC use. It works fine
+ *          in 'baycom-mode' :-)  In contrast to the TCM3105 modem, power is
+ *          externally supplied. So there's no need to provide the 0x00-byte-stream
+ *          when receiving or idle, which drastically reduces interrupt load.
+ *
+ *  Command line options (insmod command line)
+ *
+ *  mode     ser#    hardware DCD
+ *           ser#*   software DCD
+ *           ser#+   hardware DCD, inverted signal at DCD pin
+ *           '#' denotes the baud rate / 100, eg. ser12* is '1200 baud, soft DCD'
+ *  iobase   base address of the port; common values are 0x3f8, 0x2f8, 0x3e8, 0x2e8
+ *  baud     baud rate (between 300 and 4800)
+ *  irq      interrupt line of the port; common values are 4,3
+ *
+ *
+ *  History:
+ *   0.1  26.06.1996  Adapted from baycom.c and made network driver interface
+ *        18.10.1996  Changed to new user space access routines (copy_{to,from}_user)
+ *   0.3  26.04.1997  init code/data tagged
+ *   0.4  08.07.1997  alternative ser12 decoding algorithm (uses delta CTS ints)
+ *   0.5  11.11.1997  ser12/par96 split into separate files
+ *   0.6  24.01.1998  Thorsten Kranzkowski, dl8bcu and Thomas Sailer:
+ *                    reduced interrupt load in transmit case
+ *                    reworked receiver
+ *   0.7  03.08.1999  adapt to Linus' new __setup/__initcall
+ *   0.8  10.08.1999  use module_init/module_exit
+ *   0.9  12.02.2000  adapted to softnet driver interface
+ *   0.10 03.07.2000  fix interface name handling
+ */
+
+/*****************************************************************************/
+
+#include <linux/capability.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/hdlcdrv.h>
+#include <linux/baycom.h>
+#include <linux/jiffies.h>
+#include <linux/time64.h>
+
+#include <linux/uaccess.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+/* --------------------------------------------------------------------- */
+
+#define BAYCOM_DEBUG
+
+/* --------------------------------------------------------------------- */
+
+static const char bc_drvname[] = "baycom_ser_fdx";
+static const char bc_drvinfo[] = KERN_INFO "baycom_ser_fdx: (C) 1996-2000 Thomas Sailer, HB9JNX/AE4WA\n"
+"baycom_ser_fdx: version 0.10\n";
+
+/* --------------------------------------------------------------------- */
+
+#define NR_PORTS 4
+
+static struct net_device *baycom_device[NR_PORTS];
+
+/* --------------------------------------------------------------------- */
+
+#define RBR(iobase) (iobase+0)
+#define THR(iobase) (iobase+0)
+#define IER(iobase) (iobase+1)
+#define IIR(iobase) (iobase+2)
+#define FCR(iobase) (iobase+2)
+#define LCR(iobase) (iobase+3)
+#define MCR(iobase) (iobase+4)
+#define LSR(iobase) (iobase+5)
+#define MSR(iobase) (iobase+6)
+#define SCR(iobase) (iobase+7)
+#define DLL(iobase) (iobase+0)
+#define DLM(iobase) (iobase+1)
+
+#define SER12_EXTENT 8
+
+/* ---------------------------------------------------------------------- */
+/*
+ * Information that need to be kept for each board.
+ */
+
+struct baycom_state {
+	struct hdlcdrv_state hdrv;
+
+	unsigned int baud, baud_us, baud_arbdiv, baud_uartdiv, baud_dcdtimeout;
+	int opt_dcd;
+
+	struct modem_state {
+		unsigned char flags;
+		unsigned char ptt;
+		unsigned int shreg;
+		struct modem_state_ser12 {
+			unsigned char tx_bit;
+			unsigned char last_rxbit;
+			int dcd_sum0, dcd_sum1, dcd_sum2;
+			int dcd_time;
+			unsigned int pll_time;
+			unsigned int txshreg;
+		} ser12;
+	} modem;
+
+#ifdef BAYCOM_DEBUG
+	struct debug_vals {
+		unsigned long last_jiffies;
+		unsigned cur_intcnt;
+		unsigned last_intcnt;
+		int cur_pllcorr;
+		int last_pllcorr;
+	} debug_vals;
+#endif /* BAYCOM_DEBUG */
+};
+
+/* --------------------------------------------------------------------- */
+
+static inline void baycom_int_freq(struct baycom_state *bc)
+{
+#ifdef BAYCOM_DEBUG
+	unsigned long cur_jiffies = jiffies;
+	/*
+	 * measure the interrupt frequency
+	 */
+	bc->debug_vals.cur_intcnt++;
+	if (time_after_eq(cur_jiffies, bc->debug_vals.last_jiffies + HZ)) {
+		bc->debug_vals.last_jiffies = cur_jiffies;
+		bc->debug_vals.last_intcnt = bc->debug_vals.cur_intcnt;
+		bc->debug_vals.cur_intcnt = 0;
+		bc->debug_vals.last_pllcorr = bc->debug_vals.cur_pllcorr;
+		bc->debug_vals.cur_pllcorr = 0;
+	}
+#endif /* BAYCOM_DEBUG */
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== SER12 specific routines =========================
+ */
+
+/* --------------------------------------------------------------------- */
+
+static inline void ser12_set_divisor(struct net_device *dev,
+                                     unsigned int divisor)
+{
+        outb(0x81, LCR(dev->base_addr));        /* DLAB = 1 */
+        outb(divisor, DLL(dev->base_addr));
+        outb(divisor >> 8, DLM(dev->base_addr));
+        outb(0x01, LCR(dev->base_addr));        /* word length = 6 */
+        /*
+         * make sure the next interrupt is generated;
+         * 0 must be used to power the modem; the modem draws its
+         * power from the TxD line
+         */
+        outb(0x00, THR(dev->base_addr));
+        /*
+         * it is important not to set the divider while transmitting;
+         * this reportedly makes some UARTs generating interrupts
+         * in the hundredthousands per second region
+         * Reported by: Ignacio.Arenaza@studi.epfl.ch (Ignacio Arenaza Nuno)
+         */
+}
+
+/* --------------------------------------------------------------------- */
+
+#if 0
+static inline unsigned int hweight16(unsigned int w)
+        __attribute__ ((unused));
+static inline unsigned int hweight8(unsigned int w)
+        __attribute__ ((unused));
+
+static inline unsigned int hweight16(unsigned int 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 int w)
+{
+        unsigned short res = (w & 0x55) + ((w >> 1) & 0x55);
+        res = (res & 0x33) + ((res >> 2) & 0x33);
+        return (res & 0x0F) + ((res >> 4) & 0x0F);
+}
+#endif
+
+/* --------------------------------------------------------------------- */
+
+static __inline__ void ser12_rx(struct net_device *dev, struct baycom_state *bc, struct timespec64 *ts, unsigned char curs)
+{
+	int timediff;
+	int bdus8 = bc->baud_us >> 3;
+	int bdus4 = bc->baud_us >> 2;
+	int bdus2 = bc->baud_us >> 1;
+
+	timediff = 1000000 + ts->tv_nsec / NSEC_PER_USEC -
+					bc->modem.ser12.pll_time;
+	while (timediff >= 500000)
+		timediff -= 1000000;
+	while (timediff >= bdus2) {
+		timediff -= bc->baud_us;
+		bc->modem.ser12.pll_time += bc->baud_us;
+		bc->modem.ser12.dcd_time--;
+		/* first check if there is room to add a bit */
+		if (bc->modem.shreg & 1) {
+			hdlcdrv_putbits(&bc->hdrv, (bc->modem.shreg >> 1) ^ 0xffff);
+			bc->modem.shreg = 0x10000;
+		}
+		/* add a one bit */
+		bc->modem.shreg >>= 1;
+	}
+	if (bc->modem.ser12.dcd_time <= 0) {
+		if (!bc->opt_dcd)
+			hdlcdrv_setdcd(&bc->hdrv, (bc->modem.ser12.dcd_sum0 + 
+						   bc->modem.ser12.dcd_sum1 + 
+						   bc->modem.ser12.dcd_sum2) < 0);
+		bc->modem.ser12.dcd_sum2 = bc->modem.ser12.dcd_sum1;
+		bc->modem.ser12.dcd_sum1 = bc->modem.ser12.dcd_sum0;
+		bc->modem.ser12.dcd_sum0 = 2; /* slight bias */
+		bc->modem.ser12.dcd_time += 120;
+	}
+	if (bc->modem.ser12.last_rxbit != curs) {
+		bc->modem.ser12.last_rxbit = curs;
+		bc->modem.shreg |= 0x10000;
+		/* adjust the PLL */
+		if (timediff > 0)
+			bc->modem.ser12.pll_time += bdus8;
+		else
+			bc->modem.ser12.pll_time += 1000000 - bdus8;
+		/* update DCD */
+		if (abs(timediff) > bdus4)
+			bc->modem.ser12.dcd_sum0 += 4;
+		else
+			bc->modem.ser12.dcd_sum0--;
+#ifdef BAYCOM_DEBUG
+		bc->debug_vals.cur_pllcorr = timediff;
+#endif /* BAYCOM_DEBUG */
+	}
+	while (bc->modem.ser12.pll_time >= 1000000)
+		bc->modem.ser12.pll_time -= 1000000;
+}
+
+/* --------------------------------------------------------------------- */
+
+static irqreturn_t ser12_interrupt(int irq, void *dev_id)
+{
+	struct net_device *dev = (struct net_device *)dev_id;
+	struct baycom_state *bc = netdev_priv(dev);
+	struct timespec64 ts;
+	unsigned char iir, msr;
+	unsigned int txcount = 0;
+
+	if (!bc || bc->hdrv.magic != HDLCDRV_MAGIC)
+		return IRQ_NONE;
+	/* fast way out for shared irq */
+	if ((iir = inb(IIR(dev->base_addr))) & 1) 	
+		return IRQ_NONE;
+	/* get current time */
+	ktime_get_ts64(&ts);
+	msr = inb(MSR(dev->base_addr));
+	/* delta DCD */
+	if ((msr & 8) && bc->opt_dcd)
+		hdlcdrv_setdcd(&bc->hdrv, !((msr ^ bc->opt_dcd) & 0x80));
+	do {
+		switch (iir & 6) {
+		case 6:
+			inb(LSR(dev->base_addr));
+			break;
+			
+		case 4:
+			inb(RBR(dev->base_addr));
+			break;
+			
+		case 2:
+			/*
+			 * make sure the next interrupt is generated;
+			 * 0 must be used to power the modem; the modem draws its
+			 * power from the TxD line
+			 */
+			outb(0x00, THR(dev->base_addr));
+			baycom_int_freq(bc);
+			txcount++;
+			/*
+			 * first output the last bit (!) then call HDLC transmitter,
+			 * since this may take quite long
+			 */
+			if (bc->modem.ptt)
+				outb(0x0e | (!!bc->modem.ser12.tx_bit), MCR(dev->base_addr));
+			else
+				outb(0x0d, MCR(dev->base_addr));       /* transmitter off */
+			break;
+			
+		default:
+			msr = inb(MSR(dev->base_addr));
+			/* delta DCD */
+			if ((msr & 8) && bc->opt_dcd) 
+				hdlcdrv_setdcd(&bc->hdrv, !((msr ^ bc->opt_dcd) & 0x80));
+			break;
+		}
+		iir = inb(IIR(dev->base_addr));
+	} while (!(iir & 1));
+	ser12_rx(dev, bc, &ts, msr & 0x10); /* CTS */
+	if (bc->modem.ptt && txcount) {
+		if (bc->modem.ser12.txshreg <= 1) {
+			bc->modem.ser12.txshreg = 0x10000 | hdlcdrv_getbits(&bc->hdrv);
+			if (!hdlcdrv_ptt(&bc->hdrv)) {
+				ser12_set_divisor(dev, 115200/100/8);
+				bc->modem.ptt = 0;
+				goto end_transmit;
+			}
+		}
+		bc->modem.ser12.tx_bit = !(bc->modem.ser12.tx_bit ^ (bc->modem.ser12.txshreg & 1));
+		bc->modem.ser12.txshreg >>= 1;
+	}
+ end_transmit:
+	local_irq_enable();
+	if (!bc->modem.ptt && txcount) {
+		hdlcdrv_arbitrate(dev, &bc->hdrv);
+		if (hdlcdrv_ptt(&bc->hdrv)) {
+			ser12_set_divisor(dev, bc->baud_uartdiv);
+			bc->modem.ser12.txshreg = 1;
+			bc->modem.ptt = 1;
+		}
+	}
+	hdlcdrv_transmitter(dev, &bc->hdrv);
+	hdlcdrv_receiver(dev, &bc->hdrv);
+	local_irq_disable();
+	return IRQ_HANDLED;
+}
+
+/* --------------------------------------------------------------------- */
+
+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 ser12_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 };
+
+	b1 = inb(MCR(iobase));
+	outb(b1 | 0x10, MCR(iobase));	/* loopback mode */
+	b2 = inb(MSR(iobase));
+	outb(0x1a, MCR(iobase));
+	b3 = inb(MSR(iobase)) & 0xf0;
+	outb(b1, MCR(iobase));			/* restore old values */
+	outb(b2, MSR(iobase));
+	if (b3 != 0x90)
+		return c_uart_unknown;
+	inb(RBR(iobase));
+	inb(RBR(iobase));
+	outb(0x01, FCR(iobase));		/* enable FIFOs */
+	u = uart_tab[(inb(IIR(iobase)) >> 6) & 3];
+	if (u == c_uart_16450) {
+		outb(0x5a, SCR(iobase));
+		b1 = inb(SCR(iobase));
+		outb(0xa5, SCR(iobase));
+		b2 = inb(SCR(iobase));
+		if ((b1 != 0x5a) || (b2 != 0xa5))
+			u = c_uart_8250;
+	}
+	return u;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int ser12_open(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+	enum uart u;
+
+	if (!dev || !bc)
+		return -ENXIO;
+	if (!dev->base_addr || dev->base_addr > 0xffff-SER12_EXTENT ||
+	    dev->irq < 2 || dev->irq > nr_irqs) {
+		printk(KERN_INFO "baycom_ser_fdx: invalid portnumber (max %u) "
+				"or irq (2 <= irq <= %d)\n",
+				0xffff-SER12_EXTENT, nr_irqs);
+		return -ENXIO;
+	}
+	if (bc->baud < 300 || bc->baud > 4800) {
+		printk(KERN_INFO "baycom_ser_fdx: invalid baudrate "
+				"(300...4800)\n");
+		return -EINVAL;
+	}
+	if (!request_region(dev->base_addr, SER12_EXTENT, "baycom_ser_fdx")) {
+		printk(KERN_WARNING "BAYCOM_SER_FSX: I/O port 0x%04lx busy\n",
+		       dev->base_addr);
+		return -EACCES;
+	}
+	memset(&bc->modem, 0, sizeof(bc->modem));
+	bc->hdrv.par.bitrate = bc->baud;
+	bc->baud_us = 1000000/bc->baud;
+	bc->baud_uartdiv = (115200/8)/bc->baud;
+	if ((u = ser12_check_uart(dev->base_addr)) == c_uart_unknown){
+		release_region(dev->base_addr, SER12_EXTENT);
+		return -EIO;
+	}
+	outb(0, FCR(dev->base_addr));  /* disable FIFOs */
+	outb(0x0d, MCR(dev->base_addr));
+	outb(0, IER(dev->base_addr));
+	if (request_irq(dev->irq, ser12_interrupt, IRQF_SHARED,
+			"baycom_ser_fdx", dev)) {
+		release_region(dev->base_addr, SER12_EXTENT);
+		return -EBUSY;
+	}
+	/*
+	 * set the SIO to 6 Bits/character; during receive,
+	 * the baud rate is set to produce 100 ints/sec
+	 * to feed the channel arbitration process,
+	 * during transmit to baud ints/sec to run
+	 * the transmitter
+	 */
+	ser12_set_divisor(dev, 115200/100/8);
+	/*
+	 * enable transmitter empty interrupt and modem status interrupt
+	 */
+	outb(0x0a, IER(dev->base_addr));
+	/*
+	 * make sure the next interrupt is generated;
+	 * 0 must be used to power the modem; the modem draws its
+	 * power from the TxD line
+	 */
+	outb(0x00, THR(dev->base_addr));
+	hdlcdrv_setdcd(&bc->hdrv, 0);
+	printk(KERN_INFO "%s: ser_fdx at iobase 0x%lx irq %u baud %u uart %s\n",
+	       bc_drvname, dev->base_addr, dev->irq, bc->baud, uart_str[u]);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int ser12_close(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+
+	if (!dev || !bc)
+		return -EINVAL;
+	/*
+	 * disable interrupts
+	 */
+	outb(0, IER(dev->base_addr));
+	outb(1, MCR(dev->base_addr));
+	free_irq(dev->irq, dev);
+	release_region(dev->base_addr, SER12_EXTENT);
+	printk(KERN_INFO "%s: close ser_fdx at iobase 0x%lx irq %u\n",
+	       bc_drvname, dev->base_addr, dev->irq);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== hdlcdrv driver interface =========================
+ */
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_ioctl(struct net_device *dev, struct ifreq *ifr,
+			struct hdlcdrv_ioctl *hi, int cmd);
+
+/* --------------------------------------------------------------------- */
+
+static const struct hdlcdrv_ops ser12_ops = {
+	.drvname = bc_drvname,
+	.drvinfo = bc_drvinfo,
+	.open    = ser12_open,
+	.close   = ser12_close,
+	.ioctl   = baycom_ioctl,
+};
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_setmode(struct baycom_state *bc, const char *modestr)
+{
+	unsigned int baud;
+
+	if (!strncmp(modestr, "ser", 3)) {
+		baud = simple_strtoul(modestr+3, NULL, 10);
+		if (baud >= 3 && baud <= 48)
+			bc->baud = baud*100;
+	}
+	if (strchr(modestr, '*'))
+		bc->opt_dcd = 0;
+	else if (strchr(modestr, '+'))
+		bc->opt_dcd = -1;
+	else
+		bc->opt_dcd = 1;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_ioctl(struct net_device *dev, struct ifreq *ifr,
+			struct hdlcdrv_ioctl *hi, int cmd)
+{
+	struct baycom_state *bc;
+	struct baycom_ioctl bi;
+
+	if (!dev)
+		return -EINVAL;
+
+	bc = netdev_priv(dev);
+	BUG_ON(bc->hdrv.magic != HDLCDRV_MAGIC);
+
+	if (cmd != SIOCDEVPRIVATE)
+		return -ENOIOCTLCMD;
+	switch (hi->cmd) {
+	default:
+		break;
+
+	case HDLCDRVCTL_GETMODE:
+		sprintf(hi->data.modename, "ser%u", bc->baud / 100);
+		if (bc->opt_dcd <= 0)
+			strcat(hi->data.modename, (!bc->opt_dcd) ? "*" : "+");
+		if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl)))
+			return -EFAULT;
+		return 0;
+
+	case HDLCDRVCTL_SETMODE:
+		if (netif_running(dev) || !capable(CAP_NET_ADMIN))
+			return -EACCES;
+		hi->data.modename[sizeof(hi->data.modename)-1] = '\0';
+		return baycom_setmode(bc, hi->data.modename);
+
+	case HDLCDRVCTL_MODELIST:
+		strcpy(hi->data.modename, "ser12,ser3,ser24");
+		if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl)))
+			return -EFAULT;
+		return 0;
+
+	case HDLCDRVCTL_MODEMPARMASK:
+		return HDLCDRV_PARMASK_IOBASE | HDLCDRV_PARMASK_IRQ;
+
+	}
+
+	if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi)))
+		return -EFAULT;
+	switch (bi.cmd) {
+	default:
+		return -ENOIOCTLCMD;
+
+#ifdef BAYCOM_DEBUG
+	case BAYCOMCTL_GETDEBUG:
+		bi.data.dbg.debug1 = bc->hdrv.ptt_keyed;
+		bi.data.dbg.debug2 = bc->debug_vals.last_intcnt;
+		bi.data.dbg.debug3 = bc->debug_vals.last_pllcorr;
+		break;
+#endif /* BAYCOM_DEBUG */
+
+	}
+	if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi)))
+		return -EFAULT;
+	return 0;
+
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * command line settable parameters
+ */
+static char *mode[NR_PORTS] = { "ser12*", };
+static int iobase[NR_PORTS] = { 0x3f8, };
+static int irq[NR_PORTS] = { 4, };
+static int baud[NR_PORTS] = { [0 ... NR_PORTS-1] = 1200 };
+
+module_param_array(mode, charp, NULL, 0);
+MODULE_PARM_DESC(mode, "baycom operating mode; * for software DCD");
+module_param_hw_array(iobase, int, ioport, NULL, 0);
+MODULE_PARM_DESC(iobase, "baycom io base address");
+module_param_hw_array(irq, int, irq, NULL, 0);
+MODULE_PARM_DESC(irq, "baycom irq number");
+module_param_array(baud, int, NULL, 0);
+MODULE_PARM_DESC(baud, "baycom baud rate (300 to 4800)");
+
+MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu");
+MODULE_DESCRIPTION("Baycom ser12 full duplex amateur radio modem driver");
+MODULE_LICENSE("GPL");
+
+/* --------------------------------------------------------------------- */
+
+static int __init init_baycomserfdx(void)
+{
+	int i, found = 0;
+	char set_hw = 1;
+
+	printk(bc_drvinfo);
+	/*
+	 * register net devices
+	 */
+	for (i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev;
+		struct baycom_state *bc;
+		char ifname[IFNAMSIZ];
+
+		sprintf(ifname, "bcsf%d", i);
+
+		if (!mode[i])
+			set_hw = 0;
+		if (!set_hw)
+			iobase[i] = irq[i] = 0;
+
+		dev = hdlcdrv_register(&ser12_ops, 
+				       sizeof(struct baycom_state),
+				       ifname, iobase[i], irq[i], 0);
+		if (IS_ERR(dev)) 
+			break;
+
+		bc = netdev_priv(dev);
+		if (set_hw && baycom_setmode(bc, mode[i]))
+			set_hw = 0;
+		bc->baud = baud[i];
+		found++;
+		baycom_device[i] = dev;
+	}
+
+	if (!found)
+		return -ENXIO;
+	return 0;
+}
+
+static void __exit cleanup_baycomserfdx(void)
+{
+	int i;
+
+	for(i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev = baycom_device[i];
+		if (dev) 
+			hdlcdrv_unregister(dev);
+	}
+}
+
+module_init(init_baycomserfdx);
+module_exit(cleanup_baycomserfdx);
+
+/* --------------------------------------------------------------------- */
+
+#ifndef MODULE
+
+/*
+ * format: baycom_ser_fdx=io,irq,mode
+ * mode: ser#    hardware DCD
+ *       ser#*   software DCD
+ *       ser#+   hardware DCD, inverted signal at DCD pin
+ * '#' denotes the baud rate / 100, eg. ser12* is '1200 baud, soft DCD'
+ */
+
+static int __init baycom_ser_fdx_setup(char *str)
+{
+        static unsigned nr_dev;
+        int ints[4];
+
+        if (nr_dev >= NR_PORTS)
+                return 0;
+        str = get_options(str, 4, ints);
+        if (ints[0] < 2)
+                return 0;
+        mode[nr_dev] = str;
+        iobase[nr_dev] = ints[1];
+        irq[nr_dev] = ints[2];
+	if (ints[0] >= 3)
+		baud[nr_dev] = ints[3];
+	nr_dev++;
+	return 1;
+}
+
+__setup("baycom_ser_fdx=", baycom_ser_fdx_setup);
+
+#endif /* MODULE */
+/* --------------------------------------------------------------------- */
diff --git a/drivers/net/hamradio/baycom_ser_hdx.c b/drivers/net/hamradio/baycom_ser_hdx.c
new file mode 100644
index 0000000..3c823c6
--- /dev/null
+++ b/drivers/net/hamradio/baycom_ser_hdx.c
@@ -0,0 +1,743 @@
+/*****************************************************************************/
+
+/*
+ *	baycom_ser_hdx.c  -- baycom ser12 halfduplex radio modem driver.
+ *
+ *	Copyright (C) 1996-2000  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.
+ *
+ *
+ *  Supported modems
+ *
+ *  ser12:  This is a very simple 1200 baud AFSK modem. The modem consists only
+ *          of a modulator/demodulator chip, usually a TI TCM3105. The computer
+ *          is responsible for regenerating the receiver bit clock, as well as
+ *          for handling the HDLC protocol. The modem connects to a serial port,
+ *          hence the name. Since the serial port is not used as an async serial
+ *          port, the kernel driver for serial ports cannot be used, and this
+ *          driver only supports standard serial hardware (8250, 16450, 16550A)
+ *
+ *
+ *  Command line options (insmod command line)
+ *
+ *  mode     ser12    hardware DCD
+ *           ser12*   software DCD
+ *           ser12@   hardware/software DCD, i.e. no explicit DCD signal but hardware
+ *                    mutes audio input to the modem
+ *           ser12+   hardware DCD, inverted signal at DCD pin
+ *  iobase   base address of the port; common values are 0x3f8, 0x2f8, 0x3e8, 0x2e8
+ *  irq      interrupt line of the port; common values are 4,3
+ *
+ *
+ *  History:
+ *   0.1  26.06.1996  Adapted from baycom.c and made network driver interface
+ *        18.10.1996  Changed to new user space access routines (copy_{to,from}_user)
+ *   0.3  26.04.1997  init code/data tagged
+ *   0.4  08.07.1997  alternative ser12 decoding algorithm (uses delta CTS ints)
+ *   0.5  11.11.1997  ser12/par96 split into separate files
+ *   0.6  14.04.1998  cleanups
+ *   0.7  03.08.1999  adapt to Linus' new __setup/__initcall
+ *   0.8  10.08.1999  use module_init/module_exit
+ *   0.9  12.02.2000  adapted to softnet driver interface
+ *   0.10 03.07.2000  fix interface name handling
+ */
+
+/*****************************************************************************/
+
+#include <linux/capability.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/uaccess.h>
+#include <asm/io.h>
+#include <linux/hdlcdrv.h>
+#include <linux/baycom.h>
+#include <linux/jiffies.h>
+
+/* --------------------------------------------------------------------- */
+
+#define BAYCOM_DEBUG
+
+/* --------------------------------------------------------------------- */
+
+static const char bc_drvname[] = "baycom_ser_hdx";
+static const char bc_drvinfo[] = KERN_INFO "baycom_ser_hdx: (C) 1996-2000 Thomas Sailer, HB9JNX/AE4WA\n"
+"baycom_ser_hdx: version 0.10\n";
+
+/* --------------------------------------------------------------------- */
+
+#define NR_PORTS 4
+
+static struct net_device *baycom_device[NR_PORTS];
+
+/* --------------------------------------------------------------------- */
+
+#define RBR(iobase) (iobase+0)
+#define THR(iobase) (iobase+0)
+#define IER(iobase) (iobase+1)
+#define IIR(iobase) (iobase+2)
+#define FCR(iobase) (iobase+2)
+#define LCR(iobase) (iobase+3)
+#define MCR(iobase) (iobase+4)
+#define LSR(iobase) (iobase+5)
+#define MSR(iobase) (iobase+6)
+#define SCR(iobase) (iobase+7)
+#define DLL(iobase) (iobase+0)
+#define DLM(iobase) (iobase+1)
+
+#define SER12_EXTENT 8
+
+/* ---------------------------------------------------------------------- */
+/*
+ * Information that need to be kept for each board.
+ */
+
+struct baycom_state {
+	struct hdlcdrv_state hdrv;
+
+	int opt_dcd;
+
+	struct modem_state {
+		short arb_divider;
+		unsigned char flags;
+		unsigned int shreg;
+		struct modem_state_ser12 {
+			unsigned char tx_bit;
+			int dcd_sum0, dcd_sum1, dcd_sum2;
+			unsigned char last_sample;
+			unsigned char last_rxbit;
+			unsigned int dcd_shreg;
+			unsigned int dcd_time;
+			unsigned int bit_pll;
+			unsigned char interm_sample;
+		} ser12;
+	} modem;
+
+#ifdef BAYCOM_DEBUG
+	struct debug_vals {
+		unsigned long last_jiffies;
+		unsigned cur_intcnt;
+		unsigned last_intcnt;
+		int cur_pllcorr;
+		int last_pllcorr;
+	} debug_vals;
+#endif /* BAYCOM_DEBUG */
+};
+
+/* --------------------------------------------------------------------- */
+
+static inline void baycom_int_freq(struct baycom_state *bc)
+{
+#ifdef BAYCOM_DEBUG
+	unsigned long cur_jiffies = jiffies;
+	/*
+	 * measure the interrupt frequency
+	 */
+	bc->debug_vals.cur_intcnt++;
+	if (time_after_eq(cur_jiffies, bc->debug_vals.last_jiffies + HZ)) {
+		bc->debug_vals.last_jiffies = cur_jiffies;
+		bc->debug_vals.last_intcnt = bc->debug_vals.cur_intcnt;
+		bc->debug_vals.cur_intcnt = 0;
+		bc->debug_vals.last_pllcorr = bc->debug_vals.cur_pllcorr;
+		bc->debug_vals.cur_pllcorr = 0;
+	}
+#endif /* BAYCOM_DEBUG */
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== SER12 specific routines =========================
+ */
+
+static inline void ser12_set_divisor(struct net_device *dev,
+				     unsigned char divisor)
+{
+	outb(0x81, LCR(dev->base_addr));	/* DLAB = 1 */
+	outb(divisor, DLL(dev->base_addr));
+	outb(0, DLM(dev->base_addr));
+	outb(0x01, LCR(dev->base_addr));	/* word length = 6 */
+	/*
+	 * make sure the next interrupt is generated;
+	 * 0 must be used to power the modem; the modem draws its
+	 * power from the TxD line
+	 */
+	outb(0x00, THR(dev->base_addr));
+	/*
+	 * it is important not to set the divider while transmitting;
+	 * this reportedly makes some UARTs generating interrupts
+	 * in the hundredthousands per second region
+	 * Reported by: Ignacio.Arenaza@studi.epfl.ch (Ignacio Arenaza Nuno)
+	 */
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * must call the TX arbitrator every 10ms
+ */
+#define SER12_ARB_DIVIDER(bc)  (bc->opt_dcd ? 24 : 36)
+			       
+#define SER12_DCD_INTERVAL(bc) (bc->opt_dcd ? 12 : 240)
+
+static inline void ser12_tx(struct net_device *dev, struct baycom_state *bc)
+{
+	/* one interrupt per channel bit */
+	ser12_set_divisor(dev, 12);
+	/*
+	 * first output the last bit (!) then call HDLC transmitter,
+	 * since this may take quite long
+	 */
+	outb(0x0e | (!!bc->modem.ser12.tx_bit), MCR(dev->base_addr));
+	if (bc->modem.shreg <= 1)
+		bc->modem.shreg = 0x10000 | hdlcdrv_getbits(&bc->hdrv);
+	bc->modem.ser12.tx_bit = !(bc->modem.ser12.tx_bit ^
+				   (bc->modem.shreg & 1));
+	bc->modem.shreg >>= 1;
+}
+
+/* --------------------------------------------------------------------- */
+
+static inline void ser12_rx(struct net_device *dev, struct baycom_state *bc)
+{
+	unsigned char cur_s;
+	/*
+	 * do demodulator
+	 */
+	cur_s = inb(MSR(dev->base_addr)) & 0x10;	/* the CTS line */
+	hdlcdrv_channelbit(&bc->hdrv, cur_s);
+	bc->modem.ser12.dcd_shreg = (bc->modem.ser12.dcd_shreg << 1) |
+		(cur_s != bc->modem.ser12.last_sample);
+	bc->modem.ser12.last_sample = cur_s;
+	if(bc->modem.ser12.dcd_shreg & 1) {
+		if (!bc->opt_dcd) {
+			unsigned int dcdspos, dcdsneg;
+
+			dcdspos = dcdsneg = 0;
+			dcdspos += ((bc->modem.ser12.dcd_shreg >> 1) & 1);
+			if (!(bc->modem.ser12.dcd_shreg & 0x7ffffffe))
+				dcdspos += 2;
+			dcdsneg += ((bc->modem.ser12.dcd_shreg >> 2) & 1);
+			dcdsneg += ((bc->modem.ser12.dcd_shreg >> 3) & 1);
+			dcdsneg += ((bc->modem.ser12.dcd_shreg >> 4) & 1);
+
+			bc->modem.ser12.dcd_sum0 += 16*dcdspos - dcdsneg;
+		} else
+			bc->modem.ser12.dcd_sum0--;
+	}
+	if(!bc->modem.ser12.dcd_time) {
+		hdlcdrv_setdcd(&bc->hdrv, (bc->modem.ser12.dcd_sum0 +
+					   bc->modem.ser12.dcd_sum1 +
+					   bc->modem.ser12.dcd_sum2) < 0);
+		bc->modem.ser12.dcd_sum2 = bc->modem.ser12.dcd_sum1;
+		bc->modem.ser12.dcd_sum1 = bc->modem.ser12.dcd_sum0;
+		/* offset to ensure DCD off on silent input */
+		bc->modem.ser12.dcd_sum0 = 2;
+		bc->modem.ser12.dcd_time = SER12_DCD_INTERVAL(bc);
+	}
+	bc->modem.ser12.dcd_time--;
+	if (!bc->opt_dcd) {
+		/*
+		 * PLL code for the improved software DCD algorithm
+		 */
+		if (bc->modem.ser12.interm_sample) {
+			/*
+			 * intermediate sample; set timing correction to normal
+			 */
+			ser12_set_divisor(dev, 4);
+		} else {
+			/*
+			 * do PLL correction and call HDLC receiver
+			 */
+			switch (bc->modem.ser12.dcd_shreg & 7) {
+			case 1: /* transition too late */
+				ser12_set_divisor(dev, 5);
+#ifdef BAYCOM_DEBUG
+				bc->debug_vals.cur_pllcorr++;
+#endif /* BAYCOM_DEBUG */
+				break;
+			case 4:	/* transition too early */
+				ser12_set_divisor(dev, 3);
+#ifdef BAYCOM_DEBUG
+				bc->debug_vals.cur_pllcorr--;
+#endif /* BAYCOM_DEBUG */
+				break;
+			default:
+				ser12_set_divisor(dev, 4);
+				break;
+			}
+			bc->modem.shreg >>= 1;
+			if (bc->modem.ser12.last_sample ==
+			    bc->modem.ser12.last_rxbit)
+				bc->modem.shreg |= 0x10000;
+			bc->modem.ser12.last_rxbit =
+				bc->modem.ser12.last_sample;
+		}
+		if (++bc->modem.ser12.interm_sample >= 3)
+			bc->modem.ser12.interm_sample = 0;
+		/*
+		 * DCD stuff
+		 */
+		if (bc->modem.ser12.dcd_shreg & 1) {
+			unsigned int dcdspos, dcdsneg;
+
+			dcdspos = dcdsneg = 0;
+			dcdspos += ((bc->modem.ser12.dcd_shreg >> 1) & 1);
+			dcdspos += (!(bc->modem.ser12.dcd_shreg & 0x7ffffffe))
+				<< 1;
+			dcdsneg += ((bc->modem.ser12.dcd_shreg >> 2) & 1);
+			dcdsneg += ((bc->modem.ser12.dcd_shreg >> 3) & 1);
+			dcdsneg += ((bc->modem.ser12.dcd_shreg >> 4) & 1);
+
+			bc->modem.ser12.dcd_sum0 += 16*dcdspos - dcdsneg;
+		}
+	} else {
+		/*
+		 * PLL algorithm for the hardware squelch DCD algorithm
+		 */
+		if (bc->modem.ser12.interm_sample) {
+			/*
+			 * intermediate sample; set timing correction to normal
+			 */
+			ser12_set_divisor(dev, 6);
+		} else {
+			/*
+			 * do PLL correction and call HDLC receiver
+			 */
+			switch (bc->modem.ser12.dcd_shreg & 3) {
+			case 1: /* transition too late */
+				ser12_set_divisor(dev, 7);
+#ifdef BAYCOM_DEBUG
+				bc->debug_vals.cur_pllcorr++;
+#endif /* BAYCOM_DEBUG */
+				break;
+			case 2:	/* transition too early */
+				ser12_set_divisor(dev, 5);
+#ifdef BAYCOM_DEBUG
+				bc->debug_vals.cur_pllcorr--;
+#endif /* BAYCOM_DEBUG */
+				break;
+			default:
+				ser12_set_divisor(dev, 6);
+				break;
+			}
+			bc->modem.shreg >>= 1;
+			if (bc->modem.ser12.last_sample ==
+			    bc->modem.ser12.last_rxbit)
+				bc->modem.shreg |= 0x10000;
+			bc->modem.ser12.last_rxbit =
+				bc->modem.ser12.last_sample;
+		}
+		bc->modem.ser12.interm_sample = !bc->modem.ser12.interm_sample;
+		/*
+		 * DCD stuff
+		 */
+		bc->modem.ser12.dcd_sum0 -= (bc->modem.ser12.dcd_shreg & 1);
+	}
+	outb(0x0d, MCR(dev->base_addr));		/* transmitter off */
+	if (bc->modem.shreg & 1) {
+		hdlcdrv_putbits(&bc->hdrv, bc->modem.shreg >> 1);
+		bc->modem.shreg = 0x10000;
+	}
+	if(!bc->modem.ser12.dcd_time) {
+		if (bc->opt_dcd & 1) 
+			hdlcdrv_setdcd(&bc->hdrv, !((inb(MSR(dev->base_addr)) ^ bc->opt_dcd) & 0x80));
+		else
+			hdlcdrv_setdcd(&bc->hdrv, (bc->modem.ser12.dcd_sum0 +
+						   bc->modem.ser12.dcd_sum1 +
+						   bc->modem.ser12.dcd_sum2) < 0);
+		bc->modem.ser12.dcd_sum2 = bc->modem.ser12.dcd_sum1;
+		bc->modem.ser12.dcd_sum1 = bc->modem.ser12.dcd_sum0;
+		/* offset to ensure DCD off on silent input */
+		bc->modem.ser12.dcd_sum0 = 2;
+		bc->modem.ser12.dcd_time = SER12_DCD_INTERVAL(bc);
+	}
+	bc->modem.ser12.dcd_time--;
+}
+
+/* --------------------------------------------------------------------- */
+
+static irqreturn_t ser12_interrupt(int irq, void *dev_id)
+{
+	struct net_device *dev = (struct net_device *)dev_id;
+	struct baycom_state *bc = netdev_priv(dev);
+	unsigned char iir;
+
+	if (!dev || !bc || bc->hdrv.magic != HDLCDRV_MAGIC)
+		return IRQ_NONE;
+	/* fast way out */
+	if ((iir = inb(IIR(dev->base_addr))) & 1)
+		return IRQ_NONE;
+	baycom_int_freq(bc);
+	do {
+		switch (iir & 6) {
+		case 6:
+			inb(LSR(dev->base_addr));
+			break;
+			
+		case 4:
+			inb(RBR(dev->base_addr));
+			break;
+			
+		case 2:
+			/*
+			 * check if transmitter active
+			 */
+			if (hdlcdrv_ptt(&bc->hdrv))
+				ser12_tx(dev, bc);
+			else {
+				ser12_rx(dev, bc);
+				bc->modem.arb_divider--;
+			}
+			outb(0x00, THR(dev->base_addr));
+			break;
+			
+		default:
+			inb(MSR(dev->base_addr));
+			break;
+		}
+		iir = inb(IIR(dev->base_addr));
+	} while (!(iir & 1));
+	if (bc->modem.arb_divider <= 0) {
+		bc->modem.arb_divider = SER12_ARB_DIVIDER(bc);
+		local_irq_enable();
+		hdlcdrv_arbitrate(dev, &bc->hdrv);
+	}
+	local_irq_enable();
+	hdlcdrv_transmitter(dev, &bc->hdrv);
+	hdlcdrv_receiver(dev, &bc->hdrv);
+	local_irq_disable();
+	return IRQ_HANDLED;
+}
+
+/* --------------------------------------------------------------------- */
+
+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 ser12_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 };
+
+	b1 = inb(MCR(iobase));
+	outb(b1 | 0x10, MCR(iobase));	/* loopback mode */
+	b2 = inb(MSR(iobase));
+	outb(0x1a, MCR(iobase));
+	b3 = inb(MSR(iobase)) & 0xf0;
+	outb(b1, MCR(iobase));			/* restore old values */
+	outb(b2, MSR(iobase));
+	if (b3 != 0x90)
+		return c_uart_unknown;
+	inb(RBR(iobase));
+	inb(RBR(iobase));
+	outb(0x01, FCR(iobase));		/* enable FIFOs */
+	u = uart_tab[(inb(IIR(iobase)) >> 6) & 3];
+	if (u == c_uart_16450) {
+		outb(0x5a, SCR(iobase));
+		b1 = inb(SCR(iobase));
+		outb(0xa5, SCR(iobase));
+		b2 = inb(SCR(iobase));
+		if ((b1 != 0x5a) || (b2 != 0xa5))
+			u = c_uart_8250;
+	}
+	return u;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int ser12_open(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+	enum uart u;
+
+	if (!dev || !bc)
+		return -ENXIO;
+	if (!dev->base_addr || dev->base_addr > 0x1000-SER12_EXTENT ||
+	    dev->irq < 2 || dev->irq > 15)
+		return -ENXIO;
+	if (!request_region(dev->base_addr, SER12_EXTENT, "baycom_ser12"))
+		return -EACCES;
+	memset(&bc->modem, 0, sizeof(bc->modem));
+	bc->hdrv.par.bitrate = 1200;
+	if ((u = ser12_check_uart(dev->base_addr)) == c_uart_unknown) {
+		release_region(dev->base_addr, SER12_EXTENT);       
+		return -EIO;
+	}
+	outb(0, FCR(dev->base_addr));  /* disable FIFOs */
+	outb(0x0d, MCR(dev->base_addr));
+	outb(0, IER(dev->base_addr));
+	if (request_irq(dev->irq, ser12_interrupt, IRQF_SHARED,
+			"baycom_ser12", dev)) {
+		release_region(dev->base_addr, SER12_EXTENT);       
+		return -EBUSY;
+	}
+	/*
+	 * enable transmitter empty interrupt
+	 */
+	outb(2, IER(dev->base_addr));
+	/*
+	 * set the SIO to 6 Bits/character and 19200 or 28800 baud, so that
+	 * we get exactly (hopefully) 2 or 3 interrupts per radio symbol,
+	 * depending on the usage of the software DCD routine
+	 */
+	ser12_set_divisor(dev, bc->opt_dcd ? 6 : 4);
+	printk(KERN_INFO "%s: ser12 at iobase 0x%lx irq %u uart %s\n", 
+	       bc_drvname, dev->base_addr, dev->irq, uart_str[u]);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int ser12_close(struct net_device *dev)
+{
+	struct baycom_state *bc = netdev_priv(dev);
+
+	if (!dev || !bc)
+		return -EINVAL;
+	/*
+	 * disable interrupts
+	 */
+	outb(0, IER(dev->base_addr));
+	outb(1, MCR(dev->base_addr));
+	free_irq(dev->irq, dev);
+	release_region(dev->base_addr, SER12_EXTENT);
+	printk(KERN_INFO "%s: close ser12 at iobase 0x%lx irq %u\n",
+	       bc_drvname, dev->base_addr, dev->irq);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== hdlcdrv driver interface =========================
+ */
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_ioctl(struct net_device *dev, struct ifreq *ifr,
+			struct hdlcdrv_ioctl *hi, int cmd);
+
+/* --------------------------------------------------------------------- */
+
+static const struct hdlcdrv_ops ser12_ops = {
+	.drvname = bc_drvname,
+	.drvinfo = bc_drvinfo,
+	.open    = ser12_open,
+	.close   = ser12_close,
+	.ioctl   = baycom_ioctl,
+};
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_setmode(struct baycom_state *bc, const char *modestr)
+{
+	if (strchr(modestr, '*'))
+		bc->opt_dcd = 0;
+	else if (strchr(modestr, '+'))
+		bc->opt_dcd = -1;
+	else if (strchr(modestr, '@'))
+		bc->opt_dcd = -2;
+	else
+		bc->opt_dcd = 1;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_ioctl(struct net_device *dev, struct ifreq *ifr,
+			struct hdlcdrv_ioctl *hi, int cmd)
+{
+	struct baycom_state *bc;
+	struct baycom_ioctl bi;
+
+	if (!dev)
+		return -EINVAL;
+
+	bc = netdev_priv(dev);
+	BUG_ON(bc->hdrv.magic != HDLCDRV_MAGIC);
+
+	if (cmd != SIOCDEVPRIVATE)
+		return -ENOIOCTLCMD;
+	switch (hi->cmd) {
+	default:
+		break;
+
+	case HDLCDRVCTL_GETMODE:
+		strcpy(hi->data.modename, "ser12");
+		if (bc->opt_dcd <= 0)
+			strcat(hi->data.modename, (!bc->opt_dcd) ? "*" : (bc->opt_dcd == -2) ? "@" : "+");
+		if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl)))
+			return -EFAULT;
+		return 0;
+
+	case HDLCDRVCTL_SETMODE:
+		if (netif_running(dev) || !capable(CAP_NET_ADMIN))
+			return -EACCES;
+		hi->data.modename[sizeof(hi->data.modename)-1] = '\0';
+		return baycom_setmode(bc, hi->data.modename);
+
+	case HDLCDRVCTL_MODELIST:
+		strcpy(hi->data.modename, "ser12");
+		if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl)))
+			return -EFAULT;
+		return 0;
+
+	case HDLCDRVCTL_MODEMPARMASK:
+		return HDLCDRV_PARMASK_IOBASE | HDLCDRV_PARMASK_IRQ;
+
+	}
+
+	if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi)))
+		return -EFAULT;
+	switch (bi.cmd) {
+	default:
+		return -ENOIOCTLCMD;
+
+#ifdef BAYCOM_DEBUG
+	case BAYCOMCTL_GETDEBUG:
+		bi.data.dbg.debug1 = bc->hdrv.ptt_keyed;
+		bi.data.dbg.debug2 = bc->debug_vals.last_intcnt;
+		bi.data.dbg.debug3 = bc->debug_vals.last_pllcorr;
+		break;
+#endif /* BAYCOM_DEBUG */
+
+	}
+	if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi)))
+		return -EFAULT;
+	return 0;
+
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * command line settable parameters
+ */
+static char *mode[NR_PORTS] = { "ser12*", };
+static int iobase[NR_PORTS] = { 0x3f8, };
+static int irq[NR_PORTS] = { 4, };
+
+module_param_array(mode, charp, NULL, 0);
+MODULE_PARM_DESC(mode, "baycom operating mode; * for software DCD");
+module_param_hw_array(iobase, int, ioport, NULL, 0);
+MODULE_PARM_DESC(iobase, "baycom io base address");
+module_param_hw_array(irq, int, irq, NULL, 0);
+MODULE_PARM_DESC(irq, "baycom irq number");
+
+MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu");
+MODULE_DESCRIPTION("Baycom ser12 half duplex amateur radio modem driver");
+MODULE_LICENSE("GPL");
+
+/* --------------------------------------------------------------------- */
+
+static int __init init_baycomserhdx(void)
+{
+	int i, found = 0;
+	char set_hw = 1;
+
+	printk(bc_drvinfo);
+	/*
+	 * register net devices
+	 */
+	for (i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev;
+		struct baycom_state *bc;
+		char ifname[IFNAMSIZ];
+
+		sprintf(ifname, "bcsh%d", i);
+
+		if (!mode[i])
+			set_hw = 0;
+		if (!set_hw)
+			iobase[i] = irq[i] = 0;
+
+		dev = hdlcdrv_register(&ser12_ops, 
+				       sizeof(struct baycom_state),
+				       ifname, iobase[i], irq[i], 0);
+		if (IS_ERR(dev)) 
+			break;
+
+		bc = netdev_priv(dev);
+		if (set_hw && baycom_setmode(bc, mode[i]))
+			set_hw = 0;
+		found++;
+		baycom_device[i] = dev;
+	}
+
+	if (!found)
+		return -ENXIO;
+	return 0;
+}
+
+static void __exit cleanup_baycomserhdx(void)
+{
+	int i;
+
+	for(i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev = baycom_device[i];
+
+		if (dev)
+			hdlcdrv_unregister(dev);
+	}
+}
+
+module_init(init_baycomserhdx);
+module_exit(cleanup_baycomserhdx);
+
+/* --------------------------------------------------------------------- */
+
+#ifndef MODULE
+
+/*
+ * format: baycom_ser_hdx=io,irq,mode
+ * mode: ser12    hardware DCD
+ *       ser12*   software DCD
+ *       ser12@   hardware/software DCD, i.e. no explicit DCD signal but hardware
+ *                mutes audio input to the modem
+ *       ser12+   hardware DCD, inverted signal at DCD pin
+ */
+
+static int __init baycom_ser_hdx_setup(char *str)
+{
+        static unsigned nr_dev;
+	int ints[3];
+
+        if (nr_dev >= NR_PORTS)
+                return 0;
+	str = get_options(str, 3, ints);
+	if (ints[0] < 2)
+		return 0;
+	mode[nr_dev] = str;
+	iobase[nr_dev] = ints[1];
+	irq[nr_dev] = ints[2];
+	nr_dev++;
+	return 1;
+}
+
+__setup("baycom_ser_hdx=", baycom_ser_hdx_setup);
+
+#endif /* MODULE */
+/* --------------------------------------------------------------------- */
diff --git a/drivers/net/hamradio/bpqether.c b/drivers/net/hamradio/bpqether.c
new file mode 100644
index 0000000..777fa59
--- /dev/null
+++ b/drivers/net/hamradio/bpqether.c
@@ -0,0 +1,613 @@
+/*
+ *	G8BPQ compatible "AX.25 via ethernet" driver release 004
+ *
+ *	This code REQUIRES 2.0.0 or higher/ NET3.029
+ *
+ *	This module:
+ *		This module 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 is a "pseudo" network driver to allow AX.25 over Ethernet
+ *	using G8BPQ encapsulation. It has been extracted from the protocol
+ *	implementation because
+ *
+ *		- things got unreadable within the protocol stack
+ *		- to cure the protocol stack from "feature-ism"
+ *		- a protocol implementation shouldn't need to know on
+ *		  which hardware it is running
+ *		- user-level programs like the AX.25 utilities shouldn't
+ *		  need to know about the hardware.
+ *		- IP over ethernet encapsulated AX.25 was impossible
+ *		- rxecho.c did not work
+ *		- to have room for extensions
+ *		- it just deserves to "live" as an own driver
+ *
+ *	This driver can use any ethernet destination address, and can be
+ *	limited to accept frames from one dedicated ethernet card only.
+ *
+ *	Note that the driver sets up the BPQ devices automagically on
+ *	startup or (if started before the "insmod" of an ethernet device)
+ *	on "ifconfig up". It hopefully will remove the BPQ on "rmmod"ing
+ *	the ethernet device (in fact: as soon as another ethernet or bpq
+ *	device gets "ifconfig"ured).
+ *
+ *	I have heard that several people are thinking of experiments
+ *	with highspeed packet radio using existing ethernet cards.
+ *	Well, this driver is prepared for this purpose, just add
+ *	your tx key control and a txdelay / tailtime algorithm,
+ *	probably some buffering, and /voila/...
+ *
+ *	History
+ *	BPQ   001	Joerg(DL1BKE)		Extracted BPQ code from AX.25
+ *						protocol stack and added my own
+ *						yet existing patches
+ *	BPQ   002	Joerg(DL1BKE)		Scan network device list on
+ *						startup.
+ *	BPQ   003	Joerg(DL1BKE)		Ethernet destination address
+ *						and accepted source address
+ *						can be configured by an ioctl()
+ *						call.
+ *						Fixed to match Linux networking
+ *						changes - 2.1.15.
+ *	BPQ   004	Joerg(DL1BKE)		Fixed to not lock up on ifconfig.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/slab.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <linux/uaccess.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/stat.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rtnetlink.h>
+
+#include <net/ip.h>
+#include <net/arp.h>
+#include <net/net_namespace.h>
+
+#include <linux/bpqether.h>
+
+static const char banner[] __initconst = KERN_INFO \
+	"AX.25: bpqether driver version 004\n";
+
+static int bpq_rcv(struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *);
+static int bpq_device_event(struct notifier_block *, unsigned long, void *);
+
+static struct packet_type bpq_packet_type __read_mostly = {
+	.type	= cpu_to_be16(ETH_P_BPQ),
+	.func	= bpq_rcv,
+};
+
+static struct notifier_block bpq_dev_notifier = {
+	.notifier_call = bpq_device_event,
+};
+
+
+struct bpqdev {
+	struct list_head bpq_list;	/* list of bpq devices chain */
+	struct net_device *ethdev;	/* link to ethernet device */
+	struct net_device *axdev;	/* bpq device (bpq#) */
+	char   dest_addr[6];		/* ether destination address */
+	char   acpt_addr[6];		/* accept ether frames from this address only */
+};
+
+static LIST_HEAD(bpq_devices);
+
+/*
+ * bpqether network devices are paired with ethernet devices below them, so
+ * form a special "super class" of normal ethernet devices; split their locks
+ * off into a separate class since they always nest.
+ */
+static struct lock_class_key bpq_netdev_xmit_lock_key;
+static struct lock_class_key bpq_netdev_addr_lock_key;
+
+static void bpq_set_lockdep_class_one(struct net_device *dev,
+				      struct netdev_queue *txq,
+				      void *_unused)
+{
+	lockdep_set_class(&txq->_xmit_lock, &bpq_netdev_xmit_lock_key);
+}
+
+static void bpq_set_lockdep_class(struct net_device *dev)
+{
+	lockdep_set_class(&dev->addr_list_lock, &bpq_netdev_addr_lock_key);
+	netdev_for_each_tx_queue(dev, bpq_set_lockdep_class_one, NULL);
+}
+
+/* ------------------------------------------------------------------------ */
+
+
+/*
+ *	Get the ethernet device for a BPQ device
+ */
+static inline struct net_device *bpq_get_ether_dev(struct net_device *dev)
+{
+	struct bpqdev *bpq = netdev_priv(dev);
+
+	return bpq ? bpq->ethdev : NULL;
+}
+
+/*
+ *	Get the BPQ device for the ethernet device
+ */
+static inline struct net_device *bpq_get_ax25_dev(struct net_device *dev)
+{
+	struct bpqdev *bpq;
+
+	list_for_each_entry_rcu(bpq, &bpq_devices, bpq_list) {
+		if (bpq->ethdev == dev)
+			return bpq->axdev;
+	}
+	return NULL;
+}
+
+static inline int dev_is_ethdev(struct net_device *dev)
+{
+	return dev->type == ARPHRD_ETHER && strncmp(dev->name, "dummy", 5);
+}
+
+/* ------------------------------------------------------------------------ */
+
+
+/*
+ *	Receive an AX.25 frame via an ethernet interface.
+ */
+static int bpq_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *ptype, struct net_device *orig_dev)
+{
+	int len;
+	char * ptr;
+	struct ethhdr *eth;
+	struct bpqdev *bpq;
+
+	if (!net_eq(dev_net(dev), &init_net))
+		goto drop;
+
+	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+		return NET_RX_DROP;
+
+	if (!pskb_may_pull(skb, sizeof(struct ethhdr)))
+		goto drop;
+
+	rcu_read_lock();
+	dev = bpq_get_ax25_dev(dev);
+
+	if (dev == NULL || !netif_running(dev)) 
+		goto drop_unlock;
+
+	/*
+	 * if we want to accept frames from just one ethernet device
+	 * we check the source address of the sender.
+	 */
+
+	bpq = netdev_priv(dev);
+
+	eth = eth_hdr(skb);
+
+	if (!(bpq->acpt_addr[0] & 0x01) &&
+	    !ether_addr_equal(eth->h_source, bpq->acpt_addr))
+		goto drop_unlock;
+
+	if (skb_cow(skb, sizeof(struct ethhdr)))
+		goto drop_unlock;
+
+	len = skb->data[0] + skb->data[1] * 256 - 5;
+
+	skb_pull(skb, 2);	/* Remove the length bytes */
+	skb_trim(skb, len);	/* Set the length of the data */
+
+	dev->stats.rx_packets++;
+	dev->stats.rx_bytes += len;
+
+	ptr = skb_push(skb, 1);
+	*ptr = 0;
+
+	skb->protocol = ax25_type_trans(skb, dev);
+	netif_rx(skb);
+unlock:
+
+	rcu_read_unlock();
+
+	return 0;
+drop_unlock:
+	kfree_skb(skb);
+	goto unlock;
+
+drop:
+	kfree_skb(skb);
+	return 0;
+}
+
+/*
+ * 	Send an AX.25 frame via an ethernet interface
+ */
+static netdev_tx_t bpq_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	unsigned char *ptr;
+	struct bpqdev *bpq;
+	struct net_device *orig_dev;
+	int size;
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return ax25_ip_xmit(skb);
+
+	/*
+	 * Just to be *really* sure not to send anything if the interface
+	 * is down, the ethernet device may have gone.
+	 */
+	if (!netif_running(dev)) {
+		kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	skb_pull(skb, 1);			/* Drop KISS byte */
+	size = skb->len;
+
+	/*
+	 * We're about to mess with the skb which may still shared with the
+	 * generic networking code so unshare and ensure it's got enough
+	 * space for the BPQ headers.
+	 */
+	if (skb_cow(skb, AX25_BPQ_HEADER_LEN)) {
+		if (net_ratelimit())
+			pr_err("bpqether: out of memory\n");
+		kfree_skb(skb);
+
+		return NETDEV_TX_OK;
+	}
+
+	ptr = skb_push(skb, 2);			/* Make space for length */
+
+	*ptr++ = (size + 5) % 256;
+	*ptr++ = (size + 5) / 256;
+
+	bpq = netdev_priv(dev);
+
+	orig_dev = dev;
+	if ((dev = bpq_get_ether_dev(dev)) == NULL) {
+		orig_dev->stats.tx_dropped++;
+		kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	skb->protocol = ax25_type_trans(skb, dev);
+	skb_reset_network_header(skb);
+	dev_hard_header(skb, dev, ETH_P_BPQ, bpq->dest_addr, NULL, 0);
+	dev->stats.tx_packets++;
+	dev->stats.tx_bytes+=skb->len;
+  
+	dev_queue_xmit(skb);
+	netif_wake_queue(dev);
+	return NETDEV_TX_OK;
+}
+
+/*
+ *	Set AX.25 callsign
+ */
+static int bpq_set_mac_address(struct net_device *dev, void *addr)
+{
+    struct sockaddr *sa = (struct sockaddr *)addr;
+
+    memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+
+    return 0;
+}
+
+/*	Ioctl commands
+ *
+ *		SIOCSBPQETHOPT		reserved for enhancements
+ *		SIOCSBPQETHADDR		set the destination and accepted
+ *					source ethernet address (broadcast
+ *					or multicast: accept all)
+ */
+static int bpq_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct bpq_ethaddr __user *ethaddr = ifr->ifr_data;
+	struct bpqdev *bpq = netdev_priv(dev);
+	struct bpq_req req;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	switch (cmd) {
+		case SIOCSBPQETHOPT:
+			if (copy_from_user(&req, ifr->ifr_data, sizeof(struct bpq_req)))
+				return -EFAULT;
+			switch (req.cmd) {
+				case SIOCGBPQETHPARAM:
+				case SIOCSBPQETHPARAM:
+				default:
+					return -EINVAL;
+			}
+
+			break;
+
+		case SIOCSBPQETHADDR:
+			if (copy_from_user(bpq->dest_addr, ethaddr->destination, ETH_ALEN))
+				return -EFAULT;
+			if (copy_from_user(bpq->acpt_addr, ethaddr->accept, ETH_ALEN))
+				return -EFAULT;
+			break;
+
+		default:
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * open/close a device
+ */
+static int bpq_open(struct net_device *dev)
+{
+	netif_start_queue(dev);
+	return 0;
+}
+
+static int bpq_close(struct net_device *dev)
+{
+	netif_stop_queue(dev);
+	return 0;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+
+/*
+ *	Proc filesystem
+ */
+static void *bpq_seq_start(struct seq_file *seq, loff_t *pos)
+	__acquires(RCU)
+{
+	int i = 1;
+	struct bpqdev *bpqdev;
+
+	rcu_read_lock();
+
+	if (*pos == 0)
+		return SEQ_START_TOKEN;
+	
+	list_for_each_entry_rcu(bpqdev, &bpq_devices, bpq_list) {
+		if (i == *pos)
+			return bpqdev;
+	}
+	return NULL;
+}
+
+static void *bpq_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	struct list_head *p;
+	struct bpqdev *bpqdev = v;
+
+	++*pos;
+
+	if (v == SEQ_START_TOKEN)
+		p = rcu_dereference(list_next_rcu(&bpq_devices));
+	else
+		p = rcu_dereference(list_next_rcu(&bpqdev->bpq_list));
+
+	return (p == &bpq_devices) ? NULL 
+		: list_entry(p, struct bpqdev, bpq_list);
+}
+
+static void bpq_seq_stop(struct seq_file *seq, void *v)
+	__releases(RCU)
+{
+	rcu_read_unlock();
+}
+
+
+static int bpq_seq_show(struct seq_file *seq, void *v)
+{
+	if (v == SEQ_START_TOKEN)
+		seq_puts(seq, 
+			 "dev   ether      destination        accept from\n");
+	else {
+		const struct bpqdev *bpqdev = v;
+
+		seq_printf(seq, "%-5s %-10s %pM  ",
+			bpqdev->axdev->name, bpqdev->ethdev->name,
+			bpqdev->dest_addr);
+
+		if (is_multicast_ether_addr(bpqdev->acpt_addr))
+			seq_printf(seq, "*\n");
+		else
+			seq_printf(seq, "%pM\n", bpqdev->acpt_addr);
+
+	}
+	return 0;
+}
+
+static const struct seq_operations bpq_seqops = {
+	.start = bpq_seq_start,
+	.next = bpq_seq_next,
+	.stop = bpq_seq_stop,
+	.show = bpq_seq_show,
+};
+
+/* ------------------------------------------------------------------------ */
+
+static const struct net_device_ops bpq_netdev_ops = {
+	.ndo_open	     = bpq_open,
+	.ndo_stop	     = bpq_close,
+	.ndo_start_xmit	     = bpq_xmit,
+	.ndo_set_mac_address = bpq_set_mac_address,
+	.ndo_do_ioctl	     = bpq_ioctl,
+};
+
+static void bpq_setup(struct net_device *dev)
+{
+	dev->netdev_ops	     = &bpq_netdev_ops;
+	dev->needs_free_netdev = true;
+
+	memcpy(dev->broadcast, &ax25_bcast, AX25_ADDR_LEN);
+	memcpy(dev->dev_addr,  &ax25_defaddr, AX25_ADDR_LEN);
+
+	dev->flags      = 0;
+	dev->features	= NETIF_F_LLTX;	/* Allow recursion */
+
+#if IS_ENABLED(CONFIG_AX25)
+	dev->header_ops      = &ax25_header_ops;
+#endif
+
+	dev->type            = ARPHRD_AX25;
+	dev->hard_header_len = AX25_MAX_HEADER_LEN + AX25_BPQ_HEADER_LEN;
+	dev->mtu             = AX25_DEF_PACLEN;
+	dev->addr_len        = AX25_ADDR_LEN;
+
+}
+
+/*
+ *	Setup a new device.
+ */
+static int bpq_new_device(struct net_device *edev)
+{
+	int err;
+	struct net_device *ndev;
+	struct bpqdev *bpq;
+
+	ndev = alloc_netdev(sizeof(struct bpqdev), "bpq%d", NET_NAME_UNKNOWN,
+			    bpq_setup);
+	if (!ndev)
+		return -ENOMEM;
+
+		
+	bpq = netdev_priv(ndev);
+	dev_hold(edev);
+	bpq->ethdev = edev;
+	bpq->axdev = ndev;
+
+	eth_broadcast_addr(bpq->dest_addr);
+	eth_broadcast_addr(bpq->acpt_addr);
+
+	err = register_netdevice(ndev);
+	if (err)
+		goto error;
+	bpq_set_lockdep_class(ndev);
+
+	/* List protected by RTNL */
+	list_add_rcu(&bpq->bpq_list, &bpq_devices);
+	return 0;
+
+ error:
+	dev_put(edev);
+	free_netdev(ndev);
+	return err;
+	
+}
+
+static void bpq_free_device(struct net_device *ndev)
+{
+	struct bpqdev *bpq = netdev_priv(ndev);
+
+	dev_put(bpq->ethdev);
+	list_del_rcu(&bpq->bpq_list);
+
+	unregister_netdevice(ndev);
+}
+
+/*
+ *	Handle device status changes.
+ */
+static int bpq_device_event(struct notifier_block *this,
+			    unsigned long event, void *ptr)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+
+	if (!net_eq(dev_net(dev), &init_net))
+		return NOTIFY_DONE;
+
+	if (!dev_is_ethdev(dev))
+		return NOTIFY_DONE;
+
+	switch (event) {
+	case NETDEV_UP:		/* new ethernet device -> new BPQ interface */
+		if (bpq_get_ax25_dev(dev) == NULL)
+			bpq_new_device(dev);
+		break;
+
+	case NETDEV_DOWN:	/* ethernet device closed -> close BPQ interface */
+		if ((dev = bpq_get_ax25_dev(dev)) != NULL)
+			dev_close(dev);
+		break;
+
+	case NETDEV_UNREGISTER:	/* ethernet device removed -> free BPQ interface */
+		if ((dev = bpq_get_ax25_dev(dev)) != NULL)
+			bpq_free_device(dev);
+		break;
+	default:
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/*
+ * Initialize driver. To be called from af_ax25 if not compiled as a
+ * module
+ */
+static int __init bpq_init_driver(void)
+{
+#ifdef CONFIG_PROC_FS
+	if (!proc_create_seq("bpqether", 0444, init_net.proc_net, &bpq_seqops)) {
+		printk(KERN_ERR
+			"bpq: cannot create /proc/net/bpqether entry.\n");
+		return -ENOENT;
+	}
+#endif  /* CONFIG_PROC_FS */
+
+	dev_add_pack(&bpq_packet_type);
+
+	register_netdevice_notifier(&bpq_dev_notifier);
+
+	printk(banner);
+
+	return 0;
+}
+
+static void __exit bpq_cleanup_driver(void)
+{
+	struct bpqdev *bpq;
+
+	dev_remove_pack(&bpq_packet_type);
+
+	unregister_netdevice_notifier(&bpq_dev_notifier);
+
+	remove_proc_entry("bpqether", init_net.proc_net);
+
+	rtnl_lock();
+	while (!list_empty(&bpq_devices)) {
+		bpq = list_entry(bpq_devices.next, struct bpqdev, bpq_list);
+		bpq_free_device(bpq->axdev);
+	}
+	rtnl_unlock();
+}
+
+MODULE_AUTHOR("Joerg Reuter DL1BKE <jreuter@yaina.de>");
+MODULE_DESCRIPTION("Transmit and receive AX.25 packets over Ethernet");
+MODULE_LICENSE("GPL");
+module_init(bpq_init_driver);
+module_exit(bpq_cleanup_driver);
diff --git a/drivers/net/hamradio/dmascc.c b/drivers/net/hamradio/dmascc.c
new file mode 100644
index 0000000..cde4120
--- /dev/null
+++ b/drivers/net/hamradio/dmascc.c
@@ -0,0 +1,1465 @@
+/*
+ * Driver for high-speed SCC boards (those with DMA support)
+ * Copyright (C) 1997-2000 Klaus Kudielka
+ *
+ * S5SCC/DMA support by Janko Koleznik S52HI
+ *
+ * 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.
+ */
+
+
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/in.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/rtnetlink.h>
+#include <linux/sockios.h>
+#include <linux/workqueue.h>
+#include <linux/atomic.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <linux/uaccess.h>
+#include <net/ax25.h>
+#include "z8530.h"
+
+
+/* Number of buffers per channel */
+
+#define NUM_TX_BUF      2	/* NUM_TX_BUF >= 1 (min. 2 recommended) */
+#define NUM_RX_BUF      6	/* NUM_RX_BUF >= 1 (min. 2 recommended) */
+#define BUF_SIZE        1576	/* BUF_SIZE >= mtu + hard_header_len */
+
+
+/* Cards supported */
+
+#define HW_PI           { "Ottawa PI", 0x300, 0x20, 0x10, 8, \
+                            0, 8, 1843200, 3686400 }
+#define HW_PI2          { "Ottawa PI2", 0x300, 0x20, 0x10, 8, \
+			    0, 8, 3686400, 7372800 }
+#define HW_TWIN         { "Gracilis PackeTwin", 0x200, 0x10, 0x10, 32, \
+			    0, 4, 6144000, 6144000 }
+#define HW_S5           { "S5SCC/DMA", 0x200, 0x10, 0x10, 32, \
+                          0, 8, 4915200, 9830400 }
+
+#define HARDWARE        { HW_PI, HW_PI2, HW_TWIN, HW_S5 }
+
+#define TMR_0_HZ        25600	/* Frequency of timer 0 */
+
+#define TYPE_PI         0
+#define TYPE_PI2        1
+#define TYPE_TWIN       2
+#define TYPE_S5         3
+#define NUM_TYPES       4
+
+#define MAX_NUM_DEVS    32
+
+
+/* SCC chips supported */
+
+#define Z8530           0
+#define Z85C30          1
+#define Z85230          2
+
+#define CHIPNAMES       { "Z8530", "Z85C30", "Z85230" }
+
+
+/* I/O registers */
+
+/* 8530 registers relative to card base */
+#define SCCB_CMD        0x00
+#define SCCB_DATA       0x01
+#define SCCA_CMD        0x02
+#define SCCA_DATA       0x03
+
+/* 8253/8254 registers relative to card base */
+#define TMR_CNT0        0x00
+#define TMR_CNT1        0x01
+#define TMR_CNT2        0x02
+#define TMR_CTRL        0x03
+
+/* Additional PI/PI2 registers relative to card base */
+#define PI_DREQ_MASK    0x04
+
+/* Additional PackeTwin registers relative to card base */
+#define TWIN_INT_REG    0x08
+#define TWIN_CLR_TMR1   0x09
+#define TWIN_CLR_TMR2   0x0a
+#define TWIN_SPARE_1    0x0b
+#define TWIN_DMA_CFG    0x08
+#define TWIN_SERIAL_CFG 0x09
+#define TWIN_DMA_CLR_FF 0x0a
+#define TWIN_SPARE_2    0x0b
+
+
+/* PackeTwin I/O register values */
+
+/* INT_REG */
+#define TWIN_SCC_MSK       0x01
+#define TWIN_TMR1_MSK      0x02
+#define TWIN_TMR2_MSK      0x04
+#define TWIN_INT_MSK       0x07
+
+/* SERIAL_CFG */
+#define TWIN_DTRA_ON       0x01
+#define TWIN_DTRB_ON       0x02
+#define TWIN_EXTCLKA       0x04
+#define TWIN_EXTCLKB       0x08
+#define TWIN_LOOPA_ON      0x10
+#define TWIN_LOOPB_ON      0x20
+#define TWIN_EI            0x80
+
+/* DMA_CFG */
+#define TWIN_DMA_HDX_T1    0x08
+#define TWIN_DMA_HDX_R1    0x0a
+#define TWIN_DMA_HDX_T3    0x14
+#define TWIN_DMA_HDX_R3    0x16
+#define TWIN_DMA_FDX_T3R1  0x1b
+#define TWIN_DMA_FDX_T1R3  0x1d
+
+
+/* Status values */
+
+#define IDLE      0
+#define TX_HEAD   1
+#define TX_DATA   2
+#define TX_PAUSE  3
+#define TX_TAIL   4
+#define RTS_OFF   5
+#define WAIT      6
+#define DCD_ON    7
+#define RX_ON     8
+#define DCD_OFF   9
+
+
+/* Ioctls */
+
+#define SIOCGSCCPARAM SIOCDEVPRIVATE
+#define SIOCSSCCPARAM (SIOCDEVPRIVATE+1)
+
+
+/* Data types */
+
+struct scc_param {
+	int pclk_hz;		/* frequency of BRG input (don't change) */
+	int brg_tc;		/* BRG terminal count; BRG disabled if < 0 */
+	int nrzi;		/* 0 (nrz), 1 (nrzi) */
+	int clocks;		/* see dmascc_cfg documentation */
+	int txdelay;		/* [1/TMR_0_HZ] */
+	int txtimeout;		/* [1/HZ] */
+	int txtail;		/* [1/TMR_0_HZ] */
+	int waittime;		/* [1/TMR_0_HZ] */
+	int slottime;		/* [1/TMR_0_HZ] */
+	int persist;		/* 1 ... 256 */
+	int dma;		/* -1 (disable), 0, 1, 3 */
+	int txpause;		/* [1/TMR_0_HZ] */
+	int rtsoff;		/* [1/TMR_0_HZ] */
+	int dcdon;		/* [1/TMR_0_HZ] */
+	int dcdoff;		/* [1/TMR_0_HZ] */
+};
+
+struct scc_hardware {
+	char *name;
+	int io_region;
+	int io_delta;
+	int io_size;
+	int num_devs;
+	int scc_offset;
+	int tmr_offset;
+	int tmr_hz;
+	int pclk_hz;
+};
+
+struct scc_priv {
+	int type;
+	int chip;
+	struct net_device *dev;
+	struct scc_info *info;
+
+	int channel;
+	int card_base, scc_cmd, scc_data;
+	int tmr_cnt, tmr_ctrl, tmr_mode;
+	struct scc_param param;
+	char rx_buf[NUM_RX_BUF][BUF_SIZE];
+	int rx_len[NUM_RX_BUF];
+	int rx_ptr;
+	struct work_struct rx_work;
+	int rx_head, rx_tail, rx_count;
+	int rx_over;
+	char tx_buf[NUM_TX_BUF][BUF_SIZE];
+	int tx_len[NUM_TX_BUF];
+	int tx_ptr;
+	int tx_head, tx_tail, tx_count;
+	int state;
+	unsigned long tx_start;
+	int rr0;
+	spinlock_t *register_lock;	/* Per scc_info */
+	spinlock_t ring_lock;
+};
+
+struct scc_info {
+	int irq_used;
+	int twin_serial_cfg;
+	struct net_device *dev[2];
+	struct scc_priv priv[2];
+	struct scc_info *next;
+	spinlock_t register_lock;	/* Per device register lock */
+};
+
+
+/* Function declarations */
+static int setup_adapter(int card_base, int type, int n) __init;
+
+static void write_scc(struct scc_priv *priv, int reg, int val);
+static void write_scc_data(struct scc_priv *priv, int val, int fast);
+static int read_scc(struct scc_priv *priv, int reg);
+static int read_scc_data(struct scc_priv *priv);
+
+static int scc_open(struct net_device *dev);
+static int scc_close(struct net_device *dev);
+static int scc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
+static int scc_send_packet(struct sk_buff *skb, struct net_device *dev);
+static int scc_set_mac_address(struct net_device *dev, void *sa);
+
+static inline void tx_on(struct scc_priv *priv);
+static inline void rx_on(struct scc_priv *priv);
+static inline void rx_off(struct scc_priv *priv);
+static void start_timer(struct scc_priv *priv, int t, int r15);
+static inline unsigned char random(void);
+
+static inline void z8530_isr(struct scc_info *info);
+static irqreturn_t scc_isr(int irq, void *dev_id);
+static void rx_isr(struct scc_priv *priv);
+static void special_condition(struct scc_priv *priv, int rc);
+static void rx_bh(struct work_struct *);
+static void tx_isr(struct scc_priv *priv);
+static void es_isr(struct scc_priv *priv);
+static void tm_isr(struct scc_priv *priv);
+
+
+/* Initialization variables */
+
+static int io[MAX_NUM_DEVS] __initdata = { 0, };
+
+/* Beware! hw[] is also used in dmascc_exit(). */
+static struct scc_hardware hw[NUM_TYPES] = HARDWARE;
+
+
+/* Global variables */
+
+static struct scc_info *first;
+static unsigned long rand;
+
+
+MODULE_AUTHOR("Klaus Kudielka");
+MODULE_DESCRIPTION("Driver for high-speed SCC boards");
+module_param_hw_array(io, int, ioport, NULL, 0);
+MODULE_LICENSE("GPL");
+
+static void __exit dmascc_exit(void)
+{
+	int i;
+	struct scc_info *info;
+
+	while (first) {
+		info = first;
+
+		/* Unregister devices */
+		for (i = 0; i < 2; i++)
+			unregister_netdev(info->dev[i]);
+
+		/* Reset board */
+		if (info->priv[0].type == TYPE_TWIN)
+			outb(0, info->dev[0]->base_addr + TWIN_SERIAL_CFG);
+		write_scc(&info->priv[0], R9, FHWRES);
+		release_region(info->dev[0]->base_addr,
+			       hw[info->priv[0].type].io_size);
+
+		for (i = 0; i < 2; i++)
+			free_netdev(info->dev[i]);
+
+		/* Free memory */
+		first = info->next;
+		kfree(info);
+	}
+}
+
+static int __init dmascc_init(void)
+{
+	int h, i, j, n;
+	int base[MAX_NUM_DEVS], tcmd[MAX_NUM_DEVS], t0[MAX_NUM_DEVS],
+	    t1[MAX_NUM_DEVS];
+	unsigned t_val;
+	unsigned long time, start[MAX_NUM_DEVS], delay[MAX_NUM_DEVS],
+	    counting[MAX_NUM_DEVS];
+
+	/* Initialize random number generator */
+	rand = jiffies;
+	/* Cards found = 0 */
+	n = 0;
+	/* Warning message */
+	if (!io[0])
+		printk(KERN_INFO "dmascc: autoprobing (dangerous)\n");
+
+	/* Run autodetection for each card type */
+	for (h = 0; h < NUM_TYPES; h++) {
+
+		if (io[0]) {
+			/* User-specified I/O address regions */
+			for (i = 0; i < hw[h].num_devs; i++)
+				base[i] = 0;
+			for (i = 0; i < MAX_NUM_DEVS && io[i]; i++) {
+				j = (io[i] -
+				     hw[h].io_region) / hw[h].io_delta;
+				if (j >= 0 && j < hw[h].num_devs &&
+				    hw[h].io_region +
+				    j * hw[h].io_delta == io[i]) {
+					base[j] = io[i];
+				}
+			}
+		} else {
+			/* Default I/O address regions */
+			for (i = 0; i < hw[h].num_devs; i++) {
+				base[i] =
+				    hw[h].io_region + i * hw[h].io_delta;
+			}
+		}
+
+		/* Check valid I/O address regions */
+		for (i = 0; i < hw[h].num_devs; i++)
+			if (base[i]) {
+				if (!request_region
+				    (base[i], hw[h].io_size, "dmascc"))
+					base[i] = 0;
+				else {
+					tcmd[i] =
+					    base[i] + hw[h].tmr_offset +
+					    TMR_CTRL;
+					t0[i] =
+					    base[i] + hw[h].tmr_offset +
+					    TMR_CNT0;
+					t1[i] =
+					    base[i] + hw[h].tmr_offset +
+					    TMR_CNT1;
+				}
+			}
+
+		/* Start timers */
+		for (i = 0; i < hw[h].num_devs; i++)
+			if (base[i]) {
+				/* Timer 0: LSB+MSB, Mode 3, TMR_0_HZ */
+				outb(0x36, tcmd[i]);
+				outb((hw[h].tmr_hz / TMR_0_HZ) & 0xFF,
+				     t0[i]);
+				outb((hw[h].tmr_hz / TMR_0_HZ) >> 8,
+				     t0[i]);
+				/* Timer 1: LSB+MSB, Mode 0, HZ/10 */
+				outb(0x70, tcmd[i]);
+				outb((TMR_0_HZ / HZ * 10) & 0xFF, t1[i]);
+				outb((TMR_0_HZ / HZ * 10) >> 8, t1[i]);
+				start[i] = jiffies;
+				delay[i] = 0;
+				counting[i] = 1;
+				/* Timer 2: LSB+MSB, Mode 0 */
+				outb(0xb0, tcmd[i]);
+			}
+		time = jiffies;
+		/* Wait until counter registers are loaded */
+		udelay(2000000 / TMR_0_HZ);
+
+		/* Timing loop */
+		while (jiffies - time < 13) {
+			for (i = 0; i < hw[h].num_devs; i++)
+				if (base[i] && counting[i]) {
+					/* Read back Timer 1: latch; read LSB; read MSB */
+					outb(0x40, tcmd[i]);
+					t_val =
+					    inb(t1[i]) + (inb(t1[i]) << 8);
+					/* Also check whether counter did wrap */
+					if (t_val == 0 ||
+					    t_val > TMR_0_HZ / HZ * 10)
+						counting[i] = 0;
+					delay[i] = jiffies - start[i];
+				}
+		}
+
+		/* Evaluate measurements */
+		for (i = 0; i < hw[h].num_devs; i++)
+			if (base[i]) {
+				if ((delay[i] >= 9 && delay[i] <= 11) &&
+				    /* Ok, we have found an adapter */
+				    (setup_adapter(base[i], h, n) == 0))
+					n++;
+				else
+					release_region(base[i],
+						       hw[h].io_size);
+			}
+
+	}			/* NUM_TYPES */
+
+	/* If any adapter was successfully initialized, return ok */
+	if (n)
+		return 0;
+
+	/* If no adapter found, return error */
+	printk(KERN_INFO "dmascc: no adapters found\n");
+	return -EIO;
+}
+
+module_init(dmascc_init);
+module_exit(dmascc_exit);
+
+static void __init dev_setup(struct net_device *dev)
+{
+	dev->type = ARPHRD_AX25;
+	dev->hard_header_len = AX25_MAX_HEADER_LEN;
+	dev->mtu = 1500;
+	dev->addr_len = AX25_ADDR_LEN;
+	dev->tx_queue_len = 64;
+	memcpy(dev->broadcast, &ax25_bcast, AX25_ADDR_LEN);
+	memcpy(dev->dev_addr, &ax25_defaddr, AX25_ADDR_LEN);
+}
+
+static const struct net_device_ops scc_netdev_ops = {
+	.ndo_open = scc_open,
+	.ndo_stop = scc_close,
+	.ndo_start_xmit = scc_send_packet,
+	.ndo_do_ioctl = scc_ioctl,
+	.ndo_set_mac_address = scc_set_mac_address,
+};
+
+static int __init setup_adapter(int card_base, int type, int n)
+{
+	int i, irq, chip, err;
+	struct scc_info *info;
+	struct net_device *dev;
+	struct scc_priv *priv;
+	unsigned long time;
+	unsigned int irqs;
+	int tmr_base = card_base + hw[type].tmr_offset;
+	int scc_base = card_base + hw[type].scc_offset;
+	char *chipnames[] = CHIPNAMES;
+
+	/* Initialize what is necessary for write_scc and write_scc_data */
+	info = kzalloc(sizeof(struct scc_info), GFP_KERNEL | GFP_DMA);
+	if (!info) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	info->dev[0] = alloc_netdev(0, "", NET_NAME_UNKNOWN, dev_setup);
+	if (!info->dev[0]) {
+		printk(KERN_ERR "dmascc: "
+		       "could not allocate memory for %s at %#3x\n",
+		       hw[type].name, card_base);
+		err = -ENOMEM;
+		goto out1;
+	}
+
+	info->dev[1] = alloc_netdev(0, "", NET_NAME_UNKNOWN, dev_setup);
+	if (!info->dev[1]) {
+		printk(KERN_ERR "dmascc: "
+		       "could not allocate memory for %s at %#3x\n",
+		       hw[type].name, card_base);
+		err = -ENOMEM;
+		goto out2;
+	}
+	spin_lock_init(&info->register_lock);
+
+	priv = &info->priv[0];
+	priv->type = type;
+	priv->card_base = card_base;
+	priv->scc_cmd = scc_base + SCCA_CMD;
+	priv->scc_data = scc_base + SCCA_DATA;
+	priv->register_lock = &info->register_lock;
+
+	/* Reset SCC */
+	write_scc(priv, R9, FHWRES | MIE | NV);
+
+	/* Determine type of chip by enabling SDLC/HDLC enhancements */
+	write_scc(priv, R15, SHDLCE);
+	if (!read_scc(priv, R15)) {
+		/* WR7' not present. This is an ordinary Z8530 SCC. */
+		chip = Z8530;
+	} else {
+		/* Put one character in TX FIFO */
+		write_scc_data(priv, 0, 0);
+		if (read_scc(priv, R0) & Tx_BUF_EMP) {
+			/* TX FIFO not full. This is a Z85230 ESCC with a 4-byte FIFO. */
+			chip = Z85230;
+		} else {
+			/* TX FIFO full. This is a Z85C30 SCC with a 1-byte FIFO. */
+			chip = Z85C30;
+		}
+	}
+	write_scc(priv, R15, 0);
+
+	/* Start IRQ auto-detection */
+	irqs = probe_irq_on();
+
+	/* Enable interrupts */
+	if (type == TYPE_TWIN) {
+		outb(0, card_base + TWIN_DMA_CFG);
+		inb(card_base + TWIN_CLR_TMR1);
+		inb(card_base + TWIN_CLR_TMR2);
+		info->twin_serial_cfg = TWIN_EI;
+		outb(info->twin_serial_cfg, card_base + TWIN_SERIAL_CFG);
+	} else {
+		write_scc(priv, R15, CTSIE);
+		write_scc(priv, R0, RES_EXT_INT);
+		write_scc(priv, R1, EXT_INT_ENAB);
+	}
+
+	/* Start timer */
+	outb(1, tmr_base + TMR_CNT1);
+	outb(0, tmr_base + TMR_CNT1);
+
+	/* Wait and detect IRQ */
+	time = jiffies;
+	while (jiffies - time < 2 + HZ / TMR_0_HZ);
+	irq = probe_irq_off(irqs);
+
+	/* Clear pending interrupt, disable interrupts */
+	if (type == TYPE_TWIN) {
+		inb(card_base + TWIN_CLR_TMR1);
+	} else {
+		write_scc(priv, R1, 0);
+		write_scc(priv, R15, 0);
+		write_scc(priv, R0, RES_EXT_INT);
+	}
+
+	if (irq <= 0) {
+		printk(KERN_ERR
+		       "dmascc: could not find irq of %s at %#3x (irq=%d)\n",
+		       hw[type].name, card_base, irq);
+		err = -ENODEV;
+		goto out3;
+	}
+
+	/* Set up data structures */
+	for (i = 0; i < 2; i++) {
+		dev = info->dev[i];
+		priv = &info->priv[i];
+		priv->type = type;
+		priv->chip = chip;
+		priv->dev = dev;
+		priv->info = info;
+		priv->channel = i;
+		spin_lock_init(&priv->ring_lock);
+		priv->register_lock = &info->register_lock;
+		priv->card_base = card_base;
+		priv->scc_cmd = scc_base + (i ? SCCB_CMD : SCCA_CMD);
+		priv->scc_data = scc_base + (i ? SCCB_DATA : SCCA_DATA);
+		priv->tmr_cnt = tmr_base + (i ? TMR_CNT2 : TMR_CNT1);
+		priv->tmr_ctrl = tmr_base + TMR_CTRL;
+		priv->tmr_mode = i ? 0xb0 : 0x70;
+		priv->param.pclk_hz = hw[type].pclk_hz;
+		priv->param.brg_tc = -1;
+		priv->param.clocks = TCTRxCP | RCRTxCP;
+		priv->param.persist = 256;
+		priv->param.dma = -1;
+		INIT_WORK(&priv->rx_work, rx_bh);
+		dev->ml_priv = priv;
+		snprintf(dev->name, sizeof(dev->name), "dmascc%i", 2 * n + i);
+		dev->base_addr = card_base;
+		dev->irq = irq;
+		dev->netdev_ops = &scc_netdev_ops;
+		dev->header_ops = &ax25_header_ops;
+	}
+	if (register_netdev(info->dev[0])) {
+		printk(KERN_ERR "dmascc: could not register %s\n",
+		       info->dev[0]->name);
+		err = -ENODEV;
+		goto out3;
+	}
+	if (register_netdev(info->dev[1])) {
+		printk(KERN_ERR "dmascc: could not register %s\n",
+		       info->dev[1]->name);
+		err = -ENODEV;
+		goto out4;
+	}
+
+
+	info->next = first;
+	first = info;
+	printk(KERN_INFO "dmascc: found %s (%s) at %#3x, irq %d\n",
+	       hw[type].name, chipnames[chip], card_base, irq);
+	return 0;
+
+      out4:
+	unregister_netdev(info->dev[0]);
+      out3:
+	if (info->priv[0].type == TYPE_TWIN)
+		outb(0, info->dev[0]->base_addr + TWIN_SERIAL_CFG);
+	write_scc(&info->priv[0], R9, FHWRES);
+	free_netdev(info->dev[1]);
+      out2:
+	free_netdev(info->dev[0]);
+      out1:
+	kfree(info);
+      out:
+	return err;
+}
+
+
+/* Driver functions */
+
+static void write_scc(struct scc_priv *priv, int reg, int val)
+{
+	unsigned long flags;
+	switch (priv->type) {
+	case TYPE_S5:
+		if (reg)
+			outb(reg, priv->scc_cmd);
+		outb(val, priv->scc_cmd);
+		return;
+	case TYPE_TWIN:
+		if (reg)
+			outb_p(reg, priv->scc_cmd);
+		outb_p(val, priv->scc_cmd);
+		return;
+	default:
+		spin_lock_irqsave(priv->register_lock, flags);
+		outb_p(0, priv->card_base + PI_DREQ_MASK);
+		if (reg)
+			outb_p(reg, priv->scc_cmd);
+		outb_p(val, priv->scc_cmd);
+		outb(1, priv->card_base + PI_DREQ_MASK);
+		spin_unlock_irqrestore(priv->register_lock, flags);
+		return;
+	}
+}
+
+
+static void write_scc_data(struct scc_priv *priv, int val, int fast)
+{
+	unsigned long flags;
+	switch (priv->type) {
+	case TYPE_S5:
+		outb(val, priv->scc_data);
+		return;
+	case TYPE_TWIN:
+		outb_p(val, priv->scc_data);
+		return;
+	default:
+		if (fast)
+			outb_p(val, priv->scc_data);
+		else {
+			spin_lock_irqsave(priv->register_lock, flags);
+			outb_p(0, priv->card_base + PI_DREQ_MASK);
+			outb_p(val, priv->scc_data);
+			outb(1, priv->card_base + PI_DREQ_MASK);
+			spin_unlock_irqrestore(priv->register_lock, flags);
+		}
+		return;
+	}
+}
+
+
+static int read_scc(struct scc_priv *priv, int reg)
+{
+	int rc;
+	unsigned long flags;
+	switch (priv->type) {
+	case TYPE_S5:
+		if (reg)
+			outb(reg, priv->scc_cmd);
+		return inb(priv->scc_cmd);
+	case TYPE_TWIN:
+		if (reg)
+			outb_p(reg, priv->scc_cmd);
+		return inb_p(priv->scc_cmd);
+	default:
+		spin_lock_irqsave(priv->register_lock, flags);
+		outb_p(0, priv->card_base + PI_DREQ_MASK);
+		if (reg)
+			outb_p(reg, priv->scc_cmd);
+		rc = inb_p(priv->scc_cmd);
+		outb(1, priv->card_base + PI_DREQ_MASK);
+		spin_unlock_irqrestore(priv->register_lock, flags);
+		return rc;
+	}
+}
+
+
+static int read_scc_data(struct scc_priv *priv)
+{
+	int rc;
+	unsigned long flags;
+	switch (priv->type) {
+	case TYPE_S5:
+		return inb(priv->scc_data);
+	case TYPE_TWIN:
+		return inb_p(priv->scc_data);
+	default:
+		spin_lock_irqsave(priv->register_lock, flags);
+		outb_p(0, priv->card_base + PI_DREQ_MASK);
+		rc = inb_p(priv->scc_data);
+		outb(1, priv->card_base + PI_DREQ_MASK);
+		spin_unlock_irqrestore(priv->register_lock, flags);
+		return rc;
+	}
+}
+
+
+static int scc_open(struct net_device *dev)
+{
+	struct scc_priv *priv = dev->ml_priv;
+	struct scc_info *info = priv->info;
+	int card_base = priv->card_base;
+
+	/* Request IRQ if not already used by other channel */
+	if (!info->irq_used) {
+		if (request_irq(dev->irq, scc_isr, 0, "dmascc", info)) {
+			return -EAGAIN;
+		}
+	}
+	info->irq_used++;
+
+	/* Request DMA if required */
+	if (priv->param.dma >= 0) {
+		if (request_dma(priv->param.dma, "dmascc")) {
+			if (--info->irq_used == 0)
+				free_irq(dev->irq, info);
+			return -EAGAIN;
+		} else {
+			unsigned long flags = claim_dma_lock();
+			clear_dma_ff(priv->param.dma);
+			release_dma_lock(flags);
+		}
+	}
+
+	/* Initialize local variables */
+	priv->rx_ptr = 0;
+	priv->rx_over = 0;
+	priv->rx_head = priv->rx_tail = priv->rx_count = 0;
+	priv->state = IDLE;
+	priv->tx_head = priv->tx_tail = priv->tx_count = 0;
+	priv->tx_ptr = 0;
+
+	/* Reset channel */
+	write_scc(priv, R9, (priv->channel ? CHRB : CHRA) | MIE | NV);
+	/* X1 clock, SDLC mode */
+	write_scc(priv, R4, SDLC | X1CLK);
+	/* DMA */
+	write_scc(priv, R1, EXT_INT_ENAB | WT_FN_RDYFN);
+	/* 8 bit RX char, RX disable */
+	write_scc(priv, R3, Rx8);
+	/* 8 bit TX char, TX disable */
+	write_scc(priv, R5, Tx8);
+	/* SDLC address field */
+	write_scc(priv, R6, 0);
+	/* SDLC flag */
+	write_scc(priv, R7, FLAG);
+	switch (priv->chip) {
+	case Z85C30:
+		/* Select WR7' */
+		write_scc(priv, R15, SHDLCE);
+		/* Auto EOM reset */
+		write_scc(priv, R7, AUTOEOM);
+		write_scc(priv, R15, 0);
+		break;
+	case Z85230:
+		/* Select WR7' */
+		write_scc(priv, R15, SHDLCE);
+		/* The following bits are set (see 2.5.2.1):
+		   - Automatic EOM reset
+		   - Interrupt request if RX FIFO is half full
+		   This bit should be ignored in DMA mode (according to the
+		   documentation), but actually isn't. The receiver doesn't work if
+		   it is set. Thus, we have to clear it in DMA mode.
+		   - Interrupt/DMA request if TX FIFO is completely empty
+		   a) If set, the ESCC behaves as if it had no TX FIFO (Z85C30
+		   compatibility).
+		   b) If cleared, DMA requests may follow each other very quickly,
+		   filling up the TX FIFO.
+		   Advantage: TX works even in case of high bus latency.
+		   Disadvantage: Edge-triggered DMA request circuitry may miss
+		   a request. No more data is delivered, resulting
+		   in a TX FIFO underrun.
+		   Both PI2 and S5SCC/DMA seem to work fine with TXFIFOE cleared.
+		   The PackeTwin doesn't. I don't know about the PI, but let's
+		   assume it behaves like the PI2.
+		 */
+		if (priv->param.dma >= 0) {
+			if (priv->type == TYPE_TWIN)
+				write_scc(priv, R7, AUTOEOM | TXFIFOE);
+			else
+				write_scc(priv, R7, AUTOEOM);
+		} else {
+			write_scc(priv, R7, AUTOEOM | RXFIFOH);
+		}
+		write_scc(priv, R15, 0);
+		break;
+	}
+	/* Preset CRC, NRZ(I) encoding */
+	write_scc(priv, R10, CRCPS | (priv->param.nrzi ? NRZI : NRZ));
+
+	/* Configure baud rate generator */
+	if (priv->param.brg_tc >= 0) {
+		/* Program BR generator */
+		write_scc(priv, R12, priv->param.brg_tc & 0xFF);
+		write_scc(priv, R13, (priv->param.brg_tc >> 8) & 0xFF);
+		/* BRG source = SYS CLK; enable BRG; DTR REQ function (required by
+		   PackeTwin, not connected on the PI2); set DPLL source to BRG */
+		write_scc(priv, R14, SSBR | DTRREQ | BRSRC | BRENABL);
+		/* Enable DPLL */
+		write_scc(priv, R14, SEARCH | DTRREQ | BRSRC | BRENABL);
+	} else {
+		/* Disable BR generator */
+		write_scc(priv, R14, DTRREQ | BRSRC);
+	}
+
+	/* Configure clocks */
+	if (priv->type == TYPE_TWIN) {
+		/* Disable external TX clock receiver */
+		outb((info->twin_serial_cfg &=
+		      ~(priv->channel ? TWIN_EXTCLKB : TWIN_EXTCLKA)),
+		     card_base + TWIN_SERIAL_CFG);
+	}
+	write_scc(priv, R11, priv->param.clocks);
+	if ((priv->type == TYPE_TWIN) && !(priv->param.clocks & TRxCOI)) {
+		/* Enable external TX clock receiver */
+		outb((info->twin_serial_cfg |=
+		      (priv->channel ? TWIN_EXTCLKB : TWIN_EXTCLKA)),
+		     card_base + TWIN_SERIAL_CFG);
+	}
+
+	/* Configure PackeTwin */
+	if (priv->type == TYPE_TWIN) {
+		/* Assert DTR, enable interrupts */
+		outb((info->twin_serial_cfg |= TWIN_EI |
+		      (priv->channel ? TWIN_DTRB_ON : TWIN_DTRA_ON)),
+		     card_base + TWIN_SERIAL_CFG);
+	}
+
+	/* Read current status */
+	priv->rr0 = read_scc(priv, R0);
+	/* Enable DCD interrupt */
+	write_scc(priv, R15, DCDIE);
+
+	netif_start_queue(dev);
+
+	return 0;
+}
+
+
+static int scc_close(struct net_device *dev)
+{
+	struct scc_priv *priv = dev->ml_priv;
+	struct scc_info *info = priv->info;
+	int card_base = priv->card_base;
+
+	netif_stop_queue(dev);
+
+	if (priv->type == TYPE_TWIN) {
+		/* Drop DTR */
+		outb((info->twin_serial_cfg &=
+		      (priv->channel ? ~TWIN_DTRB_ON : ~TWIN_DTRA_ON)),
+		     card_base + TWIN_SERIAL_CFG);
+	}
+
+	/* Reset channel, free DMA and IRQ */
+	write_scc(priv, R9, (priv->channel ? CHRB : CHRA) | MIE | NV);
+	if (priv->param.dma >= 0) {
+		if (priv->type == TYPE_TWIN)
+			outb(0, card_base + TWIN_DMA_CFG);
+		free_dma(priv->param.dma);
+	}
+	if (--info->irq_used == 0)
+		free_irq(dev->irq, info);
+
+	return 0;
+}
+
+
+static int scc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct scc_priv *priv = dev->ml_priv;
+
+	switch (cmd) {
+	case SIOCGSCCPARAM:
+		if (copy_to_user
+		    (ifr->ifr_data, &priv->param,
+		     sizeof(struct scc_param)))
+			return -EFAULT;
+		return 0;
+	case SIOCSSCCPARAM:
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+		if (netif_running(dev))
+			return -EAGAIN;
+		if (copy_from_user
+		    (&priv->param, ifr->ifr_data,
+		     sizeof(struct scc_param)))
+			return -EFAULT;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+
+static int scc_send_packet(struct sk_buff *skb, struct net_device *dev)
+{
+	struct scc_priv *priv = dev->ml_priv;
+	unsigned long flags;
+	int i;
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return ax25_ip_xmit(skb);
+
+	/* Temporarily stop the scheduler feeding us packets */
+	netif_stop_queue(dev);
+
+	/* Transfer data to DMA buffer */
+	i = priv->tx_head;
+	skb_copy_from_linear_data_offset(skb, 1, priv->tx_buf[i], skb->len - 1);
+	priv->tx_len[i] = skb->len - 1;
+
+	/* Clear interrupts while we touch our circular buffers */
+
+	spin_lock_irqsave(&priv->ring_lock, flags);
+	/* Move the ring buffer's head */
+	priv->tx_head = (i + 1) % NUM_TX_BUF;
+	priv->tx_count++;
+
+	/* If we just filled up the last buffer, leave queue stopped.
+	   The higher layers must wait until we have a DMA buffer
+	   to accept the data. */
+	if (priv->tx_count < NUM_TX_BUF)
+		netif_wake_queue(dev);
+
+	/* Set new TX state */
+	if (priv->state == IDLE) {
+		/* Assert RTS, start timer */
+		priv->state = TX_HEAD;
+		priv->tx_start = jiffies;
+		write_scc(priv, R5, TxCRC_ENAB | RTS | TxENAB | Tx8);
+		write_scc(priv, R15, 0);
+		start_timer(priv, priv->param.txdelay, 0);
+	}
+
+	/* Turn interrupts back on and free buffer */
+	spin_unlock_irqrestore(&priv->ring_lock, flags);
+	dev_kfree_skb(skb);
+
+	return NETDEV_TX_OK;
+}
+
+
+static int scc_set_mac_address(struct net_device *dev, void *sa)
+{
+	memcpy(dev->dev_addr, ((struct sockaddr *) sa)->sa_data,
+	       dev->addr_len);
+	return 0;
+}
+
+
+static inline void tx_on(struct scc_priv *priv)
+{
+	int i, n;
+	unsigned long flags;
+
+	if (priv->param.dma >= 0) {
+		n = (priv->chip == Z85230) ? 3 : 1;
+		/* Program DMA controller */
+		flags = claim_dma_lock();
+		set_dma_mode(priv->param.dma, DMA_MODE_WRITE);
+		set_dma_addr(priv->param.dma,
+			     (int) priv->tx_buf[priv->tx_tail] + n);
+		set_dma_count(priv->param.dma,
+			      priv->tx_len[priv->tx_tail] - n);
+		release_dma_lock(flags);
+		/* Enable TX underrun interrupt */
+		write_scc(priv, R15, TxUIE);
+		/* Configure DREQ */
+		if (priv->type == TYPE_TWIN)
+			outb((priv->param.dma ==
+			      1) ? TWIN_DMA_HDX_T1 : TWIN_DMA_HDX_T3,
+			     priv->card_base + TWIN_DMA_CFG);
+		else
+			write_scc(priv, R1,
+				  EXT_INT_ENAB | WT_FN_RDYFN |
+				  WT_RDY_ENAB);
+		/* Write first byte(s) */
+		spin_lock_irqsave(priv->register_lock, flags);
+		for (i = 0; i < n; i++)
+			write_scc_data(priv,
+				       priv->tx_buf[priv->tx_tail][i], 1);
+		enable_dma(priv->param.dma);
+		spin_unlock_irqrestore(priv->register_lock, flags);
+	} else {
+		write_scc(priv, R15, TxUIE);
+		write_scc(priv, R1,
+			  EXT_INT_ENAB | WT_FN_RDYFN | TxINT_ENAB);
+		tx_isr(priv);
+	}
+	/* Reset EOM latch if we do not have the AUTOEOM feature */
+	if (priv->chip == Z8530)
+		write_scc(priv, R0, RES_EOM_L);
+}
+
+
+static inline void rx_on(struct scc_priv *priv)
+{
+	unsigned long flags;
+
+	/* Clear RX FIFO */
+	while (read_scc(priv, R0) & Rx_CH_AV)
+		read_scc_data(priv);
+	priv->rx_over = 0;
+	if (priv->param.dma >= 0) {
+		/* Program DMA controller */
+		flags = claim_dma_lock();
+		set_dma_mode(priv->param.dma, DMA_MODE_READ);
+		set_dma_addr(priv->param.dma,
+			     (int) priv->rx_buf[priv->rx_head]);
+		set_dma_count(priv->param.dma, BUF_SIZE);
+		release_dma_lock(flags);
+		enable_dma(priv->param.dma);
+		/* Configure PackeTwin DMA */
+		if (priv->type == TYPE_TWIN) {
+			outb((priv->param.dma ==
+			      1) ? TWIN_DMA_HDX_R1 : TWIN_DMA_HDX_R3,
+			     priv->card_base + TWIN_DMA_CFG);
+		}
+		/* Sp. cond. intr. only, ext int enable, RX DMA enable */
+		write_scc(priv, R1, EXT_INT_ENAB | INT_ERR_Rx |
+			  WT_RDY_RT | WT_FN_RDYFN | WT_RDY_ENAB);
+	} else {
+		/* Reset current frame */
+		priv->rx_ptr = 0;
+		/* Intr. on all Rx characters and Sp. cond., ext int enable */
+		write_scc(priv, R1, EXT_INT_ENAB | INT_ALL_Rx | WT_RDY_RT |
+			  WT_FN_RDYFN);
+	}
+	write_scc(priv, R0, ERR_RES);
+	write_scc(priv, R3, RxENABLE | Rx8 | RxCRC_ENAB);
+}
+
+
+static inline void rx_off(struct scc_priv *priv)
+{
+	/* Disable receiver */
+	write_scc(priv, R3, Rx8);
+	/* Disable DREQ / RX interrupt */
+	if (priv->param.dma >= 0 && priv->type == TYPE_TWIN)
+		outb(0, priv->card_base + TWIN_DMA_CFG);
+	else
+		write_scc(priv, R1, EXT_INT_ENAB | WT_FN_RDYFN);
+	/* Disable DMA */
+	if (priv->param.dma >= 0)
+		disable_dma(priv->param.dma);
+}
+
+
+static void start_timer(struct scc_priv *priv, int t, int r15)
+{
+	outb(priv->tmr_mode, priv->tmr_ctrl);
+	if (t == 0) {
+		tm_isr(priv);
+	} else if (t > 0) {
+		outb(t & 0xFF, priv->tmr_cnt);
+		outb((t >> 8) & 0xFF, priv->tmr_cnt);
+		if (priv->type != TYPE_TWIN) {
+			write_scc(priv, R15, r15 | CTSIE);
+			priv->rr0 |= CTS;
+		}
+	}
+}
+
+
+static inline unsigned char random(void)
+{
+	/* See "Numerical Recipes in C", second edition, p. 284 */
+	rand = rand * 1664525L + 1013904223L;
+	return (unsigned char) (rand >> 24);
+}
+
+static inline void z8530_isr(struct scc_info *info)
+{
+	int is, i = 100;
+
+	while ((is = read_scc(&info->priv[0], R3)) && i--) {
+		if (is & CHARxIP) {
+			rx_isr(&info->priv[0]);
+		} else if (is & CHATxIP) {
+			tx_isr(&info->priv[0]);
+		} else if (is & CHAEXT) {
+			es_isr(&info->priv[0]);
+		} else if (is & CHBRxIP) {
+			rx_isr(&info->priv[1]);
+		} else if (is & CHBTxIP) {
+			tx_isr(&info->priv[1]);
+		} else {
+			es_isr(&info->priv[1]);
+		}
+		write_scc(&info->priv[0], R0, RES_H_IUS);
+		i++;
+	}
+	if (i < 0) {
+		printk(KERN_ERR "dmascc: stuck in ISR with RR3=0x%02x.\n",
+		       is);
+	}
+	/* Ok, no interrupts pending from this 8530. The INT line should
+	   be inactive now. */
+}
+
+
+static irqreturn_t scc_isr(int irq, void *dev_id)
+{
+	struct scc_info *info = dev_id;
+
+	spin_lock(info->priv[0].register_lock);
+	/* At this point interrupts are enabled, and the interrupt under service
+	   is already acknowledged, but masked off.
+
+	   Interrupt processing: We loop until we know that the IRQ line is
+	   low. If another positive edge occurs afterwards during the ISR,
+	   another interrupt will be triggered by the interrupt controller
+	   as soon as the IRQ level is enabled again (see asm/irq.h).
+
+	   Bottom-half handlers will be processed after scc_isr(). This is
+	   important, since we only have small ringbuffers and want new data
+	   to be fetched/delivered immediately. */
+
+	if (info->priv[0].type == TYPE_TWIN) {
+		int is, card_base = info->priv[0].card_base;
+		while ((is = ~inb(card_base + TWIN_INT_REG)) &
+		       TWIN_INT_MSK) {
+			if (is & TWIN_SCC_MSK) {
+				z8530_isr(info);
+			} else if (is & TWIN_TMR1_MSK) {
+				inb(card_base + TWIN_CLR_TMR1);
+				tm_isr(&info->priv[0]);
+			} else {
+				inb(card_base + TWIN_CLR_TMR2);
+				tm_isr(&info->priv[1]);
+			}
+		}
+	} else
+		z8530_isr(info);
+	spin_unlock(info->priv[0].register_lock);
+	return IRQ_HANDLED;
+}
+
+
+static void rx_isr(struct scc_priv *priv)
+{
+	if (priv->param.dma >= 0) {
+		/* Check special condition and perform error reset. See 2.4.7.5. */
+		special_condition(priv, read_scc(priv, R1));
+		write_scc(priv, R0, ERR_RES);
+	} else {
+		/* Check special condition for each character. Error reset not necessary.
+		   Same algorithm for SCC and ESCC. See 2.4.7.1 and 2.4.7.4. */
+		int rc;
+		while (read_scc(priv, R0) & Rx_CH_AV) {
+			rc = read_scc(priv, R1);
+			if (priv->rx_ptr < BUF_SIZE)
+				priv->rx_buf[priv->rx_head][priv->
+							    rx_ptr++] =
+				    read_scc_data(priv);
+			else {
+				priv->rx_over = 2;
+				read_scc_data(priv);
+			}
+			special_condition(priv, rc);
+		}
+	}
+}
+
+
+static void special_condition(struct scc_priv *priv, int rc)
+{
+	int cb;
+	unsigned long flags;
+
+	/* See Figure 2-15. Only overrun and EOF need to be checked. */
+
+	if (rc & Rx_OVR) {
+		/* Receiver overrun */
+		priv->rx_over = 1;
+		if (priv->param.dma < 0)
+			write_scc(priv, R0, ERR_RES);
+	} else if (rc & END_FR) {
+		/* End of frame. Get byte count */
+		if (priv->param.dma >= 0) {
+			flags = claim_dma_lock();
+			cb = BUF_SIZE - get_dma_residue(priv->param.dma) -
+			    2;
+			release_dma_lock(flags);
+		} else {
+			cb = priv->rx_ptr - 2;
+		}
+		if (priv->rx_over) {
+			/* We had an overrun */
+			priv->dev->stats.rx_errors++;
+			if (priv->rx_over == 2)
+				priv->dev->stats.rx_length_errors++;
+			else
+				priv->dev->stats.rx_fifo_errors++;
+			priv->rx_over = 0;
+		} else if (rc & CRC_ERR) {
+			/* Count invalid CRC only if packet length >= minimum */
+			if (cb >= 15) {
+				priv->dev->stats.rx_errors++;
+				priv->dev->stats.rx_crc_errors++;
+			}
+		} else {
+			if (cb >= 15) {
+				if (priv->rx_count < NUM_RX_BUF - 1) {
+					/* Put good frame in FIFO */
+					priv->rx_len[priv->rx_head] = cb;
+					priv->rx_head =
+					    (priv->rx_head +
+					     1) % NUM_RX_BUF;
+					priv->rx_count++;
+					schedule_work(&priv->rx_work);
+				} else {
+					priv->dev->stats.rx_errors++;
+					priv->dev->stats.rx_over_errors++;
+				}
+			}
+		}
+		/* Get ready for new frame */
+		if (priv->param.dma >= 0) {
+			flags = claim_dma_lock();
+			set_dma_addr(priv->param.dma,
+				     (int) priv->rx_buf[priv->rx_head]);
+			set_dma_count(priv->param.dma, BUF_SIZE);
+			release_dma_lock(flags);
+		} else {
+			priv->rx_ptr = 0;
+		}
+	}
+}
+
+
+static void rx_bh(struct work_struct *ugli_api)
+{
+	struct scc_priv *priv = container_of(ugli_api, struct scc_priv, rx_work);
+	int i = priv->rx_tail;
+	int cb;
+	unsigned long flags;
+	struct sk_buff *skb;
+	unsigned char *data;
+
+	spin_lock_irqsave(&priv->ring_lock, flags);
+	while (priv->rx_count) {
+		spin_unlock_irqrestore(&priv->ring_lock, flags);
+		cb = priv->rx_len[i];
+		/* Allocate buffer */
+		skb = dev_alloc_skb(cb + 1);
+		if (skb == NULL) {
+			/* Drop packet */
+			priv->dev->stats.rx_dropped++;
+		} else {
+			/* Fill buffer */
+			data = skb_put(skb, cb + 1);
+			data[0] = 0;
+			memcpy(&data[1], priv->rx_buf[i], cb);
+			skb->protocol = ax25_type_trans(skb, priv->dev);
+			netif_rx(skb);
+			priv->dev->stats.rx_packets++;
+			priv->dev->stats.rx_bytes += cb;
+		}
+		spin_lock_irqsave(&priv->ring_lock, flags);
+		/* Move tail */
+		priv->rx_tail = i = (i + 1) % NUM_RX_BUF;
+		priv->rx_count--;
+	}
+	spin_unlock_irqrestore(&priv->ring_lock, flags);
+}
+
+
+static void tx_isr(struct scc_priv *priv)
+{
+	int i = priv->tx_tail, p = priv->tx_ptr;
+
+	/* Suspend TX interrupts if we don't want to send anything.
+	   See Figure 2-22. */
+	if (p == priv->tx_len[i]) {
+		write_scc(priv, R0, RES_Tx_P);
+		return;
+	}
+
+	/* Write characters */
+	while ((read_scc(priv, R0) & Tx_BUF_EMP) && p < priv->tx_len[i]) {
+		write_scc_data(priv, priv->tx_buf[i][p++], 0);
+	}
+
+	/* Reset EOM latch of Z8530 */
+	if (!priv->tx_ptr && p && priv->chip == Z8530)
+		write_scc(priv, R0, RES_EOM_L);
+
+	priv->tx_ptr = p;
+}
+
+
+static void es_isr(struct scc_priv *priv)
+{
+	int i, rr0, drr0, res;
+	unsigned long flags;
+
+	/* Read status, reset interrupt bit (open latches) */
+	rr0 = read_scc(priv, R0);
+	write_scc(priv, R0, RES_EXT_INT);
+	drr0 = priv->rr0 ^ rr0;
+	priv->rr0 = rr0;
+
+	/* Transmit underrun (2.4.9.6). We can't check the TxEOM flag, since
+	   it might have already been cleared again by AUTOEOM. */
+	if (priv->state == TX_DATA) {
+		/* Get remaining bytes */
+		i = priv->tx_tail;
+		if (priv->param.dma >= 0) {
+			disable_dma(priv->param.dma);
+			flags = claim_dma_lock();
+			res = get_dma_residue(priv->param.dma);
+			release_dma_lock(flags);
+		} else {
+			res = priv->tx_len[i] - priv->tx_ptr;
+			priv->tx_ptr = 0;
+		}
+		/* Disable DREQ / TX interrupt */
+		if (priv->param.dma >= 0 && priv->type == TYPE_TWIN)
+			outb(0, priv->card_base + TWIN_DMA_CFG);
+		else
+			write_scc(priv, R1, EXT_INT_ENAB | WT_FN_RDYFN);
+		if (res) {
+			/* Update packet statistics */
+			priv->dev->stats.tx_errors++;
+			priv->dev->stats.tx_fifo_errors++;
+			/* Other underrun interrupts may already be waiting */
+			write_scc(priv, R0, RES_EXT_INT);
+			write_scc(priv, R0, RES_EXT_INT);
+		} else {
+			/* Update packet statistics */
+			priv->dev->stats.tx_packets++;
+			priv->dev->stats.tx_bytes += priv->tx_len[i];
+			/* Remove frame from FIFO */
+			priv->tx_tail = (i + 1) % NUM_TX_BUF;
+			priv->tx_count--;
+			/* Inform upper layers */
+			netif_wake_queue(priv->dev);
+		}
+		/* Switch state */
+		write_scc(priv, R15, 0);
+		if (priv->tx_count &&
+		    (jiffies - priv->tx_start) < priv->param.txtimeout) {
+			priv->state = TX_PAUSE;
+			start_timer(priv, priv->param.txpause, 0);
+		} else {
+			priv->state = TX_TAIL;
+			start_timer(priv, priv->param.txtail, 0);
+		}
+	}
+
+	/* DCD transition */
+	if (drr0 & DCD) {
+		if (rr0 & DCD) {
+			switch (priv->state) {
+			case IDLE:
+			case WAIT:
+				priv->state = DCD_ON;
+				write_scc(priv, R15, 0);
+				start_timer(priv, priv->param.dcdon, 0);
+			}
+		} else {
+			switch (priv->state) {
+			case RX_ON:
+				rx_off(priv);
+				priv->state = DCD_OFF;
+				write_scc(priv, R15, 0);
+				start_timer(priv, priv->param.dcdoff, 0);
+			}
+		}
+	}
+
+	/* CTS transition */
+	if ((drr0 & CTS) && (~rr0 & CTS) && priv->type != TYPE_TWIN)
+		tm_isr(priv);
+
+}
+
+
+static void tm_isr(struct scc_priv *priv)
+{
+	switch (priv->state) {
+	case TX_HEAD:
+	case TX_PAUSE:
+		tx_on(priv);
+		priv->state = TX_DATA;
+		break;
+	case TX_TAIL:
+		write_scc(priv, R5, TxCRC_ENAB | Tx8);
+		priv->state = RTS_OFF;
+		if (priv->type != TYPE_TWIN)
+			write_scc(priv, R15, 0);
+		start_timer(priv, priv->param.rtsoff, 0);
+		break;
+	case RTS_OFF:
+		write_scc(priv, R15, DCDIE);
+		priv->rr0 = read_scc(priv, R0);
+		if (priv->rr0 & DCD) {
+			priv->dev->stats.collisions++;
+			rx_on(priv);
+			priv->state = RX_ON;
+		} else {
+			priv->state = WAIT;
+			start_timer(priv, priv->param.waittime, DCDIE);
+		}
+		break;
+	case WAIT:
+		if (priv->tx_count) {
+			priv->state = TX_HEAD;
+			priv->tx_start = jiffies;
+			write_scc(priv, R5,
+				  TxCRC_ENAB | RTS | TxENAB | Tx8);
+			write_scc(priv, R15, 0);
+			start_timer(priv, priv->param.txdelay, 0);
+		} else {
+			priv->state = IDLE;
+			if (priv->type != TYPE_TWIN)
+				write_scc(priv, R15, DCDIE);
+		}
+		break;
+	case DCD_ON:
+	case DCD_OFF:
+		write_scc(priv, R15, DCDIE);
+		priv->rr0 = read_scc(priv, R0);
+		if (priv->rr0 & DCD) {
+			rx_on(priv);
+			priv->state = RX_ON;
+		} else {
+			priv->state = WAIT;
+			start_timer(priv,
+				    random() / priv->param.persist *
+				    priv->param.slottime, DCDIE);
+		}
+		break;
+	}
+}
diff --git a/drivers/net/hamradio/hdlcdrv.c b/drivers/net/hamradio/hdlcdrv.c
new file mode 100644
index 0000000..97e3bc6
--- /dev/null
+++ b/drivers/net/hamradio/hdlcdrv.c
@@ -0,0 +1,780 @@
+/*****************************************************************************/
+
+/*
+ *	hdlcdrv.c  -- HDLC packet radio network driver.
+ *
+ *	Copyright (C) 1996-2000  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.
+ *
+ *  The driver was derived from Donald Beckers skeleton.c
+ *	Written 1993-94 by Donald Becker.
+ *
+ *  History:
+ *   0.1  21.09.1996  Started
+ *        18.10.1996  Changed to new user space access routines 
+ *                    (copy_{to,from}_user)
+ *   0.2  21.11.1996  various small changes
+ *   0.3  03.03.1997  fixed (hopefully) IP not working with ax.25 as a module
+ *   0.4  16.04.1997  init code/data tagged
+ *   0.5  30.07.1997  made HDLC buffers bigger (solves a problem with the
+ *                    soundmodem driver)
+ *   0.6  05.04.1998  add spinlocks
+ *   0.7  03.08.1999  removed some old compatibility cruft
+ *   0.8  12.02.2000  adapted to softnet driver interface
+ */
+
+/*****************************************************************************/
+
+#include <linux/capability.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/net.h>
+#include <linux/in.h>
+#include <linux/if.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <linux/hdlcdrv.h>
+#include <linux/random.h>
+#include <net/ax25.h> 
+#include <linux/uaccess.h>
+
+#include <linux/crc-ccitt.h>
+
+/* --------------------------------------------------------------------- */
+
+#define KISS_VERBOSE
+
+/* --------------------------------------------------------------------- */
+
+#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
+
+/* --------------------------------------------------------------------- */
+/*
+ * the CRC routines are stolen from WAMPES
+ * by Dieter Deyke
+ */
+
+
+/*---------------------------------------------------------------------------*/
+
+static inline void append_crc_ccitt(unsigned char *buffer, int len)
+{
+ 	unsigned int crc = crc_ccitt(0xffff, buffer, len) ^ 0xffff;
+	buffer += len;
+	*buffer++ = crc;
+	*buffer++ = crc >> 8;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static inline int check_crc_ccitt(const unsigned char *buf, int cnt)
+{
+	return (crc_ccitt(0xffff, buf, cnt) & 0xffff) == 0xf0b8;
+}
+
+/*---------------------------------------------------------------------------*/
+
+#if 0
+static int calc_crc_ccitt(const unsigned char *buf, int cnt)
+{
+	unsigned int crc = 0xffff;
+
+	for (; cnt > 0; cnt--)
+		crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buf++) & 0xff];
+	crc ^= 0xffff;
+	return crc & 0xffff;
+}
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+#define tenms_to_2flags(s,tenms) ((tenms * s->par.bitrate) / 100 / 16)
+
+/* ---------------------------------------------------------------------- */
+/*
+ * The HDLC routines
+ */
+
+static int hdlc_rx_add_bytes(struct hdlcdrv_state *s, unsigned int bits, 
+			     int num)
+{
+	int added = 0;
+	
+	while (s->hdlcrx.rx_state && num >= 8) {
+		if (s->hdlcrx.len >= sizeof(s->hdlcrx.buffer)) {
+			s->hdlcrx.rx_state = 0;
+			return 0;
+		}
+		*s->hdlcrx.bp++ = bits >> (32-num);
+		s->hdlcrx.len++;
+		num -= 8;
+		added += 8;
+	}
+	return added;
+}
+
+static void hdlc_rx_flag(struct net_device *dev, struct hdlcdrv_state *s)
+{
+	struct sk_buff *skb;
+	int pkt_len;
+	unsigned char *cp;
+
+	if (s->hdlcrx.len < 4) 
+		return;
+	if (!check_crc_ccitt(s->hdlcrx.buffer, s->hdlcrx.len)) 
+		return;
+	pkt_len = s->hdlcrx.len - 2 + 1; /* KISS kludge */
+	if (!(skb = dev_alloc_skb(pkt_len))) {
+		printk("%s: memory squeeze, dropping packet\n", dev->name);
+		dev->stats.rx_dropped++;
+		return;
+	}
+	cp = skb_put(skb, pkt_len);
+	*cp++ = 0; /* KISS kludge */
+	memcpy(cp, s->hdlcrx.buffer, pkt_len - 1);
+	skb->protocol = ax25_type_trans(skb, dev);
+	netif_rx(skb);
+	dev->stats.rx_packets++;
+}
+
+void hdlcdrv_receiver(struct net_device *dev, struct hdlcdrv_state *s)
+{
+	int i;
+	unsigned int mask1, mask2, mask3, mask4, mask5, mask6, word;
+	
+	if (!s || s->magic != HDLCDRV_MAGIC) 
+		return;
+	if (test_and_set_bit(0, &s->hdlcrx.in_hdlc_rx))
+		return;
+
+	while (!hdlcdrv_hbuf_empty(&s->hdlcrx.hbuf)) {
+		word = hdlcdrv_hbuf_get(&s->hdlcrx.hbuf);	
+
+#ifdef HDLCDRV_DEBUG
+		hdlcdrv_add_bitbuffer_word(&s->bitbuf_hdlc, word);
+#endif /* HDLCDRV_DEBUG */
+	       	s->hdlcrx.bitstream >>= 16;
+		s->hdlcrx.bitstream |= word << 16;
+		s->hdlcrx.bitbuf >>= 16;
+		s->hdlcrx.bitbuf |= word << 16;
+		s->hdlcrx.numbits += 16;
+		for(i = 15, mask1 = 0x1fc00, mask2 = 0x1fe00, mask3 = 0x0fc00,
+		    mask4 = 0x1f800, mask5 = 0xf800, mask6 = 0xffff; 
+		    i >= 0; 
+		    i--, mask1 <<= 1, mask2 <<= 1, mask3 <<= 1, mask4 <<= 1, 
+		    mask5 <<= 1, mask6 = (mask6 << 1) | 1) {
+			if ((s->hdlcrx.bitstream & mask1) == mask1)
+				s->hdlcrx.rx_state = 0; /* abort received */
+			else if ((s->hdlcrx.bitstream & mask2) == mask3) {
+				/* flag received */
+				if (s->hdlcrx.rx_state) {
+					hdlc_rx_add_bytes(s, s->hdlcrx.bitbuf 
+							  << (8+i),
+							  s->hdlcrx.numbits
+							  -8-i);
+					hdlc_rx_flag(dev, s);
+				}
+				s->hdlcrx.len = 0;
+				s->hdlcrx.bp = s->hdlcrx.buffer;
+				s->hdlcrx.rx_state = 1;
+				s->hdlcrx.numbits = i;
+			} else if ((s->hdlcrx.bitstream & mask4) == mask5) {
+				/* stuffed bit */
+				s->hdlcrx.numbits--;
+				s->hdlcrx.bitbuf = (s->hdlcrx.bitbuf & (~mask6)) |
+					((s->hdlcrx.bitbuf & mask6) << 1);
+			}
+		}
+		s->hdlcrx.numbits -= hdlc_rx_add_bytes(s, s->hdlcrx.bitbuf,
+						       s->hdlcrx.numbits);
+	}
+	clear_bit(0, &s->hdlcrx.in_hdlc_rx);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static inline void do_kiss_params(struct hdlcdrv_state *s,
+				  unsigned char *data, unsigned long len)
+{
+
+#ifdef KISS_VERBOSE
+#define PKP(a,b) printk(KERN_INFO "hdlcdrv.c: channel params: " a "\n", b)
+#else /* KISS_VERBOSE */	      
+#define PKP(a,b) 
+#endif /* KISS_VERBOSE */	      
+
+	if (len < 2)
+		return;
+	switch(data[0]) {
+	case PARAM_TXDELAY:
+		s->ch_params.tx_delay = data[1];
+		PKP("TX delay = %ums", 10 * s->ch_params.tx_delay);
+		break;
+	case PARAM_PERSIST:   
+		s->ch_params.ppersist = data[1];
+		PKP("p persistence = %u", s->ch_params.ppersist);
+		break;
+	case PARAM_SLOTTIME:  
+		s->ch_params.slottime = data[1];
+		PKP("slot time = %ums", s->ch_params.slottime);
+		break;
+	case PARAM_TXTAIL:    
+		s->ch_params.tx_tail = data[1];
+		PKP("TX tail = %ums", s->ch_params.tx_tail);
+		break;
+	case PARAM_FULLDUP:   
+		s->ch_params.fulldup = !!data[1];
+		PKP("%s duplex", s->ch_params.fulldup ? "full" : "half");
+		break;
+	default:
+		break;
+	}
+#undef PKP
+}
+
+/* ---------------------------------------------------------------------- */
+
+void hdlcdrv_transmitter(struct net_device *dev, struct hdlcdrv_state *s)
+{
+	unsigned int mask1, mask2, mask3;
+	int i;
+	struct sk_buff *skb;
+	int pkt_len;
+
+	if (!s || s->magic != HDLCDRV_MAGIC) 
+		return;
+	if (test_and_set_bit(0, &s->hdlctx.in_hdlc_tx))
+		return;
+	for (;;) {
+		if (s->hdlctx.numbits >= 16) {
+			if (hdlcdrv_hbuf_full(&s->hdlctx.hbuf)) {
+				clear_bit(0, &s->hdlctx.in_hdlc_tx);
+				return;
+			}
+			hdlcdrv_hbuf_put(&s->hdlctx.hbuf, s->hdlctx.bitbuf);
+			s->hdlctx.bitbuf >>= 16;
+			s->hdlctx.numbits -= 16;
+		}
+		switch (s->hdlctx.tx_state) {
+		default:
+			clear_bit(0, &s->hdlctx.in_hdlc_tx);
+			return;
+		case 0:
+		case 1:
+			if (s->hdlctx.numflags) {
+				s->hdlctx.numflags--;
+				s->hdlctx.bitbuf |= 
+					0x7e7e << s->hdlctx.numbits;
+				s->hdlctx.numbits += 16;
+				break;
+			}
+			if (s->hdlctx.tx_state == 1) {
+				clear_bit(0, &s->hdlctx.in_hdlc_tx);
+				return;
+			}
+			if (!(skb = s->skb)) {
+				int flgs = tenms_to_2flags(s, s->ch_params.tx_tail);
+				if (flgs < 2)
+					flgs = 2;
+				s->hdlctx.tx_state = 1;
+				s->hdlctx.numflags = flgs;
+				break;
+			}
+			s->skb = NULL;
+			netif_wake_queue(dev);
+			pkt_len = skb->len-1; /* strip KISS byte */
+			if (pkt_len >= HDLCDRV_MAXFLEN || pkt_len < 2) {
+				s->hdlctx.tx_state = 0;
+				s->hdlctx.numflags = 1;
+				dev_kfree_skb_irq(skb);
+				break;
+			}
+			skb_copy_from_linear_data_offset(skb, 1,
+							 s->hdlctx.buffer,
+							 pkt_len);
+			dev_kfree_skb_irq(skb);
+			s->hdlctx.bp = s->hdlctx.buffer;
+			append_crc_ccitt(s->hdlctx.buffer, pkt_len);
+			s->hdlctx.len = pkt_len+2; /* the appended CRC */
+			s->hdlctx.tx_state = 2;
+			s->hdlctx.bitstream = 0;
+			dev->stats.tx_packets++;
+			break;
+		case 2:
+			if (!s->hdlctx.len) {
+				s->hdlctx.tx_state = 0;
+				s->hdlctx.numflags = 1;
+				break;
+			}
+			s->hdlctx.len--;
+			s->hdlctx.bitbuf |= *s->hdlctx.bp <<
+				s->hdlctx.numbits;
+			s->hdlctx.bitstream >>= 8;
+			s->hdlctx.bitstream |= (*s->hdlctx.bp++) << 16;
+			mask1 = 0x1f000;
+			mask2 = 0x10000;
+			mask3 = 0xffffffff >> (31-s->hdlctx.numbits);
+			s->hdlctx.numbits += 8;
+			for(i = 0; i < 8; i++, mask1 <<= 1, mask2 <<= 1, 
+			    mask3 = (mask3 << 1) | 1) {
+				if ((s->hdlctx.bitstream & mask1) != mask1) 
+					continue;
+				s->hdlctx.bitstream &= ~mask2;
+				s->hdlctx.bitbuf = 
+					(s->hdlctx.bitbuf & mask3) |
+						((s->hdlctx.bitbuf & 
+						 (~mask3)) << 1);
+				s->hdlctx.numbits++;
+				mask3 = (mask3 << 1) | 1;
+			}
+			break;
+		}
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void start_tx(struct net_device *dev, struct hdlcdrv_state *s)
+{
+	s->hdlctx.tx_state = 0;
+	s->hdlctx.numflags = tenms_to_2flags(s, s->ch_params.tx_delay);
+	s->hdlctx.bitbuf = s->hdlctx.bitstream = s->hdlctx.numbits = 0;
+	hdlcdrv_transmitter(dev, s);
+	s->hdlctx.ptt = 1;
+	s->ptt_keyed++;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void hdlcdrv_arbitrate(struct net_device *dev, struct hdlcdrv_state *s)
+{
+	if (!s || s->magic != HDLCDRV_MAGIC || s->hdlctx.ptt || !s->skb) 
+		return;
+	if (s->ch_params.fulldup) {
+		start_tx(dev, s);
+		return;
+	}
+	if (s->hdlcrx.dcd) {
+		s->hdlctx.slotcnt = s->ch_params.slottime;
+		return;
+	}
+	if ((--s->hdlctx.slotcnt) > 0)
+		return;
+	s->hdlctx.slotcnt = s->ch_params.slottime;
+	if ((prandom_u32() % 256) > s->ch_params.ppersist)
+		return;
+	start_tx(dev, s);
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== network driver interface =========================
+ */
+
+static netdev_tx_t hdlcdrv_send_packet(struct sk_buff *skb,
+				       struct net_device *dev)
+{
+	struct hdlcdrv_state *sm = netdev_priv(dev);
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return ax25_ip_xmit(skb);
+
+	if (skb->data[0] != 0) {
+		do_kiss_params(sm, skb->data, skb->len);
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+	if (sm->skb) {
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+	netif_stop_queue(dev);
+	sm->skb = skb;
+	return NETDEV_TX_OK;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int hdlcdrv_set_mac_address(struct net_device *dev, void *addr)
+{
+	struct sockaddr *sa = (struct sockaddr *)addr;
+
+	/* addr is an AX.25 shifted ASCII mac address */
+	memcpy(dev->dev_addr, sa->sa_data, dev->addr_len); 
+	return 0;                                         
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * Open/initialize the board. This is called (in the current kernel)
+ * sometime after booting when the 'ifconfig' program is run.
+ *
+ * This routine should set everything up anew at each open, even
+ * registers that "should" only need to be set once at boot, so that
+ * there is non-reboot way to recover if something goes wrong.
+ */
+
+static int hdlcdrv_open(struct net_device *dev)
+{
+	struct hdlcdrv_state *s = netdev_priv(dev);
+	int i;
+
+	if (!s->ops || !s->ops->open)
+		return -ENODEV;
+
+	/*
+	 * initialise some variables
+	 */
+	s->opened = 1;
+	s->hdlcrx.hbuf.rd = s->hdlcrx.hbuf.wr = 0;
+	s->hdlcrx.in_hdlc_rx = 0;
+	s->hdlcrx.rx_state = 0;
+	
+	s->hdlctx.hbuf.rd = s->hdlctx.hbuf.wr = 0;
+	s->hdlctx.in_hdlc_tx = 0;
+	s->hdlctx.tx_state = 1;
+	s->hdlctx.numflags = 0;
+	s->hdlctx.bitstream = s->hdlctx.bitbuf = s->hdlctx.numbits = 0;
+	s->hdlctx.ptt = 0;
+	s->hdlctx.slotcnt = s->ch_params.slottime;
+	s->hdlctx.calibrate = 0;
+
+	i = s->ops->open(dev);
+	if (i)
+		return i;
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+/* 
+ * The inverse routine to hdlcdrv_open(). 
+ */
+
+static int hdlcdrv_close(struct net_device *dev)
+{
+	struct hdlcdrv_state *s = netdev_priv(dev);
+	int i = 0;
+
+	netif_stop_queue(dev);
+
+	if (s->ops && s->ops->close)
+		i = s->ops->close(dev);
+	if (s->skb)
+		dev_kfree_skb(s->skb);
+	s->skb = NULL;
+	s->opened = 0;
+	return i;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int hdlcdrv_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct hdlcdrv_state *s = netdev_priv(dev);
+	struct hdlcdrv_ioctl bi;
+
+	if (cmd != SIOCDEVPRIVATE) {
+		if (s->ops && s->ops->ioctl)
+			return s->ops->ioctl(dev, ifr, &bi, cmd);
+		return -ENOIOCTLCMD;
+	}
+	if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi)))
+		return -EFAULT;
+
+	switch (bi.cmd) {
+	default:
+		if (s->ops && s->ops->ioctl)
+			return s->ops->ioctl(dev, ifr, &bi, cmd);
+		return -ENOIOCTLCMD;
+
+	case HDLCDRVCTL_GETCHANNELPAR:
+		bi.data.cp.tx_delay = s->ch_params.tx_delay;
+		bi.data.cp.tx_tail = s->ch_params.tx_tail;
+		bi.data.cp.slottime = s->ch_params.slottime;
+		bi.data.cp.ppersist = s->ch_params.ppersist;
+		bi.data.cp.fulldup = s->ch_params.fulldup;
+		break;
+
+	case HDLCDRVCTL_SETCHANNELPAR:
+		if (!capable(CAP_NET_ADMIN))
+			return -EACCES;
+		s->ch_params.tx_delay = bi.data.cp.tx_delay;
+		s->ch_params.tx_tail = bi.data.cp.tx_tail;
+		s->ch_params.slottime = bi.data.cp.slottime;
+		s->ch_params.ppersist = bi.data.cp.ppersist;
+		s->ch_params.fulldup = bi.data.cp.fulldup;
+		s->hdlctx.slotcnt = 1;
+		return 0;
+		
+	case HDLCDRVCTL_GETMODEMPAR:
+		bi.data.mp.iobase = dev->base_addr;
+		bi.data.mp.irq = dev->irq;
+		bi.data.mp.dma = dev->dma;
+		bi.data.mp.dma2 = s->ptt_out.dma2;
+		bi.data.mp.seriobase = s->ptt_out.seriobase;
+		bi.data.mp.pariobase = s->ptt_out.pariobase;
+		bi.data.mp.midiiobase = s->ptt_out.midiiobase;
+		break;
+
+	case HDLCDRVCTL_SETMODEMPAR:
+		if ((!capable(CAP_SYS_RAWIO)) || netif_running(dev))
+			return -EACCES;
+		dev->base_addr = bi.data.mp.iobase;
+		dev->irq = bi.data.mp.irq;
+		dev->dma = bi.data.mp.dma;
+		s->ptt_out.dma2 = bi.data.mp.dma2;
+		s->ptt_out.seriobase = bi.data.mp.seriobase;
+		s->ptt_out.pariobase = bi.data.mp.pariobase;
+		s->ptt_out.midiiobase = bi.data.mp.midiiobase;
+		return 0;	
+	
+	case HDLCDRVCTL_GETSTAT:
+		bi.data.cs.ptt = hdlcdrv_ptt(s);
+		bi.data.cs.dcd = s->hdlcrx.dcd;
+		bi.data.cs.ptt_keyed = s->ptt_keyed;
+		bi.data.cs.tx_packets = dev->stats.tx_packets;
+		bi.data.cs.tx_errors = dev->stats.tx_errors;
+		bi.data.cs.rx_packets = dev->stats.rx_packets;
+		bi.data.cs.rx_errors = dev->stats.rx_errors;
+		break;		
+
+	case HDLCDRVCTL_OLDGETSTAT:
+		bi.data.ocs.ptt = hdlcdrv_ptt(s);
+		bi.data.ocs.dcd = s->hdlcrx.dcd;
+		bi.data.ocs.ptt_keyed = s->ptt_keyed;
+		break;		
+
+	case HDLCDRVCTL_CALIBRATE:
+		if(!capable(CAP_SYS_RAWIO))
+			return -EPERM;
+		if (s->par.bitrate <= 0)
+			return -EINVAL;
+		if (bi.data.calibrate > INT_MAX / s->par.bitrate)
+			return -EINVAL;
+		s->hdlctx.calibrate = bi.data.calibrate * s->par.bitrate / 16;
+		return 0;
+
+	case HDLCDRVCTL_GETSAMPLES:
+#ifndef HDLCDRV_DEBUG
+		return -EPERM;
+#else /* HDLCDRV_DEBUG */
+		if (s->bitbuf_channel.rd == s->bitbuf_channel.wr) 
+			return -EAGAIN;
+		bi.data.bits = 
+			s->bitbuf_channel.buffer[s->bitbuf_channel.rd];
+		s->bitbuf_channel.rd = (s->bitbuf_channel.rd+1) %
+			sizeof(s->bitbuf_channel.buffer);
+		break;
+#endif /* HDLCDRV_DEBUG */
+				
+	case HDLCDRVCTL_GETBITS:
+#ifndef HDLCDRV_DEBUG
+		return -EPERM;
+#else /* HDLCDRV_DEBUG */
+		if (s->bitbuf_hdlc.rd == s->bitbuf_hdlc.wr) 
+			return -EAGAIN;
+		bi.data.bits = 
+			s->bitbuf_hdlc.buffer[s->bitbuf_hdlc.rd];
+		s->bitbuf_hdlc.rd = (s->bitbuf_hdlc.rd+1) %
+			sizeof(s->bitbuf_hdlc.buffer);
+		break;		
+#endif /* HDLCDRV_DEBUG */
+
+	case HDLCDRVCTL_DRIVERNAME:
+		if (s->ops && s->ops->drvname) {
+			strncpy(bi.data.drivername, s->ops->drvname, 
+				sizeof(bi.data.drivername));
+			break;
+		}
+		bi.data.drivername[0] = '\0';
+		break;
+		
+	}
+	if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi)))
+		return -EFAULT;
+	return 0;
+
+}
+
+/* --------------------------------------------------------------------- */
+
+static const struct net_device_ops hdlcdrv_netdev = {
+	.ndo_open	= hdlcdrv_open,
+	.ndo_stop	= hdlcdrv_close,
+	.ndo_start_xmit = hdlcdrv_send_packet,
+	.ndo_do_ioctl	= hdlcdrv_ioctl,
+	.ndo_set_mac_address = hdlcdrv_set_mac_address,
+};
+
+/*
+ * Initialize fields in hdlcdrv
+ */
+static void hdlcdrv_setup(struct net_device *dev)
+{
+	static const struct hdlcdrv_channel_params dflt_ch_params = { 
+		20, 2, 10, 40, 0 
+	};
+	struct hdlcdrv_state *s = netdev_priv(dev);
+
+	/*
+	 * initialize the hdlcdrv_state struct
+	 */
+	s->ch_params = dflt_ch_params;
+	s->ptt_keyed = 0;
+
+	spin_lock_init(&s->hdlcrx.hbuf.lock);
+	s->hdlcrx.hbuf.rd = s->hdlcrx.hbuf.wr = 0;
+	s->hdlcrx.in_hdlc_rx = 0;
+	s->hdlcrx.rx_state = 0;
+	
+	spin_lock_init(&s->hdlctx.hbuf.lock);
+	s->hdlctx.hbuf.rd = s->hdlctx.hbuf.wr = 0;
+	s->hdlctx.in_hdlc_tx = 0;
+	s->hdlctx.tx_state = 1;
+	s->hdlctx.numflags = 0;
+	s->hdlctx.bitstream = s->hdlctx.bitbuf = s->hdlctx.numbits = 0;
+	s->hdlctx.ptt = 0;
+	s->hdlctx.slotcnt = s->ch_params.slottime;
+	s->hdlctx.calibrate = 0;
+
+#ifdef HDLCDRV_DEBUG
+	s->bitbuf_channel.rd = s->bitbuf_channel.wr = 0;
+	s->bitbuf_channel.shreg = 0x80;
+
+	s->bitbuf_hdlc.rd = s->bitbuf_hdlc.wr = 0;
+	s->bitbuf_hdlc.shreg = 0x80;
+#endif /* HDLCDRV_DEBUG */
+
+
+	/* Fill in the fields of the device structure */
+
+	s->skb = NULL;
+	
+	dev->netdev_ops = &hdlcdrv_netdev;
+	dev->header_ops = &ax25_header_ops;
+	
+	dev->type = ARPHRD_AX25;           /* AF_AX25 device */
+	dev->hard_header_len = AX25_MAX_HEADER_LEN + AX25_BPQ_HEADER_LEN;
+	dev->mtu = AX25_DEF_PACLEN;        /* eth_mtu is the default */
+	dev->addr_len = AX25_ADDR_LEN;     /* sizeof an ax.25 address */
+	memcpy(dev->broadcast, &ax25_bcast, AX25_ADDR_LEN);
+	memcpy(dev->dev_addr, &ax25_defaddr, AX25_ADDR_LEN);
+	dev->tx_queue_len = 16;
+}
+
+/* --------------------------------------------------------------------- */
+struct net_device *hdlcdrv_register(const struct hdlcdrv_ops *ops,
+				    unsigned int privsize, const char *ifname,
+				    unsigned int baseaddr, unsigned int irq, 
+				    unsigned int dma) 
+{
+	struct net_device *dev;
+	struct hdlcdrv_state *s;
+	int err;
+
+	BUG_ON(ops == NULL);
+
+	if (privsize < sizeof(struct hdlcdrv_state))
+		privsize = sizeof(struct hdlcdrv_state);
+
+	dev = alloc_netdev(privsize, ifname, NET_NAME_UNKNOWN, hdlcdrv_setup);
+	if (!dev)
+		return ERR_PTR(-ENOMEM);
+
+	/*
+	 * initialize part of the hdlcdrv_state struct
+	 */
+	s = netdev_priv(dev);
+	s->magic = HDLCDRV_MAGIC;
+	s->ops = ops;
+	dev->base_addr = baseaddr;
+	dev->irq = irq;
+	dev->dma = dma;
+
+	err = register_netdev(dev);
+	if (err < 0) {
+		printk(KERN_WARNING "hdlcdrv: cannot register net "
+		       "device %s\n", dev->name);
+		free_netdev(dev);
+		dev = ERR_PTR(err);
+	}
+	return dev;
+}
+
+/* --------------------------------------------------------------------- */
+
+void hdlcdrv_unregister(struct net_device *dev) 
+{
+	struct hdlcdrv_state *s = netdev_priv(dev);
+
+	BUG_ON(s->magic != HDLCDRV_MAGIC);
+
+	if (s->opened && s->ops->close)
+		s->ops->close(dev);
+	unregister_netdev(dev);
+	
+	free_netdev(dev);
+}
+
+/* --------------------------------------------------------------------- */
+
+EXPORT_SYMBOL(hdlcdrv_receiver);
+EXPORT_SYMBOL(hdlcdrv_transmitter);
+EXPORT_SYMBOL(hdlcdrv_arbitrate);
+EXPORT_SYMBOL(hdlcdrv_register);
+EXPORT_SYMBOL(hdlcdrv_unregister);
+
+/* --------------------------------------------------------------------- */
+
+static int __init hdlcdrv_init_driver(void)
+{
+	printk(KERN_INFO "hdlcdrv: (C) 1996-2000 Thomas Sailer HB9JNX/AE4WA\n");
+	printk(KERN_INFO "hdlcdrv: version 0.8\n");
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void __exit hdlcdrv_cleanup_driver(void)
+{
+	printk(KERN_INFO "hdlcdrv: cleanup\n");
+}
+
+/* --------------------------------------------------------------------- */
+
+MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu");
+MODULE_DESCRIPTION("Packet Radio network interface HDLC encoder/decoder");
+MODULE_LICENSE("GPL");
+module_init(hdlcdrv_init_driver);
+module_exit(hdlcdrv_cleanup_driver);
+
+/* --------------------------------------------------------------------- */
diff --git a/drivers/net/hamradio/mkiss.c b/drivers/net/hamradio/mkiss.c
new file mode 100644
index 0000000..13e4c1e
--- /dev/null
+++ b/drivers/net/hamradio/mkiss.c
@@ -0,0 +1,1013 @@
+/*
+ *  This program is free software; you can distribute it and/or modify it
+ *  under the terms of the GNU General Public License (Version 2) as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) Hans Alblas PE1AYX <hans@esrac.ele.tue.nl>
+ * Copyright (C) 2004, 05 Ralf Baechle DL5RB <ralf@linux-mips.org>
+ * Copyright (C) 2004, 05 Thomas Osterried DL9SAU <thomas@x-berg.in-berlin.de>
+ */
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/uaccess.h>
+#include <linux/crc16.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/rtnetlink.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/jiffies.h>
+#include <linux/compat.h>
+
+#include <net/ax25.h>
+
+#define AX_MTU		236
+
+/* SLIP/KISS protocol characters. */
+#define END             0300		/* indicates end of frame	*/
+#define ESC             0333		/* indicates byte stuffing	*/
+#define ESC_END         0334		/* ESC ESC_END means END 'data'	*/
+#define ESC_ESC         0335		/* ESC ESC_ESC means ESC 'data'	*/
+
+struct mkiss {
+	struct tty_struct	*tty;	/* ptr to TTY structure		*/
+	struct net_device	*dev;	/* easy for intr handling	*/
+
+	/* These are pointers to the malloc()ed frame buffers. */
+	spinlock_t		buflock;/* lock for rbuf and xbuf */
+	unsigned char		*rbuff;	/* receiver buffer		*/
+	int			rcount;	/* received chars counter       */
+	unsigned char		*xbuff;	/* transmitter buffer		*/
+	unsigned char		*xhead;	/* pointer to next byte to XMIT */
+	int			xleft;	/* bytes left in XMIT queue     */
+
+	/* Detailed SLIP statistics. */
+	int		mtu;		/* Our mtu (to spot changes!)   */
+	int		buffsize;	/* Max buffers sizes            */
+
+	unsigned long	flags;		/* Flag values/ mode etc	*/
+					/* long req'd: used by set_bit --RR */
+#define AXF_INUSE	0		/* Channel in use               */
+#define AXF_ESCAPE	1               /* ESC received                 */
+#define AXF_ERROR	2               /* Parity, etc. error           */
+#define AXF_KEEPTEST	3		/* Keepalive test flag		*/
+#define AXF_OUTWAIT	4		/* is outpacket was flag	*/
+
+	int		mode;
+        int		crcmode;	/* MW: for FlexNet, SMACK etc.  */
+	int		crcauto;	/* CRC auto mode */
+
+#define CRC_MODE_NONE		0
+#define CRC_MODE_FLEX		1
+#define CRC_MODE_SMACK		2
+#define CRC_MODE_FLEX_TEST	3
+#define CRC_MODE_SMACK_TEST	4
+
+	atomic_t		refcnt;
+	struct semaphore	dead_sem;
+};
+
+/*---------------------------------------------------------------------------*/
+
+static const unsigned short crc_flex_table[] = {
+	0x0f87, 0x1e0e, 0x2c95, 0x3d1c, 0x49a3, 0x582a, 0x6ab1, 0x7b38,
+	0x83cf, 0x9246, 0xa0dd, 0xb154, 0xc5eb, 0xd462, 0xe6f9, 0xf770,
+	0x1f06, 0x0e8f, 0x3c14, 0x2d9d, 0x5922, 0x48ab, 0x7a30, 0x6bb9,
+	0x934e, 0x82c7, 0xb05c, 0xa1d5, 0xd56a, 0xc4e3, 0xf678, 0xe7f1,
+	0x2e85, 0x3f0c, 0x0d97, 0x1c1e, 0x68a1, 0x7928, 0x4bb3, 0x5a3a,
+	0xa2cd, 0xb344, 0x81df, 0x9056, 0xe4e9, 0xf560, 0xc7fb, 0xd672,
+	0x3e04, 0x2f8d, 0x1d16, 0x0c9f, 0x7820, 0x69a9, 0x5b32, 0x4abb,
+	0xb24c, 0xa3c5, 0x915e, 0x80d7, 0xf468, 0xe5e1, 0xd77a, 0xc6f3,
+	0x4d83, 0x5c0a, 0x6e91, 0x7f18, 0x0ba7, 0x1a2e, 0x28b5, 0x393c,
+	0xc1cb, 0xd042, 0xe2d9, 0xf350, 0x87ef, 0x9666, 0xa4fd, 0xb574,
+	0x5d02, 0x4c8b, 0x7e10, 0x6f99, 0x1b26, 0x0aaf, 0x3834, 0x29bd,
+	0xd14a, 0xc0c3, 0xf258, 0xe3d1, 0x976e, 0x86e7, 0xb47c, 0xa5f5,
+	0x6c81, 0x7d08, 0x4f93, 0x5e1a, 0x2aa5, 0x3b2c, 0x09b7, 0x183e,
+	0xe0c9, 0xf140, 0xc3db, 0xd252, 0xa6ed, 0xb764, 0x85ff, 0x9476,
+	0x7c00, 0x6d89, 0x5f12, 0x4e9b, 0x3a24, 0x2bad, 0x1936, 0x08bf,
+	0xf048, 0xe1c1, 0xd35a, 0xc2d3, 0xb66c, 0xa7e5, 0x957e, 0x84f7,
+	0x8b8f, 0x9a06, 0xa89d, 0xb914, 0xcdab, 0xdc22, 0xeeb9, 0xff30,
+	0x07c7, 0x164e, 0x24d5, 0x355c, 0x41e3, 0x506a, 0x62f1, 0x7378,
+	0x9b0e, 0x8a87, 0xb81c, 0xa995, 0xdd2a, 0xcca3, 0xfe38, 0xefb1,
+	0x1746, 0x06cf, 0x3454, 0x25dd, 0x5162, 0x40eb, 0x7270, 0x63f9,
+	0xaa8d, 0xbb04, 0x899f, 0x9816, 0xeca9, 0xfd20, 0xcfbb, 0xde32,
+	0x26c5, 0x374c, 0x05d7, 0x145e, 0x60e1, 0x7168, 0x43f3, 0x527a,
+	0xba0c, 0xab85, 0x991e, 0x8897, 0xfc28, 0xeda1, 0xdf3a, 0xceb3,
+	0x3644, 0x27cd, 0x1556, 0x04df, 0x7060, 0x61e9, 0x5372, 0x42fb,
+	0xc98b, 0xd802, 0xea99, 0xfb10, 0x8faf, 0x9e26, 0xacbd, 0xbd34,
+	0x45c3, 0x544a, 0x66d1, 0x7758, 0x03e7, 0x126e, 0x20f5, 0x317c,
+	0xd90a, 0xc883, 0xfa18, 0xeb91, 0x9f2e, 0x8ea7, 0xbc3c, 0xadb5,
+	0x5542, 0x44cb, 0x7650, 0x67d9, 0x1366, 0x02ef, 0x3074, 0x21fd,
+	0xe889, 0xf900, 0xcb9b, 0xda12, 0xaead, 0xbf24, 0x8dbf, 0x9c36,
+	0x64c1, 0x7548, 0x47d3, 0x565a, 0x22e5, 0x336c, 0x01f7, 0x107e,
+	0xf808, 0xe981, 0xdb1a, 0xca93, 0xbe2c, 0xafa5, 0x9d3e, 0x8cb7,
+	0x7440, 0x65c9, 0x5752, 0x46db, 0x3264, 0x23ed, 0x1176, 0x00ff
+};
+
+static unsigned short calc_crc_flex(unsigned char *cp, int size)
+{
+	unsigned short crc = 0xffff;
+
+	while (size--)
+		crc = (crc << 8) ^ crc_flex_table[((crc >> 8) ^ *cp++) & 0xff];
+
+	return crc;
+}
+
+static int check_crc_flex(unsigned char *cp, int size)
+{
+	unsigned short crc = 0xffff;
+
+	if (size < 3)
+		return -1;
+
+	while (size--)
+		crc = (crc << 8) ^ crc_flex_table[((crc >> 8) ^ *cp++) & 0xff];
+
+	if ((crc & 0xffff) != 0x7070)
+		return -1;
+
+	return 0;
+}
+
+static int check_crc_16(unsigned char *cp, int size)
+{
+	unsigned short crc = 0x0000;
+
+	if (size < 3)
+		return -1;
+
+	crc = crc16(0, cp, size);
+
+	if (crc != 0x0000)
+		return -1;
+
+	return 0;
+}
+
+/*
+ * Standard encapsulation
+ */
+
+static int kiss_esc(unsigned char *s, unsigned char *d, int len)
+{
+	unsigned char *ptr = d;
+	unsigned char c;
+
+	/*
+	 * Send an initial END character to flush out any data that may have
+	 * accumulated in the receiver due to line noise.
+	 */
+
+	*ptr++ = END;
+
+	while (len-- > 0) {
+		switch (c = *s++) {
+		case END:
+			*ptr++ = ESC;
+			*ptr++ = ESC_END;
+			break;
+		case ESC:
+			*ptr++ = ESC;
+			*ptr++ = ESC_ESC;
+			break;
+		default:
+			*ptr++ = c;
+			break;
+		}
+	}
+
+	*ptr++ = END;
+
+	return ptr - d;
+}
+
+/*
+ * MW:
+ * OK its ugly, but tell me a better solution without copying the
+ * packet to a temporary buffer :-)
+ */
+static int kiss_esc_crc(unsigned char *s, unsigned char *d, unsigned short crc,
+	int len)
+{
+	unsigned char *ptr = d;
+	unsigned char c=0;
+
+	*ptr++ = END;
+	while (len > 0) {
+		if (len > 2)
+			c = *s++;
+		else if (len > 1)
+			c = crc >> 8;
+		else
+			c = crc & 0xff;
+
+		len--;
+
+		switch (c) {
+		case END:
+			*ptr++ = ESC;
+			*ptr++ = ESC_END;
+			break;
+		case ESC:
+			*ptr++ = ESC;
+			*ptr++ = ESC_ESC;
+			break;
+		default:
+			*ptr++ = c;
+			break;
+		}
+	}
+	*ptr++ = END;
+
+	return ptr - d;
+}
+
+/* Send one completely decapsulated AX.25 packet to the AX.25 layer. */
+static void ax_bump(struct mkiss *ax)
+{
+	struct sk_buff *skb;
+	int count;
+
+	spin_lock_bh(&ax->buflock);
+	if (ax->rbuff[0] > 0x0f) {
+		if (ax->rbuff[0] & 0x80) {
+			if (check_crc_16(ax->rbuff, ax->rcount) < 0) {
+				ax->dev->stats.rx_errors++;
+				spin_unlock_bh(&ax->buflock);
+
+				return;
+			}
+			if (ax->crcmode != CRC_MODE_SMACK && ax->crcauto) {
+				printk(KERN_INFO
+				       "mkiss: %s: Switching to crc-smack\n",
+				       ax->dev->name);
+				ax->crcmode = CRC_MODE_SMACK;
+			}
+			ax->rcount -= 2;
+			*ax->rbuff &= ~0x80;
+		} else if (ax->rbuff[0] & 0x20)  {
+			if (check_crc_flex(ax->rbuff, ax->rcount) < 0) {
+				ax->dev->stats.rx_errors++;
+				spin_unlock_bh(&ax->buflock);
+				return;
+			}
+			if (ax->crcmode != CRC_MODE_FLEX && ax->crcauto) {
+				printk(KERN_INFO
+				       "mkiss: %s: Switching to crc-flexnet\n",
+				       ax->dev->name);
+				ax->crcmode = CRC_MODE_FLEX;
+			}
+			ax->rcount -= 2;
+
+			/*
+			 * dl9sau bugfix: the trailling two bytes flexnet crc
+			 * will not be passed to the kernel. thus we have to
+			 * correct the kissparm signature, because it indicates
+			 * a crc but there's none
+			 */
+			*ax->rbuff &= ~0x20;
+		}
+ 	}
+
+	count = ax->rcount;
+
+	if ((skb = dev_alloc_skb(count)) == NULL) {
+		printk(KERN_ERR "mkiss: %s: memory squeeze, dropping packet.\n",
+		       ax->dev->name);
+		ax->dev->stats.rx_dropped++;
+		spin_unlock_bh(&ax->buflock);
+		return;
+	}
+
+	skb_put_data(skb, ax->rbuff, count);
+	skb->protocol = ax25_type_trans(skb, ax->dev);
+	netif_rx(skb);
+	ax->dev->stats.rx_packets++;
+	ax->dev->stats.rx_bytes += count;
+	spin_unlock_bh(&ax->buflock);
+}
+
+static void kiss_unesc(struct mkiss *ax, unsigned char s)
+{
+	switch (s) {
+	case END:
+		/* drop keeptest bit = VSV */
+		if (test_bit(AXF_KEEPTEST, &ax->flags))
+			clear_bit(AXF_KEEPTEST, &ax->flags);
+
+		if (!test_and_clear_bit(AXF_ERROR, &ax->flags) && (ax->rcount > 2))
+			ax_bump(ax);
+
+		clear_bit(AXF_ESCAPE, &ax->flags);
+		ax->rcount = 0;
+		return;
+
+	case ESC:
+		set_bit(AXF_ESCAPE, &ax->flags);
+		return;
+	case ESC_ESC:
+		if (test_and_clear_bit(AXF_ESCAPE, &ax->flags))
+			s = ESC;
+		break;
+	case ESC_END:
+		if (test_and_clear_bit(AXF_ESCAPE, &ax->flags))
+			s = END;
+		break;
+	}
+
+	spin_lock_bh(&ax->buflock);
+	if (!test_bit(AXF_ERROR, &ax->flags)) {
+		if (ax->rcount < ax->buffsize) {
+			ax->rbuff[ax->rcount++] = s;
+			spin_unlock_bh(&ax->buflock);
+			return;
+		}
+
+		ax->dev->stats.rx_over_errors++;
+		set_bit(AXF_ERROR, &ax->flags);
+	}
+	spin_unlock_bh(&ax->buflock);
+}
+
+static int ax_set_mac_address(struct net_device *dev, void *addr)
+{
+	struct sockaddr_ax25 *sa = addr;
+
+	netif_tx_lock_bh(dev);
+	netif_addr_lock(dev);
+	memcpy(dev->dev_addr, &sa->sax25_call, AX25_ADDR_LEN);
+	netif_addr_unlock(dev);
+	netif_tx_unlock_bh(dev);
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static void ax_changedmtu(struct mkiss *ax)
+{
+	struct net_device *dev = ax->dev;
+	unsigned char *xbuff, *rbuff, *oxbuff, *orbuff;
+	int len;
+
+	len = dev->mtu * 2;
+
+	/*
+	 * allow for arrival of larger UDP packets, even if we say not to
+	 * also fixes a bug in which SunOS sends 512-byte packets even with
+	 * an MSS of 128
+	 */
+	if (len < 576 * 2)
+		len = 576 * 2;
+
+	xbuff = kmalloc(len + 4, GFP_ATOMIC);
+	rbuff = kmalloc(len + 4, GFP_ATOMIC);
+
+	if (xbuff == NULL || rbuff == NULL)  {
+		printk(KERN_ERR "mkiss: %s: unable to grow ax25 buffers, "
+		       "MTU change cancelled.\n",
+		       ax->dev->name);
+		dev->mtu = ax->mtu;
+		kfree(xbuff);
+		kfree(rbuff);
+		return;
+	}
+
+	spin_lock_bh(&ax->buflock);
+
+	oxbuff    = ax->xbuff;
+	ax->xbuff = xbuff;
+	orbuff    = ax->rbuff;
+	ax->rbuff = rbuff;
+
+	if (ax->xleft) {
+		if (ax->xleft <= len) {
+			memcpy(ax->xbuff, ax->xhead, ax->xleft);
+		} else  {
+			ax->xleft = 0;
+			dev->stats.tx_dropped++;
+		}
+	}
+
+	ax->xhead = ax->xbuff;
+
+	if (ax->rcount) {
+		if (ax->rcount <= len) {
+			memcpy(ax->rbuff, orbuff, ax->rcount);
+		} else  {
+			ax->rcount = 0;
+			dev->stats.rx_over_errors++;
+			set_bit(AXF_ERROR, &ax->flags);
+		}
+	}
+
+	ax->mtu      = dev->mtu + 73;
+	ax->buffsize = len;
+
+	spin_unlock_bh(&ax->buflock);
+
+	kfree(oxbuff);
+	kfree(orbuff);
+}
+
+/* Encapsulate one AX.25 packet and stuff into a TTY queue. */
+static void ax_encaps(struct net_device *dev, unsigned char *icp, int len)
+{
+	struct mkiss *ax = netdev_priv(dev);
+	unsigned char *p;
+	int actual, count;
+
+	if (ax->mtu != ax->dev->mtu + 73)	/* Someone has been ifconfigging */
+		ax_changedmtu(ax);
+
+	if (len > ax->mtu) {		/* Sigh, shouldn't occur BUT ... */
+		printk(KERN_ERR "mkiss: %s: truncating oversized transmit packet!\n", ax->dev->name);
+		dev->stats.tx_dropped++;
+		netif_start_queue(dev);
+		return;
+	}
+
+	p = icp;
+
+	spin_lock_bh(&ax->buflock);
+	if ((*p & 0x0f) != 0) {
+		/* Configuration Command (kissparms(1).
+		 * Protocol spec says: never append CRC.
+		 * This fixes a very old bug in the linux
+		 * kiss driver. -- dl9sau */
+		switch (*p & 0xff) {
+		case 0x85:
+			/* command from userspace especially for us,
+			 * not for delivery to the tnc */
+			if (len > 1) {
+				int cmd = (p[1] & 0xff);
+				switch(cmd) {
+				case 3:
+				  ax->crcmode = CRC_MODE_SMACK;
+				  break;
+				case 2:
+				  ax->crcmode = CRC_MODE_FLEX;
+				  break;
+				case 1:
+				  ax->crcmode = CRC_MODE_NONE;
+				  break;
+				case 0:
+				default:
+				  ax->crcmode = CRC_MODE_SMACK_TEST;
+				  cmd = 0;
+				}
+				ax->crcauto = (cmd ? 0 : 1);
+				printk(KERN_INFO "mkiss: %s: crc mode set to %d\n",
+				       ax->dev->name, cmd);
+			}
+			spin_unlock_bh(&ax->buflock);
+			netif_start_queue(dev);
+
+			return;
+		default:
+			count = kiss_esc(p, ax->xbuff, len);
+		}
+	} else {
+		unsigned short crc;
+		switch (ax->crcmode) {
+		case CRC_MODE_SMACK_TEST:
+			ax->crcmode  = CRC_MODE_FLEX_TEST;
+			printk(KERN_INFO "mkiss: %s: Trying crc-smack\n", ax->dev->name);
+			// fall through
+		case CRC_MODE_SMACK:
+			*p |= 0x80;
+			crc = swab16(crc16(0, p, len));
+			count = kiss_esc_crc(p, ax->xbuff, crc, len+2);
+			break;
+		case CRC_MODE_FLEX_TEST:
+			ax->crcmode = CRC_MODE_NONE;
+			printk(KERN_INFO "mkiss: %s: Trying crc-flexnet\n", ax->dev->name);
+			// fall through
+		case CRC_MODE_FLEX:
+			*p |= 0x20;
+			crc = calc_crc_flex(p, len);
+			count = kiss_esc_crc(p, ax->xbuff, crc, len+2);
+			break;
+
+		default:
+			count = kiss_esc(p, ax->xbuff, len);
+		}
+  	}
+	spin_unlock_bh(&ax->buflock);
+
+	set_bit(TTY_DO_WRITE_WAKEUP, &ax->tty->flags);
+	actual = ax->tty->ops->write(ax->tty, ax->xbuff, count);
+	dev->stats.tx_packets++;
+	dev->stats.tx_bytes += actual;
+
+	netif_trans_update(ax->dev);
+	ax->xleft = count - actual;
+	ax->xhead = ax->xbuff + actual;
+}
+
+/* Encapsulate an AX.25 packet and kick it into a TTY queue. */
+static netdev_tx_t ax_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct mkiss *ax = netdev_priv(dev);
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return ax25_ip_xmit(skb);
+
+	if (!netif_running(dev))  {
+		printk(KERN_ERR "mkiss: %s: xmit call when iface is down\n", dev->name);
+		return NETDEV_TX_BUSY;
+	}
+
+	if (netif_queue_stopped(dev)) {
+		/*
+		 * May be we must check transmitter timeout here ?
+		 *      14 Oct 1994 Dmitry Gorodchanin.
+		 */
+		if (time_before(jiffies, dev_trans_start(dev) + 20 * HZ)) {
+			/* 20 sec timeout not reached */
+			return NETDEV_TX_BUSY;
+		}
+
+		printk(KERN_ERR "mkiss: %s: transmit timed out, %s?\n", dev->name,
+		       (tty_chars_in_buffer(ax->tty) || ax->xleft) ?
+		       "bad line quality" : "driver error");
+
+		ax->xleft = 0;
+		clear_bit(TTY_DO_WRITE_WAKEUP, &ax->tty->flags);
+		netif_start_queue(dev);
+	}
+
+	/* We were not busy, so we are now... :-) */
+	netif_stop_queue(dev);
+	ax_encaps(dev, skb->data, skb->len);
+	kfree_skb(skb);
+
+	return NETDEV_TX_OK;
+}
+
+static int ax_open_dev(struct net_device *dev)
+{
+	struct mkiss *ax = netdev_priv(dev);
+
+	if (ax->tty == NULL)
+		return -ENODEV;
+
+	return 0;
+}
+
+/* Open the low-level part of the AX25 channel. Easy! */
+static int ax_open(struct net_device *dev)
+{
+	struct mkiss *ax = netdev_priv(dev);
+	unsigned long len;
+
+	if (ax->tty == NULL)
+		return -ENODEV;
+
+	/*
+	 * Allocate the frame buffers:
+	 *
+	 * rbuff	Receive buffer.
+	 * xbuff	Transmit buffer.
+	 */
+	len = dev->mtu * 2;
+
+	/*
+	 * allow for arrival of larger UDP packets, even if we say not to
+	 * also fixes a bug in which SunOS sends 512-byte packets even with
+	 * an MSS of 128
+	 */
+	if (len < 576 * 2)
+		len = 576 * 2;
+
+	if ((ax->rbuff = kmalloc(len + 4, GFP_KERNEL)) == NULL)
+		goto norbuff;
+
+	if ((ax->xbuff = kmalloc(len + 4, GFP_KERNEL)) == NULL)
+		goto noxbuff;
+
+	ax->mtu	     = dev->mtu + 73;
+	ax->buffsize = len;
+	ax->rcount   = 0;
+	ax->xleft    = 0;
+
+	ax->flags   &= (1 << AXF_INUSE);      /* Clear ESCAPE & ERROR flags */
+
+	spin_lock_init(&ax->buflock);
+
+	return 0;
+
+noxbuff:
+	kfree(ax->rbuff);
+
+norbuff:
+	return -ENOMEM;
+}
+
+
+/* Close the low-level part of the AX25 channel. Easy! */
+static int ax_close(struct net_device *dev)
+{
+	struct mkiss *ax = netdev_priv(dev);
+
+	if (ax->tty)
+		clear_bit(TTY_DO_WRITE_WAKEUP, &ax->tty->flags);
+
+	netif_stop_queue(dev);
+
+	return 0;
+}
+
+static const struct net_device_ops ax_netdev_ops = {
+	.ndo_open            = ax_open_dev,
+	.ndo_stop            = ax_close,
+	.ndo_start_xmit	     = ax_xmit,
+	.ndo_set_mac_address = ax_set_mac_address,
+};
+
+static void ax_setup(struct net_device *dev)
+{
+	/* Finish setting up the DEVICE info. */
+	dev->mtu             = AX_MTU;
+	dev->hard_header_len = AX25_MAX_HEADER_LEN;
+	dev->addr_len        = AX25_ADDR_LEN;
+	dev->type            = ARPHRD_AX25;
+	dev->tx_queue_len    = 10;
+	dev->header_ops      = &ax25_header_ops;
+	dev->netdev_ops	     = &ax_netdev_ops;
+
+
+	memcpy(dev->broadcast, &ax25_bcast, AX25_ADDR_LEN);
+	memcpy(dev->dev_addr,  &ax25_defaddr,  AX25_ADDR_LEN);
+
+	dev->flags      = IFF_BROADCAST | IFF_MULTICAST;
+}
+
+/*
+ * We have a potential race on dereferencing tty->disc_data, because the tty
+ * layer provides no locking at all - thus one cpu could be running
+ * sixpack_receive_buf while another calls sixpack_close, which zeroes
+ * tty->disc_data and frees the memory that sixpack_receive_buf is using.  The
+ * best way to fix this is to use a rwlock in the tty struct, but for now we
+ * use a single global rwlock for all ttys in ppp line discipline.
+ */
+static DEFINE_RWLOCK(disc_data_lock);
+
+static struct mkiss *mkiss_get(struct tty_struct *tty)
+{
+	struct mkiss *ax;
+
+	read_lock(&disc_data_lock);
+	ax = tty->disc_data;
+	if (ax)
+		atomic_inc(&ax->refcnt);
+	read_unlock(&disc_data_lock);
+
+	return ax;
+}
+
+static void mkiss_put(struct mkiss *ax)
+{
+	if (atomic_dec_and_test(&ax->refcnt))
+		up(&ax->dead_sem);
+}
+
+static int crc_force = 0;	/* Can be overridden with insmod */
+
+static int mkiss_open(struct tty_struct *tty)
+{
+	struct net_device *dev;
+	struct mkiss *ax;
+	int err;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+	if (tty->ops->write == NULL)
+		return -EOPNOTSUPP;
+
+	dev = alloc_netdev(sizeof(struct mkiss), "ax%d", NET_NAME_UNKNOWN,
+			   ax_setup);
+	if (!dev) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	ax = netdev_priv(dev);
+	ax->dev = dev;
+
+	spin_lock_init(&ax->buflock);
+	atomic_set(&ax->refcnt, 1);
+	sema_init(&ax->dead_sem, 0);
+
+	ax->tty = tty;
+	tty->disc_data = ax;
+	tty->receive_room = 65535;
+
+	tty_driver_flush_buffer(tty);
+
+	/* Restore default settings */
+	dev->type = ARPHRD_AX25;
+
+	/* Perform the low-level AX25 initialization. */
+	err = ax_open(ax->dev);
+	if (err)
+		goto out_free_netdev;
+
+	err = register_netdev(dev);
+	if (err)
+		goto out_free_buffers;
+
+	/* after register_netdev() - because else printk smashes the kernel */
+	switch (crc_force) {
+	case 3:
+		ax->crcmode  = CRC_MODE_SMACK;
+		printk(KERN_INFO "mkiss: %s: crc mode smack forced.\n",
+		       ax->dev->name);
+		break;
+	case 2:
+		ax->crcmode  = CRC_MODE_FLEX;
+		printk(KERN_INFO "mkiss: %s: crc mode flexnet forced.\n",
+		       ax->dev->name);
+		break;
+	case 1:
+		ax->crcmode  = CRC_MODE_NONE;
+		printk(KERN_INFO "mkiss: %s: crc mode disabled.\n",
+		       ax->dev->name);
+		break;
+	case 0:
+		/* fall through */
+	default:
+		crc_force = 0;
+		printk(KERN_INFO "mkiss: %s: crc mode is auto.\n",
+		       ax->dev->name);
+		ax->crcmode  = CRC_MODE_SMACK_TEST;
+	}
+	ax->crcauto = (crc_force ? 0 : 1);
+
+	netif_start_queue(dev);
+
+	/* Done.  We have linked the TTY line to a channel. */
+	return 0;
+
+out_free_buffers:
+	kfree(ax->rbuff);
+	kfree(ax->xbuff);
+
+out_free_netdev:
+	free_netdev(dev);
+
+out:
+	return err;
+}
+
+static void mkiss_close(struct tty_struct *tty)
+{
+	struct mkiss *ax;
+
+	write_lock_bh(&disc_data_lock);
+	ax = tty->disc_data;
+	tty->disc_data = NULL;
+	write_unlock_bh(&disc_data_lock);
+
+	if (!ax)
+		return;
+
+	/*
+	 * We have now ensured that nobody can start using ap from now on, but
+	 * we have to wait for all existing users to finish.
+	 */
+	if (!atomic_dec_and_test(&ax->refcnt))
+		down(&ax->dead_sem);
+	/*
+	 * Halt the transmit queue so that a new transmit cannot scribble
+	 * on our buffers
+	 */
+	netif_stop_queue(ax->dev);
+
+	/* Free all AX25 frame buffers. */
+	kfree(ax->rbuff);
+	kfree(ax->xbuff);
+
+	ax->tty = NULL;
+
+	unregister_netdev(ax->dev);
+}
+
+/* Perform I/O control on an active ax25 channel. */
+static int mkiss_ioctl(struct tty_struct *tty, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	struct mkiss *ax = mkiss_get(tty);
+	struct net_device *dev;
+	unsigned int tmp, err;
+
+	/* First make sure we're connected. */
+	if (ax == NULL)
+		return -ENXIO;
+	dev = ax->dev;
+
+	switch (cmd) {
+ 	case SIOCGIFNAME:
+		err = copy_to_user((void __user *) arg, ax->dev->name,
+		                   strlen(ax->dev->name) + 1) ? -EFAULT : 0;
+		break;
+
+	case SIOCGIFENCAP:
+		err = put_user(4, (int __user *) arg);
+		break;
+
+	case SIOCSIFENCAP:
+		if (get_user(tmp, (int __user *) arg)) {
+			err = -EFAULT;
+			break;
+		}
+
+		ax->mode = tmp;
+		dev->addr_len        = AX25_ADDR_LEN;
+		dev->hard_header_len = AX25_KISS_HEADER_LEN +
+		                       AX25_MAX_HEADER_LEN + 3;
+		dev->type            = ARPHRD_AX25;
+
+		err = 0;
+		break;
+
+	case SIOCSIFHWADDR: {
+		char addr[AX25_ADDR_LEN];
+
+		if (copy_from_user(&addr,
+		                   (void __user *) arg, AX25_ADDR_LEN)) {
+			err = -EFAULT;
+			break;
+		}
+
+		netif_tx_lock_bh(dev);
+		memcpy(dev->dev_addr, addr, AX25_ADDR_LEN);
+		netif_tx_unlock_bh(dev);
+
+		err = 0;
+		break;
+	}
+	default:
+		err = -ENOIOCTLCMD;
+	}
+
+	mkiss_put(ax);
+
+	return err;
+}
+
+#ifdef CONFIG_COMPAT
+static long mkiss_compat_ioctl(struct tty_struct *tty, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case SIOCGIFNAME:
+	case SIOCGIFENCAP:
+	case SIOCSIFENCAP:
+	case SIOCSIFHWADDR:
+		return mkiss_ioctl(tty, file, cmd,
+				   (unsigned long)compat_ptr(arg));
+	}
+
+	return -ENOIOCTLCMD;
+}
+#endif
+
+/*
+ * Handle the 'receiver data ready' interrupt.
+ * This function is called by the 'tty_io' module in the kernel when
+ * a block of data has been received, which can now be decapsulated
+ * and sent on to the AX.25 layer for further processing.
+ */
+static void mkiss_receive_buf(struct tty_struct *tty, const unsigned char *cp,
+	char *fp, int count)
+{
+	struct mkiss *ax = mkiss_get(tty);
+
+	if (!ax)
+		return;
+
+	/*
+	 * Argh! mtu change time! - costs us the packet part received
+	 * at the change
+	 */
+	if (ax->mtu != ax->dev->mtu + 73)
+		ax_changedmtu(ax);
+
+	/* Read the characters out of the buffer */
+	while (count--) {
+		if (fp != NULL && *fp++) {
+			if (!test_and_set_bit(AXF_ERROR, &ax->flags))
+				ax->dev->stats.rx_errors++;
+			cp++;
+			continue;
+		}
+
+		kiss_unesc(ax, *cp++);
+	}
+
+	mkiss_put(ax);
+	tty_unthrottle(tty);
+}
+
+/*
+ * Called by the driver when there's room for more data.  If we have
+ * more packets to send, we send them here.
+ */
+static void mkiss_write_wakeup(struct tty_struct *tty)
+{
+	struct mkiss *ax = mkiss_get(tty);
+	int actual;
+
+	if (!ax)
+		return;
+
+	if (ax->xleft <= 0)  {
+		/* Now serial buffer is almost free & we can start
+		 * transmission of another packet
+		 */
+		clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+
+		netif_wake_queue(ax->dev);
+		goto out;
+	}
+
+	actual = tty->ops->write(tty, ax->xhead, ax->xleft);
+	ax->xleft -= actual;
+	ax->xhead += actual;
+
+out:
+	mkiss_put(ax);
+}
+
+static struct tty_ldisc_ops ax_ldisc = {
+	.owner		= THIS_MODULE,
+	.magic		= TTY_LDISC_MAGIC,
+	.name		= "mkiss",
+	.open		= mkiss_open,
+	.close		= mkiss_close,
+	.ioctl		= mkiss_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= mkiss_compat_ioctl,
+#endif
+	.receive_buf	= mkiss_receive_buf,
+	.write_wakeup	= mkiss_write_wakeup
+};
+
+static const char banner[] __initconst = KERN_INFO \
+	"mkiss: AX.25 Multikiss, Hans Albas PE1AYX\n";
+static const char msg_regfail[] __initconst = KERN_ERR \
+	"mkiss: can't register line discipline (err = %d)\n";
+
+static int __init mkiss_init_driver(void)
+{
+	int status;
+
+	printk(banner);
+
+	status = tty_register_ldisc(N_AX25, &ax_ldisc);
+	if (status != 0)
+		printk(msg_regfail, status);
+
+	return status;
+}
+
+static const char msg_unregfail[] = KERN_ERR \
+	"mkiss: can't unregister line discipline (err = %d)\n";
+
+static void __exit mkiss_exit_driver(void)
+{
+	int ret;
+
+	if ((ret = tty_unregister_ldisc(N_AX25)))
+		printk(msg_unregfail, ret);
+}
+
+MODULE_AUTHOR("Ralf Baechle DL5RB <ralf@linux-mips.org>");
+MODULE_DESCRIPTION("KISS driver for AX.25 over TTYs");
+module_param(crc_force, int, 0);
+MODULE_PARM_DESC(crc_force, "crc [0 = auto | 1 = none | 2 = flexnet | 3 = smack]");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_LDISC(N_AX25);
+
+module_init(mkiss_init_driver);
+module_exit(mkiss_exit_driver);
diff --git a/drivers/net/hamradio/scc.c b/drivers/net/hamradio/scc.c
new file mode 100644
index 0000000..6c03932
--- /dev/null
+++ b/drivers/net/hamradio/scc.c
@@ -0,0 +1,2173 @@
+#define RCS_ID "$Id: scc.c,v 1.75 1998/11/04 15:15:01 jreuter Exp jreuter $"
+
+#define VERSION "3.0"
+
+/*
+ * Please use z8530drv-utils-3.0 with this version.
+ *            ------------------
+ *
+ * You can find a subset of the documentation in 
+ * Documentation/networking/z8530drv.txt.
+ */
+
+/*
+   ********************************************************************
+   *   SCC.C - Linux driver for Z8530 based HDLC cards for AX.25      *
+   ********************************************************************
+
+
+   ********************************************************************
+
+	Copyright (c) 1993, 2000 Joerg Reuter DL1BKE
+
+	portions (c) 1993 Guido ten Dolle PE1NNZ
+
+   ********************************************************************
+   
+   The driver and the programs in the archive are UNDER CONSTRUCTION.
+   The code is likely to fail, and so your kernel could --- even 
+   a whole network. 
+
+   This driver is intended for Amateur Radio use. If you are running it
+   for commercial purposes, please drop me a note. I am nosy...
+
+   ...BUT:
+ 
+   ! You  m u s t  recognize the appropriate legislations of your country !
+   ! before you connect a radio to the SCC board and start to transmit or !
+   ! receive. The GPL allows you to use the  d r i v e r,  NOT the RADIO! !
+
+   For non-Amateur-Radio use please note that you might need a special
+   allowance/licence from the designer of the SCC Board and/or the
+   MODEM. 
+
+   This program is free software; you can redistribute it and/or modify 
+   it under the terms of the (modified) GNU General Public License 
+   delivered with the Linux kernel source.
+   
+   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 find a copy of the GNU General Public License in 
+   /usr/src/linux/COPYING; 
+   
+   ******************************************************************** 
+
+		
+   Incomplete history of z8530drv:
+   -------------------------------
+
+   1994-09-13	started to write the driver, rescued most of my own
+		code (and Hans Alblas' memory buffer pool concept) from 
+		an earlier project "sccdrv" which was initiated by 
+		Guido ten Dolle. Not much of the old driver survived, 
+		though. The first version I put my hands on was sccdrv1.3
+		from August 1993. The memory buffer pool concept
+		appeared in an unauthorized sccdrv version (1.5) from
+		August 1994.
+
+   1995-01-31	changed copyright notice to GPL without limitations.
+   
+     .
+     .	<SNIP>
+     .
+   		  
+   1996-10-05	New semester, new driver... 
+
+   		  * KISS TNC emulator removed (TTY driver)
+   		  * Source moved to drivers/net/
+   		  * Includes Z8530 defines from drivers/net/z8530.h
+   		  * Uses sk_buffer memory management
+   		  * Reduced overhead of /proc/net/z8530drv output
+   		  * Streamlined quite a lot things
+   		  * Invents brand new bugs... ;-)
+
+   		  The move to version number 3.0 reflects theses changes.
+   		  You can use 'kissbridge' if you need a KISS TNC emulator.
+
+   1996-12-13	Fixed for Linux networking changes. (G4KLX)
+   1997-01-08	Fixed the remaining problems.
+   1997-04-02	Hopefully fixed the problems with the new *_timer()
+   		routines, added calibration code.
+   1997-10-12	Made SCC_DELAY a CONFIG option, added CONFIG_SCC_TRXECHO
+   1998-01-29	Small fix to avoid lock-up on initialization
+   1998-09-29	Fixed the "grouping" bugs, tx_inhibit works again,
+   		using dev->tx_queue_len now instead of MAXQUEUE now.
+   1998-10-21	Postponed the spinlock changes, would need a lot of
+   		testing I currently don't have the time to. Softdcd doesn't
+   		work.
+   1998-11-04	Softdcd does not work correctly in DPLL mode, in fact it 
+   		never did. The DPLL locks on noise, the SYNC unit sees
+   		flags that aren't... Restarting the DPLL does not help
+   		either, it resynchronizes too slow and the first received
+   		frame gets lost.
+   2000-02-13	Fixed for new network driver interface changes, still
+   		does TX timeouts itself since it uses its own queue
+   		scheme.
+
+   Thanks to all who contributed to this driver with ideas and bug
+   reports!
+   
+   NB -- if you find errors, change something, please let me know
+      	 first before you distribute it... And please don't touch
+   	 the version number. Just replace my callsign in
+   	 "v3.0.dl1bke" with your own. Just to avoid confusion...
+
+   If you want to add your modification to the linux distribution
+   please (!) contact me first.
+   
+   New versions of the driver will be announced on the linux-hams
+   mailing list on vger.kernel.org. To subscribe send an e-mail
+   to majordomo@vger.kernel.org with the following line in
+   the body of the mail:
+   
+	   subscribe linux-hams
+	   
+   The content of the "Subject" field will be ignored.
+
+   vy 73,
+   Joerg Reuter	ampr-net: dl1bke@db0pra.ampr.org
+		AX-25   : DL1BKE @ DB0ABH.#BAY.DEU.EU
+		Internet: jreuter@yaina.de
+		www     : http://yaina.de/jreuter
+*/
+
+/* ----------------------------------------------------------------------- */
+
+#undef  SCC_LDELAY		/* slow it even a bit more down */
+#undef  SCC_DONT_CHECK		/* don't look if the SCCs you specified are available */
+
+#define SCC_MAXCHIPS	4       /* number of max. supported chips */
+#define SCC_BUFSIZE	384     /* must not exceed 4096 */
+#undef	SCC_DEBUG
+
+#define SCC_DEFAULT_CLOCK	4915200 
+				/* default pclock if nothing is specified */
+
+/* ----------------------------------------------------------------------- */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/string.h>
+#include <linux/in.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <linux/socket.h>
+#include <linux/init.h>
+#include <linux/scc.h>
+#include <linux/ctype.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/bitops.h>
+
+#include <net/net_namespace.h>
+#include <net/ax25.h>
+
+#include <asm/irq.h>
+#include <asm/io.h>
+#include <linux/uaccess.h>
+
+#include "z8530.h"
+
+static const char banner[] __initconst = KERN_INFO \
+	"AX.25: Z8530 SCC driver version "VERSION".dl1bke\n";
+
+static void t_dwait(struct timer_list *t);
+static void t_txdelay(struct timer_list *t);
+static void t_tail(struct timer_list *t);
+static void t_busy(struct timer_list *);
+static void t_maxkeyup(struct timer_list *);
+static void t_idle(struct timer_list *t);
+static void scc_tx_done(struct scc_channel *);
+static void scc_start_tx_timer(struct scc_channel *,
+			       void (*)(struct timer_list *), unsigned long);
+static void scc_start_maxkeyup(struct scc_channel *);
+static void scc_start_defer(struct scc_channel *);
+
+static void z8530_init(void);
+
+static void init_channel(struct scc_channel *scc);
+static void scc_key_trx (struct scc_channel *scc, char tx);
+static void scc_init_timer(struct scc_channel *scc);
+
+static int scc_net_alloc(const char *name, struct scc_channel *scc);
+static void scc_net_setup(struct net_device *dev);
+static int scc_net_open(struct net_device *dev);
+static int scc_net_close(struct net_device *dev);
+static void scc_net_rx(struct scc_channel *scc, struct sk_buff *skb);
+static netdev_tx_t scc_net_tx(struct sk_buff *skb,
+			      struct net_device *dev);
+static int scc_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
+static int scc_net_set_mac_address(struct net_device *dev, void *addr);
+static struct net_device_stats * scc_net_get_stats(struct net_device *dev);
+
+static unsigned char SCC_DriverName[] = "scc";
+
+static struct irqflags { unsigned char used : 1; } Ivec[NR_IRQS];
+	
+static struct scc_channel SCC_Info[2 * SCC_MAXCHIPS];	/* information per channel */
+
+static struct scc_ctrl {
+	io_port chan_A;
+	io_port chan_B;
+	int irq;
+} SCC_ctrl[SCC_MAXCHIPS+1];
+
+static unsigned char Driver_Initialized;
+static int Nchips;
+static io_port Vector_Latch;
+
+
+/* ******************************************************************** */
+/* *			Port Access Functions			      * */
+/* ******************************************************************** */
+
+/* These provide interrupt save 2-step access to the Z8530 registers */
+
+static DEFINE_SPINLOCK(iolock);	/* Guards paired accesses */
+
+static inline unsigned char InReg(io_port port, unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char r;
+
+	spin_lock_irqsave(&iolock, flags);	
+#ifdef SCC_LDELAY
+	Outb(port, reg);
+	udelay(SCC_LDELAY);
+	r=Inb(port);
+	udelay(SCC_LDELAY);
+#else
+	Outb(port, reg);
+	r=Inb(port);
+#endif
+	spin_unlock_irqrestore(&iolock, flags);
+	return r;
+}
+
+static inline void OutReg(io_port port, unsigned char reg, unsigned char val)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&iolock, flags);
+#ifdef SCC_LDELAY
+	Outb(port, reg); udelay(SCC_LDELAY);
+	Outb(port, val); udelay(SCC_LDELAY);
+#else
+	Outb(port, reg);
+	Outb(port, val);
+#endif
+	spin_unlock_irqrestore(&iolock, flags);
+}
+
+static inline void wr(struct scc_channel *scc, unsigned char reg,
+	unsigned char val)
+{
+	OutReg(scc->ctrl, reg, (scc->wreg[reg] = val));
+}
+
+static inline void or(struct scc_channel *scc, unsigned char reg, unsigned char val)
+{
+	OutReg(scc->ctrl, reg, (scc->wreg[reg] |= val));
+}
+
+static inline void cl(struct scc_channel *scc, unsigned char reg, unsigned char val)
+{
+	OutReg(scc->ctrl, reg, (scc->wreg[reg] &= ~val));
+}
+
+/* ******************************************************************** */
+/* *			Some useful macros			      * */
+/* ******************************************************************** */
+
+static inline void scc_discard_buffers(struct scc_channel *scc)
+{
+	unsigned long flags;
+	
+	spin_lock_irqsave(&scc->lock, flags);	
+	if (scc->tx_buff != NULL)
+	{
+		dev_kfree_skb(scc->tx_buff);
+		scc->tx_buff = NULL;
+	}
+	
+	while (!skb_queue_empty(&scc->tx_queue))
+		dev_kfree_skb(skb_dequeue(&scc->tx_queue));
+
+	spin_unlock_irqrestore(&scc->lock, flags);
+}
+
+
+
+/* ******************************************************************** */
+/* *			Interrupt Service Routines		      * */
+/* ******************************************************************** */
+
+
+/* ----> subroutines for the interrupt handlers <---- */
+
+static inline void scc_notify(struct scc_channel *scc, int event)
+{
+	struct sk_buff *skb;
+	char *bp;
+	
+        if (scc->kiss.fulldup != KISS_DUPLEX_OPTIMA)
+		return;
+
+	skb = dev_alloc_skb(2);
+	if (skb != NULL)
+	{
+		bp = skb_put(skb, 2);
+		*bp++ = PARAM_HWEVENT;
+		*bp++ = event;
+		scc_net_rx(scc, skb);
+	} else
+		scc->stat.nospace++;
+}
+
+static inline void flush_rx_FIFO(struct scc_channel *scc)
+{
+	int k;
+	
+	for (k=0; k<3; k++)
+		Inb(scc->data);
+		
+	if(scc->rx_buff != NULL)		/* did we receive something? */
+	{
+		scc->stat.rxerrs++;  /* then count it as an error */
+		dev_kfree_skb_irq(scc->rx_buff);
+		scc->rx_buff = NULL;
+	}
+}
+
+static void start_hunt(struct scc_channel *scc)
+{
+	if ((scc->modem.clocksrc != CLK_EXTERNAL))
+		OutReg(scc->ctrl,R14,SEARCH|scc->wreg[R14]); /* DPLL: enter search mode */
+	or(scc,R3,ENT_HM|RxENABLE);  /* enable the receiver, hunt mode */
+}
+
+/* ----> four different interrupt handlers for Tx, Rx, changing of	*/
+/*       DCD/CTS and Rx/Tx errors					*/
+
+/* Transmitter interrupt handler */
+static inline void scc_txint(struct scc_channel *scc)
+{
+	struct sk_buff *skb;
+
+	scc->stat.txints++;
+	skb = scc->tx_buff;
+	
+	/* send first octet */
+	
+	if (skb == NULL)
+	{
+		skb = skb_dequeue(&scc->tx_queue);
+		scc->tx_buff = skb;
+		netif_wake_queue(scc->dev);
+
+		if (skb == NULL)
+		{
+			scc_tx_done(scc);
+			Outb(scc->ctrl, RES_Tx_P);
+			return;
+		}
+		
+		if (skb->len == 0)		/* Paranoia... */
+		{
+			dev_kfree_skb_irq(skb);
+			scc->tx_buff = NULL;
+			scc_tx_done(scc);
+			Outb(scc->ctrl, RES_Tx_P);
+			return;
+		}
+
+		scc->stat.tx_state = TXS_ACTIVE;
+
+		OutReg(scc->ctrl, R0, RES_Tx_CRC);
+						/* reset CRC generator */
+		or(scc,R10,ABUNDER);		/* re-install underrun protection */
+		Outb(scc->data,*skb->data);	/* send byte */
+		skb_pull(skb, 1);
+
+		if (!scc->enhanced)		/* reset EOM latch */
+			Outb(scc->ctrl,RES_EOM_L);
+		return;
+	}
+	
+	/* End Of Frame... */
+	
+	if (skb->len == 0)
+	{
+		Outb(scc->ctrl, RES_Tx_P);	/* reset pending int */
+		cl(scc, R10, ABUNDER);		/* send CRC */
+		dev_kfree_skb_irq(skb);
+		scc->tx_buff = NULL;
+		scc->stat.tx_state = TXS_NEWFRAME; /* next frame... */
+		return;
+	} 
+	
+	/* send octet */
+	
+	Outb(scc->data,*skb->data);		
+	skb_pull(skb, 1);
+}
+
+
+/* External/Status interrupt handler */
+static inline void scc_exint(struct scc_channel *scc)
+{
+	unsigned char status,changes,chg_and_stat;
+
+	scc->stat.exints++;
+
+	status = InReg(scc->ctrl,R0);
+	changes = status ^ scc->status;
+	chg_and_stat = changes & status;
+	
+	/* ABORT: generated whenever DCD drops while receiving */
+
+	if (chg_and_stat & BRK_ABRT)		/* Received an ABORT */
+		flush_rx_FIFO(scc);
+
+	/* HUNT: software DCD; on = waiting for SYNC, off = receiving frame */
+
+	if ((changes & SYNC_HUNT) && scc->kiss.softdcd)
+	{
+		if (status & SYNC_HUNT)
+		{
+			scc->dcd = 0;
+			flush_rx_FIFO(scc);
+			if ((scc->modem.clocksrc != CLK_EXTERNAL))
+				OutReg(scc->ctrl,R14,SEARCH|scc->wreg[R14]); /* DPLL: enter search mode */
+		} else {
+			scc->dcd = 1;
+		}
+
+		scc_notify(scc, scc->dcd? HWEV_DCD_OFF:HWEV_DCD_ON);
+	}
+
+	/* DCD: on = start to receive packet, off = ABORT condition */
+	/* (a successfully received packet generates a special condition int) */
+	
+	if((changes & DCD) && !scc->kiss.softdcd) /* DCD input changed state */
+	{
+		if(status & DCD)                /* DCD is now ON */
+		{
+			start_hunt(scc);
+			scc->dcd = 1;
+		} else {                        /* DCD is now OFF */
+			cl(scc,R3,ENT_HM|RxENABLE); /* disable the receiver */
+			flush_rx_FIFO(scc);
+			scc->dcd = 0;
+		}
+		
+		scc_notify(scc, scc->dcd? HWEV_DCD_ON:HWEV_DCD_OFF);
+	}
+
+#ifdef notdef
+	/* CTS: use external TxDelay (what's that good for?!)
+	 * Anyway: If we _could_ use it (BayCom USCC uses CTS for
+	 * own purposes) we _should_ use the "autoenable" feature
+	 * of the Z8530 and not this interrupt...
+	 */
+	 
+	if (chg_and_stat & CTS)			/* CTS is now ON */
+	{
+		if (scc->kiss.txdelay == 0)	/* zero TXDELAY = wait for CTS */
+			scc_start_tx_timer(scc, t_txdelay, 0);
+	}
+#endif
+	
+	if (scc->stat.tx_state == TXS_ACTIVE && (status & TxEOM))
+	{
+		scc->stat.tx_under++;	  /* oops, an underrun! count 'em */
+		Outb(scc->ctrl, RES_EXT_INT);	/* reset ext/status interrupts */
+
+		if (scc->tx_buff != NULL)
+		{
+			dev_kfree_skb_irq(scc->tx_buff);
+			scc->tx_buff = NULL;
+		}
+		
+		or(scc,R10,ABUNDER);
+		scc_start_tx_timer(scc, t_txdelay, 0);	/* restart transmission */
+	}
+		
+	scc->status = status;
+	Outb(scc->ctrl,RES_EXT_INT);
+}
+
+
+/* Receiver interrupt handler */
+static inline void scc_rxint(struct scc_channel *scc)
+{
+	struct sk_buff *skb;
+
+	scc->stat.rxints++;
+
+	if((scc->wreg[5] & RTS) && scc->kiss.fulldup == KISS_DUPLEX_HALF)
+	{
+		Inb(scc->data);		/* discard char */
+		or(scc,R3,ENT_HM);	/* enter hunt mode for next flag */
+		return;
+	}
+
+	skb = scc->rx_buff;
+	
+	if (skb == NULL)
+	{
+		skb = dev_alloc_skb(scc->stat.bufsize);
+		if (skb == NULL)
+		{
+			scc->dev_stat.rx_dropped++;
+			scc->stat.nospace++;
+			Inb(scc->data);
+			or(scc, R3, ENT_HM);
+			return;
+		}
+		
+		scc->rx_buff = skb;
+		skb_put_u8(skb, 0);	/* KISS data */
+	}
+	
+	if (skb->len >= scc->stat.bufsize)
+	{
+#ifdef notdef
+		printk(KERN_DEBUG "z8530drv: oops, scc_rxint() received huge frame...\n");
+#endif
+		dev_kfree_skb_irq(skb);
+		scc->rx_buff = NULL;
+		Inb(scc->data);
+		or(scc, R3, ENT_HM);
+		return;
+	}
+
+	skb_put_u8(skb, Inb(scc->data));
+}
+
+
+/* Receive Special Condition interrupt handler */
+static inline void scc_spint(struct scc_channel *scc)
+{
+	unsigned char status;
+	struct sk_buff *skb;
+
+	scc->stat.spints++;
+
+	status = InReg(scc->ctrl,R1);		/* read receiver status */
+	
+	Inb(scc->data);				/* throw away Rx byte */
+	skb = scc->rx_buff;
+
+	if(status & Rx_OVR)			/* receiver overrun */
+	{
+		scc->stat.rx_over++;             /* count them */
+		or(scc,R3,ENT_HM);               /* enter hunt mode for next flag */
+		
+		if (skb != NULL) 
+			dev_kfree_skb_irq(skb);
+		scc->rx_buff = skb = NULL;
+	}
+
+	if(status & END_FR && skb != NULL)	/* end of frame */
+	{
+		/* CRC okay, frame ends on 8 bit boundary and received something ? */
+		
+		if (!(status & CRC_ERR) && (status & 0xe) == RES8 && skb->len > 0)
+		{
+			/* ignore last received byte (first of the CRC bytes) */
+			skb_trim(skb, skb->len-1);
+			scc_net_rx(scc, skb);
+			scc->rx_buff = NULL;
+			scc->stat.rxframes++;
+		} else {				/* a bad frame */
+			dev_kfree_skb_irq(skb);
+			scc->rx_buff = NULL;
+			scc->stat.rxerrs++;
+		}
+	} 
+
+	Outb(scc->ctrl,ERR_RES);
+}
+
+
+/* ----> interrupt service routine for the Z8530 <---- */
+
+static void scc_isr_dispatch(struct scc_channel *scc, int vector)
+{
+	spin_lock(&scc->lock);
+	switch (vector & VECTOR_MASK)
+	{
+		case TXINT: scc_txint(scc); break;
+		case EXINT: scc_exint(scc); break;
+		case RXINT: scc_rxint(scc); break;
+		case SPINT: scc_spint(scc); break;
+	}
+	spin_unlock(&scc->lock);
+}
+
+/* If the card has a latch for the interrupt vector (like the PA0HZP card)
+   use it to get the number of the chip that generated the int.
+   If not: poll all defined chips.
+ */
+
+#define SCC_IRQTIMEOUT 30000
+
+static irqreturn_t scc_isr(int irq, void *dev_id)
+{
+	int chip_irq = (long) dev_id;
+	unsigned char vector;	
+	struct scc_channel *scc;
+	struct scc_ctrl *ctrl;
+	int k;
+	
+	if (Vector_Latch)
+	{
+	    	for(k=0; k < SCC_IRQTIMEOUT; k++)
+    		{
+			Outb(Vector_Latch, 0);      /* Generate INTACK */
+        
+			/* Read the vector */
+			if((vector=Inb(Vector_Latch)) >= 16 * Nchips) break; 
+			if (vector & 0x01) break;
+        	 
+		        scc=&SCC_Info[vector >> 3 ^ 0x01];
+			if (!scc->dev) break;
+
+			scc_isr_dispatch(scc, vector);
+
+			OutReg(scc->ctrl,R0,RES_H_IUS);              /* Reset Highest IUS */
+		}  
+
+		if (k == SCC_IRQTIMEOUT)
+			printk(KERN_WARNING "z8530drv: endless loop in scc_isr()?\n");
+
+		return IRQ_HANDLED;
+	}
+
+	/* Find the SCC generating the interrupt by polling all attached SCCs
+	 * reading RR3A (the interrupt pending register)
+	 */
+
+	ctrl = SCC_ctrl;
+	while (ctrl->chan_A)
+	{
+		if (ctrl->irq != chip_irq)
+		{
+			ctrl++;
+			continue;
+		}
+
+		scc = NULL;
+		for (k = 0; InReg(ctrl->chan_A,R3) && k < SCC_IRQTIMEOUT; k++)
+		{
+			vector=InReg(ctrl->chan_B,R2);	/* Read the vector */
+			if (vector & 0x01) break; 
+
+			scc = &SCC_Info[vector >> 3 ^ 0x01];
+		        if (!scc->dev) break;
+
+			scc_isr_dispatch(scc, vector);
+		}
+
+		if (k == SCC_IRQTIMEOUT)
+		{
+			printk(KERN_WARNING "z8530drv: endless loop in scc_isr()?!\n");
+			break;
+		}
+
+		/* This looks weird and it is. At least the BayCom USCC doesn't
+		 * use the Interrupt Daisy Chain, thus we'll have to start
+		 * all over again to be sure not to miss an interrupt from 
+		 * (any of) the other chip(s)...
+		 * Honestly, the situation *is* braindamaged...
+		 */
+
+		if (scc != NULL)
+		{
+			OutReg(scc->ctrl,R0,RES_H_IUS);
+			ctrl = SCC_ctrl; 
+		} else
+			ctrl++;
+	}
+	return IRQ_HANDLED;
+}
+
+
+
+/* ******************************************************************** */
+/* *			Init Channel					*/
+/* ******************************************************************** */
+
+
+/* ----> set SCC channel speed <---- */
+
+static inline void set_brg(struct scc_channel *scc, unsigned int tc)
+{
+	cl(scc,R14,BRENABL);		/* disable baudrate generator */
+	wr(scc,R12,tc & 255);		/* brg rate LOW */
+	wr(scc,R13,tc >> 8);   		/* brg rate HIGH */
+	or(scc,R14,BRENABL);		/* enable baudrate generator */
+}
+
+static inline void set_speed(struct scc_channel *scc)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&scc->lock, flags);
+
+	if (scc->modem.speed > 0)	/* paranoia... */
+		set_brg(scc, (unsigned) (scc->clock / (scc->modem.speed * 64)) - 2);
+		
+	spin_unlock_irqrestore(&scc->lock, flags);
+}
+
+
+/* ----> initialize a SCC channel <---- */
+
+static inline void init_brg(struct scc_channel *scc)
+{
+	wr(scc, R14, BRSRC);				/* BRG source = PCLK */
+	OutReg(scc->ctrl, R14, SSBR|scc->wreg[R14]);	/* DPLL source = BRG */
+	OutReg(scc->ctrl, R14, SNRZI|scc->wreg[R14]);	/* DPLL NRZI mode */
+}
+
+/*
+ * Initialization according to the Z8530 manual (SGS-Thomson's version):
+ *
+ * 1. Modes and constants
+ *
+ * WR9	11000000	chip reset
+ * WR4	XXXXXXXX	Tx/Rx control, async or sync mode
+ * WR1	0XX00X00	select W/REQ (optional)
+ * WR2	XXXXXXXX	program interrupt vector
+ * WR3	XXXXXXX0	select Rx control
+ * WR5	XXXX0XXX	select Tx control
+ * WR6	XXXXXXXX	sync character
+ * WR7	XXXXXXXX	sync character
+ * WR9	000X0XXX	select interrupt control
+ * WR10	XXXXXXXX	miscellaneous control (optional)
+ * WR11	XXXXXXXX	clock control
+ * WR12	XXXXXXXX	time constant lower byte (optional)
+ * WR13	XXXXXXXX	time constant upper byte (optional)
+ * WR14	XXXXXXX0	miscellaneous control
+ * WR14	XXXSSSSS	commands (optional)
+ *
+ * 2. Enables
+ *
+ * WR14	000SSSS1	baud rate enable
+ * WR3	SSSSSSS1	Rx enable
+ * WR5	SSSS1SSS	Tx enable
+ * WR0	10000000	reset Tx CRG (optional)
+ * WR1	XSS00S00	DMA enable (optional)
+ *
+ * 3. Interrupt status
+ *
+ * WR15	XXXXXXXX	enable external/status
+ * WR0	00010000	reset external status
+ * WR0	00010000	reset external status twice
+ * WR1	SSSXXSXX	enable Rx, Tx and Ext/status
+ * WR9	000SXSSS	enable master interrupt enable
+ *
+ * 1 = set to one, 0 = reset to zero
+ * X = user defined, S = same as previous init
+ *
+ *
+ * Note that the implementation differs in some points from above scheme.
+ *
+ */
+ 
+static void init_channel(struct scc_channel *scc)
+{
+	del_timer(&scc->tx_t);
+	del_timer(&scc->tx_wdog);
+
+	disable_irq(scc->irq);
+
+	wr(scc,R4,X1CLK|SDLC);		/* *1 clock, SDLC mode */
+	wr(scc,R1,0);			/* no W/REQ operation */
+	wr(scc,R3,Rx8|RxCRC_ENAB);	/* RX 8 bits/char, CRC, disabled */	
+	wr(scc,R5,Tx8|DTR|TxCRC_ENAB);	/* TX 8 bits/char, disabled, DTR */
+	wr(scc,R6,0);			/* SDLC address zero (not used) */
+	wr(scc,R7,FLAG);		/* SDLC flag value */
+	wr(scc,R9,VIS);			/* vector includes status */
+	wr(scc,R10,(scc->modem.nrz? NRZ : NRZI)|CRCPS|ABUNDER); /* abort on underrun, preset CRC generator, NRZ(I) */
+	wr(scc,R14, 0);
+
+
+/* set clock sources:
+
+   CLK_DPLL: normal halfduplex operation
+   
+		RxClk: use DPLL
+		TxClk: use DPLL
+		TRxC mode DPLL output
+		
+   CLK_EXTERNAL: external clocking (G3RUH or DF9IC modem)
+   
+  	        BayCom: 		others:
+  	        
+  	        TxClk = pin RTxC	TxClk = pin TRxC
+  	        RxClk = pin TRxC 	RxClk = pin RTxC
+  	     
+
+   CLK_DIVIDER:
+   		RxClk = use DPLL
+   		TxClk = pin RTxC
+   		
+   		BayCom:			others:
+   		pin TRxC = DPLL		pin TRxC = BRG
+   		(RxClk * 1)		(RxClk * 32)
+*/  
+
+   		
+	switch(scc->modem.clocksrc)
+	{
+		case CLK_DPLL:
+			wr(scc, R11, RCDPLL|TCDPLL|TRxCOI|TRxCDP);
+			init_brg(scc);
+			break;
+
+		case CLK_DIVIDER:
+			wr(scc, R11, ((scc->brand & BAYCOM)? TRxCDP : TRxCBR) | RCDPLL|TCRTxCP|TRxCOI);
+			init_brg(scc);
+			break;
+
+		case CLK_EXTERNAL:
+			wr(scc, R11, (scc->brand & BAYCOM)? RCTRxCP|TCRTxCP : RCRTxCP|TCTRxCP);
+			OutReg(scc->ctrl, R14, DISDPLL);
+			break;
+
+	}
+	
+	set_speed(scc);			/* set baudrate */
+	
+	if(scc->enhanced)
+	{
+		or(scc,R15,SHDLCE|FIFOE);	/* enable FIFO, SDLC/HDLC Enhancements (From now R7 is R7') */
+		wr(scc,R7,AUTOEOM);
+	}
+
+	if(scc->kiss.softdcd || (InReg(scc->ctrl,R0) & DCD))
+						/* DCD is now ON */
+	{
+		start_hunt(scc);
+	}
+	
+	/* enable ABORT, DCD & SYNC/HUNT interrupts */
+
+	wr(scc,R15, BRKIE|TxUIE|(scc->kiss.softdcd? SYNCIE:DCDIE));
+
+	Outb(scc->ctrl,RES_EXT_INT);	/* reset ext/status interrupts */
+	Outb(scc->ctrl,RES_EXT_INT);	/* must be done twice */
+
+	or(scc,R1,INT_ALL_Rx|TxINT_ENAB|EXT_INT_ENAB); /* enable interrupts */
+	
+	scc->status = InReg(scc->ctrl,R0);	/* read initial status */
+	
+	or(scc,R9,MIE);			/* master interrupt enable */
+	
+	scc_init_timer(scc);
+			
+	enable_irq(scc->irq);
+}
+
+
+
+
+/* ******************************************************************** */
+/* *			SCC timer functions			      * */
+/* ******************************************************************** */
+
+
+/* ----> scc_key_trx sets the time constant for the baudrate 
+         generator and keys the transmitter		     <---- */
+
+static void scc_key_trx(struct scc_channel *scc, char tx)
+{
+	unsigned int time_const;
+		
+	if (scc->brand & PRIMUS)
+		Outb(scc->ctrl + 4, scc->option | (tx? 0x80 : 0));
+
+	if (scc->modem.speed < 300) 
+		scc->modem.speed = 1200;
+
+	time_const = (unsigned) (scc->clock / (scc->modem.speed * (tx? 2:64))) - 2;
+
+	disable_irq(scc->irq);
+
+	if (tx)
+	{
+		or(scc, R1, TxINT_ENAB);	/* t_maxkeyup may have reset these */
+		or(scc, R15, TxUIE);
+	}
+
+	if (scc->modem.clocksrc == CLK_DPLL)
+	{				/* force simplex operation */
+		if (tx)
+		{
+#ifdef CONFIG_SCC_TRXECHO
+			cl(scc, R3, RxENABLE|ENT_HM);	/* switch off receiver */
+			cl(scc, R15, DCDIE|SYNCIE);	/* No DCD changes, please */
+#endif
+			set_brg(scc, time_const);	/* reprogram baudrate generator */
+
+			/* DPLL -> Rx clk, BRG -> Tx CLK, TRxC mode output, TRxC = BRG */
+			wr(scc, R11, RCDPLL|TCBR|TRxCOI|TRxCBR);
+			
+			/* By popular demand: tx_inhibit */
+			if (scc->kiss.tx_inhibit)
+			{
+				or(scc,R5, TxENAB);
+				scc->wreg[R5] |= RTS;
+			} else {
+				or(scc,R5,RTS|TxENAB);	/* set the RTS line and enable TX */
+			}
+		} else {
+			cl(scc,R5,RTS|TxENAB);
+			
+			set_brg(scc, time_const);	/* reprogram baudrate generator */
+			
+			/* DPLL -> Rx clk, DPLL -> Tx CLK, TRxC mode output, TRxC = DPLL */
+			wr(scc, R11, RCDPLL|TCDPLL|TRxCOI|TRxCDP);
+
+#ifndef CONFIG_SCC_TRXECHO
+			if (scc->kiss.softdcd)
+#endif
+			{
+				or(scc,R15, scc->kiss.softdcd? SYNCIE:DCDIE);
+				start_hunt(scc);
+			}
+		}
+	} else {
+		if (tx)
+		{
+#ifdef CONFIG_SCC_TRXECHO
+			if (scc->kiss.fulldup == KISS_DUPLEX_HALF)
+			{
+				cl(scc, R3, RxENABLE);
+				cl(scc, R15, DCDIE|SYNCIE);
+			}
+#endif
+				
+			if (scc->kiss.tx_inhibit)
+			{
+				or(scc,R5, TxENAB);
+				scc->wreg[R5] |= RTS;
+			} else {	
+				or(scc,R5,RTS|TxENAB);	/* enable tx */
+			}
+		} else {
+			cl(scc,R5,RTS|TxENAB);		/* disable tx */
+
+			if ((scc->kiss.fulldup == KISS_DUPLEX_HALF) &&
+#ifndef CONFIG_SCC_TRXECHO
+			    scc->kiss.softdcd)
+#else
+			    1)
+#endif
+			{
+				or(scc, R15, scc->kiss.softdcd? SYNCIE:DCDIE);
+				start_hunt(scc);
+			}
+		}
+	}
+
+	enable_irq(scc->irq);
+}
+
+
+/* ----> SCC timer interrupt handler and friends. <---- */
+
+static void __scc_start_tx_timer(struct scc_channel *scc,
+				 void (*handler)(struct timer_list *t),
+				 unsigned long when)
+{
+	del_timer(&scc->tx_t);
+
+	if (when == 0)
+	{
+		handler(&scc->tx_t);
+	} else 
+	if (when != TIMER_OFF)
+	{
+		scc->tx_t.function = handler;
+		scc->tx_t.expires = jiffies + (when*HZ)/100;
+		add_timer(&scc->tx_t);
+	}
+}
+
+static void scc_start_tx_timer(struct scc_channel *scc,
+			       void (*handler)(struct timer_list *t),
+			       unsigned long when)
+{
+	unsigned long flags;
+	
+	spin_lock_irqsave(&scc->lock, flags);
+	__scc_start_tx_timer(scc, handler, when);
+	spin_unlock_irqrestore(&scc->lock, flags);
+}
+
+static void scc_start_defer(struct scc_channel *scc)
+{
+	unsigned long flags;
+	
+	spin_lock_irqsave(&scc->lock, flags);
+	del_timer(&scc->tx_wdog);
+	
+	if (scc->kiss.maxdefer != 0 && scc->kiss.maxdefer != TIMER_OFF)
+	{
+		scc->tx_wdog.function = t_busy;
+		scc->tx_wdog.expires = jiffies + HZ*scc->kiss.maxdefer;
+		add_timer(&scc->tx_wdog);
+	}
+	spin_unlock_irqrestore(&scc->lock, flags);
+}
+
+static void scc_start_maxkeyup(struct scc_channel *scc)
+{
+	unsigned long flags;
+	
+	spin_lock_irqsave(&scc->lock, flags);
+	del_timer(&scc->tx_wdog);
+	
+	if (scc->kiss.maxkeyup != 0 && scc->kiss.maxkeyup != TIMER_OFF)
+	{
+		scc->tx_wdog.function = t_maxkeyup;
+		scc->tx_wdog.expires = jiffies + HZ*scc->kiss.maxkeyup;
+		add_timer(&scc->tx_wdog);
+	}
+	spin_unlock_irqrestore(&scc->lock, flags);
+}
+
+/* 
+ * This is called from scc_txint() when there are no more frames to send.
+ * Not exactly a timer function, but it is a close friend of the family...
+ */
+
+static void scc_tx_done(struct scc_channel *scc)
+{
+	/* 
+	 * trx remains keyed in fulldup mode 2 until t_idle expires.
+	 */
+				 
+	switch (scc->kiss.fulldup)
+	{
+		case KISS_DUPLEX_LINK:
+			scc->stat.tx_state = TXS_IDLE2;
+			if (scc->kiss.idletime != TIMER_OFF)
+				scc_start_tx_timer(scc, t_idle,
+						   scc->kiss.idletime*100);
+			break;
+		case KISS_DUPLEX_OPTIMA:
+			scc_notify(scc, HWEV_ALL_SENT);
+			break;
+		default:
+			scc->stat.tx_state = TXS_BUSY;
+			scc_start_tx_timer(scc, t_tail, scc->kiss.tailtime);
+	}
+
+	netif_wake_queue(scc->dev);
+}
+
+
+static unsigned char Rand = 17;
+
+static inline int is_grouped(struct scc_channel *scc)
+{
+	int k;
+	struct scc_channel *scc2;
+	unsigned char grp1, grp2;
+
+	grp1 = scc->kiss.group;
+	
+	for (k = 0; k < (Nchips * 2); k++)
+	{
+		scc2 = &SCC_Info[k];
+		grp2 = scc2->kiss.group;
+		
+		if (scc2 == scc || !(scc2->dev && grp2))
+			continue;
+		
+		if ((grp1 & 0x3f) == (grp2 & 0x3f))
+		{
+			if ( (grp1 & TXGROUP) && (scc2->wreg[R5] & RTS) )
+				return 1;
+			
+			if ( (grp1 & RXGROUP) && scc2->dcd )
+				return 1;
+		}
+	}
+	return 0;
+}
+
+/* DWAIT and SLOTTIME expired
+ *
+ * fulldup == 0:  DCD is active or Rand > P-persistence: start t_busy timer
+ *                else key trx and start txdelay
+ * fulldup == 1:  key trx and start txdelay
+ * fulldup == 2:  mintime expired, reset status or key trx and start txdelay
+ */
+
+static void t_dwait(struct timer_list *t)
+{
+	struct scc_channel *scc = from_timer(scc, t, tx_t);
+	
+	if (scc->stat.tx_state == TXS_WAIT)	/* maxkeyup or idle timeout */
+	{
+		if (skb_queue_empty(&scc->tx_queue)) {	/* nothing to send */
+			scc->stat.tx_state = TXS_IDLE;
+			netif_wake_queue(scc->dev);	/* t_maxkeyup locked it. */
+			return;
+		}
+
+		scc->stat.tx_state = TXS_BUSY;
+	}
+
+	if (scc->kiss.fulldup == KISS_DUPLEX_HALF)
+	{
+		Rand = Rand * 17 + 31;
+		
+		if (scc->dcd || (scc->kiss.persist) < Rand || (scc->kiss.group && is_grouped(scc)) )
+		{
+			scc_start_defer(scc);
+			scc_start_tx_timer(scc, t_dwait, scc->kiss.slottime);
+			return ;
+		}
+	}
+
+	if ( !(scc->wreg[R5] & RTS) )
+	{
+		scc_key_trx(scc, TX_ON);
+		scc_start_tx_timer(scc, t_txdelay, scc->kiss.txdelay);
+	} else {
+		scc_start_tx_timer(scc, t_txdelay, 0);
+	}
+}
+
+
+/* TXDELAY expired
+ *
+ * kick transmission by a fake scc_txint(scc), start 'maxkeyup' watchdog.
+ */
+
+static void t_txdelay(struct timer_list *t)
+{
+	struct scc_channel *scc = from_timer(scc, t, tx_t);
+
+	scc_start_maxkeyup(scc);
+
+	if (scc->tx_buff == NULL)
+	{
+		disable_irq(scc->irq);
+		scc_txint(scc);	
+		enable_irq(scc->irq);
+	}
+}
+	
+
+/* TAILTIME expired
+ *
+ * switch off transmitter. If we were stopped by Maxkeyup restart
+ * transmission after 'mintime' seconds
+ */
+
+static void t_tail(struct timer_list *t)
+{
+	struct scc_channel *scc = from_timer(scc, t, tx_t);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&scc->lock, flags); 
+ 	del_timer(&scc->tx_wdog);	
+ 	scc_key_trx(scc, TX_OFF);
+	spin_unlock_irqrestore(&scc->lock, flags);
+
+ 	if (scc->stat.tx_state == TXS_TIMEOUT)		/* we had a timeout? */
+ 	{
+ 		scc->stat.tx_state = TXS_WAIT;
+		scc_start_tx_timer(scc, t_dwait, scc->kiss.mintime*100);
+ 		return;
+ 	}
+ 	
+ 	scc->stat.tx_state = TXS_IDLE;
+	netif_wake_queue(scc->dev);
+}
+
+
+/* BUSY timeout
+ *
+ * throw away send buffers if DCD remains active too long.
+ */
+
+static void t_busy(struct timer_list *t)
+{
+	struct scc_channel *scc = from_timer(scc, t, tx_wdog);
+
+	del_timer(&scc->tx_t);
+	netif_stop_queue(scc->dev);	/* don't pile on the wabbit! */
+
+	scc_discard_buffers(scc);
+	scc->stat.txerrs++;
+	scc->stat.tx_state = TXS_IDLE;
+
+	netif_wake_queue(scc->dev);	
+}
+
+/* MAXKEYUP timeout
+ *
+ * this is our watchdog.
+ */
+
+static void t_maxkeyup(struct timer_list *t)
+{
+	struct scc_channel *scc = from_timer(scc, t, tx_wdog);
+	unsigned long flags;
+
+	spin_lock_irqsave(&scc->lock, flags);
+	/* 
+	 * let things settle down before we start to
+	 * accept new data.
+	 */
+
+	netif_stop_queue(scc->dev);
+	scc_discard_buffers(scc);
+
+	del_timer(&scc->tx_t);
+
+	cl(scc, R1, TxINT_ENAB);	/* force an ABORT, but don't */
+	cl(scc, R15, TxUIE);		/* count it. */
+	OutReg(scc->ctrl, R0, RES_Tx_P);
+
+	spin_unlock_irqrestore(&scc->lock, flags);
+
+	scc->stat.txerrs++;
+	scc->stat.tx_state = TXS_TIMEOUT;
+	scc_start_tx_timer(scc, t_tail, scc->kiss.tailtime);
+}
+
+/* IDLE timeout
+ *
+ * in fulldup mode 2 it keys down the transmitter after 'idle' seconds
+ * of inactivity. We will not restart transmission before 'mintime'
+ * expires.
+ */
+
+static void t_idle(struct timer_list *t)
+{
+	struct scc_channel *scc = from_timer(scc, t, tx_t);
+	
+	del_timer(&scc->tx_wdog);
+
+	scc_key_trx(scc, TX_OFF);
+	if(scc->kiss.mintime)
+		scc_start_tx_timer(scc, t_dwait, scc->kiss.mintime*100);
+	scc->stat.tx_state = TXS_WAIT;
+}
+
+static void scc_init_timer(struct scc_channel *scc)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&scc->lock, flags);	
+	scc->stat.tx_state = TXS_IDLE;
+	spin_unlock_irqrestore(&scc->lock, flags);
+}
+
+
+/* ******************************************************************** */
+/* *			Set/get L1 parameters			      * */
+/* ******************************************************************** */
+
+
+/*
+ * this will set the "hardware" parameters through KISS commands or ioctl()
+ */
+
+#define CAST(x) (unsigned long)(x)
+
+static unsigned int scc_set_param(struct scc_channel *scc, unsigned int cmd, unsigned int arg)
+{
+	switch (cmd)
+	{
+		case PARAM_TXDELAY:	scc->kiss.txdelay=arg;		break;
+		case PARAM_PERSIST:	scc->kiss.persist=arg;		break;
+		case PARAM_SLOTTIME:	scc->kiss.slottime=arg;		break;
+		case PARAM_TXTAIL:	scc->kiss.tailtime=arg;		break;
+		case PARAM_FULLDUP:	scc->kiss.fulldup=arg;		break;
+		case PARAM_DTR:		break; /* does someone need this? */
+		case PARAM_GROUP:	scc->kiss.group=arg;		break;
+		case PARAM_IDLE:	scc->kiss.idletime=arg;		break;
+		case PARAM_MIN:		scc->kiss.mintime=arg;		break;
+		case PARAM_MAXKEY:	scc->kiss.maxkeyup=arg;		break;
+		case PARAM_WAIT:	scc->kiss.waittime=arg;		break;
+		case PARAM_MAXDEFER:	scc->kiss.maxdefer=arg;		break;
+		case PARAM_TX:		scc->kiss.tx_inhibit=arg;	break;
+
+		case PARAM_SOFTDCD:	
+			scc->kiss.softdcd=arg;
+			if (arg)
+			{
+				or(scc, R15, SYNCIE);
+				cl(scc, R15, DCDIE);
+				start_hunt(scc);
+			} else {
+				or(scc, R15, DCDIE);
+				cl(scc, R15, SYNCIE);
+			}
+			break;
+				
+		case PARAM_SPEED:
+			if (arg < 256)
+				scc->modem.speed=arg*100;
+			else
+				scc->modem.speed=arg;
+
+			if (scc->stat.tx_state == 0)	/* only switch baudrate on rx... ;-) */
+				set_speed(scc);
+			break;
+			
+		case PARAM_RTS:	
+			if ( !(scc->wreg[R5] & RTS) )
+			{
+				if (arg != TX_OFF) {
+					scc_key_trx(scc, TX_ON);
+					scc_start_tx_timer(scc, t_txdelay, scc->kiss.txdelay);
+				}
+			} else {
+				if (arg == TX_OFF)
+				{
+					scc->stat.tx_state = TXS_BUSY;
+					scc_start_tx_timer(scc, t_tail, scc->kiss.tailtime);
+				}
+			}
+			break;
+			
+		case PARAM_HWEVENT:
+			scc_notify(scc, scc->dcd? HWEV_DCD_ON:HWEV_DCD_OFF);
+			break;
+
+		default:		return -EINVAL;
+	}
+	
+	return 0;
+}
+
+
+ 
+static unsigned long scc_get_param(struct scc_channel *scc, unsigned int cmd)
+{
+	switch (cmd)
+	{
+		case PARAM_TXDELAY:	return CAST(scc->kiss.txdelay);
+		case PARAM_PERSIST:	return CAST(scc->kiss.persist);
+		case PARAM_SLOTTIME:	return CAST(scc->kiss.slottime);
+		case PARAM_TXTAIL:	return CAST(scc->kiss.tailtime);
+		case PARAM_FULLDUP:	return CAST(scc->kiss.fulldup);
+		case PARAM_SOFTDCD:	return CAST(scc->kiss.softdcd);
+		case PARAM_DTR:		return CAST((scc->wreg[R5] & DTR)? 1:0);
+		case PARAM_RTS:		return CAST((scc->wreg[R5] & RTS)? 1:0);
+		case PARAM_SPEED:	return CAST(scc->modem.speed);
+		case PARAM_GROUP:	return CAST(scc->kiss.group);
+		case PARAM_IDLE:	return CAST(scc->kiss.idletime);
+		case PARAM_MIN:		return CAST(scc->kiss.mintime);
+		case PARAM_MAXKEY:	return CAST(scc->kiss.maxkeyup);
+		case PARAM_WAIT:	return CAST(scc->kiss.waittime);
+		case PARAM_MAXDEFER:	return CAST(scc->kiss.maxdefer);
+		case PARAM_TX:		return CAST(scc->kiss.tx_inhibit);
+		default:		return NO_SUCH_PARAM;
+	}
+
+}
+
+#undef CAST
+
+/* ******************************************************************* */
+/* *			Send calibration pattern		     * */
+/* ******************************************************************* */
+
+static void scc_stop_calibrate(struct timer_list *t)
+{
+	struct scc_channel *scc = from_timer(scc, t, tx_wdog);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&scc->lock, flags);
+	del_timer(&scc->tx_wdog);
+	scc_key_trx(scc, TX_OFF);
+	wr(scc, R6, 0);
+	wr(scc, R7, FLAG);
+	Outb(scc->ctrl,RES_EXT_INT);	/* reset ext/status interrupts */
+	Outb(scc->ctrl,RES_EXT_INT);
+
+	netif_wake_queue(scc->dev);
+	spin_unlock_irqrestore(&scc->lock, flags);
+}
+
+
+static void
+scc_start_calibrate(struct scc_channel *scc, int duration, unsigned char pattern)
+{
+	unsigned long flags;
+	
+	spin_lock_irqsave(&scc->lock, flags);
+	netif_stop_queue(scc->dev);
+	scc_discard_buffers(scc);
+
+	del_timer(&scc->tx_wdog);
+
+	scc->tx_wdog.function = scc_stop_calibrate;
+	scc->tx_wdog.expires = jiffies + HZ*duration;
+	add_timer(&scc->tx_wdog);
+
+	/* This doesn't seem to work. Why not? */	
+	wr(scc, R6, 0);
+	wr(scc, R7, pattern);
+
+	/* 
+	 * Don't know if this works. 
+	 * Damn, where is my Z8530 programming manual...? 
+	 */
+
+	Outb(scc->ctrl,RES_EXT_INT);	/* reset ext/status interrupts */
+	Outb(scc->ctrl,RES_EXT_INT);
+
+	scc_key_trx(scc, TX_ON);
+	spin_unlock_irqrestore(&scc->lock, flags);
+}
+
+/* ******************************************************************* */
+/* *		Init channel structures, special HW, etc...	     * */
+/* ******************************************************************* */
+
+/*
+ * Reset the Z8530s and setup special hardware
+ */
+
+static void z8530_init(void)
+{
+	struct scc_channel *scc;
+	int chip, k;
+	unsigned long flags;
+	char *flag;
+
+
+	printk(KERN_INFO "Init Z8530 driver: %u channels, IRQ", Nchips*2);
+	
+	flag=" ";
+	for (k = 0; k < nr_irqs; k++)
+		if (Ivec[k].used) 
+		{
+			printk("%s%d", flag, k);
+			flag=",";
+		}
+	printk("\n");
+
+
+	/* reset and pre-init all chips in the system */
+	for (chip = 0; chip < Nchips; chip++)
+	{
+		scc=&SCC_Info[2*chip];
+		if (!scc->ctrl) continue;
+
+		/* Special SCC cards */
+
+		if(scc->brand & EAGLE)			/* this is an EAGLE card */
+			Outb(scc->special,0x08);	/* enable interrupt on the board */
+			
+		if(scc->brand & (PC100 | PRIMUS))	/* this is a PC100/PRIMUS card */
+			Outb(scc->special,scc->option);	/* set the MODEM mode (0x22) */
+
+			
+		/* Reset and pre-init Z8530 */
+
+		spin_lock_irqsave(&scc->lock, flags);
+				
+		Outb(scc->ctrl, 0);
+		OutReg(scc->ctrl,R9,FHWRES);		/* force hardware reset */
+		udelay(100);				/* give it 'a bit' more time than required */
+		wr(scc, R2, chip*16);			/* interrupt vector */
+		wr(scc, R9, VIS);			/* vector includes status */
+		spin_unlock_irqrestore(&scc->lock, flags);		
+        }
+
+ 
+	Driver_Initialized = 1;
+}
+
+/*
+ * Allocate device structure, err, instance, and register driver
+ */
+
+static int scc_net_alloc(const char *name, struct scc_channel *scc)
+{
+	int err;
+	struct net_device *dev;
+
+	dev = alloc_netdev(0, name, NET_NAME_UNKNOWN, scc_net_setup);
+	if (!dev) 
+		return -ENOMEM;
+
+	dev->ml_priv = scc;
+	scc->dev = dev;
+	spin_lock_init(&scc->lock);
+	timer_setup(&scc->tx_t, NULL, 0);
+	timer_setup(&scc->tx_wdog, NULL, 0);
+
+	err = register_netdevice(dev);
+	if (err) {
+		printk(KERN_ERR "%s: can't register network device (%d)\n", 
+		       name, err);
+		free_netdev(dev);
+		scc->dev = NULL;
+		return err;
+	}
+
+	return 0;
+}
+
+
+
+/* ******************************************************************** */
+/* *			    Network driver methods		      * */
+/* ******************************************************************** */
+
+static const struct net_device_ops scc_netdev_ops = {
+	.ndo_open            = scc_net_open,
+	.ndo_stop	     = scc_net_close,
+	.ndo_start_xmit	     = scc_net_tx,
+	.ndo_set_mac_address = scc_net_set_mac_address,
+	.ndo_get_stats       = scc_net_get_stats,
+	.ndo_do_ioctl        = scc_net_ioctl,
+};
+
+/* ----> Initialize device <----- */
+
+static void scc_net_setup(struct net_device *dev)
+{
+	dev->tx_queue_len    = 16;	/* should be enough... */
+
+	dev->netdev_ops	     = &scc_netdev_ops;
+	dev->header_ops      = &ax25_header_ops;
+
+	memcpy(dev->broadcast, &ax25_bcast,  AX25_ADDR_LEN);
+	memcpy(dev->dev_addr,  &ax25_defaddr, AX25_ADDR_LEN);
+ 
+	dev->flags      = 0;
+
+	dev->type = ARPHRD_AX25;
+	dev->hard_header_len = AX25_MAX_HEADER_LEN + AX25_BPQ_HEADER_LEN;
+	dev->mtu = AX25_DEF_PACLEN;
+	dev->addr_len = AX25_ADDR_LEN;
+
+}
+
+/* ----> open network device <---- */
+
+static int scc_net_open(struct net_device *dev)
+{
+	struct scc_channel *scc = (struct scc_channel *) dev->ml_priv;
+
+ 	if (!scc->init)
+		return -EINVAL;
+
+	scc->tx_buff = NULL;
+	skb_queue_head_init(&scc->tx_queue);
+ 
+	init_channel(scc);
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* ----> close network device <---- */
+
+static int scc_net_close(struct net_device *dev)
+{
+	struct scc_channel *scc = (struct scc_channel *) dev->ml_priv;
+	unsigned long flags;
+
+	netif_stop_queue(dev);
+
+	spin_lock_irqsave(&scc->lock, flags);	
+	Outb(scc->ctrl,0);		/* Make sure pointer is written */
+	wr(scc,R1,0);			/* disable interrupts */
+	wr(scc,R3,0);
+	spin_unlock_irqrestore(&scc->lock, flags);
+
+	del_timer_sync(&scc->tx_t);
+	del_timer_sync(&scc->tx_wdog);
+	
+	scc_discard_buffers(scc);
+
+	return 0;
+}
+
+/* ----> receive frame, called from scc_rxint() <---- */
+
+static void scc_net_rx(struct scc_channel *scc, struct sk_buff *skb)
+{
+	if (skb->len == 0) {
+		dev_kfree_skb_irq(skb);
+		return;
+	}
+		
+	scc->dev_stat.rx_packets++;
+	scc->dev_stat.rx_bytes += skb->len;
+
+	skb->protocol = ax25_type_trans(skb, scc->dev);
+	
+	netif_rx(skb);
+}
+
+/* ----> transmit frame <---- */
+
+static netdev_tx_t scc_net_tx(struct sk_buff *skb, struct net_device *dev)
+{
+	struct scc_channel *scc = (struct scc_channel *) dev->ml_priv;
+	unsigned long flags;
+	char kisscmd;
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return ax25_ip_xmit(skb);
+
+	if (skb->len > scc->stat.bufsize || skb->len < 2) {
+		scc->dev_stat.tx_dropped++;	/* bogus frame */
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+	
+	scc->dev_stat.tx_packets++;
+	scc->dev_stat.tx_bytes += skb->len;
+	scc->stat.txframes++;
+	
+	kisscmd = *skb->data & 0x1f;
+	skb_pull(skb, 1);
+
+	if (kisscmd) {
+		scc_set_param(scc, kisscmd, *skb->data);
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	spin_lock_irqsave(&scc->lock, flags);
+		
+	if (skb_queue_len(&scc->tx_queue) > scc->dev->tx_queue_len) {
+		struct sk_buff *skb_del;
+		skb_del = skb_dequeue(&scc->tx_queue);
+		dev_kfree_skb(skb_del);
+	}
+	skb_queue_tail(&scc->tx_queue, skb);
+	netif_trans_update(dev);
+	
+
+	/*
+	 * Start transmission if the trx state is idle or
+	 * t_idle hasn't expired yet. Use dwait/persistence/slottime
+	 * algorithm for normal halfduplex operation.
+	 */
+
+	if(scc->stat.tx_state == TXS_IDLE || scc->stat.tx_state == TXS_IDLE2) {
+		scc->stat.tx_state = TXS_BUSY;
+		if (scc->kiss.fulldup == KISS_DUPLEX_HALF)
+			__scc_start_tx_timer(scc, t_dwait, scc->kiss.waittime);
+		else
+			__scc_start_tx_timer(scc, t_dwait, 0);
+	}
+	spin_unlock_irqrestore(&scc->lock, flags);
+	return NETDEV_TX_OK;
+}
+
+/* ----> ioctl functions <---- */
+
+/*
+ * SIOCSCCCFG		- configure driver	arg: (struct scc_hw_config *) arg
+ * SIOCSCCINI		- initialize driver	arg: ---
+ * SIOCSCCCHANINI	- initialize channel	arg: (struct scc_modem *) arg
+ * SIOCSCCSMEM		- set memory		arg: (struct scc_mem_config *) arg
+ * SIOCSCCGKISS		- get level 1 parameter	arg: (struct scc_kiss_cmd *) arg
+ * SIOCSCCSKISS		- set level 1 parameter arg: (struct scc_kiss_cmd *) arg
+ * SIOCSCCGSTAT		- get driver status	arg: (struct scc_stat *) arg
+ * SIOCSCCCAL		- send calib. pattern	arg: (struct scc_calibrate *) arg
+ */
+
+static int scc_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct scc_kiss_cmd kiss_cmd;
+	struct scc_mem_config memcfg;
+	struct scc_hw_config hwcfg;
+	struct scc_calibrate cal;
+	struct scc_channel *scc = (struct scc_channel *) dev->ml_priv;
+	int chan;
+	unsigned char device_name[IFNAMSIZ];
+	void __user *arg = ifr->ifr_data;
+	
+	
+	if (!Driver_Initialized)
+	{
+		if (cmd == SIOCSCCCFG)
+		{
+			int found = 1;
+
+			if (!capable(CAP_SYS_RAWIO)) return -EPERM;
+			if (!arg) return -EFAULT;
+
+			if (Nchips >= SCC_MAXCHIPS) 
+				return -EINVAL;
+
+			if (copy_from_user(&hwcfg, arg, sizeof(hwcfg)))
+				return -EFAULT;
+
+			if (hwcfg.irq == 2) hwcfg.irq = 9;
+
+			if (hwcfg.irq < 0 || hwcfg.irq >= nr_irqs)
+				return -EINVAL;
+				
+			if (!Ivec[hwcfg.irq].used && hwcfg.irq)
+			{
+				if (request_irq(hwcfg.irq, scc_isr,
+						0, "AX.25 SCC",
+						(void *)(long) hwcfg.irq))
+					printk(KERN_WARNING "z8530drv: warning, cannot get IRQ %d\n", hwcfg.irq);
+				else
+					Ivec[hwcfg.irq].used = 1;
+			}
+
+			if (hwcfg.vector_latch && !Vector_Latch) {
+				if (!request_region(hwcfg.vector_latch, 1, "scc vector latch"))
+					printk(KERN_WARNING "z8530drv: warning, cannot reserve vector latch port 0x%lx\n, disabled.", hwcfg.vector_latch);
+				else
+					Vector_Latch = hwcfg.vector_latch;
+			}
+
+			if (hwcfg.clock == 0)
+				hwcfg.clock = SCC_DEFAULT_CLOCK;
+
+#ifndef SCC_DONT_CHECK
+
+			if(request_region(hwcfg.ctrl_a, 1, "scc-probe"))
+			{
+				disable_irq(hwcfg.irq);
+				Outb(hwcfg.ctrl_a, 0);
+				OutReg(hwcfg.ctrl_a, R9, FHWRES);
+				udelay(100);
+				OutReg(hwcfg.ctrl_a,R13,0x55);		/* is this chip really there? */
+				udelay(5);
+
+				if (InReg(hwcfg.ctrl_a,R13) != 0x55)
+					found = 0;
+				enable_irq(hwcfg.irq);
+				release_region(hwcfg.ctrl_a, 1);
+			}
+			else
+				found = 0;
+#endif
+
+			if (found)
+			{
+				SCC_Info[2*Nchips  ].ctrl = hwcfg.ctrl_a;
+				SCC_Info[2*Nchips  ].data = hwcfg.data_a;
+				SCC_Info[2*Nchips  ].irq  = hwcfg.irq;
+				SCC_Info[2*Nchips+1].ctrl = hwcfg.ctrl_b;
+				SCC_Info[2*Nchips+1].data = hwcfg.data_b;
+				SCC_Info[2*Nchips+1].irq  = hwcfg.irq;
+			
+				SCC_ctrl[Nchips].chan_A = hwcfg.ctrl_a;
+				SCC_ctrl[Nchips].chan_B = hwcfg.ctrl_b;
+				SCC_ctrl[Nchips].irq    = hwcfg.irq;
+			}
+
+
+			for (chan = 0; chan < 2; chan++)
+			{
+				sprintf(device_name, "%s%i", SCC_DriverName, 2*Nchips+chan);
+
+				SCC_Info[2*Nchips+chan].special = hwcfg.special;
+				SCC_Info[2*Nchips+chan].clock = hwcfg.clock;
+				SCC_Info[2*Nchips+chan].brand = hwcfg.brand;
+				SCC_Info[2*Nchips+chan].option = hwcfg.option;
+				SCC_Info[2*Nchips+chan].enhanced = hwcfg.escc;
+
+#ifdef SCC_DONT_CHECK
+				printk(KERN_INFO "%s: data port = 0x%3.3x  control port = 0x%3.3x\n",
+					device_name, 
+					SCC_Info[2*Nchips+chan].data, 
+					SCC_Info[2*Nchips+chan].ctrl);
+
+#else
+				printk(KERN_INFO "%s: data port = 0x%3.3lx  control port = 0x%3.3lx -- %s\n",
+					device_name,
+					chan? hwcfg.data_b : hwcfg.data_a, 
+					chan? hwcfg.ctrl_b : hwcfg.ctrl_a,
+					found? "found" : "missing");
+#endif
+
+				if (found)
+				{
+					request_region(SCC_Info[2*Nchips+chan].ctrl, 1, "scc ctrl");
+					request_region(SCC_Info[2*Nchips+chan].data, 1, "scc data");
+					if (Nchips+chan != 0 &&
+					    scc_net_alloc(device_name, 
+							  &SCC_Info[2*Nchips+chan]))
+					    return -EINVAL;
+				}
+			}
+			
+			if (found) Nchips++;
+			
+			return 0;
+		}
+		
+		if (cmd == SIOCSCCINI)
+		{
+			if (!capable(CAP_SYS_RAWIO))
+				return -EPERM;
+				
+			if (Nchips == 0)
+				return -EINVAL;
+
+			z8530_init();
+			return 0;
+		}
+		
+		return -EINVAL;	/* confuse the user */
+	}
+	
+	if (!scc->init)
+	{
+		if (cmd == SIOCSCCCHANINI)
+		{
+			if (!capable(CAP_NET_ADMIN)) return -EPERM;
+			if (!arg) return -EINVAL;
+			
+			scc->stat.bufsize   = SCC_BUFSIZE;
+
+			if (copy_from_user(&scc->modem, arg, sizeof(struct scc_modem)))
+				return -EINVAL;
+			
+			/* default KISS Params */
+		
+			if (scc->modem.speed < 4800)
+			{
+				scc->kiss.txdelay = 36;		/* 360 ms */
+				scc->kiss.persist = 42;		/* 25% persistence */			/* was 25 */
+				scc->kiss.slottime = 16;	/* 160 ms */
+				scc->kiss.tailtime = 4;		/* minimal reasonable value */
+				scc->kiss.fulldup = 0;		/* CSMA */
+				scc->kiss.waittime = 50;	/* 500 ms */
+				scc->kiss.maxkeyup = 10;	/* 10 s */
+				scc->kiss.mintime = 3;		/* 3 s */
+				scc->kiss.idletime = 30;	/* 30 s */
+				scc->kiss.maxdefer = 120;	/* 2 min */
+				scc->kiss.softdcd = 0;		/* hardware dcd */
+			} else {
+				scc->kiss.txdelay = 10;		/* 100 ms */
+				scc->kiss.persist = 64;		/* 25% persistence */			/* was 25 */
+				scc->kiss.slottime = 8;		/* 160 ms */
+				scc->kiss.tailtime = 1;		/* minimal reasonable value */
+				scc->kiss.fulldup = 0;		/* CSMA */
+				scc->kiss.waittime = 50;	/* 500 ms */
+				scc->kiss.maxkeyup = 7;		/* 7 s */
+				scc->kiss.mintime = 3;		/* 3 s */
+				scc->kiss.idletime = 30;	/* 30 s */
+				scc->kiss.maxdefer = 120;	/* 2 min */
+				scc->kiss.softdcd = 0;		/* hardware dcd */
+			}
+			
+			scc->tx_buff = NULL;
+			skb_queue_head_init(&scc->tx_queue);
+			scc->init = 1;
+			
+			return 0;
+		}
+		
+		return -EINVAL;
+	}
+	
+	switch(cmd)
+	{
+		case SIOCSCCRESERVED:
+			return -ENOIOCTLCMD;
+
+		case SIOCSCCSMEM:
+			if (!capable(CAP_SYS_RAWIO)) return -EPERM;
+			if (!arg || copy_from_user(&memcfg, arg, sizeof(memcfg)))
+				return -EINVAL;
+			scc->stat.bufsize   = memcfg.bufsize;
+			return 0;
+		
+		case SIOCSCCGSTAT:
+			if (!arg || copy_to_user(arg, &scc->stat, sizeof(scc->stat)))
+				return -EINVAL;
+			return 0;
+		
+		case SIOCSCCGKISS:
+			if (!arg || copy_from_user(&kiss_cmd, arg, sizeof(kiss_cmd)))
+				return -EINVAL;
+			kiss_cmd.param = scc_get_param(scc, kiss_cmd.command);
+			if (copy_to_user(arg, &kiss_cmd, sizeof(kiss_cmd)))
+				return -EINVAL;
+			return 0;
+		
+		case SIOCSCCSKISS:
+			if (!capable(CAP_NET_ADMIN)) return -EPERM;
+			if (!arg || copy_from_user(&kiss_cmd, arg, sizeof(kiss_cmd)))
+				return -EINVAL;
+			return scc_set_param(scc, kiss_cmd.command, kiss_cmd.param);
+		
+		case SIOCSCCCAL:
+			if (!capable(CAP_SYS_RAWIO)) return -EPERM;
+			if (!arg || copy_from_user(&cal, arg, sizeof(cal)) || cal.time == 0)
+				return -EINVAL;
+
+			scc_start_calibrate(scc, cal.time, cal.pattern);
+			return 0;
+
+		default:
+			return -ENOIOCTLCMD;
+		
+	}
+	
+	return -EINVAL;
+}
+
+/* ----> set interface callsign <---- */
+
+static int scc_net_set_mac_address(struct net_device *dev, void *addr)
+{
+	struct sockaddr *sa = (struct sockaddr *) addr;
+	memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+	return 0;
+}
+
+/* ----> get statistics <---- */
+
+static struct net_device_stats *scc_net_get_stats(struct net_device *dev)
+{
+	struct scc_channel *scc = (struct scc_channel *) dev->ml_priv;
+	
+	scc->dev_stat.rx_errors = scc->stat.rxerrs + scc->stat.rx_over;
+	scc->dev_stat.tx_errors = scc->stat.txerrs + scc->stat.tx_under;
+	scc->dev_stat.rx_fifo_errors = scc->stat.rx_over;
+	scc->dev_stat.tx_fifo_errors = scc->stat.tx_under;
+
+	return &scc->dev_stat;
+}
+
+/* ******************************************************************** */
+/* *		dump statistics to /proc/net/z8530drv		      * */
+/* ******************************************************************** */
+
+#ifdef CONFIG_PROC_FS
+
+static inline struct scc_channel *scc_net_seq_idx(loff_t pos)
+{
+	int k;
+
+	for (k = 0; k < Nchips*2; ++k) {
+		if (!SCC_Info[k].init) 
+			continue;
+		if (pos-- == 0)
+			return &SCC_Info[k];
+	}
+	return NULL;
+}
+
+static void *scc_net_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	return *pos ? scc_net_seq_idx(*pos - 1) : SEQ_START_TOKEN;
+	
+}
+
+static void *scc_net_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	unsigned k;
+	struct scc_channel *scc = v;
+	++*pos;
+	
+	for (k = (v == SEQ_START_TOKEN) ? 0 : (scc - SCC_Info)+1;
+	     k < Nchips*2; ++k) {
+		if (SCC_Info[k].init) 
+			return &SCC_Info[k];
+	}
+	return NULL;
+}
+
+static void scc_net_seq_stop(struct seq_file *seq, void *v)
+{
+}
+
+static int scc_net_seq_show(struct seq_file *seq, void *v)
+{
+	if (v == SEQ_START_TOKEN) {
+		seq_puts(seq, "z8530drv-"VERSION"\n");
+	} else if (!Driver_Initialized) {
+		seq_puts(seq, "not initialized\n");
+	} else if (!Nchips) {
+		seq_puts(seq, "chips missing\n");
+	} else {
+		const struct scc_channel *scc = v;
+		const struct scc_stat *stat = &scc->stat;
+		const struct scc_kiss *kiss = &scc->kiss;
+
+
+		/* dev	data ctrl irq clock brand enh vector special option 
+		 *	baud nrz clocksrc softdcd bufsize
+		 *	rxints txints exints spints
+		 *	rcvd rxerrs over / xmit txerrs under / nospace bufsize
+		 *	txd pers slot tail ful wait min maxk idl defr txof grp
+		 *	W ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
+		 *	R ## ## XX ## ## ## ## ## XX ## ## ## ## ## ## ##
+		 */
+
+		seq_printf(seq, "%s\t%3.3lx %3.3lx %d %lu %2.2x %d %3.3lx %3.3lx %d\n",
+				scc->dev->name,
+				scc->data, scc->ctrl, scc->irq, scc->clock, scc->brand,
+				scc->enhanced, Vector_Latch, scc->special,
+				scc->option);
+		seq_printf(seq, "\t%lu %d %d %d %d\n",
+				scc->modem.speed, scc->modem.nrz,
+				scc->modem.clocksrc, kiss->softdcd,
+				stat->bufsize);
+		seq_printf(seq, "\t%lu %lu %lu %lu\n",
+				stat->rxints, stat->txints, stat->exints, stat->spints);
+		seq_printf(seq, "\t%lu %lu %d / %lu %lu %d / %d %d\n",
+				stat->rxframes, stat->rxerrs, stat->rx_over,
+				stat->txframes, stat->txerrs, stat->tx_under,
+				stat->nospace,  stat->tx_state);
+
+#define K(x) kiss->x
+		seq_printf(seq, "\t%d %d %d %d %d %d %d %d %d %d %d %d\n",
+				K(txdelay), K(persist), K(slottime), K(tailtime),
+				K(fulldup), K(waittime), K(mintime), K(maxkeyup),
+				K(idletime), K(maxdefer), K(tx_inhibit), K(group));
+#undef K
+#ifdef SCC_DEBUG
+		{
+			int reg;
+
+		seq_printf(seq, "\tW ");
+			for (reg = 0; reg < 16; reg++)
+				seq_printf(seq, "%2.2x ", scc->wreg[reg]);
+			seq_printf(seq, "\n");
+			
+		seq_printf(seq, "\tR %2.2x %2.2x XX ", InReg(scc->ctrl,R0), InReg(scc->ctrl,R1));
+			for (reg = 3; reg < 8; reg++)
+				seq_printf(seq, "%2.2x ", InReg(scc->ctrl, reg));
+			seq_printf(seq, "XX ");
+			for (reg = 9; reg < 16; reg++)
+				seq_printf(seq, "%2.2x ", InReg(scc->ctrl, reg));
+			seq_printf(seq, "\n");
+		}
+#endif
+		seq_putc(seq, '\n');
+	}
+
+        return 0;
+}
+
+static const struct seq_operations scc_net_seq_ops = {
+	.start  = scc_net_seq_start,
+	.next   = scc_net_seq_next,
+	.stop   = scc_net_seq_stop,
+	.show   = scc_net_seq_show,
+};
+#endif /* CONFIG_PROC_FS */
+
+ 
+/* ******************************************************************** */
+/* * 			Init SCC driver 			      * */
+/* ******************************************************************** */
+
+static int __init scc_init_driver (void)
+{
+	char devname[IFNAMSIZ];
+	
+	printk(banner);
+	
+	sprintf(devname,"%s0", SCC_DriverName);
+	
+	rtnl_lock();
+	if (scc_net_alloc(devname, SCC_Info)) {
+		rtnl_unlock();
+		printk(KERN_ERR "z8530drv: cannot initialize module\n");
+		return -EIO;
+	}
+	rtnl_unlock();
+
+	proc_create_seq("z8530drv", 0, init_net.proc_net, &scc_net_seq_ops);
+
+	return 0;
+}
+
+static void __exit scc_cleanup_driver(void)
+{
+	io_port ctrl;
+	int k;
+	struct scc_channel *scc;
+	struct net_device *dev;
+	
+	if (Nchips == 0 && (dev = SCC_Info[0].dev)) 
+	{
+		unregister_netdev(dev);
+		free_netdev(dev);
+	}
+
+	/* Guard against chip prattle */
+	local_irq_disable();
+	
+	for (k = 0; k < Nchips; k++)
+		if ( (ctrl = SCC_ctrl[k].chan_A) )
+		{
+			Outb(ctrl, 0);
+			OutReg(ctrl,R9,FHWRES);	/* force hardware reset */
+			udelay(50);
+		}
+		
+	/* To unload the port must be closed so no real IRQ pending */
+	for (k = 0; k < nr_irqs ; k++)
+		if (Ivec[k].used) free_irq(k, NULL);
+		
+	local_irq_enable();
+		
+	/* Now clean up */
+	for (k = 0; k < Nchips*2; k++)
+	{
+		scc = &SCC_Info[k];
+		if (scc->ctrl)
+		{
+			release_region(scc->ctrl, 1);
+			release_region(scc->data, 1);
+		}
+		if (scc->dev)
+		{
+			unregister_netdev(scc->dev);
+			free_netdev(scc->dev);
+		}
+	}
+	
+		
+	if (Vector_Latch)
+		release_region(Vector_Latch, 1);
+
+	remove_proc_entry("z8530drv", init_net.proc_net);
+}
+
+MODULE_AUTHOR("Joerg Reuter <jreuter@yaina.de>");
+MODULE_DESCRIPTION("AX.25 Device Driver for Z8530 based HDLC cards");
+MODULE_SUPPORTED_DEVICE("Z8530 based SCC cards for Amateur Radio");
+MODULE_LICENSE("GPL");
+module_init(scc_init_driver);
+module_exit(scc_cleanup_driver);
diff --git a/drivers/net/hamradio/yam.c b/drivers/net/hamradio/yam.c
new file mode 100644
index 0000000..ba9df43
--- /dev/null
+++ b/drivers/net/hamradio/yam.c
@@ -0,0 +1,1208 @@
+/*****************************************************************************/
+
+/*
+ *    yam.c  -- YAM radio modem driver.
+ *
+ *      Copyright (C) 1998 Frederic Rible F1OAT (frible@teaser.fr)
+ *      Adapted from baycom.c driver written by 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.
+ *
+ *
+ *  History:
+ *   0.0 F1OAT 06.06.98  Begin of work with baycom.c source code V 0.3
+ *   0.1 F1OAT 07.06.98  Add timer polling routine for channel arbitration
+ *   0.2 F6FBB 08.06.98  Added delay after FPGA programming
+ *   0.3 F6FBB 29.07.98  Delayed PTT implementation for dupmode=2
+ *   0.4 F6FBB 30.07.98  Added TxTail, Slottime and Persistence
+ *   0.5 F6FBB 01.08.98  Shared IRQs, /proc/net and network statistics
+ *   0.6 F6FBB 25.08.98  Added 1200Bds format
+ *   0.7 F6FBB 12.09.98  Added to the kernel configuration
+ *   0.8 F6FBB 14.10.98  Fixed slottime/persistence timing bug
+ *       OK1ZIA 2.09.01  Fixed "kfree_skb on hard IRQ" 
+ *                       using dev_kfree_skb_any(). (important in 2.4 kernel)
+ *   
+ */
+
+/*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/net.h>
+#include <linux/in.h>
+#include <linux/if.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/bitops.h>
+#include <linux/random.h>
+#include <asm/io.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/firmware.h>
+#include <linux/platform_device.h>
+
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <net/ax25.h>
+
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <net/net_namespace.h>
+
+#include <linux/uaccess.h>
+#include <linux/init.h>
+
+#include <linux/yam.h>
+
+/* --------------------------------------------------------------------- */
+
+static const char yam_drvname[] = "yam";
+static const char yam_drvinfo[] __initconst = KERN_INFO \
+	"YAM driver version 0.8 by F1OAT/F6FBB\n";
+
+/* --------------------------------------------------------------------- */
+
+#define FIRMWARE_9600	"yam/9600.bin"
+#define FIRMWARE_1200	"yam/1200.bin"
+
+#define YAM_9600	1
+#define YAM_1200	2
+
+#define NR_PORTS	4
+#define YAM_MAGIC	0xF10A7654
+
+/* Transmitter states */
+
+#define TX_OFF		0
+#define TX_HEAD		1
+#define TX_DATA		2
+#define TX_CRC1		3
+#define TX_CRC2		4
+#define TX_TAIL		5
+
+#define YAM_MAX_FRAME	1024
+
+#define DEFAULT_BITRATE	9600			/* bps */
+#define DEFAULT_HOLDD	10			/* sec */
+#define DEFAULT_TXD	300			/* ms */
+#define DEFAULT_TXTAIL	10			/* ms */
+#define DEFAULT_SLOT	100			/* ms */
+#define DEFAULT_PERS	64			/* 0->255 */
+
+struct yam_port {
+	int magic;
+	int bitrate;
+	int baudrate;
+	int iobase;
+	int irq;
+	int dupmode;
+
+	struct net_device *dev;
+
+	int nb_rxint;
+	int nb_mdint;
+
+	/* Parameters section */
+
+	int txd;				/* tx delay */
+	int holdd;				/* duplex ptt delay */
+	int txtail;				/* txtail delay */
+	int slot;				/* slottime */
+	int pers;				/* persistence */
+
+	/* Tx section */
+
+	int tx_state;
+	int tx_count;
+	int slotcnt;
+	unsigned char tx_buf[YAM_MAX_FRAME];
+	int tx_len;
+	int tx_crcl, tx_crch;
+	struct sk_buff_head send_queue;		/* Packets awaiting transmission */
+
+	/* Rx section */
+
+	int dcd;
+	unsigned char rx_buf[YAM_MAX_FRAME];
+	int rx_len;
+	int rx_crcl, rx_crch;
+};
+
+struct yam_mcs {
+	unsigned char bits[YAM_FPGA_SIZE];
+	int bitrate;
+	struct yam_mcs *next;
+};
+
+static struct net_device *yam_devs[NR_PORTS];
+
+static struct yam_mcs *yam_data;
+
+static DEFINE_TIMER(yam_timer, NULL);
+
+/* --------------------------------------------------------------------- */
+
+#define RBR(iobase)	(iobase+0)
+#define THR(iobase)	(iobase+0)
+#define IER(iobase)	(iobase+1)
+#define IIR(iobase)	(iobase+2)
+#define FCR(iobase)	(iobase+2)
+#define LCR(iobase)	(iobase+3)
+#define MCR(iobase)	(iobase+4)
+#define LSR(iobase)	(iobase+5)
+#define MSR(iobase)	(iobase+6)
+#define SCR(iobase)	(iobase+7)
+#define DLL(iobase)	(iobase+0)
+#define DLM(iobase)	(iobase+1)
+
+#define YAM_EXTENT	8
+
+/* Interrupt Identification Register Bit Masks */
+#define IIR_NOPEND	1
+#define IIR_MSR		0
+#define IIR_TX		2
+#define IIR_RX		4
+#define IIR_LSR		6
+#define IIR_TIMEOUT	12			/* Fifo mode only */
+
+#define IIR_MASK	0x0F
+
+/* Interrupt Enable Register Bit Masks */
+#define IER_RX		1			/* enable rx interrupt */
+#define IER_TX		2			/* enable tx interrupt */
+#define IER_LSR		4			/* enable line status interrupts */
+#define IER_MSR		8			/* enable modem status interrupts */
+
+/* Modem Control Register Bit Masks */
+#define MCR_DTR		0x01			/* DTR output */
+#define MCR_RTS		0x02			/* RTS output */
+#define MCR_OUT1	0x04			/* OUT1 output (not accessible in RS232) */
+#define MCR_OUT2	0x08			/* Master Interrupt enable (must be set on PCs) */
+#define MCR_LOOP	0x10			/* Loopback enable */
+
+/* Modem Status Register Bit Masks */
+#define MSR_DCTS	0x01			/* Delta CTS input */
+#define MSR_DDSR	0x02			/* Delta DSR */
+#define MSR_DRIN	0x04			/* Delta RI */
+#define MSR_DDCD	0x08			/* Delta DCD */
+#define MSR_CTS		0x10			/* CTS input */
+#define MSR_DSR		0x20			/* DSR input */
+#define MSR_RING	0x40			/* RI  input */
+#define MSR_DCD		0x80			/* DCD input */
+
+/* line status register bit mask */
+#define LSR_RXC		0x01
+#define LSR_OE		0x02
+#define LSR_PE		0x04
+#define LSR_FE		0x08
+#define LSR_BREAK	0x10
+#define LSR_THRE	0x20
+#define LSR_TSRE	0x40
+
+/* Line Control Register Bit Masks */
+#define LCR_DLAB	0x80
+#define LCR_BREAK	0x40
+#define LCR_PZERO	0x28
+#define LCR_PEVEN	0x18
+#define LCR_PODD	0x08
+#define LCR_STOP1	0x00
+#define LCR_STOP2	0x04
+#define LCR_BIT5	0x00
+#define LCR_BIT6	0x02
+#define LCR_BIT7	0x01
+#define LCR_BIT8	0x03
+
+/* YAM Modem <-> UART Port mapping */
+
+#define TX_RDY		MSR_DCTS		/* transmitter ready to send */
+#define RX_DCD		MSR_DCD			/* carrier detect */
+#define RX_FLAG		MSR_RING		/* hdlc flag received */
+#define FPGA_DONE	MSR_DSR			/* FPGA is configured */
+#define PTT_ON		(MCR_RTS|MCR_OUT2)	/* activate PTT */
+#define PTT_OFF		(MCR_DTR|MCR_OUT2)	/* release PTT */
+
+#define ENABLE_RXINT	IER_RX			/* enable uart rx interrupt during rx */
+#define ENABLE_TXINT	IER_MSR			/* enable uart ms interrupt during tx */
+#define ENABLE_RTXINT	(IER_RX|IER_MSR)	/* full duplex operations */
+
+
+/*************************************************************************
+* CRC Tables
+************************************************************************/
+
+static const unsigned char chktabl[256] =
+{0x00, 0x89, 0x12, 0x9b, 0x24, 0xad, 0x36, 0xbf, 0x48, 0xc1, 0x5a, 0xd3, 0x6c, 0xe5, 0x7e,
+ 0xf7, 0x81, 0x08, 0x93, 0x1a, 0xa5, 0x2c, 0xb7, 0x3e, 0xc9, 0x40, 0xdb, 0x52, 0xed, 0x64,
+ 0xff, 0x76, 0x02, 0x8b, 0x10, 0x99, 0x26, 0xaf, 0x34, 0xbd, 0x4a, 0xc3, 0x58, 0xd1, 0x6e,
+ 0xe7, 0x7c, 0xf5, 0x83, 0x0a, 0x91, 0x18, 0xa7, 0x2e, 0xb5, 0x3c, 0xcb, 0x42, 0xd9, 0x50,
+ 0xef, 0x66, 0xfd, 0x74, 0x04, 0x8d, 0x16, 0x9f, 0x20, 0xa9, 0x32, 0xbb, 0x4c, 0xc5, 0x5e,
+ 0xd7, 0x68, 0xe1, 0x7a, 0xf3, 0x85, 0x0c, 0x97, 0x1e, 0xa1, 0x28, 0xb3, 0x3a, 0xcd, 0x44,
+ 0xdf, 0x56, 0xe9, 0x60, 0xfb, 0x72, 0x06, 0x8f, 0x14, 0x9d, 0x22, 0xab, 0x30, 0xb9, 0x4e,
+ 0xc7, 0x5c, 0xd5, 0x6a, 0xe3, 0x78, 0xf1, 0x87, 0x0e, 0x95, 0x1c, 0xa3, 0x2a, 0xb1, 0x38,
+ 0xcf, 0x46, 0xdd, 0x54, 0xeb, 0x62, 0xf9, 0x70, 0x08, 0x81, 0x1a, 0x93, 0x2c, 0xa5, 0x3e,
+ 0xb7, 0x40, 0xc9, 0x52, 0xdb, 0x64, 0xed, 0x76, 0xff, 0x89, 0x00, 0x9b, 0x12, 0xad, 0x24,
+ 0xbf, 0x36, 0xc1, 0x48, 0xd3, 0x5a, 0xe5, 0x6c, 0xf7, 0x7e, 0x0a, 0x83, 0x18, 0x91, 0x2e,
+ 0xa7, 0x3c, 0xb5, 0x42, 0xcb, 0x50, 0xd9, 0x66, 0xef, 0x74, 0xfd, 0x8b, 0x02, 0x99, 0x10,
+ 0xaf, 0x26, 0xbd, 0x34, 0xc3, 0x4a, 0xd1, 0x58, 0xe7, 0x6e, 0xf5, 0x7c, 0x0c, 0x85, 0x1e,
+ 0x97, 0x28, 0xa1, 0x3a, 0xb3, 0x44, 0xcd, 0x56, 0xdf, 0x60, 0xe9, 0x72, 0xfb, 0x8d, 0x04,
+ 0x9f, 0x16, 0xa9, 0x20, 0xbb, 0x32, 0xc5, 0x4c, 0xd7, 0x5e, 0xe1, 0x68, 0xf3, 0x7a, 0x0e,
+ 0x87, 0x1c, 0x95, 0x2a, 0xa3, 0x38, 0xb1, 0x46, 0xcf, 0x54, 0xdd, 0x62, 0xeb, 0x70, 0xf9,
+ 0x8f, 0x06, 0x9d, 0x14, 0xab, 0x22, 0xb9, 0x30, 0xc7, 0x4e, 0xd5, 0x5c, 0xe3, 0x6a, 0xf1,
+ 0x78};
+static const unsigned char chktabh[256] =
+{0x00, 0x11, 0x23, 0x32, 0x46, 0x57, 0x65, 0x74, 0x8c, 0x9d, 0xaf, 0xbe, 0xca, 0xdb, 0xe9,
+ 0xf8, 0x10, 0x01, 0x33, 0x22, 0x56, 0x47, 0x75, 0x64, 0x9c, 0x8d, 0xbf, 0xae, 0xda, 0xcb,
+ 0xf9, 0xe8, 0x21, 0x30, 0x02, 0x13, 0x67, 0x76, 0x44, 0x55, 0xad, 0xbc, 0x8e, 0x9f, 0xeb,
+ 0xfa, 0xc8, 0xd9, 0x31, 0x20, 0x12, 0x03, 0x77, 0x66, 0x54, 0x45, 0xbd, 0xac, 0x9e, 0x8f,
+ 0xfb, 0xea, 0xd8, 0xc9, 0x42, 0x53, 0x61, 0x70, 0x04, 0x15, 0x27, 0x36, 0xce, 0xdf, 0xed,
+ 0xfc, 0x88, 0x99, 0xab, 0xba, 0x52, 0x43, 0x71, 0x60, 0x14, 0x05, 0x37, 0x26, 0xde, 0xcf,
+ 0xfd, 0xec, 0x98, 0x89, 0xbb, 0xaa, 0x63, 0x72, 0x40, 0x51, 0x25, 0x34, 0x06, 0x17, 0xef,
+ 0xfe, 0xcc, 0xdd, 0xa9, 0xb8, 0x8a, 0x9b, 0x73, 0x62, 0x50, 0x41, 0x35, 0x24, 0x16, 0x07,
+ 0xff, 0xee, 0xdc, 0xcd, 0xb9, 0xa8, 0x9a, 0x8b, 0x84, 0x95, 0xa7, 0xb6, 0xc2, 0xd3, 0xe1,
+ 0xf0, 0x08, 0x19, 0x2b, 0x3a, 0x4e, 0x5f, 0x6d, 0x7c, 0x94, 0x85, 0xb7, 0xa6, 0xd2, 0xc3,
+ 0xf1, 0xe0, 0x18, 0x09, 0x3b, 0x2a, 0x5e, 0x4f, 0x7d, 0x6c, 0xa5, 0xb4, 0x86, 0x97, 0xe3,
+ 0xf2, 0xc0, 0xd1, 0x29, 0x38, 0x0a, 0x1b, 0x6f, 0x7e, 0x4c, 0x5d, 0xb5, 0xa4, 0x96, 0x87,
+ 0xf3, 0xe2, 0xd0, 0xc1, 0x39, 0x28, 0x1a, 0x0b, 0x7f, 0x6e, 0x5c, 0x4d, 0xc6, 0xd7, 0xe5,
+ 0xf4, 0x80, 0x91, 0xa3, 0xb2, 0x4a, 0x5b, 0x69, 0x78, 0x0c, 0x1d, 0x2f, 0x3e, 0xd6, 0xc7,
+ 0xf5, 0xe4, 0x90, 0x81, 0xb3, 0xa2, 0x5a, 0x4b, 0x79, 0x68, 0x1c, 0x0d, 0x3f, 0x2e, 0xe7,
+ 0xf6, 0xc4, 0xd5, 0xa1, 0xb0, 0x82, 0x93, 0x6b, 0x7a, 0x48, 0x59, 0x2d, 0x3c, 0x0e, 0x1f,
+ 0xf7, 0xe6, 0xd4, 0xc5, 0xb1, 0xa0, 0x92, 0x83, 0x7b, 0x6a, 0x58, 0x49, 0x3d, 0x2c, 0x1e,
+ 0x0f};
+
+/*************************************************************************
+* FPGA functions
+************************************************************************/
+
+static void delay(int ms)
+{
+	unsigned long timeout = jiffies + ((ms * HZ) / 1000);
+	while (time_before(jiffies, timeout))
+		cpu_relax();
+}
+
+/*
+ * reset FPGA
+ */
+
+static void fpga_reset(int iobase)
+{
+	outb(0, IER(iobase));
+	outb(LCR_DLAB | LCR_BIT5, LCR(iobase));
+	outb(1, DLL(iobase));
+	outb(0, DLM(iobase));
+
+	outb(LCR_BIT5, LCR(iobase));
+	inb(LSR(iobase));
+	inb(MSR(iobase));
+	/* turn off FPGA supply voltage */
+	outb(MCR_OUT1 | MCR_OUT2, MCR(iobase));
+	delay(100);
+	/* turn on FPGA supply voltage again */
+	outb(MCR_DTR | MCR_RTS | MCR_OUT1 | MCR_OUT2, MCR(iobase));
+	delay(100);
+}
+
+/*
+ * send one byte to FPGA
+ */
+
+static int fpga_write(int iobase, unsigned char wrd)
+{
+	unsigned char bit;
+	int k;
+	unsigned long timeout = jiffies + HZ / 10;
+
+	for (k = 0; k < 8; k++) {
+		bit = (wrd & 0x80) ? (MCR_RTS | MCR_DTR) : MCR_DTR;
+		outb(bit | MCR_OUT1 | MCR_OUT2, MCR(iobase));
+		wrd <<= 1;
+		outb(0xfc, THR(iobase));
+		while ((inb(LSR(iobase)) & LSR_TSRE) == 0)
+			if (time_after(jiffies, timeout))
+				return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * predef should be 0 for loading user defined mcs
+ * predef should be YAM_1200 for loading predef 1200 mcs
+ * predef should be YAM_9600 for loading predef 9600 mcs
+ */
+static unsigned char *add_mcs(unsigned char *bits, int bitrate,
+			      unsigned int predef)
+{
+	const char *fw_name[2] = {FIRMWARE_9600, FIRMWARE_1200};
+	const struct firmware *fw;
+	struct platform_device *pdev;
+	struct yam_mcs *p;
+	int err;
+
+	switch (predef) {
+	case 0:
+		fw = NULL;
+		break;
+	case YAM_1200:
+	case YAM_9600:
+		predef--;
+		pdev = platform_device_register_simple("yam", 0, NULL, 0);
+		if (IS_ERR(pdev)) {
+			printk(KERN_ERR "yam: Failed to register firmware\n");
+			return NULL;
+		}
+		err = request_firmware(&fw, fw_name[predef], &pdev->dev);
+		platform_device_unregister(pdev);
+		if (err) {
+			printk(KERN_ERR "Failed to load firmware \"%s\"\n",
+			       fw_name[predef]);
+			return NULL;
+		}
+		if (fw->size != YAM_FPGA_SIZE) {
+			printk(KERN_ERR "Bogus length %zu in firmware \"%s\"\n",
+			       fw->size, fw_name[predef]);
+			release_firmware(fw);
+			return NULL;
+		}
+		bits = (unsigned char *)fw->data;
+		break;
+	default:
+		printk(KERN_ERR "yam: Invalid predef number %u\n", predef);
+		return NULL;
+	}
+
+	/* If it already exists, replace the bit data */
+	p = yam_data;
+	while (p) {
+		if (p->bitrate == bitrate) {
+			memcpy(p->bits, bits, YAM_FPGA_SIZE);
+			goto out;
+		}
+		p = p->next;
+	}
+
+	/* Allocate a new mcs */
+	if ((p = kmalloc(sizeof(struct yam_mcs), GFP_KERNEL)) == NULL) {
+		release_firmware(fw);
+		return NULL;
+	}
+	memcpy(p->bits, bits, YAM_FPGA_SIZE);
+	p->bitrate = bitrate;
+	p->next = yam_data;
+	yam_data = p;
+ out:
+	release_firmware(fw);
+	return p->bits;
+}
+
+static unsigned char *get_mcs(int bitrate)
+{
+	struct yam_mcs *p;
+
+	p = yam_data;
+	while (p) {
+		if (p->bitrate == bitrate)
+			return p->bits;
+		p = p->next;
+	}
+
+	/* Load predefined mcs data */
+	switch (bitrate) {
+	case 1200:
+		/* setting predef as YAM_1200 for loading predef 1200 mcs */
+		return add_mcs(NULL, bitrate, YAM_1200);
+	default:
+		/* setting predef as YAM_9600 for loading predef 9600 mcs */
+		return add_mcs(NULL, bitrate, YAM_9600);
+	}
+}
+
+/*
+ * download bitstream to FPGA
+ * data is contained in bits[] array in yam1200.h resp. yam9600.h
+ */
+
+static int fpga_download(int iobase, int bitrate)
+{
+	int i, rc;
+	unsigned char *pbits;
+
+	pbits = get_mcs(bitrate);
+	if (pbits == NULL)
+		return -1;
+
+	fpga_reset(iobase);
+	for (i = 0; i < YAM_FPGA_SIZE; i++) {
+		if (fpga_write(iobase, pbits[i])) {
+			printk(KERN_ERR "yam: error in write cycle\n");
+			return -1;			/* write... */
+		}
+	}
+
+	fpga_write(iobase, 0xFF);
+	rc = inb(MSR(iobase));		/* check DONE signal */
+
+	/* Needed for some hardwares */
+	delay(50);
+
+	return (rc & MSR_DSR) ? 0 : -1;
+}
+
+
+/************************************************************************
+* Serial port init 
+************************************************************************/
+
+static void yam_set_uart(struct net_device *dev)
+{
+	struct yam_port *yp = netdev_priv(dev);
+	int divisor = 115200 / yp->baudrate;
+
+	outb(0, IER(dev->base_addr));
+	outb(LCR_DLAB | LCR_BIT8, LCR(dev->base_addr));
+	outb(divisor, DLL(dev->base_addr));
+	outb(0, DLM(dev->base_addr));
+	outb(LCR_BIT8, LCR(dev->base_addr));
+	outb(PTT_OFF, MCR(dev->base_addr));
+	outb(0x00, FCR(dev->base_addr));
+
+	/* Flush pending irq */
+
+	inb(RBR(dev->base_addr));
+	inb(MSR(dev->base_addr));
+
+	/* Enable rx irq */
+
+	outb(ENABLE_RTXINT, IER(dev->base_addr));
+}
+
+
+/* --------------------------------------------------------------------- */
+
+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 yam_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};
+
+	b1 = inb(MCR(iobase));
+	outb(b1 | 0x10, MCR(iobase));	/* loopback mode */
+	b2 = inb(MSR(iobase));
+	outb(0x1a, MCR(iobase));
+	b3 = inb(MSR(iobase)) & 0xf0;
+	outb(b1, MCR(iobase));		/* restore old values */
+	outb(b2, MSR(iobase));
+	if (b3 != 0x90)
+		return c_uart_unknown;
+	inb(RBR(iobase));
+	inb(RBR(iobase));
+	outb(0x01, FCR(iobase));	/* enable FIFOs */
+	u = uart_tab[(inb(IIR(iobase)) >> 6) & 3];
+	if (u == c_uart_16450) {
+		outb(0x5a, SCR(iobase));
+		b1 = inb(SCR(iobase));
+		outb(0xa5, SCR(iobase));
+		b2 = inb(SCR(iobase));
+		if ((b1 != 0x5a) || (b2 != 0xa5))
+			u = c_uart_8250;
+	}
+	return u;
+}
+
+/******************************************************************************
+* Rx Section
+******************************************************************************/
+static inline void yam_rx_flag(struct net_device *dev, struct yam_port *yp)
+{
+	if (yp->dcd && yp->rx_len >= 3 && yp->rx_len < YAM_MAX_FRAME) {
+		int pkt_len = yp->rx_len - 2 + 1;	/* -CRC + kiss */
+		struct sk_buff *skb;
+
+		if ((yp->rx_crch & yp->rx_crcl) != 0xFF) {
+			/* Bad crc */
+		} else {
+			if (!(skb = dev_alloc_skb(pkt_len))) {
+				printk(KERN_WARNING "%s: memory squeeze, dropping packet\n", dev->name);
+				++dev->stats.rx_dropped;
+			} else {
+				unsigned char *cp;
+				cp = skb_put(skb, pkt_len);
+				*cp++ = 0;		/* KISS kludge */
+				memcpy(cp, yp->rx_buf, pkt_len - 1);
+				skb->protocol = ax25_type_trans(skb, dev);
+				netif_rx(skb);
+				++dev->stats.rx_packets;
+			}
+		}
+	}
+	yp->rx_len = 0;
+	yp->rx_crcl = 0x21;
+	yp->rx_crch = 0xf3;
+}
+
+static inline void yam_rx_byte(struct net_device *dev, struct yam_port *yp, unsigned char rxb)
+{
+	if (yp->rx_len < YAM_MAX_FRAME) {
+		unsigned char c = yp->rx_crcl;
+		yp->rx_crcl = (chktabl[c] ^ yp->rx_crch);
+		yp->rx_crch = (chktabh[c] ^ rxb);
+		yp->rx_buf[yp->rx_len++] = rxb;
+	}
+}
+
+/********************************************************************************
+* TX Section
+********************************************************************************/
+
+static void ptt_on(struct net_device *dev)
+{
+	outb(PTT_ON, MCR(dev->base_addr));
+}
+
+static void ptt_off(struct net_device *dev)
+{
+	outb(PTT_OFF, MCR(dev->base_addr));
+}
+
+static netdev_tx_t yam_send_packet(struct sk_buff *skb,
+					 struct net_device *dev)
+{
+	struct yam_port *yp = netdev_priv(dev);
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return ax25_ip_xmit(skb);
+
+	skb_queue_tail(&yp->send_queue, skb);
+	netif_trans_update(dev);
+	return NETDEV_TX_OK;
+}
+
+static void yam_start_tx(struct net_device *dev, struct yam_port *yp)
+{
+	if ((yp->tx_state == TX_TAIL) || (yp->txd == 0))
+		yp->tx_count = 1;
+	else
+		yp->tx_count = (yp->bitrate * yp->txd) / 8000;
+	yp->tx_state = TX_HEAD;
+	ptt_on(dev);
+}
+
+static void yam_arbitrate(struct net_device *dev)
+{
+	struct yam_port *yp = netdev_priv(dev);
+
+	if (yp->magic != YAM_MAGIC || yp->tx_state != TX_OFF ||
+	    skb_queue_empty(&yp->send_queue))
+		return;
+	/* tx_state is TX_OFF and there is data to send */
+
+	if (yp->dupmode) {
+		/* Full duplex mode, don't wait */
+		yam_start_tx(dev, yp);
+		return;
+	}
+	if (yp->dcd) {
+		/* DCD on, wait slotime ... */
+		yp->slotcnt = yp->slot / 10;
+		return;
+	}
+	/* Is slottime passed ? */
+	if ((--yp->slotcnt) > 0)
+		return;
+
+	yp->slotcnt = yp->slot / 10;
+
+	/* is random > persist ? */
+	if ((prandom_u32() % 256) > yp->pers)
+		return;
+
+	yam_start_tx(dev, yp);
+}
+
+static void yam_dotimer(struct timer_list *unused)
+{
+	int i;
+
+	for (i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev = yam_devs[i];
+		if (dev && netif_running(dev))
+			yam_arbitrate(dev);
+	}
+	yam_timer.expires = jiffies + HZ / 100;
+	add_timer(&yam_timer);
+}
+
+static void yam_tx_byte(struct net_device *dev, struct yam_port *yp)
+{
+	struct sk_buff *skb;
+	unsigned char b, temp;
+
+	switch (yp->tx_state) {
+	case TX_OFF:
+		break;
+	case TX_HEAD:
+		if (--yp->tx_count <= 0) {
+			if (!(skb = skb_dequeue(&yp->send_queue))) {
+				ptt_off(dev);
+				yp->tx_state = TX_OFF;
+				break;
+			}
+			yp->tx_state = TX_DATA;
+			if (skb->data[0] != 0) {
+/*                              do_kiss_params(s, skb->data, skb->len); */
+				dev_kfree_skb_any(skb);
+				break;
+			}
+			yp->tx_len = skb->len - 1;	/* strip KISS byte */
+			if (yp->tx_len >= YAM_MAX_FRAME || yp->tx_len < 2) {
+        			dev_kfree_skb_any(skb);
+				break;
+			}
+			skb_copy_from_linear_data_offset(skb, 1,
+							 yp->tx_buf,
+							 yp->tx_len);
+			dev_kfree_skb_any(skb);
+			yp->tx_count = 0;
+			yp->tx_crcl = 0x21;
+			yp->tx_crch = 0xf3;
+			yp->tx_state = TX_DATA;
+		}
+		break;
+	case TX_DATA:
+		b = yp->tx_buf[yp->tx_count++];
+		outb(b, THR(dev->base_addr));
+		temp = yp->tx_crcl;
+		yp->tx_crcl = chktabl[temp] ^ yp->tx_crch;
+		yp->tx_crch = chktabh[temp] ^ b;
+		if (yp->tx_count >= yp->tx_len) {
+			yp->tx_state = TX_CRC1;
+		}
+		break;
+	case TX_CRC1:
+		yp->tx_crch = chktabl[yp->tx_crcl] ^ yp->tx_crch;
+		yp->tx_crcl = chktabh[yp->tx_crcl] ^ chktabl[yp->tx_crch] ^ 0xff;
+		outb(yp->tx_crcl, THR(dev->base_addr));
+		yp->tx_state = TX_CRC2;
+		break;
+	case TX_CRC2:
+		outb(chktabh[yp->tx_crch] ^ 0xFF, THR(dev->base_addr));
+		if (skb_queue_empty(&yp->send_queue)) {
+			yp->tx_count = (yp->bitrate * yp->txtail) / 8000;
+			if (yp->dupmode == 2)
+				yp->tx_count += (yp->bitrate * yp->holdd) / 8;
+			if (yp->tx_count == 0)
+				yp->tx_count = 1;
+			yp->tx_state = TX_TAIL;
+		} else {
+			yp->tx_count = 1;
+			yp->tx_state = TX_HEAD;
+		}
+		++dev->stats.tx_packets;
+		break;
+	case TX_TAIL:
+		if (--yp->tx_count <= 0) {
+			yp->tx_state = TX_OFF;
+			ptt_off(dev);
+		}
+		break;
+	}
+}
+
+/***********************************************************************************
+* ISR routine
+************************************************************************************/
+
+static irqreturn_t yam_interrupt(int irq, void *dev_id)
+{
+	struct net_device *dev;
+	struct yam_port *yp;
+	unsigned char iir;
+	int counter = 100;
+	int i;
+	int handled = 0;
+
+	for (i = 0; i < NR_PORTS; i++) {
+		dev = yam_devs[i];
+		yp = netdev_priv(dev);
+
+		if (!netif_running(dev))
+			continue;
+
+		while ((iir = IIR_MASK & inb(IIR(dev->base_addr))) != IIR_NOPEND) {
+			unsigned char msr = inb(MSR(dev->base_addr));
+			unsigned char lsr = inb(LSR(dev->base_addr));
+			unsigned char rxb;
+
+			handled = 1;
+
+			if (lsr & LSR_OE)
+				++dev->stats.rx_fifo_errors;
+
+			yp->dcd = (msr & RX_DCD) ? 1 : 0;
+
+			if (--counter <= 0) {
+				printk(KERN_ERR "%s: too many irq iir=%d\n",
+						dev->name, iir);
+				goto out;
+			}
+			if (msr & TX_RDY) {
+				++yp->nb_mdint;
+				yam_tx_byte(dev, yp);
+			}
+			if (lsr & LSR_RXC) {
+				++yp->nb_rxint;
+				rxb = inb(RBR(dev->base_addr));
+				if (msr & RX_FLAG)
+					yam_rx_flag(dev, yp);
+				else
+					yam_rx_byte(dev, yp, rxb);
+			}
+		}
+	}
+out:
+	return IRQ_RETVAL(handled);
+}
+
+#ifdef CONFIG_PROC_FS
+
+static void *yam_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	return (*pos < NR_PORTS) ? yam_devs[*pos] : NULL;
+}
+
+static void *yam_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	++*pos;
+	return (*pos < NR_PORTS) ? yam_devs[*pos] : NULL;
+}
+
+static void yam_seq_stop(struct seq_file *seq, void *v)
+{
+}
+
+static int yam_seq_show(struct seq_file *seq, void *v)
+{
+	struct net_device *dev = v;
+	const struct yam_port *yp = netdev_priv(dev);
+
+	seq_printf(seq, "Device %s\n", dev->name);
+	seq_printf(seq, "  Up       %d\n", netif_running(dev));
+	seq_printf(seq, "  Speed    %u\n", yp->bitrate);
+	seq_printf(seq, "  IoBase   0x%x\n", yp->iobase);
+	seq_printf(seq, "  BaudRate %u\n", yp->baudrate);
+	seq_printf(seq, "  IRQ      %u\n", yp->irq);
+	seq_printf(seq, "  TxState  %u\n", yp->tx_state);
+	seq_printf(seq, "  Duplex   %u\n", yp->dupmode);
+	seq_printf(seq, "  HoldDly  %u\n", yp->holdd);
+	seq_printf(seq, "  TxDelay  %u\n", yp->txd);
+	seq_printf(seq, "  TxTail   %u\n", yp->txtail);
+	seq_printf(seq, "  SlotTime %u\n", yp->slot);
+	seq_printf(seq, "  Persist  %u\n", yp->pers);
+	seq_printf(seq, "  TxFrames %lu\n", dev->stats.tx_packets);
+	seq_printf(seq, "  RxFrames %lu\n", dev->stats.rx_packets);
+	seq_printf(seq, "  TxInt    %u\n", yp->nb_mdint);
+	seq_printf(seq, "  RxInt    %u\n", yp->nb_rxint);
+	seq_printf(seq, "  RxOver   %lu\n", dev->stats.rx_fifo_errors);
+	seq_printf(seq, "\n");
+	return 0;
+}
+
+static const struct seq_operations yam_seqops = {
+	.start = yam_seq_start,
+	.next = yam_seq_next,
+	.stop = yam_seq_stop,
+	.show = yam_seq_show,
+};
+#endif
+
+
+/* --------------------------------------------------------------------- */
+
+static int yam_open(struct net_device *dev)
+{
+	struct yam_port *yp = netdev_priv(dev);
+	enum uart u;
+	int i;
+	int ret=0;
+
+	printk(KERN_INFO "Trying %s at iobase 0x%lx irq %u\n", dev->name, dev->base_addr, dev->irq);
+
+	if (!yp->bitrate)
+		return -ENXIO;
+	if (!dev->base_addr || dev->base_addr > 0x1000 - YAM_EXTENT ||
+		dev->irq < 2 || dev->irq > 15) {
+		return -ENXIO;
+	}
+	if (!request_region(dev->base_addr, YAM_EXTENT, dev->name))
+	{
+		printk(KERN_ERR "%s: cannot 0x%lx busy\n", dev->name, dev->base_addr);
+		return -EACCES;
+	}
+	if ((u = yam_check_uart(dev->base_addr)) == c_uart_unknown) {
+		printk(KERN_ERR "%s: cannot find uart type\n", dev->name);
+		ret = -EIO;
+		goto out_release_base;
+	}
+	if (fpga_download(dev->base_addr, yp->bitrate)) {
+		printk(KERN_ERR "%s: cannot init FPGA\n", dev->name);
+		ret = -EIO;
+		goto out_release_base;
+	}
+	outb(0, IER(dev->base_addr));
+	if (request_irq(dev->irq, yam_interrupt, IRQF_SHARED, dev->name, dev)) {
+		printk(KERN_ERR "%s: irq %d busy\n", dev->name, dev->irq);
+		ret = -EBUSY;
+		goto out_release_base;
+	}
+
+	yam_set_uart(dev);
+
+	netif_start_queue(dev);
+	
+	yp->slotcnt = yp->slot / 10;
+
+	/* Reset overruns for all ports - FPGA programming makes overruns */
+	for (i = 0; i < NR_PORTS; i++) {
+		struct net_device *yam_dev = yam_devs[i];
+
+		inb(LSR(yam_dev->base_addr));
+		yam_dev->stats.rx_fifo_errors = 0;
+	}
+
+	printk(KERN_INFO "%s at iobase 0x%lx irq %u uart %s\n", dev->name, dev->base_addr, dev->irq,
+		   uart_str[u]);
+	return 0;
+
+out_release_base:
+	release_region(dev->base_addr, YAM_EXTENT);
+	return ret;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int yam_close(struct net_device *dev)
+{
+	struct sk_buff *skb;
+	struct yam_port *yp = netdev_priv(dev);
+
+	if (!dev)
+		return -EINVAL;
+
+	/*
+	 * disable interrupts
+	 */
+	outb(0, IER(dev->base_addr));
+	outb(1, MCR(dev->base_addr));
+	/* Remove IRQ handler if last */
+	free_irq(dev->irq,dev);
+	release_region(dev->base_addr, YAM_EXTENT);
+	netif_stop_queue(dev);
+	while ((skb = skb_dequeue(&yp->send_queue)))
+		dev_kfree_skb(skb);
+
+	printk(KERN_INFO "%s: close yam at iobase 0x%lx irq %u\n",
+		   yam_drvname, dev->base_addr, dev->irq);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int yam_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct yam_port *yp = netdev_priv(dev);
+	struct yamdrv_ioctl_cfg yi;
+	struct yamdrv_ioctl_mcs *ym;
+	int ioctl_cmd;
+
+	if (copy_from_user(&ioctl_cmd, ifr->ifr_data, sizeof(int)))
+		 return -EFAULT;
+
+	if (yp->magic != YAM_MAGIC)
+		return -EINVAL;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	if (cmd != SIOCDEVPRIVATE)
+		return -EINVAL;
+
+	switch (ioctl_cmd) {
+
+	case SIOCYAMRESERVED:
+		return -EINVAL;			/* unused */
+
+	case SIOCYAMSMCS:
+		if (netif_running(dev))
+			return -EINVAL;		/* Cannot change this parameter when up */
+		ym = memdup_user(ifr->ifr_data,
+				 sizeof(struct yamdrv_ioctl_mcs));
+		if (IS_ERR(ym))
+			return PTR_ERR(ym);
+		if (ym->cmd != SIOCYAMSMCS)
+			return -EINVAL;
+		if (ym->bitrate > YAM_MAXBITRATE) {
+			kfree(ym);
+			return -EINVAL;
+		}
+		/* setting predef as 0 for loading userdefined mcs data */
+		add_mcs(ym->bits, ym->bitrate, 0);
+		kfree(ym);
+		break;
+
+	case SIOCYAMSCFG:
+		if (!capable(CAP_SYS_RAWIO))
+			return -EPERM;
+		if (copy_from_user(&yi, ifr->ifr_data, sizeof(struct yamdrv_ioctl_cfg)))
+			 return -EFAULT;
+
+		if (yi.cmd != SIOCYAMSCFG)
+			return -EINVAL;
+		if ((yi.cfg.mask & YAM_IOBASE) && netif_running(dev))
+			return -EINVAL;		/* Cannot change this parameter when up */
+		if ((yi.cfg.mask & YAM_IRQ) && netif_running(dev))
+			return -EINVAL;		/* Cannot change this parameter when up */
+		if ((yi.cfg.mask & YAM_BITRATE) && netif_running(dev))
+			return -EINVAL;		/* Cannot change this parameter when up */
+		if ((yi.cfg.mask & YAM_BAUDRATE) && netif_running(dev))
+			return -EINVAL;		/* Cannot change this parameter when up */
+
+		if (yi.cfg.mask & YAM_IOBASE) {
+			yp->iobase = yi.cfg.iobase;
+			dev->base_addr = yi.cfg.iobase;
+		}
+		if (yi.cfg.mask & YAM_IRQ) {
+			if (yi.cfg.irq > 15)
+				return -EINVAL;
+			yp->irq = yi.cfg.irq;
+			dev->irq = yi.cfg.irq;
+		}
+		if (yi.cfg.mask & YAM_BITRATE) {
+			if (yi.cfg.bitrate > YAM_MAXBITRATE)
+				return -EINVAL;
+			yp->bitrate = yi.cfg.bitrate;
+		}
+		if (yi.cfg.mask & YAM_BAUDRATE) {
+			if (yi.cfg.baudrate > YAM_MAXBAUDRATE)
+				return -EINVAL;
+			yp->baudrate = yi.cfg.baudrate;
+		}
+		if (yi.cfg.mask & YAM_MODE) {
+			if (yi.cfg.mode > YAM_MAXMODE)
+				return -EINVAL;
+			yp->dupmode = yi.cfg.mode;
+		}
+		if (yi.cfg.mask & YAM_HOLDDLY) {
+			if (yi.cfg.holddly > YAM_MAXHOLDDLY)
+				return -EINVAL;
+			yp->holdd = yi.cfg.holddly;
+		}
+		if (yi.cfg.mask & YAM_TXDELAY) {
+			if (yi.cfg.txdelay > YAM_MAXTXDELAY)
+				return -EINVAL;
+			yp->txd = yi.cfg.txdelay;
+		}
+		if (yi.cfg.mask & YAM_TXTAIL) {
+			if (yi.cfg.txtail > YAM_MAXTXTAIL)
+				return -EINVAL;
+			yp->txtail = yi.cfg.txtail;
+		}
+		if (yi.cfg.mask & YAM_PERSIST) {
+			if (yi.cfg.persist > YAM_MAXPERSIST)
+				return -EINVAL;
+			yp->pers = yi.cfg.persist;
+		}
+		if (yi.cfg.mask & YAM_SLOTTIME) {
+			if (yi.cfg.slottime > YAM_MAXSLOTTIME)
+				return -EINVAL;
+			yp->slot = yi.cfg.slottime;
+			yp->slotcnt = yp->slot / 10;
+		}
+		break;
+
+	case SIOCYAMGCFG:
+		memset(&yi, 0, sizeof(yi));
+		yi.cfg.mask = 0xffffffff;
+		yi.cfg.iobase = yp->iobase;
+		yi.cfg.irq = yp->irq;
+		yi.cfg.bitrate = yp->bitrate;
+		yi.cfg.baudrate = yp->baudrate;
+		yi.cfg.mode = yp->dupmode;
+		yi.cfg.txdelay = yp->txd;
+		yi.cfg.holddly = yp->holdd;
+		yi.cfg.txtail = yp->txtail;
+		yi.cfg.persist = yp->pers;
+		yi.cfg.slottime = yp->slot;
+		if (copy_to_user(ifr->ifr_data, &yi, sizeof(struct yamdrv_ioctl_cfg)))
+			 return -EFAULT;
+		break;
+
+	default:
+		return -EINVAL;
+
+	}
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int yam_set_mac_address(struct net_device *dev, void *addr)
+{
+	struct sockaddr *sa = (struct sockaddr *) addr;
+
+	/* addr is an AX.25 shifted ASCII mac address */
+	memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static const struct net_device_ops yam_netdev_ops = {
+	.ndo_open	     = yam_open,
+	.ndo_stop	     = yam_close,
+	.ndo_start_xmit      = yam_send_packet,
+	.ndo_do_ioctl 	     = yam_ioctl,
+	.ndo_set_mac_address = yam_set_mac_address,
+};
+
+static void yam_setup(struct net_device *dev)
+{
+	struct yam_port *yp = netdev_priv(dev);
+
+	yp->magic = YAM_MAGIC;
+	yp->bitrate = DEFAULT_BITRATE;
+	yp->baudrate = DEFAULT_BITRATE * 2;
+	yp->iobase = 0;
+	yp->irq = 0;
+	yp->dupmode = 0;
+	yp->holdd = DEFAULT_HOLDD;
+	yp->txd = DEFAULT_TXD;
+	yp->txtail = DEFAULT_TXTAIL;
+	yp->slot = DEFAULT_SLOT;
+	yp->pers = DEFAULT_PERS;
+	yp->dev = dev;
+
+	dev->base_addr = yp->iobase;
+	dev->irq = yp->irq;
+
+	skb_queue_head_init(&yp->send_queue);
+
+	dev->netdev_ops = &yam_netdev_ops;
+	dev->header_ops = &ax25_header_ops;
+
+	dev->type = ARPHRD_AX25;
+	dev->hard_header_len = AX25_MAX_HEADER_LEN;
+	dev->mtu = AX25_MTU;
+	dev->addr_len = AX25_ADDR_LEN;
+	memcpy(dev->broadcast, &ax25_bcast, AX25_ADDR_LEN);
+	memcpy(dev->dev_addr, &ax25_defaddr, AX25_ADDR_LEN);
+}
+
+static int __init yam_init_driver(void)
+{
+	struct net_device *dev;
+	int i, err;
+	char name[IFNAMSIZ];
+
+	printk(yam_drvinfo);
+
+	for (i = 0; i < NR_PORTS; i++) {
+		sprintf(name, "yam%d", i);
+		
+		dev = alloc_netdev(sizeof(struct yam_port), name,
+				   NET_NAME_UNKNOWN, yam_setup);
+		if (!dev) {
+			pr_err("yam: cannot allocate net device\n");
+			err = -ENOMEM;
+			goto error;
+		}
+		
+		err = register_netdev(dev);
+		if (err) {
+			printk(KERN_WARNING "yam: cannot register net device %s\n", dev->name);
+			goto error;
+		}
+		yam_devs[i] = dev;
+
+	}
+
+	timer_setup(&yam_timer, yam_dotimer, 0);
+	yam_timer.expires = jiffies + HZ / 100;
+	add_timer(&yam_timer);
+
+	proc_create_seq("yam", 0444, init_net.proc_net, &yam_seqops);
+	return 0;
+ error:
+	while (--i >= 0) {
+		unregister_netdev(yam_devs[i]);
+		free_netdev(yam_devs[i]);
+	}
+	return err;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void __exit yam_cleanup_driver(void)
+{
+	struct yam_mcs *p;
+	int i;
+
+	del_timer_sync(&yam_timer);
+	for (i = 0; i < NR_PORTS; i++) {
+		struct net_device *dev = yam_devs[i];
+		if (dev) {
+			unregister_netdev(dev);
+			free_netdev(dev);
+		}
+	}
+
+	while (yam_data) {
+		p = yam_data;
+		yam_data = yam_data->next;
+		kfree(p);
+	}
+
+	remove_proc_entry("yam", init_net.proc_net);
+}
+
+/* --------------------------------------------------------------------- */
+
+MODULE_AUTHOR("Frederic Rible F1OAT frible@teaser.fr");
+MODULE_DESCRIPTION("Yam amateur radio modem driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FIRMWARE_1200);
+MODULE_FIRMWARE(FIRMWARE_9600);
+
+module_init(yam_init_driver);
+module_exit(yam_cleanup_driver);
+
+/* --------------------------------------------------------------------- */
+
diff --git a/drivers/net/hamradio/z8530.h b/drivers/net/hamradio/z8530.h
new file mode 100644
index 0000000..1655901
--- /dev/null
+++ b/drivers/net/hamradio/z8530.h
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* 8530 Serial Communications Controller Register definitions */
+#define	FLAG	0x7e
+
+/* Write Register 0 */
+#define	R0	0		/* Register selects */
+#define	R1	1
+#define	R2	2
+#define	R3	3
+#define	R4	4
+#define	R5	5
+#define	R6	6
+#define	R7	7
+#define	R8	8
+#define	R9	9
+#define	R10	10
+#define	R11	11
+#define	R12	12
+#define	R13	13
+#define	R14	14
+#define	R15	15
+
+#define	NULLCODE	0	/* Null Code */
+#define	POINT_HIGH	0x8	/* Select upper half of registers */
+#define	RES_EXT_INT	0x10	/* Reset Ext. Status Interrupts */
+#define	SEND_ABORT	0x18	/* HDLC Abort */
+#define	RES_RxINT_FC	0x20	/* Reset RxINT on First Character */
+#define	RES_Tx_P	0x28	/* Reset TxINT Pending */
+#define	ERR_RES		0x30	/* Error Reset */
+#define	RES_H_IUS	0x38	/* Reset highest IUS */
+
+#define	RES_Rx_CRC	0x40	/* Reset Rx CRC Checker */
+#define	RES_Tx_CRC	0x80	/* Reset Tx CRC Checker */
+#define	RES_EOM_L	0xC0	/* Reset EOM latch */
+
+/* Write Register 1 */
+
+#define	EXT_INT_ENAB	0x1	/* Ext Int Enable */
+#define	TxINT_ENAB	0x2	/* Tx Int Enable */
+#define	PAR_SPEC	0x4	/* Parity is special condition */
+
+#define	RxINT_DISAB	0	/* Rx Int Disable */
+#define	RxINT_FCERR	0x8	/* Rx Int on First Character Only or Error */
+#define	INT_ALL_Rx	0x10	/* Int on all Rx Characters or error */
+#define	INT_ERR_Rx	0x18	/* Int on error only */
+
+#define	WT_RDY_RT	0x20	/* Wait/Ready on R/T */
+#define	WT_FN_RDYFN	0x40	/* Wait/FN/Ready FN */
+#define	WT_RDY_ENAB	0x80	/* Wait/Ready Enable */
+
+/* Write Register #2 (Interrupt Vector) */
+
+/* Write Register 3 */
+
+#define	RxENABLE	0x1	/* Rx Enable */
+#define	SYNC_L_INH	0x2	/* Sync Character Load Inhibit */
+#define	ADD_SM		0x4	/* Address Search Mode (SDLC) */
+#define	RxCRC_ENAB	0x8	/* Rx CRC Enable */
+#define	ENT_HM		0x10	/* Enter Hunt Mode */
+#define	AUTO_ENAB	0x20	/* Auto Enables */
+#define	Rx5		0x0	/* Rx 5 Bits/Character */
+#define	Rx7		0x40	/* Rx 7 Bits/Character */
+#define	Rx6		0x80	/* Rx 6 Bits/Character */
+#define	Rx8		0xc0	/* Rx 8 Bits/Character */
+
+/* Write Register 4 */
+
+#define	PAR_ENA		0x1	/* Parity Enable */
+#define	PAR_EVEN	0x2	/* Parity Even/Odd* */
+
+#define	SYNC_ENAB	0	/* Sync Modes Enable */
+#define	SB1		0x4	/* 1 stop bit/char */
+#define	SB15		0x8	/* 1.5 stop bits/char */
+#define	SB2		0xc	/* 2 stop bits/char */
+
+#define	MONSYNC		0	/* 8 Bit Sync character */
+#define	BISYNC		0x10	/* 16 bit sync character */
+#define	SDLC		0x20	/* SDLC Mode (01111110 Sync Flag) */
+#define	EXTSYNC		0x30	/* External Sync Mode */
+
+#define	X1CLK		0x0	/* x1 clock mode */
+#define	X16CLK		0x40	/* x16 clock mode */
+#define	X32CLK		0x80	/* x32 clock mode */
+#define	X64CLK		0xC0	/* x64 clock mode */
+
+/* Write Register 5 */
+
+#define	TxCRC_ENAB	0x1	/* Tx CRC Enable */
+#define	RTS		0x2	/* RTS */
+#define	SDLC_CRC	0x4	/* SDLC/CRC-16 */
+#define	TxENAB		0x8	/* Tx Enable */
+#define	SND_BRK		0x10	/* Send Break */
+#define	Tx5		0x0	/* Tx 5 bits (or less)/character */
+#define	Tx7		0x20	/* Tx 7 bits/character */
+#define	Tx6		0x40	/* Tx 6 bits/character */
+#define	Tx8		0x60	/* Tx 8 bits/character */
+#define	DTR		0x80	/* DTR */
+
+/* Write Register 6 (Sync bits 0-7/SDLC Address Field) */
+
+/* Write Register 7 (Sync bits 8-15/SDLC 01111110) */
+
+/* Write Register 8 (transmit buffer) */
+
+/* Write Register 9 (Master interrupt control) */
+#define	VIS	1	/* Vector Includes Status */
+#define	NV	2	/* No Vector */
+#define	DLC	4	/* Disable Lower Chain */
+#define	MIE	8	/* Master Interrupt Enable */
+#define	STATHI	0x10	/* Status high */
+#define	NORESET	0	/* No reset on write to R9 */
+#define	CHRB	0x40	/* Reset channel B */
+#define	CHRA	0x80	/* Reset channel A */
+#define	FHWRES	0xc0	/* Force hardware reset */
+
+/* Write Register 10 (misc control bits) */
+#define	BIT6	1	/* 6 bit/8bit sync */
+#define	LOOPMODE 2	/* SDLC Loop mode */
+#define	ABUNDER	4	/* Abort/flag on SDLC xmit underrun */
+#define	MARKIDLE 8	/* Mark/flag on idle */
+#define	GAOP	0x10	/* Go active on poll */
+#define	NRZ	0	/* NRZ mode */
+#define	NRZI	0x20	/* NRZI mode */
+#define	FM1	0x40	/* FM1 (transition = 1) */
+#define	FM0	0x60	/* FM0 (transition = 0) */
+#define	CRCPS	0x80	/* CRC Preset I/O */
+
+/* Write Register 11 (Clock Mode control) */
+#define	TRxCXT	0	/* TRxC = Xtal output */
+#define	TRxCTC	1	/* TRxC = Transmit clock */
+#define	TRxCBR	2	/* TRxC = BR Generator Output */
+#define	TRxCDP	3	/* TRxC = DPLL output */
+#define	TRxCOI	4	/* TRxC O/I */
+#define	TCRTxCP	0	/* Transmit clock = RTxC pin */
+#define	TCTRxCP	8	/* Transmit clock = TRxC pin */
+#define	TCBR	0x10	/* Transmit clock = BR Generator output */
+#define	TCDPLL	0x18	/* Transmit clock = DPLL output */
+#define	RCRTxCP	0	/* Receive clock = RTxC pin */
+#define	RCTRxCP	0x20	/* Receive clock = TRxC pin */
+#define	RCBR	0x40	/* Receive clock = BR Generator output */
+#define	RCDPLL	0x60	/* Receive clock = DPLL output */
+#define	RTxCX	0x80	/* RTxC Xtal/No Xtal */
+
+/* Write Register 12 (lower byte of baud rate generator time constant) */
+
+/* Write Register 13 (upper byte of baud rate generator time constant) */
+
+/* Write Register 14 (Misc control bits) */
+#define	BRENABL	1	/* Baud rate generator enable */
+#define	BRSRC	2	/* Baud rate generator source */
+#define	DTRREQ	4	/* DTR/Request function */
+#define	AUTOECHO 8	/* Auto Echo */
+#define	LOOPBAK	0x10	/* Local loopback */
+#define	SEARCH	0x20	/* Enter search mode */
+#define	RMC	0x40	/* Reset missing clock */
+#define	DISDPLL	0x60	/* Disable DPLL */
+#define	SSBR	0x80	/* Set DPLL source = BR generator */
+#define	SSRTxC	0xa0	/* Set DPLL source = RTxC */
+#define	SFMM	0xc0	/* Set FM mode */
+#define	SNRZI	0xe0	/* Set NRZI mode */
+
+/* Write Register 15 (external/status interrupt control) */
+#define	ZCIE	2	/* Zero count IE */
+#define	DCDIE	8	/* DCD IE */
+#define	SYNCIE	0x10	/* Sync/hunt IE */
+#define	CTSIE	0x20	/* CTS IE */
+#define	TxUIE	0x40	/* Tx Underrun/EOM IE */
+#define	BRKIE	0x80	/* Break/Abort IE */
+
+
+/* Read Register 0 */
+#define	Rx_CH_AV	0x1	/* Rx Character Available */
+#define	ZCOUNT		0x2	/* Zero count */
+#define	Tx_BUF_EMP	0x4	/* Tx Buffer empty */
+#define	DCD		0x8	/* DCD */
+#define	SYNC_HUNT	0x10	/* Sync/hunt */
+#define	CTS		0x20	/* CTS */
+#define	TxEOM		0x40	/* Tx underrun */
+#define	BRK_ABRT	0x80	/* Break/Abort */
+
+/* Read Register 1 */
+#define	ALL_SNT		0x1	/* All sent */
+/* Residue Data for 8 Rx bits/char programmed */
+#define	RES3		0x8	/* 0/3 */
+#define	RES4		0x4	/* 0/4 */
+#define	RES5		0xc	/* 0/5 */
+#define	RES6		0x2	/* 0/6 */
+#define	RES7		0xa	/* 0/7 */
+#define	RES8		0x6	/* 0/8 */
+#define	RES18		0xe	/* 1/8 */
+#define	RES28		0x0	/* 2/8 */
+/* Special Rx Condition Interrupts */
+#define	PAR_ERR		0x10	/* Parity error */
+#define	Rx_OVR		0x20	/* Rx Overrun Error */
+#define	CRC_ERR		0x40	/* CRC/Framing Error */
+#define	END_FR		0x80	/* End of Frame (SDLC) */
+
+/* Read Register 2 (channel b only) - Interrupt vector */
+
+/* Read Register 3 (interrupt pending register) ch a only */
+#define	CHBEXT	0x1		/* Channel B Ext/Stat IP */
+#define	CHBTxIP	0x2		/* Channel B Tx IP */
+#define	CHBRxIP	0x4		/* Channel B Rx IP */
+#define	CHAEXT	0x8		/* Channel A Ext/Stat IP */
+#define	CHATxIP	0x10		/* Channel A Tx IP */
+#define	CHARxIP	0x20		/* Channel A Rx IP */
+
+/* Read Register 8 (receive data register) */
+
+/* Read Register 10  (misc status bits) */
+#define	ONLOOP	2		/* On loop */
+#define	LOOPSEND 0x10		/* Loop sending */
+#define	CLK2MIS	0x40		/* Two clocks missing */
+#define	CLK1MIS	0x80		/* One clock missing */
+
+/* Read Register 12 (lower byte of baud rate generator constant) */
+
+/* Read Register 13 (upper byte of baud rate generator constant) */
+
+/* Read Register 15 (value of WR 15) */
+
+/* Z85C30/Z85230 Enhanced SCC register definitions */
+
+/* Write Register 7' (SDLC/HDLC Programmable Enhancements) */
+#define AUTOTXF	0x01		/* Auto Tx Flag */
+#define AUTOEOM 0x02		/* Auto EOM Latch Reset */
+#define AUTORTS	0x04		/* Auto RTS */
+#define TXDNRZI 0x08		/* TxD Pulled High in SDLC NRZI mode */
+#define RXFIFOH 0x08		/* Z85230: Int on RX FIFO half full */
+#define FASTDTR 0x10		/* Fast DTR/REQ Mode */
+#define CRCCBCR	0x20		/* CRC Check Bytes Completely Received */
+#define TXFIFOE 0x20		/* Z85230: Int on TX FIFO completely empty */
+#define EXTRDEN	0x40		/* Extended Read Enabled */
+
+/* Write Register 15 (external/status interrupt control) */
+#define SHDLCE	1		/* SDLC/HDLC Enhancements Enable */
+#define FIFOE	4		/* FIFO Enable */
+
+/* Read Register 6 (frame status FIFO) */
+#define BCLSB	0xff		/* LSB of 14 bits count */
+
+/* Read Register 7 (frame status FIFO) */
+#define BCMSB	0x3f		/* MSB of 14 bits count */
+#define FDA	0x40		/* FIFO Data Available Status */
+#define FOS	0x80		/* FIFO Overflow Status */