/*
   atarisio V0.02 alpha
   a kernel module for handling the Atari 8bit SIO protocol

   Copyright (C) 2002, 2003 Matthias Reichl <hias@horus.com>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <linux/config.h>
#include <linux/version.h>

#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
#define MODVERSIONS
#endif

#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif

#include <asm/atomic.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/io.h>

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>

#include <linux/ioport.h>
#include <linux/init.h>


#include <linux/poll.h>
#include <linux/serial_reg.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>

#include "atarisio.h"


/*
#define ATARISIO_DEBUG
#define ATARISIO_DEBUG_IRQ
*/

/*
#define ATARISIO_DEBUG_NOISY
#define ATARISIO_DEBUG_IRQ_NOISY
#define ATARISIO_DEBUG_VERY_NOISY
#define ATARISIO_DEBUG_IRQ_VERY_NOISY
*/

/*
 * if ATARISIO_EARLY_NOTIFICATION is defined, poll will indicate a
 * pending command frame at the very start of the command frame,
 * not when it's completely received
 */
#define ATARISIO_EARLY_NOTIFICATION

#define PRINTK(x...) printk(NAME ": " x)

#ifdef ATARISIO_DEBUG
#define DPRINTK(x...) printk(NAME ": " x)
#else
#define DPRINTK(x...) do { } while(0)
#endif

#ifdef ATARISIO_DEBUG_NOISY
#define DDPRINTK(x...) printk(NAME ": " x)
#else
#define DDPRINTK(x...) do { } while(0)
#endif

#ifdef ATARISIO_DEBUG_VERY_NOISY
#define DDDPRINTK(x...) printk(NAME ": " x)
#else
#define DDDPRINTK(x...) do { } while(0)
#endif

#ifdef ATARISIO_DEBUG_IRQ
#define IDPRINTK(x...) printk(NAME ": " x)
#else
#define IDPRINTK(x...) do { } while(0)
#endif

#ifdef ATARISIO_DEBUG_IRQ_NOISY
#define IDDPRINTK(x...) printk(NAME ": " x)
#else
#define IDDPRINTK(x...) do { } while(0)
#endif

#ifdef ATARISIO_DEBUG_IRQ_VERY_NOISY
#define IDDDPRINTK(x...) printk(NAME ": " x)
#else
#define IDDDPRINTK(x...) do { } while(0)
#endif


/*
#define ATARISIO_DEBUG_TIMEOUTS
#define ATARISIO_DEBUG_TIMEOUT_WAIT (HZ/2)
*/

#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif
#ifdef MODULE_AUTHOR
MODULE_AUTHOR("Matthias Reichl <hias@horus.com>");
#endif
#ifdef MODULE_DESCRIPTION
MODULE_DESCRIPTION("Serial Atari 8bit SIO driver module");
#endif

/* 
 * currently we use major 10, minor 240, which is a reserved local
 * number within the miscdevice block
 */

#define ATARISIO_MINOR 240
#define NAME "atarisio"

/*
 * module parameters:
 * io:  the base address of the 16550
 * irq: interrupt number
 */
static int io = 0x3f8;
static int irq = 4;

MODULE_PARM(io,"i");
MODULE_PARM(irq,"i");

/*
 * constants of the SIO protocol
 */

#define MAX_COMMAND_FRAME_RETRIES 14
#define COMMAND_FRAME_ACK_CHAR 0x41
#define COMMAND_FRAME_NAK_CHAR 0x4e

#define OPERATION_COMPLETE_CHAR 0x43
#define OPERATION_ERROR_CHAR 0x45
#define DATA_FRAME_ACK_CHAR 0x41
#define DATA_FRAME_NAK_CHAR 0x4e


/*
 * delays, according to the SIO specs, in uSecs
 */

#define DELAY_T0 900
#define DELAY_T1 850
#define DELAY_T2_MIN 10
#define DELAY_T2_MAX 20000
#define DELAY_T3_MIN 1000
#define DELAY_T3_MAX 1600
#define DELAY_T4_MIN 450
#define DELAY_T4_MAX 16000
#define DELAY_T5_MIN 250

/* this one is in mSecs, one jiffy (10 mSec on i386) is minimum,
 * try higher values for slower machines. The default of 30 mSecs
 * should be sufficient for most PCs
 */
#define DELAY_RECEIVE_HEADROOM 30
#define DELAY_SEND_HEADROOM 30

#define MODE_1050_2_PC 0
#define MODE_SIOSERVER 1

static volatile int current_mode = MODE_1050_2_PC;

/*
 * helper functions to access the 16550
 */
static inline void serial_out(unsigned int offset, u8 value)
{
	if (offset >=8) {
		DPRINTK("illegal offset in serial_out\n");
	} else {
		outb(value, io+offset);
	}
}

static inline u8 serial_in(unsigned int offset)
{
	if (offset >=8) {
		DPRINTK("illegal offset in serial_in\n");
		return 0;
	} else {
		return inb(io+offset);
	}
}

/*
 * IO buffers for the intterupt handlers
 */

#define IOBUF_LENGTH 8200
/*#define IOBUF_LENGTH 300*/

#define MAX_SIO_DATA_LENGTH (IOBUF_LENGTH-2)
/* circular buffers can hold IOBUF_LENGTH-1 chars, reserve one further
 * char for the checksum
 */

static volatile struct rx_buf_struct {
	unsigned char buf[IOBUF_LENGTH];
	unsigned int head;
	unsigned int tail;
	int wakeup_len;
} rx_buf;

spinlock_t rx_lock = SPIN_LOCK_UNLOCKED;

static volatile struct tx_buf_struct {
	unsigned char buf[IOBUF_LENGTH];
	unsigned int head;
	unsigned int tail;
} tx_buf;

spinlock_t tx_lock = SPIN_LOCK_UNLOCKED;

static volatile struct cmdframe_buf_struct {
	unsigned char buf[5];
	unsigned int is_valid;
	unsigned long reception_time; /* in jiffies */
	size_t missed_count;
} cmdframe_buf;

spinlock_t cmdframe_lock = SPIN_LOCK_UNLOCKED;


static volatile struct tmp_cmdframe_struct {
	unsigned char buf[5];
	unsigned char char_errors;
	int pos;
	int receiving;
	int cmd_counter;
	unsigned int error_counter;
} tmp_cmdframe = { {0,0,0,0,0},0,0,0,0,0};

spinlock_t tmp_cmdframe_lock = SPIN_LOCK_UNLOCKED;

/*
 * wait queues
 */

wait_queue_head_t rx_queue;
wait_queue_head_t tx_queue;
wait_queue_head_t cmdframe_queue;

