
/*
 * drivers/char/atari_serial.c: Common source file for all Atari serial ports
 *
 * Copyright 1994 Roman Hodek
 *   EMail: rnhodek@cip.informatik.uni-erlangen.de (Internet)
 *      or: Roman_Hodek@n.maus.de (MausNet, NO mail > 16 KB!)
 *
 * Partially based on PC-Linux serial.c by Linus Torvalds and Theodore Ts'o
 * 
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file README.legal in the main directory of this archive
 * for more details.
 *
 */

/*
 * Notes and Design Goals:
 * -----------------------
 * The PC serial drivers can rely on the fact that all the serial
 * hardware is very similar to program for all ports. Unfortunately,
 * this is not true for the Atari. Here it is nearly the other way
 * round: All ports need different treatment for the low-level stuff.
 *
 * For this reason, I split the serial driver code into a
 * port-independent part (atari_serial.c) and port-specific parts
 * (atari_*.c). The first manages all what can be done without
 * accessing the hardware directly, i.e. interfacing with the
 * high-level tty drivers, wait queues, managing the existing ports
 * and the like. The latter do the actual hardware programming and are
 * accessed by the hardware-independant part by a "switch" structure,
 * that contains pointers to functions for specific tasks. See the
 * comment before the definition of the SERIALSWITCH structure in
 * atari_serial.h for more details.
 *
 * Despite its name, the port-independant code should be usable by
 * other machines than Atari, too, if there are similar circumstances
 * with different serial port hardware. Feel free to use it, but
 * please inform me if you have to do changes to it. I'll try to keep
 * it really device-independant. 
 *
 */


#include <linux/types.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/bootinfo.h>
#include <linux/tty.h>
#include <linux/termios.h>
#include <linux/major.h>
#include <linux/fcntl.h>

#include <asm/bitops.h>
#include <asm/segment.h>

#include "atari_serial.h"
#include "atari_SCC.h"
#include "atari_MFPser.h"



struct async_struct rs_table[ NR_PORTS ];
char rs_event[ (NR_PORTS + 7) / 8 ];



/***************************** Prototypes *****************************/

static inline void handle_rs_break( struct async_struct *info );
static void do_softint( void *unused );
static int startup( struct async_struct * info, int get_irq );
static void shutdown( struct async_struct * info, int do_free_irq );
static int get_serial_info( struct async_struct * info, struct
                            serial_struct * retinfo );
static int set_serial_info( struct async_struct * info, struct
                            serial_struct * new_info );
static int get_modem_info( struct async_struct * info, unsigned int *value
                           );
static int set_modem_info( struct async_struct * info, unsigned int cmd,
                           unsigned int *value );
static void send_break( struct async_struct * info, int duration );
static int rs_ioctl( struct tty_struct *tty, struct file * file, unsigned
                     int cmd, unsigned long arg );
static void rs_set_termios( struct tty_struct *tty, struct termios
                            *old_termios );
static void rs_close( struct tty_struct *tty, struct file * filp );
static int block_til_ready( struct tty_struct *tty, struct file * filp,
                            struct async_struct *info );

/************************* End of Prototypes **************************/



/*
 * ------------------------------------------------------------
 * rs_stop() and rs_start()
 *
 * This routines are called before setting or resetting tty->stopped.
 * They enable or disable transmitter interrupts, as necessary.
 * ------------------------------------------------------------ */

void rs_stop( struct tty_struct *tty )

{
	struct async_struct *info;
	
	info = rs_table + DEV_TO_SL(tty->line);

	if (info->flags & ASYNC_CLOSING) {
		tty->stopped = 0;
		tty->hw_stopped = 0;
		return;
	}

	if (info->sw->enab_tx_int)
		info->sw->enab_tx_int( info, 0 );
}

void rs_start( struct tty_struct *tty )

{
	struct async_struct *info;

	info = rs_table + DEV_TO_SL(tty->line);
	
	if (info->sw->enab_tx_int) {
		info->sw->enab_tx_int( info, 1 );
	}
}


/*
 * This routine is called when we receive a break on a serial line.
 * It is executed out of the software interrupt routine.
 */

static inline void handle_rs_break( struct async_struct *info )

