patch-2.3.50 linux/drivers/char/sh-sci.c

Next file: linux/drivers/char/sh-sci.h
Previous file: linux/drivers/char/pty.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.49/linux/drivers/char/sh-sci.c linux/drivers/char/sh-sci.c
@@ -0,0 +1,992 @@
+/* $Id: sh-sci.c,v 1.32 2000-03-05 13:56:18+09 gniibe Exp $
+ *
+ *  linux/drivers/char/sh-sci.c
+ *
+ *  SuperH on-chip serial module support.  (SCI with no FIFO / with FIFO)
+ *  Copyright (C) 1999, 2000  Niibe Yutaka
+ *
+ * TTY code is based on sx.c (Specialix SX driver) by:
+ *
+ *   (C) 1998 R.E.Wolff@BitWizard.nl
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/console.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <asm/bitops.h>
+
+#include "generic_serial.h"
+#include "sh-sci.h"
+
+#ifdef CONFIG_DEBUG_KERNEL_WITH_GDB_STUB
+static void gdb_detach(void);
+#endif
+
+struct sci_port sci_ports[1];
+
+/* Function prototypes */
+static void sci_disable_tx_interrupts(void *ptr);
+static void sci_enable_tx_interrupts(void *ptr);
+static void sci_disable_rx_interrupts(void *ptr);
+static void sci_enable_rx_interrupts(void *ptr);
+static int  sci_get_CD(void *ptr);
+static void sci_shutdown_port(void *ptr);
+static void sci_set_real_termios(void *ptr);
+static void sci_hungup(void *ptr);
+static void sci_close(void *ptr);
+static int sci_chars_in_buffer(void *ptr);
+static int sci_init_drivers(void);
+
+static struct tty_driver sci_driver, sci_callout_driver;
+
+#define SCI_NPORTS 1
+static struct tty_struct *sci_table[SCI_NPORTS] = { NULL, };
+static struct termios *sci_termios[2]; /* nomal, locked */
+
+int sci_refcount;
+int sci_debug = 0;
+
+#ifdef MODULE
+MODULE_PARM(sci_debug, "i");
+#endif
+
+static struct real_driver sci_real_driver = {
+	sci_disable_tx_interrupts,
+	sci_enable_tx_interrupts,
+	sci_disable_rx_interrupts,
+	sci_enable_rx_interrupts,
+	sci_get_CD,
+	sci_shutdown_port,
+	sci_set_real_termios,
+	sci_chars_in_buffer,
+	sci_close,
+	sci_hungup,
+	NULL
+};
+
+static void sci_setsignals(struct sci_port *port, int dtr, int rts)
+{
+	/* This routine is used for seting signals of: DTR, DCD, CTS/RTS */
+	/* We use SCIF's hardware for CTS/RTS, so don't need any for that. */
+	/* If you have signals for DTR and DCD, please implement here. */
+	;
+}
+
+static int sci_getsignals(struct sci_port *port)
+{
+	/* This routine is used for geting signals of: DTR, DCD, DSR, RI,
+	   and CTS/RTS */
+
+	return TIOCM_DTR|TIOCM_RTS|TIOCM_DSR;
+/*
+	(((o_stat & OP_DTR)?TIOCM_DTR:0) |
+	 ((o_stat & OP_RTS)?TIOCM_RTS:0) |
+	 ((i_stat & IP_CTS)?TIOCM_CTS:0) |
+	 ((i_stat & IP_DCD)?TIOCM_CAR:0) |
+	 ((i_stat & IP_DSR)?TIOCM_DSR:0) |
+	 ((i_stat & IP_RI) ?TIOCM_RNG:0)
+*/
+}
+
+static void sci_set_baud(struct sci_port *port)
+{
+	int t;
+
+	switch (port->gs.baud) {
+	case 0:
+		t = -1;
+		break;
+	case 2400:
+		t = BPS_2400;
+		break;
+	case 4800:
+		t = BPS_4800;
+		break;
+	case 9600:
+		t = BPS_9600;
+		break;
+	case 19200:
+		t = BPS_19200;
+		break;
+	case 38400:
+		t = BPS_38400;
+		break;
+	default:
+		printk(KERN_INFO "sci: unsupported baud rate: %d, use 115200 instead.\n", port->gs.baud);
+	case 115200:
+		t = BPS_115200;
+		break;
+	}
+
+	if (t > 0) {
+		sci_setsignals (port, 1, -1); 
+		ctrl_outb(t, SCBRR);
+		ctrl_outw(0xa400, RFCR); /* Refresh counter clear */
+		while (ctrl_inw(RFCR) < WAIT_RFCR_COUNTER)
+			;
+	} else {
+		sci_setsignals (port, 0, -1);
+	}
+}
+
+static void sci_set_termios_cflag(struct sci_port *port)
+{
+	unsigned short status;
+	unsigned short smr_val=0;
+#if defined(CONFIG_SH_SCIF_SERIAL)
+	unsigned short fcr_val=6; /* TFRST=1, RFRST=1 */
+#endif
+
+	do
+		status = ctrl_in(SC_SR);
+	while (!(status & SCI_TEND));
+
+	port->old_cflag = port->gs.tty->termios->c_cflag;
+
+	ctrl_out(0x00, SCSCR);	/* TE=0, RE=0, CKE1=0 */
+#if defined(CONFIG_SH_SCIF_SERIAL)
+	ctrl_out(fcr_val, SCFCR);
+	fcr_val = 0;
+#endif
+
+	if ((port->gs.tty->termios->c_cflag & CSIZE) == CS7)
+		smr_val |= 0x40;
+	if (C_PARENB(port->gs.tty))
+		smr_val |= 0x20;
+	if (C_PARODD(port->gs.tty))
+		smr_val |= 0x10;
+	if (C_CSTOPB(port->gs.tty))
+		smr_val |= 0x08;
+	ctrl_out(smr_val, SCSMR);
+
+#if defined(CONFIG_SH_SCIF_SERIAL)
+	if (C_CRTSCTS(port->gs.tty))
+		fcr_val |= 0x08;
+	ctrl_out(fcr_val, SCFCR);
+#endif
+
+	sci_set_baud(port);
+	ctrl_out(SCSCR_INIT, SCSCR);	/* TIE=0,RIE=0,TE=1,RE=1 */
+#if 0 /* defined(CONFIG_SH_SCIF_SERIAL) */
+	ctrl_outw(0x0080, SCSPTR); /* Set RTS = 1 */
+#endif
+	sci_enable_rx_interrupts(port);
+}
+
+static void sci_set_real_termios(void *ptr)
+{
+	struct sci_port *port = ptr;
+
+	if (port->old_cflag != port->gs.tty->termios->c_cflag)
+		sci_set_termios_cflag(port);
+
+	/* Tell line discipline whether we will do input cooking */
+	if (I_OTHER(port->gs.tty))
+		clear_bit(TTY_HW_COOK_IN, &port->gs.tty->flags);
+	else
+		set_bit(TTY_HW_COOK_IN, &port->gs.tty->flags);
+
+/* Tell line discipline whether we will do output cooking.
+ * If OPOST is set and no other output flags are set then we can do output
+ * processing.  Even if only *one* other flag in the O_OTHER group is set
+ * we do cooking in software.
+ */
+	if (O_OPOST(port->gs.tty) && !O_OTHER(port->gs.tty))
+		set_bit(TTY_HW_COOK_OUT, &port->gs.tty->flags);
+	else
+		clear_bit(TTY_HW_COOK_OUT, &port->gs.tty->flags);
+}
+
+/* ********************************************************************** *
+ *                   the interrupt related routines                       *
+ * ********************************************************************** */
+
+static void sci_transmit_chars(struct sci_port *port)
+{
+	int count, i;
+	int txroom;
+	unsigned long flags;
+	unsigned short status;
+	unsigned short ctrl;
+	unsigned char c;
+
+	status = ctrl_in(SC_SR);
+	if (!(status & SCI_TD_E)) {
+		save_and_cli(flags);
+		ctrl = ctrl_in(SCSCR);
+		if (port->gs.xmit_cnt == 0) {
+			ctrl &= ~SCI_CTRL_FLAGS_TIE;
+			port->gs.flags &= ~GS_TX_INTEN;
+		} else
+			ctrl |= SCI_CTRL_FLAGS_TIE;
+		ctrl_out(ctrl, SCSCR);
+		restore_flags(flags);
+		return;
+	}
+
+	while (1) {
+		count = port->gs.xmit_cnt;
+#if defined(CONFIG_SH_SCIF_SERIAL)
+		txroom = 16 - (ctrl_inw(SCFDR)>>8);
+#else
+		txroom = (ctrl_in(SC_SR)&SCI_TD_E)?1:0;
+#endif
+		if (count > txroom)
+			count = txroom;
+
+		/* Don't copy pas the end of the source buffer */
+		if (count > SERIAL_XMIT_SIZE - port->gs.xmit_tail)
+                	count = SERIAL_XMIT_SIZE - port->gs.xmit_tail;
+
+		/* If for one reason or another, we can't copy more data, we're done! */
+		if (count == 0)
+			break;
+
+		for (i=0; i<count; i++) {
+			c = port->gs.xmit_buf[port->gs.xmit_tail + i];
+			ctrl_outb(c, SC_TDR);
+		}
+		ctrl_out(SCI_TD_E_CLEAR, SC_SR);
+
+		/* Update the kernel buffer end */
+		port->gs.xmit_tail = (port->gs.xmit_tail + count) & (SERIAL_XMIT_SIZE-1);
+
+		/* This one last. (this is essential)
+		   It would allow others to start putting more data into the buffer! */
+		port->gs.xmit_cnt -= count;
+	}
+
+	if (port->gs.xmit_cnt <= port->gs.wakeup_chars) {
+		if ((port->gs.tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+		    port->gs.tty->ldisc.write_wakeup)
+			(port->gs.tty->ldisc.write_wakeup)(port->gs.tty);
+		wake_up_interruptible(&port->gs.tty->write_wait);
+	}
+
+	save_and_cli(flags);
+	ctrl = ctrl_in(SCSCR);
+	if (port->gs.xmit_cnt == 0) {
+		ctrl &= ~SCI_CTRL_FLAGS_TIE;
+		port->gs.flags &= ~GS_TX_INTEN;
+	} else {
+#if defined(CONFIG_SH_SCIF_SERIAL)
+		ctrl_in(SC_SR); /* Dummy read */
+		ctrl_out(SCI_TD_E_CLEAR, SC_SR);
+#endif
+		ctrl |= SCI_CTRL_FLAGS_TIE;
+	}
+	ctrl_out(ctrl, SCSCR);
+	restore_flags(flags);
+}
+
+static inline void sci_receive_chars(struct sci_port *port)
+{
+	int i, count;
+	struct tty_struct *tty;
+	int copied=0;
+	unsigned short status;
+
+	status = ctrl_in(SC_SR);
+	if (!(status & SCI_RD_F))
+		return;
+
+	tty = port->gs.tty;
+	while (1) {
+#if defined(CONFIG_SH_SCIF_SERIAL)
+		count = ctrl_inw(SCFDR)&0x001f;
+#else
+		count = (ctrl_in(SC_SR)&SCI_RD_F)?1:0;
+#endif
+
+		/* Don't copy more bytes than there is room for in the buffer */
+		if (tty->flip.count + count > TTY_FLIPBUF_SIZE)
+			count = TTY_FLIPBUF_SIZE - tty->flip.count;
+
+		/* If for one reason or another, we can't copy more data, we're done! */
+		if (count == 0)
+			break;
+
+		for (i=0; i<count; i++)
+			tty->flip.char_buf_ptr[i] = ctrl_inb(SC_RDR);
+		ctrl_in(SC_SR); /* dummy read */
+		ctrl_out(SCI_RDRF_CLEAR, SC_SR);
+
+		memset(tty->flip.flag_buf_ptr, TTY_NORMAL, count);
+
+		/* Update the kernel buffer end */
+		tty->flip.count += count;
+		tty->flip.char_buf_ptr += count;
+		tty->flip.flag_buf_ptr += count;
+
+		copied += count;
+	}
+
+	if (copied)
+		/* Tell the rest of the system the news. New characters! */
+		tty_flip_buffer_push(tty);
+}
+
+static void sci_rx_interrupt(int irq, void *ptr, struct pt_regs *regs)
+{
+	struct sci_port *port = ptr;
+
+	if (port->gs.flags & GS_ACTIVE)
+		if (!(port->gs.flags & SCI_RX_THROTTLE))
+			sci_receive_chars(port);
+}
+
+static void sci_tx_interrupt(int irq, void *ptr, struct pt_regs *regs)
+{
+	struct sci_port *port = ptr;
+
+	if (port->gs.flags & GS_ACTIVE)
+		if (port->gs.xmit_cnt) {
+			sci_transmit_chars(port);
+		}
+}
+
+static void sci_er_interrupt(int irq, void *ptr, struct pt_regs *regs)
+{
+	/* Handle errors */
+	if (ctrl_in(SC_SR) & SCI_ERRORS)
+		ctrl_out(SCI_ERROR_CLEAR, SC_SR);
+
+	/* Kick the transmission */
+	sci_tx_interrupt(irq, ptr, regs);
+}
+
+/* ********************************************************************** *
+ *                Here are the routines that actually                     *
+ *              interface with the generic_serial driver                  *
+ * ********************************************************************** */
+
+static void sci_disable_tx_interrupts(void *ptr)
+{
+	unsigned long flags;
+	unsigned short ctrl;
+
+	/* Clear TIE (Transmit Interrupt Enable) bit in SCSCR */
+	save_and_cli(flags);
+	ctrl = ctrl_in(SCSCR);
+	ctrl &= ~SCI_CTRL_FLAGS_TIE;
+	ctrl_out(ctrl, SCSCR);
+	restore_flags(flags);
+}
+
+static void sci_enable_tx_interrupts(void *ptr)
+{
+	struct sci_port *port = ptr; 
+
+	disable_irq(SCI_TXI_IRQ);
+	sci_transmit_chars(port);
+	enable_irq(SCI_TXI_IRQ);
+}
+
+static void sci_disable_rx_interrupts(void * ptr)
+{
+	unsigned long flags;
+	unsigned short ctrl;
+
+	/* Clear RIE (Receive Interrupt Enable) bit in SCSCR */
+	save_and_cli(flags);
+	ctrl = ctrl_in(SCSCR);
+	ctrl &= ~SCI_CTRL_FLAGS_RIE;
+	ctrl_out(ctrl, SCSCR);
+	restore_flags(flags);
+}
+
+static void sci_enable_rx_interrupts(void * ptr)
+{
+	unsigned long flags;
+	unsigned short ctrl;
+
+	/* Set RIE (Receive Interrupt Enable) bit in SCSCR */
+	save_and_cli(flags);
+	ctrl = ctrl_in(SCSCR);
+	ctrl |= SCI_CTRL_FLAGS_RIE;
+	ctrl_out(ctrl, SCSCR);
+	restore_flags(flags);
+}
+
+static int sci_get_CD(void * ptr)
+{
+	/* If you have signal for CD (Carrier Detect), please change here. */
+	return 1;
+}
+
+static int sci_chars_in_buffer(void * ptr)
+{
+#if defined(CONFIG_SH_SCIF_SERIAL)
+	return (ctrl_inw(SCFDR) >> 8) + ((ctrl_in(SC_SR) & SCI_TEND)? 0: 1);
+#else
+	return (ctrl_in(SC_SR) & SCI_TEND)? 0: 1;
+#endif
+}
+
+static void sci_shutdown_port(void * ptr)
+{
+	struct sci_port *port = ptr; 
+
+	port->gs.flags &= ~ GS_ACTIVE;
+	if (port->gs.tty && port->gs.tty->termios->c_cflag & HUPCL)
+		sci_setsignals(port, 0, 0);
+}
+
+/* ********************************************************************** *
+ *                Here are the routines that actually                     *
+ *               interface with the rest of the system                    *
+ * ********************************************************************** */
+
+static int sci_open(struct tty_struct * tty, struct file * filp)
+{
+	struct sci_port *port;
+	int retval, line;
+
+	line = MINOR(tty->device) - SCI_MINOR_START;
+
+	if ((line < 0) || (line >= SCI_NPORTS))
+		return -ENODEV;
+
+	port = &sci_ports[line];
+
+	tty->driver_data = port;
+	port->gs.tty = tty;
+	port->gs.count++;
+
+	/*
+	 * Start up serial port
+	 */
+	retval = gs_init_port(&port->gs);
+	if (retval) {
+		port->gs.count--;
+		return retval;
+	}
+
+	port->gs.flags |= GS_ACTIVE;
+	sci_setsignals(port, 1,1);
+
+	if (port->gs.count == 1) {
+		MOD_INC_USE_COUNT;
+	}
+
+	retval = block_til_ready(port, filp);
+
+	if (retval) {
+		MOD_DEC_USE_COUNT;
+		port->gs.count--;
+		return retval;
+	}
+
+	if ((port->gs.count == 1) && (port->gs.flags & ASYNC_SPLIT_TERMIOS)) {
+		if (tty->driver.subtype == SERIAL_TYPE_NORMAL)
+			*tty->termios = port->gs.normal_termios;
+		else 
+			*tty->termios = port->gs.callout_termios;
+		sci_set_real_termios(port);
+	}
+
+	sci_enable_rx_interrupts(port);
+
+	port->gs.session = current->session;
+	port->gs.pgrp = current->pgrp;
+
+	return 0;
+}
+
+static void sci_hungup(void *ptr)
+{
+	MOD_DEC_USE_COUNT;
+}
+
+static void sci_close(void *ptr)
+{
+	MOD_DEC_USE_COUNT;
+}
+
+static int sci_ioctl(struct tty_struct * tty, struct file * filp, 
+                     unsigned int cmd, unsigned long arg)
+{
+	int rc;
+	struct sci_port *port = tty->driver_data;
+	int ival;
+
+	rc = 0;
+	switch (cmd) {
+	case TIOCGSOFTCAR:
+		rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+		              (unsigned int *) arg);
+		break;
+	case TIOCSSOFTCAR:
+		if ((rc = verify_area(VERIFY_READ, (void *) arg,
+		                      sizeof(int))) == 0) {
+			get_user(ival, (unsigned int *) arg);
+			tty->termios->c_cflag =
+				(tty->termios->c_cflag & ~CLOCAL) |
+				(ival ? CLOCAL : 0);
+		}
+		break;
+	case TIOCGSERIAL:
+		if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+		                      sizeof(struct serial_struct))) == 0)
+			gs_getserial(&port->gs, (struct serial_struct *) arg);
+		break;
+	case TIOCSSERIAL:
+		if ((rc = verify_area(VERIFY_READ, (void *) arg,
+		                      sizeof(struct serial_struct))) == 0)
+			rc = gs_setserial(&port->gs,
+					  (struct serial_struct *) arg);
+		break;
+	case TIOCMGET:
+		if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+		                      sizeof(unsigned int))) == 0) {
+			ival = sci_getsignals(port);
+			put_user(ival, (unsigned int *) arg);
+		}
+		break;
+	case TIOCMBIS:
+		if ((rc = verify_area(VERIFY_READ, (void *) arg,
+		                      sizeof(unsigned int))) == 0) {
+			get_user(ival, (unsigned int *) arg);
+			sci_setsignals(port, ((ival & TIOCM_DTR) ? 1 : -1),
+			                     ((ival & TIOCM_RTS) ? 1 : -1));
+		}
+		break;
+	case TIOCMBIC:
+		if ((rc = verify_area(VERIFY_READ, (void *) arg,
+		                      sizeof(unsigned int))) == 0) {
+			get_user(ival, (unsigned int *) arg);
+			sci_setsignals(port, ((ival & TIOCM_DTR) ? 0 : -1),
+			                     ((ival & TIOCM_RTS) ? 0 : -1));
+		}
+		break;
+	case TIOCMSET:
+		if ((rc = verify_area(VERIFY_READ, (void *) arg,
+		                      sizeof(unsigned int))) == 0) {
+			get_user(ival, (unsigned int *)arg);
+			sci_setsignals(port, ((ival & TIOCM_DTR) ? 1 : 0),
+			                     ((ival & TIOCM_RTS) ? 1 : 0));
+		}
+		break;
+
+	default:
+		rc = -ENOIOCTLCMD;
+		break;
+	}
+
+	return rc;
+}
+
+static void sci_throttle(struct tty_struct * tty)
+{
+	struct sci_port *port = (struct sci_port *)tty->driver_data;
+
+	/* If the port is using any type of input flow
+	 * control then throttle the port.
+	 */
+	if ((tty->termios->c_cflag & CRTSCTS) || (I_IXOFF(tty)) )
+		port->gs.flags |= SCI_RX_THROTTLE;
+}
+
+static void sci_unthrottle(struct tty_struct * tty)
+{
+	struct sci_port *port = (struct sci_port *)tty->driver_data;
+
+	/* Always unthrottle even if flow control is not enabled on
+	 * this port in case we disabled flow control while the port
+	 * was throttled
+	 */
+	port->gs.flags &= ~SCI_RX_THROTTLE;
+	return;
+}
+
+/* ********************************************************************** *
+ *                    Here are the initialization routines.               *
+ * ********************************************************************** */
+
+static int sci_init_drivers(void)
+{
+	int error;
+	struct sci_port *port;
+
+	memset(&sci_driver, 0, sizeof(sci_driver));
+	sci_driver.magic = TTY_DRIVER_MAGIC;
+	sci_driver.driver_name = "serial";
+	sci_driver.name = "ttyS";
+	sci_driver.major = TTY_MAJOR;
+	sci_driver.minor_start = SCI_MINOR_START;
+	sci_driver.num = 1;
+	sci_driver.type = TTY_DRIVER_TYPE_SERIAL;
+	sci_driver.subtype = SERIAL_TYPE_NORMAL;
+	sci_driver.init_termios = tty_std_termios;
+	sci_driver.init_termios.c_cflag =
+		B115200 | CS8 | CREAD | HUPCL | CLOCAL;
+	sci_driver.flags = TTY_DRIVER_REAL_RAW;
+	sci_driver.refcount = &sci_refcount;
+	sci_driver.table = sci_table;
+	sci_driver.termios = &sci_termios[0];
+	sci_driver.termios_locked = &sci_termios[1];
+	sci_termios[0] = sci_termios[1] = NULL;
+
+	sci_driver.open	= sci_open;
+	sci_driver.close = gs_close;
+	sci_driver.write = gs_write;
+	sci_driver.put_char = gs_put_char;
+	sci_driver.flush_chars = gs_flush_chars;
+	sci_driver.write_room = gs_write_room;
+	sci_driver.chars_in_buffer = gs_chars_in_buffer;
+	sci_driver.flush_buffer = gs_flush_buffer;
+	sci_driver.ioctl = sci_ioctl;
+	sci_driver.throttle = sci_throttle;
+	sci_driver.unthrottle = sci_unthrottle;
+	sci_driver.set_termios = gs_set_termios;
+	sci_driver.stop = gs_stop;
+	sci_driver.start = gs_start;
+	sci_driver.hangup = gs_hangup;
+
+	sci_callout_driver = sci_driver;
+	sci_callout_driver.name = "cua";
+	sci_callout_driver.major = TTYAUX_MAJOR;
+	sci_callout_driver.subtype = SERIAL_TYPE_CALLOUT;
+
+	if ((error = tty_register_driver(&sci_driver))) {
+		printk(KERN_ERR "sci: Couldn't register SCI driver, error = %d\n",
+		       error);
+		return 1;
+	}
+	if ((error = tty_register_driver(&sci_callout_driver))) {
+		tty_unregister_driver(&sci_driver);
+		printk(KERN_ERR "sci: Couldn't register SCI callout driver, error = %d\n",
+		       error);
+		return 1;
+	}
+
+	port = &sci_ports[0];
+	port->gs.callout_termios = tty_std_termios;
+	port->gs.normal_termios	= tty_std_termios;
+	port->gs.magic = SCI_MAGIC;
+	port->gs.close_delay = HZ/2;
+	port->gs.closing_wait = 30 * HZ;
+	port->gs.rd = &sci_real_driver;
+	init_waitqueue_head(&port->gs.open_wait);
+	init_waitqueue_head(&port->gs.close_wait);
+	port->old_cflag = 0;
+
+	return 0;
+}
+
+#ifdef MODULE
+#define sci_init init_module
+#else
+#define sci_init rs_init
+#endif
+
+int __init sci_init(void)
+{
+	struct sci_port *port;
+	int i;
+
+	for (i=SCI_ERI_IRQ; i<SCI_IRQ_END; i++)
+		set_ipr_data(i, SCI_IPR_OFFSET, SCI_PRIORITY);
+
+	port = &sci_ports[0];
+
+	if (request_irq(SCI_ERI_IRQ, sci_er_interrupt, SA_INTERRUPT,
+			"serial", port)) {
+		printk(KERN_ERR "sci: Cannot allocate error irq.\n");
+		return -ENODEV;
+	}
+	if (request_irq(SCI_RXI_IRQ, sci_rx_interrupt, SA_INTERRUPT,
+			"serial", port)) {
+		printk(KERN_ERR "sci: Cannot allocate rx irq.\n");
+		return -ENODEV;
+	}
+	if (request_irq(SCI_TXI_IRQ, sci_tx_interrupt, SA_INTERRUPT,
+			"serial", port)) {
+		printk(KERN_ERR "sci: Cannot allocate tx irq.\n");
+		return -ENODEV;
+	}
+	/* XXX: How about BRI interrupt?? */
+
+	sci_init_drivers();
+
+#ifdef CONFIG_DEBUG_KERNEL_WITH_GDB_STUB
+	gdb_detach();
+#endif
+	return 0;		/* Return -EIO when not detected */
+}
+
+#ifdef MODULE
+#undef func_enter
+#undef func_exit
+
+void cleanup_module(void)
+{
+	int i;
+
+	for (i=SCI_ERI_IRQ; i<SCI_TEI_IRQ; i++) /* XXX: irq_end?? */
+		free_irq(i, port);
+
+	tty_unregister_driver(&sci_driver);
+	tty_unregister_driver(&sci_callout_driver);
+}
+
+#include "generic_serial.c"
+#endif
+
+#ifdef CONFIG_SERIAL_CONSOLE
+/*
+ * ------------------------------------------------------------
+ * Serial console driver for SH-3/SH-4 SCI (with no FIFO)
+ * ------------------------------------------------------------
+ */
+
+static inline void put_char(char c)
+{
+	unsigned long flags;
+	unsigned short status;
+
+	save_and_cli(flags);
+
+	do
+		status = ctrl_in(SC_SR);
+	while (!(status & SCI_TD_E));
+
+	ctrl_outb(c, SC_TDR);
+	ctrl_out(SCI_TD_E_CLEAR, SC_SR);
+
+	restore_flags(flags);
+}
+
+#ifdef CONFIG_DEBUG_KERNEL_WITH_GDB_STUB
+static int in_gdb = 1;
+
+static inline void handle_error(void)
+{				/* Clear error flags */
+	ctrl_out(SCI_ERROR_CLEAR, SC_SR);
+}
+
+static inline int get_char(void)
+{
+	unsigned long flags;
+	unsigned short status;
+	int c;
+
+	save_and_cli(flags);
+	do {
+		status = ctrl_in(SC_SR);
+		if (status & SCI_ERRORS) {
+			handle_error();
+			continue;
+		}
+	} while (!(status & SCI_RD_F));
+	c = ctrl_inb(SC_RDR);
+	ctrl_out(SCI_RDRF_CLEAR, SC_SR);
+	restore_flags(flags);
+
+	return c;
+}
+
+/* Taken from sh-stub.c of GDB 4.18 */
+static const char hexchars[] = "0123456789abcdef";
+static char highhex(int  x)
+{
+	return hexchars[(x >> 4) & 0xf];
+}
+
+static char lowhex(int  x)
+{
+	return hexchars[x & 0xf];
+}
+
+static void gdb_detach(void)
+{
+	asm volatile("trapa	#0xff");
+
+	if (in_gdb == 1) {
+		in_gdb = 0;
+		get_char();
+		put_char('\r');
+		put_char('\n');
+	}
+}
+#endif
+
+/* send the packet in buffer.  The host get's one chance to read it.
+   This routine does not wait for a positive acknowledge.  */
+
+static void
+put_string(const char *buffer, int count)
+{
+	int i;
+	const unsigned char *p = buffer;
+#ifdef CONFIG_DEBUG_KERNEL_WITH_GDB_STUB
+	int checksum;
+
+if (in_gdb) {
+	/*  $<packet info>#<checksum>. */
+	do {
+		unsigned char c;
+		put_char('$');
+		put_char('O'); /* 'O'utput to console */
+		checksum = 'O';
+
+		for (i=0; i<count; i++) { /* Don't use run length encoding */
+			int h, l;
+
+			c = *p++;
+			h = highhex(c);
+			l = lowhex(c);
+			put_char(h);
+			put_char(l);
+			checksum += h + l;
+		}
+		put_char('#');
+		put_char(highhex(checksum));
+		put_char(lowhex(checksum));
+	} while  (get_char() != '+');
+} else
+#endif
+	for (i=0; i<count; i++) {
+		if (*p == 10)
+			put_char('\r');
+		put_char(*p++);
+	}
+}
+
+/*
+ *	Print a string to the serial port trying not to disturb
+ *	any possible real use of the port...
+ */
+static void serial_console_write(struct console *co, const char *s,
+				 unsigned count)
+{
+	put_string(s, count);
+}
+
+/*
+ *	Receive character from the serial port
+ */
+static int serial_console_wait_key(struct console *co)
+{
+	/* Not implemented yet */
+	return 0;
+}
+
+static kdev_t serial_console_device(struct console *c)
+{
+	return MKDEV(TTY_MAJOR, SCI_MINOR_START + c->index);
+}
+
+/*
+ *	Setup initial baud/bits/parity. We do two things here:
+ *	- construct a cflag setting for the first rs_open()
+ *	- initialize the serial port
+ *	Return non-zero if we didn't find a serial port.
+ */
+static int __init serial_console_setup(struct console *co, char *options)
+{
+	int	baud = 115200;
+	int	bits = 8;
+	int	parity = 'n';
+	int	cflag = CREAD | HUPCL | CLOCAL;
+	char	*s;
+
+	if (options) {
+		baud = simple_strtoul(options, NULL, 10);
+		s = options;
+		while(*s >= '0' && *s <= '9')
+			s++;
+		if (*s) parity = *s++;
+		if (*s) bits   = *s - '0';
+	}
+
+	/*
+	 *	Now construct a cflag setting.
+	 */
+	switch (baud) {
+		case 19200:
+			cflag |= B19200;
+			break;
+		case 38400:
+			cflag |= B38400;
+			break;
+		case 57600:
+			cflag |= B57600;
+			break;
+		case 115200:
+			cflag |= B115200;
+			break;
+		case 9600:
+		default:
+			cflag |= B9600;
+			break;
+	}
+	switch (bits) {
+		case 7:
+			cflag |= CS7;
+			break;
+		default:
+		case 8:
+			cflag |= CS8;
+			break;
+	}
+	switch (parity) {
+		case 'o': case 'O':
+			cflag |= PARODD;
+			break;
+		case 'e': case 'E':
+			cflag |= PARENB;
+			break;
+	}
+	co->cflag = cflag;
+
+	/* XXX: set baud, char, and parity here. */
+	return 0;
+}
+
+static struct console sercons = {
+	"ttyS",
+	serial_console_write,
+	NULL,
+	serial_console_device,
+	serial_console_wait_key,
+	NULL,
+	serial_console_setup,
+	CON_PRINTBUFFER,
+	-1,
+	0,
+	NULL
+};
+
+/*
+ *	Register console.
+ */
+
+void __init serial_console_init(void)
+{
+	register_console(&sercons);
+}
+#endif /* CONFIG_SERIAL_CONSOLE */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)