#ifdef ATARISIO_EARLY_NOTIFICATION
wait_queue_head_t tmp_cmdframe_queue;
#endif

static inline void reset_rx_buf()
{
	unsigned long flags;
	spin_lock_irqsave(&rx_lock, flags);

	rx_buf.head=rx_buf.tail=0;
	rx_buf.wakeup_len=-1;

	spin_unlock_irqrestore(&rx_lock, flags);
}

static inline void reset_tx_buf()
{
	unsigned long flags;
	spin_lock_irqsave(&tx_lock, flags);

	tx_buf.head=tx_buf.tail=0;

	spin_unlock_irqrestore(&tx_lock, flags);
}

static inline void reset_cmdframe_buf()
{
	unsigned long flags;
	spin_lock_irqsave(&cmdframe_lock, flags);

	cmdframe_buf.is_valid = 0;
	cmdframe_buf.missed_count=0;

	spin_unlock_irqrestore(&cmdframe_lock, flags);
}

/*
 * configuration of the serial port
 */

static volatile struct serial_config_struct {
	unsigned int baudrate;
	unsigned char IER;
	unsigned char MCR;
	unsigned char LCR;
	unsigned long baud_base;
} serial_config;

spinlock_t serial_config_lock = SPIN_LOCK_UNLOCKED;

/*
 * The command line has to be set to LOW during the
 * transmission of a command frame and HI otherwise
 */
static unsigned char command_line_mask = ~UART_MCR_RTS;
static unsigned char command_line_low = UART_MCR_RTS;
static unsigned char command_line_high = 0;

static unsigned int default_baudrate = 19200;
static int do_autobaud = 0;

/*
 * indicate the start of a command frame
 */
static inline void set_command_line()
{
	serial_config.MCR = (serial_config.MCR & command_line_mask) | command_line_low;
	serial_out(UART_MCR, serial_config.MCR);
}

/*
 * indicate the end of a command frame
 */
static inline void clear_command_line()
{
	serial_config.MCR = (serial_config.MCR & command_line_mask) | command_line_high;
	serial_out(UART_MCR, serial_config.MCR);
}

static inline int set_baudrate(unsigned int baudrate)
{
	unsigned long flags;
	unsigned int divisor;

	spin_lock_irqsave(&serial_config_lock, flags);

	serial_config.baudrate = baudrate;
	divisor = serial_config.baud_base / baudrate;

	serial_out(UART_LCR, serial_config.LCR | UART_LCR_DLAB);
	serial_out(UART_DLL, divisor & 0xff);
	serial_out(UART_DLM, divisor >> 8);
	serial_out(UART_LCR, serial_config.LCR);

	spin_unlock_irqrestore(&serial_config_lock, flags);

	return 0;
}

static inline unsigned char calculate_checksum(const unsigned char* circ_buf, int start, int len, int total_len)
{
	unsigned int chksum=0;
	int i;
	for (i=0;i<len;i++) {
		chksum += circ_buf[(start+i) % total_len];
		if (chksum >= 0x100) {
			chksum = (chksum & 0xff) + 1;
		}
	}
	return chksum;
}

static inline void receive_chars()
{
	unsigned char c,lsr;
	int do_wakeup=0;
	spin_lock(&rx_lock);
	spin_lock(&tmp_cmdframe_lock);
	while ( (lsr=serial_in(UART_LSR)) & UART_LSR_DR ) {
		c = serial_in(UART_RX);



		if (lsr & UART_LSR_FE) {
			tmp_cmdframe.char_errors |= (1<<tmp_cmdframe.pos);
			IDPRINTK("got framing error\n");
			if (tmp_cmdframe.receiving) {
				tmp_cmdframe.error_counter++;
			}
		} else {

			tmp_cmdframe.char_errors &= ~(1<<tmp_cmdframe.pos);

			/*
		 	* always remember the last five characters, they could
		 	* have been a command frame!
		 	*/
			tmp_cmdframe.buf[tmp_cmdframe.pos] = c;
			tmp_cmdframe.pos = (tmp_cmdframe.pos + 1) % 5;

			if (tmp_cmdframe.receiving) {
				tmp_cmdframe.cmd_counter++;
			}

			if ( (rx_buf.head+1) % IOBUF_LENGTH != rx_buf.tail ) {
				rx_buf.buf[rx_buf.head] = c;
				rx_buf.head = (rx_buf.head+1) % IOBUF_LENGTH;
				if ( (rx_buf.head + IOBUF_LENGTH - rx_buf.tail) % IOBUF_LENGTH == rx_buf.wakeup_len) {
					do_wakeup=1;
				}
				IDDDPRINTK("received character 0x%02x\n",c);
			} else {
				IDDDPRINTK("sinking character 0x%02x\n",c);
			}
		}
	}
	spin_unlock(&tmp_cmdframe_lock);
	spin_unlock(&rx_lock);
	if ( do_wakeup ) {
		wake_up(&rx_queue);
	}
}

static inline void send_chars()
{
	int do_wakeup=0;
	int count = 16; /* FIFO can hold 16 chars at maximum */
	unsigned char lsr;

	lsr = serial_in(UART_LSR);
	if (lsr & UART_LSR_THRE) {
		spin_lock(&tx_lock);
		while ( (count > 0) && (tx_buf.head != tx_buf.tail) ) {
			IDDDPRINTK("transmit char 0x%02x\n",tx_buf.buf[tx_buf.head]);

			serial_out(UART_TX, tx_buf.buf[tx_buf.tail]);
			tx_buf.tail = (tx_buf.tail+1) % IOBUF_LENGTH;
			count--;
		};
		if ( tx_buf.head == tx_buf.tail ) {
			/* end of TX-buffer reached, disable further TX-interrupts */
			serial_config.IER &= ~UART_IER_THRI;
			serial_out(UART_IER, serial_config.IER);
			do_wakeup=1;
		}
		spin_unlock(&tx_lock);
		if (do_wakeup) {
			wake_up(&tx_queue);
		}
	}
}

static inline void try_switchbaud()
{
	unsigned int baud = serial_config.baudrate;

	if (do_autobaud) {
		if (baud == 19200) {
			baud = 57600;
		/*
		} else if (baud == 57600) {
			baud = 38400;
		*/
		} else {
			baud = 19200;
		}

		IDPRINTK("switching to %d baud\n",baud);
	}

	set_baudrate(baud);
}

