summaryrefslogtreecommitdiffstats
path: root/drivers/net/hamradio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/hamradio')
-rw-r--r--drivers/net/hamradio/6pack.c1005
-rw-r--r--drivers/net/hamradio/Kconfig193
-rw-r--r--drivers/net/hamradio/Makefile23
-rw-r--r--drivers/net/hamradio/baycom_epp.c1338
-rw-r--r--drivers/net/hamradio/baycom_par.c615
-rw-r--r--drivers/net/hamradio/baycom_ser_fdx.c718
-rw-r--r--drivers/net/hamradio/baycom_ser_hdx.c743
-rw-r--r--drivers/net/hamradio/bpqether.c613
-rw-r--r--drivers/net/hamradio/dmascc.c1465
-rw-r--r--drivers/net/hamradio/hdlcdrv.c780
-rw-r--r--drivers/net/hamradio/mkiss.c1017
-rw-r--r--drivers/net/hamradio/scc.c2173
-rw-r--r--drivers/net/hamradio/yam.c1207
-rw-r--r--drivers/net/hamradio/z8530.h246
14 files changed, 12136 insertions, 0 deletions
diff --git a/drivers/net/hamradio/6pack.c b/drivers/net/hamradio/6pack.c
new file mode 100644
index 000000000..87094189a
--- /dev/null
+++ b/drivers/net/hamradio/6pack.c
@@ -0,0 +1,1005 @@
+/*
+ * 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 25 /* 250 ms */
+#define SIXP_PERSIST 50 /* in 256ths */
+#define SIXP_SLOTTIME 10 /* 100 ms */
+#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->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 */
+ mod_timer(&sp->resync_t, jiffies + SIXP_RESYNC_TIMEOUT);
+}
+
+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);
+
+ mod_timer(&sp->resync_t, jiffies + SIXP_RESYNC_TIMEOUT);
+
+ 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_irq(&disc_data_lock);
+ sp = tty->disc_data;
+ tty->disc_data = NULL;
+ write_unlock_irq(&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);
+
+ unregister_netdev(sp->dev);
+
+ del_timer_sync(&sp->tx_t);
+ del_timer_sync(&sp->resync_t);
+
+ /* Free all 6pack frame buffers after unreg. */
+ kfree(sp->rbuff);
+ kfree(sp->xbuff);
+
+ free_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;
+ }
+
+ if (sp->rx_count_cooked + 2 >= sizeof(sp->cooked_buf)) {
+ pr_err("6pack: cooked buffer overrun, data loss\n");
+ sp->rx_count = 0;
+ 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)
+ mod_timer(&sp->resync_t, jiffies + SIXP_INIT_RESYNC_TIMEOUT);
+
+ 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 000000000..bf5e59687
--- /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 000000000..7a1518d76
--- /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 000000000..1e62d0073
--- /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 000000000..1f7ceafd6
--- /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 000000000..190f66c88
--- /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 000000000..3c823c648
--- /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 000000000..777fa59f5
--- /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 000000000..cde41200f
--- /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 000000000..97e3bc60c
--- /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 000000000..29a83fb76
--- /dev/null
+++ b/drivers/net/hamradio/mkiss.c
@@ -0,0 +1,1017 @@
+/*
+ * 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
+
+/* some arch define END as assembly function ending, just undef it */
+#undef END
+/* 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_irq(&disc_data_lock);
+ ax = tty->disc_data;
+ tty->disc_data = NULL;
+ write_unlock_irq(&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);
+
+ unregister_netdev(ax->dev);
+
+ /* Free all AX25 frame buffers after unreg. */
+ kfree(ax->rbuff);
+ kfree(ax->xbuff);
+
+ ax->tty = NULL;
+
+ free_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 000000000..6c03932d8
--- /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 000000000..3db86f247
--- /dev/null
+++ b/drivers/net/hamradio/yam.c
@@ -0,0 +1,1207 @@
+/*****************************************************************************/
+
+/*
+ * 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 || 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);
+ free_netdev(dev);
+ 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 000000000..1655901d7
--- /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 */