
/*
 * atari_serial.h: Common definitions 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!)
 *
 * 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.
 *
 */


#ifndef _ATARI_SERIAL_H
#define _ATARI_SERIAL_H

#include <linux/tty.h>

/* 4 is sufficient for now... */
#define	NR_PORTS 4


/* Structure describing one bit in a register that may be high- or
 * low-active. This is used for RI detection, because the location of
 * this signal varies widely. The function TEST_BITPOS() is provided
 * to return the status of such a bit.
 */

typedef struct {
	volatile unsigned char	*addr;			/* address of register */
	unsigned char			bitno;			/* bit#, 0..7 */
	unsigned char			active_state;	/* 1 for high-active,
											 * 0 for * low-active */
} BITPOS;

static __inline__ int TEST_BITPOS( BITPOS bp )

{
	return( !bp.addr ? 0 :
            !!(*(bp.addr) & (1 << bp.bitno)) == bp.active_state );
}
	

/* Atari serial port types are numbered from 100 to avoid interference
 * with the PC types (1..4)
 */
typedef enum {
	SCC_NORM = 100,	/* standard SCC channel */
	SCC_DMA,		/* SCC channel with DMA support */
	MFP_CTRL,		/* standard MFP port with modem control signals */
	MFP_BARE,		/* MFP port without modem controls */
} SERTYPE;

/* This function tables does the abstraction from the underlying
 * hardware:
 *
 *   init(): Initialize the port as necessary, set RTS and DTR and
 *      enable interrupts. It does not need to set the speed and other
 *      parameters, because change_speed() is called, too.
 *   deinit(): Stop the port (wait for all characters to be sent,
 *      disable interrupts)
 *   enab_tx_int(): Enable or disable the Tx Buffer Empty interrupt
 *      independantly from other interrupt sources. The pointer to
 *      this function may be NULL, if there is no way to do this or it
 *      is too complex. This Tx ints are just disabled to save some
 *      interrupts if the transmitter is stopped anyway.
 *   check_custom_divisor(): Check the given custom divisor for legality
 *      and return 0 if OK, non-zero otherwise.
 *   change_speed(): Set port speed, character size, number of stop
 *      bits and parity from the termios structure. If the user wants
 *      to set the speed with a custom divisor, he is required to
 *      check the baud_base first!
 *   restart(): Is called every time new data arrived in the Tx queue
 *      and may be used to restart the transmitter if it ran empty.
 *      'c' is the first character to be output or -1 if there is
 *      none. If more than one character can be written (FIFOs!), you
 *      have to take the remaining ones from the write queue directly.
 *      This function is called with interrupts off.
 *   throttle(): Set or clear the RTS line according to 'status'.
 *   set_break(): Set or clear the 'Send a Break' flag.
 *   get_serial_info(): Fill in the baud_base and custom_divisor
 *      fields of a serial_struct. It may also modify other fields, if
 *      needed.
 *   get_modem_info(): Return the status of RTS, DTR, DCD, RI, DSR and CTS.
 *   set_modem_info(): Set the status of RTS and DTR according to
 *      'new_dtr' and 'new_rts', resp. 0 = clear, 1 = set, -1 = don't change
 *   ioctl(): Process any port-specific ioctl's. This pointer may be
 *      NULL, if the port has no own ioctl's.
 *
 */

struct async_struct;

typedef struct {
	void (*init)( struct async_struct *info );
	void (*deinit)( struct async_struct *info, int leave_dtr );
	void (*enab_tx_int)( struct async_struct *info, int enab_flag );
	int  (*check_custom_divisor)( struct async_struct *info, int divisor );
	void (*change_speed)( struct async_struct *info );
	void (*restart)( struct async_struct *info, int c );
	void (*throttle)( struct async_struct *info, int status );
	void (*set_break)( struct async_struct *info, int break_flag );
	void (*get_serial_info)( struct async_struct *info,
							 struct serial_struct *retinfo );
	unsigned int (*get_modem_info)( struct async_struct *info );
	int  (*set_modem_info)( struct async_struct *info, int new_dtr,
							int new_rts );
	int  (*ioctl)( struct tty_struct *tty, struct file *file,
				   struct async_struct *info, unsigned int cmd,
				   unsigned long arg );
} SERIALSWITCH;


/* This is a modified version of the PC-Linux async_struct. Mainly,
 * stuff like port address, xmit_fifo_size and the like are left out
 * and other information fields were added.
 */

struct async_struct {
	/* Atari specific fields */
	SERTYPE				type; 		/* UART type */
	volatile void		*base;		/* base address */
	BITPOS				RI;			/* Where the Ring Indicator is signalled */
	SERIALSWITCH		*sw;		/* functions to manage this port */
	/* common fields */
	int					flags;
	struct tty_struct 	*tty;
	int					timeout;
	int					custom_divisor;
	int					x_char;		/* xon/xoff characater */
	int					close_delay;
	int					event;
	int					line;
	int					count;	    /* # of fd on device */
	int					blocked_open; /* # of blocked opens */
	long				session;	/* Session of opening process */
	long				pgrp;		/* pgrp of opening process */
	struct termios		normal_termios;
	struct termios		callout_termios;
	struct wait_queue	*open_wait;
	struct wait_queue	*close_wait;
	struct wait_queue	*xmit_wait;
};