static inline void check_modem_lines_before_receive(unsigned char old_msr, unsigned char new_msr)
{
	if (current_mode == MODE_SIOSERVER) {
		if ( (new_msr & UART_MSR_RI) != (old_msr & UART_MSR_RI)) {
			IDDDPRINTK("msr changed from 0x%02x to 0x%02x [%ld]\n",old_msr, new_msr, jiffies);
			if (new_msr & UART_MSR_RI) {
				/* start of a new command frame */
				spin_lock(&cmdframe_lock);
				spin_lock(&tmp_cmdframe_lock);
				if (cmdframe_buf.is_valid) {
					IDPRINTK("invalidating command frame (detected new frame) %d\n", 
						cmdframe_buf.missed_count);
					cmdframe_buf.missed_count++;
					cmdframe_buf.is_valid = 0;
				}
				if (tmp_cmdframe.receiving) {
					IDPRINTK("restarted reception of command frame (detected new frame)\n");
					tmp_cmdframe.error_counter++;
				}
				tmp_cmdframe.receiving = 1;
				tmp_cmdframe.cmd_counter=0;
				spin_unlock(&tmp_cmdframe_lock);
				spin_unlock(&cmdframe_lock);

#ifdef ATARISIO_EARLY_NOTIFICATION
				wake_up(&tmp_cmdframe_queue);
#endif
			}
		}
	}
}

#define DEBUG_PRINT_LAST_FIVE \
	"last 5: %02x %02x %02x %02x %02x err: 0x%02x [cnt=%d] [%ld]\n",\
	tmp_cmdframe.buf[(tmp_cmdframe.pos)],\
	tmp_cmdframe.buf[(tmp_cmdframe.pos+1)%5],\
	tmp_cmdframe.buf[(tmp_cmdframe.pos+2)%5],\
	tmp_cmdframe.buf[(tmp_cmdframe.pos+3)%5],\
	tmp_cmdframe.buf[(tmp_cmdframe.pos+4)%5],\
	tmp_cmdframe.cmd_counter,\
	tmp_cmdframe.char_errors,\
	jiffies

static inline void check_modem_lines_after_receive(unsigned char old_msr, unsigned char new_msr)
{
	int do_wakeup = 0;
	int OK = 1;
	unsigned char checksum;


	if (current_mode == MODE_SIOSERVER) {
		/*
		 * a command frame should have been received, so check the last
		 * 5 bytes
		 */
		if (new_msr & UART_MSR_TERI) {
			spin_lock(&tmp_cmdframe_lock);
			spin_lock(&rx_lock);
			spin_lock(&cmdframe_lock);


			rx_buf.tail = rx_buf.head; /* flush input buffer */
			tx_buf.tail = tx_buf.head; /* flush output buffer */

			if (tmp_cmdframe.char_errors & 0x1f) {
				IDPRINTK("got framing error in command frame\n");
				IDDPRINTK(DEBUG_PRINT_LAST_FIVE);
				tmp_cmdframe.error_counter++;
				OK = 0;
			}
			if (tmp_cmdframe.cmd_counter != 5) {
				if (tmp_cmdframe.cmd_counter <=3 || tmp_cmdframe.cmd_counter >=7) {
					tmp_cmdframe.error_counter+=5;
				} else {
					tmp_cmdframe.error_counter++;
				}
				IDDPRINTK("got %d chars for this command frame\n",
					tmp_cmdframe.cmd_counter);
				OK = 0;
			}
			if (! tmp_cmdframe.receiving) {
			 	IDPRINTK("indicated end of command frame, but I'm not currently receiving\n");
			}
			if (cmdframe_buf.is_valid) {
				IDPRINTK("invalidated command frame %d\n", 
					cmdframe_buf.missed_count);
				cmdframe_buf.missed_count++;
			}

			if (OK) {
				IDDDPRINTK(DEBUG_PRINT_LAST_FIVE);

				checksum = calculate_checksum((unsigned char*)tmp_cmdframe.buf, tmp_cmdframe.pos, 4, 5);

				if (tmp_cmdframe.buf[(tmp_cmdframe.pos + 4) % 5] == checksum) {
					do_wakeup = 1;
					IDDPRINTK("found command frame [%ld]\n", jiffies);

					cmdframe_buf.is_valid = 1;
					cmdframe_buf.buf[0] = tmp_cmdframe.buf[(tmp_cmdframe.pos)];
					cmdframe_buf.buf[1] = tmp_cmdframe.buf[(tmp_cmdframe.pos+1) % 5];
					cmdframe_buf.buf[2] = tmp_cmdframe.buf[(tmp_cmdframe.pos+2) % 5];
					cmdframe_buf.buf[3] = tmp_cmdframe.buf[(tmp_cmdframe.pos+3) % 5];
					cmdframe_buf.reception_time = jiffies;

					tmp_cmdframe.buf[0]=0;
					tmp_cmdframe.buf[1]=0;
					tmp_cmdframe.buf[2]=0;
					tmp_cmdframe.buf[3]=0;
					tmp_cmdframe.buf[4]=0;
					tmp_cmdframe.pos=0;
					tmp_cmdframe.char_errors=0;
					tmp_cmdframe.cmd_counter=0;
					tmp_cmdframe.error_counter = 0;

				} else {
					cmdframe_buf.is_valid = 0;
					/* invalidate old command frame due to reception of a new one */
					IDPRINTK("possible command frame checksum error [%ld]\n",jiffies);
					IDDPRINTK(DEBUG_PRINT_LAST_FIVE);
					tmp_cmdframe.error_counter++;
					tmp_cmdframe.buf[0]=0;
					tmp_cmdframe.buf[1]=0;
					tmp_cmdframe.buf[2]=0;
					tmp_cmdframe.buf[3]=0;
					tmp_cmdframe.buf[4]=0;
					tmp_cmdframe.pos=0;
					tmp_cmdframe.cmd_counter=0;
					tmp_cmdframe.char_errors=0;
				}
			}

			if (tmp_cmdframe.error_counter >= 3) {
				IDPRINTK("error counter exceeded limit [%d]\n",tmp_cmdframe.error_counter);
				try_switchbaud();
				tmp_cmdframe.error_counter=0;
			}
			tmp_cmdframe.receiving=0;

			spin_unlock(&rx_lock);
			spin_unlock(&tmp_cmdframe_lock);
			spin_unlock(&cmdframe_lock);

			if (do_wakeup) {
				wake_up(&cmdframe_queue);
			}
		}
	}
}

/*
 * the interrupt handler
 */

static void atarisio_interrupt(int irq, void* dev_id, struct pt_regs* regs)
{
	unsigned char iir,msr;
	static unsigned char last_msr=0;

	while (! ((iir=serial_in(UART_IIR)) & UART_IIR_NO_INT)) {

		msr = serial_in(UART_MSR);

		check_modem_lines_before_receive(last_msr, msr);

		receive_chars();

		check_modem_lines_after_receive(last_msr, msr);

		send_chars();

		last_msr = msr;
	}
}

