

/*
 * drivers/char/atari_MFPser.c: Atari MFP serial ports implementation
 *
 * 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.
 *
 */


/* This file implements the MFP serial ports. These come in two
 * flavors: with or without control lines (RTS, CTS, DTR, ...). They
 * are distinguished by having two different types, MFP_CTRL and
 * MFP_BARE, resp. Most of the low-level functions are the same for
 * both, but some differ.
 *
 * Note that some assumptions are made about where to access the
 * control lines. If the port type is MFP_CTRL, the input lines (CTS
 * and DCD) are assumed to be in the MFP GPIP register, bits 1 and 2.
 * The output lines (DTR and RTS) have to be in the Soundchip Port A,
 * bits 3 and 4. This is the standard ST/TT assigment. If Atari will
 * build a serial port in future, that uses other registers, you have
 * to rewrite this code. But if the port type is MFP_BARE, no such
 * assumptions are necessary. All registers needed are fixed by the
 * MFP hardware. The only parameter is the MFP base address. This is
 * used to implement Serial1 for the TT and the (not connected) MFP
 * port of the Falcon.
 * 
 */

#include <linux/types.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/tty.h>
#include <linux/termios.h>
#include <linux/atarihw.h>
#include <linux/atariints.h>

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



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

static void MFPser_rx_int( struct intframe *fp, struct async_struct *info );
static void MFPser_rxerr_int( struct intframe *fp, struct async_struct
                              *info );
static void MFPser_tx_int( struct intframe *fp, struct async_struct *info );
static void MFPctrl_dcd_int( struct intframe *fp, struct async_struct *info
                             );
static void MFPctrl_cts_int( struct intframe *fp, struct async_struct *info
                             );
static void MFPser_init( struct async_struct *info );
static void MFPser_deinit( struct async_struct *info, int leave_dtr );
static void MFPser_enab_tx_int( struct async_struct *info, int enab_flag );
static int MFPser_check_custom_divisor( struct async_struct *info, int
                                        divisor );
static void MFPser_change_speed( struct async_struct *info );
static void MFPser_restart( struct async_struct *info, int ch );
static void MFPctrl_throttle( struct async_struct *info, int status );
static void MFPbare_throttle( struct async_struct *info, int status );
static void MFPser_set_break( struct async_struct *info, int break_flag );
static void MFPser_get_serial_info( struct async_struct *info, struct
                                    serial_struct *retinfo );
static unsigned int MFPctrl_get_modem_info( struct async_struct *info );
static unsigned int MFPbare_get_modem_info( struct async_struct *info );
static int MFPctrl_set_modem_info( struct async_struct *info, int new_dtr,
                                   int new_rts );
static int MFPbare_set_modem_info( struct async_struct *info, int new_dtr,
                                   int new_rts );

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



/* SERIALSWITCH structures for MFP ports
 * Most functions are common to MFP ports with or without control lines
 */

static SERIALSWITCH MFPctrl_switch = {
	MFPser_init, MFPser_deinit, MFPser_enab_tx_int,
	MFPser_check_custom_divisor, MFPser_change_speed,
	MFPser_restart, MFPctrl_throttle, MFPser_set_break,
	MFPser_get_serial_info, MFPctrl_get_modem_info,
	MFPctrl_set_modem_info, NULL
};


static SERIALSWITCH MFPbare_switch = {
	MFPser_init, MFPser_deinit, MFPser_enab_tx_int,
	MFPser_check_custom_divisor, MFPser_change_speed,
	MFPser_restart, MFPbare_throttle, MFPser_set_break,
	MFPser_get_serial_info, MFPbare_get_modem_info,
	MFPbare_set_modem_info, NULL
};

/* MFP Timer Modes divided by 2 (this already done in the BAUD_BASE */
int MFP_timer_modes[] = { 2, 5, 8, 25, 32, 50, 100 };

/* Divisors for standard speeds */
int MFP_baud_table[15] = {
	/* B0     */ 0,
	/* B50    */ 768,
	/* B75    */ 512,
	/* B110   */ 350, /* really 109.71 bps */
	/* B134	  */ 286, /* really 134.27 bps */
	/* B150	  */ 256,
	/* B200	  */ 192,
	/* B300	  */ 128,
	/* B600	  */ 64,
	/* B1200  */ 32,
	/* B1800  */ 21,
	/* B2400  */ 16,
	/* B4800  */ 8,
	/* B9600  */ 4,
	/* B19200 */ 2
};
	

