diff options
Diffstat (limited to 'drivers/net/irda')
-rw-r--r-- | drivers/net/irda/.cvsignore | 2 | ||||
-rw-r--r-- | drivers/net/irda/Config.in | 17 | ||||
-rw-r--r-- | drivers/net/irda/Makefile | 80 | ||||
-rw-r--r-- | drivers/net/irda/actisys.c | 314 | ||||
-rw-r--r-- | drivers/net/irda/esi.c | 194 | ||||
-rw-r--r-- | drivers/net/irda/irport.c | 403 | ||||
-rw-r--r-- | drivers/net/irda/irtty.c | 748 | ||||
-rw-r--r-- | drivers/net/irda/pc87108.c | 1502 | ||||
-rw-r--r-- | drivers/net/irda/tekram.c | 314 | ||||
-rw-r--r-- | drivers/net/irda/uircc.c | 914 | ||||
-rw-r--r-- | drivers/net/irda/w83977af_ir.c | 1273 |
11 files changed, 5761 insertions, 0 deletions
diff --git a/drivers/net/irda/.cvsignore b/drivers/net/irda/.cvsignore new file mode 100644 index 000000000..857dd22e9 --- /dev/null +++ b/drivers/net/irda/.cvsignore @@ -0,0 +1,2 @@ +.depend +.*.flags diff --git a/drivers/net/irda/Config.in b/drivers/net/irda/Config.in new file mode 100644 index 000000000..c40b6f5c8 --- /dev/null +++ b/drivers/net/irda/Config.in @@ -0,0 +1,17 @@ +mainmenu_option next_comment +comment 'Infrared-port device drivers' + +dep_tristate 'IrTTY (uses serial driver)' CONFIG_IRTTY_SIR $CONFIG_IRDA +if [ "$CONFIG_IRTTY_SIR" != "n" ]; then + comment ' Dongle support' + bool ' Serial dongle support' CONFIG_DONGLE + if [ "$CONFIG_DONGLE" != "n" ]; then + dep_tristate ' ESI JetEye PC dongle' CONFIG_ESI_DONGLE $CONFIG_IRTTY_SIR + dep_tristate ' ACTiSYS IR-220L and IR220L+ dongle' CONFIG_ACTISYS_DONGLE $CONFIG_IRTTY_SIR + dep_tristate ' Tekram IrMate 210B dongle' CONFIG_TEKRAM_DONGLE $CONFIG_IRTTY_SIR + fi +fi +dep_tristate ' NSC PC87108' CONFIG_NSC_FIR $CONFIG_IRDA +dep_tristate ' Winbond W83977AF (IR)' CONFIG_WINBOND_FIR $CONFIG_IRDA +dep_tristate ' Sharp UIRCC' CONFIG_SHARP_FIR $CONFIG_IRDA +endmenu diff --git a/drivers/net/irda/Makefile b/drivers/net/irda/Makefile new file mode 100644 index 000000000..05880d5e1 --- /dev/null +++ b/drivers/net/irda/Makefile @@ -0,0 +1,80 @@ +# File: drivers/irda/Makefile +# +# Makefile for the Linux IrDA infrared port device drivers. +# + +SUB_DIRS := +MOD_SUB_DIRS := $(SUB_DIRS) +ALL_SUB_DIRS := $(SUB_DIRS) + +L_TARGET := irda_drivers.a +L_OBJS := +M_OBJS := +MOD_LIST_NAME := IRDA_MODULES + +ifeq ($(CONFIG_IRTTY_SIR),y) +L_OBJS += irtty.o +else + ifeq ($(CONFIG_IRTTY_SIR),m) + M_OBJS += irtty.o + endif +endif + +ifeq ($(CONFIG_NSC_FIR),y) +L_OBJS += pc87108.o +else + ifeq ($(CONFIG_NSC_FIR),m) + M_OBJS += pc87108.o + endif +endif + +ifeq ($(CONFIG_WINBOND_FIR),y) +L_OBJS += w83977af_ir.o +else + ifeq ($(CONFIG_WINBOND_FIR),m) + M_OBJS += w83977af_ir.o + endif +endif + +ifeq ($(CONFIG_SHARP_FIR),y) +L_OBJS += uircc.o irport.o +else + ifeq ($(CONFIG_SHARP_FIR),m) + M_OBJS += uircc.o irport.o + endif +endif + +ifeq ($(CONFIG_ESI_DONGLE),y) +L_OBJS += esi.o +else + ifeq ($(CONFIG_ESI_DONGLE),m) + M_OBJS += esi.o + endif +endif + +ifeq ($(CONFIG_TEKRAM_DONGLE),y) +L_OBJS += tekram.o +else + ifeq ($(CONFIG_TEKRAM_DONGLE),m) + M_OBJS += tekram.o + endif +endif + +ifeq ($(CONFIG_ACTISYS_DONGLE),y) +L_OBJS += actisys.o +else + ifeq ($(CONFIG_ACTISYS_DONGLE),m) + M_OBJS += actisys.o + endif +endif + +include $(TOPDIR)/Rules.make + +clean: + rm -f core *.o *.a *.s + + + + + + diff --git a/drivers/net/irda/actisys.c b/drivers/net/irda/actisys.c new file mode 100644 index 000000000..97fb5fa27 --- /dev/null +++ b/drivers/net/irda/actisys.c @@ -0,0 +1,314 @@ +/********************************************************************* + * + * Filename: actisys.c + * Version: 0.4 + * Description: Implementation for the ACTiSYS IR-220L and IR-220L+ + * dongles + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Wed Oct 21 20:02:35 1998 + * Modified at: Mon Jan 18 11:30:25 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998 Dag Brattli, All Rights Reserved. + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/tty.h> +#include <linux/sched.h> +#include <linux/init.h> + +#include <asm/ioctls.h> +#include <asm/segment.h> +#include <asm/uaccess.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irda_device.h> +#include <net/irda/irtty.h> +#include <net/irda/dongle.h> + +static void actisys_reset( struct irda_device *dev, int unused); +static void actisys_open( struct irda_device *idev, int type); +static void actisys_close( struct irda_device *dev); +static void actisys_change_speed( struct irda_device *dev, int baudrate); +static void actisys_reset( struct irda_device *dev, int unused); +static void actisys_init_qos( struct irda_device *idev, struct qos_info *qos); + +/* These are the baudrates supported */ +static int baud_rates[] = { 9600, 19200, 57600, 115200, 38400}; + +static struct dongle dongle = { + ACTISYS_DONGLE, + actisys_open, + actisys_close, + actisys_reset, + actisys_change_speed, + actisys_init_qos, +}; + +__initfunc(void actisys_init(void)) +{ + irtty_register_dongle(&dongle); +} + +void actisys_cleanup(void) +{ + irtty_unregister_dongle(&dongle); +} + +static void actisys_open( struct irda_device *idev, int type) +{ + strcat( idev->name, " <-> actisys"); + + idev->io.dongle_id = type; + + MOD_INC_USE_COUNT; +} + +static void actisys_close( struct irda_device *dev) +{ + MOD_DEC_USE_COUNT; +} + +/* + * Function actisys_change_speed (tty, baud) + * + * Change speed of the ACTiSYS IR-220L and IR-220L+ type IrDA dongles. + * To cycle through the available baud rates, pulse RTS low for a few ms. + * To be compatible with the new IR-220L+, we have to reset the dongle + * first since its not possible cycle around anymore and still be + * compatible with both dongles :-( + */ +static void actisys_change_speed( struct irda_device *idev, int baudrate) +{ + struct irtty_cb *self; + struct tty_struct *tty; + int arg; + struct termios old_termios; + int cflag; + int current_baudrate; + int index = 0; + mm_segment_t fs; + + DEBUG( 0, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = (struct irtty_cb *) idev->priv; + + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + current_baudrate = idev->qos.baud_rate.value; + + /* Find the correct baudrate index for the currently used baudrate */ + while ( current_baudrate != baud_rates[index]) + index++; + + DEBUG( 0, __FUNCTION__ "(), index=%d\n", index); + + if ( !self->tty) + return; + + tty = self->tty; + + /* Cycle through avaiable baudrates until we reach the correct one */ + while ( current_baudrate != baudrate) { + DEBUG( 0, __FUNCTION__ "(), current baudrate = %d\n", + baud_rates[index]); + DEBUG( 0, __FUNCTION__ "(), Clearing RTS\n"); + + /* Set DTR, clear RTS */ + arg = TIOCM_DTR|TIOCM_OUT2; + + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, + (unsigned long) &arg)) { + DEBUG( 0, __FUNCTION__ + "Error clearing RTS!\n"); + } + set_fs(fs); + + /* Wait at a few ms */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(2); + + /* Set DTR, Set RTS */ + arg = TIOCM_DTR | TIOCM_RTS |TIOCM_OUT2; + + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, + (unsigned long) &arg)) { + DEBUG( 0, __FUNCTION__ "Error setting RTS!\n"); + } + set_fs(fs); + + /* Wait at a few ms again */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout( 2); + + /* Go to next baudrate */ + if ( idev->io.dongle_id == ACTISYS_DONGLE) + index = (index+1) % 4; /* IR-220L */ + else + index = (index+1) % 5; /* IR-220L+ */ + + current_baudrate = baud_rates[index]; + } + DEBUG( 0, __FUNCTION__ "(), current baudrate = %d\n", + baud_rates[index]); + + /* Now change the speed of the serial port */ + old_termios = *(tty->termios); + cflag = tty->termios->c_cflag; + + cflag &= ~CBAUD; + + switch ( baudrate) { + case 9600: + default: + cflag |= B9600; + break; + case 19200: + cflag |= B19200; + break; + case 38400: + cflag |= B38400; + break; + case 57600: + cflag |= B57600; + break; + case 115200: + cflag |= B115200; + break; + } + + tty->termios->c_cflag = cflag; + + DEBUG( 0, __FUNCTION__ "(), Setting the speed of the serial port\n"); + tty->driver.set_termios( tty, &old_termios); +} + +/* + * Function actisys_reset (dev) + * + * Reset the Actisys type dongle. Warning, this function must only be + * called with a process context! + * + * 1. Clear DTR for a few ms. + * + */ +static void actisys_reset( struct irda_device *idev, int unused) +{ + struct irtty_cb *self; + struct tty_struct *tty; + int arg = 0; + mm_segment_t fs; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = (struct irtty_cb *) idev->priv; + + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + tty = self->tty; + if ( !tty) + return; + + DEBUG( 0, __FUNCTION__ "(), Clearing DTR\n"); + arg = TIOCM_RTS | TIOCM_OUT2; + + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, + (unsigned long) &arg)) + { + DEBUG( 0, __FUNCTION__"(), ioctl error!\n"); + } + set_fs(fs); + + /* Sleep 10-20 ms*/ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(2); + + DEBUG( 0, __FUNCTION__ "(), Setting DTR\n"); + arg = TIOCM_RTS | TIOCM_DTR | TIOCM_OUT2; + + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, + (unsigned long) &arg)) + { + DEBUG( 0, __FUNCTION__"(), ioctl error!\n"); + } + set_fs(fs); + + idev->qos.baud_rate.value = 9600; +} + +/* + * Function actisys_init_qos (qos) + * + * Initialize QoS capabilities + * + */ +static void actisys_init_qos( struct irda_device *idev, struct qos_info *qos) +{ + qos->baud_rate.bits &= IR_9600|IR_19200|IR_38400|IR_57600|IR_115200; + + /* Remove support for 38400 if this is not a 220L+ dongle */ + if ( idev->io.dongle_id == ACTISYS_DONGLE) + qos->baud_rate.bits &= ~IR_38400; + + qos->min_turn_time.bits &= 0xfe; /* All except 0 ms */ +} + +#ifdef MODULE + +/* + * Function init_module (void) + * + * Initialize Actisys module + * + */ +int init_module(void) +{ + actisys_init(); + return(0); +} + +/* + * Function cleanup_module (void) + * + * Cleanup Actisys module + * + */ +void cleanup_module(void) +{ + actisys_cleanup(); +} + +#endif diff --git a/drivers/net/irda/esi.c b/drivers/net/irda/esi.c new file mode 100644 index 000000000..a93e167aa --- /dev/null +++ b/drivers/net/irda/esi.c @@ -0,0 +1,194 @@ +/********************************************************************* + * + * Filename: esi.c + * Version: 1.1 + * Description: Driver for the Extended Systems JetEye PC + * Status: Experimental. + * Author: Thomas Davis, <ratbert@radiks.net> + * Created at: Sat Feb 21 18:54:38 1998 + * Modified at: Mon Jan 18 11:30:32 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * Sources: esi.c + * + * Copyright (c) 1998, Thomas Davis, <ratbert@radiks.net>, + * Copyright (c) 1998, Dag Brattli, <dagb@cs.uit.no> + * All Rights Reserved. + * + * 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. + * + * I, Thomas Davis, provide no warranty for any of this software. + * This material is provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/module.h> + +#include <linux/delay.h> +#include <linux/tty.h> +#include <linux/sched.h> +#include <linux/init.h> + +#include <asm/ioctls.h> +#include <asm/segment.h> +#include <asm/uaccess.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irda_device.h> +#include <net/irda/irtty.h> +#include <net/irda/dongle.h> + +static void esi_open( struct irda_device *idev, int type); +static void esi_close( struct irda_device *driver); +static void esi_change_speed( struct irda_device *idev, int baud); +static void esi_reset( struct irda_device *idev, int unused); +static void esi_qos_init( struct irda_device *idev, struct qos_info *qos); + +static struct dongle dongle = { + ESI_DONGLE, + esi_open, + esi_close, + esi_reset, + esi_change_speed, + esi_qos_init, +}; + +__initfunc(void esi_init(void)) +{ + irtty_register_dongle( &dongle); +} + +void esi_cleanup(void) +{ + irtty_unregister_dongle( &dongle); +} + +static void esi_open( struct irda_device *idev, int type) +{ + strcat( idev->description, " <-> esi"); + + idev->io.dongle_id = type; + + MOD_INC_USE_COUNT; +} + +static void esi_close( struct irda_device *driver) +{ + MOD_DEC_USE_COUNT; +} + +/* + * Function esi_change_speed (tty, baud) + * + * Set the speed for the Extended Systems JetEye PC ESI-9680 type dongle + * + */ +static void esi_change_speed( struct irda_device *idev, int baud) +{ + struct irtty_cb *self; + struct tty_struct *tty; + int arg = TIOCM_OUT2; + struct termios old_termios; + int cflag; + mm_segment_t fs; + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = (struct irtty_cb *) idev->priv; + + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + if ( !self->tty) + return; + + tty = self->tty; + + old_termios = *(tty->termios); + cflag = tty->termios->c_cflag; + + cflag &= ~CBAUD; + + switch (baud) { + case 19200: + cflag |= B19200; + arg |= TIOCM_DTR; + break; + case 115200: + cflag |= B115200; + arg |= TIOCM_RTS | TIOCM_DTR; + break; + case 9600: + default: + cflag |= B9600; + arg |= TIOCM_RTS; + break; + } + + tty->termios->c_cflag = cflag; + tty->driver.set_termios( tty, &old_termios); + + /* + * The ioctl function, or actually set_modem_info in serial.c + * expects a pointer to the argument in user space. To hack us + * around this we use the set_fs function to fool the routines + * that check if they are called from user space. We also need + * to send a pointer to the argument so get_user() gets happy. + * DB. + */ + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, (unsigned long) &arg)) { + DEBUG( 0, __FUNCTION__ "(), error setting ESI speed!\n"); + } + set_fs(fs); +} + +static void esi_reset( struct irda_device *idev, int unused) +{ + /* Empty */ +} + +/* + * Function esi_qos_init (qos) + * + * Init QoS capabilities for the dongle + * + */ +static void esi_qos_init( struct irda_device *idev, struct qos_info *qos) +{ + qos->baud_rate.bits &= IR_9600|IR_19200|IR_115200; +} + +#ifdef MODULE + +/* + * Function init_module (void) + * + * Initialize ESI module + * + */ +int init_module(void) +{ + esi_init(); + return(0); +} + +/* + * Function cleanup_module (void) + * + * Cleanup ESI module + * + */ +void cleanup_module(void) +{ + esi_cleanup(); +} + +#endif + diff --git a/drivers/net/irda/irport.c b/drivers/net/irda/irport.c new file mode 100644 index 000000000..ae8bcb27b --- /dev/null +++ b/drivers/net/irda/irport.c @@ -0,0 +1,403 @@ +/********************************************************************* + * + * Filename: irport.c + * Version: 0.8 + * Description: Serial driver for IrDA. + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Aug 3 13:49:59 1997 + * Modified at: Sat May 23 23:15:20 1998 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * Sources: serial.c by Linus Torvalds + * serial_serial.c by Aage Kvalnes <aage@cs.uit.no> + * + * Copyright (c) 1997,1998 Dag Brattli <dagb@cs.uit.no> + * All Rights Reserved. + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + * NOTICE: + * + * This driver is ment to be a small serial driver to be used for + * IR-chipsets that has a UART (16550) compatibility mode. If your + * chipset is is UART only, you should probably use IrTTY instead since + * the Linux serial driver is probably more robust and optimized. + * + * The functions in this file may be used by FIR drivers, but this + * driver knows nothing about FIR drivers so don't ever insert such + * code into this file. Instead you should code your FIR driver in a + * separate file, and then call the functions in this file if + * necessary. This is becase it is difficult to use the Linux serial + * driver with a FIR driver becase they must share interrupts etc. Most + * FIR chipsets can function in advanced SIR mode, and you should + * probably use that mode instead of the UART compatibility mode (and + * then just forget about this file) + * + ********************************************************************/ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <linux/errno.h> +#include <linux/init.h> + +#include <linux/skbuff.h> +#include <linux/serial_reg.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/wrapper.h> +#include <net/irda/irport.h> + +#define IO_EXTENT 8 + +static unsigned int io[] = { 0x3e8, ~0, ~0, ~0 }; +static unsigned int irq[] = { 11, 0, 0, 0 }; + +static void irport_write_wakeup( struct irda_device *idev); +static int irport_write( int iobase, int fifo_size, __u8 *buf, int len); +static void irport_receive( struct irda_device *idev); + +__initfunc(int irport_init(void)) +{ +/* int i; */ + +/* for ( i=0; (io[i] < 2000) && (i < 4); i++) { */ +/* int ioaddr = io[i]; */ +/* if (check_region(ioaddr, IO_EXTENT)) */ +/* continue; */ +/* if (irport_open( i, io[i], io2[i], irq[i], dma[i]) == 0) */ +/* return 0; */ +/* } */ +/* return -ENODEV; */ + return 0; +} + +/* + * Function pc87108_cleanup () + * + * Close all configured chips + * + */ +#ifdef MODULE +static void irport_cleanup(void) +{ + int i; + + DEBUG( 4, __FUNCTION__ "()\n"); + + /* for ( i=0; i < 4; i++) { */ +/* if ( dev_self[i]) */ +/* irport_close( &(dev_self[i]->idev)); */ +/* } */ +} +#endif /* MODULE */ + +/* + * Function irport_open (void) + * + * Start IO port + * + */ +int irport_open( int iobase) +{ + DEBUG( 0, __FUNCTION__ "(), iobase=%#x\n", iobase); + + /* Initialize UART */ + outb( UART_LCR_WLEN8, iobase+UART_LCR); /* Reset DLAB */ + outb(( UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase+UART_MCR); + + /* Turn on interrups */ + outb(( UART_IER_THRI |UART_IER_RLSI | UART_IER_RDI), iobase+UART_IER); + + return 0; +} + +/* + * Function irport_cleanup () + * + * Stop IO port + * + */ +void irport_close( int iobase) +{ + DEBUG( 0, __FUNCTION__ "()\n"); + + /* Reset UART */ + outb( 0, iobase+UART_MCR); + + /* Turn off interrupts */ + outb( 0, iobase+UART_IER); +} + +/* + * Function irport_change_speed (idev, speed) + * + * Set speed of port to specified baudrate + * + */ +void irport_change_speed( int iobase, int speed) +{ + int fcr; /* FIFO control reg */ + int lcr; /* Line control reg */ + int divisor; + + DEBUG( 0, __FUNCTION__ "(), Setting speed to: %d\n", speed); + + DEBUG( 0, __FUNCTION__ "(), iobase=%#x\n", iobase); + + /* Turn off interrupts */ + outb( 0, iobase+UART_IER); + + divisor = SPEED_MAX/speed; + + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_14; + + /* IrDA ports use 8N1 */ + lcr = UART_LCR_WLEN8; + + outb( UART_LCR_DLAB | lcr, iobase+UART_LCR); /* Set DLAB */ + outb( divisor & 0xff, iobase+UART_DLL); /* Set speed */ + outb( divisor >> 8, iobase+UART_DLM); + outb( lcr, iobase+UART_LCR); /* Set 8N1 */ + outb( fcr, iobase+UART_FCR); /* Enable FIFO's */ + + /* Turn on interrups */ + outb( UART_IER_THRI|UART_IER_RLSI|UART_IER_RDI, iobase+UART_IER); +} + +/* + * Function irport_interrupt (irq, dev_id, regs) + * + * + */ +void irport_interrupt( int irq, void *dev_id, struct pt_regs *regs) +{ + struct irda_device *idev = (struct irda_device *) dev_id; + + int iobase, status; + int iir; + + DEBUG( 4, __FUNCTION__ "(), irq %d\n", irq); + + if ( !idev) { + printk( KERN_WARNING __FUNCTION__ + "() irq %d for unknown device.\n", irq); + return; + } + + idev->netdev.interrupt = 1; + + iobase = idev->io.iobase2; + + iir = inb(iobase + UART_IIR); + do { + status = inb( iobase+UART_LSR); + + if (status & UART_LSR_DR) { + /* Receive interrupt */ + irport_receive(idev); + } + if (status & UART_LSR_THRE) { + /* Transmitter ready for data */ + irport_write_wakeup(idev); + } + } while (!(inb(iobase+UART_IIR) & UART_IIR_NO_INT)); + + idev->netdev.interrupt = 0; +} + +/* + * Function irport_write_wakeup (tty) + * + * Called by the driver when there's room for more data. If we have + * more packets to send, we send them here. + * + */ +static void irport_write_wakeup( struct irda_device *idev) +{ + int actual = 0, count; + + DEBUG( 4, __FUNCTION__ "() <%ld>\n", jiffies); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + /* Finished with frame? */ + if ( idev->tx_buff.offset == idev->tx_buff.len) { + + /* + * Now serial buffer is almost free & we can start + * transmission of another packet + */ + DEBUG( 4, __FUNCTION__ "(), finished with frame!\n"); + + idev->netdev.tbusy = 0; /* Unlock */ + idev->stats.tx_packets++; + + /* Schedule network layer, so we can get some more frames */ + mark_bh( NET_BH); + + return; + } + + /* Write data left in transmit buffer */ + count = idev->tx_buff.len - idev->tx_buff.offset; + actual = irport_write( idev->io.iobase2, idev->io.fifo_size, + idev->tx_buff.head, count); + idev->tx_buff.offset += actual; + idev->tx_buff.head += actual; +} + +/* + * Function irport_write (driver) + * + * + * + */ +static int irport_write( int iobase, int fifo_size, __u8 *buf, int len) +{ + int actual = 0; + + /* Tx FIFO should be empty! */ + if (!(inb( iobase+UART_LSR) & UART_LSR_THRE)) { + DEBUG( 0, __FUNCTION__ "(), failed, fifo not empty!\n"); + return -1; + } + + /* Fill FIFO with current frame */ + while (( fifo_size-- > 0) && (actual < len)) { + /* Transmit next byte */ + outb( buf[actual], iobase+UART_TX); + + actual++; + } + + DEBUG( 4, __FUNCTION__ "(), fifo_size %d ; %d sent of %d\n", + fifo_size, actual, len); + + return actual; +} + +/* + * Function irport_xmit (void) + * + * Transmits the current frame until FIFO is full, then + * waits until the next transmitt interrupt, and continues until the + * frame is transmited. + */ +int irport_hard_xmit( struct sk_buff *skb, struct device *dev) +{ + struct irda_device *idev; + int xbofs; + int actual; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( dev != NULL, return -1;); + + if ( dev->tbusy) { + DEBUG( 4, __FUNCTION__ "(), tbusy==TRUE\n"); + + return -EBUSY; + } + + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return -1;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + /* Lock transmit buffer */ + if ( irda_lock( (void *) &dev->tbusy) == FALSE) + return -EBUSY; + + /* + * Transfer skb to tx_buff while wrapping, stuffing and making CRC + */ + idev->tx_buff.len = async_wrap_skb( skb, idev->tx_buff.data, + idev->tx_buff.truesize); + + actual = irport_write( idev->io.iobase2, idev->io.fifo_size, + idev->tx_buff.data, idev->tx_buff.len); + + idev->tx_buff.offset = actual; + idev->tx_buff.head = idev->tx_buff.data + actual; + + dev_kfree_skb( skb); + + return 0; +} + +/* + * Function irport_receive (void) + * + * Receive one frame from the infrared port + * + */ +static void irport_receive( struct irda_device *idev) +{ + int iobase; + + if ( !idev) + return; + + DEBUG( 4, __FUNCTION__ "()\n"); + + iobase = idev->io.iobase2; + + if ( idev->rx_buff.len == 0) + idev->rx_buff.head = idev->rx_buff.data; + + /* + * Receive all characters in Rx FIFO, unwrap and unstuff them. + * async_unwrap_char will deliver all found frames + */ + do { + async_unwrap_char( idev, inb( iobase+UART_RX)); + + } while ( inb( iobase+UART_LSR) & UART_LSR_DR); +} + +#ifdef MODULE + +/* + * Function cleanup_module (void) + * + * + * + */ +void cleanup_module(void) +{ + irport_cleanup(); +} + +/* + * Function init_module (void) + * + * + */ +int init_module(void) +{ + if (irport_init() < 0) { + cleanup_module(); + return 1; + } + return(0); +} + +#endif /* MODULE */ + diff --git a/drivers/net/irda/irtty.c b/drivers/net/irda/irtty.c new file mode 100644 index 000000000..09853b5b6 --- /dev/null +++ b/drivers/net/irda/irtty.c @@ -0,0 +1,748 @@ +/********************************************************************* + * + * Filename: irtty.c + * Version: 1.0 + * Description: IrDA line discipline implementation + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Tue Dec 9 21:18:38 1997 + * Modified at: Mon Jan 18 15:32:03 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * Sources: slip.c by Laurence Culhane, <loz@holmes.demon.co.uk> + * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org> + * + * Copyright (c) 1998 Dag Brattli, All Rights Reserved. + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> +#include <asm/uaccess.h> +#include <linux/kernel.h> +#include <linux/tty.h> +#include <asm/segment.h> +#include <linux/init.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irtty.h> +#include <net/irda/wrapper.h> +#include <net/irda/irlap.h> +#include <net/irda/timer.h> +#include <net/irda/irda_device.h> +#include <linux/kmod.h> + +static hashbin_t *irtty = NULL; +static hashbin_t *dongles = NULL; + +static struct tty_ldisc irda_ldisc; + +static int irtty_hard_xmit( struct sk_buff *skb, struct device *dev); +static void irtty_wait_until_sent( struct irda_device *driver); +static int irtty_is_receiving( struct irda_device *idev); +static int irtty_net_init( struct device *dev); +static int irtty_net_open(struct device *dev); +static int irtty_net_close(struct device *dev); + +static int irtty_open( struct tty_struct *tty); +static void irtty_close( struct tty_struct *tty); +static int irtty_ioctl( struct tty_struct *, void *, int, void *); +static int irtty_receive_room( struct tty_struct *tty); +static void irtty_change_speed( struct irda_device *dev, int baud); +static void irtty_write_wakeup( struct tty_struct *tty); + +static void irtty_receive_buf( struct tty_struct *, const unsigned char *, + char *, int); +char *driver_name = "irtty"; + +__initfunc(int irtty_init(void)) +{ + int status; + + irtty = hashbin_new( HB_LOCAL); + if ( irtty == NULL) { + printk( KERN_WARNING "IrDA: Can't allocate irtty hashbin!\n"); + return -ENOMEM; + } + + dongles = hashbin_new( HB_LOCAL); + if ( dongles == NULL) { + printk( KERN_WARNING + "IrDA: Can't allocate dongles hashbin!\n"); + return -ENOMEM; + } + + /* Fill in our line protocol discipline, and register it */ + memset( &irda_ldisc, 0, sizeof( irda_ldisc)); + + irda_ldisc.magic = TTY_LDISC_MAGIC; + irda_ldisc.name = "irda"; + irda_ldisc.flags = 0; + irda_ldisc.open = irtty_open; + irda_ldisc.close = irtty_close; + irda_ldisc.read = NULL; + irda_ldisc.write = NULL; + irda_ldisc.ioctl = (int (*)(struct tty_struct *, struct file *, + unsigned int, unsigned long)) irtty_ioctl; + irda_ldisc.poll = NULL; + irda_ldisc.receive_buf = irtty_receive_buf; + irda_ldisc.receive_room = irtty_receive_room; + irda_ldisc.write_wakeup = irtty_write_wakeup; + + if (( status = tty_register_ldisc( N_IRDA, &irda_ldisc)) != 0) { + printk( KERN_ERR + "IrDA: can't register line discipline (err = %d)\n", + status); + } + + return status; +} + +/* + * Function irtty_cleanup ( ) + * + * Called when the irda module is removed. Here we remove all instances + * of the driver, and the master array. + */ +#ifdef MODULE +static void irtty_cleanup(void) +{ + int ret; + + /* + * Unregister tty line-discipline + */ + if (( ret = tty_register_ldisc( N_IRDA, NULL))) { + printk( KERN_ERR + "IrTTY: can't unregister line discipline (err = %d)\n", + ret); + } + + /* + * The TTY should care of deallocating the instances by using the + * callback to irtty_close(), therefore we do give any deallocation + * function to hashbin_destroy(). + */ + hashbin_delete( irtty, NULL); + hashbin_delete( dongles, NULL); +} +#endif /* MODULE */ + +/* + * Function irtty_open(tty) + * + * This function is called by the TTY module when the IrDA line + * discipline is called for. Because we are sure the tty line exists, + * we only have to link it to a free IrDA channel. + */ +static int irtty_open( struct tty_struct *tty) +{ + struct irtty_cb *self; + char name[16]; + + ASSERT( tty != NULL, return -EEXIST;); + + /* First make sure we're not already connected. */ + self = (struct irtty_cb *) tty->disc_data; + if ( self != NULL && self->magic == IRTTY_MAGIC) + return -EEXIST; + + /* + * Allocate new instance of the driver + */ + self = kmalloc( sizeof(struct irtty_cb), GFP_KERNEL); + if ( self == NULL) { + printk( KERN_ERR "IrDA: Can't allocate memory for " + "IrDA control block!\n"); + return -ENOMEM; + } + memset( self, 0, sizeof(struct irtty_cb)); + + self->tty = tty; + tty->disc_data = self; + + /* Give self a name */ + sprintf( name, "%s%d", tty->driver.name, + MINOR(tty->device) - tty->driver.minor_start + + tty->driver.name_base); + + /* hashbin_insert( irtty, (QUEUE*) self, 0, self->name); */ + hashbin_insert( irtty, (QUEUE*) self, (int) self, NULL); + + if (tty->driver.flush_buffer) { + tty->driver.flush_buffer(tty); + } + + if (tty->ldisc.flush_buffer) { + tty->ldisc.flush_buffer(tty); + } + + self->magic = IRTTY_MAGIC; + + /* + * Initialize driver + */ + /* self->idev.flags |= SIR_MODE | IO_PIO; */ + self->idev.rx_buff.state = OUTSIDE_FRAME; + + /* + * Initialize QoS capabilities, we fill in all the stuff that + * we support. Be careful not to place any restrictions on values + * that are not device dependent (such as link disconnect time) so + * this parameter can be set by IrLAP (or the user) instead. DB + */ + irda_init_max_qos_capabilies( &self->idev.qos); + + /* The only value we must override it the baudrate */ + self->idev.qos.baud_rate.bits = IR_9600|IR_19200|IR_38400|IR_57600| + IR_115200; + self->idev.qos.min_turn_time.bits = 0x03; + irda_qos_bits_to_value( &self->idev.qos); + + /* Specify which buffer allocation policy we need */ + self->idev.rx_buff.flags = GFP_KERNEL; + self->idev.tx_buff.flags = GFP_KERNEL; + + /* Specify how much memory we want */ + self->idev.rx_buff.truesize = 4000; + self->idev.tx_buff.truesize = 4000; + + /* Initialize callbacks */ + self->idev.change_speed = irtty_change_speed; + self->idev.is_receiving = irtty_is_receiving; + /* self->idev.is_tbusy = irtty_is_tbusy; */ + self->idev.wait_until_sent = irtty_wait_until_sent; + + /* Override the network functions we need to use */ + self->idev.netdev.init = irtty_net_init; + self->idev.netdev.hard_start_xmit = irtty_hard_xmit; + self->idev.netdev.open = irtty_net_open; + self->idev.netdev.stop = irtty_net_close; + + /* Open the IrDA device */ + irda_device_open( &self->idev, name, self); + + MOD_INC_USE_COUNT; + + return 0; +} + +/* + * Function irtty_close ( tty) + * + * Close down a IrDA channel. This means flushing out any pending queues, + * and then restoring the TTY line discipline to what it was before it got + * hooked to IrDA (which usually is TTY again). + */ +static void irtty_close( struct tty_struct *tty) +{ + struct irtty_cb *self = (struct irtty_cb *) tty->disc_data; + + /* First make sure we're connected. */ + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + /* We are not using any dongle anymore! */ + if ( self->dongle_q) + self->dongle_q->dongle->close( &self->idev); + + /* Remove driver */ + irda_device_close( &self->idev); + + /* Stop tty */ + tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + tty->disc_data = 0; + + self->tty = NULL; + self->magic = 0; + + /* hashbin_remove( irtty, 0, self->name); */ + self = hashbin_remove( irtty, (int) self, NULL); + + if ( self != NULL) + kfree( self); + + MOD_DEC_USE_COUNT; + + DEBUG( 4, "IrTTY: close() -->\n"); +} + +/* + * Function irtty_change_speed ( self, baud) + * + * Change the speed of the serial port. The driver layer must check that + * all transmission has finished using the irtty_wait_until_sent() + * function. + */ +static void irtty_change_speed( struct irda_device *idev, int baud) +{ + struct termios old_termios; + struct irtty_cb *self; + int cflag; + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = (struct irtty_cb *) idev->priv; + + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + old_termios = *(self->tty->termios); + cflag = self->tty->termios->c_cflag; + + cflag &= ~CBAUD; + + DEBUG( 4, __FUNCTION__ "(), Setting speed to %d\n", baud); + + switch( baud) { + case 1200: + cflag |= B1200; + break; + case 2400: + cflag |= B2400; + break; + case 4800: + cflag |= B4800; + break; + case 19200: + cflag |= B19200; + break; + case 38400: + cflag |= B38400; + break; + case 57600: + cflag |= B57600; + break; + case 115200: + cflag |= B115200; + break; + case 9600: + default: + cflag |= B9600; + break; + } + + self->tty->termios->c_cflag = cflag; + self->tty->driver.set_termios( self->tty, &old_termios); +} + +/* + * Function irtty_init_dongle (self, type) + * + * Initialize attached dongle. Warning, must be called with a process + * context! + */ +static void irtty_init_dongle( struct irtty_cb *self, int type) +{ + struct dongle_q *node; + + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + +#ifdef CONFIG_KMOD + /* Try to load the module needed */ + switch( type) { + case ESI_DONGLE: + DEBUG( 0, __FUNCTION__ "(), ESI dongle!\n"); + request_module( "esi"); + break; + case TEKRAM_DONGLE: + DEBUG( 0, __FUNCTION__ "(), Tekram dongle!\n"); + request_module( "tekram"); + break; + case ACTISYS_DONGLE: + DEBUG( 0, __FUNCTION__ "(), ACTiSYS dongle!\n"); + request_module( "actisys"); + break; + default: + DEBUG( 0, __FUNCTION__ "(), Unknown dongle type!\n"); + return; + break; + } +#endif /* CONFIG_KMOD */ + + node = hashbin_find( dongles, type, NULL); + if ( !node) { + DEBUG( 0, __FUNCTION__ + "(), Unable to find requested dongle\n"); + return; + } + self->dongle_q = node; + + /* Use this change speed function instead of the default */ + self->idev.change_speed = node->dongle->change_speed; + + /* + * Now initialize the dongle! + */ + node->dongle->open( &self->idev, type); + node->dongle->qos_init( &self->idev, &self->idev.qos); + + /* Reset dongle */ + node->dongle->reset( &self->idev, 0); + + /* Set to default baudrate */ + node->dongle->change_speed( &self->idev, 9600); +} + +/* + * Function irtty_ioctl (tty, file, cmd, arg) + * + * The Swiss army knife of system calls :-) + * + */ +static int irtty_ioctl( struct tty_struct *tty, void *file, int cmd, + void *arg) +{ + struct irtty_cb *self; + int err = 0; + int size = _IOC_SIZE(cmd); + + self = (struct irtty_cb *) tty->disc_data; + + ASSERT( self != NULL, return -ENODEV;); + ASSERT( self->magic == IRTTY_MAGIC, return -EBADR;); + + if ( _IOC_DIR(cmd) & _IOC_READ) + err = verify_area( VERIFY_WRITE, (void *) arg, size); + else if ( _IOC_DIR(cmd) & _IOC_WRITE) + err = verify_area( VERIFY_READ, (void *) arg, size); + if ( err) + return err; + + switch(cmd) { + case TCGETS: + case TCGETA: + return n_tty_ioctl( tty, (struct file *) file, cmd, + (unsigned long) arg); + break; + case IRTTY_IOCTDONGLE: + /* Initialize dongle */ + irtty_init_dongle( self, (int) arg); + break; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +/* + * Function irtty_receive_buf( tty, cp, count) + * + * Handle the 'receiver data ready' interrupt. This function is called + * by the 'tty_io' module in the kernel when a block of IrDA data has + * been received, which can now be decapsulated and delivered for + * further processing + */ +static void irtty_receive_buf( struct tty_struct *tty, const unsigned + char *cp, char *fp, int count) +{ + struct irtty_cb *self = (struct irtty_cb *) tty->disc_data; + + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + /* Read the characters out of the buffer */ + while (count--) { + /* + * Characters received with a parity error, etc? + */ + if (fp && *fp++) { + DEBUG( 0, "Framing or parity error!\n"); + irda_device_set_media_busy( &self->idev, TRUE); + /* sl->rx_errors++; */ + cp++; + continue; + } + /* + * Unwrap and destuff one byte + */ + async_unwrap_char( &self->idev, *cp++); + /* self->rx_over_errors++; */ + } +} + +/* + * Function irtty_hard_xmit (skb, dev) + * + * Transmit skb + * + */ +static int irtty_hard_xmit( struct sk_buff *skb, struct device *dev) +{ + struct irtty_cb *self; + struct irda_device *idev; + int actual = 0; + + ASSERT( dev != NULL, return 0;); + ASSERT( skb != NULL, return 0;); + + if ( dev->tbusy) { + DEBUG( 4, __FUNCTION__ "(), tbusy==TRUE\n"); + + return -EBUSY; + } + + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + self = (struct irtty_cb *) idev->priv; + + ASSERT( self != NULL, return 0;); + ASSERT( self->magic == IRTTY_MAGIC, return 0;); + + /* Lock transmit buffer */ + if ( irda_lock( (void *) &dev->tbusy) == FALSE) + return 0; + + /* + * Transfer skb to tx_buff while wrapping, stuffing and making CRC + */ + idev->tx_buff.len = async_wrap_skb( skb, idev->tx_buff.data, + idev->tx_buff.truesize); + + self->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP); + + dev->trans_start = jiffies; + + if ( self->tty->driver.write) + actual = self->tty->driver.write( self->tty, 0, + idev->tx_buff.data, + idev->tx_buff.len); + + idev->tx_buff.offset = actual; + idev->tx_buff.head = idev->tx_buff.data + actual; +#if 0 + /* + * Did we transmit the whole frame? Commented out for now since + * I must check if this optimalization really works. DB. + */ + if (( idev->tx.count - idev->tx.ptr) <= 0) { + DEBUG( 4, "irtty_xmit_buf: finished with frame!\n"); + self->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + irda_unlock( &self->tbusy); + } +#endif + + dev_kfree_skb( skb); + + return 0; +} + +/* + * Function irtty_receive_room (tty) + * + * Used by the TTY to find out how much data we can receive at a time + * +*/ +static int irtty_receive_room( struct tty_struct *tty) +{ + return 65536; /* We can handle an infinite amount of data. :-) */ +} + +/* + * Function irtty_write_wakeup (tty) + * + * Called by the driver when there's room for more data. If we have + * more packets to send, we send them here. + * + */ +static void irtty_write_wakeup( struct tty_struct *tty) +{ + int actual = 0, count; + struct irtty_cb *self = (struct irtty_cb *) tty->disc_data; + struct irda_device *idev; + + /* + * First make sure we're connected. + */ + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + idev = &self->idev; + + /* + * Finished with frame? + */ + if ( idev->tx_buff.offset == idev->tx_buff.len) { + + /* + * Now serial buffer is almost free & we can start + * transmission of another packet + */ + DEBUG( 4, __FUNCTION__ "(), finished with frame!\n"); + + tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + + idev->netdev.tbusy = 0; /* Unlock */ + idev->stats.tx_packets++; + idev->stats.tx_bytes += idev->tx_buff.len; + + /* Tell network layer that we want more frames */ + mark_bh( NET_BH); + + return; + } + /* + * Write data left in transmit buffer + */ + count = idev->tx_buff.len - idev->tx_buff.offset; + actual = tty->driver.write( tty, 0, idev->tx_buff.head, count); + idev->tx_buff.offset += actual; + idev->tx_buff.head += actual; + + DEBUG( 4, "actual=%d, sent %d\n", actual, count); +} + +/* + * Function irtty_is_receiving (idev) + * + * Return TRUE is we are currently receiving a frame + * + */ +static int irtty_is_receiving( struct irda_device *idev) +{ + return ( idev->rx_buff.state != OUTSIDE_FRAME); +} + +/* + * Function irtty_change_speed_ready (idev) + * + * Are we completely finished with transmitting frames so its possible + * to change the speed of the serial port. Warning this function must + * be called with a process context! + */ +static void irtty_wait_until_sent( struct irda_device *idev) +{ + struct irtty_cb *self = (struct irtty_cb *) idev->priv; + + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + DEBUG( 4, "Chars in buffer %d\n", + self->tty->driver.chars_in_buffer( self->tty)); + + tty_wait_until_sent( self->tty, 0); +} + +int irtty_register_dongle( struct dongle *dongle) +{ + struct dongle_q *new; + + /* Check if this compressor has been registred before */ + if ( hashbin_find ( dongles, dongle->type, NULL)) { + DEBUG( 0, __FUNCTION__ "(), Dongle already registered\n"); + return 0; + } + + /* Make new IrDA dongle */ + new = (struct dongle_q *) kmalloc (sizeof (struct dongle_q), + GFP_KERNEL); + if (new == NULL) { + return 1; + + } + memset( new, 0, sizeof( struct dongle_q)); + new->dongle = dongle; + + /* Insert IrDA compressor into hashbin */ + hashbin_insert( dongles, (QUEUE *) new, dongle->type, NULL); + + return 0; +} + +void irtty_unregister_dongle( struct dongle *dongle) +{ + struct dongle_q *node; + + node = hashbin_remove( dongles, dongle->type, NULL); + if ( !node) { + DEBUG( 0, __FUNCTION__ "(), dongle not found!\n"); + return; + } + kfree( node); +} + +static int irtty_net_init( struct device *dev) +{ + /* Set up to be a normal IrDA network device driver */ + irda_device_setup( dev); + + /* Insert overrides below this line! */ + + return 0; +} + + +static int irtty_net_open( struct device *dev) +{ + ASSERT( dev != NULL, return -1;); + + /* Ready to play! */ + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + MOD_INC_USE_COUNT; + + return 0; +} + +static int irtty_net_close(struct device *dev) +{ + ASSERT( dev != NULL, return -1;); + + /* Stop device */ + dev->tbusy = 1; + dev->start = 0; + + MOD_DEC_USE_COUNT; + + return 0; +} + +#ifdef MODULE + +/* + * Function init_module (void) + * + * Initialize IrTTY module + * + */ +int init_module(void) +{ + irtty_init(); + return(0); +} + +/* + * Function cleanup_module (void) + * + * Cleanup IrTTY module + * + */ +void cleanup_module(void) +{ + irtty_cleanup(); +} + +#endif /* MODULE */ + + + + + + + diff --git a/drivers/net/irda/pc87108.c b/drivers/net/irda/pc87108.c new file mode 100644 index 000000000..faf9eea33 --- /dev/null +++ b/drivers/net/irda/pc87108.c @@ -0,0 +1,1502 @@ +/********************************************************************* + * + * Filename: pc87108.c + * Version: 0.8 + * Description: FIR/MIR driver for the NS PC87108 chip + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sat Nov 7 21:43:15 1998 + * Modified at: Mon Dec 28 08:46:16 1998 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998 Dag Brattli <dagb@cs.uit.no> + * Copyright (c) 1998 Lichen Wang, <lwang@actisys.com> + * Copyright (c) 1998 Actisys Corp., www.actisys.com + * All Rights Reserved + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + * Notice that all functions that needs to access the chip in _any_ + * way, must save BSR register on entry, and restore it on exit. + * It is _very_ important to follow this policy! + * + * __u8 bank; + * + * bank = inb( iobase+BSR); + * + * do_your_stuff_here(); + * + * outb( bank, iobase+BSR); + * + * If you find bugs in this file, its very likely that the same bug + * will also be in w83977af_ir.c since the implementations is quite + * similar. + * + ********************************************************************/ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/malloc.h> +#include <linux/delay.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> + +#include <net/irda/wrapper.h> +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irlap_frame.h> +#include <net/irda/irda_device.h> + +#include <net/irda/pc87108.h> + +#define BROKEN_DONGLE_ID + +static char *driver_name = "pc87108"; + +#define CHIP_IO_EXTENT 8 + +static unsigned int io[] = { 0x2f8, ~0, ~0, ~0 }; +static unsigned int io2[] = { 0x150, 0, 0, 0}; +static unsigned int irq[] = { 3, 0, 0, 0 }; +static unsigned int dma[] = { 0, 0, 0, 0 }; + +static struct pc87108 *dev_self[] = { NULL, NULL, NULL, NULL}; + +static char *dongle_types[] = { + "Differential serial interface", + "Differential serial interface", + "Reserved", + "Reserved", + "Sharp RY5HD01", + "Reserved", + "Single-ended serial interface", + "Consumer-IR only", + "HP HSDL-2300, HP HSDL-3600/HSDL-3610", + "IBM31T1100 or Temic TFDS6000/TFDS6500", + "Reserved", + "Reserved", + "HP HSDL-1100/HSDL-2100", + "HP HSDL-1100/HSDL-2100" + "Supports SIR Mode only", + "No dongle connected", +}; + +/* Some prototypes */ +static int pc87108_open( int i, unsigned int iobase, unsigned int board_addr, + unsigned int irq, unsigned int dma); +static int pc87108_close( struct irda_device *idev); +static int pc87108_probe( int iobase, int board_addr, int irq, int dma); +static void pc87108_pio_receive( struct irda_device *idev); +static int pc87108_dma_receive( struct irda_device *idev); +static int pc87108_dma_receive_complete(struct irda_device *idev, int iobase); +static int pc87108_hard_xmit( struct sk_buff *skb, struct device *dev); +static int pc87108_pio_write( int iobase, __u8 *buf, int len, int fifo_size); +static void pc87108_dma_write( struct irda_device *idev, int iobase); +static void pc87108_change_speed( struct irda_device *idev, int baud); +static void pc87108_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void pc87108_wait_until_sent( struct irda_device *idev); +static int pc87108_is_receiving( struct irda_device *idev); +static int pc87108_read_dongle_id ( int iobase); +static void pc87108_init_dongle_interface ( int iobase, int dongle_id); + +static int pc87108_net_init( struct device *dev); +static int pc87108_net_open( struct device *dev); +static int pc87108_net_close( struct device *dev); + +/* + * Function pc87108_init () + * + * Initialize chip. Just try to find out how many chips we are dealing with + * and where they are + */ +__initfunc(int pc87108_init(void)) +{ + int i; + + for ( i=0; (io[i] < 2000) && (i < 4); i++) { + int ioaddr = io[i]; + if (check_region(ioaddr, CHIP_IO_EXTENT)) + continue; + if (pc87108_open( i, io[i], io2[i], irq[i], dma[i]) == 0) + return 0; + } + return -ENODEV; +} + +/* + * Function pc87108_cleanup () + * + * Close all configured chips + * + */ +#ifdef MODULE +static void pc87108_cleanup(void) +{ + int i; + + DEBUG( 4, __FUNCTION__ "()\n"); + + for ( i=0; i < 4; i++) { + if ( dev_self[i]) + pc87108_close( &(dev_self[i]->idev)); + } +} +#endif /* MODULE */ + +/* + * Function pc87108_open (iobase, irq) + * + * Open driver instance + * + */ +static int pc87108_open( int i, unsigned int iobase, unsigned int board_addr, + unsigned int irq, unsigned int dma) +{ + struct pc87108 *self; + struct irda_device *idev; + int ret; + int dongle_id; + + DEBUG( 0, __FUNCTION__ "()\n"); + + if (( dongle_id = pc87108_probe( iobase, board_addr, irq, dma)) == -1) + return -1; + + /* + * Allocate new instance of the driver + */ + self = kmalloc( sizeof(struct pc87108), GFP_KERNEL); + if ( self == NULL) { + printk( KERN_ERR "IrDA: Can't allocate memory for " + "IrDA control block!\n"); + return -ENOMEM; + } + memset( self, 0, sizeof(struct pc87108)); + + /* Need to store self somewhere */ + dev_self[i] = self; + + idev = &self->idev; + + /* Initialize IO */ + idev->io.iobase = iobase; + idev->io.irq = irq; + idev->io.io_ext = CHIP_IO_EXTENT; + idev->io.dma = dma; + idev->io.fifo_size = 32; + + /* Lock the port that we need */ + ret = check_region( idev->io.iobase, idev->io.io_ext); + if ( ret < 0) { + DEBUG( 0, __FUNCTION__ "(), can't get iobase of 0x%03x\n", + idev->io.iobase); + /* pc87108_cleanup( self->idev); */ + return -ENODEV; + } + request_region( idev->io.iobase, idev->io.io_ext, idev->name); + + /* Initialize QoS for this device */ + irda_init_max_qos_capabilies( &idev->qos); + + /* The only value we must override it the baudrate */ + idev->qos.baud_rate.bits = IR_9600|IR_19200|IR_38400|IR_57600| + IR_115200|IR_576000|IR_1152000|(IR_4000000 << 8); + + idev->qos.min_turn_time.bits = 0x07; + irda_qos_bits_to_value( &idev->qos); + + /* Specify which buffer allocation policy we need */ + idev->rx_buff.flags = GFP_KERNEL | GFP_DMA; + idev->tx_buff.flags = GFP_KERNEL | GFP_DMA; + + /* Max DMA buffer size needed = (data_size + 6) * (window_size) + 6; */ + idev->rx_buff.truesize = 14384; + idev->tx_buff.truesize = 4000; + + /* Initialize callbacks */ + idev->hard_xmit = pc87108_hard_xmit; + idev->change_speed = pc87108_change_speed; + idev->wait_until_sent = pc87108_wait_until_sent; + idev->is_receiving = pc87108_is_receiving; + + /* Override the network functions we need to use */ + idev->netdev.init = pc87108_net_init; + idev->netdev.hard_start_xmit = pc87108_hard_xmit; + idev->netdev.open = pc87108_net_open; + idev->netdev.stop = pc87108_net_close; + + idev->io.dongle_id = dongle_id; + pc87108_init_dongle_interface( iobase, dongle_id); + + /* Open the IrDA device */ + irda_device_open( idev, driver_name, self); + + return 0; +} + +/* + * Function pc87108_close (idev) + * + * Close driver instance + * + */ +static int pc87108_close( struct irda_device *idev) +{ + int iobase; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return -1;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + iobase = idev->io.iobase; + + /* Release the PORT that this driver is using */ + DEBUG( 4, __FUNCTION__ "(), Releasing Region %03x\n", + idev->io.iobase); + release_region( idev->io.iobase, idev->io.io_ext); + + irda_device_close( idev); + + return 0; +} + +/* + * Function pc87108_probe (iobase, board_addr, irq, dma) + * + * Returns non-negative on success. + * + */ +static int pc87108_probe( int iobase, int board_addr, int irq, int dma) +{ + int version; + __u8 temp=0; + int dongle_id; + + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Base Address and Interrupt Control Register BAIC */ + outb(0, board_addr); + switch ( iobase) { + case 0x3E8: outb( 0x14, board_addr+1); break; + case 0x2E8: outb( 0x15, board_addr+1); break; + case 0x3F8: outb( 0x16, board_addr+1); break; + case 0x2F8: outb( 0x17, board_addr+1); break; + default: DEBUG(0, __FUNCTION__ "(), invalid base_address"); + } + + /* Control Signal Routing Register CSRT */ + switch (irq) { + case 3: temp = 0x01; break; + case 4: temp = 0x02; break; + case 5: temp = 0x03; break; + case 7: temp = 0x04; break; + case 9: temp = 0x05; break; + case 11: temp = 0x06; break; + case 15: temp = 0x07; break; + default: DEBUG( 0, __FUNCTION__ "(), invalid irq"); + } + outb( 1, board_addr); + + switch (dma) { + case 0: outb( 0x08+temp, board_addr+1); break; + case 1: outb( 0x10+temp, board_addr+1); break; + case 3: outb( 0x18+temp, board_addr+1); break; + default: DEBUG( 0, __FUNCTION__ "(), invalid dma"); + } + + /* Mode Control Register MCTL */ + outb( 2, board_addr); + outb( 0x03, board_addr+1); + + /* read the Module ID */ + switch_bank( iobase, BANK3); + version = inb( iobase+MID); + + /* should be 0x2? */ + if (0x20 != (version & 0xf0)) + { + DEBUG( 0, __FUNCTION__ "(), Wrong chip version"); + return -1; + } + + /* Switch to advanced mode */ + switch_bank( iobase, BANK2); + outb( ECR1_EXT_SL, iobase+ECR1); + switch_bank( iobase, BANK0); + + dongle_id = pc87108_read_dongle_id( iobase); + DEBUG( 0, __FUNCTION__ "(), Found dongle: %s\n", + dongle_types[ dongle_id]); + + /* Set FIFO threshold to TX17, RX16, reset and enable FIFO's */ + switch_bank( iobase, BANK0); + outb( FCR_RXTH|FCR_TXTH|FCR_TXSR|FCR_RXSR|FCR_FIFO_EN, iobase+FCR); + + /* Set FIFO size to 32 */ + switch_bank( iobase, BANK2); + outb( EXCR2_RFSIZ|EXCR2_TFSIZ, iobase+EXCR2); + + /* IRCR2: FEND_MD is set */ + switch_bank( iobase, BANK5); + outb( 0x2a, iobase+4); + + /* Make sure that some defaults are OK */ + switch_bank( iobase, BANK6); + outb( 0x20, iobase+0); /* Set 32 bits FIR CRC */ + outb( 0x0a, iobase+1); /* Set MIR pulse width */ + outb( 0x0d, iobase+2); /* Set SIR pulse width */ + outb( 0x2a, iobase+4); /* Set beginning frag, and preamble length */ + + /* Receiver frame length */ + switch_bank( iobase, BANK4); + outb( 2048 & 0xff, iobase+6); + outb(( 2048 >> 8) & 0x1f, iobase+7); + + /* Transmitter frame length */ + outb( 2048 & 0xff, iobase+4); + outb(( 2048 >> 8) & 0x1f, iobase+5); + + DEBUG( 0, "PC87108 driver loaded. Version: 0x%02x\n", version); + + /* Enable receive interrupts */ + switch_bank( iobase, BANK0); + outb( IER_RXHDL_IE, iobase+IER); + + return dongle_id; +} + +/* + * Function pc87108_read_dongle_id (void) + * + * Try to read dongle indentification. This procedure needs to be executed + * once after power-on/reset. It also needs to be used whenever you suspect + * that the user may have plugged/unplugged the IrDA Dongle. + * + */ +static int pc87108_read_dongle_id ( int iobase) +{ + int dongle_id; + __u8 bank; + + DEBUG( 4, __FUNCTION__ "()\n"); + + bank = inb( iobase+BSR); + + /* Select Bank 7 */ + switch_bank( iobase, BANK7); + + /* IRCFG4: IRSL0_DS and IRSL21_DS are cleared */ + outb( 0x00, iobase+7); + + /* ID0, 1, and 2 are pulled up/down very slowly */ + udelay(50); + + /* IRCFG1: read the ID bits */ + dongle_id = inb( iobase+4) & 0x0f; + +#ifdef BROKEN_DONGLE_ID + if ( dongle_id == 0x0a) + dongle_id = 0x09; +#endif + + /* Go back to bank 0 before returning */ + switch_bank( iobase, BANK0); + + DEBUG( 0, __FUNCTION__ "(), Dongle = %#x\n", dongle_id); + + outb( bank, iobase+BSR); + + return dongle_id; +} + +/* + * Function pc87108_init_dongle_interface (iobase, dongle_id) + * + * This function initializes the dongle for the transceiver that is + * used. This procedure needs to be executed once after + * power-on/reset. It also needs to be used whenever you suspect that + * the dongle is changed. + */ +static void pc87108_init_dongle_interface ( int iobase, int dongle_id) +{ + int bank; + + /* Save current bank */ + bank = inb( iobase+BSR); + + /* Select Bank 7 */ + switch_bank( iobase, BANK7); + + /* IRCFG4: set according to dongle_id */ + switch (dongle_id) { + case 0x00: /* same as */ + case 0x01: /* Differential serial interface */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet\n", + dongle_types[dongle_id]); + break; + case 0x02: /* same as */ + case 0x03: /* Reserved */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet\n", + dongle_types[dongle_id]); + break; + case 0x04: /* Sharp RY5HD01 */ + DEBUG( 0, __FUNCTION__ "(), %s not supported yet\n", + dongle_types[dongle_id]); + break; + case 0x05: /* Reserved */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet", + dongle_types[dongle_id]); + break; + case 0x06: /* Single-ended serial interface */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet\n", + dongle_types[dongle_id]); + break; + case 0x07: /* Consumer-IR only */ + DEBUG( 0, __FUNCTION__ "(), %s is not for IrDA mode\n", + dongle_types[dongle_id]); + break; + case 0x08: /* HP HSDL-2300, HP HSDL-3600/HSDL-3610 */ + DEBUG( 0, __FUNCTION__ "(), %s not supported yet\n", + dongle_types[dongle_id]); + break; + case 0x09: /* IBM31T1100 or Temic TFDS6000/TFDS6500 */ + outb_p( 0x28, iobase+7); /* Set irsl[0-2] as output */ + break; + case 0x0A: /* same as */ + case 0x0B: /* Reserved */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet\n", + dongle_types[dongle_id]); + break; + case 0x0C: /* same as */ + case 0x0D: /* HP HSDL-1100/HSDL-2100 */ + /* + * Set irsl0 as input, irsl[1-2] as output, and separate + * inputs are used for SIR and MIR/FIR + */ + outb( 0x48, iobase+7); + break; + case 0x0E: /* Supports SIR Mode only */ + outb( 0x28, iobase+7); /* Set irsl[0-2] as output */ + break; + case 0x0F: /* No dongle connected */ + DEBUG( 0, __FUNCTION__ "(), %s\n", + dongle_types[dongle_id]); + DEBUG( 0, "***\n"); + + switch_bank( iobase, BANK0); + outb( 0x62, iobase+MCR); + break; + default: + DEBUG( 0, __FUNCTION__ "(), invalid dongle_id %#x", dongle_id); + } + + /* IRCFG1: IRSL1 and 2 are set to IrDA mode */ + outb( 0x00, iobase+4); + + /* Restore bank register */ + outb( bank, iobase+BSR); + +} /* set_up_dongle_interface */ + +/* + * Function pc87108_change_dongle_speed (iobase, speed, dongle_id) + * + * Change speed of the attach dongle + * + */ +static void pc87108_change_dongle_speed( int iobase, int speed, int dongle_id) +{ + unsigned long flags; + __u8 bank; + + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Save current bank */ + bank = inb( iobase+BSR); + + /* Select Bank 7 */ + switch_bank( iobase, BANK7); + + /* IRCFG1: set according to dongle_id */ + switch (dongle_id) { + case 0x00: /* same as */ + case 0x01: /* Differential serial interface */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet\n", + dongle_types[dongle_id]); + break; + case 0x02: /* same as */ + case 0x03: /* Reserved */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet\n", + dongle_types[dongle_id]); + break; + case 0x04: /* Sharp RY5HD01 */ + DEBUG( 0, __FUNCTION__ "(), %s not supported yet\n", + dongle_types[dongle_id]); + case 0x05: /* Reserved */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet\n", + dongle_types[dongle_id]); + break; + case 0x06: /* Single-ended serial interface */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet\n", + dongle_types[dongle_id]); + break; + case 0x07: /* Consumer-IR only */ + DEBUG( 0, __FUNCTION__ "(), %s is not for IrDA mode\n", + dongle_types[dongle_id]); + break; + case 0x08: /* HP HSDL-2300, HP HSDL-3600/HSDL-3610 */ + DEBUG( 0, __FUNCTION__ "(), %s not supported yet\n", + dongle_types[dongle_id]); + case 0x09: /* IBM31T1100 or Temic TFDS6000/TFDS6500 */ + switch_bank( iobase, BANK7); + outb_p( 0x01, iobase+4); + + if ( speed == 4000000) { + save_flags(flags); + cli(); + outb( 0x81, iobase+4); + outb( 0x80, iobase+4); + restore_flags(flags); + } + else + outb_p( 0x00, iobase+4); + break; + case 0x0A: /* same as */ + case 0x0B: /* Reserved */ + DEBUG( 0, __FUNCTION__ "(), %s not defined by irda yet\n", + dongle_types[dongle_id]); + break; + case 0x0C: /* same as */ + case 0x0D: /* HP HSDL-1100/HSDL-2100 */ + break; + case 0x0E: /* Supports SIR Mode only */ + break; + case 0x0F: /* No dongle connected */ + DEBUG( 0, __FUNCTION__ "(), %s is not for IrDA mode\n", + dongle_types[dongle_id]); + + switch_bank( iobase, BANK0); + outb( 0x62, iobase+MCR); + break; + default: + DEBUG( 0, __FUNCTION__ "(), invalid data_rate\n"); + } + /* Restore bank register */ + outb( bank, iobase+BSR); +} + +/* + * Function pc87108_change_speed (idev, baud) + * + * Change the speed of the device + * + */ +static void pc87108_change_speed( struct irda_device *idev, int speed) +{ + __u8 mcr = MCR_SIR; + __u8 bank; + int iobase; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + iobase = idev->io.iobase; + + /* Update accounting for new speed */ + idev->io.baudrate = speed; + + /* Save current bank */ + bank = inb( iobase+BSR); + + /* Disable interrupts */ + switch_bank( iobase, BANK0); + outb( 0, iobase+IER); + + /* Select Bank 2 */ + switch_bank( iobase, BANK2); + + outb( 0x00, iobase+BGDH); + switch ( speed) { + case 9600: outb( 0x0c, iobase+BGDL); break; + case 19200: outb( 0x06, iobase+BGDL); break; + case 37600: outb( 0x03, iobase+BGDL); break; + case 57600: outb( 0x02, iobase+BGDL); break; + case 115200: outb( 0x01, iobase+BGDL); break; + case 576000: + switch_bank( iobase, BANK5); + + /* IRCR2: MDRS is set */ + outb( inb( iobase+4) | 0x04, iobase+4); + + mcr = MCR_MIR; + DEBUG(0, __FUNCTION__ "(), handling baud of 576000\n"); + break; + case 1152000: + mcr = MCR_MIR; + DEBUG(0, __FUNCTION__ "(), handling baud of 1152000\n"); + break; + case 4000000: + mcr = MCR_FIR; + DEBUG(0, __FUNCTION__ "(), handling baud of 4000000\n"); + break; + default: + mcr = MCR_FIR; + DEBUG( 0, __FUNCTION__ "(), unknown baud rate of %d\n", speed); + break; + } + + /* Set appropriate speed mode */ + switch_bank(iobase, BANK0); + outb( mcr|MCR_TX_DFR, iobase+MCR); + + /* Give some hits to the transceiver */ + pc87108_change_dongle_speed( iobase, speed, idev->io.dongle_id); + + /* Set FIFO threshold to TX17, RX16 */ + switch_bank( iobase, BANK0); + outb( FCR_RXTH| /* Set Rx FIFO threshold */ + FCR_TXTH| /* Set Tx FIFO threshold */ + FCR_TXSR| /* Reset Tx FIFO */ + FCR_RXSR| /* Reset Rx FIFO */ + FCR_FIFO_EN, /* Enable FIFOs */ + iobase+FCR); + /* outb( 0xa7, iobase+FCR); */ + + /* Set FIFO size to 32 */ + switch_bank( iobase, BANK2); + outb( EXCR2_RFSIZ|EXCR2_TFSIZ, iobase+EXCR2); + + idev->netdev.tbusy = 0; + + /* Enable some interrupts so we can receive frames */ + switch_bank( iobase, BANK0); + if ( speed > 115200) { + outb( IER_SFIF_IE, iobase+IER); + pc87108_dma_receive( idev); + } else + outb( IER_RXHDL_IE, iobase+IER); + + /* Restore BSR */ + outb( bank, iobase+BSR); +} + +/* + * Function pc87108_hard_xmit (skb, dev) + * + * Transmit the frame! + * + */ +static int pc87108_hard_xmit( struct sk_buff *skb, struct device *dev) +{ + struct irda_device *idev; + int iobase; + __u8 bank; + int mtt; + + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + DEBUG(4, __FUNCTION__ "(%ld), skb->len=%d\n", jiffies, (int) skb->len); + + if ( dev->tbusy) { + DEBUG( 4, __FUNCTION__ "(), tbusy==TRUE\n"); + + return -EBUSY; + } + + /* Lock transmit buffer */ + if ( irda_lock( (void *) &dev->tbusy) == FALSE) + return -EBUSY; + + /* Save current bank */ + bank = inb( iobase+BSR); + + /* Decide if we should use PIO or DMA transfer */ + if ( idev->io.baudrate > 115200) { + memcpy( idev->tx_buff.data, skb->data, skb->len); + idev->tx_buff.len = skb->len; + idev->tx_buff.head = idev->tx_buff.data; + idev->tx_buff.offset = 0; + + mtt = irda_get_mtt( skb); + if ( mtt > 50) { + /* Adjust for timer resolution */ + mtt = mtt / 125 + 1; + + /* Setup timer */ + switch_bank( iobase, BANK4); + outb( mtt & 0xff, iobase+TMRL); + outb(( mtt >> 8) & 0x0f, iobase+TMRH); + + /* Start timer */ + outb( IRCR1_TMR_EN, iobase+IRCR1); + idev->io.direction = IO_XMIT; + + /* Enable timer interrupt */ + switch_bank( iobase, BANK0); + outb( IER_TMR_IE, iobase+IER); + } else { + /* Use udelay for delays less than 50 us. */ + if (mtt) + udelay( mtt); + + /* Enable DMA interrupt */ + switch_bank( iobase, BANK0); + outb( IER_DMA_IE, iobase+IER); + pc87108_dma_write( idev, iobase); + } + } else { + idev->tx_buff.len = async_wrap_skb( skb, idev->tx_buff.data, + idev->tx_buff.truesize); + + idev->tx_buff.offset = 0; + idev->tx_buff.head = idev->tx_buff.data; + + /* Add interrupt on tx low level (will fire immediately) */ + switch_bank( iobase, BANK0); + outb( IER_TXLDL_IE, iobase+IER); + } + dev_kfree_skb( skb); + + /* Restore bank register */ + outb( bank, iobase+BSR); + + return 0; +} + +/* + * Function pc87108_dma_xmit (idev, iobase) + * + * Transmit data using DMA + * + */ +static void pc87108_dma_write( struct irda_device *idev, int iobase) +{ + int bsr; + + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Save current bank */ + bsr = inb( iobase+BSR); + + /* Disable DMA */ + switch_bank(iobase, BANK0); + outb( inb( iobase+MCR) & ~MCR_DMA_EN, iobase+MCR); + + setup_dma( idev->io.dma, idev->tx_buff.data, idev->tx_buff.len, + DMA_MODE_WRITE); + + /* idev->media_busy = TRUE; */ + idev->io.direction = IO_XMIT; + + /* Choose transmit DMA channel */ + switch_bank(iobase, BANK2); + outb( inb( iobase+ECR1) | ECR1_DMASWP|ECR1_DMANF|ECR1_EXT_SL, + iobase+ECR1); + + /* Enable DMA */ + switch_bank( iobase, BANK0); + outb( inb( iobase+MCR)|MCR_DMA_EN, iobase+MCR); + + /* Restore bank register */ + outb( bsr, iobase+BSR); +} + +/* + * Function pc87108_pio_xmit (idev, iobase) + * + * Transmit data using PIO. Returns the number of bytes that actually + * got transfered + * + */ +static int pc87108_pio_write( int iobase, __u8 *buf, int len, int fifo_size) +{ + int actual = 0; + __u8 bank; + + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Save current bank */ + bank = inb( iobase+BSR); + + switch_bank( iobase, BANK0); + if (!(inb_p( iobase+LSR) & LSR_TXEMP)) { + DEBUG( 4, __FUNCTION__ "(), warning, FIFO not empty yet!\n"); + + fifo_size -= 17; + DEBUG( 4, __FUNCTION__ "%d bytes left in tx fifo\n", fifo_size); + } + + /* Fill FIFO with current frame */ + while (( fifo_size-- > 0) && (actual < len)) { + /* Transmit next byte */ + outb( buf[actual++], iobase+TXD); + } + + DEBUG( 4, __FUNCTION__ "(), fifo_size %d ; %d sent of %d\n", + fifo_size, actual, len); + + /* Restore bank */ + outb( bank, iobase+BSR); + + return actual; +} + +/* + * Function pc87108_dma_xmit_complete (idev) + * + * The transfer of a frame in finished. This function will only be called + * by the interrupt handler + * + */ +static void pc87108_dma_xmit_complete( struct irda_device *idev) +{ + int iobase; + __u8 bank; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + iobase = idev->io.iobase; + + /* Save current bank */ + bank = inb( iobase+BSR); + + /* Disable DMA */ + switch_bank( iobase, BANK0); + outb( inb(iobase+MCR) & ~MCR_DMA_EN, iobase+MCR); + + /* Check for underrrun! */ + if ( inb( iobase+ASCR) & ASCR_TXUR) { + idev->stats.tx_errors++; + idev->stats.tx_fifo_errors++; + + /* Clear bit, by writing 1 into it */ + outb( ASCR_TXUR, iobase+ASCR); + } else { + idev->stats.tx_packets++; + idev->stats.tx_bytes += idev->tx_buff.len; + } + + /* Unlock tx_buff and request another frame */ + idev->netdev.tbusy = 0; /* Unlock */ + idev->media_busy = FALSE; + + /* Tell the network layer, that we can accept more frames */ + mark_bh( NET_BH); + + /* Restore bank */ + outb( bank, iobase+BSR); +} + +/* + * Function pc87108_dma_receive (idev) + * + * Get ready for receiving a frame. The device will initiate a DMA + * if it starts to receive a frame. + * + */ +static int pc87108_dma_receive( struct irda_device *idev) +{ + struct pc87108 *self; + int iobase; + __u8 bsr; + + ASSERT( idev != NULL, return -1;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + DEBUG( 4, __FUNCTION__ "\n"); + + self = idev->priv; + iobase= idev->io.iobase; + + /* Save current bank */ + bsr = inb( iobase+BSR); + + /* Disable DMA */ + switch_bank( iobase, BANK0); + outb( inb(iobase+MCR) & ~MCR_DMA_EN, iobase+MCR); + + setup_dma( idev->io.dma, idev->rx_buff.data, idev->rx_buff.truesize, + DMA_MODE_READ); + + /* driver->media_busy = FALSE; */ + idev->io.direction = IO_RECV; + idev->rx_buff.head = idev->rx_buff.data; + idev->rx_buff.offset = 0; + + /* Reset Rx FIFO. This will also flush the ST_FIFO */ + outb( FCR_RXTH|FCR_TXTH|FCR_RXSR|FCR_FIFO_EN, iobase+FCR); + self->st_fifo.len = self->st_fifo.tail = self->st_fifo.head = 0; + + /* Choose DMA Rx, DMA Fairness, and Advanced mode */ + switch_bank(iobase, BANK2); + outb(( inb( iobase+ECR1) & ~ECR1_DMASWP)|ECR1_DMANF|ECR1_EXT_SL, + iobase+ECR1); + + /* enable DMA */ + switch_bank(iobase, BANK0); + outb( inb( iobase+MCR)|MCR_DMA_EN, iobase+MCR); + + /* Restore bank register */ + outb( bsr, iobase+BSR); + + DEBUG( 4, __FUNCTION__ "(), done!\n"); + + return 0; +} + +/* + * Function pc87108_dma_receive_complete (idev) + * + * Finished with receiving frames + * + * + */ +static int pc87108_dma_receive_complete( struct irda_device *idev, int iobase) +{ + struct sk_buff *skb; + struct pc87108 *self; + struct st_fifo *st_fifo; + int len; + __u8 bank; + __u8 status; + + self = idev->priv; + st_fifo = &self->st_fifo; + + /* Save current bank */ + bank = inb( iobase+BSR); + + iobase = idev->io.iobase; + + /* Read status FIFO */ + switch_bank(iobase, BANK5); + while (( status = inb( iobase+FRM_ST)) & FRM_ST_VLD) { + st_fifo->entries[ st_fifo->tail].status = status; + + st_fifo->entries[ st_fifo->tail].len = inb(iobase+RFLFL); + st_fifo->entries[ st_fifo->tail].len |= inb(iobase+RFLFH) << 8; + + st_fifo->tail++; + st_fifo->len++; + } + + /* Try to process all entries in status FIFO */ + switch_bank( iobase, BANK0); + while ( st_fifo->len) { + + /* Get first entry */ + status = st_fifo->entries[ st_fifo->head].status; + len = st_fifo->entries[ st_fifo->head].len; + st_fifo->head++; + st_fifo->len--; + + /* Check for errors */ + if ( status & FRM_ST_ERR_MSK) { + if ( status & FRM_ST_LOST_FR) { + /* Add number of lost frames to stats */ + idev->stats.rx_errors += len; + } else { + /* Skip frame */ + idev->stats.rx_errors++; + + idev->rx_buff.offset += len; + idev->rx_buff.head += len; + + if ( status & FRM_ST_MAX_LEN) + idev->stats.rx_length_errors++; + + if ( status & FRM_ST_PHY_ERR) + idev->stats.rx_frame_errors++; + + if ( status & FRM_ST_BAD_CRC) + idev->stats.rx_crc_errors++; + } + /* The errors below can be reported in both cases */ + if ( status & FRM_ST_OVR1) + idev->stats.rx_fifo_errors++; + + if ( status & FRM_ST_OVR2) + idev->stats.rx_fifo_errors++; + + } else { + /* Check if we have transfered all data to memory */ + if ( inb( iobase+LSR) & LSR_RXDA) { + /* Put this entry back in fifo */ + st_fifo->head--; + st_fifo->len++; + st_fifo->entries[st_fifo->head].status = status; + st_fifo->entries[ st_fifo->head].len = len; + + /* Restore bank register */ + outb( bank, iobase+BSR); + + return FALSE; /* I'll be back! */ + } + + /* Should be OK then */ + skb = dev_alloc_skb( len+1); + if (skb == NULL) { + printk( KERN_INFO __FUNCTION__ + "(), memory squeeze, dropping frame.\n"); + /* Restore bank register */ + outb( bank, iobase+BSR); + + return FALSE; + } + + /* Make sure IP header gets aligned */ + skb_reserve( skb, 1); + + /* Copy frame without CRC */ + if ( idev->io.baudrate < 4000000) { + skb_put( skb, len-2); + memcpy( skb->data, idev->rx_buff.head, len-2); + } else { + skb_put( skb, len-4); + memcpy( skb->data, idev->rx_buff.head, len-4); + } + + /* Move to next frame */ + idev->rx_buff.offset += len; + idev->rx_buff.head += len; + idev->stats.rx_packets++; + + skb->dev = &idev->netdev; + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IRDA); + netif_rx( skb); + } + } + /* Restore bank register */ + outb( bank, iobase+BSR); + + return TRUE; +} + +/* + * Function pc87108_pio_receive (idev) + * + * Receive all data in receiver FIFO + * + */ +static void pc87108_pio_receive( struct irda_device *idev) +{ + __u8 byte = 0x00; + int iobase; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + iobase = idev->io.iobase; + + if ( idev->rx_buff.len == 0) { + idev->rx_buff.head = idev->rx_buff.data; + } + + /* Receive all characters in Rx FIFO */ + do { + byte = inb( iobase+RXD); + async_unwrap_char( idev, byte); + + } while ( inb( iobase+LSR) & LSR_RXDA); /* Data available */ +} + +/* + * Function pc87108_sir_interrupt (idev, eir) + * + * Handle SIR interrupt + * + */ +static __u8 pc87108_sir_interrupt( struct irda_device *idev, int eir) +{ + int len; + int actual; + __u8 new_ier = 0; + + /* Transmit FIFO low on data */ + if ( eir & EIR_TXLDL_EV) { + /* Write data left in transmit buffer */ + len = idev->tx_buff.len - idev->tx_buff.offset; + + ASSERT( len > 0, return 0;); + actual = pc87108_pio_write( idev->io.iobase, + idev->tx_buff.head, + len, idev->io.fifo_size); + idev->tx_buff.offset += actual; + idev->tx_buff.head += actual; + + idev->io.direction = IO_XMIT; + ASSERT( actual <= len, return 0;); + + /* Check if finished */ + if ( actual == len) { + DEBUG( 4, __FUNCTION__ "(), finished with frame!\n"); + idev->netdev.tbusy = 0; /* Unlock */ + idev->stats.tx_packets++; + + mark_bh(NET_BH); + + new_ier |= IER_TXEMP_IE; + } else + new_ier |= IER_TXLDL_IE; + } + /* Check if transmission has completed */ + if ( eir & EIR_TXEMP_EV) { + + /* Turn around and get ready to receive some data */ + idev->io.direction = IO_RECV; + new_ier |= IER_RXHDL_IE; + } + + /* Rx FIFO threshold or timeout */ + if ( eir & EIR_RXHDL_EV) { + pc87108_pio_receive( idev); + + /* Keep receiving */ + new_ier |= IER_RXHDL_IE; + } + return new_ier; +} + +/* + * Function pc87108_fir_interrupt (idev, eir) + * + * Handle MIR/FIR interrupt + * + */ +static __u8 pc87108_fir_interrupt( struct irda_device *idev, int iobase, + int eir) +{ + __u8 new_ier = 0; + __u8 bank; + + bank = inb( iobase+BSR); + + /* Status event, or end of frame detected in FIFO */ + if ( eir & (EIR_SFIF_EV|EIR_LS_EV)) { + if ( pc87108_dma_receive_complete( idev, iobase)) { + + /* Wait for next status FIFO interrupt */ + new_ier |= IER_SFIF_IE; + } else { + /* DMA not finished yet */ + + /* Set timer value, resolution 125 us */ + switch_bank( iobase, BANK4); + outb( 0x0f, iobase+TMRL); /* 125 us */ + outb( 0x00, iobase+TMRH); + + /* Start timer */ + outb( IRCR1_TMR_EN, iobase+IRCR1); + + new_ier |= IER_TMR_IE; + } + } + /* Timer finished */ + if ( eir & EIR_TMR_EV) { + /* Disable timer */ + switch_bank( iobase, BANK4); + outb( 0, iobase+IRCR1); + + /* Clear timer event */ + switch_bank(iobase, BANK0); + outb( ASCR_CTE, iobase+ASCR); + + /* Check if this is a TX timer interrupt */ + if ( idev->io.direction == IO_XMIT) { + pc87108_dma_write( idev, iobase); + + /* Interrupt on DMA */ + new_ier |= IER_DMA_IE; + } else { + /* Check if DMA has now finished */ + pc87108_dma_receive_complete( idev, iobase); + + new_ier |= IER_SFIF_IE; + } + } + /* Finished with transmission */ + if ( eir & EIR_DMA_EV) { + pc87108_dma_xmit_complete( idev); + + /* Check if there are more frames to be transmitted */ + if ( irda_device_txqueue_empty( idev)) { + /* Prepare for receive */ + pc87108_dma_receive( idev); + + new_ier = IER_LS_IE|IER_SFIF_IE; + } + } + outb( bank, iobase+BSR); + + return new_ier; +} + +/* + * Function pc87108_interrupt (irq, dev_id, regs) + * + * An interrupt from the chip has arrived. Time to do some work + * + */ +static void pc87108_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + __u8 bsr, eir, ier; + int iobase; + + struct irda_device *idev = (struct irda_device *) dev_id; + + if (idev == NULL) { + printk( KERN_WARNING "%s: irq %d for unknown device.\n", + driver_name, irq); + return; + } + + idev->netdev.interrupt = 1; + + iobase = idev->io.iobase; + + /* Save current bank */ + bsr = inb( iobase+BSR); + + switch_bank( iobase, BANK0); + ier = inb( iobase+IER); + eir = inb( iobase+EIR) & ier; /* Mask out the interesting ones */ + + outb( 0, iobase+IER); /* Disable interrupts */ + + if ( eir) { + /* Dispatch interrupt handler for the current speed */ + if ( idev->io.baudrate > 115200) + ier = pc87108_fir_interrupt( idev, iobase, eir); + else + ier = pc87108_sir_interrupt( idev, eir); + } + + outb( ier, iobase+IER); /* Restore interrupts */ + outb( bsr, iobase+BSR); /* Restore bank register */ + + idev->netdev.interrupt = 0; +} + +/* + * Function pc87108_wait_until_sent (idev) + * + * This function should put the current thread to sleep until all data + * have been sent, so it is safe to f.eks. change the speed. + */ +static void pc87108_wait_until_sent( struct irda_device *idev) +{ + /* Just delay 60 ms */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(6); +} + +/* + * Function pc87108_is_receiving (idev) + * + * Return TRUE is we are currently receiving a frame + * + */ +static int pc87108_is_receiving( struct irda_device *idev) +{ + int status = FALSE; + int iobase; + __u8 bank; + + ASSERT( idev != NULL, return FALSE;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return FALSE;); + + if ( idev->io.baudrate > 115200) { + iobase = idev->io.iobase; + + /* Check if rx FIFO is not empty */ + bank = inb( iobase+BSR); + switch_bank( iobase, BANK2); + if (( inb( iobase+RXFLV) & 0x3f) != 0) { + /* We are receiving something */ + status = TRUE; + } + outb( bank, iobase+BSR); + } else + status = ( idev->rx_buff.state != OUTSIDE_FRAME); + + return status; +} + +/* + * Function pc87108_net_init (dev) + * + * Initialize network device + * + */ +static int pc87108_net_init( struct device *dev) +{ + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Setup to be a normal IrDA network device driver */ + irda_device_setup( dev); + + /* Insert overrides below this line! */ + + return 0; +} + + +/* + * Function pc87108_net_open (dev) + * + * Start the device + * + */ +static int pc87108_net_open( struct device *dev) +{ + struct irda_device *idev; + int iobase; + __u8 bank; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + if (request_irq( idev->io.irq, pc87108_interrupt, 0, idev->name, + (void *) idev)) { + return -EAGAIN; + } + /* + * Always allocate the DMA channel after the IRQ, + * and clean up on failure. + */ + if (request_dma(idev->io.dma, idev->name)) { + free_irq( idev->io.irq, idev); + return -EAGAIN; + } + + /* Ready to play! */ + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + /* Save current bank */ + bank = inb( iobase+BSR); + + /* turn on interrupts */ + switch_bank( iobase, BANK0); + outb( IER_LS_IE | IER_RXHDL_IE, iobase+IER); + + /* Restore bank register */ + outb( bank, iobase+BSR); + + MOD_INC_USE_COUNT; + + return 0; +} + +/* + * Function pc87108_net_close (dev) + * + * Stop the device + * + */ +static int pc87108_net_close(struct device *dev) +{ + struct irda_device *idev; + int iobase; + __u8 bank; + + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Stop device */ + dev->tbusy = 1; + dev->start = 0; + + ASSERT( dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + disable_dma( idev->io.dma); + + /* Save current bank */ + bank = inb( iobase+BSR); + + /* Disable interrupts */ + switch_bank( iobase, BANK0); + outb( 0, iobase+IER); + + free_irq( idev->io.irq, idev); + free_dma( idev->io.dma); + + /* Restore bank register */ + outb( bank, iobase+BSR); + + MOD_DEC_USE_COUNT; + + return 0; +} + +#ifdef MODULE + +/* + * Function init_module (void) + * + * + * + */ +int init_module(void) +{ + pc87108_init(); + + return(0); +} + +/* + * Function cleanup_module (void) + * + * + * + */ +void cleanup_module(void) +{ + pc87108_cleanup(); +} + +#endif + diff --git a/drivers/net/irda/tekram.c b/drivers/net/irda/tekram.c new file mode 100644 index 000000000..bdada4afa --- /dev/null +++ b/drivers/net/irda/tekram.c @@ -0,0 +1,314 @@ +/********************************************************************* + * + * Filename: tekram.c + * Version: 0.4 + * Description: Implementation of the Tekram IrMate IR-210B dongle + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Wed Oct 21 20:02:35 1998 + * Modified at: Mon Jan 18 11:30:38 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998 Dag Brattli, All Rights Reserved. + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/tty.h> +#include <linux/sched.h> +#include <linux/init.h> + +#include <asm/ioctls.h> +#include <asm/segment.h> +#include <asm/uaccess.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irda_device.h> +#include <net/irda/irtty.h> +#include <net/irda/dongle.h> + +static void tekram_reset( struct irda_device *dev, int unused); +static void tekram_open( struct irda_device *dev, int type); +static void tekram_close( struct irda_device *dev); +static void tekram_change_speed( struct irda_device *dev, int baud); +static void tekram_init_qos( struct irda_device *idev, struct qos_info *qos); + +static struct dongle dongle = { + TEKRAM_DONGLE, + tekram_open, + tekram_close, + tekram_reset, + tekram_change_speed, + tekram_init_qos, +}; + +__initfunc(void tekram_init(void)) +{ + irtty_register_dongle( &dongle); +} + +void tekram_cleanup(void) +{ + irtty_unregister_dongle( &dongle); +} + +static void tekram_open( struct irda_device *dev, int type) +{ + strcat( dev->name, " <-> tekram"); + + MOD_INC_USE_COUNT; +} + +static void tekram_close( struct irda_device *dev) +{ + MOD_DEC_USE_COUNT; +} + +/* + * Function tekram_change_speed (tty, baud) + * + * Set the speed for the Tekram IRMate 210 type dongle. Warning, this + * function must be called with a process context! + * + * Algorithm + * 1. clear DTR + * 2. set RTS, and wait at least 7 us + * 3. send Control Byte to the IR-210 through TXD to set new baud rate + * wait until the stop bit of Control Byte is sent (for 9600 baud rate, + * it takes about 100 msec) + * 5. clear RTS (return to NORMAL Operation) + * 6. wait at least 50 us, new setting (baud rate, etc) takes effect here + * after + */ +static void tekram_change_speed( struct irda_device *dev, int baud) +{ + struct irtty_cb *self; + struct tty_struct *tty; + struct termios old_termios; + int arg = 0; + int cflag; + __u8 byte; + int actual; + mm_segment_t fs; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( dev != NULL, return;); + ASSERT( dev->magic == IRDA_DEVICE_MAGIC, return;); + + self = (struct irtty_cb *) dev->priv; + + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + if ( !self->tty) + return; + + tty = self->tty; + + old_termios = *(tty->termios); + cflag = tty->termios->c_cflag; + + cflag &= ~CBAUD; + + switch (baud) { + case 9600: + default: + cflag |= B9600; + byte = 4; + break; + case 19200: + cflag |= B19200; + byte = 3; + break; + case 34800: + cflag |= B38400; + byte = 2; + break; + case 57600: + cflag |= B57600; + byte = 1; + break; + case 115200: + cflag |= B115200; + byte = 0; + break; + } + + /* Set DTR, Clear RTS */ + DEBUG( 0, __FUNCTION__ "(), Setting DTR, Clearing RTS\n"); + arg = TIOCM_DTR | TIOCM_OUT2; + + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, + (unsigned long) &arg)) { + DEBUG( 0, "error setting Tekram speed!\n"); + } + set_fs(fs); + + /* Wait at least 7us */ + udelay( 7); + + DEBUG( 0, __FUNCTION__ "(), Writing control byte\n"); + /* Write control byte */ + if ( tty->driver.write) + actual = tty->driver.write( self->tty, 0, &byte, 1); + + /* Wait at least 100 ms */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout( 10); + + /* Set DTR, Set RTS */ + DEBUG( 0, __FUNCTION__ "(), Setting DTR, Setting RTS\n"); + arg = TIOCM_DTR | TIOCM_RTS | TIOCM_OUT2; + + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, + (unsigned long) &arg)) { + DEBUG( 0, "error setting Tekram speed!\n"); + } + set_fs(fs); + + DEBUG( 0, __FUNCTION__ "(), Setting new speed on serial port\n"); + /* Now change the speed of the serial port */ + tty->termios->c_cflag = cflag; + tty->driver.set_termios( tty, &old_termios); +} + +/* + * Function tekram_reset (driver) + * + * This function resets the tekram dongle. Warning, this function + * must be called with a process context!! + * + * Algorithm: + * 0. set RTS and DTR, and wait 50 ms + * ( power off the IR-210 ) + * 1. clear RTS + * 2. set DTR, and wait at least 1 ms + * 3. clear DTR to SPACE state, wait at least 50 us for further + * operation + */ +void tekram_reset( struct irda_device *dev, int unused) +{ + struct irtty_cb *self; + struct tty_struct *tty; + int arg = 0; + mm_segment_t fs; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( dev != NULL, return;); + ASSERT( dev->magic == IRDA_DEVICE_MAGIC, return;); + + self = (struct irtty_cb *) dev->priv; + + ASSERT( self != NULL, return;); + ASSERT( self->magic == IRTTY_MAGIC, return;); + + tty = self->tty; + if ( !tty) + return; + + DEBUG( 0, __FUNCTION__ "(), Power off dongle\n"); + arg = TIOCM_RTS | TIOCM_DTR | TIOCM_OUT2; + + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, + (unsigned long) &arg)) + { + DEBUG(0, "error setting ESI speed!\n"); + } + set_fs(fs); + + /* Sleep 50 ms */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(5); + + DEBUG( 0, __FUNCTION__ "(), Set DTR, clear RTS\n"); + /* Set DTR, clear RTS */ + arg = TIOCM_DTR | TIOCM_OUT2; + + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, + (unsigned long) &arg)) { + DEBUG( 0, "Error setting Tekram speed!\n"); + } + set_fs(fs); + + /* Should sleep 1 ms, but 10-20 should not do any harm */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(2); + + DEBUG( 0, __FUNCTION__ "(), STATE3\n"); + /* Clear DTR, clear RTS */ + arg = TIOCM_OUT2; + + fs = get_fs(); + set_fs( get_ds()); + + if ( tty->driver.ioctl( tty, NULL, TIOCMSET, (unsigned long) &arg)) { + DEBUG( 0, "error setting Tekram speed!\n"); + } + set_fs(fs); + + /* Finished! */ +} + +/* + * Function tekram_init_qos (qos) + * + * Initialize QoS capabilities + * + */ +static void tekram_init_qos( struct irda_device *idev, struct qos_info *qos) +{ + qos->baud_rate.bits &= IR_9600|IR_19200|IR_38400|IR_57600|IR_115200; + qos->min_turn_time.bits &= 0xfe; /* All except 0 ms */ +} + +#ifdef MODULE + +/* + * Function init_module (void) + * + * Initialize Tekram module + * + */ +int init_module(void) +{ + tekram_init(); + return(0); +} + +/* + * Function cleanup_module (void) + * + * Cleanup Tekram module + * + */ +void cleanup_module(void) +{ + tekram_cleanup(); +} + +#endif diff --git a/drivers/net/irda/uircc.c b/drivers/net/irda/uircc.c new file mode 100644 index 000000000..05da78a00 --- /dev/null +++ b/drivers/net/irda/uircc.c @@ -0,0 +1,914 @@ +/********************************************************************* + * + * Filename: uircc.c + * Version: 0.1 + * Description: Driver for the Sharp Universal Infrared + * Communications Controller (UIRCC) + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sat Dec 26 10:59:03 1998 + * Modified at: Tue Jan 19 23:54:04 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998 Dag Brattli, All Rights Reserved. + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + * Applicable Models : Tecra 510CDT, 500C Series, 530CDT, 520CDT, + * 740CDT, Portege 300CT, 660CDT, Satellite 220C Series, + * Satellite Pro, 440C Series, 470CDT, 460C Series, 480C Series + * + * Notice that FIR mode is not working yet, since I don't know + * how to make the UIRCC drive the interrupt line, and not the + * UART (which is used for SIR speeds). Please mail me if you know! + * + ********************************************************************/ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/malloc.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> + +#include <net/irda/wrapper.h> +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irlap_frame.h> +#include <net/irda/irda_device.h> + +#include <net/irda/uircc.h> +#include <net/irda/irport.h> + +static char *driver_name = "uircc"; + +#define CHIP_IO_EXTENT 16 + +static unsigned int io[] = { 0x300, ~0, ~0, ~0 }; +static unsigned int io2[] = { 0x3e8, 0, 0, 0}; +static unsigned int irq[] = { 11, 0, 0, 0 }; +static unsigned int dma[] = { 5, 0, 0, 0 }; + +static struct uircc_cb *dev_self[] = { NULL, NULL, NULL, NULL}; + +/* Some prototypes */ +static int uircc_open( int i, unsigned int iobase, unsigned int board_addr, + unsigned int irq, unsigned int dma); +static int uircc_close( struct irda_device *idev); +static int uircc_probe( int iobase, int board_addr, int irq, int dma); +static int uircc_dma_receive( struct irda_device *idev); +static int uircc_dma_receive_complete(struct irda_device *idev, int iobase); +static int uircc_hard_xmit( struct sk_buff *skb, struct device *dev); +static void uircc_dma_write( struct irda_device *idev, int iobase); +static void uircc_change_speed( struct irda_device *idev, int baud); +static void uircc_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void uircc_wait_until_sent( struct irda_device *idev); +static int uircc_is_receiving( struct irda_device *idev); + +static int uircc_net_init( struct device *dev); +static int uircc_net_open( struct device *dev); +static int uircc_net_close( struct device *dev); + +/* + * Function uircc_init () + * + * Initialize chip. Just try to find out how many chips we are dealing with + * and where they are + */ +__initfunc(int uircc_init(void)) +{ + int i; + + for ( i=0; (io[i] < 2000) && (i < 4); i++) { + int ioaddr = io[i]; + if (check_region(ioaddr, CHIP_IO_EXTENT)) + continue; + if (uircc_open( i, io[i], io2[i], irq[i], dma[i]) == 0) + return 0; + } + return -ENODEV; +} + +/* + * Function uircc_cleanup () + * + * Close all configured chips + * + */ +#ifdef MODULE +static void uircc_cleanup(void) +{ + int i; + + DEBUG( 4, __FUNCTION__ "()\n"); + + for ( i=0; i < 4; i++) { + if ( dev_self[i]) + uircc_close( &(dev_self[i]->idev)); + } +} +#endif /* MODULE */ + +/* + * Function uircc_open (iobase, irq) + * + * Open driver instance + * + */ +static int uircc_open( int i, unsigned int iobase, unsigned int iobase2, + unsigned int irq, unsigned int dma) +{ + struct uircc_cb *self; + struct irda_device *idev; + int ret; + + DEBUG( 0, __FUNCTION__ "()\n"); + + if (( uircc_probe( iobase, iobase2, irq, dma)) == -1) + return -1; + + /* + * Allocate new instance of the driver + */ + self = kmalloc( sizeof(struct uircc_cb), GFP_KERNEL); + if ( self == NULL) { + printk( KERN_ERR "IrDA: Can't allocate memory for " + "IrDA control block!\n"); + return -ENOMEM; + } + memset( self, 0, sizeof(struct uircc_cb)); + + /* Need to store self somewhere */ + dev_self[i] = self; + + idev = &self->idev; + + /* Initialize IO */ + idev->io.iobase = iobase; + idev->io.iobase2 = iobase2; /* Used by irport */ + idev->io.irq = irq; + idev->io.io_ext = CHIP_IO_EXTENT; + idev->io.io_ext2 = 8; /* Used by irport */ + idev->io.dma = dma; + idev->io.fifo_size = 16; + + /* Lock the port that we need */ + ret = check_region( idev->io.iobase, idev->io.io_ext); + if ( ret < 0) { + DEBUG( 0, __FUNCTION__ "(), can't get iobase of 0x%03x\n", + idev->io.iobase); + /* uircc_cleanup( self->idev); */ + return -ENODEV; + } + ret = check_region( idev->io.iobase2, idev->io.io_ext2); + if ( ret < 0) { + DEBUG( 0, __FUNCTION__ "(), can't get iobase of 0x%03x\n", + idev->io.iobase2); + /* uircc_cleanup( self->idev); */ + return -ENODEV; + } + request_region( idev->io.iobase, idev->io.io_ext, idev->name); + request_region( idev->io.iobase2, idev->io.io_ext2, idev->name); + + /* Initialize QoS for this device */ + irda_init_max_qos_capabilies( &idev->qos); + + /* The only value we must override it the baudrate */ + idev->qos.baud_rate.bits = IR_9600|IR_19200|IR_38400|IR_57600| + IR_115200|IR_576000|IR_1152000|(IR_4000000 << 8); + + idev->qos.min_turn_time.bits = 0x07; + irda_qos_bits_to_value( &idev->qos); + + /* Specify which buffer allocation policy we need */ + idev->rx_buff.flags = GFP_KERNEL | GFP_DMA; + idev->tx_buff.flags = GFP_KERNEL | GFP_DMA; + + /* Max DMA buffer size needed = (data_size + 6) * (window_size) + 6; */ + idev->rx_buff.truesize = 4000; + idev->tx_buff.truesize = 4000; + + /* Initialize callbacks */ + idev->hard_xmit = uircc_hard_xmit; + idev->change_speed = uircc_change_speed; + idev->wait_until_sent = uircc_wait_until_sent; + idev->is_receiving = uircc_is_receiving; + + /* Override the network functions we need to use */ + idev->netdev.init = uircc_net_init; + idev->netdev.hard_start_xmit = uircc_hard_xmit; + idev->netdev.open = uircc_net_open; + idev->netdev.stop = uircc_net_close; + + irport_open( iobase2); + + /* Open the IrDA device */ + irda_device_open( idev, driver_name, self); + + return 0; +} + +/* + * Function uircc_close (idev) + * + * Close driver instance + * + */ +static int uircc_close( struct irda_device *idev) +{ + int iobase; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return -1;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + iobase = idev->io.iobase; + + /* Disable modem */ + outb( 0x00, iobase+UIRCC_CR10); + + irport_close( idev->io.iobase2); + + /* Release the PORT that this driver is using */ + DEBUG( 4, __FUNCTION__ "(), Releasing Region %03x\n", idev->io.iobase); + release_region( idev->io.iobase, idev->io.io_ext); + + if ( idev->io.iobase2) { + DEBUG( 4, __FUNCTION__ "(), Releasing Region %03x\n", + idev->io.iobase2); + release_region( idev->io.iobase2, idev->io.io_ext2); + } + + irda_device_close( idev); + + return 0; +} + +/* + * Function uircc_probe (iobase, board_addr, irq, dma) + * + * Returns non-negative on success. + * + */ +static int uircc_probe( int iobase, int iobase2, int irq, int dma) +{ + int version; + int probe_irq=0; + unsigned long mask; + int i; + + DEBUG( 0, __FUNCTION__ "()\n"); + + /* read the chip version, should be 0x03 */ + version = inb( iobase+UIRCC_SR8); + + if ( version != 0x03) { + DEBUG( 0, __FUNCTION__ "(), Wrong chip version"); + return -1; + } + DEBUG( 0, "UIRCC driver loaded. Version: 0x%02x\n", version); + + /* Reset chip */ + outb( UIRCC_CR0_SYS_RST, iobase+UIRCC_CR0); + + /* Initialize some registers */ + outb( 0, iobase+UIRCC_CR11); + outb( 0, iobase+UIRCC_CR9); + + /* Enable DMA single mode */ + outb( UIRCC_CR1_RX_DMA|UIRCC_CR1_TX_DMA|UIRCC_CR1_MUST_SET, + iobase+UIRCC_CR1); + + /* Disable interrupts */ + outb( 0xff, iobase+UIRCC_CR2); + +#if 0 + irport_close( iobase2); + + for (i=0;i<1;i++) { + + /* Set appropriate speed mode */ + outb( UIRCC_CR10_FIR, iobase+UIRCC_CR10); + + /* Enable DMA single mode */ + outb( UIRCC_CR1_RX_DMA|UIRCC_CR1_TX_DMA|UIRCC_CR1_MUST_SET, + iobase+UIRCC_CR1); + + /* Set up timer */ + outb( 0x01, iobase+UIRCC_CR12); + outb( 0x00, iobase+UIRCC_CR13); + + /* Set interrupt mask */ + outb( 0x82, iobase+UIRCC_CR2); + + DEBUG( 0, __FUNCTION__ "(*), sr3=%#x, sr2=%#x, sr10=%#x, sr12=%#x\n", + inb( iobase+UIRCC_SR3), inb( iobase+UIRCC_SR2), + inb( iobase+UIRCC_SR10), inb( iobase+UIRCC_SR12)); + + mask = probe_irq_on(); + + /* Enable timer */ + outb( 0x08, iobase+UIRCC_CR11); + + udelay( 10000); /* Wait for interrupt! */ + + probe_irq = probe_irq_off( mask); + + DEBUG( 0, "Found irq=%d\n", probe_irq); + + DEBUG( 0, __FUNCTION__ "(), sr3=%#x, sr2=%#x, sr10=%#x, sr12=%#x\n", + inb( iobase+UIRCC_SR3), inb( iobase+UIRCC_SR2), + inb( iobase+UIRCC_SR10), inb( iobase+UIRCC_SR12)); + + + /* Diable timer */ + outb( 0x00, iobase+UIRCC_CR11); + } +#endif + + /* Set self poll address */ + + return 0; +} + +/* + * Function uircc_change_speed (idev, baud) + * + * Change the speed of the device + * + */ +static void uircc_change_speed( struct irda_device *idev, int speed) +{ + struct uircc_cb *self; + int iobase; + int modem = UIRCC_CR10_SIR; + + DEBUG( 0, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = idev->priv; + iobase = idev->io.iobase; + + /* Update accounting for new speed */ + idev->io.baudrate = speed; + + /* Disable interrupts */ + outb( 0xff, iobase+UIRCC_CR2); + + switch ( speed) { + case 9600: + case 19200: + case 37600: + case 57600: + case 115200: +/* irport_open( idev->io.iobase2); */ + irport_change_speed( idev->io.iobase2, speed); + modem = UIRCC_CR10_SIR; + break; + case 576000: + + DEBUG(0, __FUNCTION__ "(), handling baud of 576000\n"); + break; + case 1152000: + + DEBUG(0, __FUNCTION__ "(), handling baud of 1152000\n"); + break; + case 4000000: + irport_close( idev->io.iobase2); + modem = UIRCC_CR10_FIR; + DEBUG(0, __FUNCTION__ "(), handling baud of 4000000\n"); + break; + default: + DEBUG( 0, __FUNCTION__ "(), unknown baud rate of %d\n", speed); + break; + } + + /* Set appropriate speed mode */ + outb( modem, iobase+UIRCC_CR10); + + idev->netdev.tbusy = 0; + + /* Enable some interrupts so we can receive frames */ + if ( speed > 115200) { + /* Enable DMA single mode */ + outb( UIRCC_CR1_RX_DMA|UIRCC_CR1_TX_DMA|UIRCC_CR1_MUST_SET, + iobase+UIRCC_CR1); + + /* outb( UIRCC_CR2_RECV_MASK, iobase+UIRCC_CR2); */ + outb( 0, iobase+UIRCC_CR2); + uircc_dma_receive( idev); + } +} + +/* + * Function uircc_hard_xmit (skb, dev) + * + * Transmit the frame! + * + */ +static int uircc_hard_xmit( struct sk_buff *skb, struct device *dev) +{ + struct irda_device *idev; + int iobase; + int mtt; + + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + DEBUG(0, __FUNCTION__ "(%ld), skb->len=%d\n", jiffies, (int) skb->len); + + /* Use irport for SIR speeds */ + if ( idev->io.baudrate <= 115200) { + return irport_hard_xmit( skb, dev); + } + + if ( dev->tbusy) { + __u8 sr3; + + DEBUG( 4, __FUNCTION__ "(), tbusy==TRUE\n"); + + return -EBUSY; + } + + /* Lock transmit buffer */ + if ( irda_lock( (void *) &dev->tbusy) == FALSE) + return -EBUSY; + + memcpy( idev->tx_buff.data, skb->data, skb->len); + + /* Make sure that the length is a multiple of 16 bits */ + if ( skb->len & 0x01) + skb->len++; + + idev->tx_buff.len = skb->len; + idev->tx_buff.head = idev->tx_buff.data; + idev->tx_buff.offset = 0; + + mtt = irda_get_mtt( skb); + + /* Use udelay for delays less than 50 us. */ + if (mtt) + udelay( mtt); + + /* Enable transmit interrupts */ + /* outb( UIRCC_CR2_XMIT_MASK, iobase+UIRCC_CR2); */ + outb( 0, iobase+UIRCC_CR2); + + uircc_dma_write( idev, iobase); + + dev_kfree_skb( skb); + + return 0; +} + +/* + * Function uircc_dma_xmit (idev, iobase) + * + * Transmit data using DMA + * + */ +static void uircc_dma_write( struct irda_device *idev, int iobase) +{ + struct uircc_cb *self; + int i; + + DEBUG( 0, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = idev->priv; + + /* Receiving disable */ + self->cr3 &= ~UIRCC_CR3_RECV_EN; + outb( self->cr3, iobase+UIRCC_CR3); + + setup_dma( idev->io.dma, idev->tx_buff.data, idev->tx_buff.len, + DMA_MODE_WRITE); + + DEBUG( 0, __FUNCTION__ "residue=%d\n", + get_dma_residue( idev->io.dma)); + + idev->io.direction = IO_XMIT; + + /* Set frame length */ + outb( idev->tx_buff.len & 0xff, iobase+UIRCC_CR4); /* Low byte */ + outb( idev->tx_buff.len >> 8, iobase+UIRCC_CR5); /* High byte */ + + /* Enable transmit and transmit CRC */ + self->cr3 |= (UIRCC_CR3_XMIT_EN|UIRCC_CR3_TX_CRC_EN); + outb( self->cr3, iobase+UIRCC_CR3); +} + +/* + * Function uircc_dma_xmit_complete (idev) + * + * The transfer of a frame in finished. This function will only be called + * by the interrupt handler + * + */ +static void uircc_dma_xmit_complete( struct irda_device *idev, int underrun) +{ + struct uircc_cb *self; + int iobase; + int len; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = idev->priv; + + iobase = idev->io.iobase; + + /* Select TX counter */ + outb( UIRCC_CR0_CNT_SWT, iobase+UIRCC_CR0); + + /* Read TX length counter */ + len = inb( iobase+UIRCC_SR4); /* Low byte */ + len |= inb( iobase+UIRCC_SR5) << 8; /* High byte */ + + /* Disable transmit */ + self->cr3 &= ~UIRCC_CR3_XMIT_EN; + outb( self->cr3, iobase+UIRCC_CR3); + + /* Check for underrrun! */ + if ( underrun) { + idev->stats.tx_errors++; + idev->stats.tx_fifo_errors++; + } else { + idev->stats.tx_packets++; + idev->stats.tx_bytes += idev->tx_buff.len; + } + + /* Unlock tx_buff and request another frame */ + idev->netdev.tbusy = 0; /* Unlock */ + idev->media_busy = FALSE; + + /* Tell the network layer, that we can accept more frames */ + mark_bh( NET_BH); +} + +/* + * Function uircc_dma_receive (idev) + * + * Get ready for receiving a frame. The device will initiate a DMA + * if it starts to receive a frame. + * + */ +static int uircc_dma_receive( struct irda_device *idev) +{ + struct uircc_cb *self; + int iobase; + + ASSERT( idev != NULL, return -1;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + DEBUG( 0, __FUNCTION__ "\n"); + + self = idev->priv; + iobase= idev->io.iobase; + + /* Disable DMA */ + + setup_dma( idev->io.dma, idev->rx_buff.data, idev->rx_buff.truesize, + DMA_MODE_READ); + + /* driver->media_busy = FALSE; */ + idev->io.direction = IO_RECV; + idev->rx_buff.head = idev->rx_buff.data; + idev->rx_buff.offset = 0; + + /* Enable receiving with CRC */ + self->cr3 |= (UIRCC_CR3_RECV_EN|UIRCC_CR3_RX_CRC_EN); + outb( self->cr3, iobase+UIRCC_CR3); + + /* Address check? */ + + DEBUG( 4, __FUNCTION__ "(), done!\n"); + + return 0; +} + +/* + * Function uircc_dma_receive_complete (idev) + * + * Finished with receiving frames + * + * + */ +static int uircc_dma_receive_complete( struct irda_device *idev, int iobase) +{ + struct sk_buff *skb; + struct uircc_cb *self; + int len; + + self = idev->priv; + + DEBUG( 0, __FUNCTION__ "()\n"); + + /* Check for CRC or framing error */ + if ( inb( iobase+UIRCC_SR0) & UIRCC_SR0_RX_CRCFRM) { + DEBUG( 0, __FUNCTION__ "(), crc or frm error\n"); + return -1; + } + + /* Select receive length counter */ + outb( 0x00, iobase+UIRCC_CR0); + + /* Read frame length */ + len = inb( iobase+UIRCC_SR4); /* Low byte */ + len |= inb( iobase+UIRCC_SR5) << 8; /* High byte */ + + DEBUG( 0, __FUNCTION__ "(), len=%d\n", len); + + /* Receiving disable */ + self->cr3 &= ~UIRCC_CR3_RECV_EN; + outb( self->cr3, iobase+UIRCC_CR3); + + skb = dev_alloc_skb( len+1); + if (skb == NULL) { + printk( KERN_INFO __FUNCTION__ + "(), memory squeeze, dropping frame.\n"); + /* Restore bank register */ + return FALSE; + } + + /* Make sure IP header gets aligned */ + skb_reserve( skb, 1); + + /* Copy frame without CRC */ + /* if ( idev->io.baudrate < 4000000) { */ +/* skb_put( skb, len-2); */ +/* memcpy( skb->data, idev->rx_buff.head, len-2); */ +/* } else { */ +/* skb_put( skb, len-4); */ +/* memcpy( skb->data, idev->rx_buff.head, len-4); */ +/* } */ + + skb_put( skb, len); + memcpy( skb->data, idev->rx_buff.head, len); + idev->stats.rx_packets++; + + skb->dev = &idev->netdev; + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IRDA); + netif_rx( skb); + + return TRUE; +} + +/* + * Function uircc_interrupt (irq, dev_id, regs) + * + * An interrupt from the chip has arrived. Time to do some work + * + */ +static void uircc_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + __u8 sr3; + int iobase; + + struct irda_device *idev = (struct irda_device *) dev_id; + + if (idev == NULL) { + printk( KERN_WARNING "%s: irq %d for unknown device.\n", + driver_name, irq); + return; + } + + if (idev->io.baudrate <= 115200) + return irport_interrupt( irq, dev_id, regs); + + iobase = idev->io.iobase; + + /* Read interrupt status */ + sr3 = inb( iobase+UIRCC_SR3); + if (!sr3) { + return; + } + + idev->netdev.interrupt = 1; + + DEBUG( 4, __FUNCTION__ "(), sr3=%#x, sr2=%#x, sr10=%#x\n", + inb( iobase+UIRCC_SR3), inb( iobase+UIRCC_SR2), + inb( iobase+UIRCC_SR10)); + + /* + * Check what interrupt this is. The UIRCC will not report two + * different interrupts at the same time! + */ + switch( sr3) { + case UIRCC_SR3_RX_EOF: /* Check of end of frame */ + uircc_dma_receive_complete( idev, iobase); + break; + case UIRCC_SR3_TXUR: /* Check for transmit underrun */ + uircc_dma_xmit_complete( idev, TRUE); + break; + case UIRCC_SR3_TX_DONE: + uircc_dma_xmit_complete( idev, FALSE); + break; + case UIRCC_SR3_TMR_OUT: + /* Disable timer */ + outb( inb( iobase+UIRCC_CR11) & ~UIRCC_CR11_TMR_EN, + iobase+UIRCC_CR11); + break; + default: + DEBUG( 0, __FUNCTION__ "(), unknown interrupt status=%#x\n", + sr3); + break; + } + + idev->netdev.interrupt = 0; +} + +/* + * Function uircc_wait_until_sent (idev) + * + * This function should put the current thread to sleep until all data + * have been sent, so it is safe to change the speed. + */ +static void uircc_wait_until_sent( struct irda_device *idev) +{ + /* Just delay 60 ms */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(6); +} + +/* + * Function uircc_is_receiving (idev) + * + * Return TRUE is we are currently receiving a frame + * + */ +static int uircc_is_receiving( struct irda_device *idev) +{ + int status = FALSE; + /* int iobase; */ + + ASSERT( idev != NULL, return FALSE;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return FALSE;); + + if ( idev->io.baudrate > 115200) { + + } else + status = ( idev->rx_buff.state != OUTSIDE_FRAME); + + return status; +} + +/* + * Function uircc_net_init (dev) + * + * Initialize network device + * + */ +static int uircc_net_init( struct device *dev) +{ + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Setup to be a normal IrDA network device driver */ + irda_device_setup( dev); + + /* Insert overrides below this line! */ + + return 0; +} + + +/* + * Function uircc_net_open (dev) + * + * Start the device + * + */ +static int uircc_net_open( struct device *dev) +{ + struct irda_device *idev; + int iobase; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + if (request_irq( idev->io.irq, uircc_interrupt, 0, idev->name, + (void *) idev)) { + return -EAGAIN; + } + /* + * Always allocate the DMA channel after the IRQ, + * and clean up on failure. + */ + if (request_dma(idev->io.dma, idev->name)) { + free_irq( idev->io.irq, idev); + return -EAGAIN; + } + + /* Ready to play! */ + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + /* turn on interrupts */ + + MOD_INC_USE_COUNT; + + return 0; +} + +/* + * Function uircc_net_close (dev) + * + * Stop the device + * + */ +static int uircc_net_close(struct device *dev) +{ + struct irda_device *idev; + int iobase; + + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Stop device */ + dev->tbusy = 1; + dev->start = 0; + + ASSERT( dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + disable_dma( idev->io.dma); + + /* Disable interrupts */ + + free_irq( idev->io.irq, idev); + free_dma( idev->io.dma); + + MOD_DEC_USE_COUNT; + + return 0; +} + +#ifdef MODULE + +/* + * Function init_module (void) + * + * + * + */ +int init_module(void) +{ + uircc_init(); + + return(0); +} + +/* + * Function cleanup_module (void) + * + * + * + */ +void cleanup_module(void) +{ + uircc_cleanup(); +} + +#endif + diff --git a/drivers/net/irda/w83977af_ir.c b/drivers/net/irda/w83977af_ir.c new file mode 100644 index 000000000..5a7995ef7 --- /dev/null +++ b/drivers/net/irda/w83977af_ir.c @@ -0,0 +1,1273 @@ +/********************************************************************* + * + * Filename: w83977af_ir.c + * Version: 0.8 + * Description: FIR/MIR driver for the Winbond W83977AF Super I/O chip + * Status: Experimental. + * Author: Paul VanderSpek + * Created at: Wed Nov 4 11:46:16 1998 + * Modified at: Mon Dec 14 21:51:53 1998 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998 Corel Computer Corp. + * Copyright (c) 1998 Dag Brattli <dagb@cs.uit.no> + * + * 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. + * + * Neither Paul VanderSpek nor Corel Computer Corp. admit liability + * nor provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + * If you find bugs in this file, its very likely that the same bug + * will also be in w83977af_ir.c since the implementations is quite + * similar. + * + * Notice that all functions that needs to access the chip in _any_ + * way, must save BSR register on entry, and restore it on exit. + * It is _very_ important to follow this policy! + * + * __u8 bank; + * + * bank = inb( iobase+BSR); + * + * do_your_stuff_here(); + * + * outb( bank, iobase+BSR); + * + ********************************************************************/ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/malloc.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/wrapper.h> +#include <net/irda/irda_device.h> +#include <net/irda/w83977af.h> +#include <net/irda/w83977af_ir.h> + +#define NETWINDER + +static char *driver_name = "w83977af_ir"; + +#define CHIP_IO_EXTENT 8 + +static unsigned int io[] = { 0x400, ~0, ~0, ~0 }; +static unsigned int irq[] = { 22, 0, 0, 0 }; +static unsigned int dma[] = { 0, 0, 0, 0 }; + +static struct irda_device *dev_self[] = { NULL, NULL, NULL, NULL}; + +/* For storing entries in the status FIFO */ +struct st_fifo_entry { + int status; + int len; +}; + +static struct st_fifo_entry prev; + +/* Some prototypes */ +static int w83977af_open( int i, unsigned int iobase, unsigned int irq, + unsigned int dma); +static int w83977af_close( struct irda_device *idev); +static int w83977af_probe( int iobase, int irq, int dma); +static int w83977af_dma_receive(struct irda_device *idev); +static int w83977af_dma_receive_complete(struct irda_device *idev); +static int w83977af_hard_xmit( struct sk_buff *skb, struct device *dev); +static int w83977af_pio_write( int iobase, __u8 *buf, int len, int fifo_size); +static void w83977af_dma_write( struct irda_device *idev, int iobase); +static void w83977af_change_speed( struct irda_device *idev, int baud); +static void w83977af_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void w83977af_wait_until_sent( struct irda_device *idev); +static int w83977af_is_receiving( struct irda_device *idev); + +static int w83977af_net_init( struct device *dev); +static int w83977af_net_open( struct device *dev); +static int w83977af_net_close( struct device *dev); + +/* + * Function w83977af_init () + * + * Initialize chip. Just try to find out how many chips we are dealing with + * and where they are + */ +__initfunc(int w83977af_init(void)) +{ + int i; + + DEBUG( 0, __FUNCTION__ "()\n"); + + prev.status = 0; + + for ( i=0; (io[i] < 2000) && (i < 4); i++) { + int ioaddr = io[i]; + if (check_region(ioaddr, CHIP_IO_EXTENT)) + continue; + if (w83977af_open( i, io[i], irq[i], dma[i]) == 0) + return 0; + } + return -ENODEV; +} + +/* + * Function w83977af_cleanup () + * + * Close all configured chips + * + */ +#ifdef MODULE +void w83977af_cleanup(void) +{ + int i; + + DEBUG( 4, __FUNCTION__ "()\n"); + + for ( i=0; i < 4; i++) { + if ( dev_self[i]) + w83977af_close( dev_self[i]); + } +} +#endif /* MODULE */ + +/* + * Function w83977af_open (iobase, irq) + * + * Open driver instance + * + */ +int w83977af_open( int i, unsigned int iobase, unsigned int irq, + unsigned int dma) +{ + struct irda_device *idev; + int ret; + + DEBUG( 0, __FUNCTION__ "()\n"); + + if ( w83977af_probe( iobase, irq, dma) == -1) + return -1; + + /* + * Allocate new instance of the driver + */ + idev = kmalloc( sizeof(struct irda_device), GFP_KERNEL); + if ( idev == NULL) { + printk( KERN_ERR "IrDA: Can't allocate memory for " + "IrDA control block!\n"); + return -ENOMEM; + } + memset( idev, 0, sizeof(struct irda_device)); + + /* Need to store self somewhere */ + dev_self[i] = idev; + + /* Initialize IO */ + idev->io.iobase = iobase; + idev->io.irq = irq; + idev->io.io_ext = CHIP_IO_EXTENT; + idev->io.dma = dma; + idev->io.fifo_size = 32; + + /* Lock the port that we need */ + ret = check_region( idev->io.iobase, idev->io.io_ext); + if ( ret < 0) { + DEBUG( 0, __FUNCTION__ "(), can't get iobase of 0x%03x\n", + idev->io.iobase); + /* w83977af_cleanup( self->idev); */ + return -ENODEV; + } + request_region( idev->io.iobase, idev->io.io_ext, idev->name); + + /* Initialize QoS for this device */ + irda_init_max_qos_capabilies( &idev->qos); + + /* The only value we must override it the baudrate */ + + /* FIXME: The HP HDLS-1100 does not support 1152000! */ + idev->qos.baud_rate.bits = IR_9600|IR_19200|IR_38400|IR_57600| + IR_115200|IR_576000|IR_1152000|(IR_4000000 << 8); + + /* The HP HDLS-1100 needs 1 ms according to the specs */ + idev->qos.min_turn_time.bits = 0x03; /* 1ms and more */ + irda_qos_bits_to_value( &idev->qos); + + /* Specify which buffer allocation policy we need */ + idev->rx_buff.flags = GFP_KERNEL | GFP_DMA; + idev->tx_buff.flags = GFP_KERNEL | GFP_DMA; + + /* Max DMA buffer size needed = (data_size + 6) * (window_size) + 6; */ + idev->rx_buff.truesize = 14384; + idev->tx_buff.truesize = 4000; + + /* Initialize callbacks */ + idev->hard_xmit = w83977af_hard_xmit; + idev->change_speed = w83977af_change_speed; + idev->wait_until_sent = w83977af_wait_until_sent; + idev->is_receiving = w83977af_is_receiving; + + /* Override the network functions we need to use */ + idev->netdev.init = w83977af_net_init; + idev->netdev.hard_start_xmit = w83977af_hard_xmit; + idev->netdev.open = w83977af_net_open; + idev->netdev.stop = w83977af_net_close; + + /* Open the IrDA device */ + irda_device_open( idev, driver_name, NULL); + + return 0; +} + +/* + * Function w83977af_close (idev) + * + * Close driver instance + * + */ +static int w83977af_close( struct irda_device *idev) +{ + int iobase; + + DEBUG( 0, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return -1;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + iobase = idev->io.iobase; + + /* enter PnP configuration mode */ + w977_efm_enter(); + + w977_select_device(W977_DEVICE_IR); + + /* Deactivate device */ + w977_write_reg(0x30, 0x00); + + w977_efm_exit(); + + /* Release the PORT that this driver is using */ + DEBUG(0 , __FUNCTION__ "(), Releasing Region %03x\n", + idev->io.iobase); + release_region( idev->io.iobase, idev->io.io_ext); + + irda_device_close( idev); + + return 0; +} + +/* + * Function w83977af_probe (iobase, irq, dma) + * + * Returns non-negative on success. + * + */ +int w83977af_probe( int iobase, int irq, int dma) +{ + int version; + + DEBUG( 0, __FUNCTION__ "()\n"); + + /* Enter PnP configuration mode */ + w977_efm_enter(); + + w977_select_device(W977_DEVICE_IR); + + /* Configure PnP port, IRQ, and DMA channel */ + w977_write_reg(0x60, (iobase >> 8) & 0xff); + w977_write_reg(0x61, (iobase) & 0xff); + w977_write_reg(0x70, 0x06); +#ifdef NETWINDER + w977_write_reg(0x74, dma+1); /* Netwinder uses 1 higher than Linux */ +#else + w977_write_reg(0x74, dma); +#endif + w977_write_reg(0x75, dma); /* Disable Tx DMA */ + + /* Set append hardware CRC, enable IR bank selection */ + w977_write_reg(0xf0, APEDCRC|ENBNKSEL); + + /* Activate device */ + w977_write_reg(0x30, 0x01); + + w977_efm_exit(); + + /* Disable Advanced mode */ + switch_bank( iobase, SET2); + outb(iobase+2, 0x00); + + /* Turn on UART (global) interrupts */ + switch_bank( iobase, SET0); + outb( HCR_EN_IRQ, iobase+HCR); + + /* Switch to advanced mode */ + switch_bank( iobase, SET2); + outb( inb( iobase+ADCR1) | ADCR1_ADV_SL, iobase+ADCR1); + + /* Set default IR-mode */ + switch_bank( iobase, SET0); + outb( HCR_SIR, iobase+HCR); + + /* Read the Advanced IR ID */ + switch_bank(iobase, SET3); + version = inb( iobase+AUID); + + /* Should be 0x1? */ + if (0x10 != (version & 0xf0)) { + DEBUG( 0, __FUNCTION__ "(), Wrong chip version"); + return -1; + } + + /* Set FIFO size to 32 */ + switch_bank( iobase, SET2); + outb( ADCR2_RXFS32|ADCR2_TXFS32, iobase+ADCR2); + + /* Set FIFO threshold to TX17, RX16 */ + switch_bank(iobase, SET0); + outb(UFR_RXTL|UFR_TXTL|UFR_TXF_RST|UFR_RXF_RST|UFR_EN_FIFO,iobase+UFR); +/* outb( 0xa7, iobase+UFR); */ + + /* Receiver frame length */ + switch_bank( iobase, SET4); + outb( 2048 & 0xff, iobase+6); + outb(( 2048 >> 8) & 0x1f, iobase+7); + + /* + * Init HP HSDL-1100 transceiver. + * + * Set IRX_MSL since we have 2 * receive paths IRRX, and + * IRRXH. Clear IRSL0D since we want IRSL0 * to be a input pin used + * for IRRXH + * + * IRRX pin 37 connected to receiver + * IRTX pin 38 connected to transmitter + * FIRRX pin 39 connected to receiver (IRSL0) + * CIRRX pin 40 connected to pin 37 + */ + switch_bank( iobase, SET7); + outb( 0x40, iobase+7); + + DEBUG(0, "W83977AF (IR) driver loaded. Version: 0x%02x\n", version); + + return 0; +} + +/* + * Function w83977af_change_speed (idev, baud) + * + * Change the speed of the device + * + */ +void w83977af_change_speed( struct irda_device *idev, int speed) +{ + int ir_mode = HCR_SIR; + int iobase; + __u8 set; + + DEBUG( 0, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + iobase = idev->io.iobase; + + /* Update accounting for new speed */ + idev->io.baudrate = speed; + + /* Save current bank */ + set = inb( iobase+SSR); + + /* Disable interrupts */ + switch_bank( iobase, SET0); + outb( 0, iobase+ICR); + + /* Select Set 2 */ + switch_bank( iobase, SET2); + + outb( 0x00, iobase+ABHL); + switch ( speed) { + case 9600: outb( 0x0c, iobase+ABLL); break; + case 19200: outb( 0x06, iobase+ABLL); break; + case 37600: outb( 0x03, iobase+ABLL); break; + case 57600: outb( 0x02, iobase+ABLL); break; + case 115200: outb( 0x01, iobase+ABLL); break; + case 576000: + ir_mode = HCR_MIR_576; + DEBUG(0, __FUNCTION__ "(), handling baud of 576000\n"); + break; + case 1152000: + ir_mode = HCR_MIR_1152; + DEBUG(0, __FUNCTION__ "(), handling baud of 1152000\n"); + break; + case 4000000: + ir_mode = HCR_FIR; + DEBUG(0, __FUNCTION__ "(), handling baud of 4000000\n"); + break; + default: + ir_mode = HCR_FIR; + DEBUG( 0, __FUNCTION__ "(), unknown baud rate of %d\n", speed); + break; + } + + /* Set speed mode */ + switch_bank(iobase, SET0); + outb( ir_mode, iobase+HCR); + + /* set FIFO size to 32 */ + switch_bank( iobase, SET2); + outb( ADCR2_RXFS32|ADCR2_TXFS32, iobase+ADCR2); + + /* set FIFO threshold to TX17, RX16 */ + switch_bank(iobase, SET0); + outb( UFR_RXTL|UFR_TXTL|UFR_TXF_RST|UFR_RXF_RST|UFR_EN_FIFO, iobase+UFR); + + idev->netdev.tbusy = 0; + + /* Enable some interrupts so we can receive frames */ + switch_bank(iobase, SET0); + if ( speed > 115200) { + outb( ICR_EFSFI, iobase+ICR); + w83977af_dma_receive( idev); + } else + outb( ICR_ERBRI, iobase+ICR); + + /* Restore SSR */ + outb( set, iobase+SSR); +} + +/* + * Function w83977af_hard_xmit (skb, dev) + * + * Sets up a DMA transfer to send the current frame. + * + */ +int w83977af_hard_xmit( struct sk_buff *skb, struct device *dev) +{ + struct irda_device *idev; + int iobase; + __u8 set; + int mtt; + + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + DEBUG(4, __FUNCTION__ "(%ld), skb->len=%d\n", jiffies, (int) skb->len); + + if ( dev->tbusy) { + DEBUG( 4, __FUNCTION__ "(), tbusy==TRUE\n"); + + return -EBUSY; + } + + /* Lock transmit buffer */ + if ( irda_lock( (void *) &dev->tbusy) == FALSE) + return -EBUSY; + + /* Save current set */ + set = inb( iobase+SSR); + + /* Decide if we should use PIO or DMA transfer */ + if ( idev->io.baudrate > 115200) { + memcpy( idev->tx_buff.data, skb->data, skb->len); + idev->tx_buff.len = skb->len; + idev->tx_buff.head = idev->tx_buff.data; + idev->tx_buff.offset = 0; + + mtt = irda_get_mtt( skb); + if ( mtt > 50) { + /* Adjust for timer resolution */ + mtt /= 1000+1; + + /* Setup timer */ + switch_bank( iobase, SET4); + outb( mtt & 0xff, iobase+TMRL); + outb(( mtt >> 8) & 0x0f, iobase+TMRH); + + /* Start timer */ + outb( IR_MSL_EN_TMR, iobase+IR_MSL); + idev->io.direction = IO_XMIT; + + /* Enable timer interrupt */ + switch_bank( iobase, SET0); + outb( ICR_ETMRI, iobase+ICR); + } else { + if ( mtt) + udelay( mtt); + + /* Enable DMA interrupt */ + switch_bank( iobase, SET0); + outb( ICR_EDMAI, iobase+ICR); + w83977af_dma_write( idev, iobase); + } + } else { + idev->tx_buff.len = async_wrap_skb( skb, idev->tx_buff.data, + idev->tx_buff.truesize); + + idev->tx_buff.offset = 0; + idev->tx_buff.head = idev->tx_buff.data; + + /* Add interrupt on tx low level (will fire immediately) */ + switch_bank( iobase, SET0); + outb( ICR_ETXTHI, iobase+ICR); + } + dev_kfree_skb( skb); + + /* Restore set register */ + outb( set, iobase+SSR); + + return 0; +} + + +/* + * Function w83977af_dma_write (idev, iobase) + * + * + * + */ +static void w83977af_dma_write( struct irda_device *idev, int iobase) +{ + __u8 set; + + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Save current set */ + set = inb( iobase+SSR); + + /* Disable DMA */ + switch_bank(iobase, SET0); + outb( inb( iobase+HCR) & ~HCR_EN_DMA, iobase+HCR); + + setup_dma( idev->io.dma, idev->tx_buff.data, idev->tx_buff.len, + DMA_MODE_WRITE); + + /* idev->media_busy = TRUE; */ + idev->io.direction = IO_XMIT; + + /* Choose transmit DMA channel */ + switch_bank(iobase, SET2); + outb( inb( iobase+ADCR1) | ADCR1_D_CHSW|ADCR1_DMA_F|ADCR1_ADV_SL, + iobase+ADCR1); + + /* Enable DMA */ + switch_bank( iobase, SET0); + outb( inb( iobase+HCR) | HCR_EN_DMA, iobase+HCR); + + /* Restore set register */ + outb( set, iobase+SSR); +} + +/* + * Function w83977af_pio_write (iobase, buf, len, fifo_size) + * + * + * + */ +static int w83977af_pio_write( int iobase, __u8 *buf, int len, int fifo_size) +{ + int actual = 0; + __u8 set; + + DEBUG( 4, __FUNCTION__ "()\n"); + + /* Save current bank */ + set = inb( iobase+SSR); + + switch_bank( iobase, SET0); + if (!( inb_p( iobase+USR) & USR_TSRE)) { + DEBUG( 4, __FUNCTION__ "(), warning, FIFO not empty yet!\n"); + + fifo_size -= 17; + DEBUG( 4, __FUNCTION__ "%d bytes left in tx fifo\n", fifo_size); + } + + /* Fill FIFO with current frame */ + while (( fifo_size-- > 0) && (actual < len)) { + /* Transmit next byte */ + outb( buf[actual++], iobase+TBR); + } + + DEBUG( 4, __FUNCTION__ "(), fifo_size %d ; %d sent of %d\n", + fifo_size, actual, len); + + /* Restore bank */ + outb( set, iobase+SSR); + + return actual; +} + +/* + * Function w83977af_dma_xmit_complete (idev) + * + * The transfer of a frame in finished. So do the necessary things + * + * + */ +void w83977af_dma_xmit_complete( struct irda_device *idev) +{ + int iobase; + __u8 set; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + iobase = idev->io.iobase; + + /* Save current set */ + set = inb( iobase+SSR); + + /* Disable DMA */ + switch_bank(iobase, SET0); + outb( inb(iobase+HCR) & ~HCR_EN_DMA, iobase+HCR); + + /* Check for underrrun! */ + if ( inb( iobase+AUDR) & AUDR_UNDR) { + DEBUG( 0, __FUNCTION__ "(), Transmit underrun!\n"); + + idev->stats.tx_errors++; + idev->stats.tx_fifo_errors++; + + /* Clear bit, by writing 1 to it */ + outb( AUDR_UNDR, iobase+AUDR); + } else + idev->stats.tx_packets++; + + /* Unlock tx_buff and request another frame */ + idev->netdev.tbusy = 0; /* Unlock */ + idev->media_busy = FALSE; + + /* Tell the network layer, that we want more frames */ + mark_bh( NET_BH); + + /* Restore set */ + outb( set, iobase+SSR); +} + +/* + * Function w83977af_dma_receive (idev) + * + * Get ready for receiving a frame. The device will initiate a DMA + * if it starts to receive a frame. + * + */ +int w83977af_dma_receive( struct irda_device *idev) +{ + int iobase; + __u8 set; +#ifdef NETWINDER + unsigned long flags; + __u8 hcr; +#endif + + ASSERT( idev != NULL, return -1;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + DEBUG( 0, __FUNCTION__ "\n"); + + iobase= idev->io.iobase; + + /* Save current set */ + set = inb( iobase+SSR); + + /* Disable DMA */ + switch_bank( iobase, SET0); + outb( inb( iobase+HCR) & ~HCR_EN_DMA, iobase+HCR); + +#ifdef NETWINDER + save_flags(flags); + cli(); + + disable_dma( idev->io.dma); + clear_dma_ff( idev->io.dma); + set_dma_mode( idev->io.dma, DMA_MODE_READ); + set_dma_addr( idev->io.dma, virt_to_bus(idev->rx_buff.data)); + set_dma_count( idev->io.dma, idev->rx_buff.truesize); +#else + setup_dma( idev->io.dma, idev->rx_buff.data, idev->rx_buff.truesize, + DMA_MODE_READ); +#endif + /* driver->media_busy = FALSE; */ + idev->io.direction = IO_RECV; + idev->rx_buff.head = idev->rx_buff.data; + idev->rx_buff.offset = 0; + + /* + * Reset Rx FIFO. This will also flush the ST_FIFO, it's very + * important that we don't reset the Tx FIFO since it might not + * be finished transmitting yet + */ + outb( UFR_RXTL|UFR_TXTL|UFR_RXF_RST|UFR_EN_FIFO, iobase+UFR); + prev.status = 0; + + /* Choose DMA Rx, DMA Fairness, and Advanced mode */ + switch_bank(iobase, SET2); + outb(( inb( iobase+ADCR1) & ~ADCR1_D_CHSW)|ADCR1_DMA_F|ADCR1_ADV_SL, + iobase+ADCR1); + + /* Enable DMA */ + switch_bank(iobase, SET0); +#ifdef NETWINDER + hcr = inb( iobase+HCR); + enable_dma( idev->io.dma); + outb( hcr | HCR_EN_DMA, iobase+HCR); + restore_flags(flags); +#else + outb( inb( iobase+HCR) | HCR_EN_DMA, iobase+HCR); +#endif + + /* Restore set */ + outb( set, iobase+SSR); + + DEBUG( 4, __FUNCTION__ "(), done!\n"); + + return 0; +} + +/* + * Function w83977af_receive_complete (idev) + * + * Finished with receiving a frame + * + */ +int w83977af_dma_receive_complete(struct irda_device *idev) +{ + struct sk_buff *skb; + int len; + int iobase; + __u8 set; + __u8 status; + + DEBUG( 0, __FUNCTION__ "\n"); + + iobase = idev->io.iobase; + + /* Save current set */ + set = inb( iobase+SSR); + + iobase = idev->io.iobase; + + switch_bank(iobase, SET5); + if ( prev.status & FS_FO_FSFDR) { + status = prev.status; + len = prev.len; + + prev.status = 0; + } else { + status = inb( iobase+FS_FO); + len = inb( iobase+RFLFL); + len |= inb( iobase+RFLFH) << 8; + } + + while ( status & FS_FO_FSFDR) { + /* Check for errors */ + if ( status & FS_FO_ERR_MSK) { + if ( status & FS_FO_LST_FR) { + /* Add number of lost frames to stats */ + idev->stats.rx_errors += len; + } else { + /* Skip frame */ + idev->stats.rx_errors++; + + idev->rx_buff.offset += len; + idev->rx_buff.head += len; + + if ( status & FS_FO_MX_LEX) + idev->stats.rx_length_errors++; + + if ( status & FS_FO_PHY_ERR) + idev->stats.rx_frame_errors++; + + if ( status & FS_FO_CRC_ERR) + idev->stats.rx_crc_errors++; + } + /* The errors below can be reported in both cases */ + if ( status & FS_FO_RX_OV) + idev->stats.rx_fifo_errors++; + + if ( status & FS_FO_FSF_OV) + idev->stats.rx_fifo_errors++; + + } else { + /* Check if we have transfered all data to memory */ + switch_bank(iobase, SET0); + if ( inb( iobase+USR) & USR_RDR) { + /* Put this entry back in fifo */ + prev.status = status; + prev.len = len; + + /* Restore set register */ + outb( set, iobase+SSR); + + return FALSE; /* I'll be back! */ + } + + skb = dev_alloc_skb( len+1); + if (skb == NULL) { + printk( KERN_INFO __FUNCTION__ + "(), memory squeeze, dropping frame.\n"); + /* Restore set register */ + outb( set, iobase+SSR); + + return FALSE; + } + + /* Align to 20 bytes */ + skb_reserve( skb, 1); + + /* Copy frame without CRC */ + if ( idev->io.baudrate < 4000000) { + skb_put( skb, len-2); + memcpy( skb->data, idev->rx_buff.head, len-2); + } else { + skb_put( skb, len-4); + memcpy( skb->data, idev->rx_buff.head, len-4); + } + + /* Move to next frame */ + idev->rx_buff.offset += len; + idev->rx_buff.head += len; + + skb->dev = &idev->netdev; + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IRDA); + netif_rx( skb); + idev->stats.rx_packets++; + } + /* Read next entry in ST_FIFO */ + switch_bank(iobase, SET5); + status = inb( iobase+FS_FO); + len = inb( iobase+RFLFL); + len |= inb( iobase+RFLFH) << 8; + } + /* Restore set register */ + outb( set, iobase+SSR); + + return TRUE; +} + +/* + * Function pc87108_pio_receive (idev) + * + * Receive all data in receiver FIFO + * + */ +static void w83977af_pio_receive( struct irda_device *idev) +{ + __u8 byte = 0x00; + int iobase; + + DEBUG( 4, __FUNCTION__ "()\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + iobase = idev->io.iobase; + + if ( idev->rx_buff.len == 0) { + idev->rx_buff.head = idev->rx_buff.data; + } + + /* Receive all characters in Rx FIFO */ + do { + byte = inb( iobase+RBR); + async_unwrap_char( idev, byte); + + } while ( inb( iobase+USR) & USR_RDR); /* Data available */ +} + +/* + * Function w83977af_sir_interrupt (idev, eir) + * + * Handle SIR interrupt + * + */ +static __u8 w83977af_sir_interrupt( struct irda_device *idev, int isr) +{ + int len; + int actual; + __u8 new_icr = 0; + + DEBUG( 4, __FUNCTION__ "(), isr=%#x\n", isr); + + /* Transmit FIFO low on data */ + if ( isr & ISR_TXTH_I) { + /* Write data left in transmit buffer */ + len = idev->tx_buff.len - idev->tx_buff.offset; + + ASSERT( len > 0, return 0;); + actual = w83977af_pio_write( idev->io.iobase, + idev->tx_buff.head, + len, idev->io.fifo_size); + idev->tx_buff.offset += actual; + idev->tx_buff.head += actual; + + idev->io.direction = IO_XMIT; + ASSERT( actual <= len, return 0;); + /* Check if finished */ + if ( actual == len) { + DEBUG( 4, __FUNCTION__ "(), finished with frame!\n"); + idev->netdev.tbusy = 0; /* Unlock */ + idev->stats.tx_packets++; + + /* Schedule network layer */ + mark_bh(NET_BH); + + new_icr |= ICR_ETBREI; + } else + new_icr |= ICR_ETXTHI; + } + /* Check if transmission has completed */ + if ( isr & ISR_TXEMP_I) { + + /* Turn around and get ready to receive some data */ + idev->io.direction = IO_RECV; + new_icr |= ICR_ERBRI; + } + + /* Rx FIFO threshold or timeout */ + if ( isr & ISR_RXTH_I) { + w83977af_pio_receive( idev); + + /* Keep receiving */ + new_icr |= ICR_ERBRI; + } + return new_icr; +} + +/* + * Function pc87108_fir_interrupt (idev, eir) + * + * Handle MIR/FIR interrupt + * + */ +static __u8 w83977af_fir_interrupt( struct irda_device *idev, int isr) +{ + __u8 new_icr = 0; + __u8 set; + int iobase; + + DEBUG( 4, __FUNCTION__ "(), isr=%#x\n", isr); + + iobase = idev->io.iobase; + + set = inb( iobase+SSR); + + /* End of frame detected in FIFO */ + if ( isr & (ISR_FEND_I|ISR_FSF_I)) { + if ( w83977af_dma_receive_complete( idev)) { + + new_icr |= ICR_EFSFI; + } else { + /* DMA not finished yet */ + + /* Set timer value, resolution 1 ms */ + switch_bank( iobase, SET4); + outb( 0x01, iobase+TMRL); /* 1 ms */ + outb( 0x00, iobase+TMRH); + + /* Start timer */ + outb( IR_MSL_EN_TMR, iobase+IR_MSL); + + new_icr |= ICR_ETMRI; + } + } + /* Timer finished */ + if ( isr & ISR_TMR_I) { + /* Disable timer */ + switch_bank( iobase, SET4); + outb( 0, iobase+IR_MSL); + + /* Clear timer event */ + /* switch_bank(iobase, SET0); */ +/* outb( ASCR_CTE, iobase+ASCR); */ + + /* Check if this is a TX timer interrupt */ + if ( idev->io.direction == IO_XMIT) { + w83977af_dma_write( idev, iobase); + + new_icr |= ICR_EDMAI; + } else { + /* Check if DMA has now finished */ + w83977af_dma_receive_complete( idev); + + new_icr |= ICR_EFSFI; + } + } + /* Finished with DMA */ + if ( isr & ISR_DMA_I) { + w83977af_dma_xmit_complete( idev); + + /* Check if there are more frames to be transmitted */ + if ( irda_device_txqueue_empty( idev)) { + + /* Prepare for receive */ + w83977af_dma_receive( idev); + new_icr = ICR_EFSFI; + } + } + + /* Restore set */ + outb( set, iobase+SSR); + + return new_icr; +} + +/* + * Function pc87108_interrupt (irq, dev_id, regs) + * + * An interrupt from the chip has arrived. Time to do some work + * + */ +static void w83977af_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + __u8 set, icr, isr; + int iobase; + + struct irda_device *idev = (struct irda_device *) dev_id; + + if ( idev == NULL) { + printk( KERN_WARNING "%s: irq %d for unknown device.\n", + driver_name, irq); + return; + } + + idev->netdev.interrupt = 1; + + iobase = idev->io.iobase; + + /* Save current bank */ + set = inb( iobase+SSR); + switch_bank( iobase, SET0); + + icr = inb( iobase+ICR); + isr = inb( iobase+ISR) & icr; /* Mask out the interesting ones */ + + outb( 0, iobase+ICR); /* Disable interrupts */ + + if ( isr) { + /* Dispatch interrupt handler for the current speed */ + if ( idev->io.baudrate > 115200) + icr = w83977af_fir_interrupt( idev, isr); + else + icr = w83977af_sir_interrupt( idev, isr); + } + + outb( icr, iobase+ICR); /* Restore (new) interrupts */ + outb( set, iobase+SSR); /* Restore bank register */ + + idev->netdev.interrupt = 0; +} + +/* + * Function w83977af_wait_until_sent (idev) + * + * This function should put the current thread to sleep until all data + * have been sent, so it is safe to f.eks. change the speed. + */ +static void w83977af_wait_until_sent( struct irda_device *idev) +{ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(6); +} + +/* + * Function w83977af_is_receiving (idev) + * + * Return TRUE is we are currently receiving a frame + * + */ +static int w83977af_is_receiving( struct irda_device *idev) +{ + int status = FALSE; + int iobase; + __u8 set; + + ASSERT( idev != NULL, return FALSE;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return FALSE;); + + if ( idev->io.baudrate > 115200) { + iobase = idev->io.iobase; + + /* Check if rx FIFO is not empty */ + set = inb( iobase+SSR); + switch_bank( iobase, SET2); + if (( inb( iobase+RXFDTH) & 0x3f) != 0) { + /* We are receiving something */ + status = TRUE; + } + outb( set, iobase+SSR); + } else + status = ( idev->rx_buff.state != OUTSIDE_FRAME); + + return status; +} + +/* + * Function w83977af_net_init (dev) + * + * + * + */ +static int w83977af_net_init( struct device *dev) +{ + DEBUG( 0, __FUNCTION__ "()\n"); + + /* Set up to be a normal IrDA network device driver */ + irda_device_setup( dev); + + /* Insert overrides below this line! */ + + return 0; +} + + +/* + * Function w83977af_net_open (dev) + * + * Start the device + * + */ +static int w83977af_net_open( struct device *dev) +{ + struct irda_device *idev; + int iobase; + __u8 set; + + DEBUG( 0, __FUNCTION__ "()\n"); + + ASSERT( dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + if (request_irq( idev->io.irq, w83977af_interrupt, 0, idev->name, + (void *) idev)) { + return -EAGAIN; + } + /* + * Always allocate the DMA channel after the IRQ, + * and clean up on failure. + */ + if (request_dma(idev->io.dma, idev->name)) { + free_irq( idev->io.irq, idev); + return -EAGAIN; + } + + /* Ready to play! */ + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + /* Save current set */ + set = inb( iobase+SSR); + + /* Enable some interrupts so we can receive frames again */ + switch_bank(iobase, SET0); + if ( idev->io.baudrate > 115200) { + outb( ICR_EFSFI, iobase+ICR); + w83977af_dma_receive( idev); + } else + outb( ICR_ERBRI, iobase+ICR); + + /* Restore bank register */ + outb( set, iobase+SSR); + + MOD_INC_USE_COUNT; + + return 0; +} + +/* + * Function w83977af_net_close (dev) + * + * Stop the device + * + */ +static int w83977af_net_close(struct device *dev) +{ + struct irda_device *idev; + int iobase; + __u8 set; + + DEBUG( 0, __FUNCTION__ "()\n"); + + /* Stop device */ + dev->tbusy = 1; + dev->start = 0; + + ASSERT( dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + disable_dma( idev->io.dma); + + /* Save current set */ + set = inb( iobase+SSR); + + /* Disable interrupts */ + switch_bank( iobase, SET0); + outb( 0, iobase+ICR); + + free_irq( idev->io.irq, idev); + free_dma( idev->io.dma); + + /* Restore bank register */ + outb( set, iobase+SSR); + + MOD_DEC_USE_COUNT; + + return 0; +} + +#ifdef MODULE + +/* + * Function init_module (void) + * + * + * + */ +int init_module(void) +{ + w83977af_init(); + + return(0); +} + +/* + * Function cleanup_module (void) + * + * + * + */ +void cleanup_module(void) +{ + w83977af_cleanup(); +} + +#endif |