static inline void initiate_send()
{
	DDDPRINTK("initiate_send: tx-head = %d tx-tail = %d\n", tx_buf.head, tx_buf.tail);

	if ( (tx_buf.head != tx_buf.tail)
	     && !(serial_config.IER & UART_IER_THRI) ) {

		DDDPRINTK("enabling TX-interrupt\n");

		serial_config.IER |= UART_IER_THRI;
		serial_out(UART_IER, serial_config.IER);
	}
}

static inline int wait_send(int timeout) /* timeout in msec */
{
	wait_queue_t wait;
	int got_timeout=0;
	signed long timeout_jiffies;
	signed long expire;
	int max_jiffies;

	timeout_jiffies = timeout*HZ/1000;
	if (timeout_jiffies <= 0) {
		timeout_jiffies = 1;
	}

	expire = timeout_jiffies;
	max_jiffies = jiffies + timeout_jiffies;

	/*
	 * first wait for the interrupt notification, telling us
	 * that the transmit buffer is empty
	 */
	init_waitqueue_entry(&wait, current);

	add_wait_queue(&tx_queue, &wait);
	while (1) {
		current->state = TASK_INTERRUPTIBLE;
		if (tx_buf.head == tx_buf.tail) {
			break;
		}
		expire = schedule_timeout(expire);
		if (expire==0) {
			got_timeout=1;
			break;
		}
		if (signal_pending(current)) {
			current->state=TASK_RUNNING;
			remove_wait_queue(&tx_queue, &wait);
			return -EINTR;
		}
	}
	current->state=TASK_RUNNING;
	remove_wait_queue(&tx_queue, &wait);

	if (got_timeout) {
		DPRINTK("timeout expired in wait_send\n");
		return 1;
	}

	/*
	 * now wait for the 16550 FIFO and transmitter to empty
	 */
	while ((jiffies < max_jiffies) && !(serial_in(UART_LSR) & UART_LSR_TEMT)) {
	}
	if (jiffies >= max_jiffies) {
		DPRINTK("timeout expired in wait_send / TEMT\n");
		return 1;
	} else {
		return 0;
	}
}

static inline int wait_receive(int len, int timeout)
{
	wait_queue_t wait;

	int got_timeout=0;
	int no_received_chars=0;
	signed long timeout_jiffies;
	signed long expire;
#ifdef ATARISIO_DEBUG
	signed long jiffies_start = jiffies;
#endif

	timeout_jiffies = timeout*HZ/1000;
	if (timeout_jiffies <= 0) {
		timeout_jiffies = 1;
	}

	expire = timeout_jiffies;

	init_waitqueue_entry(&wait, current);

	rx_buf.wakeup_len = len;
	add_wait_queue(&rx_queue, &wait);
	while (1) {
		current->state = TASK_INTERRUPTIBLE;
		if ( (no_received_chars=(rx_buf.head+IOBUF_LENGTH - rx_buf.tail) % IOBUF_LENGTH) >=len) {
			break;
		}
		expire = schedule_timeout(expire);
		if (expire==0) {
			got_timeout=1;
			break;
		}
		if (signal_pending(current)) {
			current->state=TASK_RUNNING;
			remove_wait_queue(&rx_queue, &wait);
			return -EINTR;
		}
	}
	current->state=TASK_RUNNING;
	remove_wait_queue(&rx_queue, &wait);

#ifdef ATARISIO_DEBUG_TIMEOUTS
	if (got_timeout) {
		got_timeout = 0;
		DPRINTK("timeout in wait_receive [wanted %d got %d time %ld], waiting again\n",
				len, no_received_chars, jiffies-jiffies_start);
		rx_buf.wakeup_len = len;
		expire = ATARISIO_DEBUG_TIMEOUT_WAIT;
		add_wait_queue(&rx_queue, &wait);
		while(1) {
			current->state = TASK_INTERRUPTIBLE;
			if ((no_received_chars=(rx_buf.head+IOBUF_LENGTH - rx_buf.tail) % IOBUF_LENGTH) >=len) {
				break;
			}
			expire = schedule_timeout(expire);
			if (expire==0) {
				got_timeout = 1;
				break;
			}
			if (signal_pending(current)) {
				current->state=TASK_RUNNING;
				remove_wait_queue(&rx_queue, &wait);
				return -EINTR;
			}
		}
		current->state=TASK_RUNNING;
		remove_wait_queue(&rx_queue, &wait);
	}
#endif
	rx_buf.wakeup_len=-1;
	if (got_timeout) {
		DPRINTK("timeout in wait_receive [wanted %d got %d time %ld]\n",
				len, no_received_chars, jiffies - jiffies_start);
		return no_received_chars;
	} else {
		return len;
	}
}

static inline int send_command_frame(SIO_parameters* params)
{
	int retry = 0;
	int last_err = 0;
	unsigned long flags;
	int i;
	unsigned char c;
	int w;

	unsigned char cmd_frame[5];

	cmd_frame[0] = params->device_id;
	cmd_frame[1] = params->command;
	cmd_frame[2] = params->aux1;
	cmd_frame[3] = params->aux2;
	cmd_frame[4] = calculate_checksum(cmd_frame, 0, 4, 5);

	while (retry < MAX_COMMAND_FRAME_RETRIES) {
		DDPRINTK("initiating command frame [%ld]\n",jiffies);

		set_command_line();
		udelay(DELAY_T0);

		spin_lock_irqsave(&tx_lock, flags);
		if (tx_buf.head != tx_buf.tail) {
			DDPRINTK("clearing tx bufffer\n");
			tx_buf.head = tx_buf.tail;
		}
		for (i=0;i<5;i++) {
			tx_buf.buf[tx_buf.head+i] = cmd_frame[i];
		}
		tx_buf.head = (tx_buf.head+5) % IOBUF_LENGTH;

		rx_buf.tail = rx_buf.head; /* clear rx-buffer */
		initiate_send();
		spin_unlock_irqrestore(&tx_lock, flags);
		if ((w=wait_send(5+DELAY_SEND_HEADROOM))) {
			if (w < 0) {
				DPRINTK("sending command frame returned %d\n",w);
				return w;
			} else {
				DPRINTK("sending command frame timed out\n");

				last_err = ATARISIO_COMMAND_TIMEOUT;
				goto again;
			}
		}

		udelay(DELAY_T1);
		clear_command_line();
		udelay(DELAY_T2_MIN);

		if ((w=wait_receive(1, 1 + DELAY_T2_MAX/1000 + DELAY_RECEIVE_HEADROOM)) != 1) {
			if (w < 0) {
				DPRINTK("wait_receive returned %d\n",w);
				return w;
			} else {
				DPRINTK("waiting for command frame ACK timed out [%ld]\n", jiffies);

				last_err = ATARISIO_COMMAND_TIMEOUT;
				goto again;
			}
		}
		c = rx_buf.buf[rx_buf.tail];
		rx_buf.tail = (rx_buf.tail+1) % IOBUF_LENGTH;
		if (c==COMMAND_FRAME_ACK_CHAR) {
			break;
		} else if (c==COMMAND_FRAME_NAK_CHAR) {
			DPRINTK("got command frame NAK\n");
			last_err = ATARISIO_COMMAND_NAK;
		} else {
			DPRINTK("illegal response to command frame: 0x%02x\n",c);
		}
		if (retry+1 < MAX_COMMAND_FRAME_RETRIES) {
			DDPRINTK("retrying command frame\n");
		}

again:
		clear_command_line();
		{
			signed long t = HZ / 5;
			current->state=TASK_INTERRUPTIBLE;
			while (t) {
				t = schedule_timeout(t);
				if (signal_pending(current)) {
					current->state=TASK_RUNNING;
					return -EINTR;
				}
			}
			current->state=TASK_RUNNING;
		}
		retry++;
	}
		
	if (retry == MAX_COMMAND_FRAME_RETRIES) {
		DPRINTK("request timed out - no ACK to command frame\n");

		if (last_err) {
			return last_err;
		} else {
			return ATARTSIO_UNKNOWN_ERROR;
		}
	} else {
		DDPRINTK("got ACK for command frame - ready to proceed\n");
		return 0;
	}
}