{
	if (info->flags & ASYNC_SAK)
		do_SAK(info->tty);
		
	if (!I_IGNBRK(info->tty) && I_BRKINT(info->tty)) {
		flush_input(info->tty);
		flush_output(info->tty);
		if (info->tty->pgrp > 0)
			kill_pg(info->tty->pgrp, SIGINT, 1 );
	}
}

/*
 * This routine is used to handle the "bottom half" processing for the
 * serial driver, known also the "software interrupt" processing.
 * This processing is done at the kernel interrupt level, after the
 * rs_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON.  This
 * is where time-consuming activities which can not be done in the
 * interrupt driver proper are done; the interrupt driver schedules
 * them using rs_sched_event(), and they get done here.
 */
static void do_softint( void *unused )

{
	int			i;
	struct async_struct	*info;
	
	for (i = 0, info = rs_table; i < NR_PORTS; i++,info++) {
		if (clear_bit(i, rs_event)) {
			if (!info->tty)	
				continue;
			if (clear_bit(RS_EVENT_READ_PROCESS, &info->event)) {
				TTY_READ_FLUSH(info->tty);
			}
			if (clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) {
				wake_up_interruptible(&info->tty->write_q.proc_list);
			}
			if (clear_bit(RS_EVENT_HANGUP, &info->event)) {
				tty_hangup(info->tty);
				wake_up_interruptible(&info->open_wait);
				info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
			}
			if (clear_bit(RS_EVENT_BREAK, &info->event))
				handle_rs_break(info);
			if (clear_bit(RS_EVENT_OPEN_WAKEUP, &info->event)) {
				wake_up_interruptible(&info->open_wait);
			}
		}
	}
}

static int startup( struct async_struct * info, int get_irq )

{
	unsigned long flags;

	if (info->flags & ASYNC_INITIALIZED)
		return 0;

	if (!info->base || !info->type) {
		if (info->tty)
			set_bit(TTY_IO_ERROR, &info->tty->flags);
		return 0;
	}

	save_flags(flags); cli();

#ifdef SERIAL_DEBUG_OPEN
	printk("starting up ttys%d (irq %d)...", info->line, info->irq);
#endif

	info->sw->init( info );

	if (info->tty)
		clear_bit(TTY_IO_ERROR, &info->tty->flags);

	/*
	 * Set the speed and other port parameters.
	 */
	info->sw->change_speed( info );

	info->flags |= ASYNC_INITIALIZED;
	restore_flags(flags);
	return 0;
}

/*
 * This routine will shutdown a serial port; interrupts are disabled, and
 * DTR is dropped if the hangup on close termio flag is on.
 */

static void shutdown( struct async_struct * info, int do_free_irq )

{
	unsigned long flags;

	if (!(info->flags & ASYNC_INITIALIZED))
		return;

#ifdef SERIAL_DEBUG_OPEN
	printk("Shutting down serial port %d (irq %d)....", info->line,
	       info->irq);
#endif
	
	save_flags(flags); cli(); /* Disable interrupts */
	
	info->sw->deinit( info, info->tty &&
					        !(info->tty->termios->c_cflag & HUPCL) );
	
	if (info->tty)
		set_bit(TTY_IO_ERROR, &info->tty->flags);
	
	info->flags &= ~ASYNC_INITIALIZED;
	restore_flags(flags);
}


/*
 * ------------------------------------------------------------
 * rs_write() and friends
 * ------------------------------------------------------------
 */

/*
 * This routine gets called when tty_write has put something into
 * the write_queue.  
 */

void rs_write( struct tty_struct * tty )

{	struct async_struct *info;

	if (!tty || tty->stopped || tty->hw_stopped)
		return;
	info = rs_table + DEV_TO_SL(tty->line);
	
	if (!info || !info->tty || !(info->flags & ASYNC_INITIALIZED))
		return;

	cli();
	info->sw->restart( info, rs_get_char_from_queue( info ) );
	if (info->sw->enab_tx_int)
		info->sw->enab_tx_int( info, 1 );
	sti();
}


/*
 * ------------------------------------------------------------
 * rs_throttle()
 * 
 * This routine is called by the upper-layer tty layer to signal that
 * incoming characters should be throttled (and that the throttle
 * should be released).
 * ------------------------------------------------------------
 */