void atari_init_MFPser( struct async_struct *info, SERTYPE type,
						int tt_flag, void *ri_addr,
						unsigned char ri_bitno, unsigned char ri_active )

{	
	/* set ISRs, but don't enable interrupts yet (done in init()) */
	add_isr( tt_flag ? IRQ_TT_MFP_SEREMPT : IRQ_MFP_SEREMPT,
			 (isrfunc)MFPser_tx_int, 0, info );
	add_isr( tt_flag ? IRQ_TT_MFP_RECFULL : IRQ_MFP_RECFULL,
			 (isrfunc)MFPser_rx_int, 0, info );
	add_isr( tt_flag ? IRQ_TT_MFP_RECERR : IRQ_MFP_RECERR,
			 (isrfunc)MFPser_rxerr_int, 0, info );
	/* Tx_err interrupt unused (it signals only that the Tx shift reg
	 * is empty)
	 */
	
	if (type == MFP_CTRL && !tt_flag) {
		add_isr( IRQ_MFP_DCD, (isrfunc)MFPctrl_dcd_int, 0, info );
		add_isr( IRQ_MFP_CTS, (isrfunc)MFPctrl_cts_int, 0, info );
		/* clear RTS and DTR */
		GIACCESS( GI_RTS | GI_DTR );
	}
	
	info->type            = type;
	info->base            = tt_flag ? &tt_mfp : &mfp;
	info->RI.addr         = (volatile unsigned char *)ri_addr;
	info->RI.bitno        = ri_bitno;
	info->RI.active_state = ri_active;
	info->sw              = (type == MFP_CTRL) ? &MFPctrl_switch :
												 &MFPbare_switch;

	currMFP(info)->rcv_stat  = 0;	/* disable Rx */
	currMFP(info)->trn_stat  = 0;	/* disable Tx */
}


static void MFPser_rx_int( struct intframe *fp, struct async_struct *info )

{	int		ch, stat, err;
	
	stat = currMFP(info)->rcv_stat;
	ch   = currMFP(info)->usart_dta;
	/* Frame Errors don't cause a RxErr IRQ! */
	err  = (stat & RSR_FRAME_ERR) ? TTY_FRAME : 0;
	
	rs_put_char_to_queue( info, ch, err );
	rs_rx_throttle_if_needed( info );
}


static void MFPser_rxerr_int( struct intframe *fp, struct async_struct *info )

{	int		ch, stat, err;
	
	stat = currMFP(info)->rcv_stat;
	ch   = currMFP(info)->usart_dta; /* most probably junk data */

	if (stat & RSR_PARITY_ERR)
		err = TTY_PARITY;
	else if (stat & RSR_OVERRUN_ERR)
		err = TTY_OVERRUN;
	else if (stat & RSR_BREAK_DETECT)
		err = TTY_BREAK;
	else if (stat & RSR_FRAME_ERR)	/* should not be needed */
		err = TTY_FRAME;
	else
		err = 0;

	rs_put_char_to_queue( info, ch, err );
	rs_rx_throttle_if_needed( info );
}


static void MFPser_tx_int( struct intframe *fp, struct async_struct *info )

{	int		ch;

	if (currMFP(info)->trn_stat & TSR_BUF_EMPTY) {
		if ((ch = rs_get_char_from_queue( info )) < 0) return;
		currMFP(info)->usart_dta = ch;
		rs_tx_wakeup_if_needed( info );
	}
}


static void MFPctrl_dcd_int( struct intframe *fp, struct async_struct *info )

{	
	/* Toggle active edge to get next change of DCD! */
	currMFP(info)->active_edge ^= GPIP_DCD;

	rs_dcd_changed( info, !(currMFP(info)->par_dt_reg & GPIP_DCD) );
}


static void MFPctrl_cts_int( struct intframe *fp, struct async_struct *info )

{	
	/* Toggle active edge to get next change of CTS! */
	currMFP(info)->active_edge ^= GPIP_CTS;
	
	if (rs_cts_changed( info, !(currMFP(info)->par_dt_reg & GPIP_CTS) ))
		/* Don't know yet if this is really necessary, or if a Tx
		 * IRQ is delivered anyway when turning on again.
		 */
		MFPser_tx_int( fp, info );
}


static void MFPser_init( struct async_struct *info )