static int setup_send_data_frame(size_t data_length, unsigned char* user_buffer)
{
	unsigned long flags;
	int len, remain;
	unsigned char checksum;

	if (data_length == 0) {
		return 1;
	}

	spin_lock_irqsave(&tx_lock, flags);
	tx_buf.head = tx_buf.tail;

	len = IOBUF_LENGTH - tx_buf.head;
	if (len > data_length) {
		len = data_length;
	}
	if ( copy_from_user((unsigned char*) (&(tx_buf.buf[tx_buf.head])), 
			user_buffer,
			len) ) {
		return -EFAULT;
	}
	remain = data_length - len;
	if (remain>0) {
		if ( copy_from_user((unsigned char*) tx_buf.buf,
				user_buffer+len,
				remain) ) {
			return -EFAULT;
		}
	}

	checksum = calculate_checksum((unsigned char*)(tx_buf.buf),
		       	tx_buf.head, data_length, IOBUF_LENGTH);

	tx_buf.buf[(tx_buf.head+data_length) % IOBUF_LENGTH] = checksum;

	tx_buf.head = (tx_buf.head + data_length + 1) % IOBUF_LENGTH;
	spin_unlock_irqrestore(&tx_lock, flags);

	return 0;
}

static int copy_received_data_to_user(size_t data_length, unsigned char* user_buffer)
{
	int len, remain;
	if (data_length == 0) {
		return -1;
	}
	/* if we received any bytes, copy the to the user buffer, even
	 * if we ran into a timeout and got less than we wanted
	 */
	len = IOBUF_LENGTH - rx_buf.tail;
	if (len > data_length) {
		len = data_length;
	}
	if ( copy_to_user(user_buffer,
			 (unsigned char*) (&(rx_buf.buf[rx_buf.tail])),
			 len) ) {
		return -EFAULT;
	}
	remain = data_length - len;
	if (remain>0) {
		if ( copy_to_user(user_buffer+len,
				 (unsigned char*) rx_buf.buf,
				 remain) ) {
			return -EFAULT;
		}
	}
	return 0;
}

static int perform_sio_receive_data(SIO_parameters * sio_params)
{
	int received_len, copy_len;
        int ret=0,w;
	unsigned char c, checksum;
	/*
	 * read data from disk
	 */
	if ((w=wait_receive(1, sio_params->timeout*1000)) != 1) {
		if (w < 0) {
			DPRINTK("wait_receive returned %d\n",w);
			return w;
		} else {
			DPRINTK("command complete timed out\n");
			return ATARISIO_COMMAND_TIMEOUT;
		}
	}
	ret = 0;
	c = rx_buf.buf[rx_buf.tail];
	rx_buf.tail = (rx_buf.tail+1) % IOBUF_LENGTH;

	if (c != OPERATION_COMPLETE_CHAR) {
		if (c != OPERATION_ERROR_CHAR) {
			DPRINTK("wanted command complete, got: 0x%02x\n",c);
		}
		ret = ATARISIO_COMMAND_COMPLETE_ERROR;
	}

	if (sio_params->data_length) {
		/* receive data block and checksum*/
		received_len = wait_receive(sio_params->data_length+1,
			sio_params->data_length+1+DELAY_RECEIVE_HEADROOM);
			/* 1msec per char should be enough */

		if (received_len < 0) {
			DPRINTK("receiving data block returned %d\n",received_len);
			return received_len;
		}

		copy_len = sio_params->data_length;
		if (received_len < copy_len) {
			copy_len = received_len;
		}
		if (copy_len > 0) {
			if ( (ret=copy_received_data_to_user(copy_len, sio_params->data_buffer)) ) {
				return ret;
			}
		}

	        if (received_len != sio_params->data_length+1) {
			DPRINTK("receive data frame timed out [wanted %d got %d]\n",
					sio_params->data_length+1, received_len);
			return ATARISIO_COMMAND_TIMEOUT;
		}
		checksum = calculate_checksum((unsigned char*)(rx_buf.buf),
			       	rx_buf.tail, sio_params->data_length, IOBUF_LENGTH);

		if (rx_buf.buf[(rx_buf.tail+sio_params->data_length) % IOBUF_LENGTH ]
			!= checksum) {
			DPRINTK("checksum error : 0x%02x != 0x%02x\n",
				checksum,
				rx_buf.buf[(rx_buf.tail+sio_params->data_length) % IOBUF_LENGTH]);
			if (! ret) {
				ret = ATARISIO_CHECKSUM_ERROR;
			}
		}


		rx_buf.tail = (rx_buf.tail + sio_params->data_length + 1 ) % IOBUF_LENGTH;

	}

#ifdef ATARISIO_DEBUG_NOISY
	if (rx_buf.head != rx_buf.tail) {
		DDPRINTK("detected excess characters\n");
	}
#endif
	return ret;
}