void rs_throttle( struct tty_struct * tty, int status )

{
	struct async_struct *info;
	unsigned long flags;

	save_flags(flags); cli();
#if SERIAL_DEBUG_THROTTLE
	printk("throttle tty%d: %d (%d, %d)....\n", DEV_TO_SL(tty->line),
	       status, LEFT(&tty->read_q), LEFT(&tty->secondary));
#endif

	switch( status ) {

	  case TTY_THROTTLE_RQ_FULL:
		info = rs_table + DEV_TO_SL(tty->line);
		if (I_IXOFF(tty))
			info->x_char = STOP_CHAR(tty);
		else if (C_CRTSCTS(tty))
			info->sw->throttle( info, status );
		break;

	  case TTY_THROTTLE_RQ_AVAIL:
		info = rs_table + DEV_TO_SL(tty->line);
		if (I_IXOFF(tty)) {
			if (info->x_char)
				info->x_char = 0;
			else
				info->x_char = START_CHAR(tty);
		} else if (C_CRTSCTS(tty))
			info->sw->throttle( info, status );
		break;
	}

	restore_flags(flags);
}

/*
 * ------------------------------------------------------------
 * rs_ioctl() and friends
 * ------------------------------------------------------------
 */

static int get_serial_info( struct async_struct * info,
						    struct serial_struct * retinfo )

{
	struct serial_struct tmp;
  
	if (!retinfo)
		return -EFAULT;

	/* Set port-independant data */
	memset(&tmp, 0, sizeof(tmp));
	tmp.type        = info->type;
	tmp.line        = info->line;
	tmp.port        = 0;	/* meaningless for non-Intel */
	tmp.irq         = 0;	/* meaningless for Atari */
	tmp.flags       = info->flags;
	tmp.close_delay = info->close_delay;
	tmp.hub6        = 0;	/* meaningless for Atari */

	/* At least baud_base and costum_divisor set by port-specific
	 * function
	 */
	info->sw->get_serial_info( info, &tmp );
	
	memcpy_tofs( retinfo, &tmp, sizeof(*retinfo) );
	return 0;
}


static int set_serial_info( struct async_struct * info,
							struct serial_struct * new_info )

{
	struct serial_struct	new_serial;
	struct async_struct 	old_info;
	
	if (!new_info) return( -EFAULT );
	memcpy_fromfs( &new_serial, new_info, sizeof(new_serial) );
	old_info = *info;

	/* The fields type, line, port, irq, xmit_fifo_size, baud_base and
	 * hub6 of new_info are silently ignored for the Atari, since they
	 * are meaningless or cannot be changed, resp. Is this right or
	 * should an error be returned?
	 */

	/* If a new custom divisor is to be set, let it check by the
	 * hardware specific code first!
	 */
	if (new_serial.custom_divisor != info->custom_divisor) {
		if (info->sw->check_custom_divisor( info, new_serial.custom_divisor ))
			return( -EINVAL );
	}
	
	if (!suser()) {
		if ((new_serial.close_delay != info->close_delay) ||
		    ((new_serial.flags & ~ASYNC_USR_MASK) !=
			 (info->flags & ~ASYNC_USR_MASK))) return( -EPERM );

		info->flags = ((info->flags & ~ASYNC_USR_MASK) |
					   (new_serial.flags & ASYNC_USR_MASK));
		info->custom_divisor = new_serial.custom_divisor;
		goto check_and_exit;
	}

	/*
	 * OK, past this point, all the error checking has been done.
	 * At this point, we start making changes.....
	 */

	info->flags = ((info->flags & ~ASYNC_FLAGS) |
				   (new_serial.flags & ASYNC_FLAGS));
	info->custom_divisor = new_serial.custom_divisor;
	info->close_delay = new_serial.close_delay;

check_and_exit:
	if (info->flags & ASYNC_INITIALIZED) {

	    if (((old_info.flags & ASYNC_SPD_MASK) !=
			 (info->flags & ASYNC_SPD_MASK)) ||
		    (old_info.custom_divisor != info->custom_divisor))
			info->sw->change_speed( info ); 
	}
	else
		(void)startup( info, 0 );

	return 0;
}

static int get_modem_info( struct async_struct * info,
						   unsigned int *value )