{	
	/* base value for UCR */
	currMFP(info)->usart_ctr = UCR_PARITY_OFF | UCR_ASYNC_1 |
							   UCR_CHSIZE_8 | UCR_PREDIV;

	/* enable Rx and clear any error conditions */
	currMFP(info)->rcv_stat = RSR_RX_ENAB;

	/* enable Tx */
	currMFP(info)->trn_stat = TSR_TX_ENAB;

	/* enable Rx, RxErr and Tx interrupts */
	currMFP(info)->int_en_a |= 0x1c;
	currMFP(info)->int_mk_a |= 0x1c;

	if (info->type == MFP_CTRL) {

		/* set RTS and DTR (low-active!) */
		GIACCESS( ~(GI_RTS | GI_DTR) );

		/* Set active edge of CTS and DCD signals depending on their
		 * current state.
		 * If the line status changes between reading the status and
		 * enabling the interrupt, this won't work :-( How could it be
		 * done better??
		 */
		if (currMFP(info)->par_dt_reg & GPIP_CTS)
			currMFP(info)->active_edge &= ~GPIP_CTS;
		else
			currMFP(info)->active_edge |= GPIP_CTS;
		
		if (currMFP(info)->par_dt_reg & GPIP_DCD)
			currMFP(info)->active_edge &= ~GPIP_DCD;
		else
			currMFP(info)->active_edge |= GPIP_DCD;

		/* enable CTS and DCD interrupts */
		currMFP(info)->int_en_b |= 0x06;
		currMFP(info)->int_mk_b |= 0x06;
	}
}


static void MFPser_deinit( struct async_struct *info, int leave_dtr )

{
	/* disable Rx, RxErr and Tx interrupts */
	currMFP(info)->int_en_a &= ~0x1c;

	if (info->type == MFP_CTRL) {
		/* disable CTS and DCD interrupts */
		currMFP(info)->int_en_b &= ~0x06;
	}

	/* disable Rx and Tx */
	currMFP(info)->rcv_stat = 0;
	currMFP(info)->trn_stat = 0;
	
	/* wait for last byte to be completely shifted out */
	while( !(currMFP(info)->trn_stat & TSR_LAST_BYTE_SENT) )
		;

	if (info->type == MFP_CTRL) {
		/* drop RTS and DTR if required */
		MFPser_RTSoff();
		if (!leave_dtr)
			MFPser_DTRoff();
	}

	/* read Rx status and data to clean up */
	(void)currMFP(info)->rcv_stat;
	(void)currMFP(info)->usart_dta;
}


static void MFPser_enab_tx_int( struct async_struct *info, int enab_flag )

{
	if (enab_flag)
		currMFP(info)->int_en_a |= 0x04;
	else
		currMFP(info)->int_en_a &= ~0x04;
}


static int MFPser_check_custom_divisor( struct async_struct *info,
										int divisor )

{	int		i;
	
	/* divisor must be a multiple of 2 or 5 (because of timer modes) */
	if (divisor == 0 || ((divisor & 1) && (divisor % 5) != 0)) return( -1 );

	/* Determine what timer mode would be selected and look if the
	 * timer value would be greater than 256
	 */
	for( i = sizeof(MFP_timer_modes)/sizeof(*MFP_timer_modes)-1; i >= 0; --i )
		if (divisor % MFP_timer_modes[i] == 0) break;
	if (i < 0) return( -1 ); /* no suitable timer mode found */

	return( divisor / MFP_timer_modes[i] <= 256 );
}


static void MFPser_change_speed( struct async_struct *info )