static int perform_sio_send_data(SIO_parameters * sio_params)
{
        int ret=0,w;
	unsigned char c;

	/*
	 * read data from disk
	 */
	udelay(DELAY_T3_MIN);


	if (sio_params->data_length) {
		if ( (ret=setup_send_data_frame(sio_params->data_length, sio_params->data_buffer)) ) {
			return ret;
		}

		initiate_send();

		if ((w=wait_send(sio_params->data_length+1+DELAY_SEND_HEADROOM))) {
			if (w < 0) {
				DPRINTK("send data frame returned %d\n",w);
				return w;
			} else {
				DPRINTK("send data frame timed out\n");
				return ATARISIO_COMMAND_TIMEOUT;
			}
		}

		/*
		 * wait for data frame ack
		 */

		if ((w=wait_receive(1, 1+DELAY_T4_MAX/1000+DELAY_RECEIVE_HEADROOM)) != 1) {
			if (w < 0) {
				DPRINTK("receive data frame ACK returned %d\n",w);
				return w;
			} else {
				DPRINTK("receive data frame ACK timed out\n");
				return ATARISIO_COMMAND_TIMEOUT;
			}
		}
		c = rx_buf.buf[rx_buf.tail];
		rx_buf.tail = (rx_buf.tail + 1) % IOBUF_LENGTH;

		if (c != DATA_FRAME_ACK_CHAR) {
			if (c != DATA_FRAME_NAK_CHAR) {
				DPRINTK("wanted data ACK or ERROR, got: 0x%02x\n",c);
			}
			return ATARISIO_DATA_NAK;
		}

		/* 
		 * wait for command complete
		 */
		if ((w=wait_receive(1, sio_params->timeout*1000)) != 1) {
			if (w < 0) {
				DPRINTK("receive command complete returned %d\n",w);
				return w;
			} else {
				DPRINTK("receive command complete timed out\n");
				return ATARISIO_COMMAND_TIMEOUT;
			}
		}
		c = rx_buf.buf[rx_buf.tail];
		rx_buf.tail = (rx_buf.tail + 1) % IOBUF_LENGTH;

		if (c != OPERATION_COMPLETE_CHAR) {
			if (c != OPERATION_ERROR_CHAR) {
				DPRINTK("wanted command complete, got: 0x%02x\n",c);
			}
			ret = ATARISIO_COMMAND_COMPLETE_ERROR;
		}
	}
	return ret;
}

static inline int perform_sio(SIO_parameters * user_sio_params)
{
	int len;
	int ret=0;
	SIO_parameters sio_params;

	len = sizeof(sio_params);
	if (copy_from_user(&sio_params, user_sio_params, sizeof(sio_params))) {
		DPRINTK("copy_from_user failed for SIO parameters\n");
		return -EFAULT;
	}

	if (sio_params.data_length >= MAX_SIO_DATA_LENGTH) {
		return ATARISIO_ERROR_BLOCK_TOO_LONG;
	}

	DDDPRINTK("performing SIO:\n");
	DDDPRINTK("unit=%d cmd=%d dir=%d timeout=%d aux1=%d aux2=%d datalen=%d\n",
		sio_params.device_id, sio_params.command, sio_params.direction,
		sio_params.timeout, sio_params.aux1, sio_params.aux2, sio_params.data_length);

	if ((ret = send_command_frame(&sio_params))) {
		return ret;
	}

	if (sio_params.direction == 0) {
		ret = perform_sio_receive_data(&sio_params);
	} else {
		ret = perform_sio_send_data(&sio_params);
	}
	return ret;
}

static void set_1050_2_pc_mode()
{
	current_mode = MODE_1050_2_PC;
	command_line_mask = ~(UART_MCR_RTS | UART_MCR_DTR);
	command_line_low = UART_MCR_RTS;
	command_line_high = 0;
	clear_command_line();

	do_autobaud = 0;
	set_baudrate(default_baudrate);
	serial_config.IER &= ~UART_IER_MSI;
	serial_out(UART_IER, serial_config.IER);

	reset_cmdframe_buf();
}

static void set_prosystem_mode()
{
	int have_to_wait = !(serial_config.MCR & UART_MCR_RTS);

	current_mode = MODE_1050_2_PC;
	command_line_mask = ~(UART_MCR_RTS | UART_MCR_DTR);
	command_line_low = UART_MCR_RTS | UART_MCR_DTR;
	command_line_high = UART_MCR_RTS;
	clear_command_line();
	if (have_to_wait) {
		schedule_timeout(HZ/2); /* wait 0.5 sec. for voltage supply to stabilize */
	}
	do_autobaud = 0;
	set_baudrate(default_baudrate);
	serial_config.IER &= ~UART_IER_MSI;
	serial_out(UART_IER, serial_config.IER);

	reset_cmdframe_buf();
}

static void set_sioserver_mode()
{
	current_mode = MODE_SIOSERVER;
	set_baudrate(default_baudrate);
	serial_config.IER |= UART_IER_MSI;
	serial_out(UART_IER, serial_config.IER);

	reset_cmdframe_buf();
}

static int get_command_frame(unsigned long arg)
{
	wait_queue_t wait;
	SIO_command_frame frame;
	signed long expire = HZ;
	signed long end_time = jiffies + HZ;
	unsigned long flags;

	int got_timeout=0;
again:
	expire = end_time - jiffies;
	if (expire <= 0) {
		return 1;
	}

	init_waitqueue_entry(&wait, current);

	add_wait_queue(&cmdframe_queue, &wait);
	while (1) {
		current->state = TASK_INTERRUPTIBLE;
		if ( cmdframe_buf.is_valid ) {
			DDPRINTK("found valid command frame\n");
			break;
		}
		expire = schedule_timeout(expire);
		if (expire==0) {
			got_timeout=1;
			break;
		}
		if (signal_pending(current)) {
			current->state=TASK_RUNNING;
			remove_wait_queue(&cmdframe_queue, &wait);
			return -EINTR;
		}
	}
	current->state=TASK_RUNNING;
	remove_wait_queue(&cmdframe_queue, &wait);

	if (got_timeout) {
		return 1;
	}
	spin_lock_irqsave(&cmdframe_lock, flags);
	if (!cmdframe_buf.is_valid) {
		DPRINTK("command frame invalidated shortly after notification\n");
		spin_unlock_irqrestore(&cmdframe_lock, flags);
		goto again;
	}

	if (cmdframe_buf.reception_time+20*HZ/1000 < jiffies) {
		DPRINTK("received command frame is too old (%ld jiffies), retrying\n",
				jiffies - cmdframe_buf.reception_time);
		cmdframe_buf.is_valid = 0;
		spin_unlock_irqrestore(&cmdframe_lock, flags);
		goto again;
	}
	frame.device_id   = cmdframe_buf.buf[0];
	frame.command     = cmdframe_buf.buf[1];
	frame.aux1        = cmdframe_buf.buf[2];
	frame.aux2        = cmdframe_buf.buf[3];
	frame.missed_count = cmdframe_buf.missed_count;
	cmdframe_buf.missed_count=0;
	cmdframe_buf.is_valid = 0; /* already got that :-) */
	spin_unlock_irqrestore(&cmdframe_lock, flags);

	reset_tx_buf();
	reset_rx_buf();
	if (copy_to_user((SIO_command_frame*) arg, &frame, sizeof(SIO_command_frame)) ) {
		return -EFAULT;
	}
	return 0;
}