{
	unsigned int result;

	result = info->sw->get_modem_info( info );
	put_fs_long( result, (unsigned long *) value );

	return 0;
}


static int set_modem_info( struct async_struct * info, unsigned int cmd,
						   unsigned int *value )

{	unsigned	arg = get_fs_long((unsigned long *) value);
	int			new_dtr = -1, new_rts = -1;

	switch( cmd ) {

	  case TIOCMBIS:
		if (arg & TIOCM_DTR) new_dtr = 1;
		if (arg & TIOCM_RTS) new_rts = 1;
		break;

	  case TIOCMBIC:
		if (arg & TIOCM_DTR) new_dtr = 0;
		if (arg & TIOCM_RTS) new_rts = 0;
		break;

	  case TIOCMSET:
		new_dtr = !!(arg & TIOCM_DTR);
		new_rts = !!(arg & TIOCM_RTS);
		break;

	  default:
		return( -EINVAL );
	}
	
	return( info->sw->set_modem_info( info, new_dtr, new_rts ) );
}


/*
 * This routine sends a break out the serial port.
 */

static void send_break(	struct async_struct * info, int duration )

{
	if (!info->base) return;
	
	current->state = TASK_INTERRUPTIBLE;
	current->timeout = jiffies + duration;
	cli();
	info->sw->set_break( info, 1 );
	schedule();
	info->sw->set_break( info, 0 );
	sti();
}

static int rs_ioctl( struct tty_struct *tty, struct file * file,
					 unsigned int cmd, unsigned long arg )