/*
 * Events are used to schedule things to happen at timer-interrupt
 * time, instead of at rs interrupt time.
 */
#define RS_EVENT_READ_PROCESS	0
#define RS_EVENT_WRITE_WAKEUP	1
#define RS_EVENT_HANGUP		2
#define RS_EVENT_BREAK		3
#define RS_EVENT_OPEN_WAKEUP	4



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

void rs_stop( struct tty_struct *tty );
void rs_start( struct tty_struct *tty );
void rs_write( struct tty_struct * tty );
void rs_throttle( struct tty_struct * tty, int status );
void rs_hangup( struct tty_struct *tty );
int rs_open( struct tty_struct *tty, struct file * filp );
long rs_init( long kmem_start );

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



/*************************** Inline Functions *************************/


#include <linux/interrupt.h>
#include <asm/bitops.h>

/*
 * This routine is used by the interrupt handler to schedule
 * processing in the software interrupt portion of the driver.
 * It is needed in the port-specific interrupt handlers, so it is
 * declared here in the common header.
 */

extern char rs_event[];

static inline void rs_sched_event( struct async_struct *info, int event )

{
	info->event |= 1 << event;
	set_bit(info->line, rs_event);
	mark_bh(SERIAL_BH);
}


/* The following functions do common tasks needed in the interrupt
 * service routine(s):
 * 
 *  - Putting a character into the read_queue, either with or without
 *    an error condition, and waking up processes waiting for
 *    something to arrive
 *  - Check if a throttle is needed due to the read queue getting full
 *  - Removing a character from the write queue, obeying pending flow
 *    control characters
 *  - Checking for enough characters in the write queue to wake up
 *    sleeping processes
 *  - Doing required actions on wait queues if DCD has changed
 *  - Doing required actions on wait queues if CTS has changed;
 *    returns non-zero, if the transmitter should be restarted
 */

static __inline__ void rs_put_char_to_queue( struct async_struct *info,
											 int c, int err )

{	struct tty_queue	*q = &info->tty->read_q;
	
	if (!FULL(q)) {

		if (err) {
			if (err == TTY_BREAK)
				rs_sched_event( info, RS_EVENT_BREAK );

			/* ignore parity errors if parity checking isn't enabled. */
			if (!(err == TTY_PARITY && !I_INPCK(info->tty))) {
				set_bit( q->head, &info->tty->readq_flags );
				c = err;
			}
		}

		q->buf[q->head] = c;
		INC(q->head);

		rs_sched_event( info, RS_EVENT_READ_PROCESS );
	}
}


static __inline__ void rs_rx_throttle_if_needed( struct async_struct *info )

{	struct tty_queue	*q = &info->tty->read_q;

	if (LEFT(q) < RQ_THRESHOLD_LW &&
		!set_bit( TTY_RQ_THROTTLED, &info->tty->flags))

		rs_throttle( info->tty, TTY_THROTTLE_RQ_FULL );
}


static __inline__ int rs_get_char_from_queue( struct async_struct *info )

{	int					ch;
	struct tty_queue	*q = &info->tty->write_q;

	if (info->x_char) {
		ch = info->x_char;
		info->x_char = 0;
	}
	else if (!EMPTY(q)) {
		ch = q->buf[q->tail];
		INC(q->tail);
	}
	else {
		rs_stop( info->tty );
		ch = -1;
	}

	return( ch );
}


static __inline__ void rs_tx_wakeup_if_needed( struct async_struct *info )

{	struct tty_queue	*q = &info->tty->write_q;

	if (LEFT(q) > WAKEUP_CHARS) {
		rs_sched_event( info, RS_EVENT_WRITE_WAKEUP );
		if (info->tty->write_data_cnt) {
			set_bit( info->tty->line, &tty_check_write );
			mark_bh( TTY_BH );
		}
	}
}


static __inline__ void rs_dcd_changed( struct async_struct *info, int dcd )

{
	if (!C_CLOCAL(info->tty)) {

		if (dcd)
			rs_sched_event( info, RS_EVENT_OPEN_WAKEUP );
		else if (!((info->flags & ASYNC_CALLOUT_ACTIVE) &&
				   (info->flags & ASYNC_CALLOUT_NOHUP)))
			rs_sched_event( info, RS_EVENT_HANGUP );
	}
}


static __inline__ int rs_cts_changed( struct async_struct *info, int cts )

{
	if (!C_CRTSCTS(info->tty) || (info->flags & ASYNC_CLOSING))
		return( 0 );

	if (info->tty->hw_stopped) {
		if (cts) {
			info->tty->hw_stopped = 0;
			rs_start( info->tty );
			return( 1 );
		}
	}
	else {
		if (!cts) {
			info->tty->hw_stopped = 1;
			rs_stop( info->tty );
		}
	}

	return( 0 );
}


#endif /* _ATARI_SERIAL_H */