static int send_single_character(unsigned char c)
{
	int w;
	tx_buf.buf[tx_buf.head]=c;
	tx_buf.head = (tx_buf.head + 1) % IOBUF_LENGTH;
	initiate_send();
	if ((w=wait_send(1+DELAY_SEND_HEADROOM))) {
		if (w < 0) {
			return w;
		} else {
			return ATARISIO_COMMAND_TIMEOUT;
		}
	} else {
		return 0;
	}
}

static int perform_send_data_frame(unsigned long arg)
{
	SIO_data_frame frame;
	int ret,w;

	if (copy_from_user(&frame, (SIO_data_frame*) arg, sizeof(SIO_data_frame)) ) {
		return -EFAULT;
	}

	udelay(DELAY_T3_MIN);


	if (frame.data_length) {
		if ((ret=setup_send_data_frame(frame.data_length, frame.data_buffer)) ) {
			return ret;
		}

		initiate_send();

		if ((w=wait_send(frame.data_length+1+DELAY_SEND_HEADROOM))) {
			if (w < 0) {
				DPRINTK("send data frame returned %d\n",w);
				return w;
			} else {
				DPRINTK("send data frame timed out\n");
				return ATARISIO_COMMAND_TIMEOUT;
			}
		}
	}
	return 0;
}

static int perform_receive_data_frame(unsigned long arg)
{
	SIO_data_frame frame;
	int ret=0;
	int received_len, copy_len;
	unsigned char checksum;

	if (copy_from_user(&frame, (SIO_data_frame*) arg, sizeof(SIO_data_frame)) ) {
		return -EFAULT;
	}


	if (frame.data_length) {
		/* receive data block and checksum*/
		received_len = wait_receive(frame.data_length+1,
			frame.data_length+1+(DELAY_T3_MAX/1000)+DELAY_RECEIVE_HEADROOM);

		if (received_len < 0) {
			DPRINTK("receive data block returned %d\n",received_len);
			return received_len;
		}
		copy_len = frame.data_length;
		if (received_len < copy_len) {
			copy_len = received_len;
		}
		if (copy_len > 0) {
			if ( (ret=copy_received_data_to_user(copy_len, frame.data_buffer)) ) {
				return ret;
			}
		}

	        if (received_len != frame.data_length+1) {
			DPRINTK("receive data frame timed out [wanted %d got %d]\n",
					frame.data_length+1, received_len);
			return ATARISIO_COMMAND_TIMEOUT;
		}
		checksum = calculate_checksum((unsigned char*)(rx_buf.buf),
			       	rx_buf.tail, frame.data_length, IOBUF_LENGTH);

		if (rx_buf.buf[(rx_buf.tail+frame.data_length) % IOBUF_LENGTH ]
			!= checksum) {
			DPRINTK("checksum error : 0x%02x != 0x%02x\n",
				checksum,
				rx_buf.buf[(rx_buf.tail+frame.data_length) % IOBUF_LENGTH]);
			if (! ret) {
				ret = ATARISIO_CHECKSUM_ERROR;
			}
		}


		rx_buf.tail = (rx_buf.tail + frame.data_length + 1 ) % IOBUF_LENGTH;

		udelay(DELAY_T4_MIN);
		if (ret == 0) {
			ret=send_single_character(DATA_FRAME_ACK_CHAR);
		} else {
			send_single_character(DATA_FRAME_NAK_CHAR);
		}
	} else {
		return -EINVAL;
	}
#ifdef ATARISIO_DEBUG_NOISY
	if (rx_buf.head != rx_buf.tail) {
		DDPRINTK("detected excess characters\n");
	}
#endif
	return ret;
}

// check if a new command frame was received, before sending
// a command ACK/NAK
static inline int check_new_command_frame()
{
	if (cmdframe_buf.is_valid) {
		DPRINTK("new command frame has arrived...\n");
		return ATARISIO_COMMAND_TIMEOUT;
	} else {
		return 0;
	}
}

static int atarisio_ioctl(struct inode* inode, struct file* f,
 	unsigned int cmd, unsigned long arg)
{
	/*
	 * check for illegal commands
	 */
	int ret=0;

	if (_IOC_TYPE(cmd) != ATARISIO_IOC_MAGIC) return -ENOTTY;
	if (_IOC_NR(cmd) > ATARISIO_IOC_MAXNR) return -ENOTTY;

        DDDPRINTK("ioctl 0x%x , 0x%lx\n", cmd, arg);

	switch (cmd) {
	case ATARISIO_IOC_GET_VERSION:
		ret = ATARISIO_VERSION;
		break;
	case ATARISIO_IOC_SET_MODE:
		switch (arg) {
		case ATARISIO_MODE_1050_2_PC:
			set_1050_2_pc_mode();
			break;
		case ATARISIO_MODE_PROSYSTEM:
			set_prosystem_mode();
			break;
		case ATARISIO_MODE_SIOSERVER:
			set_sioserver_mode();
			break;
		default:
			return -EINVAL;
		}
		break;
	case ATARISIO_IOC_SET_BAUDRATE:
		default_baudrate = arg;
		ret = set_baudrate(default_baudrate);
		break;
	case ATARISIO_IOC_SET_AUTOBAUD:
		do_autobaud = arg;
		if (do_autobaud == 0) {
			ret = set_baudrate(default_baudrate);
		}
		break;
	case ATARISIO_IOC_DO_SIO:
		ret = perform_sio((SIO_parameters*)arg);
#ifdef ATARISIO_DEBUG_NOISY
		if (ret == 0) {
			DDPRINTK("sio successful\n");
		} else {
			DDPRINTK("sio failed : %d\n", ret);
		}
#endif
		break;
	case ATARISIO_IOC_GET_COMMAND_FRAME:
		ret = get_command_frame(arg);
		break;
	case ATARISIO_IOC_SEND_COMMAND_ACK:
		if ((ret = check_new_command_frame())) {
			break;
		}
		ret = send_single_character(COMMAND_FRAME_ACK_CHAR);
		break;
	case ATARISIO_IOC_SEND_COMMAND_NAK:
		if ((ret = check_new_command_frame())) {
			break;
		}
		ret = send_single_character(COMMAND_FRAME_NAK_CHAR);
		break;
	case ATARISIO_IOC_SEND_DATA_ACK:
		udelay(DELAY_T4_MIN);
		ret = send_single_character(COMMAND_FRAME_ACK_CHAR);
		break;
	case ATARISIO_IOC_SEND_DATA_NAK:
		udelay(DELAY_T4_MIN);
		ret = send_single_character(COMMAND_FRAME_NAK_CHAR);
		break;
	case ATARISIO_IOC_SEND_COMPLETE:
		udelay(DELAY_T5_MIN);
		ret = send_single_character(OPERATION_COMPLETE_CHAR);
		break;
	case ATARISIO_IOC_SEND_ERROR:
		udelay(DELAY_T5_MIN);
		ret = send_single_character(OPERATION_ERROR_CHAR);
		break;
	case ATARISIO_IOC_SEND_DATA_FRAME:
		ret = perform_send_data_frame(arg);
		break;
	case ATARISIO_IOC_RECEIVE_DATA_FRAME:
		ret = perform_receive_data_frame(arg);
		break;
	default:
		return -EINVAL;
	}
	return ret;
}