{
	int error, line;
	struct async_struct * info;

	line = DEV_TO_SL(tty->line);
	if (line < 0 || line >= NR_PORTS)
		return -ENODEV;
	info = rs_table + line;
	if (!info->base) return( -ENODEV );
	
	switch( cmd ) {
	  case TCSBRK:	/* SVID version: non-zero arg --> no break */
		if (!arg)
			send_break(info, HZ/4);	/* 1/4 second */
		return 0;
	  case TCSBRKP:	/* support for POSIX tcsendbreak() */
		send_break(info, arg ? arg*(HZ/10) : HZ/4);
		return 0;
	  case TIOCGSOFTCAR:
		error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(long));
		if (error)
			return error;
		put_fs_long(C_CLOCAL(tty) ? 1 : 0, (unsigned long *) arg);
		return 0;
	  case TIOCSSOFTCAR:
		arg = get_fs_long((unsigned long *) arg);
		tty->termios->c_cflag =
			((tty->termios->c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
		return 0;
	  case TIOCMGET:
		error = verify_area(VERIFY_WRITE, (void *) arg,
							sizeof(unsigned int));
		if (error)
			return error;
		return get_modem_info(info, (unsigned int *) arg);
	  case TIOCMBIS:
	  case TIOCMBIC:
	  case TIOCMSET:
		return set_modem_info(info, cmd, (unsigned int *) arg);
	  case TIOCGSERIAL:
		error = verify_area(VERIFY_WRITE, (void *) arg,
							sizeof(struct serial_struct));
		if (error)
			return error;
		return get_serial_info(info, (struct serial_struct *) arg);
	  case TIOCSSERIAL:
		return set_serial_info(info, (struct serial_struct *) arg);
	  case TIOCSERCONFIG:
		/* return do_autoconfig(info); */
		return( 0 );
		
	  default:
		if (info->sw->ioctl)
			return( info->sw->ioctl( tty, file, info, cmd, arg ) );
		else
			return( -EINVAL );
	}
	return 0;
}


static void rs_set_termios( struct tty_struct *tty,
							struct termios *old_termios )

{
	struct async_struct *info;

	if (tty->termios->c_cflag == old_termios->c_cflag)
		return;

	info = &rs_table[DEV_TO_SL(tty->line)];

	info->sw->change_speed( info );
	
	if ((old_termios->c_cflag & CRTSCTS) &&
		!(tty->termios->c_cflag & CRTSCTS)) {
		tty->hw_stopped = 0;
		rs_write(tty);
	}

	if (!(old_termios->c_cflag & CLOCAL) &&
	    (tty->termios->c_cflag & CLOCAL))
		wake_up_interruptible(&info->open_wait);
}


/*
 * ------------------------------------------------------------
 * rs_close()
 * 
 * This routine is called when the serial port gets closed.  First, we
 * wait for the last remaining data to be sent.  Then, we unlink its
 * async structure from the interrupt chain if necessary, and we free
 * that IRQ if nothing is left in the chain.
 * ------------------------------------------------------------
 */

static void rs_close( struct tty_struct *tty, struct file * filp )

{
	struct async_struct * info;
	int line;

	if (tty_hung_up_p(filp)) return;
	
	line = DEV_TO_SL(tty->line);
	if ((line < 0) || (line >= NR_PORTS)) return;
	info = rs_table + line;
	if (!info->base) return;
	
#ifdef SERIAL_DEBUG_OPEN
	printk("rs_close ttys%d, count = %d\n", info->line, info->count);
#endif

	if ((tty->count == 1) && (info->count != 1)) {
		/*
		 * Uh, oh.  tty->count is 1, which means that the tty
		 * structure will be freed.  Info->count should always
		 * be one in these conditions.  If it's greater than
		 * one, we've got real problems, since it means the
		 * serial port won't be shutdown.
		 */
		printk("rs_close: bad serial port count; tty->count is 1, "
		       "info->count is %d\n", info->count);
		info->count = 1;
	}
	if (--info->count < 0) {
		printk("rs_close: bad serial port count for ttys%d: %d\n",
		       info->line, info->count);
		info->count = 0;
	}
	if (info->count)
		return;
	info->flags |= ASYNC_CLOSING;
	/*
	 * Save the termios structure, since this port may have
	 * separate termios for callout and dialin.
	 */
	if (info->flags & ASYNC_NORMAL_ACTIVE)
		info->normal_termios = *tty->termios;
	if (info->flags & ASYNC_CALLOUT_ACTIVE)
		info->callout_termios = *tty->termios;
	tty->stopped = 0;		/* Force flush to succeed */
	tty->hw_stopped = 0;
	if (info->flags & ASYNC_INITIALIZED) {
		rs_start(tty);
		wait_until_sent(tty, 6000); /* 60 seconds timeout */
	} else
		flush_output(tty);
	flush_input(tty);

	shutdown(info, 1);
	clear_bit(line, rs_event);
	info->event = 0;
	info->tty = 0;
	if (info->blocked_open) {
		if (info->close_delay) {
			tty->count++; /* avoid race condition */
			current->state = TASK_INTERRUPTIBLE;
			current->timeout = jiffies + info->close_delay;
			schedule();
			tty->count--;
		}
		wake_up_interruptible(&info->open_wait);
	}
	info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE|ASYNC_CLOSING);
	wake_up_interruptible(&info->close_wait);
}


/*
 * rs_hangup() --- called by tty_hangup() when a hangup is signaled.
 */

void rs_hangup( struct tty_struct *tty )

{
	struct async_struct * info;
	int line;

	line = DEV_TO_SL(tty->line);
	if ((line < 0) || (line >= NR_PORTS)) return;
	info = rs_table + line;
	if (!info->base) return;
	
	shutdown(info, 1);
	clear_bit(line, rs_event);
	info->event = 0;
	info->count = 0;
	info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
	info->tty = 0;
	wake_up_interruptible(&info->open_wait);
}


/*
 * ------------------------------------------------------------
 * rs_open() and friends
 * ------------------------------------------------------------
 */

static int block_til_ready( struct tty_struct *tty, struct file * filp,
							struct async_struct *info )

{
	struct wait_queue wait = { current, NULL };
	int		retval;
	int		do_clocal = C_CLOCAL(tty);

	/*
	 * If the device is in the middle of being closed, then block
	 * until it's done, and then try again.
	 */
	if (info->flags & ASYNC_CLOSING) {
		interruptible_sleep_on(&info->close_wait);
#ifdef SERIAL_DO_RESTART
		if (info->flags & ASYNC_HUP_NOTIFY)
			return -EAGAIN;
		else
			return -ERESTARTSYS;
#else
		return -EAGAIN;
#endif
	}

