patch-2.2.18 linux/drivers/char/amiga_ser.c
Next file: linux/drivers/char/amikeyb.c
Previous file: linux/drivers/char/agp/agpgart_fe.c
Back to the patch index
Back to the overall index
- Lines: 559
- Date:
Fri Oct 13 23:57:54 2000
- Orig file:
v2.2.17/drivers/char/amiga_ser.c
- Orig date:
Thu Jan 1 01:00:00 1970
diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.17/drivers/char/amiga_ser.c linux/drivers/char/amiga_ser.c
@@ -0,0 +1,558 @@
+/*
+ * drivers/char/amiga_ser.c: Amiga built-in serial port driver.
+ *
+ * Copyright 1994 Roman Hodek, 1994 Hamish Macdonald
+ * Based on the Atari MFP driver by Roman Hodek
+ *
+ * Modifications by Matthias Welwarsky <Matthias.Welwarsky@ppp.th-darmstadt.de>
+ * - fixed reentrancy problem in ser_tx_int()
+ *
+ * 20/02/99 - Jesper Skov: Added mb() calls and KGDB support.
+ * 27/04/96 - Jes Soerensen: Upgraded for Linux-1.3.x.
+ * 02/09/96 - Jes Soerensen: Moved the {request,free}_irq call for
+ * AMIGA_VERTB interrupts into the init/deinit funtions as
+ * there is no reason to service the ser_vbl_int when the
+ * serial port is not in use.
+ * 30/04/96 - Geert Uytterhoeven: Added incoming BREAK detection
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+
+
+/*
+ * This file implements the driver for the Amiga built-in serial port.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/termios.h>
+#include <linux/interrupt.h>
+#include <linux/m68kserial.h>
+#include <linux/linkage.h>
+
+#include <asm/setup.h>
+#include <asm/amigahw.h>
+#include <asm/amigaints.h>
+#include <asm/traps.h>
+#include <asm/irq.h>
+#include <asm/atomic.h>
+
+/* some serial hardware definitions */
+#define SDR_OVRUN (1<<15)
+#define SDR_RBF (1<<14)
+#define SDR_TBE (1<<13)
+#define SDR_TSRE (1<<12)
+
+#define AC_SETCLR (1<<15)
+#define AC_UARTBRK (1<<11)
+
+#define SER_DTR (1<<7)
+#define SER_RTS (1<<6)
+#define SER_DCD (1<<5)
+#define SER_CTS (1<<4)
+#define SER_DSR (1<<3)
+
+/***************************** Prototypes *****************************/
+
+static void ser_rx_int( int irq, void *data, struct pt_regs *fp);
+static void ser_tx_int( int irq, void *data, struct pt_regs *fp);
+static void ser_vbl_int( int irq, void *data, struct pt_regs *fp);
+static void ser_init( struct m68k_async_struct *info );
+static void ser_deinit( struct m68k_async_struct *info, int leave_dtr );
+static void ser_enab_tx_int( struct m68k_async_struct *info, int enab_flag );
+static int ser_check_custom_divisor (struct m68k_async_struct *info,
+ int baud_base, int divisor);
+static void ser_change_speed( struct m68k_async_struct *info );
+static void ser_throttle( struct m68k_async_struct *info, int status );
+static void ser_set_break( struct m68k_async_struct *info, int break_flag );
+static void ser_get_serial_info( struct m68k_async_struct *info,
+ struct serial_struct *retinfo );
+static unsigned int ser_get_modem_info( struct m68k_async_struct *info );
+static int ser_set_modem_info( struct m68k_async_struct *info, int new_dtr,
+ int new_rts );
+static void ser_stop_receive( struct m68k_async_struct *info );
+static int ser_trans_empty( struct m68k_async_struct *info );
+
+/************************* End of Prototypes **************************/
+
+
+
+/* SERIALSWITCH structure for the Amiga serial port
+ */
+
+static SERIALSWITCH amiga_ser_switch = {
+ ser_init, ser_deinit, ser_enab_tx_int,
+ ser_check_custom_divisor, ser_change_speed,
+ ser_throttle, ser_set_break,
+ ser_get_serial_info, ser_get_modem_info,
+ ser_set_modem_info, NULL, ser_stop_receive, ser_trans_empty, NULL
+};
+
+/* Standard speeds table */
+static int baud_table[19] = {
+ /* B0 */ 0,
+ /* B50 */ 50,
+ /* B75 */ 75,
+ /* B110 */ 110,
+ /* B134 */ 134,
+ /* B150 */ 150,
+ /* B200 */ 200,
+ /* B300 */ 300,
+ /* B600 */ 600,
+ /* B1200 */ 1200,
+ /* B1800 */ 1800,
+ /* B2400 */ 2400,
+ /* B4800 */ 4800,
+ /* B9600 */ 9600,
+ /* B19200 */ 19200,
+ /* B38400 */ 38400,
+ /* B57600 */ 57600,
+ /* B115200*/ 115200,
+ /* B230400*/ 230400
+};
+
+static __inline__ void ser_DTRoff(void)
+{
+ ciab.pra |= SER_DTR; /* active low */
+}
+
+static __inline__ void ser_DTRon(void)
+{
+ ciab.pra &= ~SER_DTR; /* active low */
+}
+
+static __inline__ void ser_RTSoff(void)
+{
+ ciab.pra |= SER_RTS; /* active low */
+}
+
+static __inline__ void ser_RTSon(void)
+{
+ ciab.pra &= ~SER_RTS; /* active low */
+}
+
+static int line; /* the serial line assigned by register_serial() */
+/* use this value in isr's to get rid of the data pointer in the future */
+/* This variable holds the current state of the DCD/CTS bits */
+static unsigned char current_ctl_bits;
+
+static __inline__ void check_modem_status(struct m68k_async_struct *info)
+{
+ unsigned char bits;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ return;
+
+ bits = ciab.pra & (SER_DCD | SER_CTS);
+
+ if (bits ^ current_ctl_bits) {
+ if ((bits ^ current_ctl_bits) & SER_DCD) {
+ rs_dcd_changed(info, !(bits & SER_DCD));
+ }
+
+ if ((bits ^ current_ctl_bits) & SER_CTS)
+ rs_check_cts(info, !(bits & SER_CTS));
+ }
+ current_ctl_bits = bits;
+}
+
+static struct m68k_async_struct *amiga_info;
+
+int amiga_serinit( void )
+{
+ unsigned long flags;
+ struct serial_struct req;
+ struct m68k_async_struct *info;
+
+ if (!MACH_IS_AMIGA || !AMIGAHW_PRESENT(AMI_SERIAL))
+ return -ENODEV;
+
+ req.line = -1; /* first free ttyS? device */
+ req.type = SER_AMIGA;
+ req.port = (int) &custom.serdatr; /* dummy value */
+ if ((line = m68k_register_serial( &req )) < 0) {
+ printk( "Cannot register built-in serial port: no free device\n" );
+ return -EBUSY;
+ }
+ info = &rs_table[line];
+
+ save_flags (flags);
+ cli();
+
+ /* set ISRs, and then disable the rx interrupts */
+ request_irq(IRQ_AMIGA_TBE, ser_tx_int, 0, "serial TX", info);
+ request_irq(IRQ_AMIGA_RBF, ser_rx_int, 0, "serial RX", info);
+
+ amiga_info = info;
+
+ /* turn off Rx and Tx interrupts */
+ custom.intena = IF_RBF | IF_TBE;
+
+ /* clear any pending interrupt */
+ custom.intreq = IF_RBF | IF_TBE;
+ restore_flags (flags);
+
+ /*
+ * set the appropriate directions for the modem control flags,
+ * and clear RTS and DTR
+ */
+ ciab.ddra |= (SER_DTR | SER_RTS); /* outputs */
+ ciab.ddra &= ~(SER_DCD | SER_CTS | SER_DSR); /* inputs */
+
+ info->sw = &amiga_ser_switch;
+
+#ifdef CONFIG_KGDB
+ /* turn Rx interrupts on for GDB */
+ custom.intena = IF_SETCLR | IF_RBF;
+ ser_RTSon();
+#endif
+
+ return 0;
+}
+
+static void ser_rx_int(int irq, void *data, struct pt_regs *fp)
+{
+#ifdef CONFIG_KGDB
+ extern void breakpoint (void);
+ int ch;
+
+ ch = custom.serdatr;
+ mb();
+
+ custom.intreq = IF_RBF;
+
+ /* Break signal from GDB? */
+ if (0x03 == (ch & 0xff)) {
+ /* FIXME: This way of doing a breakpoint sucks
+ big time. I will fix it later. */
+ breakpoint ();
+ }
+#else
+ struct m68k_async_struct *info = data;
+ int ch, err;
+
+ ch = custom.serdatr;
+ mb();
+
+ custom.intreq = IF_RBF;
+
+ if ((ch & 0x1ff) == 0)
+ err = TTY_BREAK;
+ else if (ch & SDR_OVRUN)
+ err = TTY_OVERRUN;
+ else
+ err = 0;
+ rs_receive_char(info, ch & 0xff, err);
+#endif
+}
+
+static void ser_tx_int( int irq, void *data, struct pt_regs *fp)
+{
+#ifndef CONFIG_KGDB
+ struct m68k_async_struct *info = data;
+ int ch;
+
+ if (custom.serdatr & SDR_TBE) {
+ if ((ch = rs_get_tx_char(info)) >= 0)
+ /* write next char */
+ custom.serdat = ch | 0x100;
+ if (ch == -1 || rs_no_more_tx( info ))
+ /* disable tx interrupts */
+ custom.intena = IF_TBE;
+ }
+#endif
+}
+
+
+static void ser_vbl_int( int irq, void *data, struct pt_regs *fp)
+{
+ struct m68k_async_struct *info = data;
+
+ check_modem_status(info);
+}
+
+
+static void ser_init( struct m68k_async_struct *info )
+{
+#ifndef CONFIG_KGDB
+ request_irq(IRQ_AMIGA_VERTB, ser_vbl_int, 0,
+ "serial status", amiga_info);
+
+ /* enable both Rx and Tx interrupts */
+ custom.intena = IF_SETCLR | IF_RBF | IF_TBE;
+
+ /* turn on DTR and RTS */
+ ser_DTRon();
+ ser_RTSon();
+
+ /* remember current state of the DCD and CTS bits */
+ current_ctl_bits = ciab.pra & (SER_DCD | SER_CTS);
+
+ MOD_INC_USE_COUNT;
+#endif
+}
+
+
+static void ser_enab_tx_int (struct m68k_async_struct *info, int enab_flag)
+{
+#ifndef CONFIG_KGDB
+ if (enab_flag) {
+ unsigned long flags;
+ save_flags(flags);
+ cli();
+ custom.intena = IF_SETCLR | IF_TBE;
+ mb();
+ /* set a pending Tx Interrupt, transmitter should restart now */
+ custom.intreq = IF_SETCLR | IF_TBE;
+ mb();
+ restore_flags(flags);
+ } else {
+ /* disable Tx interrupt and remove any pending interrupts */
+ custom.intena = IF_TBE;
+ custom.intreq = IF_TBE;
+ }
+#endif
+}
+
+
+static void ser_deinit( struct m68k_async_struct *info, int leave_dtr )
+{
+#ifndef CONFIG_KGDB
+ /* disable Rx and Tx interrupt */
+ custom.intena = IF_RBF | IF_TBE;
+ mb();
+
+ /* wait for last byte to be completely shifted out */
+ while( !(custom.serdatr & SDR_TSRE) )
+ barrier();
+
+ /* drop RTS and DTR if required */
+ ser_RTSoff();
+ if (!leave_dtr)
+ ser_DTRoff();
+
+ free_irq(IRQ_AMIGA_VERTB, amiga_info);
+ MOD_DEC_USE_COUNT;
+#endif
+}
+
+
+static int ser_check_custom_divisor (struct m68k_async_struct *info,
+ int baud_base, int divisor)
+{
+ /* allow any divisor */
+ return 0;
+}
+
+
+static void ser_change_speed( struct m68k_async_struct *info )
+{
+#ifndef CONFIG_KGDB
+ unsigned cflag, baud, chsize, stopb, parity, aflags;
+ unsigned div = 0;
+ int realbaud;
+
+ 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 (cflag & CRTSCTS)
+ info->flags |= ASYNC_CTS_FLOW;
+ else
+ info->flags &= ~ASYNC_CTS_FLOW;
+ if (cflag & CLOCAL)
+ info->flags &= ~ASYNC_CHECK_CD;
+ else
+ info->flags |= ASYNC_CHECK_CD;
+
+ if (baud & CBAUDEX)
+ baud = baud - (B57600 - B38400 - 1);
+
+
+ if (baud == 15) {
+ switch (aflags) {
+ case ASYNC_SPD_HI:
+ baud += 1;
+ break;
+ case ASYNC_SPD_VHI:
+ baud += 2;
+ break;
+ case ASYNC_SPD_SHI:
+ baud += 3;
+ break;
+ case ASYNC_SPD_WARP:
+ baud += 4;
+ break;
+ case ASYNC_SPD_CUST:
+ div = info->custom_divisor;
+ break;
+ }
+ }
+ if (!div){
+ /* Maximum speed is 230400 :-) */
+ if (baud > 18) baud = 18;
+ realbaud = baud_table[baud];
+ if (realbaud)
+ div = (amiga_colorclock+realbaud/2)/realbaud - 1;
+ }
+
+ if (div) {
+ /* turn on DTR */
+ ser_DTRon();
+ } else {
+ /* speed == 0 -> drop DTR */
+ ser_DTRoff();
+ return;
+ }
+
+ /* setup the serial port period register */
+ custom.serper = div;
+#endif
+}
+
+
+static void ser_throttle( struct m68k_async_struct *info, int status )
+{
+#ifndef CONFIG_KGDB
+ if (status)
+ ser_RTSoff();
+ else
+ ser_RTSon();
+#endif
+}
+
+
+static void ser_set_break( struct m68k_async_struct *info, int break_flag )
+{
+#ifndef CONFIG_KGDB
+ if (break_flag)
+ custom.adkcon = AC_SETCLR | AC_UARTBRK;
+ else
+ custom.adkcon = AC_UARTBRK;
+#endif
+}
+
+
+static void ser_get_serial_info( struct m68k_async_struct *info,
+ struct serial_struct *retinfo )
+{
+ retinfo->baud_base = amiga_colorclock;
+ retinfo->custom_divisor = info->custom_divisor;
+}
+
+
+static unsigned int ser_get_modem_info( struct m68k_async_struct *info )
+{
+ unsigned int minfo = ciab.pra;
+
+ return(
+ ((minfo & SER_DTR) ? 0 : TIOCM_DTR) |
+ ((minfo & SER_RTS) ? 0 : TIOCM_RTS) |
+ ((minfo & SER_DCD) ? 0 : TIOCM_CAR) |
+ ((minfo & SER_CTS) ? 0 : TIOCM_CTS) |
+ ((minfo & SER_DSR) ? 0 : TIOCM_DSR) |
+ /* TICM_RNG */ 0
+ );
+}
+
+
+static int ser_set_modem_info( struct m68k_async_struct *info,
+ int new_dtr, int new_rts )
+{
+#ifndef CONFIG_KGDB
+ if (new_dtr == 0)
+ ser_DTRoff();
+ else if (new_dtr == 1)
+ ser_DTRon();
+
+ if (new_rts == 0)
+ ser_RTSoff();
+ else if (new_rts == 1)
+ ser_RTSon();
+
+ return 0;
+#endif
+}
+
+static void ser_stop_receive( struct m68k_async_struct *info )
+{
+#ifndef CONFIG_KGDB
+ /* disable receive interrupts */
+ custom.intena = IF_RBF;
+ /* clear any pending receive interrupt */
+ custom.intreq = IF_RBF;
+#endif
+}
+
+static int ser_trans_empty( struct m68k_async_struct *info )
+{
+ return (custom.serdatr & SDR_TSRE);
+}
+
+#ifdef CONFIG_KGDB
+int amiga_ser_out( unsigned char c )
+{
+ custom.serdat = c | 0x100;
+ mb();
+ while (!(custom.serdatr & 0x2000))
+ barrier();
+ return 1;
+}
+
+unsigned char amiga_ser_in( void )
+{
+ unsigned char c;
+
+ /* XXX: is that ok?? derived from amiga_ser.c... */
+ while( !(custom.intreqr & IF_RBF) )
+ barrier();
+ c = custom.serdatr;
+ /* clear the interrupt, so that another character can be read */
+ custom.intreq = IF_RBF;
+ return c;
+}
+#endif
+
+#ifdef MODULE
+int init_module(void)
+{
+ return amiga_serinit();
+}
+
+void cleanup_module(void)
+{
+ m68k_unregister_serial(line);
+ custom.intena = IF_RBF | IF_TBE; /* forbid interrupts */
+ custom.intreq = IF_RBF | IF_TBE; /* clear pending interrupts */
+ mb();
+ free_irq(IRQ_AMIGA_TBE, amiga_info);
+ free_irq(IRQ_AMIGA_RBF, amiga_info);
+}
+#endif
+
+
+/* ------------------------------------
+ * Local variables:
+ * c-indent-level: 4
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * End:
+ * ------------------------------------
+ */
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)