{	unsigned	cflag, baud, chsize, stopb, parity, aflags;
	unsigned	div, timer_val;
	int			timer_mode;
	
	if (!info->tty || !info->tty->termios) return;

	cflag  = info->tty->termios->c_cflag;
	baud   = cflag & CBAUD;
	chsize = cflag & CSIZE;
	stopb  = cflag & CSTOPB;
	parity = cflag & (PARENB | PARODD);
	aflags = info->flags & ASYNC_SPD_MASK;

	if (baud == 15 && aflags == ASYNC_SPD_CUST)
		div = info->custom_divisor;
	else {
		/* Maximum MFP speed is 19200 :-( */
		if (baud > 14) baud = 14;
		div = MFP_baud_table[baud];
	}

	if (!div) {
		/* speed == 0 -> drop DTR */
		MFPser_DTRoff();
		return;
	}

	/* compute timer value and timer mode (garuateed to succeed, because
	 * the divisor was checked before by check_custom_divisor(), if it
	 * is used-supplied)
	 */
	for( timer_mode = sizeof(MFP_timer_modes)/sizeof(*MFP_timer_modes)-1;
		 timer_mode >= 0; --timer_mode )
		if (div % MFP_timer_modes[timer_mode] == 0) break;
	timer_val = div / MFP_timer_modes[timer_mode];
	
	cli();
	/* disable Rx and Tx while changing parameters */
	currMFP(info)->rcv_stat = 0;
	currMFP(info)->trn_stat = 0;

	/* stop timer D to set new timer value immediatly after re-enabling */
	currMFP(info)->tim_ct_cd &= ~0x07;
	currMFP(info)->tim_dt_d = timer_val;
	currMFP(info)->tim_ct_cd |= (timer_mode+1);

	currMFP(info)->usart_ctr =
		( (parity & PARENB) ?
		      ((parity & PARODD) ? UCR_PARITY_ODD : UCR_PARITY_EVEN) :
		      UCR_PARITY_OFF ) |
		( chsize == CS5 ? UCR_CHSIZE_5 :
		  chsize == CS6 ? UCR_CHSIZE_6 :
		  chsize == CS7 ? UCR_CHSIZE_7 :
						  UCR_CHSIZE_8 ) |
		( stopb ? UCR_ASYNC_2 : UCR_ASYNC_1 ) |
		UCR_PREDIV;

	/* re-enable Rx and Tx */
	currMFP(info)->rcv_stat = RSR_RX_ENAB;
	currMFP(info)->trn_stat = TSR_TX_ENAB;
	sti();
}
	
	
static void MFPser_restart( struct async_struct *info, int ch )

{
	if (ch >= 0 && currMFP(info)->trn_stat & TSR_BUF_EMPTY)
		currMFP(info)->usart_dta = ch;
}


static void MFPctrl_throttle( struct async_struct *info, int status )

{
	if (status == TTY_THROTTLE_RQ_FULL)
		MFPser_RTSoff();
	else if (status == TTY_THROTTLE_RQ_AVAIL)
		MFPser_RTSon();
}


static void MFPbare_throttle( struct async_struct *info, int status )

{
	/* no-op */
}


static void MFPser_set_break( struct async_struct *info, int break_flag )

{
	if (break_flag)
		currMFP(info)->trn_stat |= TSR_SEND_BREAK;
	else
		currMFP(info)->trn_stat &= ~TSR_SEND_BREAK;
}


static void MFPser_get_serial_info( struct async_struct *info,
								   struct serial_struct *retinfo )

{
	retinfo->baud_base = MFP_BAUD_BASE;
	retinfo->custom_divisor = info->custom_divisor;
}


static unsigned int MFPctrl_get_modem_info( struct async_struct *info )

{	unsigned	gpip, gi;

	cli();
	gpip = currMFP(info)->par_dt_reg;
	gi   = GIACCESS( 0 );
	sti();
	
	/* DSR is not connected on the Atari, assume it to be set;
	 * RI is tested by the RI bitpos field of info, because the RI is
	 * signalled at different ports on TT and Falcon
	 */
	return( ((gi   & GI_RTS  ) ? TIOCM_RTS : 0) |
			((gi   & GI_DTR  ) ? TIOCM_DTR : 0) |
			((gpip & GPIP_DCD) ? TIOCM_CAR : 0) |
			((gpip & GPIP_CTS) ? TIOCM_CTS : 0) |
			TIOCM_DSR |
			(TEST_BITPOS( info->RI ) ? TIOCM_RNG : 0)
		  );
}


static unsigned int MFPbare_get_modem_info( struct async_struct *info )

{
	return( TIOCM_RTS | TIOCM_DTR | TIOCM_CAR | TIOCM_CTS | TIOCM_DSR );
}


static int MFPctrl_set_modem_info( struct async_struct *info,
								   int new_dtr, int new_rts )

{
	if (new_dtr == 0)
		MFPser_DTRoff();
	else if (new_dtr == 1)
		MFPser_DTRon();

	if (new_rts == 0)
		MFPser_RTSoff();
	else if (new_rts == 1)
		MFPser_RTSon();

	return( 0 );
}


static int MFPbare_set_modem_info( struct async_struct *info,
								   int new_dtr, int new_rts )

{
	/* no-op */

	/* Is it right to return an error or should the attempt to change
	 * DTR or RTS be silently ignored?
	 */
	return( -EINVAL );
}