	/*
	 * If this is a callout device, then just make sure the normal
	 * device isn't being used.
	 */
	if (MAJOR(filp->f_rdev) == TTYAUX_MAJOR) {
		if (info->flags & ASYNC_NORMAL_ACTIVE)
			return -EBUSY;
		if ((info->flags & ASYNC_CALLOUT_ACTIVE) &&
		    (info->flags & ASYNC_SESSION_LOCKOUT) &&
		    (info->session != current->session))
		    return -EBUSY;
		if ((info->flags & ASYNC_CALLOUT_ACTIVE) &&
		    (info->flags & ASYNC_PGRP_LOCKOUT) &&
		    (info->pgrp != current->pgrp))
		    return -EBUSY;
		info->flags |= ASYNC_CALLOUT_ACTIVE;
		return 0;
	}
	
	/*
	 * If non-blocking mode is set, then make the check up front
	 * and then exit.
	 */
	if (filp->f_flags & O_NONBLOCK) {
		if (info->flags & ASYNC_CALLOUT_ACTIVE)
			return -EBUSY;
		info->flags |= ASYNC_NORMAL_ACTIVE;
		return 0;
	}

	/*
	 * Block waiting for the carrier detect and the line to become
	 * free (i.e., not in use by the callout).  While we are in
	 * this loop, info->count is dropped by one, so that
	 * rs_close() knows when to free things.  We restore it upon
	 * exit, either normal or abnormal.
	 */
	retval = 0;
	add_wait_queue(&info->open_wait, &wait);
#ifdef SERIAL_DEBUG_OPEN
	printk("block_til_ready before block: ttys%d, count = %d\n",
	       info->line, info->count);
#endif
	info->count--;
	info->blocked_open++;
	while (1) {
		cli();
		if (!(info->flags & ASYNC_CALLOUT_ACTIVE))
			info->sw->set_modem_info( info, 1, 1 );
		sti();
		current->state = TASK_INTERRUPTIBLE;
		if (tty_hung_up_p(filp) ||
		    !(info->flags & ASYNC_INITIALIZED)) {
#ifdef SERIAL_DO_RESTART
			if (info->flags & ASYNC_HUP_NOTIFY)
				retval = -EAGAIN;
			else
				retval = -ERESTARTSYS;
#else
			retval = -EAGAIN;
#endif
			break;
		}
		if (!(info->flags & ASYNC_CALLOUT_ACTIVE) &&
		    !(info->flags & ASYNC_CLOSING) &&
		    (do_clocal || (info->sw->get_modem_info( info ) & TIOCM_CAR)))
			break;
		if (current->signal & ~current->blocked) {
			retval = -ERESTARTSYS;
			break;
		}
#ifdef SERIAL_DEBUG_OPEN
		printk("block_til_ready blocking: ttys%d, count = %d\n",
		       info->line, info->count);
#endif
		schedule();
	}
	current->state = TASK_RUNNING;
	remove_wait_queue(&info->open_wait, &wait);
	if (!tty_hung_up_p(filp))
		info->count++;
	info->blocked_open--;
#ifdef SERIAL_DEBUG_OPEN
	printk("block_til_ready after blocking: ttys%d, count = %d\n",
	       info->line, info->count);
#endif
	if (retval)
		return retval;
	info->flags |= ASYNC_NORMAL_ACTIVE;
	return 0;
}	


/*
 * This routine is called whenever a serial port is opened.
 */

int rs_open( struct tty_struct *tty, struct file * filp )

{
	struct async_struct	*info;
	int 				retval, line;

	line = DEV_TO_SL(tty->line);
	if ((line < 0) || (line >= NR_PORTS))
		return( -ENODEV );
	info = rs_table + line;
	if (!info->base)
		return( -ENODEV );

#ifdef SERIAL_DEBUG_OPEN
	printk("rs_open ttyS%d, count = %d\n", info->line, info->count);
#endif
	info->count++;
	info->tty = tty;
	
	tty->write       = rs_write;
	tty->close       = rs_close;
	tty->ioctl       = rs_ioctl;
	tty->throttle    = rs_throttle;
	tty->set_termios = rs_set_termios;
	tty->stop        = rs_stop;
	tty->start       = rs_start;
	tty->hangup      = rs_hangup;

	if ((info->count == 1) && (info->flags & ASYNC_SPLIT_TERMIOS)) {
		if (MAJOR(filp->f_rdev) == TTY_MAJOR)
			*tty->termios = info->normal_termios;
		else 
			*tty->termios = info->callout_termios;
	}

	/*
	 * Start up serial port
	 */
	if ((retval = startup(info, 1))) return retval;

	retval = block_til_ready(tty, filp, info);
	if (retval) {
#ifdef SERIAL_DEBUG_OPEN
		printk( "rs_open returning after block_til_ready with %d\n", retval );
#endif
		return retval;
	}

	info->session = current->session;
	info->pgrp = current->pgrp;

#ifdef SERIAL_DEBUG_OPEN
	printk("rs_open ttyS%d successfull...", info->line);
#endif
	return 0;
}