/*
static void probe_interrupts()
{
	unsigned long mask;
	int myirq;

	default_baudrate = 19200;
	set_baudrate(default_baudrate);
	serial_out(UART_IER,0);
	serial_out(UART_LCR, UART_LCR_WLEN8);

	mask = probe_irq_on();
	serial_out(UART_IER, UART_IER_THRI);
	serial_out(UART_TX,42);
	udelay(5000);
	serial_out(UART_IER,0);
	myirq = probe_irq_off(mask);

	PRINTK("probe irq: mask = 0x%lx irq=%x\n", mask,irq);
}
*/

static unsigned int atarisio_poll(struct file* filep, poll_table* wait)
{
	unsigned int mask=0;

	poll_wait(filep, &cmdframe_queue, wait);

#ifdef ATARISIO_EARLY_NOTIFICATION
	poll_wait(filep, &tmp_cmdframe_queue, wait);

	if (tmp_cmdframe.receiving) {
		mask |= POLLPRI;
	}
#endif
	if (cmdframe_buf.is_valid) {
		mask |= POLLPRI;
	}

	return mask;
}

static int busy=0;

static int atarisio_open(struct inode* inode, struct file* f)
{
	unsigned long flags;

	if (busy) return -EBUSY;
	busy=1;
	MOD_INC_USE_COUNT;

	current_mode = MODE_1050_2_PC;

	reset_rx_buf();
	reset_tx_buf();
	reset_cmdframe_buf();

	do_autobaud = 0;
	default_baudrate = 19200;

	serial_config.baud_base = 115200;
	serial_config.baudrate = default_baudrate;
	serial_config.IER = UART_IER_RDI;
	serial_config.MCR = UART_MCR_OUT2;
			/* OUT2 is essential for enabling interrupts - when it's required :-) */
	serial_config.LCR = UART_LCR_WLEN8; /* 1 stopbit, no parity */

	save_flags(flags); cli();

	tmp_cmdframe.pos = 0;
	tmp_cmdframe.receiving = 0;
	tmp_cmdframe.cmd_counter=0;
	tmp_cmdframe.char_errors = 0;
	tmp_cmdframe.error_counter = 0;
	tmp_cmdframe.buf[0] = 0;
	tmp_cmdframe.buf[1] = 0;
	tmp_cmdframe.buf[2] = 0;
	tmp_cmdframe.buf[3] = 0;
	tmp_cmdframe.buf[4] = 0;

	serial_out(UART_IER, 0);

	serial_out(UART_LCR, serial_config.LCR);

	set_baudrate(default_baudrate);

	set_1050_2_pc_mode(); /* this call sets MCR */

	serial_out(UART_FCR, 0);
	serial_out(UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
	serial_out(UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1);


	/*
	 * clear any interrupts
	 */
        (void)serial_in(UART_LSR);
	(void)serial_in(UART_IIR);
	(void)serial_in(UART_RX);
	(void)serial_in(UART_MSR);


	/*
	if (request_irq(irq, atarisio_interrupt, SA_SHIRQ | SA_INTERRUPT, NAME, &io)) {
	*/
		if (request_irq(irq, atarisio_interrupt, SA_SHIRQ, NAME, &io)) {
			PRINTK("could not register interrupt %d\n",irq);
			busy=0;
			MOD_DEC_USE_COUNT;
			restore_flags(flags);
			return -EFAULT;
		} else {
			DDPRINTK("got slow interrupt\n");
		}
	/*
	} else {
		DDPRINTK("got fast interrupt\n");
	}
	*/

	restore_flags(flags);

	serial_out(UART_IER, serial_config.IER);

	return 0;
}

static int atarisio_release(struct inode* inode, struct file* f)
{
	unsigned long flags;
	save_flags(flags); cli();
	serial_out(UART_MCR,0);
	serial_out(UART_IER,0);
	restore_flags(flags);

	free_irq(irq,&io);
	MOD_DEC_USE_COUNT;
	busy=0;
	return 0;
}

static struct file_operations atarisio_fops = {
	poll:		atarisio_poll,
	ioctl:		atarisio_ioctl,
	open:		atarisio_open,
	release:	atarisio_release
};

static struct miscdevice atarisio_dev = {
	ATARISIO_MINOR,
	NAME,
	&atarisio_fops
};

int init_module(void)
{
	if (check_region(io,8)) {
		PRINTK("cannot access IO-ports 0x%04x-0x%04x\n",io,io+7);
		return 1;
	}
	request_region(io, 8, NAME);
	
	/*
	 * check for 16550:
	 * this is just a very simple test using the scratch register
	 */
	serial_out(UART_SCR,0xaa);
	if (serial_in(UART_SCR) != 0xaa) {
		release_region(io, 8);
		PRINTK("couldn't detect 16550\n");
		return 1;
	}

	serial_out(UART_SCR,0x55);
	if (serial_in(UART_SCR) != 0x55) {
		release_region(io, 8);
		PRINTK("couldn't detect 16550\n");
		return 1;
	}

	/*
	 * checks are OK so far, try to register the device
	 */
	init_waitqueue_head(&rx_queue);
	init_waitqueue_head(&tx_queue);
	init_waitqueue_head(&cmdframe_queue);

#ifdef ATARISIO_EARLY_NOTIFICATION
	init_waitqueue_head(&tmp_cmdframe_queue);
#endif

	if (misc_register(&atarisio_dev)) {
		PRINTK("failed to register device\n");
		return 1;
	}
	
	PRINTK("module successfully loaded io=0x%04x, irq=%d\n", io, irq);
	return 0;
}
	
void cleanup_module(void)
{
	if (misc_deregister(&atarisio_dev)) {
		PRINTK("cannot unregister device\n");
	}
	release_region(io,8);
	PRINTK("module terminated\n");
}  

