diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-12-16 06:32:09 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1997-12-16 06:32:09 +0000 |
commit | 1d68c48efe2d331857dfd4dfa4104fb366c100c5 (patch) | |
tree | e6c894ae6b00193b25948fa58cf596ee633053d7 /drivers/net | |
parent | aa944aa3453e47706685bc562711a9e87375941e (diff) |
Merge with Linux 2.1.72, part 3.
Diffstat (limited to 'drivers/net')
27 files changed, 17189 insertions, 0 deletions
diff --git a/drivers/net/hamradio/.cvsignore b/drivers/net/hamradio/.cvsignore new file mode 100644 index 000000000..4671378ae --- /dev/null +++ b/drivers/net/hamradio/.cvsignore @@ -0,0 +1 @@ +.depend diff --git a/drivers/net/hamradio/Config.in b/drivers/net/hamradio/Config.in new file mode 100644 index 000000000..94a86238f --- /dev/null +++ b/drivers/net/hamradio/Config.in @@ -0,0 +1,60 @@ +# +# Amateur Radio protocols and AX.25 device configuration +# +# 19971130 Now in an own category to make correct compilation of the +# AX.25 stuff easier... +# Joerg Reuter DL1BKE <jreuter@poboxes.com> + +mainmenu_option next_comment +comment 'Amateur Radio support' +bool 'Amateur Radio support' CONFIG_HAMRADIO + +if [ "$CONFIG_HAMRADIO" != "n" ] ; then + if [ "$CONFIG_NET" != "n" ] ; then + comment 'Packet Radio protocols' + tristate 'Amateur Radio AX.25 Level 2 protocol' CONFIG_AX25 + if [ "$CONFIG_AX25" != "n" ]; then + bool ' AX.25 DAMA Slave support' CONFIG_AX25_DAMA_SLAVE +# bool ' AX.25 DAMA Master support' CONFIG_AX25_DAMA_MASTER + dep_tristate ' Amateur Radio NET/ROM protocol' CONFIG_NETROM $CONFIG_AX25 + dep_tristate ' Amateur Radio X.25 PLP (Rose)' CONFIG_ROSE $CONFIG_AX25 + fi + + if [ "$CONFIG_AX25" != "n" ]; then + comment 'AX.25 network device drivers' + tristate 'Serial port KISS driver' CONFIG_MKISS +# tristate 'Serial port 6PACK driver' CONFIG_6PACK + tristate 'BPQ Ethernet driver' CONFIG_BPQETHER + + tristate 'Z8530 SCC driver' CONFIG_SCC + if [ "$CONFIG_SCC" != "n" ]; then + bool ' additional delay for PA0HZP OptoSCC compatible boards' CONFIG_SCC_DELAY + bool ' support for TRX that feedback the tx signal to rx' CONFIG_SCC_TRXECHO + fi + + tristate 'BAYCOM ser12 fullduplex driver for AX.25' CONFIG_BAYCOM_SER_FDX + tristate 'BAYCOM ser12 halfduplex driver for AX.25' CONFIG_BAYCOM_SER_HDX + tristate 'BAYCOM picpar and par96 driver for AX.25' CONFIG_BAYCOM_PAR + + tristate 'Soundcard modem driver' CONFIG_SOUNDMODEM + if [ "$CONFIG_SOUNDMODEM" != "n" ]; then + bool ' soundmodem support for Soundblaster and compatible cards' CONFIG_SOUNDMODEM_SBC + bool ' soundmodem support for WSS and Crystal cards' CONFIG_SOUNDMODEM_WSS + bool ' soundmodem support for 1200 baud AFSK modulation' CONFIG_SOUNDMODEM_AFSK1200 + bool ' soundmodem support for 2400 baud AFSK modulation (7.3728MHz crystal)' CONFIG_SOUNDMODEM_AFSK2400_7 + bool ' soundmodem support for 2400 baud AFSK modulation (8MHz crystal)' CONFIG_SOUNDMODEM_AFSK2400_8 + bool ' soundmodem support for 4800 baud HAPN-1 modulation' CONFIG_SOUNDMODEM_HAPN4800 + bool ' soundmodem support for 9600 baud FSK G3RUH modulation' CONFIG_SOUNDMODEM_FSK9600 + fi + fi + fi + + comment 'Misc. hamradio protocols' + tristate 'Shortwave radio modem driver' CONFIG_HFMODEM + if [ "$CONFIG_HFMODEM" != "n" ]; then + bool ' HFmodem support for Soundblaster and compatible cards' CONFIG_HFMODEM_SBC + bool ' HFmodem support for WSS and Crystal cards' CONFIG_HFMODEM_WSS + fi +fi + +endmenu diff --git a/drivers/net/hamradio/Makefile b/drivers/net/hamradio/Makefile new file mode 100644 index 000000000..349371dd0 --- /dev/null +++ b/drivers/net/hamradio/Makefile @@ -0,0 +1,120 @@ +# File: drivers/hamradio/Makefile +# +# 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 maintainance. +# Joerg Reuter DL1BKE <jreuter@poboxes.com> + + +SUB_DIRS := +MOD_SUB_DIRS := $(SUB_DIRS) +ALL_SUB_DIRS := $(SUB_DIRS) + +L_TARGET := hamradio.a +L_OBJS := +M_OBJS := +MOD_LIST_NAME := HAM_MODULES + +# Need these to keep track of whether the hdlc module should +# really go in the kernel or a module. +CONFIG_HDLCDRV_BUILTIN := +CONFIG_HDLCDRV_MODULE := + +ifeq ($(CONFIG_SCC),y) +L_OBJS += scc.o +else + ifeq ($(CONFIG_SCC),m) + M_OBJS += scc.o + endif +endif + +ifeq ($(CONFIG_MKISS),y) +L_OBJS += mkiss.o +else + ifeq ($(CONFIG_MKISS),m) + M_OBJS += mkiss.o + endif +endif + +ifeq ($(CONFIG_PI),y) +L_OBJS += pi2.o +else + ifeq ($(CONFIG_PI),m) + M_OBJS += pi2.o + endif +endif + +ifeq ($(CONFIG_PT),y) +L_OBJS += pt.o +else + ifeq ($(CONFIG_PT),m) + M_OBJS += pt.o + endif +endif + +ifeq ($(CONFIG_BPQETHER),y) +L_OBJS += bpqether.o +else + ifeq ($(CONFIG_BPQETHER),m) + M_OBJS += bpqether.o + endif +endif + +ifeq ($(CONFIG_BAYCOM_SER_FDX),y) +L_OBJS += baycom_ser_fdx.o +CONFIG_HDLCDRV_BUILTIN = y +else + ifeq ($(CONFIG_BAYCOM_SER_FDX),m) + CONFIG_HDLCDRV_MODULE = y + M_OBJS += baycom_ser_fdx.o + endif +endif + +ifeq ($(CONFIG_BAYCOM_SER_HDX),y) +L_OBJS += baycom_ser_hdx.o +CONFIG_HDLCDRV_BUILTIN = y +else + ifeq ($(CONFIG_BAYCOM_SER_HDX),m) + CONFIG_HDLCDRV_MODULE = y + M_OBJS += baycom_ser_hdx.o + endif +endif + +ifeq ($(CONFIG_BAYCOM_PAR),y) +L_OBJS += baycom_par.o +CONFIG_HDLCDRV_BUILTIN = y +else + ifeq ($(CONFIG_BAYCOM_PAR),m) + CONFIG_HDLCDRV_MODULE = y + M_OBJS += baycom_par.o + endif +endif + +ifeq ($(CONFIG_SOUNDMODEM),y) +ALL_SUB_DIRS += soundmodem +SUB_DIRS += soundmodem +L_OBJS += soundmodem/soundmodem.o +CONFIG_HDLCDRV_BUILTIN = y +else + ifeq ($(CONFIG_SOUNDMODEM),m) + CONFIG_HDLCDRV_MODULE = y + ALL_SUB_DIRS += soundmodem + MOD_SUB_DIRS += soundmodem + endif +endif + +# If anything built-in uses the hdlcdrv, then build it into the kernel also. +# If not, but a module uses it, build as a module. +ifdef CONFIG_HDLCDRV_BUILTIN +LX_OBJS += hdlcdrv.o +else + ifdef CONFIG_HDLCDRV_MODULE + MX_OBJS += hdlcdrv.o + endif +endif + +include $(TOPDIR)/Rules.make + +clean: + rm -f core *.o *.a *.s diff --git a/drivers/net/hamradio/baycom_par.c b/drivers/net/hamradio/baycom_par.c new file mode 100644 index 000000000..0b470e2f3 --- /dev/null +++ b/drivers/net/hamradio/baycom_par.c @@ -0,0 +1,661 @@ +/*****************************************************************************/ + +/* + * baycom_par.c -- baycom par96 and picpar radio modem driver. + * + * Copyright (C) 1997 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.96 Adapted from baycom.c and made network driver interface + * 18.10.96 Changed to new user space access routines (copy_{to,from}_user) + * 0.3 26.04.97 init code/data tagged + * 0.4 08.07.97 alternative ser12 decoding algorithm (uses delta CTS ints) + * 0.5 11.11.97 split into separate files for ser12/par96 + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/hdlcdrv.h> +#include <linux/baycom.h> +#include <linux/parport.h> + +/* --------------------------------------------------------------------- */ + +/* + * currently this module is supposed to support both module styles, i.e. + * the old one present up to about 2.1.9, and the new one functioning + * starting with 2.1.21. The reason is I have a kit allowing to compile + * this module also under 2.0.x which was requested by several people. + * This will go in 2.2 + */ +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= 0x20100 +#include <asm/uaccess.h> +#else +#include <asm/segment.h> +#include <linux/mm.h> + +#undef put_user +#undef get_user + +#define put_user(x,ptr) ({ __put_user((unsigned long)(x),(ptr),sizeof(*(ptr))); 0; }) +#define get_user(x,ptr) ({ x = ((__typeof__(*(ptr)))__get_user((ptr),sizeof(*(ptr)))); 0; }) + +extern __inline__ int copy_from_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_READ, from, n); + if (i) + return i; + memcpy_fromfs(to, from, n); + return 0; +} + +extern __inline__ int copy_to_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_WRITE, to, n); + if (i) + return i; + memcpy_tofs(to, from, n); + return 0; +} +#endif + +#if LINUX_VERSION_CODE >= 0x20123 +#include <linux/init.h> +#else +#define __init +#define __initdata +#define __initfunc(x) x +#endif + +/* --------------------------------------------------------------------- */ + +#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) 1997 Thomas Sailer, HB9JNX/AE4WA\n" +KERN_INFO "baycom_par: version 0.5 compiled " __TIME__ " " __DATE__ "\n"; + +/* --------------------------------------------------------------------- */ + +#define NR_PORTS 4 + +static struct device baycom_device[NR_PORTS]; + +static struct { + const char *mode; + int iobase; +} baycom_ports[NR_PORTS] = { { NULL, 0 }, }; + +/* --------------------------------------------------------------------- */ + +#define SER12_EXTENT 8 + +#define LPT_DATA(dev) ((dev)->base_addr+0) +#define LPT_STATUS(dev) ((dev)->base_addr+1) +#define LPT_CONTROL(dev) ((dev)->base_addr+2) +#define LPT_IRQ_ENABLE 0x10 + +#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 */ +}; + +/* --------------------------------------------------------------------- */ + +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +/* --------------------------------------------------------------------- */ + +static void __inline__ 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 ((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 device *dev, struct baycom_state *bc) +{ + int i; + unsigned int data = hdlcdrv_getbits(&bc->hdrv); + + 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; + outb(val, LPT_DATA(dev)); + outb(val | PAR96_BURST, LPT_DATA(dev)); + } +} + +/* --------------------------------------------------------------------- */ + +static __inline__ void par96_rx(struct device *dev, struct baycom_state *bc) +{ + int i; + unsigned int data, mask, mask2, descx; + + /* + * 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 (inb(LPT_STATUS(dev)) & 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 */ + outb(PAR97_POWER | PAR96_PTT, LPT_DATA(dev)); + descx ^= ((descx >> PAR96_DESCRAM_TAPSH1) ^ + (descx >> PAR96_DESCRAM_TAPSH2)); + data >>= 1; + if (!(descx & 1)) + data |= 0x8000; + outb(PAR97_POWER | PAR96_PTT | PAR96_BURST, LPT_DATA(dev)); + } + 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, !!(inb(LPT_STATUS(dev)) & PAR96_DCD)); + } +} + +/* --------------------------------------------------------------------- */ + +static void par96_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct parport *pp = (struct parport *)dev_id; + struct pardevice *pd = pp->cad; + struct device *dev = (struct device *)pd->private; + struct baycom_state *bc = (struct baycom_state *)dev->priv; + + if (!dev || !bc || bc->hdrv.magic != HDLCDRV_MAGIC) + return; + + 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; + sti(); + hdlcdrv_arbitrate(dev, &bc->hdrv); + } + } + sti(); + hdlcdrv_transmitter(dev, &bc->hdrv); + hdlcdrv_receiver(dev, &bc->hdrv); +} + +/* --------------------------------------------------------------------- */ + +static int par96_preempt(void *handle) +{ + /* we cannot relinquish the port in the middle of an operation */ + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void par96_wakeup(void *handle) +{ + struct device *dev = (struct device *)handle; + struct baycom_state *bc = (struct baycom_state *)dev->priv; + + 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 device *dev) +{ + struct baycom_state *bc = (struct baycom_state *)dev->priv; + struct parport *pp = parport_enumerate(); + + if (!dev || !bc) + return -ENXIO; + while (pp && pp->base != dev->base_addr) + pp = pp->next; + 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%x has no irq\n", pp->base); + return -ENXIO; + } + memset(&bc->modem, 0, sizeof(bc->modem)); + bc->hdrv.par.bitrate = 9600; + if (!(bc->pdev = parport_register_device(pp, dev->name, par96_preempt, par96_wakeup, + par96_interrupt, PARPORT_DEV_LURK, dev))) { + printk(KERN_ERR "baycom_par: cannot register parport at 0x%x\n", pp->base); + return -ENXIO; + } + if (parport_claim(bc->pdev)) { + printk(KERN_ERR "baycom_par: parport at 0x%x busy\n", pp->base); + parport_unregister_device(bc->pdev); + return -EBUSY; + } + dev->irq = pp->irq; + /* bc->pdev->port->ops->change_mode(bc->pdev->port, PARPORT_MODE_PCSPP); not yet implemented */ + /* switch off PTT */ + outb(PAR96_PTT | PAR97_POWER, LPT_DATA(dev)); + /*bc->pdev->port->ops->enable_irq(bc->pdev->port); not yet implemented */ + outb(LPT_IRQ_ENABLE, LPT_CONTROL(dev)); + printk(KERN_INFO "%s: par96 at iobase 0x%lx irq %u options 0x%x\n", + bc_drvname, dev->base_addr, dev->irq, bc->options); + MOD_INC_USE_COUNT; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int par96_close(struct device *dev) +{ + struct baycom_state *bc = (struct baycom_state *)dev->priv; + + if (!dev || !bc) + return -EINVAL; + /* disable interrupt */ + outb(0, LPT_CONTROL(dev)); + /*bc->pdev->port->ops->disable_irq(bc->pdev->port); not yet implemented */ + /* switch off PTT */ + outb(PAR96_PTT | PAR97_POWER, LPT_DATA(dev)); + 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); + MOD_DEC_USE_COUNT; + return 0; +} + +/* --------------------------------------------------------------------- */ +/* + * ===================== hdlcdrv driver interface ========================= + */ + +static int baycom_ioctl(struct device *dev, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd); + +/* --------------------------------------------------------------------- */ + +static struct hdlcdrv_ops par96_ops = { + bc_drvname, + bc_drvinfo, + par96_open, + par96_close, + 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 device *dev, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd) +{ + struct baycom_state *bc; + struct baycom_ioctl bi; + int cmd2; + + if (!dev || !dev->priv || + ((struct baycom_state *)dev->priv)->hdrv.magic != HDLCDRV_MAGIC) { + printk(KERN_ERR "bc_ioctl: invalid device struct\n"); + return -EINVAL; + } + bc = (struct baycom_state *)dev->priv; + + if (cmd != SIOCDEVPRIVATE) + return -ENOIOCTLCMD; + if (get_user(cmd2, (int *)ifr->ifr_data)) + return -EFAULT; + 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 (!suser() || dev->start) + 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; + +} + +/* --------------------------------------------------------------------- */ + +__initfunc(int baycom_par_init(void)) +{ + int i, j, found = 0; + char set_hw = 1; + struct baycom_state *bc; + char ifname[HDLCDRV_IFNAMELEN]; + + + printk(bc_drvinfo); + /* + * register net devices + */ + for (i = 0; i < NR_PORTS; i++) { + struct device *dev = baycom_device+i; + sprintf(ifname, "bcp%d", i); + + if (!baycom_ports[i].mode) + set_hw = 0; + if (!set_hw) + baycom_ports[i].iobase = 0; + j = hdlcdrv_register_hdlcdrv(dev, &par96_ops, + sizeof(struct baycom_state), + ifname, baycom_ports[i].iobase, 0, 0); + if (!j) { + bc = (struct baycom_state *)dev->priv; + if (set_hw && baycom_setmode(bc, baycom_ports[i].mode)) + set_hw = 0; + found++; + } else { + printk(KERN_WARNING "%s: cannot register net device\n", + bc_drvname); + } + } + if (!found) + return -ENXIO; + return 0; +} + +/* --------------------------------------------------------------------- */ + +#ifdef MODULE + +/* + * command line settable parameters + */ +static const char *mode[NR_PORTS] = { "picpar", }; +static int iobase[NR_PORTS] = { 0x378, }; + +#if LINUX_VERSION_CODE >= 0x20115 + +MODULE_PARM(mode, "1-" __MODULE_STRING(NR_PORTS) "s"); +MODULE_PARM_DESC(mode, "baycom operating mode; eg. par96 or picpar"); +MODULE_PARM(iobase, "1-" __MODULE_STRING(NR_PORTS) "i"); +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"); + +#endif + +__initfunc(int init_module(void)) +{ + int i; + + for (i = 0; (i < NR_PORTS) && (mode[i]); i++) { + baycom_ports[i].mode = mode[i]; + baycom_ports[i].iobase = iobase[i]; + } + if (i < NR_PORTS-1) + baycom_ports[i+1].mode = NULL; + return baycom_par_init(); +} + +/* --------------------------------------------------------------------- */ + +void cleanup_module(void) +{ + int i; + + for(i = 0; i < NR_PORTS; i++) { + struct device *dev = baycom_device+i; + struct baycom_state *bc = (struct baycom_state *)dev->priv; + + if (bc) { + if (bc->hdrv.magic != HDLCDRV_MAGIC) + printk(KERN_ERR "baycom: invalid magic in " + "cleanup_module\n"); + else + hdlcdrv_unregister_hdlcdrv(dev); + } + } +} + +#else /* MODULE */ +/* --------------------------------------------------------------------- */ +/* + * format: baycom_par=io,mode + * mode: par96,picpar + */ + +__initfunc(void baycom_par_setup(char *str, int *ints)) +{ + int i; + + for (i = 0; (i < NR_PORTS) && (baycom_ports[i].mode); i++); + if ((i >= NR_PORTS) || (ints[0] < 1)) { + printk(KERN_INFO "%s: too many or invalid interface " + "specifications\n", bc_drvname); + return; + } + baycom_ports[i].mode = str; + baycom_ports[i].iobase = ints[1]; + if (i < NR_PORTS-1) + baycom_ports[i+1].mode = NULL; +} + +#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..9005356d3 --- /dev/null +++ b/drivers/net/hamradio/baycom_ser_fdx.c @@ -0,0 +1,762 @@ +/*****************************************************************************/ + +/* + * baycom_ser_fdx.c -- baycom ser12 fullduplex radio modem driver. + * + * Copyright (C) 1997 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 * enables software 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.96 Adapted from baycom.c and made network driver interface + * 18.10.96 Changed to new user space access routines (copy_{to,from}_user) + * 0.3 26.04.97 init code/data tagged + * 0.4 08.07.97 alternative ser12 decoding algorithm (uses delta CTS ints) + * 0.5 11.11.97 ser12/par96 split into separate files + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/hdlcdrv.h> +#include <linux/baycom.h> + +/* --------------------------------------------------------------------- */ + +/* + * currently this module is supposed to support both module styles, i.e. + * the old one present up to about 2.1.9, and the new one functioning + * starting with 2.1.21. The reason is I have a kit allowing to compile + * this module also under 2.0.x which was requested by several people. + * This will go in 2.2 + */ +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= 0x20100 +#include <asm/uaccess.h> +#else +#include <asm/segment.h> +#include <linux/mm.h> + +#undef put_user +#undef get_user + +#define put_user(x,ptr) ({ __put_user((unsigned long)(x),(ptr),sizeof(*(ptr))); 0; }) +#define get_user(x,ptr) ({ x = ((__typeof__(*(ptr)))__get_user((ptr),sizeof(*(ptr)))); 0; }) + +extern inline int copy_from_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_READ, from, n); + if (i) + return i; + memcpy_fromfs(to, from, n); + return 0; +} + +extern inline int copy_to_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_WRITE, to, n); + if (i) + return i; + memcpy_tofs(to, from, n); + return 0; +} +#endif + +#if LINUX_VERSION_CODE >= 0x20123 +#include <linux/init.h> +#else +#define __init +#define __initdata +#define __initfunc(x) x +#endif + +/* --------------------------------------------------------------------- */ + +#define BAYCOM_DEBUG + +/* + * modem options; bit mask + */ +#define BAYCOM_OPTIONS_SOFTDCD 1 + +/* --------------------------------------------------------------------- */ + +static const char bc_drvname[] = "baycom_ser_fdx"; +static const char bc_drvinfo[] = KERN_INFO "baycom_ser_fdx: (C) 1997 Thomas Sailer, HB9JNX/AE4WA\n" +KERN_INFO "baycom_ser_fdx: version 0.5 compiled " __TIME__ " " __DATE__ "\n"; + +/* --------------------------------------------------------------------- */ + +#define NR_PORTS 4 + +static struct device baycom_device[NR_PORTS]; + +static struct { + char *mode; + int iobase, irq, baud; +} baycom_ports[NR_PORTS] = { { NULL, 0, 0 }, }; + +/* --------------------------------------------------------------------- */ + +#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_us8, baud_arbdiv; + unsigned int options; + + 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 long last_jiffies; + 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 */ +}; + +/* --------------------------------------------------------------------- */ + +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +/* --------------------------------------------------------------------- */ + +static void inline 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 ((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 ========================= + */ + +/* --------------------------------------------------------------------- */ + +extern inline unsigned int hweight16(unsigned short w) + __attribute__ ((unused)); +extern inline unsigned int hweight8(unsigned char w) + __attribute__ ((unused)); + +extern inline unsigned int hweight16(unsigned short w) +{ + unsigned short res = (w & 0x5555) + ((w >> 1) & 0x5555); + res = (res & 0x3333) + ((res >> 2) & 0x3333); + res = (res & 0x0F0F) + ((res >> 4) & 0x0F0F); + return (res & 0x00FF) + ((res >> 8) & 0x00FF); +} + +extern inline unsigned int hweight8(unsigned char w) +{ + unsigned short res = (w & 0x55) + ((w >> 1) & 0x55); + res = (res & 0x33) + ((res >> 2) & 0x33); + return (res & 0x0F) + ((res >> 4) & 0x0F); +} + +/* --------------------------------------------------------------------- */ + +static __inline__ void ser12_rxsample(struct device *dev, struct baycom_state *bc, unsigned char news) +{ + bc->modem.ser12.dcd_shreg <<= 1; + bc->modem.ser12.bit_pll += 0x2000; + if (bc->modem.ser12.last_sample != news) { + bc->modem.ser12.last_sample = news; + bc->modem.ser12.dcd_shreg |= 1; + if (bc->modem.ser12.bit_pll < 0x9000) + bc->modem.ser12.bit_pll += 0x1000; + else + bc->modem.ser12.bit_pll -= 0x1000; + bc->modem.ser12.dcd_sum0 += 4 * hweight8(bc->modem.ser12.dcd_shreg & 0x38) + - hweight16(bc->modem.ser12.dcd_shreg & 0x7c0); + } + hdlcdrv_channelbit(&bc->hdrv, !!bc->modem.ser12.last_sample); + if ((--bc->modem.ser12.dcd_time) <= 0) { + 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.bit_pll >= 0x10000) { + bc->modem.ser12.bit_pll &= 0xffff; + bc->modem.shreg >>= 1; + if (bc->modem.ser12.last_rxbit == bc->modem.ser12.last_sample) + bc->modem.shreg |= 0x10000; + bc->modem.ser12.last_rxbit = bc->modem.ser12.last_sample; + if (bc->modem.shreg & 1) { + hdlcdrv_putbits(&bc->hdrv, bc->modem.shreg >> 1); + bc->modem.shreg = 0x10000; + } + } +} + +/* --------------------------------------------------------------------- */ + +static __inline__ void ser12_rx(struct device *dev, struct baycom_state *bc, unsigned char curs) +{ + unsigned long curjiff; + struct timeval tv; + unsigned int timediff; + + /* + * get current time + */ + curjiff = jiffies; + do_gettimeofday(&tv); + if ((signed)(curjiff - bc->modem.ser12.last_jiffies) >= HZ/4) { + /* long inactivity; clear HDLC and DCD */ + bc->modem.ser12.dcd_sum1 = 0; + bc->modem.ser12.dcd_sum2 = 0; + bc->modem.ser12.dcd_sum0 = 2; + bc->modem.ser12.dcd_time = 120; + hdlcdrv_setdcd(&bc->hdrv, 0); + hdlcdrv_putbits(&bc->hdrv, 0xffff); + bc->modem.ser12.last_jiffies = curjiff; + bc->modem.ser12.pll_time = tv.tv_usec; + } + bc->modem.ser12.last_jiffies = curjiff; + timediff = tv.tv_usec + 1000000 - bc->modem.ser12.pll_time; + timediff %= 1000000; + timediff /= bc->baud_us8; + bc->modem.ser12.pll_time = (bc->modem.ser12.pll_time + timediff * (bc->baud_us8)) % 1000000; + for (; timediff > 1; timediff--) + ser12_rxsample(dev, bc, bc->modem.ser12.last_sample); + if (timediff >= 1) + ser12_rxsample(dev, bc, curs); +} + +/* --------------------------------------------------------------------- */ + +static void ser12_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_id; + struct baycom_state *bc = (struct baycom_state *)dev->priv; + unsigned char iir, msr = 0; + unsigned int txcount = 0; + unsigned int rxcount = 0; + + if (!dev || !bc || bc->hdrv.magic != HDLCDRV_MAGIC) + return; + + for (;;) { + iir = inb(IIR(dev->base_addr)); + if (iir & 1) + break; + switch (iir & 6) { + case 6: + inb(LSR(dev->base_addr)); + continue; + + case 4: + inb(RBR(dev->base_addr)); + continue; + + 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)); + bc->modem.arb_divider--; + baycom_int_freq(bc); + if (hdlcdrv_ptt(&bc->hdrv)) { + /* + * 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)); + txcount++; + } else + outb(0x0d, MCR(dev->base_addr)); /* transmitter off */ + continue; + + default: + msr = inb(MSR(dev->base_addr)); + if (msr & 1) /* delta CTS interrupt */ + rxcount++; + continue; + } + } + if (rxcount) + ser12_rx(dev, bc, msr & 0x10); + if (txcount) { +#ifdef BAYCOM_DEBUG + if (bc->debug_vals.cur_pllcorr < txcount) + bc->debug_vals.cur_pllcorr = txcount; +#endif /* BAYCOM_DEBUG */ + if (bc->modem.ser12.txshreg <= 1) + bc->modem.ser12.txshreg = 0x10000 | hdlcdrv_getbits(&bc->hdrv); + bc->modem.ser12.tx_bit = !(bc->modem.ser12.tx_bit ^ (bc->modem.ser12.txshreg & 1)); + bc->modem.ser12.txshreg >>= 1; + } + sti(); + if (bc->modem.arb_divider <= 0) { + bc->modem.arb_divider = bc->baud_arbdiv; + hdlcdrv_arbitrate(dev, &bc->hdrv); + } + hdlcdrv_transmitter(dev, &bc->hdrv); + hdlcdrv_receiver(dev, &bc->hdrv); +} + +/* --------------------------------------------------------------------- */ + +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 device *dev) +{ + struct baycom_state *bc = (struct baycom_state *)dev->priv; + 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 (bc->baud < 300 || bc->baud > 4800) + return -EINVAL; + if (check_region(dev->base_addr, SER12_EXTENT)) + return -EACCES; + memset(&bc->modem, 0, sizeof(bc->modem)); + bc->hdrv.par.bitrate = bc->baud; + bc->baud_us8 = 125000/bc->baud; + bc->baud_arbdiv = bc->baud/100; + if ((u = ser12_check_uart(dev->base_addr)) == c_uart_unknown) + return -EIO; + outb(0, FCR(dev->base_addr)); /* disable FIFOs */ + outb(0x0d, MCR(dev->base_addr)); + outb(0x0d, MCR(dev->base_addr)); + outb(0, IER(dev->base_addr)); + if (request_irq(dev->irq, ser12_interrupt, SA_INTERRUPT, + "baycom_ser_fdx", dev)) + return -EBUSY; + request_region(dev->base_addr, SER12_EXTENT, "baycom_ser_fdx"); + /* + * set the SIO to 6 Bits/character and 19600 baud, so that + * we get exactly (hopefully) one interrupt per radio symbol + */ + outb(0x81, LCR(dev->base_addr)); /* DLAB = 1 */ + outb(115200/8/bc->baud, DLL(dev->base_addr)); + outb(0, DLM(dev->base_addr)); + outb(0x01, LCR(dev->base_addr)); /* word length = 6 */ + /* + * 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)); + printk(KERN_INFO "%s: ser_fdx at iobase 0x%lx irq %u options " + "0x%x baud %u uart %s\n", bc_drvname, dev->base_addr, dev->irq, + bc->options, bc->baud, uart_str[u]); + MOD_INC_USE_COUNT; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int ser12_close(struct device *dev) +{ + struct baycom_state *bc = (struct baycom_state *)dev->priv; + + 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); + MOD_DEC_USE_COUNT; + return 0; +} + +/* --------------------------------------------------------------------- */ +/* + * ===================== hdlcdrv driver interface ========================= + */ + +/* --------------------------------------------------------------------- */ + +static int baycom_ioctl(struct device *dev, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd); + +/* --------------------------------------------------------------------- */ + +static struct hdlcdrv_ops ser12_ops = { + bc_drvname, + bc_drvinfo, + ser12_open, + ser12_close, + 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; + } + bc->options = !!strchr(modestr, '*'); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int baycom_ioctl(struct device *dev, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd) +{ + struct baycom_state *bc; + struct baycom_ioctl bi; + int cmd2; + + if (!dev || !dev->priv || + ((struct baycom_state *)dev->priv)->hdrv.magic != HDLCDRV_MAGIC) { + printk(KERN_ERR "bc_ioctl: invalid device struct\n"); + return -EINVAL; + } + bc = (struct baycom_state *)dev->priv; + + if (cmd != SIOCDEVPRIVATE) + return -ENOIOCTLCMD; + if (get_user(cmd2, (int *)ifr->ifr_data)) + return -EFAULT; + switch (hi->cmd) { + default: + break; + + case HDLCDRVCTL_GETMODE: + sprintf(hi->data.modename, "ser%u", bc->baud / 100); + if (bc->options & 1) + strcat(hi->data.modename, "*"); + if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl))) + return -EFAULT; + return 0; + + case HDLCDRVCTL_SETMODE: + if (!suser() || dev->start) + 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; + +} + +/* --------------------------------------------------------------------- */ + +__initfunc(int baycom_ser_fdx_init(void)) +{ + int i, j, found = 0; + char set_hw = 1; + struct baycom_state *bc; + char ifname[HDLCDRV_IFNAMELEN]; + + + printk(bc_drvinfo); + /* + * register net devices + */ + for (i = 0; i < NR_PORTS; i++) { + struct device *dev = baycom_device+i; + sprintf(ifname, "bcsf%d", i); + + if (!baycom_ports[i].mode) + set_hw = 0; + if (!set_hw) + baycom_ports[i].iobase = baycom_ports[i].irq = 0; + j = hdlcdrv_register_hdlcdrv(dev, &ser12_ops, + sizeof(struct baycom_state), + ifname, baycom_ports[i].iobase, + baycom_ports[i].irq, 0); + if (!j) { + bc = (struct baycom_state *)dev->priv; + if (set_hw && baycom_setmode(bc, baycom_ports[i].mode)) + set_hw = 0; + bc->baud = baycom_ports[i].baud; + found++; + } else { + printk(KERN_WARNING "%s: cannot register net device\n", + bc_drvname); + } + } + if (!found) + return -ENXIO; + return 0; +} + +/* --------------------------------------------------------------------- */ + +#ifdef MODULE + +/* + * 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 }; + +#if LINUX_VERSION_CODE >= 0x20115 + +MODULE_PARM(mode, "1-" __MODULE_STRING(NR_PORTS) "s"); +MODULE_PARM_DESC(mode, "baycom operating mode; * for software DCD"); +MODULE_PARM(iobase, "1-" __MODULE_STRING(NR_PORTS) "i"); +MODULE_PARM_DESC(iobase, "baycom io base address"); +MODULE_PARM(irq, "1-" __MODULE_STRING(NR_PORTS) "i"); +MODULE_PARM_DESC(irq, "baycom irq number"); +MODULE_PARM(baud, "1-" __MODULE_STRING(NR_PORTS) "i"); +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"); + +#endif + +__initfunc(int init_module(void)) +{ + int i; + + for (i = 0; (i < NR_PORTS) && (mode[i]); i++) { + baycom_ports[i].mode = mode[i]; + baycom_ports[i].iobase = iobase[i]; + baycom_ports[i].irq = irq[i]; + baycom_ports[i].baud = baud[i]; + } + if (i < NR_PORTS-1) + baycom_ports[i+1].mode = NULL; + return baycom_ser_fdx_init(); +} + +/* --------------------------------------------------------------------- */ + +void cleanup_module(void) +{ + int i; + + for(i = 0; i < NR_PORTS; i++) { + struct device *dev = baycom_device+i; + struct baycom_state *bc = (struct baycom_state *)dev->priv; + + if (bc) { + if (bc->hdrv.magic != HDLCDRV_MAGIC) + printk(KERN_ERR "baycom: invalid magic in " + "cleanup_module\n"); + else + hdlcdrv_unregister_hdlcdrv(dev); + } + } +} + +#else /* MODULE */ +/* --------------------------------------------------------------------- */ +/* + * format: baycom_ser_=io,irq,mode + * mode: [*] + * * indicates sofware DCD + */ + +__initfunc(void baycom_ser_fdx_setup(char *str, int *ints)) +{ + int i; + + for (i = 0; (i < NR_PORTS) && (baycom_ports[i].mode); i++); + if ((i >= NR_PORTS) || (ints[0] < 2)) { + printk(KERN_INFO "%s: too many or invalid interface " + "specifications\n", bc_drvname); + return; + } + baycom_ports[i].mode = str; + baycom_ports[i].iobase = ints[1]; + baycom_ports[i].irq = ints[2]; + if (ints[0] >= 3) + baycom_ports[i].baud = ints[3]; + else + baycom_ports[i].baud = 1200; + if (i < NR_PORTS-1) + baycom_ports[i+1].mode = NULL; +} + +#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..e3d7ac998 --- /dev/null +++ b/drivers/net/hamradio/baycom_ser_hdx.c @@ -0,0 +1,792 @@ +/*****************************************************************************/ + +/* + * baycom_ser_hdx.c -- baycom ser12 halfduplex radio modem driver. + * + * Copyright (C) 1997 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 * enables software DCD. + * 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.96 Adapted from baycom.c and made network driver interface + * 18.10.96 Changed to new user space access routines (copy_{to,from}_user) + * 0.3 26.04.97 init code/data tagged + * 0.4 08.07.97 alternative ser12 decoding algorithm (uses delta CTS ints) + * 0.5 11.11.97 ser12/par96 split into separate files + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/hdlcdrv.h> +#include <linux/baycom.h> + +/* --------------------------------------------------------------------- */ + +/* + * currently this module is supposed to support both module styles, i.e. + * the old one present up to about 2.1.9, and the new one functioning + * starting with 2.1.21. The reason is I have a kit allowing to compile + * this module also under 2.0.x which was requested by several people. + * This will go in 2.2 + */ +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= 0x20100 +#include <asm/uaccess.h> +#else +#include <asm/segment.h> +#include <linux/mm.h> + +#undef put_user +#undef get_user + +#define put_user(x,ptr) ({ __put_user((unsigned long)(x),(ptr),sizeof(*(ptr))); 0; }) +#define get_user(x,ptr) ({ x = ((__typeof__(*(ptr)))__get_user((ptr),sizeof(*(ptr)))); 0; }) + +extern inline int copy_from_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_READ, from, n); + if (i) + return i; + memcpy_fromfs(to, from, n); + return 0; +} + +extern inline int copy_to_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_WRITE, to, n); + if (i) + return i; + memcpy_tofs(to, from, n); + return 0; +} +#endif + +#if LINUX_VERSION_CODE >= 0x20123 +#include <linux/init.h> +#else +#define __init +#define __initdata +#define __initfunc(x) x +#endif + +/* --------------------------------------------------------------------- */ + +#define BAYCOM_DEBUG + +/* + * modem options; bit mask + */ +#define BAYCOM_OPTIONS_SOFTDCD 1 + +/* --------------------------------------------------------------------- */ + +static const char bc_drvname[] = "baycom_ser_hdx"; +static const char bc_drvinfo[] = KERN_INFO "baycom_ser_hdx: (C) 1997 Thomas Sailer, HB9JNX/AE4WA\n" +KERN_INFO "baycom_ser_hdx: version 0.5 compiled " __TIME__ " " __DATE__ "\n"; + +/* --------------------------------------------------------------------- */ + +#define NR_PORTS 4 + +static struct device baycom_device[NR_PORTS]; + +static struct { + char *mode; + int iobase, irq; +} baycom_ports[NR_PORTS] = { { NULL, 0, 0 }, }; + +/* --------------------------------------------------------------------- */ + +#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 options; + + 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 */ +}; + +/* --------------------------------------------------------------------- */ + +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +/* --------------------------------------------------------------------- */ + +static void inline 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 ((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 void inline ser12_set_divisor(struct 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->options & BAYCOM_OPTIONS_SOFTDCD) ? \ + 36 : 24) +#define SER12_DCD_INTERVAL(bc) ((bc->options & BAYCOM_OPTIONS_SOFTDCD) ? \ + 240 : 12) + +static inline void ser12_tx(struct 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 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->options & BAYCOM_OPTIONS_SOFTDCD) { + 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->options & BAYCOM_OPTIONS_SOFTDCD) { + /* + * 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) { + 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 void ser12_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_id; + struct baycom_state *bc = (struct baycom_state *)dev->priv; + + if (!dev || !bc || bc->hdrv.magic != HDLCDRV_MAGIC) + return; + + baycom_int_freq(bc); + /* + * check if transmitter active + */ + if (hdlcdrv_ptt(&bc->hdrv)) + ser12_tx(dev, bc); + else { + ser12_rx(dev, bc); + if (--bc->modem.arb_divider <= 0) { + bc->modem.arb_divider = SER12_ARB_DIVIDER(bc); + sti(); + hdlcdrv_arbitrate(dev, &bc->hdrv); + } + } + sti(); + hdlcdrv_transmitter(dev, &bc->hdrv); + hdlcdrv_receiver(dev, &bc->hdrv); +} + +/* --------------------------------------------------------------------- */ + +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 device *dev) +{ + struct baycom_state *bc = (struct baycom_state *)dev->priv; + 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 (check_region(dev->base_addr, SER12_EXTENT)) + 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) + return -EIO; + outb(0, FCR(dev->base_addr)); /* disable FIFOs */ + outb(0x0d, MCR(dev->base_addr)); + outb(0x0d, MCR(dev->base_addr)); + outb(0, IER(dev->base_addr)); + if (request_irq(dev->irq, ser12_interrupt, SA_INTERRUPT, + "baycom_ser12", dev)) + return -EBUSY; + request_region(dev->base_addr, SER12_EXTENT, "baycom_ser12"); + /* + * 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->options & BAYCOM_OPTIONS_SOFTDCD) ? 4 : 6); + printk(KERN_INFO "%s: ser12 at iobase 0x%lx irq %u options " + "0x%x uart %s\n", bc_drvname, dev->base_addr, dev->irq, + bc->options, uart_str[u]); + MOD_INC_USE_COUNT; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int ser12_close(struct device *dev) +{ + struct baycom_state *bc = (struct baycom_state *)dev->priv; + + 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); + MOD_DEC_USE_COUNT; + return 0; +} + +/* --------------------------------------------------------------------- */ +/* + * ===================== hdlcdrv driver interface ========================= + */ + +/* --------------------------------------------------------------------- */ + +static int baycom_ioctl(struct device *dev, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd); + +/* --------------------------------------------------------------------- */ + +static struct hdlcdrv_ops ser12_ops = { + bc_drvname, + bc_drvinfo, + ser12_open, + ser12_close, + baycom_ioctl +}; + +/* --------------------------------------------------------------------- */ + +static int baycom_setmode(struct baycom_state *bc, const char *modestr) +{ + bc->options = !!strchr(modestr, '*'); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int baycom_ioctl(struct device *dev, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd) +{ + struct baycom_state *bc; + struct baycom_ioctl bi; + int cmd2; + + if (!dev || !dev->priv || + ((struct baycom_state *)dev->priv)->hdrv.magic != HDLCDRV_MAGIC) { + printk(KERN_ERR "bc_ioctl: invalid device struct\n"); + return -EINVAL; + } + bc = (struct baycom_state *)dev->priv; + + if (cmd != SIOCDEVPRIVATE) + return -ENOIOCTLCMD; + if (get_user(cmd2, (int *)ifr->ifr_data)) + return -EFAULT; + switch (hi->cmd) { + default: + break; + + case HDLCDRVCTL_GETMODE: + strcpy(hi->data.modename, "ser12"); + if (bc->options & 1) + strcat(hi->data.modename, "*"); + if (copy_to_user(ifr->ifr_data, hi, sizeof(struct hdlcdrv_ioctl))) + return -EFAULT; + return 0; + + case HDLCDRVCTL_SETMODE: + if (!suser() || dev->start) + 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; + +} + +/* --------------------------------------------------------------------- */ + +__initfunc(int baycom_ser_hdx_init(void)) +{ + int i, j, found = 0; + char set_hw = 1; + struct baycom_state *bc; + char ifname[HDLCDRV_IFNAMELEN]; + + + printk(bc_drvinfo); + /* + * register net devices + */ + for (i = 0; i < NR_PORTS; i++) { + struct device *dev = baycom_device+i; + sprintf(ifname, "bcsh%d", i); + + if (!baycom_ports[i].mode) + set_hw = 0; + if (!set_hw) + baycom_ports[i].iobase = baycom_ports[i].irq = 0; + j = hdlcdrv_register_hdlcdrv(dev, &ser12_ops, + sizeof(struct baycom_state), + ifname, baycom_ports[i].iobase, + baycom_ports[i].irq, 0); + if (!j) { + bc = (struct baycom_state *)dev->priv; + if (set_hw && baycom_setmode(bc, baycom_ports[i].mode)) + set_hw = 0; + found++; + } else { + printk(KERN_WARNING "%s: cannot register net device\n", + bc_drvname); + } + } + if (!found) + return -ENXIO; + return 0; +} + +/* --------------------------------------------------------------------- */ + +#ifdef MODULE + +/* + * command line settable parameters + */ +static char *mode[NR_PORTS] = { "ser12*", }; +static int iobase[NR_PORTS] = { 0x3f8, }; +static int irq[NR_PORTS] = { 4, }; + +#if LINUX_VERSION_CODE >= 0x20115 + +MODULE_PARM(mode, "1-" __MODULE_STRING(NR_PORTS) "s"); +MODULE_PARM_DESC(mode, "baycom operating mode; * for software DCD"); +MODULE_PARM(iobase, "1-" __MODULE_STRING(NR_PORTS) "i"); +MODULE_PARM_DESC(iobase, "baycom io base address"); +MODULE_PARM(irq, "1-" __MODULE_STRING(NR_PORTS) "i"); +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"); + +#endif + +__initfunc(int init_module(void)) +{ + int i; + + for (i = 0; (i < NR_PORTS) && (mode[i]); i++) { + baycom_ports[i].mode = mode[i]; + baycom_ports[i].iobase = iobase[i]; + baycom_ports[i].irq = irq[i]; + } + if (i < NR_PORTS-1) + baycom_ports[i+1].mode = NULL; + return baycom_ser_hdx_init(); +} + +/* --------------------------------------------------------------------- */ + +void cleanup_module(void) +{ + int i; + + for(i = 0; i < NR_PORTS; i++) { + struct device *dev = baycom_device+i; + struct baycom_state *bc = (struct baycom_state *)dev->priv; + + if (bc) { + if (bc->hdrv.magic != HDLCDRV_MAGIC) + printk(KERN_ERR "baycom: invalid magic in " + "cleanup_module\n"); + else + hdlcdrv_unregister_hdlcdrv(dev); + } + } +} + +#else /* MODULE */ +/* --------------------------------------------------------------------- */ +/* + * format: baycom_ser_=io,irq,mode + * mode: [*] + * * indicates sofware DCD + */ + +__initfunc(void baycom_ser_hdx_setup(char *str, int *ints)) +{ + int i; + + for (i = 0; (i < NR_PORTS) && (baycom_ports[i].mode); i++); + if ((i >= NR_PORTS) || (ints[0] < 2)) { + printk(KERN_INFO "%s: too many or invalid interface " + "specifications\n", bc_drvname); + return; + } + baycom_ports[i].mode = str; + baycom_ports[i].iobase = ints[1]; + baycom_ports[i].irq = ints[2]; + if (i < NR_PORTS-1) + baycom_ports[i+1].mode = NULL; +} + +#endif /* MODULE */ +/* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/bpqether.c b/drivers/net/hamradio/bpqether.c new file mode 100644 index 000000000..54d63121e --- /dev/null +++ b/drivers/net/hamradio/bpqether.c @@ -0,0 +1,669 @@ +/* + * G8BPQ compatible "AX.25 via ethernet" driver release 003 + * + * 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. + */ + +#include <linux/config.h> +#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 <net/ax25.h> +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/skbuff.h> +#include <net/sock.h> +#include <asm/segment.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/notifier.h> +#include <linux/proc_fs.h> +#include <linux/stat.h> +#include <linux/firewall.h> +#include <linux/module.h> +#include <linux/init.h> + +#include <net/ip.h> +#include <net/arp.h> + +#include <linux/bpqether.h> + +static unsigned char ax25_bcast[AX25_ADDR_LEN] = + {'Q' << 1, 'S' << 1, 'T' << 1, ' ' << 1, ' ' << 1, ' ' << 1, '0' << 1}; +static unsigned char ax25_defaddr[AX25_ADDR_LEN] = + {'L' << 1, 'I' << 1, 'N' << 1, 'U' << 1, 'X' << 1, ' ' << 1, '1' << 1}; + +static char bcast_addr[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; + +static char bpq_eth_addr[6]; + +static int bpq_rcv(struct sk_buff *, struct device *, struct packet_type *); +static int bpq_device_event(struct notifier_block *, unsigned long, void *); +static char *bpq_print_ethaddr(unsigned char *); + +static struct packet_type bpq_packet_type = { + 0, /* ntohs(ETH_P_BPQ),*/ + 0, /* copy */ + bpq_rcv, + NULL, + NULL, +}; + +static struct notifier_block bpq_dev_notifier = { + bpq_device_event, + 0 +}; + + +#define MAXBPQDEV 100 + +static struct bpqdev { + struct bpqdev *next; + char ethname[14]; /* ether device name */ + struct device *ethdev; /* link to ethernet device */ + struct device axdev; /* bpq device (bpq#) */ + struct net_device_stats stats; /* some statistics */ + char dest_addr[6]; /* ether destination address */ + char acpt_addr[6]; /* accept ether frames from this address only */ +} *bpq_devices = NULL; + + +/* ------------------------------------------------------------------------ */ + + +/* + * Get the ethernet device for a BPQ device + */ +static __inline__ struct device *bpq_get_ether_dev(struct device *dev) +{ + struct bpqdev *bpq; + + bpq = (struct bpqdev *)dev->priv; + + return (bpq != NULL) ? bpq->ethdev : NULL; +} + +/* + * Get the BPQ device for the ethernet device + */ +static __inline__ struct device *bpq_get_ax25_dev(struct device *dev) +{ + struct bpqdev *bpq; + + for (bpq = bpq_devices; bpq != NULL; bpq = bpq->next) + if (bpq->ethdev == dev) + return &bpq->axdev; + + return NULL; +} + +static __inline__ int dev_is_ethdev(struct device *dev) +{ + return ( + dev->type == ARPHRD_ETHER + && strncmp(dev->name, "dummy", 5) + ); +} + +/* + * Sanity check: remove all devices that ceased to exists and + * return '1' if the given BPQ device was affected. + */ +static int bpq_check_devices(struct device *dev) +{ + struct bpqdev *bpq, *bpq_prev; + int result = 0; + unsigned long flags; + + save_flags(flags); + cli(); + + bpq_prev = NULL; + + for (bpq = bpq_devices; bpq != NULL; bpq = bpq->next) { + if (!dev_get(bpq->ethname)) { + if (bpq_prev) + bpq_prev->next = bpq->next; + else + bpq_devices = bpq->next; + + if (&bpq->axdev == dev) + result = 1; + + unregister_netdev(&bpq->axdev); + kfree(bpq); + } + + bpq_prev = bpq; + } + + restore_flags(flags); + + return result; +} + + +/* ------------------------------------------------------------------------ */ + + +/* + * Receive an AX.25 frame via an ethernet interface. + */ +static int bpq_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *ptype) +{ + int len; + char * ptr; + struct ethhdr *eth = (struct ethhdr *)skb->mac.raw; + struct bpqdev *bpq; + + skb->sk = NULL; /* Initially we don't know who it's for */ + + dev = bpq_get_ax25_dev(dev); + + if (dev == NULL || dev->start == 0) { + kfree_skb(skb, FREE_READ); + return 0; + } + + /* + * if we want to accept frames from just one ethernet device + * we check the source address of the sender. + */ + + bpq = (struct bpqdev *)dev->priv; + + if (!(bpq->acpt_addr[0] & 0x01) && memcmp(eth->h_source, bpq->acpt_addr, ETH_ALEN)) { + printk(KERN_DEBUG "bpqether: wrong dest %s\n", bpq_print_ethaddr(eth->h_source)); + kfree_skb(skb, FREE_READ); + return 0; + } + + 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 */ + + ((struct bpqdev *)dev->priv)->stats.rx_packets++; + ((struct bpqdev *)dev->priv)->stats.rx_bytes+=len; + + ptr = skb_push(skb, 1); + *ptr = 0; + + skb->dev = dev; + skb->protocol = htons(ETH_P_AX25); + skb->mac.raw = skb->data; + skb->pkt_type = PACKET_HOST; + + netif_rx(skb); + + return 0; +} + +/* + * Send an AX.25 frame via an ethernet interface + */ +static int bpq_xmit(struct sk_buff *skb, struct device *dev) +{ + struct sk_buff *newskb; + unsigned char *ptr; + struct bpqdev *bpq; + int size; + + /* + * Just to be *really* sure not to send anything if the interface + * is down, the ethernet device may have gone. + */ + if (!dev->start) { + bpq_check_devices(dev); + kfree_skb(skb, FREE_WRITE); + return -ENODEV; + } + + skb_pull(skb, 1); + size = skb->len; + + /* + * The AX.25 code leaves enough room for the ethernet header, but + * sendto() does not. + */ + if (skb_headroom(skb) < AX25_BPQ_HEADER_LEN) { /* Ough! */ + if ((newskb = skb_realloc_headroom(skb, AX25_BPQ_HEADER_LEN)) == NULL) { + printk(KERN_WARNING "bpqether: out of memory\n"); + kfree_skb(skb, FREE_WRITE); + return -ENOMEM; + } + + if (skb->sk != NULL) + skb_set_owner_w(newskb, skb->sk); + + kfree_skb(skb, FREE_WRITE); + skb = newskb; + } + + skb->protocol = htons(ETH_P_AX25); + + ptr = skb_push(skb, 2); + + *ptr++ = (size + 5) % 256; + *ptr++ = (size + 5) / 256; + + bpq = (struct bpqdev *)dev->priv; + + if ((dev = bpq_get_ether_dev(dev)) == NULL) { + bpq->stats.tx_dropped++; + kfree_skb(skb, FREE_WRITE); + return -ENODEV; + } + + skb->dev = dev; + dev->hard_header(skb, dev, ETH_P_BPQ, bpq->dest_addr, NULL, 0); + bpq->stats.tx_packets++; + bpq->stats.tx_bytes+=skb->len; + + dev_queue_xmit(skb); + + return 0; +} + +/* + * Statistics + */ +static struct net_device_stats *bpq_get_stats(struct device *dev) +{ + struct bpqdev *bpq; + + bpq = (struct bpqdev *)dev->priv; + + return &bpq->stats; +} + +/* + * Set AX.25 callsign + */ +static int bpq_set_mac_address(struct 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 device *dev, struct ifreq *ifr, int cmd) +{ + int err; + struct bpq_ethaddr *ethaddr = (struct bpq_ethaddr *)ifr->ifr_data; + struct bpqdev *bpq = dev->priv; + struct bpq_req req; + + if (!suser()) + return -EPERM; + + if (bpq == NULL) /* woops! */ + return -ENODEV; + + switch (cmd) { + case SIOCSBPQETHOPT: + if ((err = verify_area(VERIFY_WRITE, ifr->ifr_data, sizeof(struct bpq_req))) != 0) + return err; + copy_from_user(&req, ifr->ifr_data, sizeof(struct bpq_req)); + switch (req.cmd) { + case SIOCGBPQETHPARAM: + case SIOCSBPQETHPARAM: + default: + return -EINVAL; + } + + break; + + case SIOCSBPQETHADDR: + if ((err = verify_area(VERIFY_READ, ethaddr, sizeof(struct bpq_ethaddr))) != 0) + return err; + copy_from_user(bpq->dest_addr, ethaddr->destination, ETH_ALEN); + copy_from_user(bpq->acpt_addr, ethaddr->accept, ETH_ALEN); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * open/close a device + */ +static int bpq_open(struct device *dev) +{ + if (bpq_check_devices(dev)) + return -ENODEV; /* oops, it's gone */ + + dev->tbusy = 0; + dev->start = 1; + + MOD_INC_USE_COUNT; + + return 0; +} + +static int bpq_close(struct device *dev) +{ + dev->tbusy = 1; + dev->start = 0; + + MOD_DEC_USE_COUNT; + + return 0; +} + +/* + * currently unused + */ +static int bpq_dev_init(struct device *dev) +{ + return 0; +} + + +/* ------------------------------------------------------------------------ */ + + +/* + * Proc filesystem + */ +static char * bpq_print_ethaddr(unsigned char *e) +{ + static char buf[18]; + + sprintf(buf, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + e[0], e[1], e[2], e[3], e[4], e[5]); + + return buf; +} + +int bpq_get_info(char *buffer, char **start, off_t offset, int length, int dummy) +{ + struct bpqdev *bpqdev; + int len = 0; + off_t pos = 0; + off_t begin = 0; + + cli(); + + len += sprintf(buffer, "dev ether destination accept from\n"); + + for (bpqdev = bpq_devices; bpqdev != NULL; bpqdev = bpqdev->next) { + len += sprintf(buffer + len, "%-5s %-10s %s ", + bpqdev->axdev.name, bpqdev->ethname, + bpq_print_ethaddr(bpqdev->dest_addr)); + + len += sprintf(buffer + len, "%s\n", + (bpqdev->acpt_addr[0] & 0x01) ? "*" : bpq_print_ethaddr(bpqdev->acpt_addr)); + + pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + + if (pos > offset + length) + break; + } + + sti(); + + *start = buffer + (offset - begin); + len -= (offset - begin); + + if (len > length) len = length; + + return len; +} + + +/* ------------------------------------------------------------------------ */ + + +/* + * Setup a new device. + */ +static int bpq_new_device(struct device *dev) +{ + int k; + unsigned char *buf; + struct bpqdev *bpq, *bpq2; + + if ((bpq = kmalloc(sizeof(struct bpqdev), GFP_KERNEL)) == NULL) + return -ENOMEM; + + memset(bpq, 0, sizeof(struct bpqdev)); + + bpq->ethdev = dev; + + bpq->ethname[sizeof(bpq->ethname)-1] = '\0'; + strncpy(bpq->ethname, dev->name, sizeof(bpq->ethname)-1); + + memcpy(bpq->dest_addr, bcast_addr, sizeof(bpq_eth_addr)); + memcpy(bpq->acpt_addr, bcast_addr, sizeof(bpq_eth_addr)); + + dev = &bpq->axdev; + buf = kmalloc(14, GFP_KERNEL); + + for (k = 0; k < MAXBPQDEV; k++) { + struct device *odev; + + sprintf(buf, "bpq%d", k); + + if ((odev = dev_get(buf)) == NULL || bpq_check_devices(odev)) + break; + } + + if (k == MAXBPQDEV) { + kfree(bpq); + return -ENODEV; + } + + dev->priv = (void *)bpq; /* pointer back */ + dev->name = buf; + dev->init = bpq_dev_init; + + if (register_netdev(dev) != 0) { + kfree(bpq); + return -EIO; + } + + dev_init_buffers(dev); + + dev->hard_start_xmit = bpq_xmit; + dev->open = bpq_open; + dev->stop = bpq_close; + dev->set_mac_address = bpq_set_mac_address; + dev->get_stats = bpq_get_stats; + dev->do_ioctl = bpq_ioctl; + + memcpy(dev->broadcast, ax25_bcast, AX25_ADDR_LEN); + memcpy(dev->dev_addr, ax25_defaddr, AX25_ADDR_LEN); + + dev->flags = 0; + +#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE) + dev->hard_header = ax25_encapsulate; + dev->rebuild_header = ax25_rebuild_header; +#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; + + cli(); + + if (bpq_devices == NULL) { + bpq_devices = bpq; + } else { + for (bpq2 = bpq_devices; bpq2->next != NULL; bpq2 = bpq2->next); + bpq2->next = bpq; + } + + sti(); + + return 0; +} + + +/* + * Handle device status changes. + */ +static int bpq_device_event(struct notifier_block *this,unsigned long event, void *ptr) +{ + struct device *dev = (struct device *)ptr; + + if (!dev_is_ethdev(dev)) + return NOTIFY_DONE; + + bpq_check_devices(NULL); + + 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; + + default: + break; + } + + return NOTIFY_DONE; +} + + +/* ------------------------------------------------------------------------ */ + +/* + * Initialize driver. To be called from af_ax25 if not compiled as a + * module + */ +__initfunc(int bpq_init(void)) +{ + struct device *dev; + + bpq_packet_type.type = htons(ETH_P_BPQ); + dev_add_pack(&bpq_packet_type); + + register_netdevice_notifier(&bpq_dev_notifier); + + printk(KERN_INFO "AX.25 ethernet driver version 0.01\n"); + +#ifdef CONFIG_PROC_FS + proc_net_register(&(struct proc_dir_entry) { + PROC_NET_AX25_BPQETHER, 8, "bpqether", + S_IFREG | S_IRUGO, 1, 0, 0, + 0, &proc_net_inode_operations, + bpq_get_info + }); +#endif + + for (dev = dev_base; dev != NULL; dev = dev->next) { + if (dev_is_ethdev(dev)) + bpq_new_device(dev); + } + + return 0; +} + +#ifdef MODULE +EXPORT_NO_SYMBOLS; + +MODULE_AUTHOR("Joerg Reuter DL1BKE <jreuter@lykos.oche.de>"); +MODULE_DESCRIPTION("Transmit and receive AX.25 packets over Ethernet"); + +int init_module(void) +{ + return bpq_init(); +} + +void cleanup_module(void) +{ + struct bpqdev *bpq; + + dev_remove_pack(&bpq_packet_type); + + unregister_netdevice_notifier(&bpq_dev_notifier); + +#ifdef CONFIG_PROC_FS + proc_net_unregister(PROC_NET_AX25_BPQETHER); +#endif + + for (bpq = bpq_devices; bpq != NULL; bpq = bpq->next) + unregister_netdev(&bpq->axdev); +} +#endif diff --git a/drivers/net/hamradio/hdlcdrv.c b/drivers/net/hamradio/hdlcdrv.c new file mode 100644 index 000000000..148ed14f4 --- /dev/null +++ b/drivers/net/hamradio/hdlcdrv.c @@ -0,0 +1,1024 @@ +/*****************************************************************************/ + +/* + * hdlcdrv.c -- HDLC packet radio network driver. + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + * The driver was derived from Donald Beckers skeleton.c + * Written 1993-94 by Donald Becker. + * + * History: + * 0.1 21.09.96 Started + * 18.10.96 Changed to new user space access routines + * (copy_{to,from}_user) + * 0.2 21.11.96 various small changes + * 0.3 03.03.97 fixed (hopefully) IP not working with ax.25 as a module + * 0.4 16.04.97 init code/data tagged + * 0.5 30.07.97 made HDLC buffers bigger (solves a problem with the + * soundmodem driver) + */ + +/*****************************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/net.h> +#include <linux/in.h> +#include <linux/if.h> +#include <linux/malloc.h> +#include <linux/errno.h> +#include <asm/bitops.h> + +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/hdlcdrv.h> +#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE) +/* prototypes for ax25_encapsulate and ax25_rebuild_header */ +#include <net/ax25.h> +#endif /* CONFIG_AX25 || CONFIG_AX25_MODULE */ + +/* make genksyms happy */ +#include <linux/ip.h> +#include <linux/udp.h> +#include <linux/tcp.h> + +/* --------------------------------------------------------------------- */ + +/* + * currently this module is supposed to support both module styles, i.e. + * the old one present up to about 2.1.9, and the new one functioning + * starting with 2.1.21. The reason is I have a kit allowing to compile + * this module also under 2.0.x which was requested by several people. + * This will go in 2.2 + */ +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= 0x20100 +#include <asm/uaccess.h> +#else +#include <asm/segment.h> +#include <linux/mm.h> + +#undef put_user +#undef get_user + +#define put_user(x,ptr) ({ __put_user((unsigned long)(x),(ptr),sizeof(*(ptr))); 0; }) +#define get_user(x,ptr) ({ x = ((__typeof__(*(ptr)))__get_user((ptr),sizeof(*(ptr)))); 0; }) + +extern inline int copy_from_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_READ, from, n); + if (i) + return i; + memcpy_fromfs(to, from, n); + return 0; +} + +extern inline int copy_to_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_WRITE, to, n); + if (i) + return i; + memcpy_tofs(to, from, n); + return 0; +} +#endif + +/* --------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE < 0x20115 +extern __inline__ void dev_init_buffers(struct device *dev) +{ + int i; + for(i=0;i<DEV_NUMBUFFS;i++) + { + skb_queue_head_init(&dev->buffs[i]); + } +} +#endif + +/* --------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= 0x20123 +#include <linux/init.h> +#else +#define __init +#define __initdata +#define __initfunc(x) x +#endif + +/* --------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE < 0x20125 +#define test_and_set_bit set_bit +#define test_and_clear_bit clear_bit +#endif + +/* --------------------------------------------------------------------- */ + +/* + * The name of the card. Is used for messages and in the requests for + * io regions, irqs and dma channels + */ + +static char ax25_bcast[AX25_ADDR_LEN] = +{'Q' << 1, 'S' << 1, 'T' << 1, ' ' << 1, ' ' << 1, ' ' << 1, '0' << 1}; +static char ax25_nocall[AX25_ADDR_LEN] = +{'L' << 1, 'I' << 1, 'N' << 1, 'U' << 1, 'X' << 1, ' ' << 1, '1' << 1}; + +/* --------------------------------------------------------------------- */ + +#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 + +/* --------------------------------------------------------------------- */ + +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +/* --------------------------------------------------------------------- */ +/* + * the CRC routines are stolen from WAMPES + * by Dieter Deyke + */ + +static const unsigned short crc_ccitt_table[] = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 +}; + +/*---------------------------------------------------------------------------*/ + +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; +} + +/*---------------------------------------------------------------------------*/ + +static inline int check_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]; + return (crc & 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 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", + s->ifname); + s->stats.rx_dropped++; + return; + } + skb->dev = dev; + cp = skb_put(skb, pkt_len); + *cp++ = 0; /* KISS kludge */ + memcpy(cp, s->hdlcrx.buffer, pkt_len - 1); + skb->protocol = htons(ETH_P_AX25); + skb->mac.raw = skb->data; + netif_rx(skb); + s->stats.rx_packets++; +} + +void hdlcdrv_receiver(struct 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 void inline do_kiss_params(struct hdlcdrv_state *s, + unsigned char *data, unsigned long len) +{ + +#ifdef KISS_VERBOSE +#define PKP(a,b) printk(KERN_INFO "%s: channel params: " a "\n", s->ifname, 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 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 = skb_dequeue(&s->send_queue))) { + 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; + } + if (skb->data[0] != 0) { + do_kiss_params(s, skb->data, skb->len); + dev_kfree_skb(skb, FREE_WRITE); + break; + } + 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(skb, FREE_WRITE); + break; + } + memcpy(s->hdlctx.buffer, skb->data+1, pkt_len); + dev_kfree_skb(skb, FREE_WRITE); + 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; + s->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 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++; +} + +/* ---------------------------------------------------------------------- */ + +static unsigned short random_seed; + +static inline unsigned short random_num(void) +{ + random_seed = 28629 * random_seed + 157; + return random_seed; +} + +/* ---------------------------------------------------------------------- */ + +void hdlcdrv_arbitrate(struct device *dev, struct hdlcdrv_state *s) +{ + if (!s || s->magic != HDLCDRV_MAGIC || s->hdlctx.ptt || + skb_queue_empty(&s->send_queue)) + 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 ((random_num() % 256) > s->ch_params.ppersist) + return; + start_tx(dev, s); +} + +/* --------------------------------------------------------------------- */ +/* + * ===================== network driver interface ========================= + */ + +static inline int hdlcdrv_paranoia_check(struct device *dev, + const char *routine) +{ + if (!dev || !dev->priv || + ((struct hdlcdrv_state *)dev->priv)->magic != HDLCDRV_MAGIC) { + printk(KERN_ERR "hdlcdrv: bad magic number for hdlcdrv_state " + "struct in routine %s\n", routine); + return 1; + } + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int hdlcdrv_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct hdlcdrv_state *sm; + + if (hdlcdrv_paranoia_check(dev, "hdlcdrv_send_packet")) + return 0; + sm = (struct hdlcdrv_state *)dev->priv; + skb_queue_tail(&sm->send_queue, skb); + dev->trans_start = jiffies; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int hdlcdrv_set_mac_address(struct 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; +} + +/* --------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= 0x20119 +static struct net_device_stats *hdlcdrv_get_stats(struct device *dev) +#else +static struct enet_statistics *hdlcdrv_get_stats(struct device *dev) +#endif +{ + struct hdlcdrv_state *sm; + + if (hdlcdrv_paranoia_check(dev, "hdlcdrv_get_stats")) + return NULL; + sm = (struct hdlcdrv_state *)dev->priv; + /* + * Get the current statistics. This may be called with the + * card open or closed. + */ + return &sm->stats; +} + +/* --------------------------------------------------------------------- */ +/* + * 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 device *dev) +{ + struct hdlcdrv_state *s; + int i; + + if (hdlcdrv_paranoia_check(dev, "hdlcdrv_open")) + return -EINVAL; + s = (struct hdlcdrv_state *)dev->priv; + + if (dev->start) + return 0; + if (!s->ops || !s->ops->open) + return -ENODEV; + + dev->start = 1; + /* + * initialise some variables + */ + 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) { + dev->start = 0; + return i; + } + + dev->tbusy = 0; + dev->interrupt = 0; + + return 0; +} + +/* --------------------------------------------------------------------- */ +/* + * The inverse routine to hdlcdrv_open(). + */ + +static int hdlcdrv_close(struct device *dev) +{ + struct hdlcdrv_state *s; + struct sk_buff *skb; + int i = 0; + + if (hdlcdrv_paranoia_check(dev, "hdlcdrv_close")) + return -EINVAL; + s = (struct hdlcdrv_state *)dev->priv; + + if (!dev->start) + return 0; + dev->start = 0; + dev->tbusy = 1; + + if (s->ops && s->ops->close) + i = s->ops->close(dev); + /* Free any buffers left in the hardware transmit queue */ + while ((skb = skb_dequeue(&s->send_queue))) + dev_kfree_skb(skb, FREE_WRITE); + return i; +} + +/* --------------------------------------------------------------------- */ + +static int hdlcdrv_ioctl(struct device *dev, struct ifreq *ifr, int cmd) +{ + struct hdlcdrv_state *s; + struct hdlcdrv_ioctl bi; + + if (hdlcdrv_paranoia_check(dev, "hdlcdrv_ioctl")) + return -EINVAL; + s = (struct hdlcdrv_state *)dev->priv; + + 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 (!suser()) + 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 ((!suser()) || dev->start) + 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 = s->stats.tx_packets; + bi.data.cs.tx_errors = s->stats.tx_errors; + bi.data.cs.rx_packets = s->stats.rx_packets; + bi.data.cs.rx_errors = s->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; +#if LINUX_VERSION_CODE < 0x20100 + bi.data.ocs.stats = s->stats; +#endif + break; + + case HDLCDRVCTL_CALIBRATE: + 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; + +} + +/* --------------------------------------------------------------------- */ + +/* + * 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 int hdlcdrv_probe(struct device *dev) +{ + const struct hdlcdrv_channel_params dflt_ch_params = { + 20, 2, 10, 40, 0 + }; + struct hdlcdrv_state *s; + + if (!dev) + return -ENXIO; + /* + * not a real probe! only initialize data structures + */ + s = (struct hdlcdrv_state *)dev->priv; + /* + * initialize the hdlcdrv_state struct + */ + s->ch_params = dflt_ch_params; + s->ptt_keyed = 0; + + 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; + +#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 */ + + /* + * initialize the device struct + */ + dev->open = hdlcdrv_open; + dev->stop = hdlcdrv_close; + dev->do_ioctl = hdlcdrv_ioctl; + dev->hard_start_xmit = hdlcdrv_send_packet; + dev->get_stats = hdlcdrv_get_stats; + + /* Fill in the fields of the device structure */ + + dev_init_buffers(dev); + + skb_queue_head_init(&s->send_queue); + +#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE) + dev->hard_header = ax25_encapsulate; + dev->rebuild_header = ax25_rebuild_header; +#else /* CONFIG_AX25 || CONFIG_AX25_MODULE */ + dev->hard_header = NULL; + dev->rebuild_header = NULL; +#endif /* CONFIG_AX25 || CONFIG_AX25_MODULE */ + dev->set_mac_address = hdlcdrv_set_mac_address; + + 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_nocall, AX25_ADDR_LEN); + + /* New style flags */ + dev->flags = 0; + + return 0; +} + +/* --------------------------------------------------------------------- */ + +int hdlcdrv_register_hdlcdrv(struct device *dev, const struct hdlcdrv_ops *ops, + unsigned int privsize, char *ifname, + unsigned int baseaddr, unsigned int irq, + unsigned int dma) +{ + struct hdlcdrv_state *s; + + if (!dev || !ops) + return -EACCES; + if (privsize < sizeof(struct hdlcdrv_state)) + privsize = sizeof(struct hdlcdrv_state); + memset(dev, 0, sizeof(struct device)); + if (!(s = dev->priv = kmalloc(privsize, GFP_KERNEL))) + return -ENOMEM; + /* + * initialize part of the hdlcdrv_state struct + */ + memset(s, 0, privsize); + s->magic = HDLCDRV_MAGIC; + strncpy(s->ifname, ifname, sizeof(s->ifname)); + s->ops = ops; + /* + * initialize part of the device struct + */ + dev->name = s->ifname; + dev->if_port = 0; + dev->init = hdlcdrv_probe; + dev->start = 0; + dev->tbusy = 1; + dev->base_addr = baseaddr; + dev->irq = irq; + dev->dma = dma; + if (register_netdev(dev)) { + printk(KERN_WARNING "hdlcdrv: cannot register net " + "device %s\n", s->ifname); + kfree(dev->priv); + return -ENXIO; + } + MOD_INC_USE_COUNT; + return 0; +} + +/* --------------------------------------------------------------------- */ + +int hdlcdrv_unregister_hdlcdrv(struct device *dev) +{ + struct hdlcdrv_state *s; + + if (!dev) + return -EINVAL; + if (!(s = (struct hdlcdrv_state *)dev->priv)) + return -EINVAL; + if (s->magic != HDLCDRV_MAGIC) + return -EINVAL; + if (dev->start && s->ops->close) + s->ops->close(dev); + unregister_netdev(dev); + kfree(s); + MOD_DEC_USE_COUNT; + return 0; +} + +/* --------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= 0x20115 + +EXPORT_SYMBOL(hdlcdrv_receiver); +EXPORT_SYMBOL(hdlcdrv_transmitter); +EXPORT_SYMBOL(hdlcdrv_arbitrate); +EXPORT_SYMBOL(hdlcdrv_register_hdlcdrv); +EXPORT_SYMBOL(hdlcdrv_unregister_hdlcdrv); + +#else + +static struct symbol_table hdlcdrv_syms = { +#include <linux/symtab_begin.h> + X(hdlcdrv_receiver), + X(hdlcdrv_transmitter), + X(hdlcdrv_arbitrate), + X(hdlcdrv_register_hdlcdrv), + X(hdlcdrv_unregister_hdlcdrv), +#include <linux/symtab_end.h> +}; + +#endif + +/* --------------------------------------------------------------------- */ + +#ifdef MODULE + +#if LINUX_VERSION_CODE >= 0x20115 + +MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu"); +MODULE_DESCRIPTION("Packet Radio network interface HDLC encoder/decoder"); + +#endif + +/* --------------------------------------------------------------------- */ + +__initfunc(int init_module(void)) +{ + printk(KERN_INFO "hdlcdrv: (C) 1996 Thomas Sailer HB9JNX/AE4WA\n"); + printk(KERN_INFO "hdlcdrv: version 0.5 compiled " __TIME__ " " __DATE__ "\n"); +#if LINUX_VERSION_CODE < 0x20115 + register_symtab(&hdlcdrv_syms); +#endif + return 0; +} + +/* --------------------------------------------------------------------- */ + +void cleanup_module(void) +{ + printk(KERN_INFO "hdlcdrv: cleanup\n"); +} + +#endif /* MODULE */ +/* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/mkiss.c b/drivers/net/hamradio/mkiss.c new file mode 100644 index 000000000..562a27681 --- /dev/null +++ b/drivers/net/hamradio/mkiss.c @@ -0,0 +1,1134 @@ +/* + * MKISS Driver + * + * 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 module implements the AX.25 protocol for kernel-based + * devices like TTYs. It interfaces between a raw TTY, and the + * kernel's AX.25 protocol layers, just like slip.c. + * AX.25 needs to be seperated from slip.c while slip.c is no + * longer a static kernel device since it is a module. + * This method clears the way to implement other kiss protocols + * like mkiss smack g8bpq ..... so far only mkiss is implemented. + * + * Hans Alblas <hans@esrac.ele.tue.nl> + * + * History + * Jonathan (G4KLX) Fixed to match Linux networking changes - 2.1.15. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/bitops.h> +#include <asm/uaccess.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/inet.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/major.h> +#include <linux/init.h> + +#include <linux/timer.h> + +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> + +#include <net/ax25.h> + +#include "mkiss.h" + +#ifdef CONFIG_INET +#include <linux/ip.h> +#include <linux/tcp.h> +#endif + +#ifdef MODULE +#define AX25_VERSION "AX25-MODULAR-NET3.019-NEWTTY" +#define min(a,b) (a < b ? a : b) +#else +#define AX25_VERSION "AX25-NET3.019-NEWTTY" +#endif + +#define NR_MKISS 4 +#define MKISS_SERIAL_TYPE_NORMAL 1 + +struct mkiss_channel { + int magic; /* magic word */ + int init; /* channel exists? */ + struct tty_struct *tty; /* link to tty control structure */ +}; + +typedef struct ax25_ctrl { + char if_name[8]; /* "ax0\0" .. "ax99999\0" */ + struct ax_disp ctrl; /* */ + struct device dev; /* the device */ +} ax25_ctrl_t; + +static ax25_ctrl_t **ax25_ctrls = NULL; + +int ax25_maxdev = AX25_MAXDEV; /* Can be overridden with insmod! */ + +static struct tty_ldisc ax_ldisc; +static struct tty_driver mkiss_driver; +static int mkiss_refcount; +static struct tty_struct *mkiss_table[NR_MKISS]; +static struct termios *mkiss_termios[NR_MKISS]; +static struct termios *mkiss_termios_locked[NR_MKISS]; +struct mkiss_channel MKISS_Info[NR_MKISS]; + +static int ax25_init(struct device *); +static int mkiss_init(void); +static int mkiss_write(struct tty_struct *, int, const unsigned char *, int); +static int kiss_esc(unsigned char *, unsigned char *, int); +static void kiss_unesc(struct ax_disp *, unsigned char); + +/* Find a free channel, and link in this `tty' line. */ +static inline struct ax_disp *ax_alloc(void) +{ + ax25_ctrl_t *axp; + int i; + + if (ax25_ctrls == NULL) /* Master array missing ! */ + return NULL; + + for (i = 0; i < ax25_maxdev; i++) { + axp = ax25_ctrls[i]; + + /* Not allocated ? */ + if (axp == NULL) + break; + + /* Not in use ? */ + if (!test_and_set_bit(AXF_INUSE, &axp->ctrl.flags)) + break; + } + + /* Sorry, too many, all slots in use */ + if (i >= ax25_maxdev) + return NULL; + + /* If no channels are available, allocate one */ + if (axp == NULL && (ax25_ctrls[i] = kmalloc(sizeof(ax25_ctrl_t), GFP_KERNEL)) != NULL) { + axp = ax25_ctrls[i]; + memset(axp, 0, sizeof(ax25_ctrl_t)); + + /* Initialize channel control data */ + set_bit(AXF_INUSE, &axp->ctrl.flags); + sprintf(axp->if_name, "ax%d", i++); + axp->ctrl.tty = NULL; + axp->dev.name = axp->if_name; + axp->dev.base_addr = i; + axp->dev.priv = (void *)&axp->ctrl; + axp->dev.next = NULL; + axp->dev.init = ax25_init; + } + + if (axp != NULL) { + /* + * register device so that it can be ifconfig'ed + * ax25_init() will be called as a side-effect + * SIDE-EFFECT WARNING: ax25_init() CLEARS axp->ctrl ! + */ + if (register_netdev(&axp->dev) == 0) { + /* (Re-)Set the INUSE bit. Very Important! */ + set_bit(AXF_INUSE, &axp->ctrl.flags); + axp->ctrl.dev = &axp->dev; + axp->dev.priv = (void *)&axp->ctrl; + + return &axp->ctrl; + } else { + clear_bit(AXF_INUSE,&axp->ctrl.flags); + printk(KERN_ERR "mkiss: ax_alloc() - register_netdev() failure.\n"); + } + } + + return NULL; +} + +/* Free an AX25 channel. */ +static inline void ax_free(struct ax_disp *ax) +{ + /* Free all AX25 frame buffers. */ + if (ax->rbuff) + kfree(ax->rbuff); + ax->rbuff = NULL; + if (ax->xbuff) + kfree(ax->xbuff); + ax->xbuff = NULL; + if (!test_and_clear_bit(AXF_INUSE, &ax->flags)) + printk(KERN_ERR "mkiss: %s: ax_free for already free unit.\n", ax->dev->name); +} + +static void ax_changedmtu(struct ax_disp *ax) +{ + struct device *dev = ax->dev; + unsigned char *xbuff, *rbuff, *oxbuff, *orbuff; + int len; + unsigned long flags; + + 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; + if (xbuff != NULL) + kfree(xbuff); + if (rbuff != NULL) + kfree(rbuff); + return; + } + + save_flags(flags); + cli(); + + 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; + ax->tx_dropped++; + } + } + + ax->xhead = ax->xbuff; + + if (ax->rcount) { + if (ax->rcount <= len) { + memcpy(ax->rbuff, orbuff, ax->rcount); + } else { + ax->rcount = 0; + ax->rx_over_errors++; + set_bit(AXF_ERROR, &ax->flags); + } + } + + ax->mtu = dev->mtu + 73; + ax->buffsize = len; + + restore_flags(flags); + + if (oxbuff != NULL) + kfree(oxbuff); + if (orbuff != NULL) + kfree(orbuff); +} + + +/* Set the "sending" flag. This must be atomic, hence the ASM. */ +static inline void ax_lock(struct ax_disp *ax) +{ + if (test_and_set_bit(0, (void *)&ax->dev->tbusy)) + printk(KERN_ERR "mkiss: %s: trying to lock already locked device!\n", ax->dev->name); +} + + +/* Clear the "sending" flag. This must be atomic, hence the ASM. */ +static inline void ax_unlock(struct ax_disp *ax) +{ + if (!test_and_clear_bit(0, (void *)&ax->dev->tbusy)) + printk(KERN_ERR "mkiss: %s: trying to unlock already unlocked device!\n", ax->dev->name); +} + +/* Send one completely decapsulated AX.25 packet to the AX.25 layer. */ +static void ax_bump(struct ax_disp *ax) +{ + struct ax_disp *tmp_ax; + struct sk_buff *skb; + struct mkiss_channel *mkiss; + int count; + + tmp_ax = ax; + + if (ax->rbuff[0] > 0x0f) { + if (ax->mkiss != NULL) { + mkiss= ax->mkiss->tty->driver_data; + if (mkiss->magic == MKISS_DRIVER_MAGIC) + tmp_ax = ax->mkiss; + } + } + + count = ax->rcount; + + if ((skb = dev_alloc_skb(count)) == NULL) { + printk(KERN_ERR "mkiss: %s: memory squeeze, dropping packet.\n", ax->dev->name); + ax->rx_dropped++; + return; + } + + skb->dev = tmp_ax->dev; + memcpy(skb_put(skb,count), ax->rbuff, count); + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_AX25); + netif_rx(skb); + tmp_ax->rx_packets++; +} + +/* Encapsulate one AX.25 packet and stuff into a TTY queue. */ +static void ax_encaps(struct ax_disp *ax, unsigned char *icp, int len) +{ + unsigned char *p; + int actual, count; + struct mkiss_channel *mkiss = ax->tty->driver_data; + + if (ax->mtu != ax->dev->mtu + 73) /* Someone has been ifconfigging */ + ax_changedmtu(ax); + + if (len > ax->mtu) { /* Sigh, shouldn't occur BUT ... */ + len = ax->mtu; + printk(KERN_ERR "mkiss: %s: truncating oversized transmit packet!\n", ax->dev->name); + ax->tx_dropped++; + ax_unlock(ax); + return; + } + + p = icp; + + if (mkiss->magic != MKISS_DRIVER_MAGIC) { + count = kiss_esc(p, (unsigned char *)ax->xbuff, len); + ax->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP); + actual = ax->tty->driver.write(ax->tty, 0, ax->xbuff, count); + ax->tx_packets++; + ax->dev->trans_start = jiffies; + ax->xleft = count - actual; + ax->xhead = ax->xbuff + actual; + } else { + count = kiss_esc(p, (unsigned char *) ax->mkiss->xbuff, len); + ax->mkiss->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP); + actual = ax->mkiss->tty->driver.write(ax->mkiss->tty, 0, ax->mkiss->xbuff, count); + ax->tx_packets++; + ax->mkiss->dev->trans_start = jiffies; + ax->mkiss->xleft = count - actual; + ax->mkiss->xhead = ax->mkiss->xbuff + actual; + } +} + +/* + * Called by the driver when there's room for more data. If we have + * more packets to send, we send them here. + */ +static void ax25_write_wakeup(struct tty_struct *tty) +{ + int actual; + struct ax_disp *ax = (struct ax_disp *)tty->disc_data; + struct mkiss_channel *mkiss; + + /* First make sure we're connected. */ + if (ax == NULL || ax->magic != AX25_MAGIC || !ax->dev->start) + return; + if (ax->xleft <= 0) { + /* Now serial buffer is almost free & we can start + * transmission of another packet + */ + tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + + if (ax->mkiss != NULL) { + mkiss= ax->mkiss->tty->driver_data; + if (mkiss->magic == MKISS_DRIVER_MAGIC) + ax_unlock(ax->mkiss); + } + + ax_unlock(ax); + mark_bh(NET_BH); + return; + } + + actual = tty->driver.write(tty, 0, ax->xhead, ax->xleft); + ax->xleft -= actual; + ax->xhead += actual; +} + +/* Encapsulate an AX.25 packet and kick it into a TTY queue. */ +static int ax_xmit(struct sk_buff *skb, struct device *dev) +{ + struct ax_disp *ax = (struct ax_disp*)dev->priv; + struct mkiss_channel *mkiss = ax->tty->driver_data; + struct ax_disp *tmp_ax; + + tmp_ax = NULL; + + if (mkiss->magic == MKISS_DRIVER_MAGIC) { + if (skb->data[0] < 0x10) + skb->data[0] = skb->data[0] + 0x10; + tmp_ax = ax->mkiss; + } + + if (!dev->start) { + printk(KERN_ERR "mkiss: %s: xmit call when iface is down\n", dev->name); + return 1; + } + + if (tmp_ax != NULL) + if (tmp_ax->dev->tbusy) + return 1; + + if (tmp_ax != NULL) + if (dev->tbusy) { + printk(KERN_ERR "mkiss: dev busy while serial dev is free\n"); + ax_unlock(ax); + } + + if (dev->tbusy) { + /* + * May be we must check transmitter timeout here ? + * 14 Oct 1994 Dmitry Gorodchanin. + */ + if (jiffies - dev->trans_start < 20 * HZ) { + /* 20 sec timeout not reached */ + return 1; + } + + printk(KERN_ERR "mkiss: %s: transmit timed out, %s?\n", dev->name, + (ax->tty->driver.chars_in_buffer(ax->tty) || ax->xleft) ? + "bad line quality" : "driver error"); + + ax->xleft = 0; + ax->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + ax_unlock(ax); + } + + /* We were not busy, so we are now... :-) */ + if (skb != NULL) { + ax_lock(ax); + if (tmp_ax != NULL) + ax_lock(tmp_ax); + ax_encaps(ax, skb->data, skb->len); + kfree_skb(skb, FREE_WRITE); + } + + return 0; +} + +#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE) + +/* Return the frame type ID */ +static int ax_header(struct sk_buff *skb, struct device *dev, unsigned short type, + void *daddr, void *saddr, unsigned len) +{ +#ifdef CONFIG_INET + if (type != htons(ETH_P_AX25)) + return ax25_encapsulate(skb, dev, type, daddr, saddr, len); +#endif + return 0; +} + + +static int ax_rebuild_header(struct sk_buff *skb) +{ +#ifdef CONFIG_INET + return ax25_rebuild_header(skb); +#else + return 0; +#endif +} + +#endif /* CONFIG_{AX25,AX25_MODULE} */ + +/* Open the low-level part of the AX25 channel. Easy! */ +static int ax_open(struct device *dev) +{ + struct ax_disp *ax = (struct ax_disp*)dev->priv; + unsigned long len; + + if (ax->tty == NULL) + return -ENODEV; + + /* + * Allocate the frame buffers: + * + * rbuff Receive buffer. + * xbuff Transmit buffer. + * cbuff Temporary compression 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 */ + dev->tbusy = 0; + dev->start = 1; + + return 0; + + /* Cleanup */ + kfree(ax->xbuff); + +noxbuff: + kfree(ax->rbuff); + +norbuff: + return -ENOMEM; +} + + +/* Close the low-level part of the AX25 channel. Easy! */ +static int ax_close(struct device *dev) +{ + struct ax_disp *ax = (struct ax_disp*)dev->priv; + + if (ax->tty == NULL) + return -EBUSY; + + ax->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + + dev->tbusy = 1; + dev->start = 0; + + return 0; +} + +static int ax25_receive_room(struct tty_struct *tty) +{ + return 65536; /* We can handle an infinite amount of data. :-) */ +} + +/* + * 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 ax25_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) +{ + struct ax_disp *ax = (struct ax_disp *)tty->disc_data; + + if (ax == NULL || ax->magic != AX25_MAGIC || !ax->dev->start) + 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->rx_errors++; + cp++; + continue; + } + + kiss_unesc(ax, *cp++); + } +} + +static int ax25_open(struct tty_struct *tty) +{ + struct ax_disp *ax = (struct ax_disp *)tty->disc_data; + struct ax_disp *tmp_ax; + struct mkiss_channel *mkiss; + int err, cnt; + + /* First make sure we're not already connected. */ + if (ax && ax->magic == AX25_MAGIC) + return -EEXIST; + + /* OK. Find a free AX25 channel to use. */ + if ((ax = ax_alloc()) == NULL) + return -ENFILE; + + ax->tty = tty; + tty->disc_data = ax; + + ax->mkiss = NULL; + tmp_ax = NULL; + + if (tty->driver.flush_buffer) + tty->driver.flush_buffer(tty); + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + + /* Restore default settings */ + ax->dev->type = ARPHRD_AX25; + + /* Perform the low-level AX25 initialization. */ + if ((err = ax_open(ax->dev))) + return err; + + mkiss= ax->tty->driver_data; + + if (mkiss->magic == MKISS_DRIVER_MAGIC) { + for (cnt = 1; cnt < ax25_maxdev; cnt++) { + if (ax25_ctrls[cnt]) { + if (ax25_ctrls[cnt]->dev.start) { + if (ax == &ax25_ctrls[cnt]->ctrl) { + cnt--; + tmp_ax = &ax25_ctrls[cnt]->ctrl; + break; + } + } + } + } + } + + if (tmp_ax != NULL) { + ax->mkiss = tmp_ax; + tmp_ax->mkiss = ax; + } + + MOD_INC_USE_COUNT; + + /* Done. We have linked the TTY line to a channel. */ + return ax->dev->base_addr; +} + +static void ax25_close(struct tty_struct *tty) +{ + struct ax_disp *ax = (struct ax_disp *)tty->disc_data; + int mkiss ; + + /* First make sure we're connected. */ + if (ax == NULL || ax->magic != AX25_MAGIC) + return; + + mkiss = ax->mode; + if (ax->dev->flags & IFF_UP) + { + dev_lock_wait(); + dev_close(ax->dev); + dev_unlock_list(); + } + + tty->disc_data = 0; + ax->tty = NULL; + + /* VSV = very important to remove timers */ + ax_free(ax); + unregister_netdev(ax->dev); + + MOD_DEC_USE_COUNT; +} + + +static struct net_device_stats *ax_get_stats(struct device *dev) +{ + static struct net_device_stats stats; + struct ax_disp *ax = (struct ax_disp*)dev->priv; + + memset(&stats, 0, sizeof(struct net_device_stats)); + + stats.rx_packets = ax->rx_packets; + stats.tx_packets = ax->tx_packets; + stats.rx_dropped = ax->rx_dropped; + stats.tx_dropped = ax->tx_dropped; + stats.tx_errors = ax->tx_errors; + stats.rx_errors = ax->rx_errors; + stats.rx_over_errors = ax->rx_over_errors; + + return &stats; +} + + +/************************************************************************ + * STANDARD ENCAPSULATION * + ************************************************************************/ + +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; +} + +static void kiss_unesc(struct ax_disp *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; + } + + if (!test_bit(AXF_ERROR, &ax->flags)) { + if (ax->rcount < ax->buffsize) { + ax->rbuff[ax->rcount++] = s; + return; + } + + ax->rx_over_errors++; + set_bit(AXF_ERROR, &ax->flags); + } +} + + +int ax_set_mac_address(struct device *dev, void *addr) +{ + int err; + + if ((err = verify_area(VERIFY_READ, addr, AX25_ADDR_LEN)) != 0) + return err; + + /* addr is an AX.25 shifted ASCII mac address */ + copy_from_user(dev->dev_addr, addr, AX25_ADDR_LEN); + + return 0; +} + +static int ax_set_dev_mac_address(struct device *dev, void *addr) +{ + struct sockaddr *sa = addr; + + memcpy(dev->dev_addr, sa->sa_data, AX25_ADDR_LEN); + + return 0; +} + + +/* Perform I/O control on an active ax25 channel. */ +static int ax25_disp_ioctl(struct tty_struct *tty, void *file, int cmd, void *arg) +{ + struct ax_disp *ax = (struct ax_disp *)tty->disc_data; + int err; + unsigned int tmp; + + /* First make sure we're connected. */ + if (ax == NULL || ax->magic != AX25_MAGIC) + return -EINVAL; + + switch (cmd) { + case SIOCGIFNAME: + if ((err = verify_area(VERIFY_WRITE, arg, strlen(ax->dev->name) + 1)) != 0) + return err; + copy_to_user(arg, ax->dev->name, strlen(ax->dev->name) + 1); + return 0; + + case SIOCGIFENCAP: + if ((err = verify_area(VERIFY_WRITE, arg, sizeof(int))) != 0) + return err; + put_user(4, (int *)arg); + return 0; + + case SIOCSIFENCAP: + if ((err = verify_area(VERIFY_READ, arg, sizeof(int))) != 0) + return err; + get_user(tmp, (int *)arg); + ax->mode = tmp; + ax->dev->addr_len = AX25_ADDR_LEN; /* sizeof an AX.25 addr */ + ax->dev->hard_header_len = AX25_KISS_HEADER_LEN + AX25_MAX_HEADER_LEN + 3; + ax->dev->type = ARPHRD_AX25; + return 0; + + case SIOCSIFHWADDR: + return ax_set_mac_address(ax->dev, arg); + + default: + return -ENOIOCTLCMD; + } +} + +static int ax_open_dev(struct device *dev) +{ + struct ax_disp *ax = (struct ax_disp*)dev->priv; + + if (ax->tty==NULL) + return -ENODEV; + + return 0; +} + +/* Initialize AX25 control device -- register AX25 line discipline */ +__initfunc(int mkiss_init_ctrl_dev(void)) +{ + int status; + + if (ax25_maxdev < 4) ax25_maxdev = 4; /* Sanity */ + + if ((ax25_ctrls = kmalloc(sizeof(void*) * ax25_maxdev, GFP_KERNEL)) == NULL) { + printk(KERN_ERR "mkiss: Can't allocate ax25_ctrls[] array ! No mkiss available\n"); + return -ENOMEM; + } + + /* Clear the pointer array, we allocate devices when we need them */ + memset(ax25_ctrls, 0, sizeof(void*) * ax25_maxdev); /* Pointers */ + + /* Fill in our line protocol discipline, and register it */ + memset(&ax_ldisc, 0, sizeof(ax_ldisc)); + ax_ldisc.magic = TTY_LDISC_MAGIC; + ax_ldisc.name = "mkiss"; + ax_ldisc.flags = 0; + ax_ldisc.open = ax25_open; + ax_ldisc.close = ax25_close; + ax_ldisc.read = NULL; + ax_ldisc.write = NULL; + ax_ldisc.ioctl = (int (*)(struct tty_struct *, struct file *, unsigned int, unsigned long))ax25_disp_ioctl; + ax_ldisc.poll = NULL; + + ax_ldisc.receive_buf = ax25_receive_buf; + ax_ldisc.receive_room = ax25_receive_room; + ax_ldisc.write_wakeup = ax25_write_wakeup; + + if ((status = tty_register_ldisc(N_AX25, &ax_ldisc)) != 0) + printk(KERN_ERR "mkiss: can't register line discipline (err = %d)\n", status); + + mkiss_init(); + +#ifdef MODULE + return status; +#else + /* + * Return "not found", so that dev_init() will unlink + * the placeholder device entry for us. + */ + return ENODEV; +#endif +} + + +/* Initialize the driver. Called by network startup. */ + +static int ax25_init(struct device *dev) +{ + struct ax_disp *ax = (struct ax_disp*)dev->priv; + + static char ax25_bcast[AX25_ADDR_LEN] = + {'Q'<<1,'S'<<1,'T'<<1,' '<<1,' '<<1,' '<<1,'0'<<1}; + static char ax25_test[AX25_ADDR_LEN] = + {'L'<<1,'I'<<1,'N'<<1,'U'<<1,'X'<<1,' '<<1,'1'<<1}; + + if (ax == NULL) /* Allocation failed ?? */ + return -ENODEV; + + /* Set up the "AX25 Control Block". (And clear statistics) */ + memset(ax, 0, sizeof (struct ax_disp)); + ax->magic = AX25_MAGIC; + ax->dev = dev; + + /* Finish setting up the DEVICE info. */ + dev->mtu = AX_MTU; + dev->hard_start_xmit = ax_xmit; + dev->open = ax_open_dev; + dev->stop = ax_close; + dev->get_stats = ax_get_stats; +#ifdef HAVE_SET_MAC_ADDR + dev->set_mac_address = ax_set_dev_mac_address; +#endif + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->type = ARPHRD_AX25; + dev->tx_queue_len = 10; + + memcpy(dev->broadcast, ax25_bcast, AX25_ADDR_LEN); + memcpy(dev->dev_addr, ax25_test, AX25_ADDR_LEN); + +#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE) + dev->hard_header = ax_header; + dev->rebuild_header = ax_rebuild_header; +#endif + + dev_init_buffers(dev); + + /* New-style flags. */ + dev->flags = 0; + + return 0; +} + +static int mkiss_open(struct tty_struct *tty, struct file *filp) +{ + struct mkiss_channel *mkiss; + int chan; + + chan = MINOR(tty->device) - tty->driver.minor_start; + + if (chan < 0 || chan >= NR_MKISS) + return -ENODEV; + + mkiss = &MKISS_Info[chan]; + + mkiss->magic = MKISS_DRIVER_MAGIC; + mkiss->init = 1; + mkiss->tty = tty; + + tty->driver_data = mkiss; + + tty->termios->c_iflag = IGNBRK | IGNPAR; + tty->termios->c_cflag = B9600 | CS8 | CLOCAL; + tty->termios->c_cflag &= ~CBAUD; + + return 0; +} + +static void mkiss_close(struct tty_struct *tty, struct file * filp) +{ + struct mkiss_channel *mkiss = tty->driver_data; + + if (mkiss == NULL || mkiss->magic != MKISS_DRIVER_MAGIC) + return; + + mkiss->tty = NULL; + mkiss->init = 0; + tty->stopped = 0; +} + +static int mkiss_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) +{ + return 0; +} + +static int mkiss_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) +{ + /* Ignore serial ioctl's */ + switch (cmd) { + case TCSBRK: + case TIOCMGET: + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + case TCSETS: + case TCSETSF: /* should flush first, but... */ + case TCSETSW: /* should wait until flush, but... */ + return 0; + default: + return -ENOIOCTLCMD; + } +} + + +static void mkiss_dummy(struct tty_struct *tty) +{ + struct mkiss_channel *mkiss = tty->driver_data; + + if (tty == NULL) + return; + + if (mkiss == NULL) + return; +} + +static void mkiss_dummy2(struct tty_struct *tty, unsigned char ch) +{ + struct mkiss_channel *mkiss = tty->driver_data; + + if (tty == NULL) + return; + + if (mkiss == NULL) + return; +} + + +static int mkiss_write_room(struct tty_struct * tty) +{ + struct mkiss_channel *mkiss = tty->driver_data; + + if (tty == NULL) + return 0; + + if (mkiss == NULL) + return 0; + + return 65536; /* We can handle an infinite amount of data. :-) */ +} + + +static int mkiss_chars_in_buffer(struct tty_struct *tty) +{ + struct mkiss_channel *mkiss = tty->driver_data; + + if (tty == NULL) + return 0; + + if (mkiss == NULL) + return 0; + + return 0; +} + + +static void mkiss_set_termios(struct tty_struct *tty, struct termios *old_termios) +{ + /* we don't do termios */ +} + +/* ******************************************************************** */ +/* * Init MKISS driver * */ +/* ******************************************************************** */ + +__initfunc(static int mkiss_init(void)) +{ + memset(&mkiss_driver, 0, sizeof(struct tty_driver)); + + mkiss_driver.magic = MKISS_DRIVER_MAGIC; + mkiss_driver.name = "mkiss"; + mkiss_driver.major = MKISS_MAJOR; + mkiss_driver.minor_start = 0; + mkiss_driver.num = NR_MKISS; + mkiss_driver.type = TTY_DRIVER_TYPE_SERIAL; + mkiss_driver.subtype = MKISS_SERIAL_TYPE_NORMAL; /* not needed */ + + mkiss_driver.init_termios = tty_std_termios; + mkiss_driver.init_termios.c_iflag = IGNBRK | IGNPAR; + mkiss_driver.init_termios.c_cflag = B9600 | CS8 | CLOCAL; + + mkiss_driver.flags = TTY_DRIVER_REAL_RAW; + mkiss_driver.refcount = &mkiss_refcount; + mkiss_driver.table = mkiss_table; + mkiss_driver.termios = (struct termios **)mkiss_termios; + mkiss_driver.termios_locked = (struct termios **)mkiss_termios_locked; + + mkiss_driver.ioctl = mkiss_ioctl; + mkiss_driver.open = mkiss_open; + mkiss_driver.close = mkiss_close; + mkiss_driver.write = mkiss_write; + mkiss_driver.write_room = mkiss_write_room; + mkiss_driver.chars_in_buffer = mkiss_chars_in_buffer; + mkiss_driver.set_termios = mkiss_set_termios; + + /* some unused functions */ + mkiss_driver.flush_buffer = mkiss_dummy; + mkiss_driver.throttle = mkiss_dummy; + mkiss_driver.unthrottle = mkiss_dummy; + mkiss_driver.stop = mkiss_dummy; + mkiss_driver.start = mkiss_dummy; + mkiss_driver.hangup = mkiss_dummy; + mkiss_driver.flush_chars = mkiss_dummy; + mkiss_driver.put_char = mkiss_dummy2; + + if (tty_register_driver(&mkiss_driver)) { + printk(KERN_ERR "mkiss: couldn't register Mkiss device\n"); + return -EIO; + } + + printk(KERN_INFO "AX.25 Multikiss device enabled\n"); + + return 0; +} + +#ifdef MODULE +EXPORT_NO_SYMBOLS; + +MODULE_PARM(ax25_maxdev, "i"); +MODULE_PARM_DESC(ax25_maxdev, "number of MKISS devices"); + +MODULE_AUTHOR("Hans Albas PE1AYX <hans@esrac.ele.tue.nl>"); +MODULE_DESCRIPTION("KISS driver for AX.25 over TTYs"); + +int init_module(void) +{ + return mkiss_init_ctrl_dev(); +} + +void cleanup_module(void) +{ + int i; + + if (ax25_ctrls != NULL) { + for (i = 0; i < ax25_maxdev; i++) { + if (ax25_ctrls[i]) { + /* + * VSV = if dev->start==0, then device + * unregistred while close proc. + */ + if (ax25_ctrls[i]->dev.start) + unregister_netdev(&(ax25_ctrls[i]->dev)); + + kfree(ax25_ctrls[i]); + ax25_ctrls[i] = NULL; + } + } + + kfree(ax25_ctrls); + ax25_ctrls = NULL; + } + + if ((i = tty_register_ldisc(N_AX25, NULL))) + printk(KERN_ERR "mkiss: can't unregister line discipline (err = %d)\n", i); + + if (tty_unregister_driver(&mkiss_driver)) /* remove devive */ + printk(KERN_ERR "mkiss: can't unregister MKISS device\n"); +} + +#endif /* MODULE */ diff --git a/drivers/net/hamradio/mkiss.h b/drivers/net/hamradio/mkiss.h new file mode 100644 index 000000000..0e32aa82b --- /dev/null +++ b/drivers/net/hamradio/mkiss.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * Defines for the Multi-KISS driver. + ****************************************************************************/ + +#define AX25_MAXDEV 16 /* MAX number of AX25 channels; + This can be overridden with + insmod -oax25_maxdev=nnn */ +#define AX_MTU 236 + +/* SLIP/KISS protocol characters. */ +#define END 0300 /* indicates end of frame */ +#define ESC 0333 /* indicates byte stuffing */ +#define ESC_END 0334 /* ESC ESC_END means END 'data' */ +#define ESC_ESC 0335 /* ESC ESC_ESC means ESC 'data' */ + +struct ax_disp { + int magic; + + /* Various fields. */ + struct tty_struct *tty; /* ptr to TTY structure */ + struct device *dev; /* easy for intr handling */ + struct ax_disp *mkiss; /* mkiss txport if mkiss channel*/ + + /* 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; /* pointer to next byte to XMIT */ + int xleft; /* bytes left in XMIT queue */ + + /* SLIP interface statistics. */ + unsigned long rx_packets; /* inbound frames counter */ + unsigned long tx_packets; /* outbound frames counter */ + unsigned long rx_errors; /* Parity, etc. errors */ + unsigned long tx_errors; /* Planned stuff */ + unsigned long rx_dropped; /* No memory for skb */ + unsigned long tx_dropped; /* When MTU change */ + unsigned long rx_over_errors; /* Frame bigger then SLIP buf. */ + + /* Detailed SLIP statistics. */ + int mtu; /* Our mtu (to spot changes!) */ + int buffsize; /* Max buffers sizes */ + + + unsigned char flags; /* Flag values/ mode etc */ +#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; +}; + +#define AX25_MAGIC 0x5316 +#define MKISS_DRIVER_MAGIC 1215 diff --git a/drivers/net/hamradio/pi2.c b/drivers/net/hamradio/pi2.c new file mode 100644 index 000000000..3eb3a9223 --- /dev/null +++ b/drivers/net/hamradio/pi2.c @@ -0,0 +1,1677 @@ +/* + pi2.c: Driver for the Ottawa Amateur Radio Club PI and PI2 interface. + Copyright (c) 1994 David Perry + + This program is free software; you can redistribute 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 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. + + The file skeleton.c by Donald Becker was used as a starting point + for this driver. + + Revision History + + April 6, 1994 (dp) Created + version 0.0 ALPHA + April 10, 1994 (dp) Included cleanup, suggestions from J. P. Morrison. + version 0.1 ALPHA + April 13, 1994 (dp) Included address probing from JPM, autoirq + version 0.2 ALPHA + April 14, 1994 (ac) Sketched in the NET3 changes. + April 17, 1994 (dp) Finished the NET3 changes. Used init_etherdev() + instead of kmalloc() to ensure that DMA buffers will + reside under the 16 meg line. + version 0.4 ALPHA + April 18, 1994 (dp) Now using the kernel provided sk_buff handling functions. + Fixed a nasty problem with DMA. + version 0.5 ALPHA + June 6, 1994 (ac) Fixed to match the buffer locking changes. Added a hack to + fix a funny I see (search for HACK) and fixed the calls in + init() so it doesn't migrate module based ethernet cards up + to eth2 Took out the old module ideas as they are no longer + relevant to the PI driver. + July 16, 1994 (dp) Fixed the B channel rx overrun problem ac referred to + above. Also added a bit of a hack to improve the maximum + baud rate on the B channel (Search for STUFF2). Included + ioctl stuff from John Paul Morrison. version 0.6 ALPHA + Feb 9, 1995 (dp) Updated for 1.1.90 kernel + version 0.7 ALPHA + Apr 6, 1995 (ac) Tweaks for NET3 pre snapshot 002 AX.25 + April 23, 1995 (dp) Fixed ioctl so it works properly with piconfig program + when changing the baud rate or clock mode. + version 0.8 ALPHA + July 17, 1995 (ac) Finally polishing of AX25.030+ support + Oct 29, 1995 (ac) A couple of minor fixes before this, and this release changes + to the proper set_mac_address semantics which will break + a few programs I suspect. + Aug 18, 1996 (jsn) Converted to be used as a module. + Dec 13, 1996 (jsn) Fixed to match Linux networking changes. +*/ + +/* The following #define invokes a hack that will improve performance (baud) + for the B port. The up side is it makes 9600 baud work ok on the B port. + It may do 38400, depending on the host. The down side is it burns up + CPU cycles with ints locked for up to 1 character time, at the beginning + of each transmitted packet. If this causes you to lose sleep, #undefine it. +*/ + +/*#define STUFF2 1*/ + +/* The default configuration */ +#define PI_DMA 3 + +#define DEF_A_SPEED 0 /* 0 means external clock */ +#define DEF_A_TXDELAY 15 /* 15 mS transmit delay */ +#define DEF_A_PERSIST 128 /* 50% persistence */ +#define DEF_A_SLOTIME 15 /* 15 mS slot time */ +#define DEF_A_SQUELDELAY 1 /* 1 mS squelch delay - allows fcs and flag */ +#define DEF_A_CLOCKMODE 0 /* clock mode - 0 is normal */ + +#define DEF_B_SPEED 1200 /* 1200 baud */ +#define DEF_B_TXDELAY 40 /* 400 mS */ +#define DEF_B_PERSIST 128 /* 50% */ +#define DEF_B_SLOTIME 30 /* 300 mS */ +#define DEF_B_SQUELDELAY 3 /* 30 mS */ +#define DEF_B_CLOCKMODE 0 /* Normal clock mode */ + +/* The following #define is only really required for the PI card, not + the PI2 - but it's safer to leave it in. */ +#define REALLY_SLOW_IO 1 + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/uaccess.h> +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/timer.h> +#include <linux/if_arp.h> +#include <linux/pi2.h> +#include <linux/init.h> +#include "z8530.h" +#include <net/ax25.h> + +struct mbuf { + struct mbuf *next; + int cnt; + char data[0]; +}; + +/* + * The actual devices we will use + */ + +/* + * PI device declarations. + */ + +static int pi0_preprobe(struct device *dev){return 0;} /* Dummy probe function */ +static struct device pi0a = { "pi0a", 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, pi0_preprobe }; +static struct device pi0b = { "pi0b", 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, pi0_preprobe }; + + +/* The number of low I/O ports used by the card. */ +#define PI_TOTAL_SIZE 8 + + +/* Index to functions, as function prototypes. */ + +static int pi_probe(struct device *dev, int card_type); +static int pi_open(struct device *dev); +static int pi_send_packet(struct sk_buff *skb, struct device *dev); +static void pi_interrupt(int reg_ptr, void *dev_id, struct pt_regs *regs); +static int pi_close(struct device *dev); +static int pi_ioctl(struct device *dev, struct ifreq *ifr, int cmd); +static struct net_device_stats *pi_get_stats(struct device *dev); +static void rts(struct pi_local *lp, int x); +static void b_rxint(struct device *dev, struct pi_local *lp); +static void b_txint(struct pi_local *lp); +static void b_exint(struct pi_local *lp); +static void a_rxint(struct device *dev, struct pi_local *lp); +static void a_txint(struct pi_local *lp); +static void a_exint(struct pi_local *lp); +static char *get_dma_buffer(unsigned long *mem_ptr); +static int valid_dma_page(unsigned long addr, unsigned long dev_buffsize); + +static char ax25_bcast[7] = +{'Q' << 1, 'S' << 1, 'T' << 1, ' ' << 1, ' ' << 1, ' ' << 1, '0' << 1}; +static char ax25_test[7] = +{'L' << 1, 'I' << 1, 'N' << 1, 'U' << 1, 'X' << 1, ' ' << 1, '1' << 1}; + +static int ext2_secrm_seed = 152; /* Random generator base */ + +extern inline unsigned char random(void) +{ + return (unsigned char) (ext2_secrm_seed = ext2_secrm_seed + * 69069l + 1); +} + +extern inline void wrtscc(int cbase, int ctl, int sccreg, int val) +{ + /* assume caller disables interrupts! */ + outb_p(0, cbase + DMAEN); /* Disable DMA while we touch the scc */ + outb_p(sccreg, ctl); /* Select register */ + outb_p(val, ctl); /* Output value */ + outb_p(1, cbase + DMAEN); /* Enable DMA */ +} + +extern inline int rdscc(int cbase, int ctl, int sccreg) +{ + int retval; + + /* assume caller disables interrupts! */ + outb_p(0, cbase + DMAEN); /* Disable DMA while we touch the scc */ + outb_p(sccreg, ctl); /* Select register */ + retval = inb_p(ctl); + outb_p(1, cbase + DMAEN); /* Enable DMA */ + return retval; +} + +static void switchbuffers(struct pi_local *lp) +{ + if (lp->rcvbuf == lp->rxdmabuf1) + lp->rcvbuf = lp->rxdmabuf2; + else + lp->rcvbuf = lp->rxdmabuf1; +} + +static void hardware_send_packet(struct pi_local *lp, struct sk_buff *skb) +{ + char kickflag; + unsigned long flags; + + lp->stats.tx_packets++; + + save_flags(flags); + cli(); + kickflag = (skb_peek(&lp->sndq) == NULL) && (lp->sndbuf == NULL); + restore_flags(flags); + + skb_queue_tail(&lp->sndq, skb); + if (kickflag) + { + /* simulate interrupt to xmit */ + switch (lp->base & 2) + { + case 2: + a_txint(lp); /* process interrupt */ + break; + case 0: + save_flags(flags); + cli(); + if (lp->tstate == IDLE) + b_txint(lp); + restore_flags(flags); + break; + } + } +} + +static void setup_rx_dma(struct pi_local *lp) +{ + unsigned long flags; + int cmd; + unsigned long dma_abs; + unsigned dmachan; + + save_flags(flags); + cli(); + + dma_abs = (unsigned long) (lp->rcvbuf->data); + dmachan = lp->dmachan; + cmd = lp->base + CTL; + + if(!valid_dma_page(dma_abs, DMA_BUFF_SIZE + sizeof(struct mbuf))) + panic("PI: RX buffer violates DMA boundary!"); + + /* Get ready for RX DMA */ + wrtscc(lp->cardbase, cmd, R1, WT_FN_RDYFN | WT_RDY_RT | INT_ERR_Rx | EXT_INT_ENAB); + + disable_dma(dmachan); + clear_dma_ff(dmachan); + + /* Set DMA mode register to single transfers, incrementing address, + * auto init, writes + */ + set_dma_mode(dmachan, DMA_MODE_READ | 0x10); + set_dma_addr(dmachan, dma_abs); + set_dma_count(dmachan, lp->bufsiz); + enable_dma(dmachan); + + /* If a packet is already coming in, this line is supposed to + avoid receiving a partial packet. + */ + wrtscc(lp->cardbase, cmd, R0, RES_Rx_CRC); + + /* Enable RX dma */ + wrtscc(lp->cardbase, cmd, R1, + WT_RDY_ENAB | WT_FN_RDYFN | WT_RDY_RT | INT_ERR_Rx | EXT_INT_ENAB); + + restore_flags(flags); +} + +static void setup_tx_dma(struct pi_local *lp, int length) +{ + unsigned long dma_abs; + unsigned long flags; + unsigned long dmachan; + + save_flags(flags); + cli(); + + dmachan = lp->dmachan; + dma_abs = (unsigned long) (lp->txdmabuf); + + if(!valid_dma_page(dma_abs, DMA_BUFF_SIZE + sizeof(struct mbuf))) + panic("PI: TX buffer violates DMA boundary!"); + + disable_dma(dmachan); + /* Set DMA mode register to single transfers, incrementing address, + * no auto init, reads + */ + set_dma_mode(dmachan, DMA_MODE_WRITE); + clear_dma_ff(dmachan); + set_dma_addr(dmachan, dma_abs); + /* output byte count */ + set_dma_count(dmachan, length); + + restore_flags(flags); +} + +static void tdelay(struct pi_local *lp, int time) +{ + int port; + unsigned int t1; + unsigned char sc; + + if (lp->base & 2) { /* If A channel */ + sc = SC1; + t1 = time; + port = lp->cardbase + TMR1; + } else { + sc = SC2; + t1 = 10 * time; /* 10s of milliseconds for the B channel */ + port = lp->cardbase + TMR2; + wrtscc(lp->cardbase, lp->base + CTL, R1, INT_ALL_Rx | EXT_INT_ENAB); + } + + /* Setup timer sc */ + outb_p(sc | LSB_MSB | MODE0, lp->cardbase + TMRCMD); + + /* times 2 to make millisecs */ + outb_p((t1 << 1) & 0xFF, port); + outb_p((t1 >> 7) & 0xFF, port); + + /* Enable correct int for timeout */ + wrtscc(lp->cardbase, lp->base + CTL, R15, CTSIE); + wrtscc(lp->cardbase, lp->base + CTL, R0, RES_EXT_INT); +} + +static void a_txint(struct pi_local *lp) +{ + int cmd; + unsigned long flags; + + save_flags(flags); + cli(); + + cmd = CTL + lp->base; + + switch (lp->tstate) { + case IDLE: + /* Transmitter idle. Find a frame for transmission */ + if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) { + rts(lp, OFF); + restore_flags(flags); + return; + } + /* If a buffer to send, we drop thru here */ + case DEFER: + /* we may have deferred prev xmit attempt */ + /* Check DCD - debounce it + * See Intel Microcommunications Handbook, p2-308 + */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) { + lp->tstate = DEFER; + tdelay(lp, 100); + /* defer until DCD transition or timeout */ + wrtscc(lp->cardbase, cmd, R15, CTSIE | DCDIE); + restore_flags(flags); + return; + } + if (random() > lp->persist) { + lp->tstate = DEFER; + tdelay(lp, lp->slotime); + restore_flags(flags); + return; + } + /* Assert RTS early minimize collision window */ + wrtscc(lp->cardbase, cmd, R5, TxCRC_ENAB | RTS | Tx8); + rts(lp, ON); /* Transmitter on */ + lp->tstate = ST_TXDELAY; + tdelay(lp, lp->txdelay); + restore_flags(flags); + return; + default: + break; + } /* end switch(lp->state) */ + + restore_flags(flags); +} /*a_txint */ + +static void a_exint(struct pi_local *lp) +{ + unsigned long flags; + int cmd; + char st; + int length; + + save_flags(flags); + cli(); /* disable interrupts */ + + st = rdscc(lp->cardbase, lp->base + CTL, R0); /* Fetch status */ + + /* reset external status latch */ + wrtscc(lp->cardbase, CTL + lp->base, R0, RES_EXT_INT); + cmd = lp->base + CTL; + + if ((lp->rstate >= ACTIVE) && (st & BRK_ABRT)) { + setup_rx_dma(lp); + lp->rstate = ACTIVE; + } + switch (lp->tstate) { + case ACTIVE: + kfree_skb(lp->sndbuf, FREE_WRITE); + lp->sndbuf = NULL; + lp->tstate = FLAGOUT; + tdelay(lp, lp->squeldelay); + break; + case FLAGOUT: + if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) { + /* Nothing to send - return to receive mode */ + lp->tstate = IDLE; + rts(lp, OFF); + restore_flags(flags); + return; + } + /* NOTE - fall through if more to send */ + case ST_TXDELAY: + /* Disable DMA chan */ + disable_dma(lp->dmachan); + + /* Set up for TX dma */ + wrtscc(lp->cardbase, cmd, R1, WT_FN_RDYFN | EXT_INT_ENAB); + + + /* Get all chars */ + /* Strip KISS control byte */ + length = lp->sndbuf->len - 1; + memcpy(lp->txdmabuf, &lp->sndbuf->data[1], length); + + + /* Setup DMA controller for tx */ + setup_tx_dma(lp, length); + + /* select transmit interrupts to enable */ + /* Allow DMA on chan */ + enable_dma(lp->dmachan); + + /* reset CRC, Txint pend*/ + wrtscc(lp->cardbase, cmd, R0, RES_Tx_CRC | RES_Tx_P); + + /* allow Underrun int only */ + wrtscc(lp->cardbase, cmd, R15, TxUIE); + + /* Enable TX DMA */ + wrtscc(lp->cardbase, cmd, R1, WT_RDY_ENAB | WT_FN_RDYFN | EXT_INT_ENAB); + + /* Send CRC on underrun */ + wrtscc(lp->cardbase, cmd, R0, RES_EOM_L); + + + /* packet going out now */ + lp->tstate = ACTIVE; + break; + case DEFER: + /* we have deferred prev xmit attempt + * See Intel Microcommunications Handbook, p2-308 + */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) { + lp->tstate = DEFER; + tdelay(lp, 100); + /* Defer until dcd transition or 100mS timeout */ + wrtscc(lp->cardbase, CTL + lp->base, R15, CTSIE | DCDIE); + restore_flags(flags); + return; + } + if (random() > lp->persist) { + lp->tstate = DEFER; + tdelay(lp, lp->slotime); + restore_flags(flags); + return; + } + /* Assert RTS early minimize collision window */ + wrtscc(lp->cardbase, cmd, R5, TxCRC_ENAB | RTS | Tx8); + rts(lp, ON); /* Transmitter on */ + lp->tstate = ST_TXDELAY; + tdelay(lp, lp->txdelay); + restore_flags(flags); + return; + } /* switch(lp->tstate) */ + + restore_flags(flags); +} /* a_exint() */ + +/* Receive interrupt handler for the A channel + */ +static void a_rxint(struct device *dev, struct pi_local *lp) +{ + unsigned long flags; + int cmd; + int bytecount; + char rse; + struct sk_buff *skb; + int sksize, pkt_len; + struct mbuf *cur_buf; + unsigned char *cfix; + + save_flags(flags); + cli(); /* disable interrupts */ + cmd = lp->base + CTL; + + rse = rdscc(lp->cardbase, cmd, R1); /* Get special condition bits from R1 */ + if (rse & Rx_OVR) + lp->rstate = RXERROR; + + if (rse & END_FR) { + /* If end of frame */ + /* figure length of frame from 8237 */ + clear_dma_ff(lp->dmachan); + bytecount = lp->bufsiz - get_dma_residue(lp->dmachan); + + if ((rse & CRC_ERR) || (lp->rstate > ACTIVE) || (bytecount < 10)) { + if ((bytecount >= 10) && (rse & CRC_ERR)) { + lp->stats.rx_crc_errors++; + } + if (lp->rstate == RXERROR) { + lp->stats.rx_errors++; + lp->stats.rx_over_errors++; + } + /* Reset buffer pointers */ + lp->rstate = ACTIVE; + setup_rx_dma(lp); + } else { + /* Here we have a valid frame */ + /* Toss 2 crc bytes , add one for KISS */ + pkt_len = lp->rcvbuf->cnt = bytecount - 2 + 1; + + /* Get buffer for next frame */ + cur_buf = lp->rcvbuf; + switchbuffers(lp); + setup_rx_dma(lp); + + + /* Malloc up new buffer. */ + sksize = pkt_len; + + skb = dev_alloc_skb(sksize); + if (skb == NULL) { + printk(KERN_ERR "PI: %s: Memory squeeze, dropping packet.\n", dev->name); + lp->stats.rx_dropped++; + restore_flags(flags); + return; + } + skb->dev = dev; + + /* KISS kludge - prefix with a 0 byte */ + cfix=skb_put(skb,pkt_len); + *cfix++=0; + /* 'skb->data' points to the start of sk_buff data area. */ + memcpy(cfix, (char *) cur_buf->data, + pkt_len - 1); + skb->protocol=htons(ETH_P_AX25); + skb->mac.raw=skb->data; + netif_rx(skb); + lp->stats.rx_packets++; + } /* end good frame */ + } /* end EOF check */ + wrtscc(lp->cardbase, lp->base + CTL, R0, ERR_RES); /* error reset */ + restore_flags(flags); +} + +static void b_rxint(struct device *dev, struct pi_local *lp) +{ + unsigned long flags; + int cmd; + char rse; + struct sk_buff *skb; + int sksize; + int pkt_len; + unsigned char *cfix; + + save_flags(flags); + cli(); /* disable interrupts */ + cmd = CTL + lp->base; + + rse = rdscc(lp->cardbase, cmd, R1); /* get status byte from R1 */ + + if ((rdscc(lp->cardbase, cmd, R0)) & Rx_CH_AV) { + /* there is a char to be stored + * read special condition bits before reading the data char + */ + if (rse & Rx_OVR) { + /* Rx overrun - toss buffer */ + /* reset buffer pointers */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + + lp->rstate = RXERROR; /* set error flag */ + lp->stats.rx_errors++; + lp->stats.rx_over_errors++; + } else if (lp->rcvbuf->cnt >= lp->bufsiz) { + /* Too large -- toss buffer */ + /* reset buffer pointers */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + lp->rstate = TOOBIG;/* when set, chars are not stored */ + } + /* ok, we can store the received character now */ + if (lp->rstate == ACTIVE) { /* If no errors... */ + *lp->rcp++ = rdscc(lp->cardbase, cmd, R8); /* char to rcv buff */ + lp->rcvbuf->cnt++; /* bump count */ + } else { + /* got to empty FIFO */ + (void) rdscc(lp->cardbase, cmd, R8); + wrtscc(lp->cardbase, cmd, R0, ERR_RES); /* reset err latch */ + lp->rstate = ACTIVE; + } + } + if (rse & END_FR) { + /* END OF FRAME -- Make sure Rx was active */ + if (lp->rcvbuf->cnt > 0) { + if ((rse & CRC_ERR) || (lp->rstate > ACTIVE) || (lp->rcvbuf->cnt < 10)) { + if ((lp->rcvbuf->cnt >= 10) && (rse & CRC_ERR)) { + lp->stats.rx_crc_errors++; + } + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + } else { + /* Here we have a valid frame */ + pkt_len = lp->rcvbuf->cnt -= 2; /* Toss 2 crc bytes */ + pkt_len += 1; /* Make room for KISS control byte */ + + /* Malloc up new buffer. */ + sksize = pkt_len; + skb = dev_alloc_skb(sksize); + if (skb == NULL) { + printk(KERN_ERR "PI: %s: Memory squeeze, dropping packet.\n", dev->name); + lp->stats.rx_dropped++; + restore_flags(flags); + return; + } + skb->dev = dev; + + /* KISS kludge - prefix with a 0 byte */ + cfix=skb_put(skb,pkt_len); + *cfix++=0; + /* 'skb->data' points to the start of sk_buff data area. */ + memcpy(cfix, lp->rcvbuf->data, pkt_len - 1); + skb->protocol=ntohs(ETH_P_AX25); + skb->mac.raw=skb->data; + netif_rx(skb); + lp->stats.rx_packets++; + /* packet queued - initialize buffer for next frame */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + + } /* end good frame queued */ + } /* end check for active receive upon EOF */ + lp->rstate = ACTIVE; /* and clear error status */ + } /* end EOF check */ + restore_flags(flags); +} + + +static void b_txint(struct pi_local *lp) +{ + unsigned long flags; + int cmd; + unsigned char c; + + save_flags(flags); + cli(); + cmd = CTL + lp->base; + + switch (lp->tstate) { + case CRCOUT: + lp->tstate = FLAGOUT; + tdelay(lp, lp->squeldelay); + restore_flags(flags); + return; + case IDLE: + /* Transmitter idle. Find a frame for transmission */ + if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) { + /* Nothing to send - return to receive mode + * Tx OFF now - flag should have gone + */ + rts(lp, OFF); + + restore_flags(flags); + return; + } + lp->txptr = lp->sndbuf->data; + lp->txptr++; /* Ignore KISS control byte */ + lp->txcnt = (int) lp->sndbuf->len - 1; + /* If a buffer to send, we drop thru here */ + case DEFER: /* we may have deferred prev xmit attempt */ + /* Check DCD - debounce it */ + /* See Intel Microcommunications Handbook, p2-308 */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) { + lp->tstate = DEFER; + tdelay(lp, 100); + /* defer until DCD transition or timeout */ + wrtscc(lp->cardbase, cmd, R15, CTSIE | DCDIE); + restore_flags(flags); + return; + } + if (random() > lp->persist) { + lp->tstate = DEFER; + tdelay(lp, lp->slotime); + restore_flags(flags); + return; + } + rts(lp, ON); /* Transmitter on */ + lp->tstate = ST_TXDELAY; + tdelay(lp, lp->txdelay); + restore_flags(flags); + return; + + case ACTIVE: + /* Here we are actively sending a frame */ + if (lp->txcnt--) { + c = *lp->txptr++; + /* next char is gone */ + wrtscc(lp->cardbase, cmd, R8, c); + /* stuffing a char satisfies Interrupt condition */ + } else { + /* No more to send */ + kfree_skb(lp->sndbuf, FREE_WRITE); + lp->sndbuf = NULL; + if ((rdscc(lp->cardbase, cmd, R0) & 0x40)) { + /* Did we underrun? */ + /* unexpected underrun */ + lp->stats.tx_errors++; + lp->stats.tx_fifo_errors++; + wrtscc(lp->cardbase, cmd, R0, SEND_ABORT); + lp->tstate = FLAGOUT; + tdelay(lp, lp->squeldelay); + restore_flags(flags); + return; + } + lp->tstate = UNDERRUN; /* Now we expect to underrun */ + /* Send flags on underrun */ + if (lp->speed) { /* If internally clocked */ + wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI); + } else { + wrtscc(lp->cardbase, cmd, R10, CRCPS); + } + wrtscc(lp->cardbase, cmd, R0, RES_Tx_P); /* reset Tx Int Pend */ + } + restore_flags(flags); + return; /* back to wait for interrupt */ + } /* end switch */ + restore_flags(flags); +} + +/* Pi SIO External/Status interrupts (for the B channel) + * This can be caused by a receiver abort, or a Tx UNDERRUN/EOM. + * Receiver automatically goes to Hunt on an abort. + * + * If the Tx Underrun interrupt hits, change state and + * issue a reset command for it, and return. + */ +static void b_exint(struct pi_local *lp) +{ + unsigned long flags; + char st; + int cmd; + char c; + + cmd = CTL + lp->base; + save_flags(flags); + cli(); /* disable interrupts */ + st = rdscc(lp->cardbase, cmd, R0); /* Fetch status */ + /* reset external status latch */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + + + switch (lp->tstate) { + case ACTIVE: /* Unexpected underrun */ + kfree_skb(lp->sndbuf, FREE_WRITE); + lp->sndbuf = NULL; + wrtscc(lp->cardbase, cmd, R0, SEND_ABORT); + lp->tstate = FLAGOUT; + lp->stats.tx_errors++; + lp->stats.tx_fifo_errors++; + tdelay(lp, lp->squeldelay); + restore_flags(flags); + return; + case UNDERRUN: + lp->tstate = CRCOUT; + restore_flags(flags); + return; + case FLAGOUT: + /* Find a frame for transmission */ + if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) { + /* Nothing to send - return to receive mode + * Tx OFF now - flag should have gone + */ + rts(lp, OFF); + lp->tstate = IDLE; + restore_flags(flags); + return; + } + lp->txptr = lp->sndbuf->data; + lp->txptr++; /* Ignore KISS control byte */ + lp->txcnt = (int) lp->sndbuf->len - 1; + /* Get first char to send */ + lp->txcnt--; + c = *lp->txptr++; + wrtscc(lp->cardbase, cmd, R0, RES_Tx_CRC); /* reset for next frame */ + + /* Send abort on underrun */ + if (lp->speed) { /* If internally clocked */ + wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI | ABUNDER); + } else { + wrtscc(lp->cardbase, cmd, R10, CRCPS | ABUNDER); + } + + wrtscc(lp->cardbase, cmd, R8, c); /* First char out now */ + wrtscc(lp->cardbase, cmd, R0, RES_EOM_L); /* Reset end of message latch */ + +#ifdef STUFF2 + /* stuff an extra one if we can */ + if (lp->txcnt) { + lp->txcnt--; + c = *lp->txptr++; + /* Wait for tx buffer empty */ + while((rdscc(lp->cardbase, cmd, R0) & 0x04) == 0) + ; + wrtscc(lp->cardbase, cmd, R8, c); + } +#endif + + /* select transmit interrupts to enable */ + + wrtscc(lp->cardbase, cmd, R15, TxUIE); /* allow Underrun int only */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + wrtscc(lp->cardbase, cmd, R1, TxINT_ENAB | EXT_INT_ENAB); /* Tx/Ext ints */ + + lp->tstate = ACTIVE; /* char going out now */ + restore_flags(flags); + return; + + case DEFER: + /* Check DCD - debounce it + * See Intel Microcommunications Handbook, p2-308 + */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) { + lp->tstate = DEFER; + tdelay(lp, 100); + /* defer until DCD transition or timeout */ + wrtscc(lp->cardbase, cmd, R15, CTSIE | DCDIE); + restore_flags(flags); + return; + } + if (random() > lp->persist) { + lp->tstate = DEFER; + tdelay(lp, lp->slotime); + restore_flags(flags); + return; + } + rts(lp, ON); /* Transmitter on */ + lp->tstate = ST_TXDELAY; + tdelay(lp, lp->txdelay); + restore_flags(flags); + return; + + case ST_TXDELAY: + + /* Get first char to send */ + lp->txcnt--; + c = *lp->txptr++; + wrtscc(lp->cardbase, cmd, R0, RES_Tx_CRC); /* reset for next frame */ + + /* Send abort on underrun */ + if (lp->speed) { /* If internally clocked */ + wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI | ABUNDER); + } else { + wrtscc(lp->cardbase, cmd, R10, CRCPS | ABUNDER); + } + + wrtscc(lp->cardbase, cmd, R8, c); /* First char out now */ + wrtscc(lp->cardbase, cmd, R0, RES_EOM_L); /* Reset end of message latch */ + +#ifdef STUFF2 + /* stuff an extra one if we can */ + if (lp->txcnt) { + lp->txcnt--; + c = *lp->txptr++; + /* Wait for tx buffer empty */ + while((rdscc(lp->cardbase, cmd, R0) & 0x04) == 0) + ; + wrtscc(lp->cardbase, cmd, R8, c); + } +#endif + + /* select transmit interrupts to enable */ + + wrtscc(lp->cardbase, cmd, R15, TxUIE); /* allow Underrun int only */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + /* Tx/Extern ints on */ + wrtscc(lp->cardbase, cmd, R1, TxINT_ENAB | EXT_INT_ENAB); + + lp->tstate = ACTIVE; /* char going out now */ + restore_flags(flags); + return; + } + + /* Receive Mode only + * This triggers when hunt mode is entered, & since an ABORT + * automatically enters hunt mode, we use that to clean up + * any waiting garbage + */ + if ((lp->rstate == ACTIVE) && (st & BRK_ABRT)) { + (void) rdscc(lp->cardbase, cmd, R8); + (void) rdscc(lp->cardbase, cmd, R8); + (void) rdscc(lp->cardbase, cmd, R8); + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; /* rewind on DCD transition */ + } + restore_flags(flags); +} + +/* Probe for a PI card. */ +/* This routine also initializes the timer chip */ + +__initfunc(static int hw_probe(int ioaddr)) +{ + int time = 1000; /* Number of milliseconds for test */ + unsigned long start_time, end_time; + + int base, tmr0, tmr1, tmrcmd; + int a = 1; + int b = 1; + + base = ioaddr & 0x3f0; + tmr0 = TMR0 + base; + tmr1 = TMR1 + base; + tmrcmd = TMRCMD + base; + + /* Set up counter chip timer 0 for 500 uS period square wave */ + /* assuming a 3.68 mhz clock for now */ + outb_p(SC0 | LSB_MSB | MODE3, tmrcmd); + outb_p(922 & 0xFF, tmr0); + outb_p(922 >> 8, tmr0); + + /* Setup timer control word for timer 1*/ + outb_p(SC1 | LSB_MSB | MODE0, tmrcmd); + outb_p((time << 1) & 0xFF, tmr1); + outb_p((time >> 7) & 0XFF, tmr1); + + /* wait until counter reg is loaded */ + do { + /* Latch count for reading */ + outb_p(SC1, tmrcmd); + a = inb_p(tmr1); + b = inb_p(tmr1); + } while (b == 0); + start_time = jiffies; + while (b != 0) { + /* Latch count for reading */ + outb_p(SC1, tmrcmd); + a = inb_p(tmr1); + b = inb_p(tmr1); + end_time = jiffies; + /* Don't wait forever - there may be no card here */ + if ((end_time - start_time) > 200) + return 0; /* No card found */ + } + end_time = jiffies; + /* 87 jiffies, for a 3.68 mhz clock, half that for a double speed clock */ + if ((end_time - start_time) > 65) { + return (1); /* PI card found */ + } else { + /* Faster crystal - tmr0 needs adjusting */ + /* Set up counter chip */ + /* 500 uS square wave */ + outb_p(SC0 | LSB_MSB | MODE3, tmrcmd); + outb_p(1844 & 0xFF, tmr0); + outb_p(1844 >> 8, tmr0); + return (2); /* PI2 card found */ + } +} + +static void rts(struct pi_local *lp, int x) +{ + int tc; + long br; + int cmd; + int dummy; + + /* assumes interrupts are off */ + cmd = CTL + lp->base; + + /* Reprogram BRG and turn on transmitter to send flags */ + if (x == ON) { /* Turn Tx ON and Receive OFF */ + /* Exints off first to avoid abort int */ + wrtscc(lp->cardbase, cmd, R15, 0); + wrtscc(lp->cardbase, cmd, R3, Rx8); /* Rx off */ + lp->rstate = IDLE; + if (cmd & 2) { /* if channel a */ + /* Set up for TX dma */ + wrtscc(lp->cardbase, cmd, R1, WT_FN_RDYFN | EXT_INT_ENAB); + } else { + wrtscc(lp->cardbase, cmd, R1, 0); /* No interrupts */ + } + + if (!lp->clockmode) { + if (lp->speed) { /* if internally clocked */ + br = lp->speed; /* get desired speed */ + tc = (lp->xtal / br) - 2; /* calc 1X BRG divisor */ + wrtscc(lp->cardbase, cmd, R12, tc & 0xFF); /* lower byte */ + wrtscc(lp->cardbase, cmd, R13, (tc >> 8) & 0xFF); /* upper byte */ + } + } + wrtscc(lp->cardbase, cmd, R5, TxCRC_ENAB | RTS | TxENAB | Tx8 | DTR); + /* Transmitter now on */ + } else { /* Tx OFF and Rx ON */ + lp->tstate = IDLE; + wrtscc(lp->cardbase, cmd, R5, Tx8 | DTR); /* TX off */ + + if (!lp->clockmode) { + if (lp->speed) { /* if internally clocked */ + /* Reprogram BRG for 32x clock for receive DPLL */ + /* BRG off, keep Pclk source */ + wrtscc(lp->cardbase, cmd, R14, BRSRC); + br = lp->speed; /* get desired speed */ + /* calc 32X BRG divisor */ + tc = ((lp->xtal / 32) / br) - 2; + wrtscc(lp->cardbase, cmd, R12, tc & 0xFF); /* lower byte */ + wrtscc(lp->cardbase, cmd, R13, (tc >> 8) & 0xFF); /* upper byte */ + /* SEARCH mode, BRG source */ + wrtscc(lp->cardbase, cmd, R14, BRSRC | SEARCH); + /* Enable the BRG */ + wrtscc(lp->cardbase, cmd, R14, BRSRC | BRENABL); + } + } + /* Flush rx fifo */ + wrtscc(lp->cardbase, cmd, R3, Rx8); /* Make sure rx is off */ + wrtscc(lp->cardbase, cmd, R0, ERR_RES); /* reset err latch */ + dummy = rdscc(lp->cardbase, cmd, R1); /* get status byte from R1 */ + (void) rdscc(lp->cardbase, cmd, R8); + (void) rdscc(lp->cardbase, cmd, R8); + + (void) rdscc(lp->cardbase, cmd, R8); + + /* Now, turn on the receiver and hunt for a flag */ + wrtscc(lp->cardbase, cmd, R3, RxENABLE | Rx8); + lp->rstate = ACTIVE; /* Normal state */ + + if (cmd & 2) { /* if channel a */ + setup_rx_dma(lp); + } else { + /* reset buffer pointers */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + wrtscc(lp->cardbase, cmd, R1, (INT_ALL_Rx | EXT_INT_ENAB)); + } + wrtscc(lp->cardbase, cmd, R15, BRKIE); /* allow ABORT int */ + } +} + +static void scc_init(struct device *dev) +{ + unsigned long flags; + struct pi_local *lp = (struct pi_local *) dev->priv; + + int tc; + long br; + register int cmd; + + /* Initialize 8530 channel for SDLC operation */ + + cmd = CTL + lp->base; + save_flags(flags); + cli(); + + switch (cmd & CHANA) { + case CHANA: + wrtscc(lp->cardbase, cmd, R9, CHRA); /* Reset channel A */ + wrtscc(lp->cardbase, cmd, R2, 0xff); /* Initialize interrupt vector */ + break; + default: + wrtscc(lp->cardbase, cmd, R9, CHRB); /* Reset channel B */ + break; + } + + /* Deselect all Rx and Tx interrupts */ + wrtscc(lp->cardbase, cmd, R1, 0); + + /* Turn off external interrupts (like CTS/CD) */ + wrtscc(lp->cardbase, cmd, R15, 0); + + /* X1 clock, SDLC mode */ + wrtscc(lp->cardbase, cmd, R4, SDLC | X1CLK); + + /* Tx/Rx parameters */ + if (lp->speed) { /* Use internal clocking */ + wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI); + if (!lp->clockmode) + /* Tx Clk from BRG. Rcv Clk from DPLL, TRxC pin outputs DPLL */ + wrtscc(lp->cardbase, cmd, R11, TCBR | RCDPLL | TRxCDP | TRxCOI); + else + /* Tx Clk from DPLL, Rcv Clk from DPLL, TRxC Outputs BRG */ + wrtscc(lp->cardbase, cmd, R11, TCDPLL | RCDPLL | TRxCBR | TRxCOI); + } else { /* Use external clocking */ + wrtscc(lp->cardbase, cmd, R10, CRCPS); + /* Tx Clk from Trxcl. Rcv Clk from Rtxcl, TRxC pin is input */ + wrtscc(lp->cardbase, cmd, R11, TCTRxCP); + } + + /* Null out SDLC start address */ + wrtscc(lp->cardbase, cmd, R6, 0); + + /* SDLC flag */ + wrtscc(lp->cardbase, cmd, R7, FLAG); + + /* Set up the Transmitter but don't enable it + * DTR, 8 bit TX chars only + */ + wrtscc(lp->cardbase, cmd, R5, Tx8 | DTR); + + /* Receiver initial setup */ + wrtscc(lp->cardbase, cmd, R3, Rx8); /* 8 bits/char */ + + /* Setting up BRG now - turn it off first */ + wrtscc(lp->cardbase, cmd, R14, BRSRC); /* BRG off, keep Pclk source */ + + /* set the 32x time constant for the BRG in Receive mode */ + + if (lp->speed) { + br = lp->speed; /* get desired speed */ + tc = ((lp->xtal / 32) / br) - 2; /* calc 32X BRG divisor */ + } else { + tc = 14; + } + + wrtscc(lp->cardbase, cmd, R12, tc & 0xFF); /* lower byte */ + wrtscc(lp->cardbase, cmd, R13, (tc >> 8) & 0xFF); /* upper byte */ + + /* Following subroutine sets up and ENABLES the receiver */ + rts(lp, OFF); /* TX OFF and RX ON */ + + if (lp->speed) { + /* DPLL frm BRG, BRG src PCLK */ + wrtscc(lp->cardbase, cmd, R14, BRSRC | SSBR); + } else { + /* DPLL frm rtxc,BRG src PCLK */ + wrtscc(lp->cardbase, cmd, R14, BRSRC | SSRTxC); + } + wrtscc(lp->cardbase, cmd, R14, BRSRC | SEARCH); /* SEARCH mode, keep BRG src */ + wrtscc(lp->cardbase, cmd, R14, BRSRC | BRENABL); /* Enable the BRG */ + + if (!(cmd & 2)) /* if channel b */ + wrtscc(lp->cardbase, cmd, R1, (INT_ALL_Rx | EXT_INT_ENAB)); + + wrtscc(lp->cardbase, cmd, R15, BRKIE); /* ABORT int */ + + /* Now, turn on the receiver and hunt for a flag */ + wrtscc(lp->cardbase, cmd, R3, RxENABLE | RxCRC_ENAB | Rx8); + + restore_flags(flags); +} + +static void chipset_init(struct device *dev) +{ + int cardbase; + unsigned long flags; + + cardbase = dev->base_addr & 0x3f0; + + save_flags(flags); + cli(); + wrtscc(cardbase, dev->base_addr + CTL, R9, FHWRES); /* Hardware reset */ + /* Disable interrupts with master interrupt ctrl reg */ + wrtscc(cardbase, dev->base_addr + CTL, R9, 0); + restore_flags(flags); + +} + + +__initfunc(int pi_init(void)) +{ + int *port; + int ioaddr = 0; + int card_type = 0; + int ports[] = {0x380, 0x300, 0x320, 0x340, 0x360, 0x3a0, 0}; + + printk(KERN_INFO "PI: V0.8 ALPHA April 23 1995 David Perry (dp@hydra.carleton.ca)\n"); + + /* Only one card supported for now */ + for (port = &ports[0]; *port && !card_type; port++) { + ioaddr = *port; + + if (check_region(ioaddr, PI_TOTAL_SIZE) == 0) { + printk(KERN_INFO "PI: Probing for card at address %#3x\n",ioaddr); + card_type = hw_probe(ioaddr); + } + } + + switch (card_type) { + case 1: + printk(KERN_INFO "PI: Found a PI card at address %#3x\n", ioaddr); + break; + case 2: + printk(KERN_INFO "PI: Found a PI2 card at address %#3x\n", ioaddr); + break; + default: + printk(KERN_ERR "PI: ERROR: No card found\n"); + return -EIO; + } + + /* Link a couple of device structures into the chain */ + /* For the A port */ + /* Allocate space for 4 buffers even though we only need 3, + because one of them may cross a DMA page boundary and + be rejected by get_dma_buffer(). + */ + register_netdev(&pi0a); + + pi0a.priv = kmalloc(sizeof(struct pi_local) + (DMA_BUFF_SIZE + sizeof(struct mbuf)) * 4, GFP_KERNEL | GFP_DMA); + + pi0a.dma = PI_DMA; + pi0a.base_addr = ioaddr + 2; + pi0a.irq = 0; + + /* And the B port */ + register_netdev(&pi0b); + pi0b.base_addr = ioaddr; + pi0b.irq = 0; + + pi0b.priv = kmalloc(sizeof(struct pi_local) + (DMA_BUFF_SIZE + sizeof(struct mbuf)) * 4, GFP_KERNEL | GFP_DMA); + + /* Now initialize them */ + pi_probe(&pi0a, card_type); + pi_probe(&pi0b, card_type); + + pi0b.irq = pi0a.irq; /* IRQ is shared */ + + return 0; +} + +static int valid_dma_page(unsigned long addr, unsigned long dev_buffsize) +{ + if (((addr & 0xffff) + dev_buffsize) <= 0x10000) + return 1; + else + return 0; +} + +static int pi_set_mac_address(struct device *dev, void *addr) +{ + struct sockaddr *sa = (struct sockaddr *)addr; + memcpy(dev->dev_addr, sa->sa_data, dev->addr_len); /* addr is an AX.25 shifted ASCII */ + return 0; /* mac address */ +} + +/* Allocate a buffer which does not cross a DMA page boundary */ +static char * +get_dma_buffer(unsigned long *mem_ptr) +{ + char *ret; + + ret = (char *)*mem_ptr; + + if(!valid_dma_page(*mem_ptr, DMA_BUFF_SIZE + sizeof(struct mbuf))){ + *mem_ptr += (DMA_BUFF_SIZE + sizeof(struct mbuf)); + ret = (char *)*mem_ptr; + } + *mem_ptr += (DMA_BUFF_SIZE + sizeof(struct mbuf)); + return (ret); +} + +static int pi_probe(struct device *dev, int card_type) +{ + short ioaddr; + struct pi_local *lp; + unsigned long flags; + unsigned long mem_ptr; + + ioaddr = dev->base_addr; + + /* Initialize the device structure. */ + /* Must be done before chipset_init */ + /* Make certain the data structures used by the PI2 are aligned. */ + dev->priv = (void *) (((int) dev->priv + 7) & ~7); + lp = (struct pi_local *) dev->priv; + + memset(dev->priv, 0, sizeof(struct pi_local)); + + /* Allocate some buffers which do not cross DMA page boundaries */ + mem_ptr = (unsigned long) dev->priv + sizeof(struct pi_local); + lp->txdmabuf = get_dma_buffer(&mem_ptr); + lp->rxdmabuf1 = (struct mbuf *) get_dma_buffer(&mem_ptr); + lp->rxdmabuf2 = (struct mbuf *) get_dma_buffer(&mem_ptr); + + /* Initialize rx buffer */ + lp->rcvbuf = lp->rxdmabuf1; + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + + /* Initialize the transmit queue head structure */ + skb_queue_head_init(&lp->sndq); + + /* These need to be initialized before scc_init is called. */ + if (card_type == 1) + lp->xtal = (unsigned long) SINGLE / 2; + else + lp->xtal = (unsigned long) DOUBLE / 2; + lp->base = dev->base_addr; + lp->cardbase = dev->base_addr & 0x3f0; + if (dev->base_addr & CHANA) { + lp->speed = DEF_A_SPEED; + /* default channel access Params */ + lp->txdelay = DEF_A_TXDELAY; + lp->persist = DEF_A_PERSIST; + lp->slotime = DEF_A_SLOTIME; + lp->squeldelay = DEF_A_SQUELDELAY; + lp->clockmode = DEF_A_CLOCKMODE; + + } else { + lp->speed = DEF_B_SPEED; + /* default channel access Params */ + lp->txdelay = DEF_B_TXDELAY; + lp->persist = DEF_B_PERSIST; + lp->slotime = DEF_B_SLOTIME; + lp->squeldelay = DEF_B_SQUELDELAY; + lp->clockmode = DEF_B_CLOCKMODE; + } + lp->bufsiz = DMA_BUFF_SIZE; + lp->tstate = IDLE; + + chipset_init(dev); + + if (dev->base_addr & CHANA) { /* Do these things only for the A port */ + /* Note that a single IRQ services 2 devices (A and B channels) */ + + lp->dmachan = dev->dma; + if (lp->dmachan < 1 || lp->dmachan > 3) + printk(KERN_ERR "PI: DMA channel %d out of range\n", lp->dmachan); + + /* chipset_init() was already called */ + + if (dev->irq < 2) { + autoirq_setup(0); + save_flags(flags); + cli(); + wrtscc(lp->cardbase, CTL + lp->base, R1, EXT_INT_ENAB); + /* enable PI card interrupts */ + wrtscc(lp->cardbase, CTL + lp->base, R9, MIE | NV); + restore_flags(flags); + /* request a timer interrupt for 1 mS hence */ + tdelay(lp, 1); + /* 20 "jiffies" should be plenty of time... */ + dev->irq = autoirq_report(20); + if (!dev->irq) { + printk(KERN_ERR "PI: Failed to detect IRQ line.\n"); + } + save_flags(flags); + cli(); + wrtscc(lp->cardbase, dev->base_addr + CTL, R9, FHWRES); /* Hardware reset */ + /* Disable interrupts with master interrupt ctrl reg */ + wrtscc(lp->cardbase, dev->base_addr + CTL, R9, 0); + restore_flags(flags); + } + + printk(KERN_INFO "PI: Autodetected IRQ %d, assuming DMA %d.\n", + dev->irq, dev->dma); + + /* This board has jumpered interrupts. Snarf the interrupt vector + now. There is no point in waiting since no other device can use + the interrupt, and this marks the 'irqaction' as busy. */ + { + int irqval = request_irq(dev->irq, &pi_interrupt,0, "pi2", dev); + if (irqval) { + printk(KERN_ERR "PI: unable to get IRQ %d (irqval=%d).\n", + dev->irq, irqval); + return EAGAIN; + } + } + + /* Grab the region */ + request_region(ioaddr & 0x3f0, PI_TOTAL_SIZE, "pi2" ); + + + } /* Only for A port */ + dev->open = pi_open; + dev->stop = pi_close; + dev->do_ioctl = pi_ioctl; + dev->hard_start_xmit = pi_send_packet; + dev->get_stats = pi_get_stats; + + /* Fill in the fields of the device structure */ + + dev_init_buffers(dev); + +#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE) + dev->hard_header = ax25_encapsulate; + dev->rebuild_header = ax25_rebuild_header; +#endif + + dev->set_mac_address = pi_set_mac_address; + + dev->type = ARPHRD_AX25; /* AF_AX25 device */ + dev->hard_header_len = 73; /* We do digipeaters now */ + dev->mtu = 1500; /* eth_mtu is the default */ + dev->addr_len = 7; /* sizeof an ax.25 address */ + memcpy(dev->broadcast, ax25_bcast, 7); + memcpy(dev->dev_addr, ax25_test, 7); + + /* New-style flags. */ + dev->flags = 0; + 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 pi_open(struct device *dev) +{ + unsigned long flags; + static first_time = 1; + + struct pi_local *lp = (struct pi_local *) dev->priv; + + if (dev->base_addr & 2) { /* if A channel */ + if (first_time) { + if (request_dma(dev->dma,"pi2")) { + free_irq(dev->irq, dev); + return -EAGAIN; + } + } + /* Reset the hardware here. */ + chipset_init(dev); + } + lp->tstate = IDLE; + + if (dev->base_addr & 2) { /* if A channel */ + scc_init(dev); /* Called once for each channel */ + scc_init(dev->next); + } + /* master interrupt enable */ + save_flags(flags); + cli(); + wrtscc(lp->cardbase, CTL + lp->base, R9, MIE | NV); + restore_flags(flags); + + lp->open_time = jiffies; + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + first_time = 0; + + MOD_INC_USE_COUNT; + + return 0; +} + +static int pi_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct pi_local *lp = (struct pi_local *) dev->priv; + + hardware_send_packet(lp, skb); + dev->trans_start = jiffies; + + return 0; +} + +/* The typical workload of the driver: + Handle the network interface interrupts. */ +static void pi_interrupt(int reg_ptr, void *dev_id, struct pt_regs *regs) +{ +/* int irq = -(((struct pt_regs *) reg_ptr)->orig_eax + 2);*/ + struct pi_local *lp; + int st; + unsigned long flags; + +/* dev_b = dev_a->next; Relies on the order defined in Space.c */ + +#if 0 + if (dev_a == NULL) { + printk(KERN_ERR "PI: pi_interrupt(): irq %d for unknown device.\n", irq); + return; + } +#endif + /* Read interrupt status register (only valid from channel A) + * Process all pending interrupts in while loop + */ + lp = (struct pi_local *) pi0a.priv; /* Assume channel A */ + while ((st = rdscc(lp->cardbase, pi0a.base_addr | CHANA | CTL, R3)) != 0) { + if (st & CHBTxIP) { + /* Channel B Transmit Int Pending */ + lp = (struct pi_local *) pi0b.priv; + b_txint(lp); + } else if (st & CHARxIP) { + /* Channel A Rcv Interrupt Pending */ + lp = (struct pi_local *) pi0a.priv; + a_rxint(&pi0a, lp); + } else if (st & CHATxIP) { + /* Channel A Transmit Int Pending */ + lp = (struct pi_local *) pi0a.priv; + a_txint(lp); + } else if (st & CHAEXT) { + /* Channel A External Status Int */ + lp = (struct pi_local *) pi0a.priv; + a_exint(lp); + } else if (st & CHBRxIP) { + /* Channel B Rcv Interrupt Pending */ + lp = (struct pi_local *) pi0b.priv; + b_rxint(&pi0b, lp); + } else if (st & CHBEXT) { + /* Channel B External Status Int */ + lp = (struct pi_local *) pi0b.priv; + b_exint(lp); + } + /* Reset highest interrupt under service */ + save_flags(flags); + cli(); + wrtscc(lp->cardbase, lp->base + CTL, R0, RES_H_IUS); + restore_flags(flags); + } /* End of while loop on int processing */ + return; +} + +/* The inverse routine to pi_open(). */ +static int pi_close(struct device *dev) +{ + unsigned long flags; + struct pi_local *lp; + struct sk_buff *ptr; + + save_flags(flags); + cli(); + + lp = (struct pi_local *) dev->priv; + ptr = NULL; + + chipset_init(dev); /* reset the scc */ + disable_dma(lp->dmachan); + + lp->open_time = 0; + + dev->tbusy = 1; + dev->start = 0; + + /* Free any buffers left in the hardware transmit queue */ + while ((ptr = skb_dequeue(&lp->sndq)) != NULL) + kfree_skb(ptr, FREE_WRITE); + + restore_flags(flags); + + MOD_DEC_USE_COUNT; + + return 0; +} + +static int pi_ioctl(struct device *dev, struct ifreq *ifr, int cmd) +{ + unsigned long flags; + struct pi_req rq; + struct pi_local *lp = (struct pi_local *) dev->priv; + + int ret = verify_area(VERIFY_WRITE, ifr->ifr_data, sizeof(struct pi_req)); + if (ret) + return ret; + + if(cmd!=SIOCDEVPRIVATE) + return -EINVAL; + + copy_from_user(&rq, ifr->ifr_data, sizeof(struct pi_req)); + + switch (rq.cmd) { + case SIOCSPIPARAM: + + if (!suser()) + return -EPERM; + save_flags(flags); + cli(); + lp->txdelay = rq.txdelay; + lp->persist = rq.persist; + lp->slotime = rq.slotime; + lp->squeldelay = rq.squeldelay; + lp->clockmode = rq.clockmode; + lp->speed = rq.speed; + pi_open(&pi0a); /* both channels get reset %%% */ + restore_flags(flags); + ret = 0; + break; + + case SIOCSPIDMA: + + if (!suser()) + return -EPERM; + ret = 0; + if (dev->base_addr & 2) { /* if A channel */ + if (rq.dmachan < 1 || rq.dmachan > 3) + return -EINVAL; + save_flags(flags); + cli(); + pi_close(dev); + free_dma(lp->dmachan); + dev->dma = lp->dmachan = rq.dmachan; + if (request_dma(lp->dmachan,"pi2")) + ret = -EAGAIN; + pi_open(dev); + restore_flags(flags); + } + break; + + case SIOCSPIIRQ: + ret = -EINVAL; /* add this later */ + break; + + case SIOCGPIPARAM: + case SIOCGPIDMA: + case SIOCGPIIRQ: + + rq.speed = lp->speed; + rq.txdelay = lp->txdelay; + rq.persist = lp->persist; + rq.slotime = lp->slotime; + rq.squeldelay = lp->squeldelay; + rq.clockmode = lp->clockmode; + rq.dmachan = lp->dmachan; + rq.irq = dev->irq; + copy_to_user(ifr->ifr_data, &rq, sizeof(struct pi_req)); + ret = 0; + break; + + default: + ret = -EINVAL; + } + return ret; +} + +/* Get the current statistics. This may be called with the card open or + closed. */ +static struct net_device_stats *pi_get_stats(struct device *dev) +{ + struct pi_local *lp = (struct pi_local *) dev->priv; + + return &lp->stats; +} + +#ifdef MODULE +EXPORT_NO_SYMBOLS; + +MODULE_AUTHOR("David Perry <dp@hydra.carleton.ca>"); +MODULE_DESCRIPTION("AX.25 driver for the Ottawa PI and PI/2 HDLC cards"); + +int init_module(void) +{ + return pi_init(); +} + +void cleanup_module(void) +{ + free_irq(pi0a.irq, &pi0a); /* IRQs and IO Ports are shared */ + release_region(pi0a.base_addr & 0x3f0, PI_TOTAL_SIZE); + + kfree(pi0a.priv); + pi0a.priv = NULL; + unregister_netdev(&pi0a); + + kfree(pi0b.priv); + pi0b.priv = NULL; + unregister_netdev(&pi0b); +} +#endif diff --git a/drivers/net/hamradio/pt.c b/drivers/net/hamradio/pt.c new file mode 100644 index 000000000..11bef1d6f --- /dev/null +++ b/drivers/net/hamradio/pt.c @@ -0,0 +1,1778 @@ +#undef PT_DEBUG 1 +/* + * pt.c: Linux device driver for the Gracilis PackeTwin. + * Copyright (c) 1995 Craig Small VK2XLZ (vk2xlz@vk2xlz.ampr.org.) + * + * This program is free software; you can redistribute 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 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. + * + * This driver is largely based upon the PI driver by David Perry. + * + * Revision History + * 23/02/95 cs Started again on driver, last one scrapped + * 27/02/95 cs Program works, we have chan A only. Tx stays on + * 28/02/95 cs Fix Tx problem (& TxUIE instead of | ) + * Fix Chan B Tx timer problem, used TMR2 instead of TMR1 + * 03/03/95 cs Painfully found out (after 3 days) SERIAL_CFG is write only + * created image of it and DMA_CFG + * 21/06/95 cs Upgraded to suit PI driver 0.8 ALPHA + * 22/08/95 cs Changed it all around to make it like pi driver + * 23/08/95 cs It now works, got caught again by TMR2 and we must have + * auto-enables for daughter boards. + * 07/10/95 cs Fixed for 1.3.30 (hopefully) + * 26/11/95 cs Fixed for 1.3.43, ala 29/10 for pi2.c by ac + * 21/12/95 cs Got rid of those nasty warnings when compiling, for 1.3.48 + * 08/08/96 jsn Convert to use as a module. Removed send_kiss, empty_scc and + * pt_loopback functions - they were unused. + * 13/12/96 jsn Fixed to match Linux networking changes. + */ + +/* + * default configuration of the PackeTwin, + * ie What Craig uses his PT for. + */ +#define PT_DMA 3 + +#define DEF_A_SPEED 4800 /* 4800 baud */ +#define DEF_A_TXDELAY 350 /* 350 mS */ +#define DEF_A_PERSIST 64 /* 25% persistence */ +#define DEF_A_SLOTIME 10 /* 10 mS */ +#define DEF_A_SQUELDELAY 30 /* 30 mS */ +#define DEF_A_CLOCKMODE 0 /* Normal clock mode */ +#define DEF_A_NRZI 1 /* NRZI mode */ + +#define DEF_B_SPEED 0 /* 0 means external clock */ +#define DEF_B_TXDELAY 250 /* 250 mS */ +#define DEF_B_PERSIST 64 /* 25% */ +#define DEF_B_SLOTIME 10 /* 10 mS */ +#define DEF_B_SQUELDELAY 30 /* 30 mS */ +#define DEF_B_CLOCKMODE 0 /* Normal clock mode ?!? */ +#define DEF_B_NRZI 1 /* NRZI mode */ + + +#define PARAM_TXDELAY 1 +#define PARAM_PERSIST 2 +#define PARAM_SLOTTIME 3 +#define PARAM_FULLDUP 5 +#define PARAM_HARDWARE 6 +#define PARAM_RETURN 255 + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/uaccess.h> +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/timer.h> +#include <linux/if_arp.h> +#include <linux/pt.h> +#include <linux/init.h> +#include "z8530.h" +#include <net/ax25.h> + +struct mbuf { + struct mbuf *next; + int cnt; + char data[0]; +}; + +/* + * The actual PT devices we will use + */ +static int pt0_preprobe(struct device *dev) {return 0;} /* Dummy probe function */ +static struct device pt0a = { "pt0a", 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, pt0_preprobe }; +static struct device pt0b = { "pt0b", 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, pt0_preprobe }; + +/* Ok, they shouldn't be here, but both channels share them */ +/* The Images of the Serial and DMA config registers */ +static unsigned char pt_sercfg = 0; +static unsigned char pt_dmacfg = 0; + +/* The number of IO ports used by the card */ +#define PT_TOTAL_SIZE 16 + +/* Index to functions, as function prototypes. */ + +static int pt_probe(struct device *dev); +static int pt_open(struct device *dev); +static int pt_send_packet(struct sk_buff *skb, struct device *dev); +static void pt_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static int pt_close(struct device *dev); +static int pt_ioctl(struct device *dev, struct ifreq *ifr, int cmd); +static struct net_device_stats *pt_get_stats(struct device *dev); +static void pt_rts(struct pt_local *lp, int x); +static void pt_rxisr(struct device *dev); +static void pt_txisr(struct pt_local *lp); +static void pt_exisr(struct pt_local *lp); +static void pt_tmrisr(struct pt_local *lp); +static char *get_dma_buffer(unsigned long *mem_ptr); +static int valid_dma_page(unsigned long addr, unsigned long dev_buffsize); +static int hw_probe(int ioaddr); +static void tdelay(struct pt_local *lp, int time); +static void chipset_init(struct device *dev); + +static char ax25_bcast[7] = +{'Q' << 1, 'S' << 1, 'T' << 1, ' ' << 1, ' ' << 1, ' ' << 1, '0' << 1}; +static char ax25_test[7] = +{'L' << 1, 'I' << 1, 'N' << 1, 'U' << 1, 'X' << 1, ' ' << 1, '1' << 1}; + + + +static int ext2_secrm_seed = 152; + +static inline unsigned char random(void) +{ + return (unsigned char) (ext2_secrm_seed = ext2_secrm_seed * 60691 + 1); +} + +static inline void wrtscc(int cbase, int ctl, int sccreg, unsigned char val) +{ + outb_p(sccreg, ctl); /* Select register */ + outb_p(val, ctl); /* Output value */ +} + +static inline unsigned char rdscc(int cbase, int ctl, int sccreg) +{ + unsigned char retval; + + outb_p(sccreg, ctl); /* Select register */ + retval = inb_p(ctl); + return retval; +} + +static void switchbuffers(struct pt_local *lp) +{ + if (lp->rcvbuf == lp->rxdmabuf1) + lp->rcvbuf = lp->rxdmabuf2; + else + lp->rcvbuf = lp->rxdmabuf1; +} + +static void hardware_send_packet(struct pt_local *lp, struct sk_buff *skb) +{ + char kickflag; + unsigned long flags; + char *ptr; + struct device *dev; + + /* First, let's see if this packet is actually a KISS packet */ + ptr = skb->data; + if (ptr[0] != 0 && skb->len >= 2) + { +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: Rx KISS... Control = %d, value = %d.\n", ptr[0], (skb->len > 1? ptr[1] : -1)); +#endif + /* Kludge to get device */ + if ((struct pt_local*)(&pt0b.priv) == lp) + dev = &pt0b; + else + dev = &pt0a; + switch(ptr[0]) + { + + case PARAM_TXDELAY: + /*TxDelay is in 10mS increments */ + lp->txdelay = ptr[1] * 10; + break; + case PARAM_PERSIST: + lp->persist = ptr[1]; + break; + case PARAM_SLOTTIME: + lp->slotime = ptr[1]; + break; + case PARAM_FULLDUP: + /* Yeah right, you wish! Fullduplex is a little while to + * go folks, but this is how you fire it up + */ + break; + /* Perhaps we should have txtail here?? */ + } /*switch */ + return; + } + + lp->stats.tx_packets++; + lp->stats.tx_bytes+=skb->len; + save_flags(flags); + cli(); + kickflag = (skb_peek(&lp->sndq) == NULL) && (lp->sndbuf == NULL); + restore_flags(flags); + +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: hardware_send_packet(): kickflag = %d (%d).\n", kickflag, lp->base & CHANA); +#endif + skb_queue_tail(&lp->sndq, skb); + if (kickflag) + { + /* Simulate interrupt to transmit */ + if (lp->dmachan) + pt_txisr(lp); + else + { + save_flags(flags); + cli(); + if (lp->tstate == IDLE) + pt_txisr(lp); + restore_flags(flags); + } + } +} /* hardware_send_packet() */ + +static void setup_rx_dma(struct pt_local *lp) +{ + unsigned long flags; + int cmd; + unsigned long dma_abs; + unsigned char dmachan; + + save_flags(flags); + cli(); + + dma_abs = (unsigned long) (lp->rcvbuf->data); + dmachan = lp->dmachan; + cmd = lp->base + CTL; + + if(!valid_dma_page(dma_abs, DMA_BUFF_SIZE + sizeof(struct mbuf))) + panic("PI: RX buffer violates DMA boundary!"); + + /* Get ready for RX DMA */ + wrtscc(lp->cardbase, cmd, R1, WT_FN_RDYFN | WT_RDY_RT | INT_ERR_Rx | EXT_INT_ENAB); + + disable_dma(dmachan); + clear_dma_ff(dmachan); + + /* + * Set DMA mode register to single transfers, incrementing address, + * auto init, writes + */ + + set_dma_mode(dmachan, DMA_MODE_READ | 0x10); + set_dma_addr(dmachan, dma_abs); + set_dma_count(dmachan, lp->bufsiz); + enable_dma(dmachan); + + /* + * If a packet is already coming in, this line is supposed to + * avoid receiving a partial packet. + */ + + wrtscc(lp->cardbase, cmd, R0, RES_Rx_CRC); + + /* Enable RX dma */ + wrtscc(lp->cardbase, cmd, R1, + WT_RDY_ENAB | WT_FN_RDYFN | WT_RDY_RT | INT_ERR_Rx | EXT_INT_ENAB); + + restore_flags(flags); +} + +static void setup_tx_dma(struct pt_local *lp, int length) +{ + unsigned long dma_abs; + unsigned long flags; + unsigned long dmachan; + + save_flags(flags); + cli(); + + dmachan = lp->dmachan; + dma_abs = (unsigned long) (lp->txdmabuf); + + if(!valid_dma_page(dma_abs, DMA_BUFF_SIZE + sizeof(struct mbuf))) + panic("PT: TX buffer violates DMA boundary!"); + + disable_dma(dmachan); + /* Set DMA mode register to single transfers, incrementing address, + * no auto init, reads + */ + set_dma_mode(dmachan, DMA_MODE_WRITE); + clear_dma_ff(dmachan); + set_dma_addr(dmachan, dma_abs); + /* output byte count */ + set_dma_count(dmachan, length); + + restore_flags(flags); +} + +/* + * This sets up all the registers in the SCC for the given channel + * based upon tsync_hwint() + */ +static void scc_init(struct device *dev) +{ + unsigned long flags; + struct pt_local *lp = (struct pt_local*) dev->priv; + register int cmd = lp->base + CTL; + int tc, br; + +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: scc_init(): (%d).\n", lp->base & CHANA); +#endif + save_flags(flags); + cli(); + + /* We may put something here to enable_escc */ + + if (cmd & CHANA) + { + wrtscc(lp->cardbase, cmd, R9, CHRA); /* Reset channel A */ + wrtscc(lp->cardbase, cmd, R2, 0xff); /* Initialise interrupt vector */ + } + else + wrtscc(lp->cardbase, cmd, R9, CHRB); /* Reset channel B */ + + /* Deselect all Rx and Tx interrupts */ + wrtscc(lp->cardbase, cmd, R1, 0); + + /* Turn off external interrupts (like CTS/CD) */ + wrtscc(lp->cardbase, cmd, R15, 0); + + /* X1 clock, SDLC mode */ + wrtscc(lp->cardbase, cmd, R4, SDLC | X1CLK); + + /* Preset CRC and set mode */ + if (lp->nrzi) + /* Preset Tx CRC, put into NRZI mode */ + wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI); + else + /* Preset Tx CRC, put into NRZ mode */ + wrtscc(lp->cardbase, cmd, R10, CRCPS); + + /* Tx/Rx parameters */ + if (lp->speed) /* Use internal clocking */ + /* Tx Clk from BRG. Rx Clk form DPLL, TRxC pin outputs DPLL */ + wrtscc(lp->cardbase, cmd, R11, TCBR | RCDPLL | TRxCDP | TRxCOI); + else /* Use external clocking */ + { + /* Tx Clk from TRxCL. Rx Clk from RTxCL, TRxC pin if input */ + wrtscc(lp->cardbase, cmd, R11, TCTRxCP | RCRTxCP | TRxCBR); + wrtscc(lp->cardbase,cmd, R14, 0); /* wiz1 */ + } + + /* Null out SDLC start address */ + wrtscc(lp->cardbase, cmd, R6, 0); + + /* SDLC flag */ + wrtscc(lp->cardbase, cmd, R7, FLAG); + + /* Setup Tx but don't enable it */ + wrtscc(lp->cardbase, cmd, R5, Tx8 | DTR); + + /* Setup Rx */ + wrtscc(lp->cardbase, cmd, R3, AUTO_ENAB | Rx8); + + /* Setup the BRG, turn it off first */ + wrtscc(lp->cardbase, cmd, R14, BRSRC); + + /* set the 32x time constant for the BRG in Rx mode */ + if (lp->speed) + { + br = lp->speed; + tc = ((lp->xtal / 32) / (br * 2)) - 2; + wrtscc(lp->cardbase, cmd, R12, tc & 0xff); /* lower byte */ + wrtscc(lp->cardbase, cmd, R13, (tc >> 8) & 0xff); /* upper byte */ + } + + /* Turn transmitter off, to setup stuff */ + pt_rts(lp, OFF); + + /* External clocking */ + if (lp->speed) + { + /* DPLL frm BRG, BRG src PCLK */ + wrtscc(lp->cardbase, cmd, R14, BRSRC | SSBR); + wrtscc(lp->cardbase, cmd, R14, BRSRC | SEARCH); /* SEARCH mode, keep BRG src */ + wrtscc(lp->cardbase, cmd, R14, BRSRC | BRENABL); /* Enable the BRG */ + + /* Turn off external clock port */ + if (lp->base & CHANA) + outb_p( (pt_sercfg &= ~PT_EXTCLKA), (lp->cardbase + SERIAL_CFG) ); + else + outb_p( (pt_sercfg &= ~PT_EXTCLKB), (lp->cardbase + SERIAL_CFG) ); + } + else + { + /* DPLL frm rtxc,BRG src PCLK */ + /* Turn on external clock port */ + if (lp->base & CHANA) + outb_p( (pt_sercfg |= PT_EXTCLKA), (lp->cardbase + SERIAL_CFG) ); + else + outb_p( (pt_sercfg |= PT_EXTCLKB), (lp->cardbase + SERIAL_CFG) ); + } + + if (!lp->dmachan) + wrtscc(lp->cardbase, cmd, R1, (INT_ALL_Rx | EXT_INT_ENAB)); + + wrtscc(lp->cardbase, cmd, R15, BRKIE); /* ABORT int */ + + /* Turn on the DTR to tell modem we're alive */ + if (lp->base & CHANA) + outb_p( (pt_sercfg |= PT_DTRA_ON), (lp->cardbase + SERIAL_CFG) ); + else + outb_p( (pt_sercfg |= PT_DTRB_ON), (lp->cardbase + SERIAL_CFG) ); + + /* Now, turn on the receiver and hunt for a flag */ + wrtscc(lp->cardbase, cmd, R3, RxENABLE | RxCRC_ENAB | AUTO_ENAB | Rx8 ); + + restore_flags(flags); + +} /* scc_init() */ + +/* Resets the given channel and whole SCC if both channels off */ +static void chipset_init(struct device *dev) +{ + + struct pt_local *lp = (struct pt_local*) dev->priv; +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: chipset_init(): pt0a tstate = %d.\n", ((struct pt_local*)pt0a.priv)->tstate); + printk(KERN_DEBUG "PT: chipset_init(): pt0b tstate = %d.\n", ((struct pt_local*)pt0b.priv)->tstate); +#endif + /* Reset SCC if both channels are to be canned */ + if ( ((lp->base & CHANA) && !(pt_sercfg & PT_DTRB_ON)) || + (!(lp->base & CHANA) && !(pt_sercfg & PT_DTRA_ON)) ) + { + wrtscc(lp->cardbase, lp->base + CTL, R9, FHWRES); + /* Reset int and dma registers */ + outb_p((pt_sercfg = 0), lp->cardbase + SERIAL_CFG); + outb_p((pt_dmacfg = 0), lp->cardbase + DMA_CFG); +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: chipset_init() Resetting SCC, called by ch (%d).\n", lp->base & CHANA); +#endif + } + /* Reset individual channel */ + if (lp->base & CHANA) { + wrtscc(lp->cardbase, lp->base + CTL, R9, MIE | DLC | NV | CHRA); + outb_p( (pt_sercfg &= ~PT_DTRA_ON), lp->cardbase + SERIAL_CFG); + } else { + wrtscc(lp->cardbase, lp->base + CTL, R9, MIE | DLC | NV | CHRB); + outb_p( (pt_sercfg &= ~PT_DTRB_ON), lp->cardbase + SERIAL_CFG); + } +} /* chipset_init() */ + + + +__initfunc(int pt_init(void)) +{ + int *port; + int ioaddr = 0; + int card_type = 0; + int ports[] = + { 0x230, 0x240, 0x250, 0x260, 0x270, 0x280, 0x290, 0x2a0, + 0x2b0, 0x300, 0x330, 0x3f0, 0}; + + printk(KERN_INFO "PT: 0.41 ALPHA 07 October 1995 Craig Small (csmall@small.dropbear.id.au)\n"); + + for (port = &ports[0]; *port && !card_type; port++) { + ioaddr = *port; + + if (check_region(ioaddr, PT_TOTAL_SIZE) == 0) { + printk(KERN_INFO "PT: Probing for card at address %#3x\n", ioaddr); + card_type = hw_probe(ioaddr); + } + } + if (card_type) { + printk(KERN_INFO "PT: Found a PT at address %#3x\n",ioaddr); + } else { + printk(KERN_ERR "PT: ERROR: No card found.\n"); + return -EIO; + } + + /* + * Link a couple of device structures into the chain + * + * For the A port + * Allocate space for 4 buffers even though we only need 3, + * because one of them may cross a DMA page boundary and + * be rejected by get_dma_buffer(). + */ + register_netdev(&pt0a); + + pt0a.priv= kmalloc(sizeof(struct pt_local) + (DMA_BUFF_SIZE + sizeof(struct mbuf)) * 4, GFP_KERNEL | GFP_DMA); + + pt0a.dma = 0; /* wizzer - no dma yet */ + pt0a.base_addr = ioaddr + CHANA; + pt0a.irq = 0; + + /* And B port */ + register_netdev(&pt0b); + + pt0b.priv= kmalloc(sizeof(struct pt_local) + (DMA_BUFF_SIZE + sizeof(struct mbuf)) * 4, GFP_KERNEL | GFP_DMA); + + pt0b.base_addr = ioaddr + CHANB; + pt0b.irq = 0; + + /* Now initialise them */ + pt_probe(&pt0a); + pt_probe(&pt0b); + + pt0b.irq = pt0a.irq; /* IRQ is shared */ + + return 0; +} /* pt_init() */ + +/* + * Probe for PT card. Also initialises the timers + */ +__initfunc(static int hw_probe(int ioaddr)) +{ + int time = 1000; /* Number of milliseconds to test */ + int a = 1; + int b = 1; + unsigned long start_time, end_time; + + inb_p(ioaddr + TMR1CLR); + inb_p(ioaddr + TMR2CLR); + + /* Timer counter channel 0, 1mS period */ + outb_p(SC0 | LSB_MSB | MODE3, ioaddr + TMRCMD); + outb_p(0x00, ioaddr + TMR0); + outb_p(0x18, ioaddr + TMR0); + + /* Setup timer control word for timer 1 */ + outb_p(SC1 | LSB_MSB | MODE0, ioaddr + TMRCMD); + outb_p((time << 1) & 0xff, ioaddr + TMR1); + outb_p((time >> 7) & 0xff, ioaddr + TMR1); + + /* wait until counter reg is loaded */ + do { + /* Latch count for reading */ + outb_p(SC1, ioaddr + TMRCMD); + a = inb_p(ioaddr + TMR1); + b = inb_p(ioaddr + TMR1); + } while (b == 0); + start_time = jiffies; + while(b != 0) + { + /* Latch count for reading */ + outb_p(SC1, ioaddr + TMRCMD); + a = inb_p(ioaddr + TMR1); + b = inb_p(ioaddr + TMR1); + end_time = jiffies; + /* Don't wait forever - there may be no card here */ + if ((end_time - start_time) > 200) + { + inb_p(ioaddr + TMR1CLR); + return 0; + } + } + + /* Now fix the timers up for general operation */ + + /* Clear the timers */ + inb_p(ioaddr + TMR1CLR); + inb_p(ioaddr + TMR2CLR); + + outb_p(SC1 | LSB_MSB | MODE0, ioaddr + TMRCMD); + inb_p(ioaddr + TMR1CLR); + + outb_p(SC2 | LSB_MSB | MODE0, ioaddr + TMRCMD); + /* Should this be tmr1 or tmr2? wiz3*/ + inb_p(ioaddr + TMR1CLR); + + return 1; +} /* hw_probe() */ + + +static void pt_rts(struct pt_local *lp, int x) +{ + int tc; + long br; + int cmd = lp->base + CTL; +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_rts(): Transmitter status will be %d (%d).\n", x, lp->base & CHANA); +#endif + if (x == ON) { + /* Ex ints off to avoid int */ + wrtscc(lp->cardbase, cmd, R15, 0); + wrtscc(lp->cardbase, cmd, R3, AUTO_ENAB | Rx8); /* Rx off */ + lp->rstate = IDLE; + + if(lp->dmachan) + { + /* Setup for Tx DMA */ + wrtscc(lp->cardbase, cmd, R1, WT_FN_RDYFN | EXT_INT_ENAB); + } else { + /* No interrupts */ + wrtscc(lp->cardbase, cmd, R1, 0); + } + + if (!lp->clockmode) + { + if (lp->speed) + { + br = lp->speed; + tc = (lp->xtal / (br * 2)) - 2; + wrtscc(lp->cardbase, cmd, R12, tc & 0xff); + wrtscc(lp->cardbase, cmd, R13, (tc >> 8) & 0xff); + } + } + /* Turn on Tx by raising RTS */ + wrtscc(lp->cardbase, cmd, R5, TxCRC_ENAB | RTS | TxENAB | Tx8 | DTR); + /* Transmitter on now */ + } else { /* turning off Tx */ + lp->tstate = IDLE; + + /* Turn off Tx by dropping RTS */ + wrtscc(lp->cardbase, cmd, R5, Tx8 | DTR); + if (!lp->clockmode) + { + if (lp->speed) /* internally clocked */ + { + /* Reprogram BRG from 32x clock for Rx DPLL */ + /* BRG off, keep PClk source */ + wrtscc(lp->cardbase, cmd, R14, BRSRC); + br = lp->speed; + tc = ((lp->xtal / 32) / (br * 2)) - 2; + wrtscc(lp->cardbase, cmd, R12, tc & 0xff); + wrtscc(lp->cardbase, cmd, R13, (tc >> 8) & 0xff); + + /* SEARCH mode, BRG source */ + wrtscc(lp->cardbase, cmd, R14, BRSRC | SEARCH); + /* Enable the BRG */ + wrtscc(lp->cardbase, cmd, R14, BRSRC | BRENABL); + } + } + /* Flush Rx fifo */ + /* Turn Rx off */ + wrtscc(lp->cardbase, cmd, R3, AUTO_ENAB | Rx8); + + /* Reset error latch */ + wrtscc(lp->cardbase, cmd, R0, ERR_RES); + + /* get status byte from R1 */ + (void) rdscc(lp->cardbase, cmd, R1); + + /* Read and dump data in queue */ + (void) rdscc(lp->cardbase, cmd, R8); + (void) rdscc(lp->cardbase, cmd, R8); + (void) rdscc(lp->cardbase, cmd, R8); + + /* Now, turn on Rx and hunt for a flag */ + wrtscc(lp->cardbase, cmd, R3, RxENABLE | AUTO_ENAB | Rx8 ); + + lp->rstate = ACTIVE; + + if (lp->dmachan) + { + setup_rx_dma(lp); + } else { + /* Reset buffer pointers */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + /* Allow aborts to interrupt us */ + wrtscc(lp->cardbase, cmd, R1, INT_ALL_Rx | EXT_INT_ENAB); + + } + wrtscc(lp->cardbase, cmd, R15, BRKIE ); + } +} /* pt_rts() */ + + +static int valid_dma_page(unsigned long addr, unsigned long dev_bufsize) +{ + if (((addr & 0xffff) + dev_bufsize) <= 0x10000) + return 1; + else + return 0; +} + +static int pt_set_mac_address(struct device *dev, void *addr) +{ + struct sockaddr *sa = (struct sockaddr *)addr; + memcpy(dev->dev_addr, sa->sa_data, dev->addr_len); /* addr is an AX.25 shifted ASCII */ + return 0; /* mac address */ +} + + +/* Allocate a buffer which does not cross a DMA page boundary */ +static char * get_dma_buffer(unsigned long *mem_ptr) +{ + char *ret; + + ret = (char *) *mem_ptr; + + if (!valid_dma_page(*mem_ptr, DMA_BUFF_SIZE + sizeof(struct mbuf))) { + *mem_ptr += (DMA_BUFF_SIZE + sizeof(struct mbuf)); + ret = (char *) *mem_ptr; + } + *mem_ptr += (DMA_BUFF_SIZE + sizeof(struct mbuf)); + return (ret); +} /* get_dma_buffer() */ + + +/* + * Sets up all the structures for the PT device + */ +static int pt_probe(struct device *dev) +{ + short ioaddr; + struct pt_local *lp; + unsigned long flags; + unsigned long mem_ptr; + + ioaddr = dev->base_addr; + + /* + * Initialise the device structure. + * Must be done before chipset_init() + * Make sure data structures used by the PT are aligned + */ + dev->priv = (void *) (((int) dev->priv + 7) & ~7); + lp = (struct pt_local*) dev->priv; + + memset(dev->priv, 0, sizeof(struct pt_local)); + + /* Allocate some buffers which do not cross DMA boundaries */ + mem_ptr = (unsigned long) dev->priv + sizeof(struct pt_local); + lp->txdmabuf = get_dma_buffer(&mem_ptr); + lp->rxdmabuf1 = (struct mbuf *) get_dma_buffer(&mem_ptr); + lp->rxdmabuf2 = (struct mbuf *) get_dma_buffer(&mem_ptr); + + /* Initialise the Rx buffer */ + lp->rcvbuf = lp->rxdmabuf1; + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + + /* Initialise the transmit queue head structure */ + skb_queue_head_init(&lp->sndq); + + lp->base = dev->base_addr; + lp->cardbase = dev->base_addr & 0x3f0; + + /* These need to be initialised before scc_init() is called. + */ + lp->xtal = XTAL; + + if (dev->base_addr & CHANA) { + lp->speed = DEF_A_SPEED; + lp->txdelay = DEF_A_TXDELAY; + lp->persist = DEF_A_PERSIST; + lp->slotime = DEF_A_SLOTIME; + lp->squeldelay = DEF_A_SQUELDELAY; + lp->clockmode = DEF_A_CLOCKMODE; + lp->nrzi = DEF_A_NRZI; + } else { + lp->speed = DEF_B_SPEED; + lp->txdelay = DEF_B_TXDELAY; + lp->persist = DEF_B_PERSIST; + lp->slotime = DEF_B_SLOTIME; + lp->squeldelay = DEF_B_SQUELDELAY; + lp->clockmode = DEF_B_CLOCKMODE; + lp->nrzi = DEF_B_NRZI; + } + lp->bufsiz = DMA_BUFF_SIZE; + lp->tstate = IDLE; + + chipset_init(dev); + + if (dev->base_addr & CHANA) { + /* Note that a single IRQ services 2 devices (A and B channels) + */ + + /* + * We disable the dma for a while, we have to get ints working + * properly first!! + */ + lp->dmachan = 0; + + if (dev->irq < 2) { + autoirq_setup(0); + + /* Turn on PT interrupts */ + save_flags(flags); + cli(); + outb_p( pt_sercfg |= PT_EI, lp->cardbase + INT_CFG); + restore_flags(flags); + + /* Set a timer interrupt */ + tdelay(lp, 1); + dev->irq = autoirq_report(20); + + /* Turn off PT interrupts */ + save_flags(flags); + cli(); + outb_p( (pt_sercfg &= ~ PT_EI), lp->cardbase + INT_CFG); + restore_flags(flags); + + if (!dev->irq) { + printk(KERN_ERR "PT: ERROR: Failed to detect IRQ line, assuming IRQ7.\n"); + } + } + + printk(KERN_INFO "PT: Autodetected IRQ %d, assuming DMA %d\n", dev->irq, dev->dma); + + /* This board has jumpered interrupts. Snarf the interrupt vector + * now. There is no point in waiting since no other device can use + * the interrupt, and this marks the 'irqaction' as busy. + */ + { + int irqval = request_irq(dev->irq, &pt_interrupt,0, "pt", dev); + if (irqval) { + printk(KERN_ERR "PT: ERROR: Unable to get IRQ %d (irqval = %d).\n", + dev->irq, irqval); + return EAGAIN; + } + } + + /* Grab the region */ + request_region(ioaddr & 0x3f0, PT_TOTAL_SIZE, "pt" ); + } /* A port */ + dev->open = pt_open; + dev->stop = pt_close; + dev->do_ioctl = pt_ioctl; + dev->hard_start_xmit = pt_send_packet; + dev->get_stats = pt_get_stats; + + /* Fill in the fields of the device structure */ + dev_init_buffers(dev); + +#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE) + dev->hard_header = ax25_encapsulate; + dev->rebuild_header = ax25_rebuild_header; +#endif + + dev->set_mac_address = pt_set_mac_address; + + dev->type = ARPHRD_AX25; /* AF_AX25 device */ + dev->hard_header_len = 73; /* We do digipeaters now */ + dev->mtu = 1500; /* eth_mtu is default */ + dev->addr_len = 7; /* sizeof an ax.25 address */ + memcpy(dev->broadcast, ax25_bcast, 7); + memcpy(dev->dev_addr, ax25_test, 7); + + /* New style flags */ + dev->flags = 0; + + return 0; +} /* pt_probe() */ + + +/* Open/initialise 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 be set once at boot, so that there is + * a non-reboot way to recover if something goes wrong. + * derived from last half of tsync_attach() + */ +static int pt_open(struct device *dev) +{ + unsigned long flags; + struct pt_local *lp = dev->priv; + static first_time = 1; + + if (dev->base_addr & CHANA) + { + if (first_time) + { + if (request_dma(dev->dma, "pt")) + { + free_irq(dev->irq, dev); + return -EAGAIN; + } + } + + /* Reset hardware */ + chipset_init(dev); + } + lp->tstate = IDLE; + + if (dev->base_addr & CHANA) + { + scc_init(dev); + scc_init(dev->next); + } + /* Save a copy of register RR0 for comparing with later on */ + /* We always put 0 in zero count */ + lp->saved_RR0 = rdscc(lp->cardbase, lp->base + CTL, R0) & ~ZCOUNT; + + /* master interrupt enable */ + save_flags(flags); + cli(); + wrtscc(lp->cardbase, lp->base + CTL, R9, MIE | NV); + outb_p( pt_sercfg |= PT_EI, lp->cardbase + INT_CFG); + restore_flags(flags); + + lp->open_time = jiffies; + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + first_time = 0; + + MOD_INC_USE_COUNT; + + return 0; +} /* pt_open() */ + +static int pt_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct pt_local *lp = (struct pt_local *) dev->priv; + +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_send_packet(): (%d)\n", lp->base & CHANA); +#endif + hardware_send_packet(lp, skb); + dev->trans_start = jiffies; + + return 0; +} + + + +/* The inverse routine to pt_open() */ +static int pt_close(struct device *dev) +{ + unsigned long flags; + struct pt_local *lp = dev->priv; + struct sk_buff *ptr = NULL; + int cmd; + + cmd = lp->base + CTL; + + save_flags(flags); + cli(); + + /* Reset SCC or channel */ + chipset_init(dev); + disable_dma(lp->dmachan); + + lp->open_time = 0; + dev->tbusy = 1; + dev->start = 0; + + /* Free any buffers left in the hardware transmit queue */ + while ((ptr = skb_dequeue(&lp->sndq)) != NULL) + kfree_skb(ptr, FREE_WRITE); + + restore_flags(flags); + +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_close(): Closing down channel (%d).\n", lp->base & CHANA); +#endif + + MOD_DEC_USE_COUNT; + + return 0; +} /* pt_close() */ + + +static int pt_ioctl(struct device *dev, struct ifreq *ifr, int cmd) +{ + unsigned long flags; + struct pt_req rq; + struct pt_local *lp = (struct pt_local *) dev->priv; + + int ret = verify_area(VERIFY_WRITE, ifr->ifr_data, sizeof(struct pt_req)); + if (ret) + return ret; + + if (cmd != SIOCDEVPRIVATE) + return -EINVAL; + + copy_from_user(&rq, ifr->ifr_data, sizeof(struct pt_req)); + + switch (rq.cmd) { + case SIOCSPIPARAM: + + if (!suser()) + return -EPERM; + save_flags(flags); + cli(); + lp->txdelay = rq.txdelay; + lp->persist = rq.persist; + lp->slotime = rq.slotime; + lp->squeldelay = rq.squeldelay; + lp->clockmode = rq.clockmode; + lp->speed = rq.speed; + pt_open(&pt0a); + restore_flags(flags); + ret = 0; + break; + + case SIOCSPIDMA: + + if (!suser()) + return -EPERM; + ret = 0; + if (dev->base_addr & CHANA) { /* if A channel */ + if (rq.dmachan < 1 || rq.dmachan > 3) + return -EINVAL; + save_flags(flags); + cli(); + pt_close(dev); + free_dma(lp->dmachan); + dev->dma = lp->dmachan = rq.dmachan; + if (request_dma(lp->dmachan,"pt")) + ret = -EAGAIN; + pt_open(dev); + restore_flags(flags); + } + break; + + case SIOCSPIIRQ: + ret = -EINVAL; /* add this later */ + break; + + case SIOCGPIPARAM: + case SIOCGPIDMA: + case SIOCGPIIRQ: + + rq.speed = lp->speed; + rq.txdelay = lp->txdelay; + rq.persist = lp->persist; + rq.slotime = lp->slotime; + rq.squeldelay = lp->squeldelay; + rq.clockmode = lp->clockmode; + rq.dmachan = lp->dmachan; + rq.irq = dev->irq; + copy_to_user(ifr->ifr_data, &rq, sizeof(struct pt_req)); + ret = 0; + break; + + default: + ret = -EINVAL; + } + return ret; +} + +/* + * Get the current statistics. + * This may be called with the card open or closed. + */ + +static struct net_device_stats *pt_get_stats(struct device *dev) +{ + struct pt_local *lp = (struct pt_local *) dev->priv; + return &lp->stats; +} + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c skeleton.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * End: + */ + + +static void tdelay(struct pt_local *lp, int time) +{ + /* For some reason, we turn off the Tx interrupts here! */ + if (!lp->dmachan) + wrtscc(lp->cardbase, lp->base + CTL, R1, INT_ALL_Rx | EXT_INT_ENAB); + + if (lp->base & CHANA) + { + outb_p(time & 0xff, lp->cardbase + TMR1); + outb_p((time >> 8)&0xff, lp->cardbase + TMR1); + } + else + { + outb_p(time & 0xff, lp->cardbase + TMR2); + outb_p((time >> 8)&0xff, lp->cardbase + TMR2); + } +} /* tdelay */ + + +static void pt_txisr(struct pt_local *lp) +{ + unsigned long flags; + int cmd; + unsigned char c; + + save_flags(flags); + cli(); + cmd = lp->base + CTL; + +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_txisr(): tstate = %d (%d).\n", lp->tstate, lp->base & CHANA); +#endif + + switch (lp->tstate) + { + case CRCOUT: + lp->tstate = FLAGOUT; + tdelay(lp, lp->squeldelay); + restore_flags(flags); + return; + + case IDLE: + /* Transmitter idle. Find a frame for transmission */ + if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) + { + /* Nothing to send - return to receive mode + * Tx off now - flag should have gone + */ + pt_rts(lp, OFF); + + restore_flags(flags); + return; + } + if (!lp->dmachan) + { + lp->txptr = lp->sndbuf->data; + lp->txptr++; /* Ignore KISS control byte */ + lp->txcnt = (int) lp->sndbuf->len - 1; + } + /* If a buffer to send, drop though here */ + + case DEFER: + /* Check DCD - debounce it */ + /* See Intel Microcommunications Handbook p2-308 */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) + { + lp->tstate = DEFER; + tdelay(lp, 100); + /* DEFER until DCD transition or timeout */ + wrtscc(lp->cardbase, cmd, R15, DCDIE); + restore_flags(flags); + return; + } + if (random() > lp->persist) + { + lp->tstate = DEFER; + tdelay(lp, lp->slotime); + restore_flags(flags); + return; + } + pt_rts(lp, ON); /* Tx on */ + if (lp->dmachan) + wrtscc(lp->cardbase, cmd, R5, TxCRC_ENAB | RTS | Tx8); + lp->tstate = ST_TXDELAY; + tdelay(lp, lp->txdelay); + restore_flags(flags); + return; + + case ACTIVE: + /* Here we are actively sending a frame */ + if (lp->txcnt--) + { + /* XLZ - checkout Gracilis PT code to see if the while + * loop is better or not. + */ + c = *lp->txptr++; + /* next char is gone */ + wrtscc(lp->cardbase, cmd, R8, c); + /* stuffing a char satisfies interrupt condition */ + } else { + /* No more to send */ + kfree_skb(lp->sndbuf, FREE_WRITE); + lp->sndbuf = NULL; + if ((rdscc(lp->cardbase, cmd, R0) & TxEOM)) + { + /* Did we underrun */ + lp->stats.tx_errors++; + lp->stats.tx_fifo_errors++; + wrtscc(lp->cardbase, cmd, R0, SEND_ABORT); + lp->tstate = FLAGOUT; + tdelay(lp, lp->squeldelay); + restore_flags(flags); + return; + } + lp->tstate = UNDERRUN; + /* Send flags on underrun */ + if (lp->nrzi) + { + wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI); + } else { + wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZ); + } + /* Reset Tx interrupt pending */ + wrtscc(lp->cardbase, cmd, R0, RES_Tx_P); + } + restore_flags(flags); + return; + default: + printk(KERN_ERR "PT: pt_txisr(): Invalid tstate (%d) for chan %s.\n", lp->tstate, (cmd & CHANA? "A": "B") ); + pt_rts(lp, OFF); + lp->tstate = IDLE; + break; + } /*switch */ + restore_flags(flags); +} + +static void pt_rxisr(struct device *dev) +{ + struct pt_local *lp = (struct pt_local*) dev->priv; + int cmd = lp->base + CTL; + int bytecount; + unsigned long flags; + char rse; + struct sk_buff *skb; + int sksize, pkt_len; + struct mbuf *cur_buf = NULL; + unsigned char *cfix; + + save_flags(flags); + cli(); + + /* Get status byte from R1 */ + rse = rdscc(lp->cardbase, cmd, R1); + +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_rxisr(): R1 = %#3x. (%d)\n", rse, lp->base & CHANA); +#endif + + if (lp->dmachan && (rse & Rx_OVR)) + lp->rstate = RXERROR; + + if (rdscc(lp->cardbase, cmd, R0) & Rx_CH_AV && !lp->dmachan) + { + /* There is a char to be stored + * Read special condition bits before reading the data char + */ + if (rse & Rx_OVR) + { + /* Rx overrun - toss buffer */ + /* wind back the pointers */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + lp->rstate = RXERROR; + lp->stats.rx_errors++; + lp->stats.rx_fifo_errors++; + } else if (lp->rcvbuf->cnt >= lp->bufsiz) + { + /* Too large packet + * wind back Rx buffer pointers + */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + lp->rstate = TOOBIG; + } + /* ok, we can store the Rx char if no errors */ + if (lp->rstate == ACTIVE) + { + *lp->rcp++ = rdscc(lp->cardbase, cmd, R8); + lp->rcvbuf->cnt++; + } else { + /* we got an error, dump the FIFO */ + (void) rdscc(lp->cardbase, cmd, R8); + (void) rdscc(lp->cardbase, cmd, R8); + (void) rdscc(lp->cardbase, cmd, R8); + + /* Reset error latch */ + wrtscc(lp->cardbase, cmd, R0, ERR_RES); + lp->rstate = ACTIVE; + + /* Resync the SCC */ + wrtscc(lp->cardbase, cmd, R3, RxENABLE | ENT_HM | AUTO_ENAB | Rx8); + + } + } + + if (rse & END_FR) + { +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_rxisr() Got end of a %u byte frame.\n", lp->rcvbuf->cnt); +#endif + if (lp->dmachan) + { + clear_dma_ff(lp->dmachan); + bytecount = lp->bufsiz - get_dma_residue(lp->dmachan); + } else { + bytecount = lp->rcvbuf->cnt; + } + + /* END OF FRAME - Make sure Rx was active */ + if (lp->rcvbuf->cnt > 0 || lp->dmachan) + { + if ((rse & CRC_ERR) || (lp->rstate > ACTIVE) || (bytecount < 10)) + { + if ((bytecount >= 10) && (rse & CRC_ERR)) + { + lp->stats.rx_crc_errors++; + } + if (lp->dmachan) + { + if (lp->rstate == RXERROR) + { + lp->stats.rx_errors++; + lp->stats.rx_over_errors++; + } + lp->rstate = ACTIVE; + setup_rx_dma(lp); + } else { + /* wind back Rx buffer pointers */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + + /* Re-sync the SCC */ + wrtscc(lp->cardbase, cmd, R3, RxENABLE | ENT_HM | AUTO_ENAB | Rx8); + + } +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_rxisr() %s error.\n", (rse & CRC_ERR)? "CRC" : "state"); +#endif + } else { + /* We have a valid frame */ + if (lp->dmachan) + { + pkt_len = lp->rcvbuf->cnt = bytecount - 2 +1; + /* Get buffer for next frame */ + cur_buf = lp->rcvbuf; + switchbuffers(lp); + setup_rx_dma(lp); + } else { + pkt_len = lp->rcvbuf->cnt -= 2; /* Toss 2 CRC bytes */ + pkt_len += 1; /* make room for KISS control byte */ + } + + /* Malloc up new buffer */ + sksize = pkt_len; + skb = dev_alloc_skb(sksize); + if (skb == NULL) + { + printk(KERN_ERR "PT: %s: Memory squeeze, dropping packet.\n", dev->name); + lp->stats.rx_dropped++; + restore_flags(flags); + return; + } + skb->dev = dev; + + /* KISS kludge = prefix with a 0 byte */ + cfix=skb_put(skb,pkt_len); + *cfix++=0; + /* skb->data points to the start of sk_buff area */ + if (lp->dmachan) + memcpy(cfix, (char*)cur_buf->data, pkt_len - 1); + else + memcpy(cfix, lp->rcvbuf->data, pkt_len - 1); + skb->protocol = ntohs(ETH_P_AX25); + skb->mac.raw=skb->data; + lp->stats.rx_bytes+=skb->len; + netif_rx(skb); + lp->stats.rx_packets++; + if (!lp->dmachan) + { + /* packet queued - wind back buffer for next frame */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + } + } /* good frame */ + } /* check active Rx */ + /* Clear error status */ + lp->rstate = ACTIVE; + /* Reset error latch */ + } /* end EOF check */ + wrtscc(lp->cardbase, cmd, R0, ERR_RES); + restore_flags(flags); +} /* pt_rxisr() */ + +/* + * This handles the two timer interrupts. + * This is a real bugger, cause you have to rip it out of the pi's + * external status code. They use the CTS line or something. + */ +static void pt_tmrisr(struct pt_local *lp) +{ + unsigned long flags; + +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_tmrisr(): tstate = %d (%d).\n", lp->tstate, lp->base & CHANA); +#endif + + save_flags(flags); + cli(); + + + switch (lp->tstate) + { + /* Most of this stuff is in pt_exisr() */ + case FLAGOUT: + case ST_TXDELAY: + case DEFER: +/* case ACTIVE: + case UNDERRUN:*/ + pt_exisr(lp); + break; + + default: + if (lp->base & CHANA) + printk(KERN_ERR "PT: pt_tmrisr(): Invalid tstate %d for Channel A\n", lp->tstate); + else + printk(KERN_ERR "PT: pt_tmrisr(): Invalid tstate %d for Channel B\n", lp->tstate); + break; + } /* end switch */ + restore_flags(flags); +} /* pt_tmrisr() */ + + +/* + * This routine is called by the kernel when there is an interrupt for the + * PT. + */ +static void pt_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + /* It's a tad dodgy here, but we assume pt0a until proven otherwise */ + struct device *dev = &pt0a; + struct pt_local *lp = dev->priv; + unsigned char intreg; + unsigned char st; + register int cbase = dev->base_addr & 0x3f0; + unsigned long flags; + + /* Read the PT's interrupt register, this is not the SCC one! */ + intreg = inb_p(cbase + INT_REG); + while(( intreg & 0x07) != 0x07) { + /* Read interrupt register pending from Channel A */ + while ((st = rdscc(cbase, cbase + CHANA + CTL, R3)) != 0) + { + /* Read interrupt vector from R2, channel B */ +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_interrupt(): R3 = %#3x", st); +#endif +/* st = rdscc(lp->cardbase, cbase + CHANB + CTL, R2) & 0x0e;*/ +#ifdef PT_DEBUG + printk(KERN_DEBUG "PI: R2 = %#3x.\n", st); +#endif + if (st & CHARxIP) { + /* Channel A Rx */ + lp = (struct pt_local*)pt0a.priv; + pt_rxisr(&pt0a); + } else if (st & CHATxIP) { + /* Channel A Tx */ + lp = (struct pt_local*)pt0a.priv; + pt_txisr(lp); + } else if (st & CHAEXT) { + /* Channel A External Status */ + lp = (struct pt_local*)pt0a.priv; + pt_exisr(lp); + } else if (st & CHBRxIP) { + /* Channel B Rx */ + lp= (struct pt_local*)pt0b.priv; + pt_rxisr(&pt0b); + } else if (st & CHBTxIP) { + /* Channel B Tx */ + lp = (struct pt_local*)pt0b.priv; + pt_txisr(lp); + } else if (st & CHBEXT) { + /* Channel B External Status */ + lp = (struct pt_local*)pt0b.priv; + pt_exisr(lp); + } + /* Reset highest interrupt under service */ + save_flags(flags); + cli(); + wrtscc(lp->cardbase, lp->base + CTL, R0, RES_H_IUS); + restore_flags(flags); + } /* end of SCC ints */ + + if (!(intreg & PT_TMR1_MSK)) + { + /* Clear timer 1 */ + inb_p(cbase + TMR1CLR); + + pt_tmrisr( (struct pt_local*)pt0a.priv); + } + + if (!(intreg & PT_TMR2_MSK)) + { + /* Clear timer 2 */ + inb_p(cbase + TMR2CLR); + + pt_tmrisr( (struct pt_local*)pt0b.priv); + } + + /* Get the next PT interrupt vector */ + intreg = inb_p(cbase + INT_REG); + } /* while (intreg) */ +} /* pt_interrupt() */ + + +static void pt_exisr(struct pt_local *lp) +{ + unsigned long flags; + int cmd = lp->base + CTL; + unsigned char st; + char c; + int length; + + save_flags(flags); + cli(); + + /* Get external status */ + st = rdscc(lp->cardbase, cmd, R0); + +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: exisr(): R0 = %#3x tstate = %d (%d).\n", st, lp->tstate, lp->base & CHANA); +#endif + /* Reset external status latch */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + + if ((lp->rstate >= ACTIVE) && (st & BRK_ABRT) && lp->dmachan) + { + setup_rx_dma(lp); + lp->rstate = ACTIVE; + } + + switch (lp->tstate) + { + case ACTIVE: /* Unexpected underrun */ +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: exisr(): unexpected underrun detected.\n"); +#endif + kfree_skb(lp->sndbuf, FREE_WRITE); + lp->sndbuf = NULL; + if (!lp->dmachan) + { + wrtscc(lp->cardbase, cmd, R0, SEND_ABORT); + lp->stats.tx_errors++; + lp->stats.tx_fifo_errors++; + } + lp->tstate = FLAGOUT; + tdelay(lp, lp->squeldelay); + restore_flags(flags); + return; + case UNDERRUN: + lp->tstate = CRCOUT; + restore_flags(flags); + return; + case FLAGOUT: + /* squeldelay has timed out */ + /* Find a frame for transmission */ + if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) + { + /* Nothing to send - return to Rx mode */ + pt_rts(lp, OFF); + lp->tstate = IDLE; + restore_flags(flags); + return; + } + if (!lp->dmachan) + { + lp->txptr = lp->sndbuf->data; + lp->txptr++; /* Ignore KISS control byte */ + lp->txcnt = (int) lp->sndbuf->len - 1; + } + /* Fall through if we have a packet */ + + case ST_TXDELAY: + if (lp->dmachan) + { + /* Disable DMA chan */ + disable_dma(lp->dmachan); + + /* Set up for TX dma */ + wrtscc(lp->cardbase, cmd, R1, WT_FN_RDYFN | EXT_INT_ENAB); + + length = lp->sndbuf->len - 1; + memcpy(lp->txdmabuf, &lp->sndbuf->data[1], length); + + /* Setup DMA controller for Tx */ + setup_tx_dma(lp, length); + + enable_dma(lp->dmachan); + + /* Reset CRC, Txint pending */ + wrtscc(lp->cardbase, cmd, R0, RES_Tx_CRC | RES_Tx_P); + + /* Allow underrun only */ + wrtscc(lp->cardbase, cmd, R15, TxUIE); + + /* Enable TX DMA */ + wrtscc(lp->cardbase, cmd, R1, WT_RDY_ENAB | WT_FN_RDYFN | EXT_INT_ENAB); + + /* Send CRC on underrun */ + wrtscc(lp->cardbase, cmd, R0, RES_EOM_L); + + lp->tstate = ACTIVE; + break; + } + /* Get first char to send */ + lp->txcnt--; + c = *lp->txptr++; + /* Reset CRC for next frame */ + wrtscc(lp->cardbase, cmd, R0, RES_Tx_CRC); + + /* send abort on underrun */ + if (lp->nrzi) + { + wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI | ABUNDER); + } else { + wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZ | ABUNDER); + } + /* send first char */ + wrtscc(lp->cardbase, cmd, R8, c); + + /* Reset end of message latch */ + wrtscc(lp->cardbase, cmd, R0, RES_EOM_L); + + /* stuff an extra one in */ +/* while ((rdscc(lp->cardbase, cmd, R0) & Tx_BUF_EMP) && lp->txcnt) + { + lp->txcnt--; + c = *lp->txptr++; + wrtscc(lp->cardbase, cmd, R8, c); + }*/ + + /* select Tx interrupts to enable */ + /* Allow underrun int only */ + wrtscc(lp->cardbase, cmd, R15, TxUIE); + + /* Reset external interrupts */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + + /* Tx and Rx ints enabled */ + wrtscc(lp->cardbase, cmd, R1, TxINT_ENAB | EXT_INT_ENAB); + + lp->tstate = ACTIVE; + restore_flags(flags); + return; + + /* slotime has timed out */ + case DEFER: + /* Check DCD - debounce it + * see Intel Microcommunications Handbook, p2-308 + */ + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT); + if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) + { + lp->tstate = DEFER; + tdelay(lp, 100); + /* DEFER until DCD transition or timeout */ + wrtscc(lp->cardbase, cmd, R15, DCDIE); + restore_flags(flags); + return; + } + if (random() > lp->persist) + { + lp->tstate = DEFER; + tdelay(lp, lp->slotime); + restore_flags(flags); + return; + } + if (lp->dmachan) + wrtscc(lp->cardbase, cmd, R5, TxCRC_ENAB | RTS | Tx8); + pt_rts(lp, ON); /* Tx on */ + lp->tstate = ST_TXDELAY; + tdelay(lp, lp->txdelay); + restore_flags(flags); + return; + + /* Only for int driven parts */ + if (lp->dmachan) + { + restore_flags(flags); + return; + } + + } /* end switch */ + /* + * Rx mode only + * This triggers when hunt mode is entered, & since an ABORT + * automatically enters hunt mode, we use that to clean up + * any waiting garbage + */ + if ((lp->rstate == ACTIVE) && (st & BRK_ABRT) ) + { +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: exisr(): abort detected.\n"); +#endif + /* read and dump all of SCC Rx FIFO */ + (void) rdscc(lp->cardbase, cmd, R8); + (void) rdscc(lp->cardbase, cmd, R8); + (void) rdscc(lp->cardbase, cmd, R8); + + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + + /* Re-sync the SCC */ + wrtscc(lp->cardbase, cmd, R3, RxENABLE | ENT_HM | AUTO_ENAB | Rx8); + + } + + /* Check for DCD transitions */ + if ( (st & DCD) != (lp->saved_RR0 & DCD)) + { +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_exisr(): DCD is now %s.\n", (st & DCD)? "ON" : "OFF" ); +#endif + if (st & DCD) + { + /* Check that we don't already have some data */ + if (lp->rcvbuf->cnt > 0) + { +#ifdef PT_DEBUG + printk(KERN_DEBUG "PT: pt_exisr() dumping %u bytes from buffer.\n", lp->rcvbuf->cnt); +#endif + /* wind back buffers */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + } + } else { /* DCD off */ + + /* read and dump al SCC FIFO */ + (void)rdscc(lp->cardbase, cmd, R8); + (void)rdscc(lp->cardbase, cmd, R8); + (void)rdscc(lp->cardbase, cmd, R8); + + /* wind back buffers */ + lp->rcp = lp->rcvbuf->data; + lp->rcvbuf->cnt = 0; + + /* Re-sync the SCC */ + wrtscc(lp->cardbase, cmd, R3, RxENABLE | ENT_HM | AUTO_ENAB | Rx8); + } + + } + /* Update the saved version of register RR) */ + lp->saved_RR0 = st &~ ZCOUNT; + restore_flags(flags); + +} /* pt_exisr() */ + +#ifdef MODULE +EXPORT_NO_SYMBOLS; + +MODULE_AUTHOR("Craig Small VK2XLZ <vk2xlz@vk2xlz.ampr.org>"); +MODULE_DESCRIPTION("AX.25 driver for the Gracillis PacketTwin HDLC card"); + +int init_module(void) +{ + return pt_init(); +} + +void cleanup_module(void) +{ + free_irq(pt0a.irq, &pt0a); /* IRQs and IO Ports are shared */ + release_region(pt0a.base_addr & 0x3f0, PT_TOTAL_SIZE); + + kfree(pt0a.priv); + pt0a.priv = NULL; + unregister_netdev(&pt0a); + + kfree(pt0b.priv); + pt0b.priv = NULL; + unregister_netdev(&pt0b); +} +#endif diff --git a/drivers/net/hamradio/scc.c b/drivers/net/hamradio/scc.c new file mode 100644 index 000000000..1665d2b2b --- /dev/null +++ b/drivers/net/hamradio/scc.c @@ -0,0 +1,2257 @@ +#define RCS_ID "$Id: scc.c,v 1.71 1997/11/29 19:59:20 jreuter Exp jreuter $" + +#define VERSION "3.0" +#define BANNER "Z8530 SCC driver version "VERSION".dl1bke (experimental) by DL1BKE\n" + +/* + * Please use z8530drv-utils-3.0 with this version. + * ------------------ + */ + +/* + ******************************************************************** + * SCC.C - Linux driver for Z8530 based HDLC cards for AX.25 * + ******************************************************************** + + + ******************************************************************** + + Copyright (c) 1993, 1997 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: + ------------------------------- + + 940913 - 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. + + 950131 - changed copyright notice to GPL without limitations. + + . + . <SNIP> + . + + 961005 - 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. + + 961213 - Fixed for Linux networking changes. (G4KLX) + 970108 - Fixed the remaining problems. + 970402 - Hopefully fixed the problems with the new *_timer() + routines, added calibration code. + 971012 - made SCC_DELAY a CONFIG option, added CONFIG_SCC_TRXECHO + + 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.rutgers.edu. To subscribe send an e-mail + to majordomo@vger.rutgers.edu 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 @ DB0ACH.#NRW.DEU.EU + Internet: jreuter@poboxes.com + www : http://www.rat.de/jr +*/ + +/* ----------------------------------------------------------------------- */ + +#undef SCC_LDELAY 1 /* slow it even a bit more down */ +#undef DONT_CHECK /* don't look if the SCCs you specified are available */ + +#define MAXSCC 4 /* number of max. supported chips */ +#define BUFSIZE 384 /* must not exceed 4096 */ +#define MAXQUEUE 8 /* number of buffers we queue ourself */ +#undef DISABLE_ALL_INTS /* use cli()/sti() in ISR instead of */ + /* enable_irq()/disable_irq() */ +#undef SCC_DEBUG + +#define DEFAULT_CLOCK 4915200 /* default pclock if nothing is specified */ + +/* ----------------------------------------------------------------------- */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.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/malloc.h> +#include <linux/delay.h> + +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/if_ether.h> +#include <linux/if_arp.h> +#include <linux/socket.h> +#include <linux/init.h> + +#include <linux/scc.h> +#include "z8530.h" + +#include <net/ax25.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/bitops.h> + +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <time.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> + +#ifdef MODULE +int init_module(void); +void cleanup_module(void); +#endif + +int scc_init(void); + +static void t_dwait(unsigned long); +static void t_txdelay(unsigned long); +static void t_tail(unsigned long); +static void t_busy(unsigned long); +static void t_maxkeyup(unsigned long); +static void t_idle(unsigned long); +static void scc_tx_done(struct scc_channel *); +static void scc_start_tx_timer(struct scc_channel *, void (*)(unsigned long), 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_isr(int irq, void *dev_id, struct pt_regs *regs); +static void scc_init_timer(struct scc_channel *scc); + +static int scc_net_setup(struct scc_channel *scc, unsigned char *name); +static int scc_net_init(struct device *dev); +static int scc_net_open(struct device *dev); +static int scc_net_close(struct device *dev); +static void scc_net_rx(struct scc_channel *scc, struct sk_buff *skb); +static int scc_net_tx(struct sk_buff *skb, struct device *dev); +static int scc_net_ioctl(struct device *dev, struct ifreq *ifr, int cmd); +static int scc_net_set_mac_address(struct device *dev, void *addr); +static int scc_net_header(struct sk_buff *skb, struct device *dev, unsigned short type, void *daddr, void *saddr, unsigned len); +static struct net_device_stats * scc_net_get_stats(struct device *dev); + +static unsigned char *SCC_DriverName = "scc"; + +static struct irqflags { unsigned char used : 1; } Ivec[16]; + +static struct scc_channel SCC_Info[2 * MAXSCC]; /* information per channel */ + +static struct scc_ctrl { + io_port chan_A; + io_port chan_B; + int irq; +} SCC_ctrl[MAXSCC+1]; + +static unsigned char Driver_Initialized = 0; +static int Nchips = 0; +static io_port Vector_Latch = 0; + +MODULE_AUTHOR("Joerg Reuter <jreuter@poboxes.com>"); +MODULE_DESCRIPTION("Network Device Driver for Z8530 based HDLC cards for Amateur Packet Radio"); +MODULE_SUPPORTED_DEVICE("scc"); + +/* ******************************************************************** */ +/* * Port Access Functions * */ +/* ******************************************************************** */ + +/* These provide interrupt save 2-step access to the Z8530 registers */ + +extern __inline__ unsigned char InReg(io_port port, unsigned char reg) +{ + unsigned long flags; + unsigned char r; + + save_flags(flags); + cli(); +#ifdef SCC_LDELAY + Outb(port, reg); + udelay(SCC_LDELAY); + r=Inb(port); + udelay(SCC_LDELAY); +#else + Outb(port, reg); + r=Inb(port); +#endif + restore_flags(flags); + return r; +} + +extern __inline__ void OutReg(io_port port, unsigned char reg, unsigned char val) +{ + unsigned long flags; + + save_flags(flags); + cli(); +#ifdef SCC_LDELAY + Outb(port, reg); udelay(SCC_LDELAY); + Outb(port, val); udelay(SCC_LDELAY); +#else + Outb(port, reg); + Outb(port, val); +#endif + restore_flags(flags); +} + +extern __inline__ void wr(struct scc_channel *scc, unsigned char reg, + unsigned char val) +{ + OutReg(scc->ctrl, reg, (scc->wreg[reg] = val)); +} + +extern __inline__ void or(struct scc_channel *scc, unsigned char reg, unsigned char val) +{ + OutReg(scc->ctrl, reg, (scc->wreg[reg] |= val)); +} + +extern __inline__ void cl(struct scc_channel *scc, unsigned char reg, unsigned char val) +{ + OutReg(scc->ctrl, reg, (scc->wreg[reg] &= ~val)); +} + +#ifdef DISABLE_ALL_INTS +extern __inline__ void scc_cli(int irq) +{ cli(); } +extern __inline__ void scc_sti(int irq) +{ sti(); } +#else +static __inline__ void scc_cli(int irq) +{ disable_irq(irq); } +static __inline__ void scc_sti(int irq) +{ enable_irq(irq); } +#endif + +/* ******************************************************************** */ +/* * Some useful macros * */ +/* ******************************************************************** */ + + +extern __inline__ void scc_lock_dev(struct scc_channel *scc) +{ + scc->dev->tbusy = 1; +} + +extern __inline__ void scc_unlock_dev(struct scc_channel *scc) +{ + scc->dev->tbusy = 0; +} + +extern __inline__ void scc_discard_buffers(struct scc_channel *scc) +{ + unsigned long flags; + + save_flags(flags); + cli(); + + if (scc->tx_buff != NULL) + { + dev_kfree_skb(scc->tx_buff, FREE_WRITE); + scc->tx_buff = NULL; + } + + while (skb_queue_len(&scc->tx_queue)) + dev_kfree_skb(skb_dequeue(&scc->tx_queue), FREE_WRITE); + + restore_flags(flags); +} + + + +/* ******************************************************************** */ +/* * Interrupt Service Routines * */ +/* ******************************************************************** */ + + +/* ----> subroutines for the interrupt handlers <---- */ + +extern __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++; +} + +extern __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 */ + kfree_skb(scc->rx_buff, FREE_READ); + scc->rx_buff = NULL; + } +} + + +/* ----> four different interrupt handlers for Tx, Rx, changing of */ +/* DCD/CTS and Rx/Tx errors */ + +/* Transmitter interrupt handler */ +extern __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; + scc_unlock_dev(scc); + + if (skb == NULL) + { + scc_tx_done(scc); + Outb(scc->ctrl, RES_Tx_P); + return; + } + + if (skb->len == 0) /* Paranoia... */ + { + dev_kfree_skb(skb, FREE_WRITE); + 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(skb, FREE_WRITE); + 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 */ +extern __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); + + + /* DCD: on = start to receive packet, off = ABORT condition */ + /* (a successfully received packet generates a special condition int) */ + + if(changes & DCD) /* DCD input changed state */ + { + if(status & DCD) /* DCD is now ON */ + { + 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 */ + } else { /* DCD is now OFF */ + cl(scc,R3,ENT_HM|RxENABLE); /* disable the receiver */ + flush_rx_FIFO(scc); + } + + if (!scc->kiss.softdcd) + scc_notify(scc, (status & DCD)? HWEV_DCD_ON:HWEV_DCD_OFF); + } + + /* HUNT: software DCD; on = waiting for SYNC, off = receiving frame */ + + if (changes & SYNC_HUNT) + { + if (scc->kiss.softdcd) + scc_notify(scc, (status & SYNC_HUNT)? HWEV_DCD_OFF:HWEV_DCD_ON); + else + cl(scc,R15,SYNCIE); /* oops, we were too lazy to disable this? */ + } + +#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(scc->tx_buff, FREE_WRITE); + 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 */ +extern __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(skb, 1)) = 0; /* KISS data */ + } + + if (skb->len >= scc->stat.bufsize) + { +#ifdef notdef + printk(KERN_DEBUG "z8530drv: oops, scc_rxint() received huge frame...\n"); +#endif + kfree_skb(skb, FREE_READ); + scc->rx_buff = NULL; + Inb(scc->data); + or(scc, R3, ENT_HM); + return; + } + + *(skb_put(skb, 1)) = Inb(scc->data); +} + + +/* Receive Special Condition interrupt handler */ +extern __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) + kfree_skb(skb, FREE_READ); + scc->rx_buff = 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 */ + kfree_skb(skb, FREE_READ); + 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) +{ + 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; + } +} + +/* 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 void scc_isr(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned char vector; + struct scc_channel *scc; + struct scc_ctrl *ctrl; + int k; + + scc_cli(irq); + + 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 */ + } + scc_sti(irq); + + if (k == SCC_IRQTIMEOUT) + printk(KERN_WARNING "z8530drv: endless loop in scc_isr()?\n"); + + return; + } + + /* 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 != 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 wierd 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++; + } + + scc_sti(irq); +} + + + +/* ******************************************************************** */ +/* * Init Channel */ +/* ******************************************************************** */ + + +/* ----> set SCC channel speed <---- */ + +extern __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 */ +} + +extern __inline__ void set_speed(struct scc_channel *scc) +{ + disable_irq(scc->irq); + + if (scc->modem.speed > 0) /* paranoia... */ + set_brg(scc, (unsigned) (scc->clock / (scc->modem.speed * 64)) - 2); + + enable_irq(scc->irq); +} + + +/* ----> initialize a SCC channel <---- */ + +extern __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((InReg(scc->ctrl,R0)) & DCD) /* DCD is now ON */ + { + if (scc->modem.clocksrc != CLK_EXTERNAL) + or(scc,R14, SEARCH); + + or(scc,R3,ENT_HM|RxENABLE); /* enable the receiver, hunt mode */ + } + + /* enable ABORT, DCD & SYNC/HUNT interrupts */ + + wr(scc,R15, BRKIE|TxUIE|DCDIE); + if (scc->kiss.softdcd) + or(scc,R15, SYNCIE); + + 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); /* 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); + + 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); +#ifdef CONFIG_SCC_TRXECHO + or(scc,R3,RxENABLE|ENT_HM); + or(scc,R15, DCDIE); +#endif + } + } else { + if (tx) + { +#ifdef CONFIG_SCC_TRXECHO + if (scc->kiss.fulldup == KISS_DUPLEX_HALF) + { + cl(scc, R3, RxENABLE); + cl(scc, R15, DCDIE); + } +#endif + + + or(scc,R5,RTS|TxENAB); /* enable tx */ + } else { + cl(scc,R5,RTS|TxENAB); /* disable tx */ + +#ifdef CONFIG_SCC_TRXECHO + if (scc->kiss.fulldup == KISS_DUPLEX_HALF) + { + or(scc, R3, RxENABLE|ENT_HM); + or(scc, R15, DCDIE); + } +#endif + } + } + + enable_irq(scc->irq); +} + + +/* ----> SCC timer interrupt handler and friends. <---- */ + +static void scc_start_tx_timer(struct scc_channel *scc, void (*handler)(unsigned long), unsigned long when) +{ + unsigned long flags; + + + save_flags(flags); + cli(); + + del_timer(&scc->tx_t); + + if (when == 0) + { + handler((unsigned long) scc); + } else + if (when != TIMER_OFF) + { + scc->tx_t.data = (unsigned long) scc; + scc->tx_t.function = handler; + scc->tx_t.expires = jiffies + (when*HZ)/100; + add_timer(&scc->tx_t); + } + + restore_flags(flags); +} + +static void scc_start_defer(struct scc_channel *scc) +{ + unsigned long flags; + + save_flags(flags); + cli(); + + del_timer(&scc->tx_wdog); + + if (scc->kiss.maxdefer != 0 && scc->kiss.maxdefer != TIMER_OFF) + { + scc->tx_wdog.data = (unsigned long) scc; + scc->tx_wdog.function = t_busy; + scc->tx_wdog.expires = jiffies + HZ*scc->kiss.maxdefer; + add_timer(&scc->tx_wdog); + } + restore_flags(flags); +} + +static void scc_start_maxkeyup(struct scc_channel *scc) +{ + unsigned long flags; + + save_flags(flags); + cli(); + + del_timer(&scc->tx_wdog); + + if (scc->kiss.maxkeyup != 0 && scc->kiss.maxkeyup != TIMER_OFF) + { + scc->tx_wdog.data = (unsigned long) scc; + scc->tx_wdog.function = t_maxkeyup; + scc->tx_wdog.expires = jiffies + HZ*scc->kiss.maxkeyup; + add_timer(&scc->tx_wdog); + } + + restore_flags(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); + } + + scc_unlock_dev(scc); +} + + +static unsigned char Rand = 17; + +extern __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->status & 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(unsigned long channel) +{ + struct scc_channel *scc = (struct scc_channel *) channel; + + if (scc->stat.tx_state == TXS_WAIT) /* maxkeyup or idle timeout */ + { + if (skb_queue_len(&scc->tx_queue) == 0) /* nothing to send */ + { + scc->stat.tx_state = TXS_IDLE; + scc_unlock_dev(scc); /* t_maxkeyup locked it. */ + return; + } + + scc->stat.tx_state = TXS_BUSY; + } + + if (scc->kiss.fulldup == KISS_DUPLEX_HALF) + { + Rand = Rand * 17 + 31; + + if ( (scc->kiss.softdcd? !(scc->status & SYNC_HUNT):(scc->status & 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(unsigned long channel) +{ + struct scc_channel *scc = (struct scc_channel *) channel; + + 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(unsigned long channel) +{ + struct scc_channel *scc = (struct scc_channel *) channel; + unsigned long flags; + + save_flags(flags); + cli(); + + del_timer(&scc->tx_wdog); + scc_key_trx(scc, TX_OFF); + + restore_flags(flags); + + if (scc->stat.tx_state == TXS_TIMEOUT) /* we had a timeout? */ + { + scc->stat.tx_state = TXS_WAIT; + + if (scc->kiss.mintime != TIMER_OFF) /* try it again */ + scc_start_tx_timer(scc, t_dwait, scc->kiss.mintime*100); + else + scc_start_tx_timer(scc, t_dwait, 0); + return; + } + + scc->stat.tx_state = TXS_IDLE; + scc_unlock_dev(scc); +} + + +/* BUSY timeout + * + * throw away send buffers if DCD remains active too long. + */ + +static void t_busy(unsigned long channel) +{ + struct scc_channel *scc = (struct scc_channel *) channel; + + del_timer(&scc->tx_t); + scc_lock_dev(scc); + + scc_discard_buffers(scc); + + scc->stat.txerrs++; + scc->stat.tx_state = TXS_IDLE; + + scc_unlock_dev(scc); +} + +/* MAXKEYUP timeout + * + * this is our watchdog. + */ + +static void t_maxkeyup(unsigned long channel) +{ + struct scc_channel *scc = (struct scc_channel *) channel; + unsigned long flags; + + save_flags(flags); + cli(); + + /* + * let things settle down before we start to + * accept new data. + */ + + scc_lock_dev(scc); + 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); + + restore_flags(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(unsigned long channel) +{ + struct scc_channel *scc = (struct scc_channel *) channel; + + del_timer(&scc->tx_wdog); + + scc_key_trx(scc, TX_OFF); + + if (scc->kiss.mintime != TIMER_OFF) + 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; + + save_flags(flags); + cli(); + + scc->stat.tx_state = TXS_IDLE; + + restore_flags(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) +{ + int dcd; + + 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); + else + 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: + dcd = (scc->kiss.softdcd? !(scc->status & SYNC_HUNT):(scc->status & DCD)); + scc_notify(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 +#undef SVAL + +/* ******************************************************************* */ +/* * Send calibration pattern * */ +/* ******************************************************************* */ + +static void scc_stop_calibrate(unsigned long channel) +{ + struct scc_channel *scc = (struct scc_channel *) channel; + unsigned long flags; + + save_flags(flags); + cli(); + + 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); + + scc_unlock_dev(scc); + + restore_flags(flags); +} + + +static void +scc_start_calibrate(struct scc_channel *scc, int duration, unsigned char pattern) +{ + unsigned long flags; + + save_flags(flags); + cli(); + + scc_lock_dev(scc); + scc_discard_buffers(scc); + + del_timer(&scc->tx_wdog); + + scc->tx_wdog.data = (unsigned long) scc; + scc->tx_wdog.function = scc_stop_calibrate; + scc->tx_wdog.expires = jiffies + HZ*scc->kiss.maxkeyup; + add_timer(&scc->tx_wdog); + + 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); + + restore_flags(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 < 16; 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 */ + + save_flags(flags); + cli(); + + 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 */ + + restore_flags(flags); + } + + + Driver_Initialized = 1; +} + +/* + * Allocate device structure, err, instance, and register driver + */ + +static int scc_net_setup(struct scc_channel *scc, unsigned char *name) +{ + unsigned char *buf; + struct device *dev; + + if (dev_get(name) != NULL) + { + printk(KERN_INFO "Z8530drv: device %s already exists.\n", name); + return -EEXIST; + } + + if ((scc->dev = (struct device *) kmalloc(sizeof(struct device), GFP_KERNEL)) == NULL) + return -ENOMEM; + + dev = scc->dev; + memset(dev, 0, sizeof(struct device)); + + buf = (unsigned char *) kmalloc(10, GFP_KERNEL); + strcpy(buf, name); + + dev->priv = (void *) scc; + dev->name = buf; + dev->init = scc_net_init; + + if (register_netdev(dev) != 0) + { + kfree(dev); + return -EIO; + } + + return 0; +} + + + +/* ******************************************************************** */ +/* * Network driver methods * */ +/* ******************************************************************** */ + +static unsigned char ax25_bcast[AX25_ADDR_LEN] = +{'Q' << 1, 'S' << 1, 'T' << 1, ' ' << 1, ' ' << 1, ' ' << 1, '0' << 1}; +static unsigned char ax25_nocall[AX25_ADDR_LEN] = +{'L' << 1, 'I' << 1, 'N' << 1, 'U' << 1, 'X' << 1, ' ' << 1, '1' << 1}; + +/* ----> Initialize device <----- */ + +static int scc_net_init(struct device *dev) +{ + dev_init_buffers(dev); + + dev->tx_queue_len = 16; /* should be enough... */ + + dev->open = scc_net_open; + dev->stop = scc_net_close; + + dev->hard_start_xmit = scc_net_tx; + dev->hard_header = scc_net_header; + dev->rebuild_header = ax25_rebuild_header; + dev->set_mac_address = scc_net_set_mac_address; + dev->get_stats = scc_net_get_stats; + dev->do_ioctl = scc_net_ioctl; + + memcpy(dev->broadcast, ax25_bcast, AX25_ADDR_LEN); + memcpy(dev->dev_addr, ax25_nocall, 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; + + return 0; +} + +/* ----> open network device <---- */ + +static int scc_net_open(struct device *dev) +{ + struct scc_channel *scc = (struct scc_channel *) dev->priv; + + if (scc == NULL || scc->magic != SCC_MAGIC) + return -ENODEV; + + if (!scc->init) + return -EINVAL; + + MOD_INC_USE_COUNT; + + scc->tx_buff = NULL; + skb_queue_head_init(&scc->tx_queue); + + init_channel(scc); + + dev->tbusy = 0; + dev->start = 1; + + return 0; +} + +/* ----> close network device <---- */ + +static int scc_net_close(struct device *dev) +{ + struct scc_channel *scc = (struct scc_channel *) dev->priv; + unsigned long flags; + + if (scc == NULL || scc->magic != SCC_MAGIC) + return -ENODEV; + + MOD_DEC_USE_COUNT; + + save_flags(flags); + cli(); + + Outb(scc->ctrl,0); /* Make sure pointer is written */ + wr(scc,R1,0); /* disable interrupts */ + wr(scc,R3,0); + + del_timer(&scc->tx_t); + del_timer(&scc->tx_wdog); + + restore_flags(flags); + + scc_discard_buffers(scc); + + dev->tbusy = 1; + dev->start = 0; + + 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) + { + kfree_skb(skb, FREE_READ); + return; + } + + scc->dev_stat.rx_packets++; + + skb->dev = scc->dev; + skb->protocol = htons(ETH_P_AX25); + skb->mac.raw = skb->data; + + netif_rx(skb); + return; +} + +/* ----> transmit frame <---- */ + +static int scc_net_tx(struct sk_buff *skb, struct device *dev) +{ + struct scc_channel *scc = (struct scc_channel *) dev->priv; + unsigned long flags; + char kisscmd; + + if (scc == NULL || scc->magic != SCC_MAGIC || dev->tbusy) + { + dev_kfree_skb(skb, FREE_WRITE); + return 0; + } + + if (skb->len > scc->stat.bufsize || skb->len < 2) + { + scc->dev_stat.tx_dropped++; /* bogus frame */ + dev_kfree_skb(skb, FREE_WRITE); + return 0; + } + + scc->dev_stat.tx_packets++; + scc->stat.txframes++; + + kisscmd = *skb->data & 0x1f; + skb_pull(skb, 1); + + if (kisscmd) + { + scc_set_param(scc, kisscmd, *skb->data); + dev_kfree_skb(skb, FREE_WRITE); + return 0; + } + + save_flags(flags); + cli(); + + if (skb_queue_len(&scc->tx_queue) >= MAXQUEUE-1) + { + struct sk_buff *skb_del; + skb_del = __skb_dequeue(&scc->tx_queue); + dev_kfree_skb(skb_del, FREE_WRITE); + } + __skb_queue_tail(&scc->tx_queue, skb); + + dev->trans_start = jiffies; + + /* + * Start transmission if the trx state is idle or + * t_idle hasn't expired yet. Use dwait/persistance/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); + } + + restore_flags(flags); + + return 0; +} + +/* ----> 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 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; + int chan; + unsigned char device_name[10]; + void *arg; + struct scc_channel *scc; + + scc = (struct scc_channel *) dev->priv; + if (scc == NULL || scc->magic != SCC_MAGIC) + return -EINVAL; + + arg = (void *) ifr->ifr_data; + + if (!Driver_Initialized) + { + if (cmd == SIOCSCCCFG) + { + int found = 1; + + if (!suser()) return -EPERM; + if (!arg) return -EFAULT; + + if (Nchips >= MAXSCC) + return -EINVAL; + + if (copy_from_user(&hwcfg, arg, sizeof(hwcfg))) + return -EFAULT; + + if (hwcfg.irq == 2) hwcfg.irq = 9; + + if (!Ivec[hwcfg.irq].used && hwcfg.irq) + { + if (request_irq(hwcfg.irq, scc_isr, SA_INTERRUPT, "AX.25 SCC", NULL)) + printk(KERN_WARNING "z8530drv: warning, cannot get IRQ %d\n", hwcfg.irq); + else + Ivec[hwcfg.irq].used = 1; + } + + if (hwcfg.vector_latch) + Vector_Latch = hwcfg.vector_latch; + + if (hwcfg.clock == 0) + hwcfg.clock = DEFAULT_CLOCK; + +#ifndef DONT_CHECK + disable_irq(hwcfg.irq); + + check_region(scc->ctrl, 1); + Outb(hwcfg.ctrl_a, 0); + udelay(5); + 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); +#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 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_setup(&SCC_Info[2*Nchips+chan], device_name); + } + } + + if (found) Nchips++; + + return 0; + } + + if (cmd == SIOCSCCINI) + { + if (!suser()) + return -EPERM; + + if (Nchips == 0) + return -EINVAL; + + z8530_init(); + return 0; + } + + return -EINVAL; /* confuse the user */ + } + + if (!scc->init) + { + if (cmd == SIOCSCCCHANINI) + { + if (!suser()) return -EPERM; + if (!arg) return -EINVAL; + + scc->stat.bufsize = 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 (!suser()) 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 (!suser()) 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 (!suser()) return -EPERM; + if (!arg || copy_from_user(&cal, arg, sizeof(cal))) + 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 device *dev, void *addr) +{ + struct sockaddr *sa = (struct sockaddr *) addr; + memcpy(dev->dev_addr, sa->sa_data, dev->addr_len); + return 0; +} + +/* ----> "hard" header <---- */ + +static int scc_net_header(struct sk_buff *skb, struct device *dev, + unsigned short type, void *daddr, void *saddr, unsigned len) +{ + return ax25_encapsulate(skb, dev, type, daddr, saddr, len); +} + +/* ----> get statistics <---- */ + +static struct net_device_stats *scc_net_get_stats(struct device *dev) +{ + struct scc_channel *scc = (struct scc_channel *) dev->priv; + + if (scc == NULL || scc->magic != SCC_MAGIC) + return NULL; + + 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 * */ +/* ******************************************************************** */ + + +static int scc_net_get_info(char *buffer, char **start, off_t offset, int length, int dummy) +{ + struct scc_channel *scc; + struct scc_kiss *kiss; + struct scc_stat *stat; + int len = 0; + off_t pos = 0; + off_t begin = 0; + int k; + + len += sprintf(buffer, "z8530drv-"VERSION"\n"); + + if (!Driver_Initialized) + { + len += sprintf(buffer+len, "not initialized\n"); + goto done; + } + + if (!Nchips) + { + len += sprintf(buffer+len, "chips missing\n"); + goto done; + } + + for (k = 0; k < Nchips*2; k++) + { + scc = &SCC_Info[k]; + stat = &scc->stat; + kiss = &scc->kiss; + + if (!scc->init) + continue; + + /* 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 ## ## ## ## ## ## ## + */ + + len += sprintf(buffer+len, "%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); + len += sprintf(buffer+len, "\t%lu %d %d %d %d\n", + scc->modem.speed, scc->modem.nrz, + scc->modem.clocksrc, kiss->softdcd, + stat->bufsize); + len += sprintf(buffer+len, "\t%lu %lu %lu %lu\n", + stat->rxints, stat->txints, stat->exints, stat->spints); + len += sprintf(buffer+len, "\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 + len += sprintf(buffer+len, "\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; + + len += sprintf(buffer+len, "\tW "); + for (reg = 0; reg < 16; reg++) + len += sprintf(buffer+len, "%2.2x ", scc->wreg[reg]); + len += sprintf(buffer+len, "\n"); + + len += sprintf(buffer+len, "\tR %2.2x %2.2x XX ", InReg(scc->ctrl,R0), InReg(scc->ctrl,R1)); + for (reg = 3; reg < 8; reg++) + len += sprintf(buffer+len, "%2.2x ", InReg(scc->ctrl, reg)); + len += sprintf(buffer+len, "XX "); + for (reg = 9; reg < 16; reg++) + len += sprintf(buffer+len, "%2.2x ", InReg(scc->ctrl, reg)); + len += sprintf(buffer+len, "\n"); + } +#endif + len += sprintf(buffer+len, "\n"); + + pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + + if (pos > offset + length) + break; + } + +done: + + *start = buffer + (offset - begin); + len -= (offset - begin); + + if (len > length) len = length; + + return len; +} + +#ifdef CONFIG_PROC_FS + +struct proc_dir_entry scc_proc_dir_entry = +{ + PROC_NET_Z8530, 8, "z8530drv", S_IFREG | S_IRUGO, 1, 0, 0, 0, + &proc_net_inode_operations, scc_net_get_info +}; + +#define scc_net_procfs_init() proc_net_register(&scc_proc_dir_entry); +#define scc_net_procfs_remove() proc_net_unregister(PROC_NET_Z8530); +#else +#define scc_net_procfs_init() +#define scc_net_procfs_remove() +#endif + + +/* ******************************************************************** */ +/* * Init SCC driver * */ +/* ******************************************************************** */ + +__initfunc(int scc_init (void)) +{ + int chip, chan, k, result; + char devname[10]; + + printk(KERN_INFO BANNER); + + memset(&SCC_ctrl, 0, sizeof(SCC_ctrl)); + + /* pre-init channel information */ + + for (chip = 0; chip < MAXSCC; chip++) + { + memset((char *) &SCC_Info[2*chip ], 0, sizeof(struct scc_channel)); + memset((char *) &SCC_Info[2*chip+1], 0, sizeof(struct scc_channel)); + + for (chan = 0; chan < 2; chan++) + SCC_Info[2*chip+chan].magic = SCC_MAGIC; + } + + for (k = 0; k < 16; k++) Ivec[k].used = 0; + + sprintf(devname,"%s0", SCC_DriverName); + + result = scc_net_setup(SCC_Info, devname); + if (result) + { + printk(KERN_ERR "z8530drv: cannot initialize module\n"); + return result; + } + + scc_net_procfs_init(); + + return 0; +} + +/* ******************************************************************** */ +/* * Module support * */ +/* ******************************************************************** */ + + +#ifdef MODULE +int init_module(void) +{ + int result = 0; + + result = scc_init(); + + if (result == 0) + printk(KERN_INFO "Copyright 1993,1997 Joerg Reuter DL1BKE (jreuter@poboxes.com)\n"); + + return result; +} + +void cleanup_module(void) +{ + long flags; + io_port ctrl; + int k; + struct scc_channel *scc; + + save_flags(flags); + cli(); + + if (Nchips == 0) + unregister_netdev(SCC_Info[0].dev); + + 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); + } + + for (k = 0; k < Nchips*2; k++) + { + scc = &SCC_Info[k]; + if (scc) + { + release_region(scc->ctrl, 1); + release_region(scc->data, 1); + if (scc->dev) + { + unregister_netdev(scc->dev); + kfree(scc->dev); + } + } + } + + for (k=0; k < 16 ; k++) + if (Ivec[k].used) free_irq(k, NULL); + + restore_flags(flags); + + scc_net_procfs_remove(); +} +#endif diff --git a/drivers/net/hamradio/soundmodem/.cvsignore b/drivers/net/hamradio/soundmodem/.cvsignore new file mode 100644 index 000000000..4671378ae --- /dev/null +++ b/drivers/net/hamradio/soundmodem/.cvsignore @@ -0,0 +1 @@ +.depend diff --git a/drivers/net/hamradio/soundmodem/Makefile b/drivers/net/hamradio/soundmodem/Makefile new file mode 100644 index 000000000..1aabdd9e8 --- /dev/null +++ b/drivers/net/hamradio/soundmodem/Makefile @@ -0,0 +1,60 @@ +# +# Makefile for the soundmodem device driver. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now inherited from the +# parent makes.. +# + +O_TARGET := soundmodem.o + +O_OBJS := sm.o +ifeq ($(CONFIG_SOUNDMODEM_SBC),y) +O_OBJS += sm_sbc.o +endif +ifeq ($(CONFIG_SOUNDMODEM_WSS),y) +O_OBJS += sm_wss.o +endif +ifeq ($(CONFIG_SOUNDMODEM_AFSK1200),y) +O_OBJS += sm_afsk1200.o +endif +ifeq ($(CONFIG_SOUNDMODEM_AFSK2400_7),y) +O_OBJS += sm_afsk2400_7.o +endif +ifeq ($(CONFIG_SOUNDMODEM_AFSK2400_8),y) +O_OBJS += sm_afsk2400_8.o +endif +ifeq ($(CONFIG_SOUNDMODEM_AFSK2666),y) +O_OBJS += sm_afsk2666.o +endif +ifeq ($(CONFIG_SOUNDMODEM_HAPN4800),y) +O_OBJS += sm_hapn4800.o +endif +ifeq ($(CONFIG_SOUNDMODEM_PSK4800),y) +O_OBJS += sm_psk4800.o +endif +ifeq ($(CONFIG_SOUNDMODEM_FSK9600),y) +O_OBJS += sm_fsk9600.o +endif + +M_OBJS := $(O_TARGET) + +all: all_targets +.PHONY: all + +gentbl: gentbl.c + $(HOSTCC) -Wall $< -o $@ -lm + +TBLHDR := sm_tbl_afsk1200.h sm_tbl_afsk2400_8.h +TBLHDR += sm_tbl_afsk2666.h sm_tbl_psk4800.h +TBLHDR += sm_tbl_hapn4800.h sm_tbl_fsk9600.h + +$(TBLHDR): gentbl + ./gentbl + +fastdep: $(TBLHDR) + +include $(TOPDIR)/Rules.make diff --git a/drivers/net/hamradio/soundmodem/gentbl.c b/drivers/net/hamradio/soundmodem/gentbl.c new file mode 100644 index 000000000..d1dce6687 --- /dev/null +++ b/drivers/net/hamradio/soundmodem/gentbl.c @@ -0,0 +1,676 @@ +/*****************************************************************************/ + +/* + * gentbl.c -- soundcard radio modem driver table generator. + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + */ + +#include <stdio.h> +#include <math.h> +#include <string.h> + +/* -------------------------------------------------------------------- */ + +static void gentbl_offscostab(FILE *f, unsigned int nbits) +{ + int i; + + fprintf(f, "\n/*\n * small cosine table in U8 format\n */\n" + "#define OFFSCOSTABBITS %u\n" + "#define OFFSCOSTABSIZE (1<<OFFSCOSTABBITS)\n\n", + nbits); + fprintf(f, "static unsigned char offscostab[OFFSCOSTABSIZE] = {\n\t"); + for (i = 0; i < (1<<nbits); i++) { + fprintf(f, "%4u", (int) + (128+127.0*cos(i*2.0*M_PI/(1<<nbits)))); + if (i < (1<<nbits)-1) + fprintf(f, "%s", (i & 7) == 7 ? ",\n\t" : ","); + } + fprintf(f, "\n};\n\n" + "#define OFFSCOS(x) offscostab[((x)>>%d)&0x%x]\n\n", + 16-nbits, (1<<nbits)-1); +} + +/* -------------------------------------------------------------------- */ + +static void gentbl_costab(FILE *f, unsigned int nbits) +{ + int i; + + fprintf(f, "\n/*\n * more accurate cosine table\n */\n\n" + "static const short costab[%d] = {", (1<<nbits)); + for (i = 0; i < (1<<nbits); i++) { + if (!(i & 7)) + fprintf(f, "\n\t"); + fprintf(f, "%6d", (int)(32767.0*cos(i*2.0*M_PI/(1<<nbits)))); + if (i != ((1<<nbits)-1)) + fprintf(f, ", "); + } + fprintf(f, "\n};\n\n#define COS(x) costab[((x)>>%d)&0x%x]\n" + "#define SIN(x) COS((x)+0xc000)\n\n", 16-nbits, + (1<<nbits)-1); +} + +/* -------------------------------------------------------------------- */ + +#define AFSK12_SAMPLE_RATE 9600 +#define AFSK12_TX_FREQ_LO 1200 +#define AFSK12_TX_FREQ_HI 2200 +#define AFSK12_CORRLEN 8 + +static void gentbl_afsk1200(FILE *f) +{ + int i, v, sum; + +#define ARGLO(x) 2.0*M_PI*(double)x*(double)AFSK12_TX_FREQ_LO/(double)AFSK12_SAMPLE_RATE +#define ARGHI(x) 2.0*M_PI*(double)x*(double)AFSK12_TX_FREQ_HI/(double)AFSK12_SAMPLE_RATE + + fprintf(f, "\n/*\n * afsk1200 specific tables\n */\n" + "#define AFSK12_SAMPLE_RATE %u\n" + "#define AFSK12_TX_FREQ_LO %u\n" + "#define AFSK12_TX_FREQ_HI %u\n" + "#define AFSK12_CORRLEN %u\n\n", + AFSK12_SAMPLE_RATE, AFSK12_TX_FREQ_LO, + AFSK12_TX_FREQ_HI, AFSK12_CORRLEN); + fprintf(f, "static const int afsk12_tx_lo_i[] = {\n\t"); + for(sum = i = 0; i < AFSK12_CORRLEN; i++) { + sum += (v = 127.0*cos(ARGLO(i))); + fprintf(f, " %4i%c", v, (i < AFSK12_CORRLEN-1) ? ',' : ' '); + } + fprintf(f, "\n};\n#define SUM_AFSK12_TX_LO_I %d\n\n" + "static const int afsk12_tx_lo_q[] = {\n\t", sum); + for(sum = i = 0; i < AFSK12_CORRLEN; i++) { + sum += (v = 127.0*sin(ARGLO(i))); + fprintf(f, " %4i%c", v, (i < AFSK12_CORRLEN-1) ? ',' : ' '); + } + fprintf(f, "\n};\n#define SUM_AFSK12_TX_LO_Q %d\n\n" + "static const int afsk12_tx_hi_i[] = {\n\t", sum); + for(sum = i = 0; i < AFSK12_CORRLEN; i++) { + sum += (v = 127.0*cos(ARGHI(i))); + fprintf(f, " %4i%c", v, (i < AFSK12_CORRLEN-1) ? ',' : ' '); + } + fprintf(f, "\n};\n#define SUM_AFSK12_TX_HI_I %d\n\n" + "static const int afsk12_tx_hi_q[] = {\n\t", sum); + for(sum = i = 0; i < AFSK12_CORRLEN; i++) { + sum += (v = 127.0*sin(ARGHI(i))); + fprintf(f, " %4i%c", v, (i < AFSK12_CORRLEN-1) ? ',' : ' '); + } + fprintf(f, "\n};\n#define SUM_AFSK12_TX_HI_Q %d\n\n", sum); +#undef ARGLO +#undef ARGHI +} + +/* -------------------------------------------------------------------- */ + +static const float fsk96_tx_coeff_4[32] = { + -0.001152, 0.000554, 0.002698, 0.002753, + -0.002033, -0.008861, -0.008002, 0.006607, + 0.023727, 0.018905, -0.018056, -0.057957, + -0.044368, 0.055683, 0.207667, 0.322048, + 0.322048, 0.207667, 0.055683, -0.044368, + -0.057957, -0.018056, 0.018905, 0.023727, + 0.006607, -0.008002, -0.008861, -0.002033, + 0.002753, 0.002698, 0.000554, -0.001152 +}; + +static const float fsk96_tx_coeff_5[40] = { + -0.001009, -0.000048, 0.001376, 0.002547, + 0.002061, -0.001103, -0.005795, -0.008170, + -0.004017, 0.006924, 0.018225, 0.019238, + 0.002925, -0.025777, -0.048064, -0.039683, + 0.013760, 0.104144, 0.200355, 0.262346, + 0.262346, 0.200355, 0.104144, 0.013760, + -0.039683, -0.048064, -0.025777, 0.002925, + 0.019238, 0.018225, 0.006924, -0.004017, + -0.008170, -0.005795, -0.001103, 0.002061, + 0.002547, 0.001376, -0.000048, -0.001009 +}; + +#define HAMMING(x) (0.54-0.46*cos(2*M_PI*(x))); + +static inline float hamming(float x) +{ + return 0.54-0.46*cos(2*M_PI*x); +} + +static inline float sinc(float x) +{ + if (x == 0) + return 1; + x *= M_PI; + return sin(x)/x; +} + +static void gentbl_fsk9600(FILE *f) +{ + int i, j, k, l, m; + float s; + float c[44]; + float min, max; + + fprintf(f, "\n/*\n * fsk9600 specific tables\n */\n"); + min = max = 0; + memset(c, 0, sizeof(c)); +#if 0 + memcpy(c+2, fsk96_tx_coeff_4, sizeof(fsk96_tx_coeff_4)); +#else + for (i = 0; i < 29; i++) + c[3+i] = sinc(1.2*((i-14.0)/4.0))*hamming(i/28.0)/3.5; +#endif + fprintf(f, "static unsigned char fsk96_txfilt_4[] = {\n\t"); + for (i = 0; i < 4; i++) { + for (j = 0; j < 256; j++) { + for (k = 1, s = 0, l = i; k < 256; k <<= 1) { + if (j & k) { + for (m = 0; m < 4; m++, l++) + s += c[l]; + } else { + for (m = 0; m < 4; m++, l++) + s -= c[l]; + } + } + s *= 0.75; + if (s > max) + max = s; + if (s < min) + min = s; + fprintf(f, "%4d", (int)(128+127*s)); + if (i < 3 || j < 255) + fprintf(f, ",%s", (j & 7) == 7 + ? "\n\t" : ""); + } + } + fprintf(stderr, "fsk9600: txfilt4: min = %f; max = %f\n", min, max); + fprintf(f, "\n};\n\n"); + min = max = 0; + memset(c, 0, sizeof(c)); +#if 0 + memcpy(c+2, fsk96_tx_coeff_5, sizeof(fsk96_tx_coeff_5)); +#else + for (i = 0; i < 36; i++) + c[4+i] = sinc(1.2*((i-17.5)/5.0))*hamming(i/35.0)/4.5; +#endif + fprintf(f, "static unsigned char fsk96_txfilt_5[] = {\n\t"); + for (i = 0; i < 5; i++) { + for (j = 0; j < 256; j++) { + for (k = 1, s = 0, l = i; k < 256; k <<= 1) { + if (j & k) { + for (m = 0; m < 5; m++, l++) + s += c[l]; + } else { + for (m = 0; m < 5; m++, l++) + s -= c[l]; + } + } + s *= 0.75; + if (s > max) + max = s; + if (s < min) + min = s; + fprintf(f, "%4d", (int)(128+127*s)); + if (i < 4 || j < 255) + fprintf(f, ",%s", (j & 7) == 7 + ? "\n\t" : ""); + } + } + fprintf(stderr, "fsk9600: txfilt5: min = %f; max = %f\n", min, max); + fprintf(f, "\n};\n\n"); +} + +/* -------------------------------------------------------------------- */ + +#define AFSK26_SAMPLERATE 16000 + +#define AFSK26_NUMCAR 2 +#define AFSK26_FIRSTCAR 2000 +#define AFSK26_MSK_LEN 6 +#define AFSK26_RXOVER 2 + +#define AFSK26_DEMCORRLEN (2*AFSK26_MSK_LEN) + +#define AFSK26_WINDOW(x) ((1-cos(2.0*M_PI*(x)))/2.0) + +#define AFSK26_AMPL(x) (((x)?1.0:0.7)) + +#undef AFSK26_AMPL +#define AFSK26_AMPL(x) 1 + +static void gentbl_afsk2666(FILE *f) +{ + int i, j, k, l, o, v, sumi, sumq; + float window[AFSK26_DEMCORRLEN*AFSK26_RXOVER]; + int cfreq[AFSK26_NUMCAR]; + + fprintf(f, "\n/*\n * afsk2666 specific tables\n */\n" + "#define AFSK26_DEMCORRLEN %d\n" + "#define AFSK26_SAMPLERATE %d\n\n", AFSK26_DEMCORRLEN, + AFSK26_SAMPLERATE); + fprintf(f, "static const unsigned int afsk26_carfreq[%d] = { ", + AFSK26_NUMCAR); + for (i = 0; i < AFSK26_NUMCAR; i++) { + cfreq[i] = 0x10000*AFSK26_FIRSTCAR/AFSK26_SAMPLERATE+ + 0x10000*i/AFSK26_MSK_LEN/2; + fprintf(f, "0x%x", cfreq[i]); + if (i < AFSK26_NUMCAR-1) + fprintf(f, ", "); + } + fprintf(f, " };\n\n"); + for (i = 0; i < AFSK26_DEMCORRLEN*AFSK26_RXOVER; i++) + window[i] = AFSK26_WINDOW(((float)i)/(AFSK26_DEMCORRLEN* + AFSK26_RXOVER)) * 127.0; + fprintf(f, "\nstatic const struct {\n\t" + "int i[%d];\n\tint q[%d];\n} afsk26_dem_tables[%d][%d] = {\n", + AFSK26_DEMCORRLEN, AFSK26_DEMCORRLEN, AFSK26_RXOVER, AFSK26_NUMCAR); + for (o = AFSK26_RXOVER-1; o >= 0; o--) { + fprintf(f, "\t{\n"); + for (i = 0; i < AFSK26_NUMCAR; i++) { + j = cfreq[i]; + fprintf(f, "\t\t{{ "); + for (l = AFSK26_DEMCORRLEN-1, k = (j * o)/AFSK26_RXOVER, sumi = 0; l >= 0; + l--, k = (k+j)&0xffffu) { + sumi += (v = AFSK26_AMPL(i)*window[l*AFSK26_RXOVER+o]* + cos(M_PI*k/32768.0)); + fprintf(f, "%6d%s", v, l ? ", " : " }, { "); + } + for (l = AFSK26_DEMCORRLEN-1, k = (j * o)/AFSK26_RXOVER, sumq = 0; l >= 0; + l--, k = (k+j)&0xffffu) { + sumq += (v = AFSK26_AMPL(i)*window[l*AFSK26_RXOVER+o]* + sin(M_PI*k/32768.0)); + fprintf(f, "%6d%s", v, l ? ", " : " }}"); + } + if (i < 1) + fprintf(f, ","); + fprintf(f, "\n#define AFSK26_DEM_SUM_I_%d_%d %d\n" + "#define AFSK26_DEM_SUM_Q_%d_%d %d\n", + AFSK26_RXOVER-1-o, i, sumi, AFSK26_RXOVER-1-o, i, sumq); + } + fprintf(f, "\t}%s\n", o ? "," : ""); + } + fprintf(f, "};\n\n"); +} + +/* -------------------------------------------------------------------- */ + +#define ATAN_TABLEN 1024 + +static void gentbl_atantab(FILE *f) +{ + int i; + short x; + + fprintf(f, "\n/*\n" + " * arctan table (indexed by i/q; should really be indexed by i/(i+q)\n" + " */\n""#define ATAN_TABLEN %d\n\n" + "static const unsigned short atan_tab[ATAN_TABLEN+2] = {", + ATAN_TABLEN); + for (i = 0; i <= ATAN_TABLEN; i++) { + if (!(i & 7)) + fprintf(f, "\n\t"); + x = atan(i / (float)ATAN_TABLEN) / M_PI * 0x8000; + fprintf(f, "%6d, ", x); + } + fprintf(f, "%6d\n};\n\n", x); + +} + +/* -------------------------------------------------------------------- */ + +#define PSK48_TXF_OVERSAMPLING 5 +#define PSK48_TXF_NUMSAMPLES 16 +#define PSK48_RXF_LEN 64 + +static const float psk48_tx_coeff[80] = { + -0.000379, -0.000640, -0.000000, 0.000772, + 0.000543, -0.000629, -0.001187, -0.000000, + 0.001634, 0.001183, -0.001382, -0.002603, + -0.000000, 0.003481, 0.002472, -0.002828, + -0.005215, -0.000000, 0.006705, 0.004678, + -0.005269, -0.009584, -0.000000, 0.012065, + 0.008360, -0.009375, -0.017028, -0.000000, + 0.021603, 0.015123, -0.017229, -0.032012, + -0.000000, 0.043774, 0.032544, -0.040365, + -0.084963, -0.000000, 0.201161, 0.374060, + 0.374060, 0.201161, -0.000000, -0.084963, + -0.040365, 0.032544, 0.043774, -0.000000, + -0.032012, -0.017229, 0.015123, 0.021603, + -0.000000, -0.017028, -0.009375, 0.008360, + 0.012065, -0.000000, -0.009584, -0.005269, + 0.004678, 0.006705, -0.000000, -0.005215, + -0.002828, 0.002472, 0.003481, -0.000000, + -0.002603, -0.001382, 0.001183, 0.001634, + -0.000000, -0.001187, -0.000629, 0.000543, + 0.000772, -0.000000, -0.000640, -0.000379 +}; + +static const float psk48_rx_coeff[PSK48_RXF_LEN] = { + -0.000219, 0.000360, 0.000873, 0.001080, + 0.000747, -0.000192, -0.001466, -0.002436, + -0.002328, -0.000699, 0.002101, 0.004809, + 0.005696, 0.003492, -0.001633, -0.007660, + -0.011316, -0.009627, -0.001780, 0.009712, + 0.019426, 0.021199, 0.011342, -0.008583, + -0.030955, -0.044093, -0.036634, -0.002651, + 0.054742, 0.123101, 0.184198, 0.220219, + 0.220219, 0.184198, 0.123101, 0.054742, + -0.002651, -0.036634, -0.044093, -0.030955, + -0.008583, 0.011342, 0.021199, 0.019426, + 0.009712, -0.001780, -0.009627, -0.011316, + -0.007660, -0.001633, 0.003492, 0.005696, + 0.004809, 0.002101, -0.000699, -0.002328, + -0.002436, -0.001466, -0.000192, 0.000747, + 0.001080, 0.000873, 0.000360, -0.000219 +}; + +static void gentbl_psk4800(FILE *f) +{ + int i, j, k; + short x; + + fprintf(f, "\n/*\n * psk4800 specific tables\n */\n" + "#define PSK48_TXF_OVERSAMPLING %d\n" + "#define PSK48_TXF_NUMSAMPLES %d\n\n" + "#define PSK48_SAMPLERATE 8000\n" + "#define PSK48_CAR_FREQ 2000\n" + "#define PSK48_PSK_LEN 5\n" + "#define PSK48_RXF_LEN %u\n" + "#define PSK48_PHASEINC (0x10000*PSK48_CAR_FREQ/PSK48_SAMPLERATE)\n" + "#define PSK48_SPHASEINC (0x10000/(2*PSK48_PSK_LEN))\n\n" + "static const short psk48_tx_table[PSK48_TXF_OVERSAMPLING*" + "PSK48_TXF_NUMSAMPLES*8*2] = {", + PSK48_TXF_OVERSAMPLING, PSK48_TXF_NUMSAMPLES, PSK48_RXF_LEN); + for (i = 0; i < PSK48_TXF_OVERSAMPLING; i++) { + for (j = 0; j < PSK48_TXF_NUMSAMPLES; j++) { + fprintf(f, "\n\t"); + for (k = 0; k < 8; k++) { + x = 32767.0 * cos(k*M_PI/4.0) * + psk48_tx_coeff[j * PSK48_TXF_OVERSAMPLING + i]; + fprintf(f, "%6d, ", x); + } + fprintf(f, "\n\t"); + for (k = 0; k < 8; k++) { + x = 32767.0 * sin(k*M_PI/4.0) * + psk48_tx_coeff[j * PSK48_TXF_OVERSAMPLING + i]; + fprintf(f, "%6d", x); + if (k != 7 || j != PSK48_TXF_NUMSAMPLES-1 || + i != PSK48_TXF_OVERSAMPLING-1) + fprintf(f, ", "); + } + } + } + fprintf(f, "\n};\n\n"); + + fprintf(f, "static const short psk48_rx_coeff[PSK48_RXF_LEN] = {\n\t"); + for (i = 0; i < PSK48_RXF_LEN; i++) { + fprintf(f, "%6d", (int)(psk48_rx_coeff[i]*32767.0)); + if (i < PSK48_RXF_LEN-1) + fprintf(f, ",%s", (i & 7) == 7 ? "\n\t" : ""); + } + fprintf(f, "\n};\n\n"); +} + +/* -------------------------------------------------------------------- */ + +static void gentbl_hapn4800(FILE *f) +{ + int i, j, k, l; + float s; + float c[40]; + float min, max; + + fprintf(f, "\n/*\n * hapn4800 specific tables\n */\n\n"); + /* + * firstly generate tables for the FM transmitter modulator + */ + min = max = 0; + memset(c, 0, sizeof(c)); + for (i = 0; i < 24; i++) + c[8+i] = sinc(1.5*((i-11.5)/8.0))*hamming(i/23.0)/2.4; + for (i = 0; i < 24; i++) + c[i] -= c[i+8]; + fprintf(f, "static unsigned char hapn48_txfilt_8[] = {\n\t"); + for (i = 0; i < 8; i++) { + for (j = 0; j < 16; j++) { + for (k = 1, s = 0, l = i; k < 16; k <<= 1, l += 8) { + if (j & k) + s += c[l]; + else + s -= c[l]; + } + if (s > max) + max = s; + if (s < min) + min = s; + fprintf(f, "%4d", (int)(128+127*s)); + if (i < 7 || j < 15) + fprintf(f, ",%s", (j & 7) == 7 + ? "\n\t" : ""); + } + } + fprintf(stderr, "hapn4800: txfilt8: min = %f; max = %f\n", min, max); + fprintf(f, "\n};\n\n"); + min = max = 0; + memset(c, 0, sizeof(c)); + for (i = 0; i < 30; i++) + c[10+i] = sinc(1.5*((i-14.5)/10.0))*hamming(i/29.0)/2.4; + for (i = 0; i < 30; i++) + c[i] -= c[i+10]; + fprintf(f, "static unsigned char hapn48_txfilt_10[] = {\n\t"); + for (i = 0; i < 10; i++) { + for (j = 0; j < 16; j++) { + for (k = 1, s = 0, l = i; k < 16; k <<= 1, l += 10) { + if (j & k) + s += c[l]; + else + s -= c[l]; + } + if (s > max) + max = s; + if (s < min) + min = s; + fprintf(f, "%4d", (int)(128+127*s)); + if (i < 9 || j < 15) + fprintf(f, ",%s", (j & 7) == 7 + ? "\n\t" : ""); + } + } + fprintf(stderr, "hapn4800: txfilt10: min = %f; max = %f\n", min, max); + fprintf(f, "\n};\n\n"); + /* + * secondly generate tables for the PM transmitter modulator + */ + min = max = 0; + memset(c, 0, sizeof(c)); + for (i = 0; i < 25; i++) + c[i] = sinc(1.4*((i-12.0)/8.0))*hamming(i/24.0)/6.3; + for (i = 0; i < 25; i++) + for (j = 1; j < 8; j++) + c[i] += c[i+j]; + fprintf(f, "static unsigned char hapn48_txfilt_pm8[] = {\n\t"); + for (i = 0; i < 8; i++) { + for (j = 0; j < 16; j++) { + for (k = 1, s = 0, l = i; k < 16; k <<= 1, l += 8) { + if (j & k) + s += c[l]; + else + s -= c[l]; + } + if (s > max) + max = s; + if (s < min) + min = s; + fprintf(f, "%4d", (int)(128+127*s)); + if (i < 7 || j < 15) + fprintf(f, ",%s", (j & 7) == 7 + ? "\n\t" : ""); + } + } + fprintf(stderr, "hapn4800: txfiltpm8: min = %f; max = %f\n", min, max); + fprintf(f, "\n};\n\n"); + min = max = 0; + memset(c, 0, sizeof(c)); + for (i = 0; i < 31; i++) + c[10+i] = sinc(1.4*((i-15.0)/10.0))*hamming(i/30.0)/7.9; + for (i = 0; i < 31; i++) + for (j = 1; j < 10; j++) + c[i] += c[i+j]; + fprintf(f, "static unsigned char hapn48_txfilt_pm10[] = {\n\t"); + for (i = 0; i < 10; i++) { + for (j = 0; j < 16; j++) { + for (k = 1, s = 0, l = i; k < 16; k <<= 1, l += 10) { + if (j & k) + s += c[l]; + else + s -= c[l]; + } + if (s > max) + max = s; + if (s < min) + min = s; + fprintf(f, "%4d", (int)(128+127*s)); + if (i < 9 || j < 15) + fprintf(f, ",%s", (j & 7) == 7 + ? "\n\t" : ""); + } + } + fprintf(stderr, "hapn4800: txfiltpm10: min = %f; max = %f\n", min, max); + fprintf(f, "\n};\n\n"); + +} + +/* -------------------------------------------------------------------- */ + +#define AFSK24_SAMPLERATE 16000 +#define AFSK24_CORRLEN 14 + +static void gentbl_afsk2400(FILE *f, float tcm3105clk) +{ + int i, sum, v; + + fprintf(f, "\n/*\n * afsk2400 specific tables (tcm3105 clk %7fHz)\n */\n" + "#define AFSK24_TX_FREQ_LO %d\n" + "#define AFSK24_TX_FREQ_HI %d\n" + "#define AFSK24_BITPLL_INC %d\n" + "#define AFSK24_SAMPLERATE %d\n\n", tcm3105clk, + (int)(tcm3105clk/3694.0), (int)(tcm3105clk/2015.0), + 0x10000*2400/AFSK24_SAMPLERATE, AFSK24_SAMPLERATE); + +#define ARGLO(x) 2.0*M_PI*(double)x*(tcm3105clk/3694.0)/(double)AFSK24_SAMPLERATE +#define ARGHI(x) 2.0*M_PI*(double)x*(tcm3105clk/2015.0)/(double)AFSK24_SAMPLERATE +#define WINDOW(x) hamming((float)(x)/(AFSK24_CORRLEN-1.0)) + + fprintf(f, "static const int afsk24_tx_lo_i[] = {\n\t"); + for(sum = i = 0; i < AFSK24_CORRLEN; i++) { + sum += (v = 127.0*cos(ARGLO(i))*WINDOW(i)); + fprintf(f, " %4i%c", v, (i < AFSK24_CORRLEN-1) ? ',' : ' '); + } + fprintf(f, "\n};\n#define SUM_AFSK24_TX_LO_I %d\n\n" + "static const int afsk24_tx_lo_q[] = {\n\t", sum); + for(sum = i = 0; i < AFSK24_CORRLEN; i++) { + sum += (v = 127.0*sin(ARGLO(i))*WINDOW(i)); + fprintf(f, " %4i%c", v, (i < AFSK24_CORRLEN-1) ? ',' : ' '); + } + fprintf(f, "\n};\n#define SUM_AFSK24_TX_LO_Q %d\n\n" + "static const int afsk24_tx_hi_i[] = {\n\t", sum); + for(sum = i = 0; i < AFSK24_CORRLEN; i++) { + sum += (v = 127.0*cos(ARGHI(i))*WINDOW(i)); + fprintf(f, " %4i%c", v, (i < AFSK24_CORRLEN-1) ? ',' : ' '); + } + fprintf(f, "\n};\n#define SUM_AFSK24_TX_HI_I %d\n\n" + "static const int afsk24_tx_hi_q[] = {\n\t", sum); + for(sum = i = 0; i < AFSK24_CORRLEN; i++) { + sum += (v = 127.0*sin(ARGHI(i))*WINDOW(i)); + fprintf(f, " %4i%c", v, (i < AFSK24_CORRLEN-1) ? ',' : ' '); + } + fprintf(f, "\n};\n#define SUM_AFSK24_TX_HI_Q %d\n\n", sum); +#undef ARGLO +#undef ARGHI +#undef WINDOW +} + +/* -------------------------------------------------------------------- */ + +static char *progname; + +static void gentbl_banner(FILE *f) +{ + fprintf(f, "/*\n * THIS FILE IS GENERATED AUTOMATICALLY BY %s, " + "DO NOT EDIT!\n */\n\n", progname); +} + +/* -------------------------------------------------------------------- */ + +void main(int argc, char *argv[]) +{ + FILE *f; + + progname = argv[0]; + if (!(f = fopen("sm_tbl_afsk1200.h", "w"))) + exit(1); + gentbl_banner(f); + gentbl_offscostab(f, 6); + gentbl_costab(f, 6); + gentbl_afsk1200(f); + fclose(f); + if (!(f = fopen("sm_tbl_afsk2666.h", "w"))) + exit(1); + gentbl_banner(f); + gentbl_offscostab(f, 6); + gentbl_costab(f, 6); + gentbl_afsk2666(f); + fclose(f); + if (!(f = fopen("sm_tbl_psk4800.h", "w"))) + exit(1); + gentbl_banner(f); + gentbl_psk4800(f); + gentbl_costab(f, 8); + gentbl_atantab(f); + fclose(f); + if (!(f = fopen("sm_tbl_hapn4800.h", "w"))) + exit(1); + gentbl_banner(f); + gentbl_hapn4800(f); + fclose(f); + if (!(f = fopen("sm_tbl_fsk9600.h", "w"))) + exit(1); + gentbl_banner(f); + gentbl_fsk9600(f); + fclose(f); + if (!(f = fopen("sm_tbl_afsk2400_8.h", "w"))) + exit(1); + gentbl_banner(f); + gentbl_offscostab(f, 6); + gentbl_costab(f, 6); + gentbl_afsk2400(f, 8000000); + fclose(f); + if (!(f = fopen("sm_tbl_afsk2400_7.h", "w"))) + exit(1); + gentbl_banner(f); + gentbl_offscostab(f, 6); + gentbl_costab(f, 6); + gentbl_afsk2400(f, 7372800); + fclose(f); + exit(0); +} + + +/* -------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/soundmodem/sm.c b/drivers/net/hamradio/soundmodem/sm.c new file mode 100644 index 000000000..171aecfc7 --- /dev/null +++ b/drivers/net/hamradio/soundmodem/sm.c @@ -0,0 +1,898 @@ +/*****************************************************************************/ + +/* + * sm.c -- soundcard radio modem driver. + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + * + * Command line options (insmod command line) + * + * mode mode string; eg. "wss:afsk1200" + * iobase base address of the soundcard; common values are 0x220 for sbc, + * 0x530 for wss + * irq interrupt number; common values are 7 or 5 for sbc, 11 for wss + * dma dma number; common values are 0 or 1 + * + * + * History: + * 0.1 21.09.96 Started + * 18.10.96 Changed to new user space access routines (copy_{to,from}_user) + * 0.4 21.01.97 Separately compileable soundcard/modem modules + * 0.5 03.03.97 fixed LPT probing (check_lpt result was interpreted the wrong way round) + * 0.6 16.04.97 init code/data tagged + * 0.7 30.07.97 fixed halfduplex interrupt handlers/hotfix for CS423X + */ + +/*****************************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/ptrace.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/ioport.h> +#include <linux/net.h> +#include <linux/in.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/bitops.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include "sm.h" + +/* --------------------------------------------------------------------- */ + +/* + * currently this module is supposed to support both module styles, i.e. + * the old one present up to about 2.1.9, and the new one functioning + * starting with 2.1.21. The reason is I have a kit allowing to compile + * this module also under 2.0.x which was requested by several people. + * This will go in 2.2 + */ +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= 0x20100 +#include <asm/uaccess.h> +#else +#include <asm/segment.h> +#include <linux/mm.h> + +#undef put_user +#undef get_user + +#define put_user(x,ptr) ({ __put_user((unsigned long)(x),(ptr),sizeof(*(ptr))); 0; }) +#define get_user(x,ptr) ({ x = ((__typeof__(*(ptr)))__get_user((ptr),sizeof(*(ptr)))); 0; }) + +extern inline int copy_from_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_READ, from, n); + if (i) + return i; + memcpy_fromfs(to, from, n); + return 0; +} + +extern inline int copy_to_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_WRITE, to, n); + if (i) + return i; + memcpy_tofs(to, from, n); + return 0; +} +#endif + +#if LINUX_VERSION_CODE >= 0x20123 +#include <linux/init.h> +#else +#define __init +#define __initdata +#define __initfunc(x) x +#endif + +/* --------------------------------------------------------------------- */ + +/*static*/ const char sm_drvname[] = "soundmodem"; +static const char sm_drvinfo[] = KERN_INFO "soundmodem: (C) 1996-1997 Thomas Sailer, HB9JNX/AE4WA\n" +KERN_INFO "soundmodem: version 0.7 compiled " __TIME__ " " __DATE__ "\n"; + +/* --------------------------------------------------------------------- */ + +/*static*/ const struct modem_tx_info *sm_modem_tx_table[] = { +#ifdef CONFIG_SOUNDMODEM_AFSK1200 + &sm_afsk1200_tx, +#endif /* CONFIG_SOUNDMODEM_AFSK1200 */ +#ifdef CONFIG_SOUNDMODEM_AFSK2400_7 + &sm_afsk2400_7_tx, +#endif /* CONFIG_SOUNDMODEM_AFSK2400_7 */ +#ifdef CONFIG_SOUNDMODEM_AFSK2400_8 + &sm_afsk2400_8_tx, +#endif /* CONFIG_SOUNDMODEM_AFSK2400_8 */ +#ifdef CONFIG_SOUNDMODEM_AFSK2666 + &sm_afsk2666_tx, +#endif /* CONFIG_SOUNDMODEM_AFSK2666 */ +#ifdef CONFIG_SOUNDMODEM_PSK4800 + &sm_psk4800_tx, +#endif /* CONFIG_SOUNDMODEM_PSK4800 */ +#ifdef CONFIG_SOUNDMODEM_HAPN4800 + &sm_hapn4800_8_tx, + &sm_hapn4800_10_tx, + &sm_hapn4800_pm8_tx, + &sm_hapn4800_pm10_tx, +#endif /* CONFIG_SOUNDMODEM_HAPN4800 */ +#ifdef CONFIG_SOUNDMODEM_FSK9600 + &sm_fsk9600_4_tx, + &sm_fsk9600_5_tx, +#endif /* CONFIG_SOUNDMODEM_FSK9600 */ + NULL +}; + +/*static*/ const struct modem_rx_info *sm_modem_rx_table[] = { +#ifdef CONFIG_SOUNDMODEM_AFSK1200 + &sm_afsk1200_rx, +#endif /* CONFIG_SOUNDMODEM_AFSK1200 */ +#ifdef CONFIG_SOUNDMODEM_AFSK2400_7 + &sm_afsk2400_7_rx, +#endif /* CONFIG_SOUNDMODEM_AFSK2400_7 */ +#ifdef CONFIG_SOUNDMODEM_AFSK2400_8 + &sm_afsk2400_8_rx, +#endif /* CONFIG_SOUNDMODEM_AFSK2400_8 */ +#ifdef CONFIG_SOUNDMODEM_AFSK2666 + &sm_afsk2666_rx, +#endif /* CONFIG_SOUNDMODEM_AFSK2666 */ +#ifdef CONFIG_SOUNDMODEM_PSK4800 + &sm_psk4800_rx, +#endif /* CONFIG_SOUNDMODEM_PSK4800 */ +#ifdef CONFIG_SOUNDMODEM_HAPN4800 + &sm_hapn4800_8_rx, + &sm_hapn4800_10_rx, + &sm_hapn4800_pm8_rx, + &sm_hapn4800_pm10_rx, +#endif /* CONFIG_SOUNDMODEM_HAPN4800 */ +#ifdef CONFIG_SOUNDMODEM_FSK9600 + &sm_fsk9600_4_rx, + &sm_fsk9600_5_rx, +#endif /* CONFIG_SOUNDMODEM_FSK9600 */ + NULL +}; + +static const struct hardware_info *sm_hardware_table[] = { +#ifdef CONFIG_SOUNDMODEM_SBC + &sm_hw_sbc, + &sm_hw_sbcfdx, +#endif /* CONFIG_SOUNDMODEM_SBC */ +#ifdef CONFIG_SOUNDMODEM_WSS + &sm_hw_wss, + &sm_hw_wssfdx, +#endif /* CONFIG_SOUNDMODEM_WSS */ + NULL +}; + +/* --------------------------------------------------------------------- */ + +#define NR_PORTS 4 + +/* --------------------------------------------------------------------- */ + +static struct device sm_device[NR_PORTS]; + +static struct { + char *mode; + int iobase, irq, dma, dma2, seriobase, pariobase, midiiobase; +} sm_ports[NR_PORTS] = { + { NULL, -1, 0, 0, 0, -1, -1, -1 }, +}; + +/* --------------------------------------------------------------------- */ + +#define UART_RBR(iobase) (iobase+0) +#define UART_THR(iobase) (iobase+0) +#define UART_IER(iobase) (iobase+1) +#define UART_IIR(iobase) (iobase+2) +#define UART_FCR(iobase) (iobase+2) +#define UART_LCR(iobase) (iobase+3) +#define UART_MCR(iobase) (iobase+4) +#define UART_LSR(iobase) (iobase+5) +#define UART_MSR(iobase) (iobase+6) +#define UART_SCR(iobase) (iobase+7) +#define UART_DLL(iobase) (iobase+0) +#define UART_DLM(iobase) (iobase+1) + +#define SER_EXTENT 8 + +#define LPT_DATA(iobase) (iobase+0) +#define LPT_STATUS(iobase) (iobase+1) +#define LPT_CONTROL(iobase) (iobase+2) +#define LPT_IRQ_ENABLE 0x10 + +#define LPT_EXTENT 3 + +#define MIDI_DATA(iobase) (iobase) +#define MIDI_STATUS(iobase) (iobase+1) +#define MIDI_READ_FULL 0x80 /* attention: negative logic!! */ +#define MIDI_WRITE_EMPTY 0x40 /* attention: negative logic!! */ + +#define MIDI_EXTENT 2 + +/* ---------------------------------------------------------------------- */ + +#define PARAM_TXDELAY 1 +#define PARAM_PERSIST 2 +#define PARAM_SLOTTIME 3 +#define PARAM_TXTAIL 4 +#define PARAM_FULLDUP 5 +#define PARAM_HARDWARE 6 +#define PARAM_RETURN 255 + +#define SP_SER 1 +#define SP_PAR 2 +#define SP_MIDI 4 + +/* --------------------------------------------------------------------- */ +/* + * ===================== port checking routines ======================== + */ + +/* + * returns 0 if ok and != 0 on error; + * the same behaviour as par96_check_lpt in baycom.c + */ + +/* + * returns 0 if ok and != 0 on error; + * the same behaviour as par96_check_lpt in baycom.c + */ + +static int check_lpt(unsigned int iobase) +{ + unsigned char b1,b2; + int i; + + if (iobase <= 0 || iobase > 0x1000-LPT_EXTENT) + return 0; + if (check_region(iobase, LPT_EXTENT)) + return 0; + b1 = inb(LPT_DATA(iobase)); + b2 = inb(LPT_CONTROL(iobase)); + outb(0xaa, LPT_DATA(iobase)); + i = inb(LPT_DATA(iobase)) == 0xaa; + outb(0x55, LPT_DATA(iobase)); + i &= inb(LPT_DATA(iobase)) == 0x55; + outb(0x0a, LPT_CONTROL(iobase)); + i &= (inb(LPT_CONTROL(iobase)) & 0xf) == 0x0a; + outb(0x05, LPT_CONTROL(iobase)); + i &= (inb(LPT_CONTROL(iobase)) & 0xf) == 0x05; + outb(b1, LPT_DATA(iobase)); + outb(b2, LPT_CONTROL(iobase)); + return !i; +} + +/* --------------------------------------------------------------------- */ + +enum uart { c_uart_unknown, c_uart_8250, + c_uart_16450, c_uart_16550, c_uart_16550A}; +static const char *uart_str[] = + { "unknown", "8250", "16450", "16550", "16550A" }; + +static enum uart check_uart(unsigned int iobase) +{ + unsigned char b1,b2,b3; + enum uart u; + enum uart uart_tab[] = + { c_uart_16450, c_uart_unknown, c_uart_16550, c_uart_16550A }; + + if (iobase <= 0 || iobase > 0x1000-SER_EXTENT) + return c_uart_unknown; + if (check_region(iobase, SER_EXTENT)) + return c_uart_unknown; + b1 = inb(UART_MCR(iobase)); + outb(b1 | 0x10, UART_MCR(iobase)); /* loopback mode */ + b2 = inb(UART_MSR(iobase)); + outb(0x1a, UART_MCR(iobase)); + b3 = inb(UART_MSR(iobase)) & 0xf0; + outb(b1, UART_MCR(iobase)); /* restore old values */ + outb(b2, UART_MSR(iobase)); + if (b3 != 0x90) + return c_uart_unknown; + inb(UART_RBR(iobase)); + inb(UART_RBR(iobase)); + outb(0x01, UART_FCR(iobase)); /* enable FIFOs */ + u = uart_tab[(inb(UART_IIR(iobase)) >> 6) & 3]; + if (u == c_uart_16450) { + outb(0x5a, UART_SCR(iobase)); + b1 = inb(UART_SCR(iobase)); + outb(0xa5, UART_SCR(iobase)); + b2 = inb(UART_SCR(iobase)); + if ((b1 != 0x5a) || (b2 != 0xa5)) + u = c_uart_8250; + } + return u; +} + +/* --------------------------------------------------------------------- */ + +static int check_midi(unsigned int iobase) +{ + unsigned long timeout; + unsigned long flags; + unsigned char b; + + if (iobase <= 0 || iobase > 0x1000-MIDI_EXTENT) + return 0; + if (check_region(iobase, MIDI_EXTENT)) + return 0; + timeout = jiffies + (HZ / 100); + while (inb(MIDI_STATUS(iobase)) & MIDI_WRITE_EMPTY) + if ((signed)(jiffies - timeout) > 0) + return 0; + save_flags(flags); + cli(); + outb(0xff, MIDI_DATA(iobase)); + b = inb(MIDI_STATUS(iobase)); + restore_flags(flags); + if (!(b & MIDI_WRITE_EMPTY)) + return 0; + while (inb(MIDI_STATUS(iobase)) & MIDI_WRITE_EMPTY) + if ((signed)(jiffies - timeout) > 0) + return 0; + return 1; +} + +/* --------------------------------------------------------------------- */ + +void sm_output_status(struct sm_state *sm) +{ + int invert_dcd = 0; + int invert_ptt = 0; + + int ptt = /*hdlcdrv_ptt(&sm->hdrv)*/(sm->dma.ptt_cnt > 0) ^ invert_ptt; + int dcd = (!!sm->hdrv.hdlcrx.dcd) ^ invert_dcd; + + if (sm->hdrv.ptt_out.flags & SP_SER) { + outb(dcd | (ptt << 1), UART_MCR(sm->hdrv.ptt_out.seriobase)); + outb(0x40 & (-ptt), UART_LCR(sm->hdrv.ptt_out.seriobase)); + } + if (sm->hdrv.ptt_out.flags & SP_PAR) { + outb(ptt | (dcd << 1), LPT_DATA(sm->hdrv.ptt_out.pariobase)); + } + if (sm->hdrv.ptt_out.flags & SP_MIDI && hdlcdrv_ptt(&sm->hdrv)) { + outb(0, MIDI_DATA(sm->hdrv.ptt_out.midiiobase)); + } +} + +/* --------------------------------------------------------------------- */ + +static void sm_output_open(struct sm_state *sm) +{ + enum uart u = c_uart_unknown; + + sm->hdrv.ptt_out.flags = 0; + if (sm->hdrv.ptt_out.seriobase > 0 && + sm->hdrv.ptt_out.seriobase <= 0x1000-SER_EXTENT && + ((u = check_uart(sm->hdrv.ptt_out.seriobase))) != c_uart_unknown) { + sm->hdrv.ptt_out.flags |= SP_SER; + request_region(sm->hdrv.ptt_out.seriobase, SER_EXTENT, "sm ser ptt"); + outb(0, UART_IER(sm->hdrv.ptt_out.seriobase)); + /* 5 bits, 1 stop, no parity, no break, Div latch access */ + outb(0x80, UART_LCR(sm->hdrv.ptt_out.seriobase)); + outb(0, UART_DLM(sm->hdrv.ptt_out.seriobase)); + outb(1, UART_DLL(sm->hdrv.ptt_out.seriobase)); /* as fast as possible */ + /* LCR and MCR set by output_status */ + } + if (sm->hdrv.ptt_out.pariobase > 0 && + sm->hdrv.ptt_out.pariobase <= 0x1000-LPT_EXTENT && + !check_lpt(sm->hdrv.ptt_out.pariobase)) { + sm->hdrv.ptt_out.flags |= SP_PAR; + request_region(sm->hdrv.ptt_out.pariobase, LPT_EXTENT, "sm par ptt"); + } + if (sm->hdrv.ptt_out.midiiobase > 0 && + sm->hdrv.ptt_out.midiiobase <= 0x1000-MIDI_EXTENT && + check_midi(sm->hdrv.ptt_out.midiiobase)) { + sm->hdrv.ptt_out.flags |= SP_MIDI; + request_region(sm->hdrv.ptt_out.midiiobase, MIDI_EXTENT, + "sm midi ptt"); + } + sm_output_status(sm); + + printk(KERN_INFO "%s: ptt output:", sm_drvname); + if (sm->hdrv.ptt_out.flags & SP_SER) + printk(" serial interface at 0x%x, uart %s", sm->hdrv.ptt_out.seriobase, + uart_str[u]); + if (sm->hdrv.ptt_out.flags & SP_PAR) + printk(" parallel interface at 0x%x", sm->hdrv.ptt_out.pariobase); + if (sm->hdrv.ptt_out.flags & SP_MIDI) + printk(" mpu401 (midi) interface at 0x%x", sm->hdrv.ptt_out.midiiobase); + if (!sm->hdrv.ptt_out.flags) + printk(" none"); + printk("\n"); +} + +/* --------------------------------------------------------------------- */ + +static void sm_output_close(struct sm_state *sm) +{ + /* release regions used for PTT output */ + sm->hdrv.hdlctx.ptt = sm->hdrv.hdlctx.calibrate = 0; + sm_output_status(sm); + if (sm->hdrv.ptt_out.flags & SP_SER) + release_region(sm->hdrv.ptt_out.seriobase, SER_EXTENT); + if (sm->hdrv.ptt_out.flags & SP_PAR) + release_region(sm->hdrv.ptt_out.pariobase, LPT_EXTENT); + if (sm->hdrv.ptt_out.flags & SP_MIDI) + release_region(sm->hdrv.ptt_out.midiiobase, MIDI_EXTENT); + sm->hdrv.ptt_out.flags = 0; +} + +/* --------------------------------------------------------------------- */ + +static int sm_open(struct device *dev); +static int sm_close(struct device *dev); +static int sm_ioctl(struct device *dev, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd); + +/* --------------------------------------------------------------------- */ + +static const struct hdlcdrv_ops sm_ops = { + sm_drvname, sm_drvinfo, sm_open, sm_close, sm_ioctl +}; + +/* --------------------------------------------------------------------- */ + +static int sm_open(struct device *dev) +{ + struct sm_state *sm; + int err; + + if (!dev || !dev->priv || + ((struct sm_state *)dev->priv)->hdrv.magic != HDLCDRV_MAGIC) { + printk(KERN_ERR "sm_open: invalid device struct\n"); + return -EINVAL; + } + sm = (struct sm_state *)dev->priv; + + if (!sm->mode_tx || !sm->mode_rx || !sm->hwdrv || !sm->hwdrv->open) + return -ENODEV; + sm->hdrv.par.bitrate = sm->mode_rx->bitrate; + err = sm->hwdrv->open(dev, sm); + if (err) + return err; + sm_output_open(sm); + MOD_INC_USE_COUNT; + printk(KERN_INFO "%s: %s mode %s.%s at iobase 0x%lx irq %u dma %u dma2 %u\n", + sm_drvname, sm->hwdrv->hw_name, sm->mode_tx->name, + sm->mode_rx->name, dev->base_addr, dev->irq, dev->dma, sm->hdrv.ptt_out.dma2); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int sm_close(struct device *dev) +{ + struct sm_state *sm; + int err = -ENODEV; + + if (!dev || !dev->priv || + ((struct sm_state *)dev->priv)->hdrv.magic != HDLCDRV_MAGIC) { + printk(KERN_ERR "sm_close: invalid device struct\n"); + return -EINVAL; + } + sm = (struct sm_state *)dev->priv; + + + if (sm->hwdrv && sm->hwdrv->close) + err = sm->hwdrv && sm->hwdrv->close(dev, sm); + sm_output_close(sm); + MOD_DEC_USE_COUNT; + printk(KERN_INFO "%s: close %s at iobase 0x%lx irq %u dma %u\n", + sm_drvname, sm->hwdrv->hw_name, dev->base_addr, dev->irq, dev->dma); + return err; +} + +/* --------------------------------------------------------------------- */ + +static int sethw(struct device *dev, struct sm_state *sm, char *mode) +{ + char *cp = strchr(mode, ':'); + const struct hardware_info **hwp = sm_hardware_table; + + if (!cp) + cp = mode; + else { + *cp++ = '\0'; + while (hwp && (*hwp) && (*hwp)->hw_name && strcmp((*hwp)->hw_name, mode)) + hwp++; + if (!hwp || !*hwp || !(*hwp)->hw_name) + return -EINVAL; + if ((*hwp)->loc_storage > sizeof(sm->hw)) { + printk(KERN_ERR "%s: insufficient storage for hw driver %s (%d)\n", + sm_drvname, (*hwp)->hw_name, (*hwp)->loc_storage); + return -EINVAL; + } + sm->hwdrv = *hwp; + } + if (!*cp) + return 0; + if (sm->hwdrv && sm->hwdrv->sethw) + return sm->hwdrv->sethw(dev, sm, cp); + return -EINVAL; +} + +/* --------------------------------------------------------------------- */ + +static int sm_ioctl(struct device *dev, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd) +{ + struct sm_state *sm; + struct sm_ioctl bi; + unsigned long flags; + unsigned int newdiagmode; + unsigned int newdiagflags; + char *cp; + const struct modem_tx_info **mtp = sm_modem_tx_table; + const struct modem_rx_info **mrp = sm_modem_rx_table; + const struct hardware_info **hwp = sm_hardware_table; + + if (!dev || !dev->priv || + ((struct sm_state *)dev->priv)->hdrv.magic != HDLCDRV_MAGIC) { + printk(KERN_ERR "sm_ioctl: invalid device struct\n"); + return -EINVAL; + } + sm = (struct sm_state *)dev->priv; + + if (cmd != SIOCDEVPRIVATE) { + if (!sm->hwdrv || !sm->hwdrv->ioctl) + return sm->hwdrv->ioctl(dev, sm, ifr, hi, cmd); + return -ENOIOCTLCMD; + } + switch (hi->cmd) { + default: + if (sm->hwdrv && sm->hwdrv->ioctl) + return sm->hwdrv->ioctl(dev, sm, ifr, hi, cmd); + return -ENOIOCTLCMD; + + case HDLCDRVCTL_GETMODE: + cp = hi->data.modename; + if (sm->hwdrv && sm->hwdrv->hw_name) + cp += sprintf(cp, "%s:", sm->hwdrv->hw_name); + else + cp += sprintf(cp, "<unspec>:"); + if (sm->mode_tx && sm->mode_tx->name) + cp += sprintf(cp, "%s", sm->mode_tx->name); + else + cp += sprintf(cp, "<unspec>"); + if (!sm->mode_rx || !sm->mode_rx || + strcmp(sm->mode_rx->name, sm->mode_tx->name)) { + if (sm->mode_rx && sm->mode_rx->name) + cp += sprintf(cp, ",%s", sm->mode_rx->name); + else + cp += sprintf(cp, ",<unspec>"); + } + if (copy_to_user(ifr->ifr_data, hi, sizeof(*hi))) + return -EFAULT; + return 0; + + case HDLCDRVCTL_SETMODE: + if (!suser() || dev->start) + return -EACCES; + hi->data.modename[sizeof(hi->data.modename)-1] = '\0'; + return sethw(dev, sm, hi->data.modename); + + case HDLCDRVCTL_MODELIST: + cp = hi->data.modename; + while (*hwp) { + if ((*hwp)->hw_name) + cp += sprintf("%s:,", (*hwp)->hw_name); + hwp++; + } + while (*mtp) { + if ((*mtp)->name) + cp += sprintf(">%s,", (*mtp)->name); + mtp++; + } + while (*mrp) { + if ((*mrp)->name) + cp += sprintf("<%s,", (*mrp)->name); + mrp++; + } + cp[-1] = '\0'; + if (copy_to_user(ifr->ifr_data, hi, sizeof(*hi))) + return -EFAULT; + return 0; + +#ifdef SM_DEBUG + case SMCTL_GETDEBUG: + if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi))) + return -EFAULT; + bi.data.dbg.int_rate = sm->debug_vals.last_intcnt; + bi.data.dbg.mod_cycles = sm->debug_vals.mod_cyc; + bi.data.dbg.demod_cycles = sm->debug_vals.demod_cyc; + bi.data.dbg.dma_residue = sm->debug_vals.dma_residue; + sm->debug_vals.mod_cyc = sm->debug_vals.demod_cyc = + sm->debug_vals.dma_residue = 0; + if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi))) + return -EFAULT; + return 0; +#endif /* SM_DEBUG */ + + case SMCTL_DIAGNOSE: + if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi))) + return -EFAULT; + newdiagmode = bi.data.diag.mode; + newdiagflags = bi.data.diag.flags; + if (newdiagmode > SM_DIAGMODE_CONSTELLATION) + return -EINVAL; + bi.data.diag.mode = sm->diag.mode; + bi.data.diag.flags = sm->diag.flags; + bi.data.diag.samplesperbit = sm->mode_rx->sperbit; + if (sm->diag.mode != newdiagmode) { + save_flags(flags); + cli(); + sm->diag.ptr = -1; + sm->diag.flags = newdiagflags & ~SM_DIAGFLAG_VALID; + sm->diag.mode = newdiagmode; + restore_flags(flags); + if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi))) + return -EFAULT; + return 0; + } + if (sm->diag.ptr < 0 || sm->diag.mode == SM_DIAGMODE_OFF) { + if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi))) + return -EFAULT; + return 0; + } + if (bi.data.diag.datalen > DIAGDATALEN) + bi.data.diag.datalen = DIAGDATALEN; + if (sm->diag.ptr < bi.data.diag.datalen) { + if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi))) + return -EFAULT; + return 0; + } + if (copy_to_user(bi.data.diag.data, sm->diag.data, + bi.data.diag.datalen * sizeof(short))) + return -EFAULT; + bi.data.diag.flags |= SM_DIAGFLAG_VALID; + save_flags(flags); + cli(); + sm->diag.ptr = -1; + sm->diag.flags = newdiagflags & ~SM_DIAGFLAG_VALID; + sm->diag.mode = newdiagmode; + restore_flags(flags); + if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi))) + return -EFAULT; + return 0; + } +} + +/* --------------------------------------------------------------------- */ + +#ifdef __i386__ + +int sm_x86_capability = 0; + +__initfunc(static void i386_capability(void)) +{ + unsigned long flags; + unsigned long fl1; + union { + struct { + unsigned int ebx, edx, ecx; + } r; + unsigned char s[13]; + } id; + unsigned int eax; + + save_flags(flags); + flags |= 0x200000; + restore_flags(flags); + save_flags(flags); + fl1 = flags; + flags &= ~0x200000; + restore_flags(flags); + save_flags(flags); + if (!(fl1 & 0x200000) || (flags & 0x200000)) { + printk(KERN_WARNING "%s: cpu does not support CPUID\n", sm_drvname); + return; + } + __asm__ ("cpuid" : "=a" (eax), "=b" (id.r.ebx), "=c" (id.r.ecx), "=d" (id.r.edx) : + "0" (0)); + id.s[12] = 0; + if (eax < 1) { + printk(KERN_WARNING "%s: cpu (vendor string %s) does not support capability " + "list\n", sm_drvname, id.s); + return; + } + printk(KERN_INFO "%s: cpu: vendor string %s ", sm_drvname, id.s); + __asm__ ("cpuid" : "=a" (eax), "=d" (sm_x86_capability) : "0" (1) : "ebx", "ecx"); + printk("fam %d mdl %d step %d cap 0x%x\n", (eax >> 8) & 15, (eax >> 4) & 15, + eax & 15, sm_x86_capability); +} +#endif /* __i386__ */ + +/* --------------------------------------------------------------------- */ + +#ifdef MODULE +__initfunc(static int sm_init(void)) +#else /* MODULE */ +__initfunc(int sm_init(void)) +#endif /* MODULE */ +{ + int i, j, found = 0; + char set_hw = 1; + struct sm_state *sm; + char ifname[HDLCDRV_IFNAMELEN]; + + printk(sm_drvinfo); +#ifdef __i386__ + i386_capability(); +#endif /* __i386__ */ + /* + * register net devices + */ + for (i = 0; i < NR_PORTS; i++) { + struct device *dev = sm_device+i; + sprintf(ifname, "sm%d", i); + + if (!sm_ports[i].mode) + set_hw = 0; + if (!set_hw) + sm_ports[i].iobase = sm_ports[i].irq = 0; + j = hdlcdrv_register_hdlcdrv(dev, &sm_ops, sizeof(struct sm_state), + ifname, sm_ports[i].iobase, + sm_ports[i].irq, sm_ports[i].dma); + if (!j) { + sm = (struct sm_state *)dev->priv; + sm->hdrv.ptt_out.dma2 = sm_ports[i].dma2; + sm->hdrv.ptt_out.seriobase = sm_ports[i].seriobase; + sm->hdrv.ptt_out.pariobase = sm_ports[i].pariobase; + sm->hdrv.ptt_out.midiiobase = sm_ports[i].midiiobase; + if (set_hw && sethw(dev, sm, sm_ports[i].mode)) + set_hw = 0; + found++; + } else { + printk(KERN_WARNING "%s: cannot register net device\n", + sm_drvname); + } + } + if (!found) + return -ENXIO; + return 0; +} + +/* --------------------------------------------------------------------- */ + +#ifdef MODULE + +/* + * command line settable parameters + */ +static char *mode = NULL; +static int iobase = -1; +static int irq = -1; +static int dma = -1; +static int dma2 = -1; +static int serio = 0; +static int pario = 0; +static int midiio = 0; + +#if LINUX_VERSION_CODE >= 0x20115 + +MODULE_PARM(mode, "s"); +MODULE_PARM_DESC(mode, "soundmodem operating mode; eg. sbc:afsk1200 or wss:fsk9600"); +MODULE_PARM(iobase, "i"); +MODULE_PARM_DESC(iobase, "soundmodem base address"); +MODULE_PARM(irq, "i"); +MODULE_PARM_DESC(irq, "soundmodem interrupt"); +MODULE_PARM(dma, "i"); +MODULE_PARM_DESC(dma, "soundmodem dma channel"); +MODULE_PARM(dma2, "i"); +MODULE_PARM_DESC(dma2, "soundmodem 2nd dma channel; full duplex only"); +MODULE_PARM(serio, "i"); +MODULE_PARM_DESC(serio, "soundmodem PTT output on serial port"); +MODULE_PARM(pario, "i"); +MODULE_PARM_DESC(pario, "soundmodem PTT output on parallel port"); +MODULE_PARM(midiio, "i"); +MODULE_PARM_DESC(midiio, "soundmodem PTT output on midi port"); + +MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu"); +MODULE_DESCRIPTION("Soundcard amateur radio modem driver"); + +#endif + +__initfunc(int init_module(void)) +{ + if (mode) { + if (iobase == -1) + iobase = (!strncmp(mode, "sbc", 3)) ? 0x220 : 0x530; + if (irq == -1) + irq = (!strncmp(mode, "sbc", 3)) ? 5 : 11; + if (dma == -1) + dma = 1; + } + sm_ports[0].mode = mode; + sm_ports[0].iobase = iobase; + sm_ports[0].irq = irq; + sm_ports[0].dma = dma; + sm_ports[0].dma2 = dma2; + sm_ports[0].seriobase = serio; + sm_ports[0].pariobase = pario; + sm_ports[0].midiiobase = midiio; + sm_ports[1].mode = NULL; + + return sm_init(); +} + +/* --------------------------------------------------------------------- */ + +void cleanup_module(void) +{ + int i; + + printk(KERN_INFO "sm: cleanup_module called\n"); + + for(i = 0; i < NR_PORTS; i++) { + struct device *dev = sm_device+i; + struct sm_state *sm = (struct sm_state *)dev->priv; + + if (sm) { + if (sm->hdrv.magic != HDLCDRV_MAGIC) + printk(KERN_ERR "sm: invalid magic in " + "cleanup_module\n"); + else + hdlcdrv_unregister_hdlcdrv(dev); + } + } +} + +#else /* MODULE */ +/* --------------------------------------------------------------------- */ +/* + * format: sm=io,irq,dma[,dma2[,serio[,pario]]],mode + * mode: hw:modem + * hw: sbc, wss, wssfdx + * modem: afsk1200, fsk9600 + */ + +__initfunc(void sm_setup(char *str, int *ints)) +{ + int i; + + for (i = 0; (i < NR_PORTS) && (sm_ports[i].mode); i++); + if ((i >= NR_PORTS) || (ints[0] < 3)) { + printk(KERN_INFO "%s: too many or invalid interface " + "specifications\n", sm_drvname); + return; + } + sm_ports[i].mode = str; + sm_ports[i].iobase = ints[1]; + sm_ports[i].irq = ints[2]; + sm_ports[i].dma = ints[3]; + sm_ports[i].dma2 = (ints[0] >= 4) ? ints[4] : 0; + sm_ports[i].seriobase = (ints[0] >= 5) ? ints[5] : 0; + sm_ports[i].pariobase = (ints[0] >= 6) ? ints[6] : 0; + sm_ports[i].midiiobase = (ints[0] >= 7) ? ints[7] : 0; + if (i < NR_PORTS-1) + sm_ports[i+1].mode = NULL; +} + +#endif /* MODULE */ +/* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/soundmodem/sm.h b/drivers/net/hamradio/soundmodem/sm.h new file mode 100644 index 000000000..25bbc8ba9 --- /dev/null +++ b/drivers/net/hamradio/soundmodem/sm.h @@ -0,0 +1,382 @@ +/*****************************************************************************/ + +/* + * sm.h -- soundcard radio modem driver internal header. + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + */ + +#ifndef _SM_H +#define _SM_H + +/* ---------------------------------------------------------------------- */ + +#include <linux/hdlcdrv.h> +#include <linux/soundmodem.h> + +#define SM_DEBUG + +/* ---------------------------------------------------------------------- */ +/* + * Information that need to be kept for each board. + */ + +struct sm_state { + struct hdlcdrv_state hdrv; + + const struct modem_tx_info *mode_tx; + const struct modem_rx_info *mode_rx; + + const struct hardware_info *hwdrv; + + /* + * Hardware (soundcard) access routines state + */ + struct { + void *ibuf; + unsigned int ifragsz; + unsigned int ifragptr; + unsigned int i16bit; + void *obuf; + unsigned int ofragsz; + unsigned int ofragptr; + unsigned int o16bit; + int ptt_cnt; + } dma; + + union { + long hw[32/sizeof(long)]; + } hw; + + /* + * state of the modem code + */ + union { + long m[32/sizeof(long)]; + } m; + union { + long d[256/sizeof(long)]; + } d; + +#define DIAGDATALEN 64 + struct diag_data { + unsigned int mode; + unsigned int flags; + volatile int ptr; + short data[DIAGDATALEN]; + } diag; + + +#ifdef SM_DEBUG + struct debug_vals { + unsigned long last_jiffies; + unsigned cur_intcnt; + unsigned last_intcnt; + unsigned mod_cyc; + unsigned demod_cyc; + unsigned dma_residue; + } debug_vals; +#endif /* SM_DEBUG */ +}; + +/* ---------------------------------------------------------------------- */ +/* + * Mode definition structure + */ + +struct modem_tx_info { + const char *name; + unsigned int loc_storage; + int srate; + int bitrate; + void (*modulator_u8)(struct sm_state *, unsigned char *, unsigned int); + void (*modulator_s16)(struct sm_state *, short *, unsigned int); + void (*init)(struct sm_state *); +}; + +struct modem_rx_info { + const char *name; + unsigned int loc_storage; + int srate; + int bitrate; + unsigned int overlap; + unsigned int sperbit; + void (*demodulator_u8)(struct sm_state *, const unsigned char *, unsigned int); + void (*demodulator_s16)(struct sm_state *, const short *, unsigned int); + void (*init)(struct sm_state *); +}; + +/* ---------------------------------------------------------------------- */ +/* + * Soundcard driver definition structure + */ + +struct hardware_info { + char *hw_name; /* used for request_{region,irq,dma} */ + unsigned int loc_storage; + /* + * mode specific open/close + */ + int (*open)(struct device *, struct sm_state *); + int (*close)(struct device *, struct sm_state *); + int (*ioctl)(struct device *, struct sm_state *, struct ifreq *, + struct hdlcdrv_ioctl *, int); + int (*sethw)(struct device *, struct sm_state *, char *); +}; + +/* --------------------------------------------------------------------- */ + +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +/* --------------------------------------------------------------------- */ + +extern const char sm_drvname[]; +extern const char sm_drvinfo[]; + +/* --------------------------------------------------------------------- */ +/* + * ===================== diagnostics stuff =============================== + */ + +extern inline void diag_trigger(struct sm_state *sm) +{ + if (sm->diag.ptr < 0) + if (!(sm->diag.flags & SM_DIAGFLAG_DCDGATE) || sm->hdrv.hdlcrx.dcd) + sm->diag.ptr = 0; +} + +/* --------------------------------------------------------------------- */ + +#define SHRT_MAX ((short)(((unsigned short)(~0U))>>1)) +#define SHRT_MIN (-SHRT_MAX-1) + +extern inline void diag_add(struct sm_state *sm, int valinp, int valdemod) +{ + int val; + + if ((sm->diag.mode != SM_DIAGMODE_INPUT && + sm->diag.mode != SM_DIAGMODE_DEMOD) || + sm->diag.ptr >= DIAGDATALEN || sm->diag.ptr < 0) + return; + val = (sm->diag.mode == SM_DIAGMODE_DEMOD) ? valdemod : valinp; + /* clip */ + if (val > SHRT_MAX) + val = SHRT_MAX; + if (val < SHRT_MIN) + val = SHRT_MIN; + sm->diag.data[sm->diag.ptr++] = val; +} + +/* --------------------------------------------------------------------- */ + +extern inline void diag_add_one(struct sm_state *sm, int val) +{ + if ((sm->diag.mode != SM_DIAGMODE_INPUT && + sm->diag.mode != SM_DIAGMODE_DEMOD) || + sm->diag.ptr >= DIAGDATALEN || sm->diag.ptr < 0) + return; + /* clip */ + if (val > SHRT_MAX) + val = SHRT_MAX; + if (val < SHRT_MIN) + val = SHRT_MIN; + sm->diag.data[sm->diag.ptr++] = val; +} + +/* --------------------------------------------------------------------- */ + +static inline void diag_add_constellation(struct sm_state *sm, int vali, int valq) +{ + if ((sm->diag.mode != SM_DIAGMODE_CONSTELLATION) || + sm->diag.ptr >= DIAGDATALEN-1 || sm->diag.ptr < 0) + return; + /* clip */ + if (vali > SHRT_MAX) + vali = SHRT_MAX; + if (vali < SHRT_MIN) + vali = SHRT_MIN; + if (valq > SHRT_MAX) + valq = SHRT_MAX; + if (valq < SHRT_MIN) + valq = SHRT_MIN; + sm->diag.data[sm->diag.ptr++] = vali; + sm->diag.data[sm->diag.ptr++] = valq; +} + +/* --------------------------------------------------------------------- */ +/* + * ===================== utility functions =============================== + */ + +extern inline unsigned int hweight32(unsigned int w) + __attribute__ ((unused)); +extern inline unsigned int hweight16(unsigned short w) + __attribute__ ((unused)); +extern inline unsigned int hweight8(unsigned char w) + __attribute__ ((unused)); + +extern inline unsigned int hweight32(unsigned int w) +{ + unsigned int res = (w & 0x55555555) + ((w >> 1) & 0x55555555); + res = (res & 0x33333333) + ((res >> 2) & 0x33333333); + res = (res & 0x0F0F0F0F) + ((res >> 4) & 0x0F0F0F0F); + res = (res & 0x00FF00FF) + ((res >> 8) & 0x00FF00FF); + return (res & 0x0000FFFF) + ((res >> 16) & 0x0000FFFF); +} + +extern inline unsigned int hweight16(unsigned short w) +{ + unsigned short res = (w & 0x5555) + ((w >> 1) & 0x5555); + res = (res & 0x3333) + ((res >> 2) & 0x3333); + res = (res & 0x0F0F) + ((res >> 4) & 0x0F0F); + return (res & 0x00FF) + ((res >> 8) & 0x00FF); +} + +extern inline unsigned int hweight8(unsigned char w) +{ + unsigned short res = (w & 0x55) + ((w >> 1) & 0x55); + res = (res & 0x33) + ((res >> 2) & 0x33); + return (res & 0x0F) + ((res >> 4) & 0x0F); +} + +extern inline unsigned int gcd(unsigned int x, unsigned int y) + __attribute__ ((unused)); +extern inline unsigned int lcm(unsigned int x, unsigned int y) + __attribute__ ((unused)); + +extern inline unsigned int gcd(unsigned int x, unsigned int y) +{ + for (;;) { + if (!x) + return y; + if (!y) + return x; + if (x > y) + x %= y; + else + y %= x; + } +} + +extern inline unsigned int lcm(unsigned int x, unsigned int y) +{ + return x * y / gcd(x, y); +} + +/* --------------------------------------------------------------------- */ +/* + * ===================== profiling ======================================= + */ + + +#ifdef __i386__ + +extern int sm_x86_capability; + +#define HAS_RDTSC (sm_x86_capability & 0x10) + +/* + * only do 32bit cycle counter arithmetic; we hope we won't overflow :-) + * in fact, overflowing modems would require over 2THz clock speeds :-) + */ + +#define time_exec(var,cmd) \ +({ \ + if (HAS_RDTSC) { \ + unsigned int cnt1, cnt2, cnt3; \ + __asm__(".byte 0x0f,0x31" : "=a" (cnt1), "=d" (cnt3)); \ + cmd; \ + __asm__(".byte 0x0f,0x31" : "=a" (cnt2), "=d" (cnt3)); \ + var = cnt2-cnt1; \ + } else { \ + cmd; \ + } \ +}) + +#else /* __i386__ */ + +#define time_exec(var,cmd) cmd + +#endif /* __i386__ */ + +/* --------------------------------------------------------------------- */ + +extern const struct modem_tx_info sm_afsk1200_tx; +extern const struct modem_tx_info sm_afsk2400_7_tx; +extern const struct modem_tx_info sm_afsk2400_8_tx; +extern const struct modem_tx_info sm_afsk2666_tx; +extern const struct modem_tx_info sm_psk4800_tx; +extern const struct modem_tx_info sm_hapn4800_8_tx; +extern const struct modem_tx_info sm_hapn4800_10_tx; +extern const struct modem_tx_info sm_hapn4800_pm8_tx; +extern const struct modem_tx_info sm_hapn4800_pm10_tx; +extern const struct modem_tx_info sm_fsk9600_4_tx; +extern const struct modem_tx_info sm_fsk9600_5_tx; + +extern const struct modem_rx_info sm_afsk1200_rx; +extern const struct modem_rx_info sm_afsk2400_7_rx; +extern const struct modem_rx_info sm_afsk2400_8_rx; +extern const struct modem_rx_info sm_afsk2666_rx; +extern const struct modem_rx_info sm_psk4800_rx; +extern const struct modem_rx_info sm_hapn4800_8_rx; +extern const struct modem_rx_info sm_hapn4800_10_rx; +extern const struct modem_rx_info sm_hapn4800_pm8_rx; +extern const struct modem_rx_info sm_hapn4800_pm10_rx; +extern const struct modem_rx_info sm_fsk9600_4_rx; +extern const struct modem_rx_info sm_fsk9600_5_rx; + +extern const struct hardware_info sm_hw_sbc; +extern const struct hardware_info sm_hw_sbcfdx; +extern const struct hardware_info sm_hw_wss; +extern const struct hardware_info sm_hw_wssfdx; + +extern const struct modem_tx_info *sm_modem_tx_table[]; +extern const struct modem_rx_info *sm_modem_rx_table[]; +extern const struct hardware_info *sm_hardware_table[]; + +/* --------------------------------------------------------------------- */ + +void sm_output_status(struct sm_state *sm); +/*void sm_output_open(struct sm_state *sm);*/ +/*void sm_output_close(struct sm_state *sm);*/ + +/* --------------------------------------------------------------------- */ + +extern void inline sm_int_freq(struct sm_state *sm) +{ +#ifdef SM_DEBUG + unsigned long cur_jiffies = jiffies; + /* + * measure the interrupt frequency + */ + sm->debug_vals.cur_intcnt++; + if ((cur_jiffies - sm->debug_vals.last_jiffies) >= HZ) { + sm->debug_vals.last_jiffies = cur_jiffies; + sm->debug_vals.last_intcnt = sm->debug_vals.cur_intcnt; + sm->debug_vals.cur_intcnt = 0; + } +#endif /* SM_DEBUG */ +} + +/* --------------------------------------------------------------------- */ +#endif /* _SM_H */ diff --git a/drivers/net/hamradio/soundmodem/sm_afsk1200.c b/drivers/net/hamradio/soundmodem/sm_afsk1200.c new file mode 100644 index 000000000..64b20a57c --- /dev/null +++ b/drivers/net/hamradio/soundmodem/sm_afsk1200.c @@ -0,0 +1,272 @@ +/*****************************************************************************/ + +/* + * sm_afsk1200.c -- soundcard radio modem driver, 1200 baud AFSK modem + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + */ + +#include "sm.h" +#include "sm_tbl_afsk1200.h" + +/* --------------------------------------------------------------------- */ + +struct demod_state_afsk12 { + unsigned int shreg; + unsigned int bit_pll; + unsigned char last_sample; + unsigned int dcd_shreg; + int dcd_sum0, dcd_sum1, dcd_sum2; + unsigned int dcd_time; + unsigned char last_rxbit; +}; + +struct mod_state_afsk12 { + unsigned int shreg; + unsigned char tx_bit; + unsigned int bit_pll; + unsigned int dds_inc; + unsigned int txphase; +}; + +/* --------------------------------------------------------------------- */ + +static const int dds_inc[2] = { + AFSK12_TX_FREQ_LO*0x10000/AFSK12_SAMPLE_RATE, + AFSK12_TX_FREQ_HI*0x10000/AFSK12_SAMPLE_RATE +}; + +static void modulator_1200_u8(struct sm_state *sm, unsigned char *buf, + unsigned int buflen) +{ + struct mod_state_afsk12 *st = (struct mod_state_afsk12 *)(&sm->m); + + for (; buflen > 0; buflen--) { + if (!((st->txphase++) & 7)) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit ^ (!(st->shreg & 1))) & 1; + st->shreg >>= 1; + } + st->dds_inc = dds_inc[st->tx_bit & 1]; + *buf++ = OFFSCOS(st->bit_pll); + st->bit_pll += st->dds_inc; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_1200_s16(struct sm_state *sm, short *buf, unsigned int buflen) +{ + struct mod_state_afsk12 *st = (struct mod_state_afsk12 *)(&sm->m); + + for (; buflen > 0; buflen--) { + if (!((st->txphase++) & 7)) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit ^ (!(st->shreg & 1))) & 1; + st->shreg >>= 1; + } + st->dds_inc = dds_inc[st->tx_bit & 1]; + *buf++ = COS(st->bit_pll); + st->bit_pll += st->dds_inc; + } +} + +/* --------------------------------------------------------------------- */ + +extern __inline__ int convolution8_u8(const unsigned char *st, const int *coeff, int csum) +{ + int sum = -0x80 * csum; + + sum += (st[0] * coeff[0]); + sum += (st[-1] * coeff[1]); + sum += (st[-2] * coeff[2]); + sum += (st[-3] * coeff[3]); + sum += (st[-4] * coeff[4]); + sum += (st[-5] * coeff[5]); + sum += (st[-6] * coeff[6]); + sum += (st[-7] * coeff[7]); + + sum >>= 7; + return sum * sum; +} + +extern __inline__ int convolution8_s16(const short *st, const int *coeff, int csum) +{ + int sum = 0; + + sum += (st[0] * coeff[0]); + sum += (st[-1] * coeff[1]); + sum += (st[-2] * coeff[2]); + sum += (st[-3] * coeff[3]); + sum += (st[-4] * coeff[4]); + sum += (st[-5] * coeff[5]); + sum += (st[-6] * coeff[6]); + sum += (st[-7] * coeff[7]); + + sum >>= 15; + return sum * sum; +} + +extern __inline__ int do_filter_1200_u8(const unsigned char *buf) +{ + int sum = convolution8_u8(buf, afsk12_tx_lo_i, SUM_AFSK12_TX_LO_I); + sum += convolution8_u8(buf, afsk12_tx_lo_q, SUM_AFSK12_TX_LO_Q); + sum -= convolution8_u8(buf, afsk12_tx_hi_i, SUM_AFSK12_TX_HI_I); + sum -= convolution8_u8(buf, afsk12_tx_hi_q, SUM_AFSK12_TX_HI_Q); + return sum; +} + +extern __inline__ int do_filter_1200_s16(const short *buf) +{ + int sum = convolution8_s16(buf, afsk12_tx_lo_i, SUM_AFSK12_TX_LO_I); + sum += convolution8_s16(buf, afsk12_tx_lo_q, SUM_AFSK12_TX_LO_Q); + sum -= convolution8_s16(buf, afsk12_tx_hi_i, SUM_AFSK12_TX_HI_I); + sum -= convolution8_s16(buf, afsk12_tx_hi_q, SUM_AFSK12_TX_HI_Q); + return sum; +} + +/* --------------------------------------------------------------------- */ + +static const int pll_corr[2] = { -0x1000, 0x1000 }; + +static void demodulator_1200_u8(struct sm_state *sm, const unsigned char *buf, unsigned int buflen) +{ + struct demod_state_afsk12 *st = (struct demod_state_afsk12 *)(&sm->d); + int j; + int sum; + unsigned char newsample; + + for (; buflen > 0; buflen--, buf++) { + sum = do_filter_1200_u8(buf); + st->dcd_shreg <<= 1; + st->bit_pll += 0x2000; + newsample = (sum > 0); + if (st->last_sample ^ newsample) { + st->last_sample = newsample; + st->dcd_shreg |= 1; + st->bit_pll += pll_corr + [st->bit_pll < 0x9000]; + j = 4 * hweight8(st->dcd_shreg & 0x38) + - hweight16(st->dcd_shreg & 0x7c0); + st->dcd_sum0 += j; + } + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 120; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->shreg >>= 1; + st->shreg |= (!(st->last_rxbit ^ + st->last_sample)) << 16; + st->last_rxbit = st->last_sample; + diag_trigger(sm); + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + } + diag_add(sm, (((int)*buf)-0x80) << 8, sum); + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_1200_s16(struct sm_state *sm, const short *buf, unsigned int buflen) +{ + struct demod_state_afsk12 *st = (struct demod_state_afsk12 *)(&sm->d); + int j; + int sum; + unsigned char newsample; + + for (; buflen > 0; buflen--, buf++) { + sum = do_filter_1200_s16(buf); + st->dcd_shreg <<= 1; + st->bit_pll += 0x2000; + newsample = (sum > 0); + if (st->last_sample ^ newsample) { + st->last_sample = newsample; + st->dcd_shreg |= 1; + st->bit_pll += pll_corr + [st->bit_pll < 0x9000]; + j = 4 * hweight8(st->dcd_shreg & 0x38) + - hweight16(st->dcd_shreg & 0x7c0); + st->dcd_sum0 += j; + } + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 120; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->shreg >>= 1; + st->shreg |= (!(st->last_rxbit ^ + st->last_sample)) << 16; + st->last_rxbit = st->last_sample; + diag_trigger(sm); + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + } + diag_add(sm, *buf, sum); + } +} + +/* --------------------------------------------------------------------- */ + +static void demod_init_1200(struct sm_state *sm) +{ + struct demod_state_afsk12 *st = (struct demod_state_afsk12 *)(&sm->d); + + st->dcd_time = 120; + st->dcd_sum0 = 2; +} + +/* --------------------------------------------------------------------- */ + +const struct modem_tx_info sm_afsk1200_tx = { + "afsk1200", sizeof(struct mod_state_afsk12), + AFSK12_SAMPLE_RATE, 1200, modulator_1200_u8, modulator_1200_s16, NULL +}; + +const struct modem_rx_info sm_afsk1200_rx = { + "afsk1200", sizeof(struct demod_state_afsk12), + AFSK12_SAMPLE_RATE, 1200, 8, AFSK12_SAMPLE_RATE/1200, + demodulator_1200_u8, demodulator_1200_s16, demod_init_1200 +}; + +/* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/soundmodem/sm_afsk2400_7.c b/drivers/net/hamradio/soundmodem/sm_afsk2400_7.c new file mode 100644 index 000000000..d217936ab --- /dev/null +++ b/drivers/net/hamradio/soundmodem/sm_afsk2400_7.c @@ -0,0 +1,296 @@ +/*****************************************************************************/ + +/* + * sm_afsk2400_7.c -- soundcard radio modem driver, 2400 baud AFSK modem + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + */ + +/* + * This driver is intended to be compatible with TCM3105 modems + * overclocked to 7.3728MHz. The mark and space frequencies therefore + * lie at 3658 and 1996 Hz. + * Note that I do _not_ recommend the building of such links, I provide + * this only for the users who live in the coverage area of such + * a "legacy" link. + */ + +#include "sm.h" +#include "sm_tbl_afsk2400_7.h" + +/* --------------------------------------------------------------------- */ + +struct demod_state_afsk24 { + unsigned int shreg; + unsigned int bit_pll; + unsigned char last_sample; + unsigned int dcd_shreg; + int dcd_sum0, dcd_sum1, dcd_sum2; + unsigned int dcd_time; + unsigned char last_rxbit; +}; + +struct mod_state_afsk24 { + unsigned int shreg; + unsigned char tx_bit; + unsigned int bit_pll; + unsigned int tx_seq; + unsigned int phinc; +}; + +/* --------------------------------------------------------------------- */ + +static const int dds_inc[2] = { AFSK24_TX_FREQ_LO*0x10000/AFSK24_SAMPLERATE, + AFSK24_TX_FREQ_HI*0x10000/AFSK24_SAMPLERATE }; + +static void modulator_2400_u8(struct sm_state *sm, unsigned char *buf, unsigned int buflen) +{ + struct mod_state_afsk24 *st = (struct mod_state_afsk24 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (st->tx_seq < 0x5555) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit ^ (!(st->shreg & 1))) & 1; + st->shreg >>= 1; + st->phinc = dds_inc[st->tx_bit & 1]; + } + st->tx_seq += 0x5555; + st->tx_seq &= 0xffff; + *buf = OFFSCOS(st->bit_pll); + st->bit_pll += st->phinc; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_2400_s16(struct sm_state *sm, short *buf, unsigned int buflen) +{ + struct mod_state_afsk24 *st = (struct mod_state_afsk24 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (st->tx_seq < 0x5555) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit ^ (!(st->shreg & 1))) & 1; + st->shreg >>= 1; + st->phinc = dds_inc[st->tx_bit & 1]; + } + st->tx_seq += 0x5555; + st->tx_seq &= 0xffff; + *buf = COS(st->bit_pll); + st->bit_pll += st->phinc; + } +} + +/* --------------------------------------------------------------------- */ + +extern __inline__ int convolution14_u8(const unsigned char *st, const int *coeff, int csum) +{ + int sum = -0x80 * csum; + + sum += (st[0] * coeff[0]); + sum += (st[-1] * coeff[1]); + sum += (st[-2] * coeff[2]); + sum += (st[-3] * coeff[3]); + sum += (st[-4] * coeff[4]); + sum += (st[-5] * coeff[5]); + sum += (st[-6] * coeff[6]); + sum += (st[-7] * coeff[7]); + sum += (st[-8] * coeff[8]); + sum += (st[-9] * coeff[9]); + sum += (st[-10] * coeff[10]); + sum += (st[-11] * coeff[11]); + sum += (st[-12] * coeff[12]); + sum += (st[-13] * coeff[13]); + + sum >>= 7; + return sum * sum; +} + +extern __inline__ int convolution14_s16(const short *st, const int *coeff, int csum) +{ + int sum = 0; + + sum += (st[0] * coeff[0]); + sum += (st[-1] * coeff[1]); + sum += (st[-2] * coeff[2]); + sum += (st[-3] * coeff[3]); + sum += (st[-4] * coeff[4]); + sum += (st[-5] * coeff[5]); + sum += (st[-6] * coeff[6]); + sum += (st[-7] * coeff[7]); + sum += (st[-8] * coeff[8]); + sum += (st[-9] * coeff[9]); + sum += (st[-10] * coeff[10]); + sum += (st[-11] * coeff[11]); + sum += (st[-12] * coeff[12]); + sum += (st[-13] * coeff[13]); + + sum >>= 15; + return sum * sum; +} + +extern __inline__ int do_filter_2400_u8(const unsigned char *buf) +{ + int sum = convolution14_u8(buf, afsk24_tx_lo_i, SUM_AFSK24_TX_LO_I); + sum += convolution14_u8(buf, afsk24_tx_lo_q, SUM_AFSK24_TX_LO_Q); + sum -= convolution14_u8(buf, afsk24_tx_hi_i, SUM_AFSK24_TX_HI_I); + sum -= convolution14_u8(buf, afsk24_tx_hi_q, SUM_AFSK24_TX_HI_Q); + return sum; +} + +extern __inline__ int do_filter_2400_s16(const short *buf) +{ + int sum = convolution14_s16(buf, afsk24_tx_lo_i, SUM_AFSK24_TX_LO_I); + sum += convolution14_s16(buf, afsk24_tx_lo_q, SUM_AFSK24_TX_LO_Q); + sum -= convolution14_s16(buf, afsk24_tx_hi_i, SUM_AFSK24_TX_HI_I); + sum -= convolution14_s16(buf, afsk24_tx_hi_q, SUM_AFSK24_TX_HI_Q); + return sum; +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_2400_u8(struct sm_state *sm, const unsigned char *buf, unsigned int buflen) +{ + struct demod_state_afsk24 *st = (struct demod_state_afsk24 *)(&sm->d); + int j; + int sum; + unsigned char newsample; + + for (; buflen > 0; buflen--, buf++) { + sum = do_filter_2400_u8(buf); + st->dcd_shreg <<= 1; + st->bit_pll += AFSK24_BITPLL_INC; + newsample = (sum > 0); + if (st->last_sample ^ newsample) { + st->last_sample = newsample; + st->dcd_shreg |= 1; + if (st->bit_pll < (0x8000+AFSK24_BITPLL_INC/2)) + st->bit_pll += AFSK24_BITPLL_INC/2; + else + st->bit_pll -= AFSK24_BITPLL_INC/2; + j = /* 2 * */ hweight8(st->dcd_shreg & 0x1c) + - hweight16(st->dcd_shreg & 0x1e0); + st->dcd_sum0 += j; + } + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 120; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->shreg >>= 1; + st->shreg |= (!(st->last_rxbit ^ + st->last_sample)) << 16; + st->last_rxbit = st->last_sample; + diag_trigger(sm); + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + } + diag_add(sm, (((int)*buf)-0x80) << 8, sum); + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_2400_s16(struct sm_state *sm, const short *buf, unsigned int buflen) +{ + struct demod_state_afsk24 *st = (struct demod_state_afsk24 *)(&sm->d); + int j; + int sum; + unsigned char newsample; + + for (; buflen > 0; buflen--, buf++) { + sum = do_filter_2400_s16(buf); + st->dcd_shreg <<= 1; + st->bit_pll += AFSK24_BITPLL_INC; + newsample = (sum > 0); + if (st->last_sample ^ newsample) { + st->last_sample = newsample; + st->dcd_shreg |= 1; + if (st->bit_pll < (0x8000+AFSK24_BITPLL_INC/2)) + st->bit_pll += AFSK24_BITPLL_INC/2; + else + st->bit_pll -= AFSK24_BITPLL_INC/2; + j = /* 2 * */ hweight8(st->dcd_shreg & 0x1c) + - hweight16(st->dcd_shreg & 0x1e0); + st->dcd_sum0 += j; + } + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 120; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->shreg >>= 1; + st->shreg |= (!(st->last_rxbit ^ + st->last_sample)) << 16; + st->last_rxbit = st->last_sample; + diag_trigger(sm); + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + } + diag_add(sm, *buf, sum); + } +} + +/* --------------------------------------------------------------------- */ + +static void demod_init_2400(struct sm_state *sm) +{ + struct demod_state_afsk24 *st = (struct demod_state_afsk24 *)(&sm->d); + + st->dcd_time = 120; + st->dcd_sum0 = 2; +} + +/* --------------------------------------------------------------------- */ + +const struct modem_tx_info sm_afsk2400_7_tx = { + "afsk2400_7", sizeof(struct mod_state_afsk24), AFSK24_SAMPLERATE, 2400, + modulator_2400_u8, modulator_2400_s16, NULL +}; + +const struct modem_rx_info sm_afsk2400_7_rx = { + "afsk2400_7", sizeof(struct demod_state_afsk24), + AFSK24_SAMPLERATE, 2400, 14, AFSK24_SAMPLERATE/2400, + demodulator_2400_u8, demodulator_2400_s16, demod_init_2400 +}; + +/* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/soundmodem/sm_afsk2400_8.c b/drivers/net/hamradio/soundmodem/sm_afsk2400_8.c new file mode 100644 index 000000000..23d233746 --- /dev/null +++ b/drivers/net/hamradio/soundmodem/sm_afsk2400_8.c @@ -0,0 +1,296 @@ +/*****************************************************************************/ + +/* + * sm_afsk2400_8.c -- soundcard radio modem driver, 2400 baud AFSK modem + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + */ + +/* + * This driver is intended to be compatible with TCM3105 modems + * overclocked to 8MHz. The mark and space frequencies therefore + * lie at 3970 and 2165 Hz. + * Note that I do _not_ recommend the building of such links, I provide + * this only for the users who live in the coverage area of such + * a "legacy" link. + */ + +#include "sm.h" +#include "sm_tbl_afsk2400_8.h" + +/* --------------------------------------------------------------------- */ + +struct demod_state_afsk24 { + unsigned int shreg; + unsigned int bit_pll; + unsigned char last_sample; + unsigned int dcd_shreg; + int dcd_sum0, dcd_sum1, dcd_sum2; + unsigned int dcd_time; + unsigned char last_rxbit; +}; + +struct mod_state_afsk24 { + unsigned int shreg; + unsigned char tx_bit; + unsigned int bit_pll; + unsigned int tx_seq; + unsigned int phinc; +}; + +/* --------------------------------------------------------------------- */ + +static const int dds_inc[2] = { AFSK24_TX_FREQ_LO*0x10000/AFSK24_SAMPLERATE, + AFSK24_TX_FREQ_HI*0x10000/AFSK24_SAMPLERATE }; + +static void modulator_2400_u8(struct sm_state *sm, unsigned char *buf, unsigned int buflen) +{ + struct mod_state_afsk24 *st = (struct mod_state_afsk24 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (st->tx_seq < 0x5555) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit ^ (!(st->shreg & 1))) & 1; + st->shreg >>= 1; + st->phinc = dds_inc[st->tx_bit & 1]; + } + st->tx_seq += 0x5555; + st->tx_seq &= 0xffff; + *buf = OFFSCOS(st->bit_pll); + st->bit_pll += st->phinc; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_2400_s16(struct sm_state *sm, short *buf, unsigned int buflen) +{ + struct mod_state_afsk24 *st = (struct mod_state_afsk24 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (st->tx_seq < 0x5555) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit ^ (!(st->shreg & 1))) & 1; + st->shreg >>= 1; + st->phinc = dds_inc[st->tx_bit & 1]; + } + st->tx_seq += 0x5555; + st->tx_seq &= 0xffff; + *buf = COS(st->bit_pll); + st->bit_pll += st->phinc; + } +} + +/* --------------------------------------------------------------------- */ + +extern __inline__ int convolution14_u8(const unsigned char *st, const int *coeff, int csum) +{ + int sum = -0x80 * csum; + + sum += (st[0] * coeff[0]); + sum += (st[-1] * coeff[1]); + sum += (st[-2] * coeff[2]); + sum += (st[-3] * coeff[3]); + sum += (st[-4] * coeff[4]); + sum += (st[-5] * coeff[5]); + sum += (st[-6] * coeff[6]); + sum += (st[-7] * coeff[7]); + sum += (st[-8] * coeff[8]); + sum += (st[-9] * coeff[9]); + sum += (st[-10] * coeff[10]); + sum += (st[-11] * coeff[11]); + sum += (st[-12] * coeff[12]); + sum += (st[-13] * coeff[13]); + + sum >>= 7; + return sum * sum; +} + +extern __inline__ int convolution14_s16(const short *st, const int *coeff, int csum) +{ + int sum = 0; + + sum += (st[0] * coeff[0]); + sum += (st[-1] * coeff[1]); + sum += (st[-2] * coeff[2]); + sum += (st[-3] * coeff[3]); + sum += (st[-4] * coeff[4]); + sum += (st[-5] * coeff[5]); + sum += (st[-6] * coeff[6]); + sum += (st[-7] * coeff[7]); + sum += (st[-8] * coeff[8]); + sum += (st[-9] * coeff[9]); + sum += (st[-10] * coeff[10]); + sum += (st[-11] * coeff[11]); + sum += (st[-12] * coeff[12]); + sum += (st[-13] * coeff[13]); + + sum >>= 15; + return sum * sum; +} + +extern __inline__ int do_filter_2400_u8(const unsigned char *buf) +{ + int sum = convolution14_u8(buf, afsk24_tx_lo_i, SUM_AFSK24_TX_LO_I); + sum += convolution14_u8(buf, afsk24_tx_lo_q, SUM_AFSK24_TX_LO_Q); + sum -= convolution14_u8(buf, afsk24_tx_hi_i, SUM_AFSK24_TX_HI_I); + sum -= convolution14_u8(buf, afsk24_tx_hi_q, SUM_AFSK24_TX_HI_Q); + return sum; +} + +extern __inline__ int do_filter_2400_s16(const short *buf) +{ + int sum = convolution14_s16(buf, afsk24_tx_lo_i, SUM_AFSK24_TX_LO_I); + sum += convolution14_s16(buf, afsk24_tx_lo_q, SUM_AFSK24_TX_LO_Q); + sum -= convolution14_s16(buf, afsk24_tx_hi_i, SUM_AFSK24_TX_HI_I); + sum -= convolution14_s16(buf, afsk24_tx_hi_q, SUM_AFSK24_TX_HI_Q); + return sum; +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_2400_u8(struct sm_state *sm, const unsigned char *buf, unsigned int buflen) +{ + struct demod_state_afsk24 *st = (struct demod_state_afsk24 *)(&sm->d); + int j; + int sum; + unsigned char newsample; + + for (; buflen > 0; buflen--, buf++) { + sum = do_filter_2400_u8(buf); + st->dcd_shreg <<= 1; + st->bit_pll += AFSK24_BITPLL_INC; + newsample = (sum > 0); + if (st->last_sample ^ newsample) { + st->last_sample = newsample; + st->dcd_shreg |= 1; + if (st->bit_pll < (0x8000+AFSK24_BITPLL_INC/2)) + st->bit_pll += AFSK24_BITPLL_INC/2; + else + st->bit_pll -= AFSK24_BITPLL_INC/2; + j = /* 2 * */ hweight8(st->dcd_shreg & 0x1c) + - hweight16(st->dcd_shreg & 0x1e0); + st->dcd_sum0 += j; + } + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 120; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->shreg >>= 1; + st->shreg |= (!(st->last_rxbit ^ + st->last_sample)) << 16; + st->last_rxbit = st->last_sample; + diag_trigger(sm); + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + } + diag_add(sm, (((int)*buf)-0x80) << 8, sum); + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_2400_s16(struct sm_state *sm, const short *buf, unsigned int buflen) +{ + struct demod_state_afsk24 *st = (struct demod_state_afsk24 *)(&sm->d); + int j; + int sum; + unsigned char newsample; + + for (; buflen > 0; buflen--, buf++) { + sum = do_filter_2400_s16(buf); + st->dcd_shreg <<= 1; + st->bit_pll += AFSK24_BITPLL_INC; + newsample = (sum > 0); + if (st->last_sample ^ newsample) { + st->last_sample = newsample; + st->dcd_shreg |= 1; + if (st->bit_pll < (0x8000+AFSK24_BITPLL_INC/2)) + st->bit_pll += AFSK24_BITPLL_INC/2; + else + st->bit_pll -= AFSK24_BITPLL_INC/2; + j = /* 2 * */ hweight8(st->dcd_shreg & 0x1c) + - hweight16(st->dcd_shreg & 0x1e0); + st->dcd_sum0 += j; + } + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 120; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->shreg >>= 1; + st->shreg |= (!(st->last_rxbit ^ + st->last_sample)) << 16; + st->last_rxbit = st->last_sample; + diag_trigger(sm); + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + } + diag_add(sm, *buf, sum); + } +} + +/* --------------------------------------------------------------------- */ + +static void demod_init_2400(struct sm_state *sm) +{ + struct demod_state_afsk24 *st = (struct demod_state_afsk24 *)(&sm->d); + + st->dcd_time = 120; + st->dcd_sum0 = 2; +} + +/* --------------------------------------------------------------------- */ + +const struct modem_tx_info sm_afsk2400_8_tx = { + "afsk2400_8", sizeof(struct mod_state_afsk24), AFSK24_SAMPLERATE, 2400, + modulator_2400_u8, modulator_2400_s16, NULL +}; + +const struct modem_rx_info sm_afsk2400_8_rx = { + "afsk2400_8", sizeof(struct demod_state_afsk24), + AFSK24_SAMPLERATE, 2400, 14, AFSK24_SAMPLERATE/2400, + demodulator_2400_u8, demodulator_2400_s16, demod_init_2400 +}; + +/* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/soundmodem/sm_fsk9600.c b/drivers/net/hamradio/soundmodem/sm_fsk9600.c new file mode 100644 index 000000000..bc2fb53b1 --- /dev/null +++ b/drivers/net/hamradio/soundmodem/sm_fsk9600.c @@ -0,0 +1,391 @@ +/*****************************************************************************/ + +/* + * sm_fsk9600.c -- soundcard radio modem driver, + * 9600 baud G3RUH compatible FSK modem + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + */ + +#include "sm.h" +#include "sm_tbl_fsk9600.h" + +/* --------------------------------------------------------------------- */ + +struct demod_state_fsk96 { + unsigned int shreg; + unsigned long descram; + unsigned int bit_pll; + unsigned char last_sample; + unsigned int dcd_shreg; + int dcd_sum0, dcd_sum1, dcd_sum2; + unsigned int dcd_time; +}; + +struct mod_state_fsk96 { + unsigned int shreg; + unsigned long scram; + unsigned char tx_bit; + unsigned char *txtbl; + unsigned int txphase; +}; + +/* --------------------------------------------------------------------- */ + +#define DESCRAM_TAP1 0x20000 +#define DESCRAM_TAP2 0x01000 +#define DESCRAM_TAP3 0x00001 + +#define DESCRAM_TAPSH1 17 +#define DESCRAM_TAPSH2 12 +#define DESCRAM_TAPSH3 0 + +#define SCRAM_TAP1 0x20000 /* X^17 */ +#define SCRAM_TAPN 0x00021 /* X^0+X^5 */ + +/* --------------------------------------------------------------------- */ + +static void modulator_9600_4_u8(struct sm_state *sm, unsigned char *buf, unsigned int buflen) +{ + struct mod_state_fsk96 *st = (struct mod_state_fsk96 *)(&sm->m); + + for (; buflen > 0; buflen--) { + if (!st->txphase++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->scram = (st->scram << 1) | (st->scram & 1); + st->scram ^= !(st->shreg & 1); + st->shreg >>= 1; + if (st->scram & (SCRAM_TAP1 << 1)) + st->scram ^= SCRAM_TAPN << 1; + st->tx_bit = (st->tx_bit << 1) | (!!(st->scram & (SCRAM_TAP1 << 2))); + st->txtbl = fsk96_txfilt_4 + (st->tx_bit & 0xff); + } + if (st->txphase >= 4) + st->txphase = 0; + *buf++ = *st->txtbl; + st->txtbl += 0x100; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_9600_4_s16(struct sm_state *sm, short *buf, unsigned int buflen) +{ + struct mod_state_fsk96 *st = (struct mod_state_fsk96 *)(&sm->m); + + for (; buflen > 0; buflen--) { + if (!st->txphase++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->scram = (st->scram << 1) | (st->scram & 1); + st->scram ^= !(st->shreg & 1); + st->shreg >>= 1; + if (st->scram & (SCRAM_TAP1 << 1)) + st->scram ^= SCRAM_TAPN << 1; + st->tx_bit = (st->tx_bit << 1) | (!!(st->scram & (SCRAM_TAP1 << 2))); + st->txtbl = fsk96_txfilt_4 + (st->tx_bit & 0xff); + } + if (st->txphase >= 4) + st->txphase = 0; + *buf++ = ((*st->txtbl)-0x80) << 8; + st->txtbl += 0x100; + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_9600_4_u8(struct sm_state *sm, const unsigned char *buf, unsigned int buflen) +{ + struct demod_state_fsk96 *st = (struct demod_state_fsk96 *)(&sm->d); + static const int pll_corr[2] = { -0x1000, 0x1000 }; + unsigned char curbit; + unsigned int descx; + + for (; buflen > 0; buflen--, buf++) { + st->dcd_shreg <<= 1; + st->bit_pll += 0x4000; + curbit = (*buf >= 0x80); + if (st->last_sample ^ curbit) { + st->dcd_shreg |= 1; + st->bit_pll += pll_corr[st->bit_pll < 0xa000]; + st->dcd_sum0 += 8 * hweight8(st->dcd_shreg & 0x0c) - + !!(st->dcd_shreg & 0x10); + } + st->last_sample = curbit; + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 240; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->descram = (st->descram << 1) | curbit; + descx = st->descram ^ (st->descram >> 1); + descx ^= ((descx >> DESCRAM_TAPSH1) ^ + (descx >> DESCRAM_TAPSH2)); + st->shreg >>= 1; + st->shreg |= (!(descx & 1)) << 16; + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + diag_trigger(sm); + } + diag_add_one(sm, ((short)(*buf - 0x80)) << 8); + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_9600_4_s16(struct sm_state *sm, const short *buf, unsigned int buflen) +{ + struct demod_state_fsk96 *st = (struct demod_state_fsk96 *)(&sm->d); + static const int pll_corr[2] = { -0x1000, 0x1000 }; + unsigned char curbit; + unsigned int descx; + + for (; buflen > 0; buflen--, buf++) { + st->dcd_shreg <<= 1; + st->bit_pll += 0x4000; + curbit = (*buf >= 0); + if (st->last_sample ^ curbit) { + st->dcd_shreg |= 1; + st->bit_pll += pll_corr[st->bit_pll < 0xa000]; + st->dcd_sum0 += 8 * hweight8(st->dcd_shreg & 0x0c) - + !!(st->dcd_shreg & 0x10); + } + st->last_sample = curbit; + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 240; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->descram = (st->descram << 1) | curbit; + descx = st->descram ^ (st->descram >> 1); + descx ^= ((descx >> DESCRAM_TAPSH1) ^ + (descx >> DESCRAM_TAPSH2)); + st->shreg >>= 1; + st->shreg |= (!(descx & 1)) << 16; + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + diag_trigger(sm); + } + diag_add_one(sm, *buf); + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_9600_5_u8(struct sm_state *sm, unsigned char *buf, unsigned int buflen) +{ + struct mod_state_fsk96 *st = (struct mod_state_fsk96 *)(&sm->m); + + for (; buflen > 0; buflen--) { + if (!st->txphase++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->scram = (st->scram << 1) | (st->scram & 1); + st->scram ^= !(st->shreg & 1); + st->shreg >>= 1; + if (st->scram & (SCRAM_TAP1 << 1)) + st->scram ^= SCRAM_TAPN << 1; + st->tx_bit = (st->tx_bit << 1) | (!!(st->scram & (SCRAM_TAP1 << 2))); + st->txtbl = fsk96_txfilt_5 + (st->tx_bit & 0xff); + } + if (st->txphase >= 5) + st->txphase = 0; + *buf++ = *st->txtbl; + st->txtbl += 0x100; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_9600_5_s16(struct sm_state *sm, short *buf, unsigned int buflen) +{ + struct mod_state_fsk96 *st = (struct mod_state_fsk96 *)(&sm->m); + + for (; buflen > 0; buflen--) { + if (!st->txphase++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->scram = (st->scram << 1) | (st->scram & 1); + st->scram ^= !(st->shreg & 1); + st->shreg >>= 1; + if (st->scram & (SCRAM_TAP1 << 1)) + st->scram ^= SCRAM_TAPN << 1; + st->tx_bit = (st->tx_bit << 1) | (!!(st->scram & (SCRAM_TAP1 << 2))); + st->txtbl = fsk96_txfilt_5 + (st->tx_bit & 0xff); + } + if (st->txphase >= 5) + st->txphase = 0; + *buf++ = ((*st->txtbl)-0x80)<<8; + st->txtbl += 0x100; + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_9600_5_u8(struct sm_state *sm, const unsigned char *buf, unsigned int buflen) +{ + struct demod_state_fsk96 *st = (struct demod_state_fsk96 *)(&sm->d); + static const int pll_corr[2] = { -0x1000, 0x1000 }; + unsigned char curbit; + unsigned int descx; + + for (; buflen > 0; buflen--, buf++) { + st->dcd_shreg <<= 1; + st->bit_pll += 0x3333; + curbit = (*buf >= 0x80); + if (st->last_sample ^ curbit) { + st->dcd_shreg |= 1; + st->bit_pll += pll_corr[st->bit_pll < 0x9999]; + st->dcd_sum0 += 16 * hweight8(st->dcd_shreg & 0x0c) - + hweight8(st->dcd_shreg & 0x70); + } + st->last_sample = curbit; + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 240; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->descram = (st->descram << 1) | curbit; + descx = st->descram ^ (st->descram >> 1); + descx ^= ((descx >> DESCRAM_TAPSH1) ^ + (descx >> DESCRAM_TAPSH2)); + st->shreg >>= 1; + st->shreg |= (!(descx & 1)) << 16; + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + diag_trigger(sm); + } + diag_add_one(sm, ((short)(*buf - 0x80)) << 8); + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_9600_5_s16(struct sm_state *sm, const short *buf, unsigned int buflen) +{ + struct demod_state_fsk96 *st = (struct demod_state_fsk96 *)(&sm->d); + static const int pll_corr[2] = { -0x1000, 0x1000 }; + unsigned char curbit; + unsigned int descx; + + for (; buflen > 0; buflen--, buf++) { + st->dcd_shreg <<= 1; + st->bit_pll += 0x3333; + curbit = (*buf >= 0); + if (st->last_sample ^ curbit) { + st->dcd_shreg |= 1; + st->bit_pll += pll_corr[st->bit_pll < 0x9999]; + st->dcd_sum0 += 16 * hweight8(st->dcd_shreg & 0x0c) - + hweight8(st->dcd_shreg & 0x70); + } + st->last_sample = curbit; + hdlcdrv_channelbit(&sm->hdrv, st->last_sample); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 240; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->descram = (st->descram << 1) | curbit; + descx = st->descram ^ (st->descram >> 1); + descx ^= ((descx >> DESCRAM_TAPSH1) ^ + (descx >> DESCRAM_TAPSH2)); + st->shreg >>= 1; + st->shreg |= (!(descx & 1)) << 16; + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + diag_trigger(sm); + } + diag_add_one(sm, *buf); + } +} + +/* --------------------------------------------------------------------- */ + +static void demod_init_9600(struct sm_state *sm) +{ + struct demod_state_fsk96 *st = (struct demod_state_fsk96 *)(&sm->d); + + st->dcd_time = 240; + st->dcd_sum0 = 2; +} + +/* --------------------------------------------------------------------- */ + +const struct modem_tx_info sm_fsk9600_4_tx = { + "fsk9600", sizeof(struct mod_state_fsk96), 38400, 9600, + modulator_9600_4_u8, modulator_9600_4_s16, NULL +}; + +const struct modem_rx_info sm_fsk9600_4_rx = { + "fsk9600", sizeof(struct demod_state_fsk96), 38400, 9600, 1, 4, + demodulator_9600_4_u8, demodulator_9600_4_s16, demod_init_9600 +}; + +/* --------------------------------------------------------------------- */ + +const struct modem_tx_info sm_fsk9600_5_tx = { + "fsk9600", sizeof(struct mod_state_fsk96), 48000, 9600, + modulator_9600_5_u8, modulator_9600_5_s16, NULL +}; + +const struct modem_rx_info sm_fsk9600_5_rx = { + "fsk9600", sizeof(struct demod_state_fsk96), 48000, 9600, 1, 5, + demodulator_9600_5_u8, demodulator_9600_5_s16, demod_init_9600 +}; + +/* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/soundmodem/sm_hapn4800.c b/drivers/net/hamradio/soundmodem/sm_hapn4800.c new file mode 100644 index 000000000..f6babcd9d --- /dev/null +++ b/drivers/net/hamradio/soundmodem/sm_hapn4800.c @@ -0,0 +1,560 @@ +/*****************************************************************************/ + +/* + * sm_hapn4800.c -- soundcard radio modem driver, 4800 baud HAPN modem + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + * + * This module implements a (hopefully) HAPN (Hamilton Area Packet + * Network) compatible 4800 baud modem. + * The HAPN modem uses kind of "duobinary signalling" (not really, + * duobinary signalling gives ... 0 0 -1 0 1 0 0 ... at the sampling + * instants, whereas HAPN signalling gives ... 0 0 -1 1 0 0 ..., see + * Proakis, Digital Communications). + * The code is untested. It is compatible with itself (i.e. it can decode + * the packets it sent), but I could not test if it is compatible with + * any "real" HAPN modem, since noone uses it in my region of the world. + * Feedback therefore welcome. + */ + +#include "sm.h" +#include "sm_tbl_hapn4800.h" + +/* --------------------------------------------------------------------- */ + +struct demod_state_hapn48 { + unsigned int shreg; + unsigned int bit_pll; + unsigned char last_bit; + unsigned char last_bit2; + unsigned int dcd_shreg; + int dcd_sum0, dcd_sum1, dcd_sum2; + unsigned int dcd_time; + int lvlhi, lvllo; +}; + +struct mod_state_hapn48 { + unsigned int shreg; + unsigned char tx_bit; + unsigned int tx_seq; + const unsigned char *tbl; +}; + +/* --------------------------------------------------------------------- */ + +static void modulator_hapn4800_10_u8(struct sm_state *sm, unsigned char *buf, unsigned int buflen) +{ + struct mod_state_hapn48 *st = (struct mod_state_hapn48 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (!st->tx_seq++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = ((st->tx_bit << 1) | + (st->tx_bit & 1)); + st->tx_bit ^= (!(st->shreg & 1)); + st->shreg >>= 1; + st->tbl = hapn48_txfilt_10 + (st->tx_bit & 0xf); + } + if (st->tx_seq >= 10) + st->tx_seq = 0; + *buf = *st->tbl; + st->tbl += 0x10; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_hapn4800_10_s16(struct sm_state *sm, short *buf, unsigned int buflen) +{ + struct mod_state_hapn48 *st = (struct mod_state_hapn48 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (!st->tx_seq++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = ((st->tx_bit << 1) | + (st->tx_bit & 1)); + st->tx_bit ^= (!(st->shreg & 1)); + st->shreg >>= 1; + st->tbl = hapn48_txfilt_10 + (st->tx_bit & 0xf); + } + if (st->tx_seq >= 10) + st->tx_seq = 0; + *buf = ((*st->tbl)-0x80)<<8; + st->tbl += 0x10; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_hapn4800_8_u8(struct sm_state *sm, unsigned char *buf, unsigned int buflen) +{ + struct mod_state_hapn48 *st = (struct mod_state_hapn48 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (!st->tx_seq++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit << 1) | (st->tx_bit & 1); + st->tx_bit ^= !(st->shreg & 1); + st->shreg >>= 1; + st->tbl = hapn48_txfilt_8 + (st->tx_bit & 0xf); + } + if (st->tx_seq >= 8) + st->tx_seq = 0; + *buf = *st->tbl; + st->tbl += 0x10; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_hapn4800_8_s16(struct sm_state *sm, short *buf, unsigned int buflen) +{ + struct mod_state_hapn48 *st = (struct mod_state_hapn48 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (!st->tx_seq++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit << 1) | (st->tx_bit & 1); + st->tx_bit ^= !(st->shreg & 1); + st->shreg >>= 1; + st->tbl = hapn48_txfilt_8 + (st->tx_bit & 0xf); + } + if (st->tx_seq >= 8) + st->tx_seq = 0; + *buf = ((*st->tbl)-0x80)<<8; + st->tbl += 0x10; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_hapn4800_pm10_u8(struct sm_state *sm, unsigned char *buf, unsigned int buflen) +{ + struct mod_state_hapn48 *st = (struct mod_state_hapn48 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (!st->tx_seq++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = ((st->tx_bit << 1) | + (st->tx_bit & 1)); + st->tx_bit ^= (!(st->shreg & 1)); + st->shreg >>= 1; + st->tbl = hapn48_txfilt_pm10 + (st->tx_bit & 0xf); + } + if (st->tx_seq >= 10) + st->tx_seq = 0; + *buf = *st->tbl; + st->tbl += 0x10; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_hapn4800_pm10_s16(struct sm_state *sm, short *buf, unsigned int buflen) +{ + struct mod_state_hapn48 *st = (struct mod_state_hapn48 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (!st->tx_seq++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = ((st->tx_bit << 1) | + (st->tx_bit & 1)); + st->tx_bit ^= (!(st->shreg & 1)); + st->shreg >>= 1; + st->tbl = hapn48_txfilt_pm10 + (st->tx_bit & 0xf); + } + if (st->tx_seq >= 10) + st->tx_seq = 0; + *buf = ((*st->tbl)-0x80)<<8; + st->tbl += 0x10; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_hapn4800_pm8_u8(struct sm_state *sm, unsigned char *buf, unsigned int buflen) +{ + struct mod_state_hapn48 *st = (struct mod_state_hapn48 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (!st->tx_seq++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit << 1) | (st->tx_bit & 1); + st->tx_bit ^= !(st->shreg & 1); + st->shreg >>= 1; + st->tbl = hapn48_txfilt_pm8 + (st->tx_bit & 0xf); + } + if (st->tx_seq >= 8) + st->tx_seq = 0; + *buf = *st->tbl; + st->tbl += 0x10; + } +} + +/* --------------------------------------------------------------------- */ + +static void modulator_hapn4800_pm8_s16(struct sm_state *sm, short *buf, unsigned int buflen) +{ + struct mod_state_hapn48 *st = (struct mod_state_hapn48 *)(&sm->m); + + for (; buflen > 0; buflen--, buf++) { + if (!st->tx_seq++) { + if (st->shreg <= 1) + st->shreg = hdlcdrv_getbits(&sm->hdrv) | 0x10000; + st->tx_bit = (st->tx_bit << 1) | (st->tx_bit & 1); + st->tx_bit ^= !(st->shreg & 1); + st->shreg >>= 1; + st->tbl = hapn48_txfilt_pm8 + (st->tx_bit & 0xf); + } + if (st->tx_seq >= 8) + st->tx_seq = 0; + *buf = ((*st->tbl)-0x80)<<8; + st->tbl += 0x10; + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_hapn4800_10_u8(struct sm_state *sm, const unsigned char *buf, unsigned int buflen) +{ + struct demod_state_hapn48 *st = (struct demod_state_hapn48 *)(&sm->d); + static const int pll_corr[2] = { -0x800, 0x800 }; + int curst, cursync; + int inv; + + for (; buflen > 0; buflen--, buf++) { + inv = ((int)(buf[-2])-0x80) << 8; + st->lvlhi = (st->lvlhi * 65309) >> 16; /* decay */ + st->lvllo = (st->lvllo * 65309) >> 16; /* decay */ + if (inv > st->lvlhi) + st->lvlhi = inv; + if (inv < st->lvllo) + st->lvllo = inv; + if (buflen & 1) + st->dcd_shreg <<= 1; + st->bit_pll += 0x199a; + curst = cursync = 0; + if (inv > st->lvlhi >> 1) { + curst = 1; + cursync = (buf[-2] > buf[-1] && buf[-2] > buf[-3] && + buf[-2] > buf[-0] && buf[-2] > buf[-4]); + } else if (inv < st->lvllo >> 1) { + curst = -1; + cursync = (buf[-2] < buf[-1] && buf[-2] < buf[-3] && + buf[-2] < buf[-0] && buf[-2] < buf[-4]); + } + if (cursync) { + st->dcd_shreg |= cursync; + st->bit_pll += pll_corr[((st->bit_pll - 0x8000u) & 0xffffu) < 0x8ccdu]; + st->dcd_sum0 += 16 * hweight32(st->dcd_shreg & 0x18c6318c) - + hweight32(st->dcd_shreg & 0xe739ce70); + } + hdlcdrv_channelbit(&sm->hdrv, cursync); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 240; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->last_bit2 = st->last_bit; + if (curst < 0) + st->last_bit = 0; + else if (curst > 0) + st->last_bit = 1; + st->shreg >>= 1; + st->shreg |= ((st->last_bit ^ st->last_bit2 ^ 1) & 1) << 16; + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + diag_trigger(sm); + } + diag_add_one(sm, inv); + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_hapn4800_10_s16(struct sm_state *sm, const short *buf, unsigned int buflen) +{ + struct demod_state_hapn48 *st = (struct demod_state_hapn48 *)(&sm->d); + static const int pll_corr[2] = { -0x800, 0x800 }; + int curst, cursync; + int inv; + + for (; buflen > 0; buflen--, buf++) { + inv = buf[-2]; + st->lvlhi = (st->lvlhi * 65309) >> 16; /* decay */ + st->lvllo = (st->lvllo * 65309) >> 16; /* decay */ + if (inv > st->lvlhi) + st->lvlhi = inv; + if (inv < st->lvllo) + st->lvllo = inv; + if (buflen & 1) + st->dcd_shreg <<= 1; + st->bit_pll += 0x199a; + curst = cursync = 0; + if (inv > st->lvlhi >> 1) { + curst = 1; + cursync = (buf[-2] > buf[-1] && buf[-2] > buf[-3] && + buf[-2] > buf[-0] && buf[-2] > buf[-4]); + } else if (inv < st->lvllo >> 1) { + curst = -1; + cursync = (buf[-2] < buf[-1] && buf[-2] < buf[-3] && + buf[-2] < buf[-0] && buf[-2] < buf[-4]); + } + if (cursync) { + st->dcd_shreg |= cursync; + st->bit_pll += pll_corr[((st->bit_pll - 0x8000u) & 0xffffu) < 0x8ccdu]; + st->dcd_sum0 += 16 * hweight32(st->dcd_shreg & 0x18c6318c) - + hweight32(st->dcd_shreg & 0xe739ce70); + } + hdlcdrv_channelbit(&sm->hdrv, cursync); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 240; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->last_bit2 = st->last_bit; + if (curst < 0) + st->last_bit = 0; + else if (curst > 0) + st->last_bit = 1; + st->shreg >>= 1; + st->shreg |= ((st->last_bit ^ st->last_bit2 ^ 1) & 1) << 16; + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + diag_trigger(sm); + } + diag_add_one(sm, inv); + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_hapn4800_8_u8(struct sm_state *sm, const unsigned char *buf, unsigned int buflen) +{ + struct demod_state_hapn48 *st = (struct demod_state_hapn48 *)(&sm->d); + static const int pll_corr[2] = { -0x800, 0x800 }; + int curst, cursync; + int inv; + + for (; buflen > 0; buflen--, buf++) { + inv = ((int)(buf[-2])-0x80) << 8; + st->lvlhi = (st->lvlhi * 65309) >> 16; /* decay */ + st->lvllo = (st->lvllo * 65309) >> 16; /* decay */ + if (inv > st->lvlhi) + st->lvlhi = inv; + if (inv < st->lvllo) + st->lvllo = inv; + if (buflen & 1) + st->dcd_shreg <<= 1; + st->bit_pll += 0x2000; + curst = cursync = 0; + if (inv > st->lvlhi >> 1) { + curst = 1; + cursync = (buf[-2] > buf[-1] && buf[-2] > buf[-3] && + buf[-2] > buf[-0] && buf[-2] > buf[-4]); + } else if (inv < st->lvllo >> 1) { + curst = -1; + cursync = (buf[-2] < buf[-1] && buf[-2] < buf[-3] && + buf[-2] < buf[-0] && buf[-2] < buf[-4]); + } + if (cursync) { + st->dcd_shreg |= cursync; + st->bit_pll += pll_corr[((st->bit_pll - 0x8000u) & 0xffffu) < 0x9000u]; + st->dcd_sum0 += 16 * hweight32(st->dcd_shreg & 0x44444444) - + hweight32(st->dcd_shreg & 0xbbbbbbbb); + } + hdlcdrv_channelbit(&sm->hdrv, cursync); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 240; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->last_bit2 = st->last_bit; + if (curst < 0) + st->last_bit = 0; + else if (curst > 0) + st->last_bit = 1; + st->shreg >>= 1; + st->shreg |= ((st->last_bit ^ st->last_bit2 ^ 1) & 1) << 16; + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + diag_trigger(sm); + } + diag_add_one(sm, inv); + } +} + +/* --------------------------------------------------------------------- */ + +static void demodulator_hapn4800_8_s16(struct sm_state *sm, const short *buf, unsigned int buflen) +{ + struct demod_state_hapn48 *st = (struct demod_state_hapn48 *)(&sm->d); + static const int pll_corr[2] = { -0x800, 0x800 }; + int curst, cursync; + int inv; + + for (; buflen > 0; buflen--, buf++) { + inv = buf[-2]; + st->lvlhi = (st->lvlhi * 65309) >> 16; /* decay */ + st->lvllo = (st->lvllo * 65309) >> 16; /* decay */ + if (inv > st->lvlhi) + st->lvlhi = inv; + if (inv < st->lvllo) + st->lvllo = inv; + if (buflen & 1) + st->dcd_shreg <<= 1; + st->bit_pll += 0x2000; + curst = cursync = 0; + if (inv > st->lvlhi >> 1) { + curst = 1; + cursync = (buf[-2] > buf[-1] && buf[-2] > buf[-3] && + buf[-2] > buf[-0] && buf[-2] > buf[-4]); + } else if (inv < st->lvllo >> 1) { + curst = -1; + cursync = (buf[-2] < buf[-1] && buf[-2] < buf[-3] && + buf[-2] < buf[-0] && buf[-2] < buf[-4]); + } + if (cursync) { + st->dcd_shreg |= cursync; + st->bit_pll += pll_corr[((st->bit_pll - 0x8000u) & 0xffffu) < 0x9000u]; + st->dcd_sum0 += 16 * hweight32(st->dcd_shreg & 0x44444444) - + hweight32(st->dcd_shreg & 0xbbbbbbbb); + } + hdlcdrv_channelbit(&sm->hdrv, cursync); + if ((--st->dcd_time) <= 0) { + hdlcdrv_setdcd(&sm->hdrv, (st->dcd_sum0 + + st->dcd_sum1 + + st->dcd_sum2) < 0); + st->dcd_sum2 = st->dcd_sum1; + st->dcd_sum1 = st->dcd_sum0; + st->dcd_sum0 = 2; /* slight bias */ + st->dcd_time = 240; + } + if (st->bit_pll >= 0x10000) { + st->bit_pll &= 0xffff; + st->last_bit2 = st->last_bit; + if (curst < 0) + st->last_bit = 0; + else if (curst > 0) + st->last_bit = 1; + st->shreg >>= 1; + st->shreg |= ((st->last_bit ^ st->last_bit2 ^ 1) & 1) << 16; + if (st->shreg & 1) { + hdlcdrv_putbits(&sm->hdrv, st->shreg >> 1); + st->shreg = 0x10000; + } + diag_trigger(sm); + } + diag_add_one(sm, inv); + } +} + +/* --------------------------------------------------------------------- */ + +static void demod_init_hapn4800(struct sm_state *sm) +{ + struct demod_state_hapn48 *st = (struct demod_state_hapn48 *)(&sm->d); + + st->dcd_time = 120; + st->dcd_sum0 = 2; +} + +/* --------------------------------------------------------------------- */ + +const struct modem_tx_info sm_hapn4800_8_tx = { + "hapn4800", sizeof(struct mod_state_hapn48), 38400, 4800, + modulator_hapn4800_8_u8, modulator_hapn4800_8_s16, NULL +}; + +const struct modem_rx_info sm_hapn4800_8_rx = { + "hapn4800", sizeof(struct demod_state_hapn48), 38400, 4800, 5, 8, + demodulator_hapn4800_8_u8, demodulator_hapn4800_8_s16, demod_init_hapn4800 +}; + +/* --------------------------------------------------------------------- */ + +const struct modem_tx_info sm_hapn4800_10_tx = { + "hapn4800", sizeof(struct mod_state_hapn48), 48000, 4800, + modulator_hapn4800_10_u8, modulator_hapn4800_10_s16, NULL +}; + +const struct modem_rx_info sm_hapn4800_10_rx = { + "hapn4800", sizeof(struct demod_state_hapn48), 48000, 4800, 5, 10, + demodulator_hapn4800_10_u8, demodulator_hapn4800_10_s16, demod_init_hapn4800 +}; + +/* --------------------------------------------------------------------- */ + +const struct modem_tx_info sm_hapn4800_pm8_tx = { + "hapn4800pm", sizeof(struct mod_state_hapn48), 38400, 4800, + modulator_hapn4800_pm8_u8, modulator_hapn4800_pm8_s16, NULL +}; + +const struct modem_rx_info sm_hapn4800_pm8_rx = { + "hapn4800pm", sizeof(struct demod_state_hapn48), 38400, 4800, 5, 8, + demodulator_hapn4800_8_u8, demodulator_hapn4800_8_s16, demod_init_hapn4800 +}; + +/* --------------------------------------------------------------------- */ + +const struct modem_tx_info sm_hapn4800_pm10_tx = { + "hapn4800pm", sizeof(struct mod_state_hapn48), 48000, 4800, + modulator_hapn4800_pm10_u8, modulator_hapn4800_pm10_s16, NULL +}; + +const struct modem_rx_info sm_hapn4800_pm10_rx = { + "hapn4800pm", sizeof(struct demod_state_hapn48), 48000, 4800, 5, 10, + demodulator_hapn4800_10_u8, demodulator_hapn4800_10_s16, demod_init_hapn4800 +}; + +/* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/soundmodem/sm_sbc.c b/drivers/net/hamradio/soundmodem/sm_sbc.c new file mode 100644 index 000000000..e66a2e5cb --- /dev/null +++ b/drivers/net/hamradio/soundmodem/sm_sbc.c @@ -0,0 +1,941 @@ +/*****************************************************************************/ + +/* + * sm_sbc.c -- soundcard radio modem driver soundblaster hardware driver + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + */ + +#include <linux/ptrace.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/ioport.h> +#include <linux/soundmodem.h> +#include <linux/delay.h> +#include "sm.h" +#include "smdma.h" + +/* --------------------------------------------------------------------- */ + +/* + * currently this module is supposed to support both module styles, i.e. + * the old one present up to about 2.1.9, and the new one functioning + * starting with 2.1.21. The reason is I have a kit allowing to compile + * this module also under 2.0.x which was requested by several people. + * This will go in 2.2 + */ +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= 0x20100 +#include <asm/uaccess.h> +#else +#include <asm/segment.h> +#include <linux/mm.h> + +#undef put_user +#undef get_user + +#define put_user(x,ptr) ({ __put_user((unsigned long)(x),(ptr),sizeof(*(ptr))); 0; }) +#define get_user(x,ptr) ({ x = ((__typeof__(*(ptr)))__get_user((ptr),sizeof(*(ptr)))); 0; }) + +extern inline int copy_from_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_READ, from, n); + if (i) + return i; + memcpy_fromfs(to, from, n); + return 0; +} + +extern inline int copy_to_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_WRITE, to, n); + if (i) + return i; + memcpy_tofs(to, from, n); + return 0; +} +#endif + +/* --------------------------------------------------------------------- */ + +struct sc_state_sbc { + unsigned char revhi, revlo; + unsigned char fmt[2]; + unsigned int sr[2]; +}; + +#define SCSTATE ((struct sc_state_sbc *)(&sm->hw)) + +/* --------------------------------------------------------------------- */ +/* + * the sbc converter's registers + */ +#define DSP_RESET(iobase) (iobase+0x6) +#define DSP_READ_DATA(iobase) (iobase+0xa) +#define DSP_WRITE_DATA(iobase) (iobase+0xc) +#define DSP_WRITE_STATUS(iobase) (iobase+0xc) +#define DSP_DATA_AVAIL(iobase) (iobase+0xe) +#define DSP_MIXER_ADDR(iobase) (iobase+0x4) +#define DSP_MIXER_DATA(iobase) (iobase+0x5) +#define DSP_INTACK_16BIT(iobase) (iobase+0xf) +#define SBC_EXTENT 16 + +/* --------------------------------------------------------------------- */ +/* + * SBC commands + */ +#define SBC_OUTPUT 0x14 +#define SBC_INPUT 0x24 +#define SBC_BLOCKSIZE 0x48 +#define SBC_HI_OUTPUT 0x91 +#define SBC_HI_INPUT 0x99 +#define SBC_LO_OUTPUT_AUTOINIT 0x1c +#define SBC_LO_INPUT_AUTOINIT 0x2c +#define SBC_HI_OUTPUT_AUTOINIT 0x90 +#define SBC_HI_INPUT_AUTOINIT 0x98 +#define SBC_IMMED_INT 0xf2 +#define SBC_GET_REVISION 0xe1 +#define ESS_GET_REVISION 0xe7 +#define SBC_SPEAKER_ON 0xd1 +#define SBC_SPEAKER_OFF 0xd3 +#define SBC_DMA_ON 0xd0 +#define SBC_DMA_OFF 0xd4 +#define SBC_SAMPLE_RATE 0x40 +#define SBC_SAMPLE_RATE_OUT 0x41 +#define SBC_SAMPLE_RATE_IN 0x42 +#define SBC_MONO_8BIT 0xa0 +#define SBC_MONO_16BIT 0xa4 +#define SBC_STEREO_8BIT 0xa8 +#define SBC_STEREO_16BIT 0xac + +#define SBC4_OUT8_AI 0xc6 +#define SBC4_IN8_AI 0xce +#define SBC4_MODE_UNS_MONO 0x00 +#define SBC4_MODE_SIGN_MONO 0x10 + +#define SBC4_OUT16_AI 0xb6 +#define SBC4_IN16_AI 0xbe + +/* --------------------------------------------------------------------- */ + +static int inline reset_dsp(struct device *dev) +{ + int i; + + outb(1, DSP_RESET(dev->base_addr)); + udelay(300); + outb(0, DSP_RESET(dev->base_addr)); + for (i = 0; i < 0xffff; i++) + if (inb(DSP_DATA_AVAIL(dev->base_addr)) & 0x80) + if (inb(DSP_READ_DATA(dev->base_addr)) == 0xaa) + return 1; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static void inline write_dsp(struct device *dev, unsigned char data) +{ + int i; + + for (i = 0; i < 0xffff; i++) + if (!(inb(DSP_WRITE_STATUS(dev->base_addr)) & 0x80)) { + outb(data, DSP_WRITE_DATA(dev->base_addr)); + return; + } +} + +/* --------------------------------------------------------------------- */ + +static int inline read_dsp(struct device *dev, unsigned char *data) +{ + int i; + + if (!data) + return 0; + for (i = 0; i < 0xffff; i++) + if (inb(DSP_DATA_AVAIL(dev->base_addr)) & 0x80) { + *data = inb(DSP_READ_DATA(dev->base_addr)); + return 1; + } + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int config_resources(struct device *dev, struct sm_state *sm, int fdx) +{ + unsigned char irqreg = 0, dmareg = 0, realirq, realdma; + unsigned long flags; + + switch (dev->irq) { + case 2: + case 9: + irqreg |= 0x01; + break; + + case 5: + irqreg |= 0x02; + break; + + case 7: + irqreg |= 0x04; + break; + + case 10: + irqreg |= 0x08; + break; + + default: + return -ENODEV; + } + + switch (dev->dma) { + case 0: + dmareg |= 0x01; + break; + + case 1: + dmareg |= 0x02; + break; + + case 3: + dmareg |= 0x08; + break; + + default: + return -ENODEV; + } + + if (fdx) { + switch (sm->hdrv.ptt_out.dma2) { + case 5: + dmareg |= 0x20; + break; + + case 6: + dmareg |= 0x40; + break; + + case 7: + dmareg |= 0x80; + break; + + default: + return -ENODEV; + } + } + save_flags(flags); + cli(); + outb(0x80, DSP_MIXER_ADDR(dev->base_addr)); + outb(irqreg, DSP_MIXER_DATA(dev->base_addr)); + realirq = inb(DSP_MIXER_DATA(dev->base_addr)); + outb(0x81, DSP_MIXER_ADDR(dev->base_addr)); + outb(dmareg, DSP_MIXER_DATA(dev->base_addr)); + realdma = inb(DSP_MIXER_DATA(dev->base_addr)); + restore_flags(flags); + if ((~realirq) & irqreg || (~realdma) & dmareg) { + printk(KERN_ERR "%s: sbc resource registers cannot be set; PnP device " + "and IRQ/DMA specified wrongly?\n", sm_drvname); + return -EINVAL; + } + return 0; +} + +/* --------------------------------------------------------------------- */ + +static void inline sbc_int_ack_8bit(struct device *dev) +{ + inb(DSP_DATA_AVAIL(dev->base_addr)); +} + +/* --------------------------------------------------------------------- */ + +static void inline sbc_int_ack_16bit(struct device *dev) +{ + inb(DSP_INTACK_16BIT(dev->base_addr)); +} + +/* --------------------------------------------------------------------- */ + +static void setup_dma_dsp(struct device *dev, struct sm_state *sm, int send) +{ + unsigned long flags; + static const unsigned char sbcmode[2][2] = { + { SBC_LO_INPUT_AUTOINIT, SBC_LO_OUTPUT_AUTOINIT }, + { SBC_HI_INPUT_AUTOINIT, SBC_HI_OUTPUT_AUTOINIT } + }; + static const unsigned char sbc4mode[2] = { SBC4_IN8_AI, SBC4_OUT8_AI }; + static const unsigned char sbcskr[2] = { SBC_SPEAKER_OFF, SBC_SPEAKER_ON }; + unsigned int nsamps; + + send = !!send; + if (!reset_dsp(dev)) { + printk(KERN_ERR "%s: sbc: cannot reset sb dsp\n", sm_drvname); + return; + } + save_flags(flags); + cli(); + sbc_int_ack_8bit(dev); + write_dsp(dev, SBC_SAMPLE_RATE); /* set sampling rate */ + write_dsp(dev, SCSTATE->fmt[send]); + write_dsp(dev, sbcskr[send]); + nsamps = dma_setup(sm, send, dev->dma) - 1; + sbc_int_ack_8bit(dev); + if (SCSTATE->revhi >= 4) { + write_dsp(dev, sbc4mode[send]); + write_dsp(dev, SBC4_MODE_UNS_MONO); + write_dsp(dev, nsamps & 0xff); + write_dsp(dev, nsamps >> 8); + } else { + write_dsp(dev, SBC_BLOCKSIZE); + write_dsp(dev, nsamps & 0xff); + write_dsp(dev, nsamps >> 8); + write_dsp(dev, sbcmode[SCSTATE->fmt[send] >= 180][send]); + /* hispeed mode if sample rate > 13kHz */ + } + restore_flags(flags); +} + +/* --------------------------------------------------------------------- */ + +static void sbc_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_id; + struct sm_state *sm = (struct sm_state *)dev->priv; + unsigned int curfrag; + + if (!dev || !sm || sm->hdrv.magic != HDLCDRV_MAGIC) + return; + cli(); + sbc_int_ack_8bit(dev); + disable_dma(dev->dma); + clear_dma_ff(dev->dma); + dma_ptr(sm, sm->dma.ptt_cnt > 0, dev->dma, &curfrag); + enable_dma(dev->dma); + sm_int_freq(sm); + sti(); + if (sm->dma.ptt_cnt <= 0) { + dma_receive(sm, curfrag); + hdlcdrv_arbitrate(dev, &sm->hdrv); + if (hdlcdrv_ptt(&sm->hdrv)) { + /* starting to transmit */ + disable_dma(dev->dma); + hdlcdrv_transmitter(dev, &sm->hdrv); /* prefill HDLC buffer */ + dma_start_transmit(sm); + setup_dma_dsp(dev, sm, 1); + dma_transmit(sm); + } + } else if (dma_end_transmit(sm, curfrag)) { + /* stopping transmission */ + disable_dma(dev->dma); + sti(); + dma_init_receive(sm); + setup_dma_dsp(dev, sm, 0); + } else + dma_transmit(sm); + sm_output_status(sm); + hdlcdrv_transmitter(dev, &sm->hdrv); + hdlcdrv_receiver(dev, &sm->hdrv); + +} + +/* --------------------------------------------------------------------- */ + +static int sbc_open(struct device *dev, struct sm_state *sm) +{ + int err; + unsigned int dmasz, u; + + if (sizeof(sm->m) < sizeof(struct sc_state_sbc)) { + printk(KERN_ERR "sm sbc: sbc state too big: %d > %d\n", + sizeof(struct sc_state_sbc), sizeof(sm->m)); + return -ENODEV; + } + if (!dev || !sm) + return -ENXIO; + if (dev->base_addr <= 0 || dev->base_addr > 0x1000-SBC_EXTENT || + dev->irq < 2 || dev->irq > 15 || dev->dma > 3) + return -ENXIO; + if (check_region(dev->base_addr, SBC_EXTENT)) + return -EACCES; + /* + * check if a card is available + */ + if (!reset_dsp(dev)) { + printk(KERN_ERR "%s: sbc: no card at io address 0x%lx\n", + sm_drvname, dev->base_addr); + return -ENODEV; + } + write_dsp(dev, SBC_GET_REVISION); + if (!read_dsp(dev, &SCSTATE->revhi) || + !read_dsp(dev, &SCSTATE->revlo)) + return -ENODEV; + printk(KERN_INFO "%s: SoundBlaster DSP revision %d.%d\n", sm_drvname, + SCSTATE->revhi, SCSTATE->revlo); + if (SCSTATE->revhi < 2) { + printk(KERN_ERR "%s: your card is an antiquity, at least DSP " + "rev 2.00 required\n", sm_drvname); + return -ENODEV; + } + if (SCSTATE->revhi < 3 && + (SCSTATE->fmt[0] >= 180 || SCSTATE->fmt[1] >= 180)) { + printk(KERN_ERR "%s: sbc io 0x%lx: DSP rev %d.%02d too " + "old, at least 3.00 required\n", sm_drvname, + dev->base_addr, SCSTATE->revhi, SCSTATE->revlo); + return -ENODEV; + } + if (SCSTATE->revhi >= 4 && + (err = config_resources(dev, sm, 0))) { + printk(KERN_ERR "%s: invalid IRQ and/or DMA specified\n", sm_drvname); + return err; + } + /* + * initialize some variables + */ + dma_init_receive(sm); + dmasz = (NUM_FRAGMENTS + 1) * sm->dma.ifragsz; + u = NUM_FRAGMENTS * sm->dma.ofragsz; + if (u > dmasz) + dmasz = u; + if (!(sm->dma.ibuf = sm->dma.obuf = kmalloc(dmasz, GFP_KERNEL | GFP_DMA))) + return -ENOMEM; + dma_init_transmit(sm); + dma_init_receive(sm); + + memset(&sm->m, 0, sizeof(sm->m)); + memset(&sm->d, 0, sizeof(sm->d)); + if (sm->mode_tx->init) + sm->mode_tx->init(sm); + if (sm->mode_rx->init) + sm->mode_rx->init(sm); + + if (request_dma(dev->dma, sm->hwdrv->hw_name)) { + kfree_s(sm->dma.obuf, dmasz); + return -EBUSY; + } + if (request_irq(dev->irq, sbc_interrupt, SA_INTERRUPT, + sm->hwdrv->hw_name, dev)) { + free_dma(dev->dma); + kfree_s(sm->dma.obuf, dmasz); + return -EBUSY; + } + request_region(dev->base_addr, SBC_EXTENT, sm->hwdrv->hw_name); + setup_dma_dsp(dev, sm, 0); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int sbc_close(struct device *dev, struct sm_state *sm) +{ + if (!dev || !sm) + return -EINVAL; + /* + * disable interrupts + */ + disable_dma(dev->dma); + reset_dsp(dev); + free_irq(dev->irq, dev); + free_dma(dev->dma); + release_region(dev->base_addr, SBC_EXTENT); + kfree(sm->dma.obuf); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int sbc_sethw(struct device *dev, struct sm_state *sm, char *mode) +{ + char *cp = strchr(mode, '.'); + const struct modem_tx_info **mtp = sm_modem_tx_table; + const struct modem_rx_info **mrp; + + if (!strcmp(mode, "off")) { + sm->mode_tx = NULL; + sm->mode_rx = NULL; + return 0; + } + if (cp) + *cp++ = '\0'; + else + cp = mode; + for (; *mtp; mtp++) { + if ((*mtp)->loc_storage > sizeof(sm->m)) { + printk(KERN_ERR "%s: insufficient storage for modulator %s (%d)\n", + sm_drvname, (*mtp)->name, (*mtp)->loc_storage); + continue; + } + if (!(*mtp)->name || strcmp((*mtp)->name, mode)) + continue; + if ((*mtp)->srate < 5000 || (*mtp)->srate > 44100) + continue; + if (!(*mtp)->modulator_u8) + continue; + for (mrp = sm_modem_rx_table; *mrp; mrp++) { + if ((*mrp)->loc_storage > sizeof(sm->d)) { + printk(KERN_ERR "%s: insufficient storage for demodulator %s (%d)\n", + sm_drvname, (*mrp)->name, (*mrp)->loc_storage); + continue; + } + if (!(*mrp)->demodulator_u8) + continue; + if ((*mrp)->name && !strcmp((*mrp)->name, cp) && + (*mrp)->srate >= 5000 && (*mrp)->srate <= 44100) { + sm->mode_tx = *mtp; + sm->mode_rx = *mrp; + SCSTATE->fmt[0] = 256-((1000000L+sm->mode_rx->srate/2)/ + sm->mode_rx->srate); + SCSTATE->fmt[1] = 256-((1000000L+sm->mode_tx->srate/2)/ + sm->mode_tx->srate); + sm->dma.ifragsz = (sm->mode_rx->srate + 50)/100; + sm->dma.ofragsz = (sm->mode_tx->srate + 50)/100; + if (sm->dma.ifragsz < sm->mode_rx->overlap) + sm->dma.ifragsz = sm->mode_rx->overlap; + sm->dma.i16bit = sm->dma.o16bit = 0; + return 0; + } + } + } + return -EINVAL; +} + +/* --------------------------------------------------------------------- */ + +static int sbc_ioctl(struct device *dev, struct sm_state *sm, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd) +{ + struct sm_ioctl bi; + unsigned long flags; + int i; + + if (cmd != SIOCDEVPRIVATE) + return -ENOIOCTLCMD; + + if (hi->cmd == HDLCDRVCTL_MODEMPARMASK) + return HDLCDRV_PARMASK_IOBASE | HDLCDRV_PARMASK_IRQ | + HDLCDRV_PARMASK_DMA | HDLCDRV_PARMASK_SERIOBASE | + HDLCDRV_PARMASK_PARIOBASE | HDLCDRV_PARMASK_MIDIIOBASE; + + if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi))) + return -EFAULT; + + switch (bi.cmd) { + default: + return -ENOIOCTLCMD; + + case SMCTL_GETMIXER: + i = 0; + bi.data.mix.sample_rate = sm->mode_rx->srate; + bi.data.mix.bit_rate = sm->hdrv.par.bitrate; + bi.data.mix.mixer_type = SM_MIXER_INVALID; + switch (SCSTATE->revhi) { + case 2: + bi.data.mix.mixer_type = SM_MIXER_CT1335; + break; + case 3: + bi.data.mix.mixer_type = SM_MIXER_CT1345; + break; + case 4: + bi.data.mix.mixer_type = SM_MIXER_CT1745; + break; + } + if (bi.data.mix.mixer_type != SM_MIXER_INVALID && + bi.data.mix.reg < 0x80) { + save_flags(flags); + cli(); + outb(bi.data.mix.reg, DSP_MIXER_ADDR(dev->base_addr)); + bi.data.mix.data = inb(DSP_MIXER_DATA(dev->base_addr)); + restore_flags(flags); + i = 1; + } + if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi))) + return -EFAULT; + return i; + + case SMCTL_SETMIXER: + if (!suser()) + return -EACCES; + switch (SCSTATE->revhi) { + case 2: + if (bi.data.mix.mixer_type != SM_MIXER_CT1335) + return -EINVAL; + break; + case 3: + if (bi.data.mix.mixer_type != SM_MIXER_CT1345) + return -EINVAL; + break; + case 4: + if (bi.data.mix.mixer_type != SM_MIXER_CT1745) + return -EINVAL; + break; + default: + return -ENODEV; + } + if (bi.data.mix.reg >= 0x80) + return -EACCES; + save_flags(flags); + cli(); + outb(bi.data.mix.reg, DSP_MIXER_ADDR(dev->base_addr)); + outb(bi.data.mix.data, DSP_MIXER_DATA(dev->base_addr)); + restore_flags(flags); + return 0; + + } + if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi))) + return -EFAULT; + return 0; + +} + +/* --------------------------------------------------------------------- */ + +const struct hardware_info sm_hw_sbc = { + "sbc", sizeof(struct sc_state_sbc), + sbc_open, sbc_close, sbc_ioctl, sbc_sethw +}; + +/* --------------------------------------------------------------------- */ + +static void setup_dma_fdx_dsp(struct device *dev, struct sm_state *sm) +{ + unsigned long flags; + unsigned int isamps, osamps; + + if (!reset_dsp(dev)) { + printk(KERN_ERR "%s: sbc: cannot reset sb dsp\n", sm_drvname); + return; + } + save_flags(flags); + cli(); + sbc_int_ack_8bit(dev); + sbc_int_ack_16bit(dev); + /* should eventually change to set rates individually by SBC_SAMPLE_RATE_{IN/OUT} */ + write_dsp(dev, SBC_SAMPLE_RATE_IN); + write_dsp(dev, SCSTATE->sr[0] >> 8); + write_dsp(dev, SCSTATE->sr[0] & 0xff); + write_dsp(dev, SBC_SAMPLE_RATE_OUT); + write_dsp(dev, SCSTATE->sr[1] >> 8); + write_dsp(dev, SCSTATE->sr[1] & 0xff); + write_dsp(dev, SBC_SPEAKER_ON); + if (sm->dma.o16bit) { + /* + * DMA channel 1 (8bit) does input (capture), + * DMA channel 2 (16bit) does output (playback) + */ + isamps = dma_setup(sm, 0, dev->dma) - 1; + osamps = dma_setup(sm, 1, sm->hdrv.ptt_out.dma2) - 1; + sbc_int_ack_8bit(dev); + sbc_int_ack_16bit(dev); + write_dsp(dev, SBC4_IN8_AI); + write_dsp(dev, SBC4_MODE_UNS_MONO); + write_dsp(dev, isamps & 0xff); + write_dsp(dev, isamps >> 8); + write_dsp(dev, SBC4_OUT16_AI); + write_dsp(dev, SBC4_MODE_SIGN_MONO); + write_dsp(dev, osamps & 0xff); + write_dsp(dev, osamps >> 8); + } else { + /* + * DMA channel 1 (8bit) does output (playback), + * DMA channel 2 (16bit) does input (capture) + */ + isamps = dma_setup(sm, 0, sm->hdrv.ptt_out.dma2) - 1; + osamps = dma_setup(sm, 1, dev->dma) - 1; + sbc_int_ack_8bit(dev); + sbc_int_ack_16bit(dev); + write_dsp(dev, SBC4_OUT8_AI); + write_dsp(dev, SBC4_MODE_UNS_MONO); + write_dsp(dev, osamps & 0xff); + write_dsp(dev, osamps >> 8); + write_dsp(dev, SBC4_IN16_AI); + write_dsp(dev, SBC4_MODE_SIGN_MONO); + write_dsp(dev, isamps & 0xff); + write_dsp(dev, isamps >> 8); + } + dma_init_receive(sm); + dma_init_transmit(sm); + restore_flags(flags); +} + +/* --------------------------------------------------------------------- */ + +static void sbcfdx_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_id; + struct sm_state *sm = (struct sm_state *)dev->priv; + unsigned char intsrc, pbint = 0, captint = 0; + unsigned int ocfrag, icfrag; + unsigned long flags; + + if (!dev || !sm || sm->hdrv.magic != HDLCDRV_MAGIC) + return; + save_flags(flags); + cli(); + outb(0x82, DSP_MIXER_ADDR(dev->base_addr)); + intsrc = inb(DSP_MIXER_DATA(dev->base_addr)); + if (intsrc & 0x01) { + sbc_int_ack_8bit(dev); + if (sm->dma.o16bit) { + captint = 1; + disable_dma(dev->dma); + clear_dma_ff(dev->dma); + dma_ptr(sm, 0, dev->dma, &icfrag); + enable_dma(dev->dma); + } else { + pbint = 1; + disable_dma(dev->dma); + clear_dma_ff(dev->dma); + dma_ptr(sm, 1, dev->dma, &ocfrag); + enable_dma(dev->dma); + } + } + if (intsrc & 0x02) { + sbc_int_ack_16bit(dev); + if (sm->dma.o16bit) { + pbint = 1; + disable_dma(sm->hdrv.ptt_out.dma2); + clear_dma_ff(sm->hdrv.ptt_out.dma2); + dma_ptr(sm, 1, sm->hdrv.ptt_out.dma2, &ocfrag); + enable_dma(sm->hdrv.ptt_out.dma2); + } else { + captint = 1; + disable_dma(sm->hdrv.ptt_out.dma2); + clear_dma_ff(sm->hdrv.ptt_out.dma2); + dma_ptr(sm, 0, sm->hdrv.ptt_out.dma2, &icfrag); + enable_dma(sm->hdrv.ptt_out.dma2); + } + } + restore_flags(flags); + sm_int_freq(sm); + sti(); + if (pbint) { + if (dma_end_transmit(sm, ocfrag)) + dma_clear_transmit(sm); + dma_transmit(sm); + } + if (captint) { + dma_receive(sm, icfrag); + hdlcdrv_arbitrate(dev, &sm->hdrv); + } + sm_output_status(sm); + hdlcdrv_transmitter(dev, &sm->hdrv); + hdlcdrv_receiver(dev, &sm->hdrv); +} + +/* --------------------------------------------------------------------- */ + +static int sbcfdx_open(struct device *dev, struct sm_state *sm) +{ + int err; + + if (sizeof(sm->m) < sizeof(struct sc_state_sbc)) { + printk(KERN_ERR "sm sbc: sbc state too big: %d > %d\n", + sizeof(struct sc_state_sbc), sizeof(sm->m)); + return -ENODEV; + } + if (!dev || !sm) + return -ENXIO; + if (dev->base_addr <= 0 || dev->base_addr > 0x1000-SBC_EXTENT || + dev->irq < 2 || dev->irq > 15 || dev->dma > 3) + return -ENXIO; + if (check_region(dev->base_addr, SBC_EXTENT)) + return -EACCES; + /* + * check if a card is available + */ + if (!reset_dsp(dev)) { + printk(KERN_ERR "%s: sbc: no card at io address 0x%lx\n", + sm_drvname, dev->base_addr); + return -ENODEV; + } + write_dsp(dev, SBC_GET_REVISION); + if (!read_dsp(dev, &SCSTATE->revhi) || + !read_dsp(dev, &SCSTATE->revlo)) + return -ENODEV; + printk(KERN_INFO "%s: SoundBlaster DSP revision %d.%d\n", sm_drvname, + SCSTATE->revhi, SCSTATE->revlo); + if (SCSTATE->revhi < 4) { + printk(KERN_ERR "%s: at least DSP rev 4.00 required\n", sm_drvname); + return -ENODEV; + } + if ((err = config_resources(dev, sm, 1))) { + printk(KERN_ERR "%s: invalid IRQ and/or DMA specified\n", sm_drvname); + return err; + } + /* + * initialize some variables + */ + if (!(sm->dma.ibuf = kmalloc(sm->dma.ifragsz * (NUM_FRAGMENTS+1), GFP_KERNEL | GFP_DMA))) + return -ENOMEM; + if (!(sm->dma.obuf = kmalloc(sm->dma.ofragsz * NUM_FRAGMENTS, GFP_KERNEL | GFP_DMA))) { + kfree(sm->dma.ibuf); + return -ENOMEM; + } + dma_init_transmit(sm); + dma_init_receive(sm); + + memset(&sm->m, 0, sizeof(sm->m)); + memset(&sm->d, 0, sizeof(sm->d)); + if (sm->mode_tx->init) + sm->mode_tx->init(sm); + if (sm->mode_rx->init) + sm->mode_rx->init(sm); + + if (request_dma(dev->dma, sm->hwdrv->hw_name)) { + kfree(sm->dma.ibuf); + kfree(sm->dma.obuf); + return -EBUSY; + } + if (request_dma(sm->hdrv.ptt_out.dma2, sm->hwdrv->hw_name)) { + kfree(sm->dma.ibuf); + kfree(sm->dma.obuf); + free_dma(dev->dma); + return -EBUSY; + } + if (request_irq(dev->irq, sbcfdx_interrupt, SA_INTERRUPT, + sm->hwdrv->hw_name, dev)) { + kfree(sm->dma.ibuf); + kfree(sm->dma.obuf); + free_dma(dev->dma); + free_dma(sm->hdrv.ptt_out.dma2); + return -EBUSY; + } + request_region(dev->base_addr, SBC_EXTENT, sm->hwdrv->hw_name); + setup_dma_fdx_dsp(dev, sm); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int sbcfdx_close(struct device *dev, struct sm_state *sm) +{ + if (!dev || !sm) + return -EINVAL; + /* + * disable interrupts + */ + disable_dma(dev->dma); + disable_dma(sm->hdrv.ptt_out.dma2); + reset_dsp(dev); + free_irq(dev->irq, dev); + free_dma(dev->dma); + free_dma(sm->hdrv.ptt_out.dma2); + release_region(dev->base_addr, SBC_EXTENT); + kfree(sm->dma.ibuf); + kfree(sm->dma.obuf); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int sbcfdx_sethw(struct device *dev, struct sm_state *sm, char *mode) +{ + char *cp = strchr(mode, '.'); + const struct modem_tx_info **mtp = sm_modem_tx_table; + const struct modem_rx_info **mrp; + + if (!strcmp(mode, "off")) { + sm->mode_tx = NULL; + sm->mode_rx = NULL; + return 0; + } + if (cp) + *cp++ = '\0'; + else + cp = mode; + for (; *mtp; mtp++) { + if ((*mtp)->loc_storage > sizeof(sm->m)) { + printk(KERN_ERR "%s: insufficient storage for modulator %s (%d)\n", + sm_drvname, (*mtp)->name, (*mtp)->loc_storage); + continue; + } + if (!(*mtp)->name || strcmp((*mtp)->name, mode)) + continue; + if ((*mtp)->srate < 5000 || (*mtp)->srate > 44100) + continue; + for (mrp = sm_modem_rx_table; *mrp; mrp++) { + if ((*mrp)->loc_storage > sizeof(sm->d)) { + printk(KERN_ERR "%s: insufficient storage for demodulator %s (%d)\n", + sm_drvname, (*mrp)->name, (*mrp)->loc_storage); + continue; + } + if ((*mrp)->name && !strcmp((*mrp)->name, cp) && + (*mtp)->srate >= 5000 && (*mtp)->srate <= 44100 && + (*mrp)->srate == (*mtp)->srate) { + sm->mode_tx = *mtp; + sm->mode_rx = *mrp; + SCSTATE->sr[0] = sm->mode_rx->srate; + SCSTATE->sr[1] = sm->mode_tx->srate; + sm->dma.ifragsz = (sm->mode_rx->srate + 50)/100; + sm->dma.ofragsz = (sm->mode_tx->srate + 50)/100; + if (sm->dma.ifragsz < sm->mode_rx->overlap) + sm->dma.ifragsz = sm->mode_rx->overlap; + if (sm->mode_rx->demodulator_s16 && sm->mode_tx->modulator_u8) { + sm->dma.i16bit = 1; + sm->dma.o16bit = 0; + sm->dma.ifragsz <<= 1; + } else if (sm->mode_rx->demodulator_u8 && sm->mode_tx->modulator_s16) { + sm->dma.i16bit = 0; + sm->dma.o16bit = 1; + sm->dma.ofragsz <<= 1; + } else { + printk(KERN_INFO "%s: mode %s or %s unusable\n", sm_drvname, + sm->mode_rx->name, sm->mode_tx->name); + sm->mode_tx = NULL; + sm->mode_rx = NULL; + return -EINVAL; + } + return 0; + } + } + } + return -EINVAL; +} + +/* --------------------------------------------------------------------- */ + +static int sbcfdx_ioctl(struct device *dev, struct sm_state *sm, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd) +{ + if (cmd != SIOCDEVPRIVATE) + return -ENOIOCTLCMD; + + if (hi->cmd == HDLCDRVCTL_MODEMPARMASK) + return HDLCDRV_PARMASK_IOBASE | HDLCDRV_PARMASK_IRQ | + HDLCDRV_PARMASK_DMA | HDLCDRV_PARMASK_DMA2 | HDLCDRV_PARMASK_SERIOBASE | + HDLCDRV_PARMASK_PARIOBASE | HDLCDRV_PARMASK_MIDIIOBASE; + + return sbc_ioctl(dev, sm, ifr, hi, cmd); +} + +/* --------------------------------------------------------------------- */ + +const struct hardware_info sm_hw_sbcfdx = { + "sbcfdx", sizeof(struct sc_state_sbc), + sbcfdx_open, sbcfdx_close, sbcfdx_ioctl, sbcfdx_sethw +}; + +/* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/soundmodem/sm_wss.c b/drivers/net/hamradio/soundmodem/sm_wss.c new file mode 100644 index 000000000..a089544d2 --- /dev/null +++ b/drivers/net/hamradio/soundmodem/sm_wss.c @@ -0,0 +1,965 @@ +/*****************************************************************************/ + +/* + * sm_wss.c -- soundcard radio modem driver, WSS (half duplex) driver + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + */ + +#include <linux/ptrace.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/ioport.h> +#include <linux/soundmodem.h> +#include "sm.h" +#include "smdma.h" + +/* --------------------------------------------------------------------- */ + +/* + * currently this module is supposed to support both module styles, i.e. + * the old one present up to about 2.1.9, and the new one functioning + * starting with 2.1.21. The reason is I have a kit allowing to compile + * this module also under 2.0.x which was requested by several people. + * This will go in 2.2 + */ +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= 0x20100 +#include <asm/uaccess.h> +#else +#include <asm/segment.h> +#include <linux/mm.h> + +#undef put_user +#undef get_user + +#define put_user(x,ptr) ({ __put_user((unsigned long)(x),(ptr),sizeof(*(ptr))); 0; }) +#define get_user(x,ptr) ({ x = ((__typeof__(*(ptr)))__get_user((ptr),sizeof(*(ptr)))); 0; }) + +extern inline int copy_from_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_READ, from, n); + if (i) + return i; + memcpy_fromfs(to, from, n); + return 0; +} + +extern inline int copy_to_user(void *to, const void *from, unsigned long n) +{ + int i = verify_area(VERIFY_WRITE, to, n); + if (i) + return i; + memcpy_tofs(to, from, n); + return 0; +} +#endif + +/* --------------------------------------------------------------------- */ + +struct sc_state_wss { + unsigned char revwss, revid, revv, revcid; + unsigned char fmt[2]; + unsigned char crystal; +}; + +#define SCSTATE ((struct sc_state_wss *)(&sm->hw)) + +/* --------------------------------------------------------------------- */ + +#define WSS_CONFIG(iobase) (iobase+0) +#define WSS_STATUS(iobase) (iobase+3) +#define WSS_CODEC_IA(iobase) (iobase+4) +#define WSS_CODEC_ID(iobase) (iobase+5) +#define WSS_CODEC_STATUS(iobase) (iobase+6) +#define WSS_CODEC_DATA(iobase) (iobase+7) + +#define WSS_EXTENT 8 + +#define CS423X_HOTFIX + +/* --------------------------------------------------------------------- */ + +static void write_codec(struct device *dev, unsigned char idx, + unsigned char data) +{ + int timeout = 900000; + + /* wait until codec ready */ + while (timeout > 0 && inb(WSS_CODEC_IA(dev->base_addr)) & 0x80) + timeout--; + outb(idx, WSS_CODEC_IA(dev->base_addr)); + outb(data, WSS_CODEC_ID(dev->base_addr)); +} + + +/* --------------------------------------------------------------------- */ + +static unsigned char read_codec(struct device *dev, unsigned char idx) +{ + int timeout = 900000; + + /* wait until codec ready */ + while (timeout > 0 && inb(WSS_CODEC_IA(dev->base_addr)) & 0x80) + timeout--; + outb(idx & 0x1f, WSS_CODEC_IA(dev->base_addr)); + return inb(WSS_CODEC_ID(dev->base_addr)); +} + +/* --------------------------------------------------------------------- */ + +extern void inline wss_ack_int(struct device *dev) +{ + outb(0, WSS_CODEC_STATUS(dev->base_addr)); +} + +/* --------------------------------------------------------------------- */ + +static int wss_srate_tab[16] = { + 8000, 5510, 16000, 11025, 27420, 18900, 32000, 22050, + -1, 37800, -1, 44100, 48000, 33075, 9600, 6620 +}; + +static int wss_srate_index(int srate) +{ + int i; + + for (i = 0; i < (sizeof(wss_srate_tab)/sizeof(wss_srate_tab[0])); i++) + if (srate == wss_srate_tab[i] && wss_srate_tab[i] > 0) + return i; + return -1; +} + +/* --------------------------------------------------------------------- */ + +static int wss_set_codec_fmt(struct device *dev, struct sm_state *sm, unsigned char fmt, + unsigned char fmt2, char fdx, char fullcalib) +{ + unsigned long time; + unsigned long flags; + + save_flags(flags); + cli(); + /* Clock and data format register */ + write_codec(dev, 0x48, fmt); + if (SCSTATE->crystal) { + write_codec(dev, 0x5c, fmt2 & 0xf0); + /* MCE and interface config reg */ + write_codec(dev, 0x49, (fdx ? 0 : 0x4) | (fullcalib ? 0x18 : 0)); + } else + /* MCE and interface config reg */ + write_codec(dev, 0x49, fdx ? 0x8 : 0xc); + outb(0xb, WSS_CODEC_IA(dev->base_addr)); /* leave MCE */ + if (SCSTATE->crystal && !fullcalib) + return 0; + /* + * wait for ACI start + */ + time = 1000; + while (!(read_codec(dev, 0x0b) & 0x20)) + if (!(--time)) { + printk(KERN_WARNING "%s: ad1848 auto calibration timed out (1)\n", + sm_drvname); + restore_flags(flags); + return -1; + } + /* + * wait for ACI end + */ + sti(); + time = jiffies + HZ/4; + while ((read_codec(dev, 0x0b) & 0x20) && ((signed)(jiffies - time) < 0)); + restore_flags(flags); + if ((signed)(jiffies - time) >= 0) { + printk(KERN_WARNING "%s: ad1848 auto calibration timed out (2)\n", + sm_drvname); + return -1; + } + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int wss_init_codec(struct device *dev, struct sm_state *sm, char fdx, + unsigned char src_l, unsigned char src_r, + int igain_l, int igain_r, + int ogain_l, int ogain_r) +{ + unsigned char tmp, reg0, reg1, reg6, reg7; + static const signed char irqtab[16] = + { -1, -1, 0x10, -1, -1, -1, -1, 0x08, -1, 0x10, 0x18, 0x20, -1, -1, + -1, -1 }; + static const signed char dmatab[4] = { 1, 2, -1, 3 }; + + tmp = inb(WSS_STATUS(dev->base_addr)); + if ((tmp & 0x3f) != 0x04 && (tmp & 0x3f) != 0x00 && + (tmp & 0x3f) != 0x0f) { + printk(KERN_WARNING "sm: WSS card id register not found, " + "address 0x%lx, ID register 0x%02x\n", + dev->base_addr, (int)tmp); + /* return -1; */ + SCSTATE->revwss = 0; + } else { + if ((tmp & 0x80) && ((dev->dma == 0) || + ((dev->irq >= 8) && (dev->irq != 9)))) { + printk(KERN_ERR "%s: WSS: DMA0 and/or IRQ8..IRQ15 " + "(except IRQ9) cannot be used on an 8bit " + "card\n", sm_drvname); + return -1; + } + if (dev->irq > 15 || irqtab[dev->irq] == -1) { + printk(KERN_ERR "%s: WSS: invalid interrupt %d\n", + sm_drvname, (int)dev->irq); + return -1; + } + if (dev->dma > 3 || dmatab[dev->dma] == -1) { + printk(KERN_ERR "%s: WSS: invalid dma channel %d\n", + sm_drvname, (int)dev->dma); + return -1; + } + tmp = irqtab[dev->irq] | dmatab[dev->dma]; + /* irq probe */ + outb((tmp & 0x38) | 0x40, WSS_CONFIG(dev->base_addr)); + if (!(inb(WSS_STATUS(dev->base_addr)) & 0x40)) { + outb(0, WSS_CONFIG(dev->base_addr)); + printk(KERN_ERR "%s: WSS: IRQ%d is not free!\n", + sm_drvname, dev->irq); + } + outb(tmp, WSS_CONFIG(dev->base_addr)); + SCSTATE->revwss = inb(WSS_STATUS(dev->base_addr)) & 0x3f; + } + /* + * initialize the codec + */ + if (igain_l < 0) + igain_l = 0; + if (igain_r < 0) + igain_r = 0; + if (ogain_l > 0) + ogain_l = 0; + if (ogain_r > 0) + ogain_r = 0; + reg0 = (src_l << 6) & 0xc0; + reg1 = (src_r << 6) & 0xc0; + if (reg0 == 0x80 && igain_l >= 20) { + reg0 |= 0x20; + igain_l -= 20; + } + if (reg1 == 0x80 && igain_r >= 20) { + reg1 |= 0x20; + igain_r -= 20; + } + if (igain_l > 23) + igain_l = 23; + if (igain_r > 23) + igain_r = 23; + reg0 |= igain_l * 2 / 3; + reg1 |= igain_r * 2 / 3; + reg6 = (ogain_l < -95) ? 0x80 : (ogain_l * (-2) / 3); + reg7 = (ogain_r < -95) ? 0x80 : (ogain_r * (-2) / 3); + write_codec(dev, 9, 0); + write_codec(dev, 0, 0x45); + if (read_codec(dev, 0) != 0x45) + goto codec_err; + write_codec(dev, 0, 0xaa); + if (read_codec(dev, 0) != 0xaa) + goto codec_err; + write_codec(dev, 12, 0x40); /* enable MODE2 */ + write_codec(dev, 16, 0); + write_codec(dev, 0, 0x45); + SCSTATE->crystal = (read_codec(dev, 16) != 0x45); + write_codec(dev, 0, 0xaa); + SCSTATE->crystal &= (read_codec(dev, 16) != 0xaa); + if (SCSTATE->crystal) { + SCSTATE->revcid = read_codec(dev, 0x19); + SCSTATE->revv = (SCSTATE->revcid >> 5) & 7; + SCSTATE->revcid &= 7; + write_codec(dev, 0x10, 0x80); /* maximum output level */ + write_codec(dev, 0x11, 0x02); /* xtal enable and no HPF */ + write_codec(dev, 0x12, 0x80); /* left line input control */ + write_codec(dev, 0x13, 0x80); /* right line input control */ + write_codec(dev, 0x16, 0); /* disable alternative freq sel */ + write_codec(dev, 0x1a, 0xe0); /* mono IO disable */ + write_codec(dev, 0x1b, 0x00); /* left out no att */ + write_codec(dev, 0x1d, 0x00); /* right out no att */ + } + + if (wss_set_codec_fmt(dev, sm, SCSTATE->fmt[0], SCSTATE->fmt[0], fdx, 1)) + goto codec_err; + + write_codec(dev, 0, reg0); /* left input control */ + write_codec(dev, 1, reg1); /* right input control */ + write_codec(dev, 2, 0x80); /* left aux#1 input control */ + write_codec(dev, 3, 0x80); /* right aux#1 input control */ + write_codec(dev, 4, 0x80); /* left aux#2 input control */ + write_codec(dev, 5, 0x80); /* right aux#2 input control */ + write_codec(dev, 6, reg6); /* left dac control */ + write_codec(dev, 7, reg7); /* right dac control */ + write_codec(dev, 0xa, 0x2); /* pin control register */ + write_codec(dev, 0xd, 0x0); /* digital mix control */ + SCSTATE->revid = read_codec(dev, 0xc) & 0xf; + /* + * print revisions + */ + if (SCSTATE->crystal) + printk(KERN_INFO "%s: Crystal CODEC ID %d, Chip revision %d, " + " Chip ID %d\n", sm_drvname, (int)SCSTATE->revid, + (int)SCSTATE->revv, (int)SCSTATE->revcid); + else + printk(KERN_INFO "%s: WSS revision %d, CODEC revision %d\n", + sm_drvname, (int)SCSTATE->revwss, + (int)SCSTATE->revid); + return 0; + codec_err: + outb(0, WSS_CONFIG(dev->base_addr)); + printk(KERN_ERR "%s: no WSS soundcard found at address 0x%lx\n", + sm_drvname, dev->base_addr); + return -1; +} + +/* --------------------------------------------------------------------- */ + +static void setup_dma_wss(struct device *dev, struct sm_state *sm, int send) +{ + unsigned long flags; + static const unsigned char codecmode[2] = { 0x0e, 0x0d }; + unsigned char oldcodecmode; + long abrt; + unsigned char fmt; + unsigned int numsamps; + + send = !!send; + fmt = SCSTATE->fmt[send]; + save_flags(flags); + cli(); + /* + * perform the final DMA sequence to disable the codec request + */ + oldcodecmode = read_codec(dev, 9); + write_codec(dev, 9, 0xc); /* disable codec */ + wss_ack_int(dev); + if (read_codec(dev, 11) & 0x10) { + dma_setup(sm, oldcodecmode & 1, dev->dma); + abrt = 0; + while ((read_codec(dev, 11) & 0x10) || ((++abrt) >= 0x10000)); + } +#ifdef CS423X_HOTFIX + if (read_codec(dev, 0x8) != fmt || SCSTATE->crystal) + wss_set_codec_fmt(dev, sm, fmt, fmt, 0, 0); +#else /* CS423X_HOTFIX */ + if (read_codec(dev, 0x8) != fmt) + wss_set_codec_fmt(dev, sm, fmt, fmt, 0, 0); +#endif /* CS423X_HOTFIX */ + numsamps = dma_setup(sm, send, dev->dma) - 1; + write_codec(dev, 15, numsamps & 0xff); + write_codec(dev, 14, numsamps >> 8); + write_codec(dev, 9, codecmode[send]); + restore_flags(flags); +} + +/* --------------------------------------------------------------------- */ + +static void wss_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_id; + struct sm_state *sm = (struct sm_state *)dev->priv; + unsigned int curfrag; + unsigned int nums; + + if (!dev || !sm || !sm->mode_rx || !sm->mode_tx || + sm->hdrv.magic != HDLCDRV_MAGIC) + return; + cli(); + wss_ack_int(dev); + disable_dma(dev->dma); + clear_dma_ff(dev->dma); + nums = dma_ptr(sm, sm->dma.ptt_cnt > 0, dev->dma, &curfrag) - 1; + write_codec(dev, 15, nums & 0xff); + write_codec(dev, 14, nums >> 8); + enable_dma(dev->dma); + sm_int_freq(sm); + sti(); + if (sm->dma.ptt_cnt <= 0) { + dma_receive(sm, curfrag); + hdlcdrv_arbitrate(dev, &sm->hdrv); + if (hdlcdrv_ptt(&sm->hdrv)) { + /* starting to transmit */ + disable_dma(dev->dma); + hdlcdrv_transmitter(dev, &sm->hdrv); /* prefill HDLC buffer */ + dma_start_transmit(sm); + setup_dma_wss(dev, sm, 1); + dma_transmit(sm); + } + } else if (dma_end_transmit(sm, curfrag)) { + /* stopping transmission */ + disable_dma(dev->dma); + dma_init_receive(sm); + setup_dma_wss(dev, sm, 0); + } else + dma_transmit(sm); + sm_output_status(sm); + hdlcdrv_transmitter(dev, &sm->hdrv); + hdlcdrv_receiver(dev, &sm->hdrv); +} + +/* --------------------------------------------------------------------- */ + +static int wss_open(struct device *dev, struct sm_state *sm) +{ + unsigned int dmasz, u; + + if (sizeof(sm->m) < sizeof(struct sc_state_wss)) { + printk(KERN_ERR "sm wss: wss state too big: %d > %d\n", + sizeof(struct sc_state_wss), sizeof(sm->m)); + return -ENODEV; + } + if (!dev || !sm || !sm->mode_rx || !sm->mode_tx) + return -ENXIO; + if (dev->base_addr <= 0 || dev->base_addr > 0x1000-WSS_EXTENT || + dev->irq < 2 || dev->irq > 15 || dev->dma > 3) + return -ENXIO; + if (check_region(dev->base_addr, WSS_EXTENT)) + return -EACCES; + /* + * check if a card is available + */ + if (wss_init_codec(dev, sm, 0, 1, 1, 0, 0, -45, -45)) + return -ENODEV; + /* + * initialize some variables + */ + dma_init_receive(sm); + dmasz = (NUM_FRAGMENTS + 1) * sm->dma.ifragsz; + u = NUM_FRAGMENTS * sm->dma.ofragsz; + if (u > dmasz) + dmasz = u; + if (!(sm->dma.ibuf = sm->dma.obuf = kmalloc(dmasz, GFP_KERNEL | GFP_DMA))) + return -ENOMEM; + dma_init_transmit(sm); + dma_init_receive(sm); + + memset(&sm->m, 0, sizeof(sm->m)); + memset(&sm->d, 0, sizeof(sm->d)); + if (sm->mode_tx->init) + sm->mode_tx->init(sm); + if (sm->mode_rx->init) + sm->mode_rx->init(sm); + + if (request_dma(dev->dma, sm->hwdrv->hw_name)) { + kfree_s(sm->dma.obuf, dmasz); + return -EBUSY; + } + if (request_irq(dev->irq, wss_interrupt, SA_INTERRUPT, + sm->hwdrv->hw_name, dev)) { + free_dma(dev->dma); + kfree_s(sm->dma.obuf, dmasz); + return -EBUSY; + } + request_region(dev->base_addr, WSS_EXTENT, sm->hwdrv->hw_name); + setup_dma_wss(dev, sm, 0); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int wss_close(struct device *dev, struct sm_state *sm) +{ + if (!dev || !sm) + return -EINVAL; + /* + * disable interrupts + */ + disable_dma(dev->dma); + write_codec(dev, 9, 0xc); /* disable codec */ + free_irq(dev->irq, dev); + free_dma(dev->dma); + release_region(dev->base_addr, WSS_EXTENT); + kfree(sm->dma.obuf); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int wss_sethw(struct device *dev, struct sm_state *sm, char *mode) +{ + char *cp = strchr(mode, '.'); + const struct modem_tx_info **mtp = sm_modem_tx_table; + const struct modem_rx_info **mrp; + int i, j; + + if (!strcmp(mode, "off")) { + sm->mode_tx = NULL; + sm->mode_rx = NULL; + return 0; + } + if (cp) + *cp++ = '\0'; + else + cp = mode; + for (; *mtp; mtp++) { + if ((*mtp)->loc_storage > sizeof(sm->m)) { + printk(KERN_ERR "%s: insufficient storage for modulator %s (%d)\n", + sm_drvname, (*mtp)->name, (*mtp)->loc_storage); + continue; + } + if (!(*mtp)->name || strcmp((*mtp)->name, mode)) + continue; + if ((i = wss_srate_index((*mtp)->srate)) < 0) + continue; + for (mrp = sm_modem_rx_table; *mrp; mrp++) { + if ((*mrp)->loc_storage > sizeof(sm->d)) { + printk(KERN_ERR "%s: insufficient storage for demodulator %s (%d)\n", + sm_drvname, (*mrp)->name, (*mrp)->loc_storage); + continue; + } + if ((*mrp)->name && !strcmp((*mrp)->name, cp) && + ((j = wss_srate_index((*mrp)->srate)) >= 0)) { + sm->mode_tx = *mtp; + sm->mode_rx = *mrp; + SCSTATE->fmt[0] = j; + SCSTATE->fmt[1] = i; + sm->dma.ifragsz = (sm->mode_rx->srate + 50)/100; + sm->dma.ofragsz = (sm->mode_tx->srate + 50)/100; + if (sm->dma.ifragsz < sm->mode_rx->overlap) + sm->dma.ifragsz = sm->mode_rx->overlap; + /* prefer same data format if possible to minimize switching times */ + sm->dma.i16bit = sm->dma.o16bit = 2; + if (sm->mode_rx->srate == sm->mode_tx->srate) { + if (sm->mode_rx->demodulator_s16 && sm->mode_tx->modulator_s16) + sm->dma.i16bit = sm->dma.o16bit = 1; + else if (sm->mode_rx->demodulator_u8 && sm->mode_tx->modulator_u8) + sm->dma.i16bit = sm->dma.o16bit = 0; + } + if (sm->dma.i16bit == 2) { + if (sm->mode_rx->demodulator_s16) + sm->dma.i16bit = 1; + else if (sm->mode_rx->demodulator_u8) + sm->dma.i16bit = 0; + } + if (sm->dma.o16bit == 2) { + if (sm->mode_tx->modulator_s16) + sm->dma.o16bit = 1; + else if (sm->mode_tx->modulator_u8) + sm->dma.o16bit = 0; + } + if (sm->dma.i16bit == 2 || sm->dma.o16bit == 2) { + printk(KERN_INFO "%s: mode %s or %s unusable\n", sm_drvname, + sm->mode_rx->name, sm->mode_tx->name); + sm->mode_tx = NULL; + sm->mode_rx = NULL; + return -EINVAL; + } +#ifdef __BIG_ENDIAN + /* big endian 16bit only works on crystal cards... */ + if (sm->dma.i16bit) { + SCSTATE->fmt[0] |= 0xc0; + sm->dma.ifragsz <<= 1; + } + if (sm->dma.o16bit) { + SCSTATE->fmt[1] |= 0xc0; + sm->dma.ofragsz <<= 1; + } +#else /* __BIG_ENDIAN */ + if (sm->dma.i16bit) { + SCSTATE->fmt[0] |= 0x40; + sm->dma.ifragsz <<= 1; + } + if (sm->dma.o16bit) { + SCSTATE->fmt[1] |= 0x40; + sm->dma.ofragsz <<= 1; + } +#endif /* __BIG_ENDIAN */ + return 0; + } + } + } + return -EINVAL; +} + +/* --------------------------------------------------------------------- */ + +static int wss_ioctl(struct device *dev, struct sm_state *sm, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd) +{ + struct sm_ioctl bi; + int i; + + if (cmd != SIOCDEVPRIVATE) + return -ENOIOCTLCMD; + + if (hi->cmd == HDLCDRVCTL_MODEMPARMASK) + return HDLCDRV_PARMASK_IOBASE | HDLCDRV_PARMASK_IRQ | + HDLCDRV_PARMASK_DMA | HDLCDRV_PARMASK_SERIOBASE | + HDLCDRV_PARMASK_PARIOBASE | HDLCDRV_PARMASK_MIDIIOBASE; + + if (copy_from_user(&bi, ifr->ifr_data, sizeof(bi))) + return -EFAULT; + + switch (bi.cmd) { + default: + return -ENOIOCTLCMD; + + case SMCTL_GETMIXER: + i = 0; + bi.data.mix.sample_rate = sm->mode_rx->srate; + bi.data.mix.bit_rate = sm->hdrv.par.bitrate; + bi.data.mix.mixer_type = SCSTATE->crystal ? + SM_MIXER_CRYSTAL : SM_MIXER_AD1848; + if (((SCSTATE->crystal ? 0x2c0c20fflu: 0x20fflu) + >> bi.data.mix.reg) & 1) { + bi.data.mix.data = read_codec(dev, bi.data.mix.reg); + i = 1; + } + if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi))) + return -EFAULT; + return i; + + case SMCTL_SETMIXER: + if (!suser()) + return -EACCES; + if ((bi.data.mix.mixer_type != SM_MIXER_CRYSTAL || + !SCSTATE->crystal) && + (bi.data.mix.mixer_type != SM_MIXER_AD1848 || + bi.data.mix.reg >= 0x10)) + return -EINVAL; + if (!((0x2c0c20fflu >> bi.data.mix.reg) & 1)) + return -EACCES; + write_codec(dev, bi.data.mix.reg, bi.data.mix.data); + return 0; + + } + if (copy_to_user(ifr->ifr_data, &bi, sizeof(bi))) + return -EFAULT; + return 0; + +} + +/* --------------------------------------------------------------------- */ + +const struct hardware_info sm_hw_wss = { + "wss", sizeof(struct sc_state_wss), + wss_open, wss_close, wss_ioctl, wss_sethw +}; + +/* --------------------------------------------------------------------- */ + +static void setup_fdx_dma_wss(struct device *dev, struct sm_state *sm) +{ + unsigned long flags; + unsigned char oldcodecmode, codecdma; + long abrt; + unsigned int osamps, isamps; + + save_flags(flags); + cli(); + /* + * perform the final DMA sequence to disable the codec request + */ + oldcodecmode = read_codec(dev, 9); + write_codec(dev, 9, 0); /* disable codec DMA */ + wss_ack_int(dev); + if ((codecdma = read_codec(dev, 11)) & 0x10) { + dma_setup(sm, 1, dev->dma); + dma_setup(sm, 0, sm->hdrv.ptt_out.dma2); + abrt = 0; + while (((codecdma = read_codec(dev, 11)) & 0x10) || ((++abrt) >= 0x10000)); + } + wss_set_codec_fmt(dev, sm, SCSTATE->fmt[1], SCSTATE->fmt[0], 1, 1); + osamps = dma_setup(sm, 1, dev->dma) - 1; + isamps = dma_setup(sm, 0, sm->hdrv.ptt_out.dma2) - 1; + write_codec(dev, 15, osamps & 0xff); + write_codec(dev, 14, osamps >> 8); + if (SCSTATE->crystal) { + write_codec(dev, 31, isamps & 0xff); + write_codec(dev, 30, isamps >> 8); + } + write_codec(dev, 9, 3); + restore_flags(flags); +} + +/* --------------------------------------------------------------------- */ + +static void wssfdx_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_id; + struct sm_state *sm = (struct sm_state *)dev->priv; + unsigned long flags; + unsigned char cry_int_src; + unsigned icfrag, ocfrag, isamps, osamps; + + if (!dev || !sm || !sm->mode_rx || !sm->mode_tx || + sm->hdrv.magic != HDLCDRV_MAGIC) + return; + save_flags(flags); + cli(); + if (SCSTATE->crystal) { + /* Crystal has an essentially different interrupt handler! */ + cry_int_src = read_codec(dev, 0x18); + wss_ack_int(dev); + if (cry_int_src & 0x10) { /* playback interrupt */ + disable_dma(dev->dma); + clear_dma_ff(dev->dma); + osamps = dma_ptr(sm, 1, dev->dma, &ocfrag)-1; + write_codec(dev, 15, osamps & 0xff); + write_codec(dev, 14, osamps >> 8); + enable_dma(dev->dma); + } + if (cry_int_src & 0x20) { /* capture interrupt */ + disable_dma(sm->hdrv.ptt_out.dma2); + clear_dma_ff(sm->hdrv.ptt_out.dma2); + isamps = dma_ptr(sm, 0, sm->hdrv.ptt_out.dma2, &icfrag)-1; + write_codec(dev, 31, isamps & 0xff); + write_codec(dev, 30, isamps >> 8); + enable_dma(sm->hdrv.ptt_out.dma2); + } + restore_flags(flags); + sm_int_freq(sm); + sti(); + if (cry_int_src & 0x10) { + if (dma_end_transmit(sm, ocfrag)) + dma_clear_transmit(sm); + dma_transmit(sm); + } + if (cry_int_src & 0x20) { + dma_receive(sm, icfrag); + hdlcdrv_arbitrate(dev, &sm->hdrv); + } + sm_output_status(sm); + hdlcdrv_transmitter(dev, &sm->hdrv); + hdlcdrv_receiver(dev, &sm->hdrv); + return; + } + wss_ack_int(dev); + disable_dma(dev->dma); + disable_dma(sm->hdrv.ptt_out.dma2); + clear_dma_ff(dev->dma); + clear_dma_ff(sm->hdrv.ptt_out.dma2); + osamps = dma_ptr(sm, 1, dev->dma, &ocfrag)-1; + isamps = dma_ptr(sm, 0, sm->hdrv.ptt_out.dma2, &icfrag)-1; + write_codec(dev, 15, osamps & 0xff); + write_codec(dev, 14, osamps >> 8); + if (SCSTATE->crystal) { + write_codec(dev, 31, isamps & 0xff); + write_codec(dev, 30, isamps >> 8); + } + enable_dma(dev->dma); + enable_dma(sm->hdrv.ptt_out.dma2); + restore_flags(flags); + sm_int_freq(sm); + sti(); + if (dma_end_transmit(sm, ocfrag)) + dma_clear_transmit(sm); + dma_transmit(sm); + dma_receive(sm, icfrag); + hdlcdrv_arbitrate(dev, &sm->hdrv); + sm_output_status(sm); + hdlcdrv_transmitter(dev, &sm->hdrv); + hdlcdrv_receiver(dev, &sm->hdrv); +} + +/* --------------------------------------------------------------------- */ + +static int wssfdx_open(struct device *dev, struct sm_state *sm) +{ + if (!dev || !sm || !sm->mode_rx || !sm->mode_tx) + return -ENXIO; + if (dev->base_addr <= 0 || dev->base_addr > 0x1000-WSS_EXTENT || + dev->irq < 2 || dev->irq > 15 || dev->dma > 3) + return -ENXIO; + if (check_region(dev->base_addr, WSS_EXTENT)) + return -EACCES; + /* + * check if a card is available + */ + if (wss_init_codec(dev, sm, 1, 1, 1, 0, 0, -45, -45)) + return -ENODEV; + /* + * initialize some variables + */ + if (!(sm->dma.ibuf = kmalloc(sm->dma.ifragsz * (NUM_FRAGMENTS+1), GFP_KERNEL | GFP_DMA))) + return -ENOMEM; + if (!(sm->dma.obuf = kmalloc(sm->dma.ofragsz * NUM_FRAGMENTS, GFP_KERNEL | GFP_DMA))) { + kfree(sm->dma.ibuf); + return -ENOMEM; + } + dma_init_transmit(sm); + dma_init_receive(sm); + + memset(&sm->m, 0, sizeof(sm->m)); + memset(&sm->d, 0, sizeof(sm->d)); + if (sm->mode_tx->init) + sm->mode_tx->init(sm); + if (sm->mode_rx->init) + sm->mode_rx->init(sm); + + if (request_dma(dev->dma, sm->hwdrv->hw_name)) { + kfree(sm->dma.ibuf); + kfree(sm->dma.obuf); + return -EBUSY; + } + if (request_dma(sm->hdrv.ptt_out.dma2, sm->hwdrv->hw_name)) { + kfree(sm->dma.ibuf); + kfree(sm->dma.obuf); + free_dma(dev->dma); + return -EBUSY; + } + if (request_irq(dev->irq, wssfdx_interrupt, SA_INTERRUPT, + sm->hwdrv->hw_name, dev)) { + kfree(sm->dma.ibuf); + kfree(sm->dma.obuf); + free_dma(dev->dma); + free_dma(sm->hdrv.ptt_out.dma2); + return -EBUSY; + } + request_region(dev->base_addr, WSS_EXTENT, sm->hwdrv->hw_name); + setup_fdx_dma_wss(dev, sm); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int wssfdx_close(struct device *dev, struct sm_state *sm) +{ + if (!dev || !sm) + return -EINVAL; + /* + * disable interrupts + */ + disable_dma(dev->dma); + disable_dma(sm->hdrv.ptt_out.dma2); + write_codec(dev, 9, 0xc); /* disable codec */ + free_irq(dev->irq, dev); + free_dma(dev->dma); + free_dma(sm->hdrv.ptt_out.dma2); + release_region(dev->base_addr, WSS_EXTENT); + kfree(sm->dma.ibuf); + kfree(sm->dma.obuf); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int wssfdx_sethw(struct device *dev, struct sm_state *sm, char *mode) +{ + char *cp = strchr(mode, '.'); + const struct modem_tx_info **mtp = sm_modem_tx_table; + const struct modem_rx_info **mrp; + int i; + + if (!strcmp(mode, "off")) { + sm->mode_tx = NULL; + sm->mode_rx = NULL; + return 0; + } + if (cp) + *cp++ = '\0'; + else + cp = mode; + for (; *mtp; mtp++) { + if ((*mtp)->loc_storage > sizeof(sm->m)) { + printk(KERN_ERR "%s: insufficient storage for modulator %s (%d)\n", + sm_drvname, (*mtp)->name, (*mtp)->loc_storage); + continue; + } + if (!(*mtp)->name || strcmp((*mtp)->name, mode)) + continue; + if ((i = wss_srate_index((*mtp)->srate)) < 0) + continue; + for (mrp = sm_modem_rx_table; *mrp; mrp++) { + if ((*mrp)->loc_storage > sizeof(sm->d)) { + printk(KERN_ERR "%s: insufficient storage for demodulator %s (%d)\n", + sm_drvname, (*mrp)->name, (*mrp)->loc_storage); + continue; + } + if ((*mrp)->name && !strcmp((*mrp)->name, cp) && + (*mtp)->srate == (*mrp)->srate) { + sm->mode_tx = *mtp; + sm->mode_rx = *mrp; + SCSTATE->fmt[0] = SCSTATE->fmt[1] = i; + sm->dma.ifragsz = sm->dma.ofragsz = (sm->mode_rx->srate + 50)/100; + if (sm->dma.ifragsz < sm->mode_rx->overlap) + sm->dma.ifragsz = sm->mode_rx->overlap; + sm->dma.i16bit = sm->dma.o16bit = 2; + if (sm->mode_rx->demodulator_s16) { + sm->dma.i16bit = 1; + sm->dma.ifragsz <<= 1; +#ifdef __BIG_ENDIAN /* big endian 16bit only works on crystal cards... */ + SCSTATE->fmt[0] |= 0xc0; +#else /* __BIG_ENDIAN */ + SCSTATE->fmt[0] |= 0x40; +#endif /* __BIG_ENDIAN */ + } else if (sm->mode_rx->demodulator_u8) + sm->dma.i16bit = 0; + if (sm->mode_tx->modulator_s16) { + sm->dma.o16bit = 1; + sm->dma.ofragsz <<= 1; +#ifdef __BIG_ENDIAN /* big endian 16bit only works on crystal cards... */ + SCSTATE->fmt[1] |= 0xc0; +#else /* __BIG_ENDIAN */ + SCSTATE->fmt[1] |= 0x40; +#endif /* __BIG_ENDIAN */ + } else if (sm->mode_tx->modulator_u8) + sm->dma.o16bit = 0; + if (sm->dma.i16bit == 2 || sm->dma.o16bit == 2) { + printk(KERN_INFO "%s: mode %s or %s unusable\n", sm_drvname, + sm->mode_rx->name, sm->mode_tx->name); + sm->mode_tx = NULL; + sm->mode_rx = NULL; + return -EINVAL; + } + return 0; + } + } + } + return -EINVAL; +} + +/* --------------------------------------------------------------------- */ + +static int wssfdx_ioctl(struct device *dev, struct sm_state *sm, struct ifreq *ifr, + struct hdlcdrv_ioctl *hi, int cmd) +{ + if (cmd != SIOCDEVPRIVATE) + return -ENOIOCTLCMD; + + if (hi->cmd == HDLCDRVCTL_MODEMPARMASK) + return HDLCDRV_PARMASK_IOBASE | HDLCDRV_PARMASK_IRQ | + HDLCDRV_PARMASK_DMA | HDLCDRV_PARMASK_DMA2 | + HDLCDRV_PARMASK_SERIOBASE | HDLCDRV_PARMASK_PARIOBASE | + HDLCDRV_PARMASK_MIDIIOBASE; + + return wss_ioctl(dev, sm, ifr, hi, cmd); +} + +/* --------------------------------------------------------------------- */ + +const struct hardware_info sm_hw_wssfdx = { + "wssfdx", sizeof(struct sc_state_wss), + wssfdx_open, wssfdx_close, wssfdx_ioctl, wssfdx_sethw +}; + +/* --------------------------------------------------------------------- */ + +#undef SCSTATE diff --git a/drivers/net/hamradio/soundmodem/smdma.h b/drivers/net/hamradio/soundmodem/smdma.h new file mode 100644 index 000000000..44e457a7a --- /dev/null +++ b/drivers/net/hamradio/soundmodem/smdma.h @@ -0,0 +1,217 @@ +/*****************************************************************************/ + +/* + * smdma.h -- soundcard radio modem driver dma buffer routines. + * + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please note that the GPL allows you to use the driver, NOT the radio. + * In order to use the radio, you need a license from the communications + * authority of your country. + * + */ + +#ifndef _SMDMA_H +#define _SMDMA_H + +/* ---------------------------------------------------------------------- */ + +#include "sm.h" + +/* ---------------------------------------------------------------------- */ + +#define DMA_MODE_AUTOINIT 0x10 +#define NUM_FRAGMENTS 4 + +/* + * NOTE: make sure that hdlcdrv_hdlcbuffer contains enough space + * for the modulator to fill the whole DMA buffer without underrun + * at the highest possible baud rate, otherwise the TX state machine will + * not work correctly. That is (9k6 FSK): HDLCDRV_HDLCBUFFER > 6*NUM_FRAGMENTS + */ + +/* --------------------------------------------------------------------- */ +/* + * ===================== DMA buffer management =========================== + */ + +/* + * returns the number of samples per fragment + */ +extern __inline__ unsigned int dma_setup(struct sm_state *sm, int send, unsigned int dmanr) +{ + if (send) { + disable_dma(dmanr); + clear_dma_ff(dmanr); + set_dma_mode(dmanr, DMA_MODE_WRITE | DMA_MODE_AUTOINIT); + set_dma_addr(dmanr, virt_to_bus(sm->dma.obuf)); + set_dma_count(dmanr, sm->dma.ofragsz * NUM_FRAGMENTS); + enable_dma(dmanr); + if (sm->dma.o16bit) + return sm->dma.ofragsz/2; + return sm->dma.ofragsz; + } else { + disable_dma(dmanr); + clear_dma_ff(dmanr); + set_dma_mode(dmanr, DMA_MODE_READ | DMA_MODE_AUTOINIT); + set_dma_addr(dmanr, virt_to_bus(sm->dma.ibuf)); + set_dma_count(dmanr, sm->dma.ifragsz * NUM_FRAGMENTS); + enable_dma(dmanr); + if (sm->dma.i16bit) + return sm->dma.ifragsz/2; + return sm->dma.ifragsz; + } +} + +/* --------------------------------------------------------------------- */ + +extern __inline__ unsigned int dma_ptr(struct sm_state *sm, int send, unsigned int dmanr, + unsigned int *curfrag) +{ + unsigned int dmaptr, sz, frg, offs; + + dmaptr = get_dma_residue(dmanr); + if (send) { + sz = sm->dma.ofragsz * NUM_FRAGMENTS; + if (dmaptr == 0 || dmaptr > sz) + dmaptr = sz; + dmaptr--; + frg = dmaptr / sm->dma.ofragsz; + offs = (dmaptr % sm->dma.ofragsz) + 1; + *curfrag = NUM_FRAGMENTS - 1 - frg; +#ifdef SM_DEBUG + if (!sm->debug_vals.dma_residue || offs < sm->debug_vals.dma_residue) + sm->debug_vals.dma_residue = offs; +#endif /* SM_DEBUG */ + if (sm->dma.o16bit) + return offs/2; + return offs; + } else { + sz = sm->dma.ifragsz * NUM_FRAGMENTS; + if (dmaptr == 0 || dmaptr > sz) + dmaptr = sz; + dmaptr--; + frg = dmaptr / sm->dma.ifragsz; + offs = (dmaptr % sm->dma.ifragsz) + 1; + *curfrag = NUM_FRAGMENTS - 1 - frg; +#ifdef SM_DEBUG + if (!sm->debug_vals.dma_residue || offs < sm->debug_vals.dma_residue) + sm->debug_vals.dma_residue = offs; +#endif /* SM_DEBUG */ + if (sm->dma.i16bit) + return offs/2; + return offs; + } +} + +/* --------------------------------------------------------------------- */ + +extern __inline__ int dma_end_transmit(struct sm_state *sm, unsigned int curfrag) +{ + unsigned int diff = (NUM_FRAGMENTS + curfrag - sm->dma.ofragptr) % NUM_FRAGMENTS; + + sm->dma.ofragptr = curfrag; + if (sm->dma.ptt_cnt <= 0) { + sm->dma.ptt_cnt = 0; + return 0; + } + sm->dma.ptt_cnt -= diff; + if (sm->dma.ptt_cnt <= 0) { + sm->dma.ptt_cnt = 0; + return -1; + } + return 0; +} + +extern __inline__ void dma_transmit(struct sm_state *sm) +{ + void *p; + + while (sm->dma.ptt_cnt < NUM_FRAGMENTS && hdlcdrv_ptt(&sm->hdrv)) { + p = (unsigned char *)sm->dma.obuf + sm->dma.ofragsz * + ((sm->dma.ofragptr + sm->dma.ptt_cnt) % NUM_FRAGMENTS); + if (sm->dma.o16bit) { + time_exec(sm->debug_vals.mod_cyc, + sm->mode_tx->modulator_s16(sm, p, sm->dma.ofragsz/2)); + } else { + time_exec(sm->debug_vals.mod_cyc, + sm->mode_tx->modulator_u8(sm, p, sm->dma.ofragsz)); + } + sm->dma.ptt_cnt++; + } +} + +extern __inline__ void dma_init_transmit(struct sm_state *sm) +{ + sm->dma.ofragptr = 0; + sm->dma.ptt_cnt = 0; +} + +extern __inline__ void dma_start_transmit(struct sm_state *sm) +{ + sm->dma.ofragptr = 0; + if (sm->dma.o16bit) { + time_exec(sm->debug_vals.mod_cyc, + sm->mode_tx->modulator_s16(sm, sm->dma.obuf, sm->dma.ofragsz/2)); + } else { + time_exec(sm->debug_vals.mod_cyc, + sm->mode_tx->modulator_u8(sm, sm->dma.obuf, sm->dma.ofragsz)); + } + sm->dma.ptt_cnt = 1; +} + +extern __inline__ void dma_clear_transmit(struct sm_state *sm) +{ + sm->dma.ptt_cnt = 0; + memset(sm->dma.obuf, (sm->dma.o16bit) ? 0 : 0x80, sm->dma.ofragsz * NUM_FRAGMENTS); +} + +/* --------------------------------------------------------------------- */ + +extern __inline__ void dma_receive(struct sm_state *sm, unsigned int curfrag) +{ + void *p; + + while (sm->dma.ifragptr != curfrag) { + if (sm->dma.ifragptr) + p = (unsigned char *)sm->dma.ibuf + + sm->dma.ifragsz * sm->dma.ifragptr; + else { + p = (unsigned char *)sm->dma.ibuf + NUM_FRAGMENTS * sm->dma.ifragsz; + memcpy(p, sm->dma.ibuf, sm->dma.ifragsz); + } + if (sm->dma.o16bit) { + time_exec(sm->debug_vals.demod_cyc, + sm->mode_rx->demodulator_s16(sm, p, sm->dma.ifragsz/2)); + } else { + time_exec(sm->debug_vals.demod_cyc, + sm->mode_rx->demodulator_u8(sm, p, sm->dma.ifragsz)); + } + sm->dma.ifragptr = (sm->dma.ifragptr + 1) % NUM_FRAGMENTS; + } +} + +extern __inline__ void dma_init_receive(struct sm_state *sm) +{ + sm->dma.ifragptr = 0; +} + +/* --------------------------------------------------------------------- */ +#endif /* _SMDMA_H */ + + + diff --git a/drivers/net/hamradio/z8530.h b/drivers/net/hamradio/z8530.h new file mode 100644 index 000000000..3d4a918d7 --- /dev/null +++ b/drivers/net/hamradio/z8530.h @@ -0,0 +1,243 @@ + +/* 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) */ + +/* 8580/85180/85280 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 FASTDTR 0x10 /* Fast DTR/REQ Mode */ +#define CRCCBCR 0x20 /* CRC Check Bytes Completely Received */ +#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 FOY 0x80 /* FIFO Overflow Status */ |