/*
 * Boot-time initialization code: Init the serial ports present on
 * this machine type.
 *
 * Current HW Port to minor/device name mapping:
 *
 *   TT                    | Falcon                    || minor | name
 *  -----------------------+---------------------------++-------+-------
 *   SCC B (Modem2)        | SCC B (Modem)             || 64    | ttyS0
 *   SCC A (Serial2/LAN)   | SCC A (LAN)               || 65    | ttyS1
 *   ST-MFP port (Modem1)  | --                        || 66    | ttyS2
 *   TT-MFP port (Serial1) | ST-MFP port (no TOS name) || 67    | ttyS3
 *
 * The background of this mapping is that I wanted to assign the same
 * minors to ports with the same capabilities. Staring with the MFPs
 * would leave a hole in the first position on the Falcon!
 *
 * Should Serial2/LAN on the TT be split into two devices, selecting thus
 * the connector used? Serial2 would be unassigned on the Falcon, and
 * if one is open, opening the other would block.
 * 
 */

long rs_init( long kmem_start )

{	int					i;
	struct async_struct	*info;

	if (boot_info.machtype != MACH_ATARI) return( kmem_start );
	
	memset( &rs_event, 0, sizeof(rs_event) );
	bh_base[SERIAL_BH].routine = do_softint;

	/* Initialize the async_struct's */
	for( i = 0, info = rs_table; i < NR_PORTS; i++, info++ ) {
		info->line           = i;
		info->base			 = NULL;
		info->tty            = 0;
		info->type           = PORT_UNKNOWN;
		info->custom_divisor = 0;
		info->close_delay    = 50;
		info->x_char         = 0;
		info->event          = 0;
		info->count          = 0;
		info->blocked_open   = 0;
		memset(&info->callout_termios, 0, sizeof(struct termios));
		memset(&info->normal_termios, 0, sizeof(struct termios));
		info->open_wait      = 0;
		info->xmit_wait      = 0;
		info->close_wait     = 0;
	}

	/* Now initialize the ports themselves */
	switch( boot_info.bi_atari.model ) {

	  case ATARI_TT:
		atari_init_SCC(
		    &rs_table[0],
			SCC_NORM,						/* type */
			1,								/* Channel B */
			(void *)&tt_mfp.par_dt_reg, 3, 0/* RI */
		);
		atari_init_SCC(
		    &rs_table[1],
			SCC_DMA,						/* type */
			0, 								/* Channel A */
			0, 0, 0							/* no RI */
		);
		atari_init_MFPser(
		    &rs_table[2],
			MFP_CTRL,						/* type */
			0,								/* ST-MFP */
			(void *)&mfp.par_dt_reg, 6, 0	/* RI */
		);
		atari_init_MFPser(
		    &rs_table[3],
			MFP_BARE,						/* type */
			1,								/* TT_MFP */
			0, 0, 0							/* no RI */
		);
		break;
		
	  case ATARI_FALCON:
		atari_init_SCC(
		    &rs_table[0],
			SCC_NORM,						/* type */
			1,								/* Channel B */
			(void *)&mfp.par_dt_reg, 6, 0	/* RI */
		);
		atari_init_SCC(
		    &rs_table[1],
			SCC_NORM,						/* type */
			0,		 						/* Channel A */
			0, 0, 0							/* no RI */
		);
		atari_init_MFPser(
		    &rs_table[3],
			MFP_BARE,						/* type */
			0,								/* ST-MFP */
			0, 0, 0							/* no RI */
		);
		break;

	}

	return( kmem_start );
}
